1
0
Fork 0

Compare commits

...

30 commits

Author SHA1 Message Date
Alexander Yakovlev c23e51d277 Merge remote-tracking branch 'upstream/main' 2024-02-17 09:24:15 +06:00
Lim Chee Aun 49cdba2652 Upgrade dependencies again. Last Preact version was causing weird bugs. 2024-02-17 00:27:21 +08:00
Lim Chee Aun 2f94cb34f6 Fix post content not updating when changed 2024-02-16 17:36:46 +08:00
Lim Chee Aun b7a79c8fdd Better memo for Notification 2024-02-15 18:07:17 +08:00
Lim Chee Aun 2f0d04eca4 Update instances list, fix script bug 2024-02-15 17:53:35 +08:00
Lim Chee Aun daabd85273 Upgrade dependencies 2024-02-15 14:59:11 +08:00
Lim Chee Aun c84ad73d0d More memoization 2024-02-14 17:17:15 +08:00
Lim Chee Aun 3295b1ab96 Remove the need for setStates 2024-02-14 17:16:53 +08:00
Chee Aun 5d7b67a410
Merge pull request #417 from Fastidious/patch-1
Update README.md
2024-02-14 01:28:12 +08:00
Fastidious 4e85f92f4b
Update README.md
Changed URL of my self hosted instance.
2024-02-13 10:08:40 -05:00
Lim Chee Aun 9d80647b11 Upgrade dependencies 2024-02-13 22:05:26 +08:00
Lim Chee Aun 24a481b782 Back to end 2024-02-12 18:59:04 +08:00
Lim Chee Aun 97cce8a828 Slightly faster bg transition 2024-02-12 11:54:47 +08:00
Lim Chee Aun 3c31c56306 Fine-tuning status actions styles 2024-02-12 11:53:59 +08:00
Lim Chee Aun 92f4371041 More granular hover/focus state for status actions 2024-02-11 22:46:21 +08:00
Lim Chee Aun a9d0100087 Stripes if PM 2024-02-11 21:04:30 +08:00
Lim Chee Aun 3fbe11295f Don't use dvh for this 2024-02-10 22:22:25 +08:00
Lim Chee Aun 98f018913d Test change to :focus 2024-02-10 20:21:03 +08:00
Lim Chee Aun 60ca577f9b Slight adjustments to status actions 2024-02-10 12:01:51 +08:00
Lim Chee Aun 1d0d02f39b Different alignment for status action menu 2024-02-10 12:00:40 +08:00
Lim Chee Aun fbd448c152 Add one more smaller text size option 2024-02-09 20:07:16 +08:00
Lim Chee Aun 038b2b2e6b Upgrade vite and dependencies 2024-02-09 20:07:06 +08:00
Lim Chee Aun 169aa2d3d3 Fix boost icon color in new status menu 2024-02-08 01:12:02 +08:00
Lim Chee Aun 9a9667d824 Redesign the context menu 2024-02-06 17:34:26 +08:00
Lim Chee Aun afd9d2cf97 Slight style adjustments 2024-02-06 17:32:17 +08:00
Lim Chee Aun b9c287b29e Don't show icon, just show text for visibility.
Icon, in the end, ain't descriptive enough.
2024-02-06 17:30:58 +08:00
Lim Chee Aun 436277c6b4 Prevent re-render dangerouslySetInnerHTML 2024-02-06 17:30:10 +08:00
Lim Chee Aun 4f28d3cc6d Less bolder bold 2024-02-06 17:28:18 +08:00
Lim Chee Aun 46415b87a6 Show lists containing the account in the menu 2024-02-05 10:17:49 +08:00
Lim Chee Aun 913d923877 Make grouped subsequent hashtag pre-meta more seamless 2024-02-04 19:38:22 +08:00
16 changed files with 1170 additions and 936 deletions

View file

@ -104,7 +104,7 @@ Prerequisites: Node.js 18+
- `npm run build` - Build for production
- `npm run preview` - Preview the production build
- `npm run fetch-instances` - Fetch instances list from [instances.social](https://instances.social/), save it to `src/data/instances.json`
- requires `.env.dev` file with `INSTANCES_SOCIAL_SECRET_TOKEN` variable set
- requires `.env.local` file with `INSTANCES_SOCIAL_SECRET_TOKEN` variable set
- `npm run sourcemap` - Run `source-map-explorer` on the production build
## Tech stack
@ -193,7 +193,7 @@ See documentation for [lingva-translate](https://github.com/thedaviddelta/lingva
These are self-hosted by other wonderful folks.
- [ferengi.one](https://ferengi.one/) by [@david@collantes.social](https://collantes.social/@david)
- [ferengi.one](https://m.ferengi.one/) by [@david@collantes.social](https://collantes.social/@david)
- [phanpy.blaede.family](https://phanpy.blaede.family/) by [@cassidy@blaede.family](https://mastodon.blaede.family/@cassidy)
- [phanpy.mstdn.mx](https://phanpy.mstdn.mx/) by [@maop@mstdn.mx](https://mstdn.mx/@maop)
- [phanpy.vmst.io](https://phanpy.vmst.io/) by [@vmstan@vmst.io](https://vmst.io/@vmstan)

75
package-lock.json generated
View file

@ -27,12 +27,12 @@
"moize": "~6.1.6",
"p-retry": "~6.2.0",
"p-throttle": "~6.1.0",
"preact": "~10.19.3",
"react-hotkeys-hook": "~4.4.4",
"react-intersection-observer": "~9.5.3",
"preact": "~10.19.5",
"react-hotkeys-hook": "~4.5.0",
"react-intersection-observer": "~9.8.0",
"react-quick-pinch-zoom": "~5.1.0",
"react-router-dom": "6.6.2",
"runes2": "~1.1.3",
"runes2": "~1.1.4",
"string-length": "5.0.1",
"swiped-events": "~1.1.9",
"toastify-js": "~1.12.0",
@ -45,14 +45,14 @@
"devDependencies": {
"@preact/preset-vite": "~2.8.1",
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
"postcss": "~8.4.33",
"postcss-dark-theme-class": "~1.1.0",
"postcss": "~8.4.35",
"postcss-dark-theme-class": "~1.2.1",
"postcss-preset-env": "~9.3.0",
"twitter-text": "~3.1.0",
"vite": "~5.0.12",
"vite": "~5.1.3",
"vite-plugin-generate-file": "~0.1.1",
"vite-plugin-html-config": "~1.0.11",
"vite-plugin-pwa": "~0.17.5",
"vite-plugin-pwa": "~0.18.2",
"vite-plugin-remove-console": "~2.2.0",
"workbox-cacheable-response": "~7.0.0",
"workbox-expiration": "~7.0.0",
@ -5933,9 +5933,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"funding": [
{
@ -6157,9 +6157,9 @@
}
},
"node_modules/postcss-dark-theme-class": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-1.1.0.tgz",
"integrity": "sha512-3Njz7Ux1YuS+DDb00rKL4JbcyePEwBi2DPzHiK3Rw8HHaXwLhBSAILtyY09FjRPXj8KRyqOxIEVw4+xdO6aFdg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-1.2.1.tgz",
"integrity": "sha512-EzQMGOcYnE0eMBjfgB+xnamlZ8O02Aiojyg/iv84cpRUdLKZW8ankZWxWUhhIid1OF7yl4G3BeYfE+7CGY2tdQ==",
"dev": true,
"funding": [
{
@ -6672,9 +6672,9 @@
"license": "MIT"
},
"node_modules/preact": {
"version": "10.19.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz",
"integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==",
"version": "10.19.5",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.5.tgz",
"integrity": "sha512-OPELkDmSVbKjbFqF9tgvOowiiQ9TmsJljIzXRyNE8nGiis94pwv1siF78rQkAP1Q1738Ce6pellRg/Ns/CtHqQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@ -6796,21 +6796,26 @@
}
},
"node_modules/react-hotkeys-hook": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.4.tgz",
"integrity": "sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz",
"integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==",
"peerDependencies": {
"react": ">=16.8.1",
"react-dom": ">=16.8.1"
}
},
"node_modules/react-intersection-observer": {
"version": "9.5.3",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.3.tgz",
"integrity": "sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==",
"license": "MIT",
"version": "9.8.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.0.tgz",
"integrity": "sha512-wXHvMQUsTagh3X0Z6jDtGkIXc3VVCd2tjDRYR9kII3GKrZr0XF0xtpfdamo2n8BSF+zzfeeBVOTjxZWpBp9X0g==",
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-is": {
@ -7081,9 +7086,9 @@
}
},
"node_modules/runes2": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.3.tgz",
"integrity": "sha512-sJ/0iVFLne4f2S7cMB1OckBtC9lqkzP5a/wPnDIkbrWzgUsJ+JMQv6y7hk76U7zvbua+je5GltfpsZazUhG05w=="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz",
"integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g=="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
@ -7773,13 +7778,13 @@
}
},
"node_modules/vite": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
"integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
"integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
"postcss": "^8.4.32",
"postcss": "^8.4.35",
"rollup": "^4.2.0"
},
"bin": {
@ -7854,9 +7859,9 @@
}
},
"node_modules/vite-plugin-pwa": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.5.tgz",
"integrity": "sha512-UxRNPiJBzh4tqU/vc8G2TxmrUTzT6BqvSzhszLk62uKsf+npXdvLxGDz9C675f4BJi6MbD2tPnJhi5txlMzxbQ==",
"version": "0.18.2",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.18.2.tgz",
"integrity": "sha512-LVFHHLcRLkP7y5xwAqMmtWQhSw34V2+vk59c18fumejiQPUBar+Au1AnOcVr96hlEWLHXI6BM31QOHq+Rey4EA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",

View file

@ -6,7 +6,7 @@
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"fetch-instances": "env $(cat .env.dev | grep -v \"#\" | xargs) node scripts/fetch-instances-list.js",
"fetch-instances": "env $(cat .env.local | grep -v \"#\" | xargs) node scripts/fetch-instances-list.js",
"sourcemap": "npx source-map-explorer dist/assets/*.js"
},
"dependencies": {
@ -29,12 +29,12 @@
"moize": "~6.1.6",
"p-retry": "~6.2.0",
"p-throttle": "~6.1.0",
"preact": "~10.19.3",
"react-hotkeys-hook": "~4.4.4",
"react-intersection-observer": "~9.5.3",
"preact": "~10.19.5",
"react-hotkeys-hook": "~4.5.0",
"react-intersection-observer": "~9.8.0",
"react-quick-pinch-zoom": "~5.1.0",
"react-router-dom": "6.6.2",
"runes2": "~1.1.3",
"runes2": "~1.1.4",
"string-length": "5.0.1",
"swiped-events": "~1.1.9",
"toastify-js": "~1.12.0",
@ -47,14 +47,14 @@
"devDependencies": {
"@preact/preset-vite": "~2.8.1",
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
"postcss": "~8.4.33",
"postcss-dark-theme-class": "~1.1.0",
"postcss": "~8.4.35",
"postcss-dark-theme-class": "~1.2.1",
"postcss-preset-env": "~9.3.0",
"twitter-text": "~3.1.0",
"vite": "~5.0.12",
"vite": "~5.1.3",
"vite-plugin-generate-file": "~0.1.1",
"vite-plugin-html-config": "~1.0.11",
"vite-plugin-pwa": "~0.17.5",
"vite-plugin-pwa": "~0.18.2",
"vite-plugin-remove-console": "~2.2.0",
"workbox-cacheable-response": "~7.0.0",
"workbox-expiration": "~7.0.0",

View file

@ -915,7 +915,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
text-decoration-line: none;
color: inherit;
user-select: none;
transition: background-color 0.2s ease-out;
transition: background-color 0.1s ease-out;
-webkit-tap-highlight-color: transparent;
animation: appear 0.2s ease-out;
-webkit-touch-callout: none;
@ -928,9 +928,11 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
-webkit-touch-callout: none;
}
}
:is(.status-link, .status-focus):is(:focus, .is-active) {
background-color: var(--link-bg-hover-color);
outline-offset: -2px;
@media not (pointer: coarse) {
:is(.status-link, .status-focus):is(:focus, .is-active) {
background-color: var(--link-bg-hover-color);
outline-offset: -2px;
}
}
@media (hover: hover) {
.status-link:hover {
@ -1803,7 +1805,7 @@ body > .szh-menu-container {
align-items: center;
line-height: 1.1;
padding: 8px 16px !important;
transition: all 0.1s ease-in-out;
/* transition: all 0.1s ease-in-out; */
text-decoration: none;
white-space: nowrap;
overflow: hidden;
@ -1850,6 +1852,7 @@ body > .szh-menu-container {
}
.szh-menu__divider {
background-color: var(--divider-color);
margin-block: 4px;
}
.szh-menu .szh-menu__item .menu-grow {
flex-grow: 1;
@ -1926,6 +1929,52 @@ body > .szh-menu-container {
opacity: 1;
}
.szh-menu {
.menu-control-group-horizontal {
display: grid;
/* auto columns */
grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
.szh-menu__item {
display: flex;
flex-direction: column;
padding: 8px !important;
gap: 2px;
.icon {
opacity: 1;
+ span {
font-size: 80%;
/* line-height: 1.2; */
width: 100%;
text-align: center;
opacity: 0.5;
text-overflow: clip;
mask-image: linear-gradient(to left, transparent, black 16px);
}
}
}
}
.menu-control-group-horizontal:first-child,
li[aria-hidden='true'] + .menu-control-group-horizontal {
margin-top: -8px;
margin-bottom: -4px;
.szh-menu__item {
padding-block: 12px !important;
}
> [class^='szh-menu']:first-child {
border-top-left-radius: 8px;
}
> [class^='szh-menu']:last-child {
border-top-right-radius: 8px;
}
}
}
.szh-menu
.szh-menu__item--type-checkbox:not(.szh-menu__item--disabled):not(
.szh-menu__item--hover

View file

@ -100,4 +100,5 @@ export const ICONS = {
building: () => import('@iconify-icons/mingcute/building-5-line'),
history: () => import('@iconify-icons/mingcute/history-2-line'),
document: () => import('@iconify-icons/mingcute/document-line'),
'arrows-right': () => import('@iconify-icons/mingcute/arrows-right-line'),
};

View file

@ -917,6 +917,7 @@ function RelatedActions({
const [showTranslatedBio, setShowTranslatedBio] = useState(false);
const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
const [showPrivateNoteModal, setShowPrivateNoteModal] = useState(false);
const [lists, setLists] = useState([]);
return (
<>
@ -976,6 +977,22 @@ function RelatedActions({
<Icon icon="more" size="l" alt="More" />
</button>
}
onMenuChange={(e) => {
if (following && e.open) {
// Fetch lists that have this account
(async () => {
try {
const lists = await currentMasto.v1.accounts
.$select(accountID.current)
.lists.list();
console.log('fetched account lists', lists);
setLists(lists);
} catch (e) {
console.error(e);
}
})();
}
}}
>
{currentAuthenticated && !isSelf && (
<>
@ -1017,7 +1034,20 @@ function RelatedActions({
}}
>
<Icon icon="list" />
<span>Add/remove from Lists</span>
{lists.length ? (
<>
<small class="menu-grow">
Add/Remove from Lists
<br />
<span class="more-insignificant">
{lists.map((list) => list.title).join(', ')}
</span>
</small>
<small class="more-insignificant">{lists.length}</small>
</>
) : (
<span>Add/Remove from Lists</span>
)}
</MenuItem>
)}
<MenuDivider />

View file

@ -273,7 +273,7 @@ function MediaModal({
<span>
<Menu2
overflow="auto"
align="center"
align="end"
position="anchor"
gap={4}
menuClassName="glass-menu"

View file

@ -2,6 +2,10 @@
color: inherit;
text-decoration: none;
display: inline;
b {
font-weight: 500;
}
}
.name-text.show-acct {
display: inline-block;

View file

@ -357,4 +357,6 @@ function TruncatedLink(props) {
return <Link {...props} data-read-more="Read more →" ref={ref} />;
}
export default memo(Notification);
export default memo(Notification, (oldProps, newProps) => {
return oldProps.notification?.id === newProps.notification?.id;
});

View file

@ -20,6 +20,16 @@
var(--hashtag-faded-color),
transparent min(160px, 50%)
);
.timeline-item-container:not(.timeline-item-container-start) & {
background: radial-gradient(
ellipse at 0 1.2em,
var(--hashtag-faded-color),
transparent max(160px, 50%)
);
background-size: 100% 3em;
background-repeat: no-repeat;
}
}
.status-reply-to {
background: linear-gradient(
@ -351,6 +361,10 @@
background-repeat: no-repeat;
background-size: 100% calc(100% - var(--top-padding) / 2);
&.visibility-direct {
background-image: var(--yellow-stripes);
}
> * {
opacity: 0.65;
transition: opacity 1s ease-out;
@ -737,7 +751,6 @@
.timeline-deck .status .content {
max-height: 50vh;
max-height: 50dvh;
overflow: clip;
position: relative;
}
@ -1826,6 +1839,53 @@ a.card:is(:hover, :focus):visited {
animation: bookmarked 1s ease-in-out;
}
/* STATUS MENU */
.status-menu {
.szh-menu__item,
.szh-menu__submenu {
.icon + span {
transition: opacity 1s ease;
}
&.szh-menu__item--hover .icon + span {
opacity: 1;
}
&.checked {
&:not(.szh-menu__item--hover) {
color: var(--checked-color) !important;
}
.szh-menu__item:not(.szh-menu__item--hover) {
color: inherit;
}
.icon + span {
opacity: 1;
}
&,
& .szh-menu__item {
box-shadow: inset 0 -2px 0 var(--checked-color),
inset 0 -16px 8px -16px var(--checked-color);
}
&:has(.szh-menu__item) {
box-shadow: unset;
}
}
}
.menu-reblog.checked {
--checked-color: var(--reblog-color);
}
.menu-favourite.checked {
--checked-color: var(--favourite-color);
}
.menu-bookmark.checked {
--checked-color: var(--link-color);
}
}
/* ENHANCED CONTENT */
.status .content pre {
@ -1853,8 +1913,8 @@ a.card:is(:hover, :focus):visited {
.status-actions {
display: flex;
position: absolute;
top: 4px;
right: 4px;
top: -6px;
right: 8px;
background-color: var(--bg-color);
border-radius: 8px;
z-index: 1;
@ -1863,10 +1923,18 @@ a.card:is(:hover, :focus):visited {
overflow: clip;
opacity: 0;
pointer-events: none;
transform: translateX(8px);
transform: translate3d(0, 6px, 0);
transform-origin: right center;
transition: all 0.15s ease-out 0.3s, border-color 0.3s ease-out;
.timeline.contextual .replies[data-comments-level='4'] & {
top: 0;
}
@media (hover: hover) {
transition: border-color 0.3s ease-out;
}
button.plain {
color: var(--text-insignificant-color);
backdrop-filter: none;
@ -1902,17 +1970,22 @@ a.card:is(:hover, :focus):visited {
border-color: var(--outline-hover-color);
}
&:hover,
.status:hover &:not(:hover),
.status:focus &,
&.open {
opacity: 1;
pointer-events: auto;
transform: translateX(0);
}
@media (pointer: coarse) {
.status:has(&):hover {
transition: background-color 0.1s ease-out 0.3s;
background-color: var(--bg-faded-blur-color);
& {
border-color: var(--outline-hover-color);
}
}
@media (pointer: fine), (hover: hover) {
.status:hover & {
opacity: 1;
pointer-events: auto;
transform: translateX(0);
}
}
@ -1928,6 +2001,23 @@ a.card:is(:hover, :focus):visited {
}
}
}
.timeline.contextual .descendant .status {
--bg-gradient: linear-gradient(
-140deg,
var(--bg-faded-color),
transparent 75%
);
&:focus {
background-image: var(--bg-gradient);
}
@media (pointer: fine), (hover: hover) {
&:hover {
background-image: var(--bg-gradient);
}
}
}
/* BADGE */

View file

@ -106,6 +106,43 @@ function getPostText(status) {
);
}
const PostContent = memo(
({ post, instance, previewMode }) => {
const { content, emojis, language, mentions, url } = post;
return (
<div
lang={language}
dir="auto"
class="inner-content"
onClick={handleContentLinks({
mentions,
instance,
previewMode,
statusURL: url,
})}
dangerouslySetInnerHTML={{
__html: enhanceContent(content, {
emojis,
postEnhanceDOM: (dom) => {
// Remove target="_blank" from links
dom.querySelectorAll('a.u-url[target="_blank"]').forEach((a) => {
if (!/http/i.test(a.innerText.trim())) {
a.removeAttribute('target');
}
});
},
}),
}}
/>
);
},
(oldProps, newProps) => {
const { post: oldPost } = oldProps;
const { post: newPost } = newProps;
return oldPost.content === newPost.content;
},
);
function Status({
statusID,
status,
@ -645,82 +682,34 @@ function Status({
const actionsRef = useRef();
const StatusMenuItems = (
<>
{!isSizeLarge && (
{isSizeLarge && (
<>
<MenuHeader>
<span class="ib">
<Icon icon={visibilityIconsMap[visibility]} size="s" />{' '}
<span>{visibilityText[visibility]}</span>
</span>{' '}
<span class="ib">
{repliesCount > 0 && (
<span>
<Icon icon="comment2" alt="Replies" size="s" />{' '}
<span>{shortenNumber(repliesCount)}</span>
</span>
)}{' '}
{reblogsCount > 0 && (
<span>
<Icon icon="rocket" alt="Boosts" size="s" />{' '}
<span>{shortenNumber(reblogsCount)}</span>
</span>
)}{' '}
{favouritesCount > 0 && (
<span>
<Icon icon="heart" alt="Likes" size="s" />{' '}
<span>{shortenNumber(favouritesCount)}</span>
</span>
)}
</span>
<br />
{createdDateText}
</MenuHeader>
<MenuLink
to={instance ? `/${instance}/s/${id}` : `/s/${id}`}
onClick={(e) => {
onStatusLinkClick(e, status);
<MenuItem
onClick={() => {
states.showGenericAccounts = {
heading: 'Boosted/Liked by…',
fetchAccounts: fetchBoostedLikedByAccounts,
instance,
showReactions: true,
};
}}
>
<Icon icon="arrow-right" />
<span>View post by @{username || acct}</span>
</MenuLink>
<Icon icon="react" />
<span>
Boosted/Liked by<span class="more-insignificant"></span>
</span>
</MenuItem>
</>
)}
{!!editedAt && (
<MenuItem
onClick={() => {
setShowEdited(id);
}}
>
<Icon icon="history" />
<span>
Show Edit History
<br />
<small class="more-insignificant">Edited: {editedDateText}</small>
</span>
</MenuItem>
)}
{(!isSizeLarge || !!editedAt) && <MenuDivider />}
{isSizeLarge && (
<MenuItem
onClick={() => {
states.showGenericAccounts = {
heading: 'Boosted/Liked by…',
fetchAccounts: fetchBoostedLikedByAccounts,
instance,
showReactions: true,
};
}}
>
<Icon icon="react" />
<span>
Boosted/Liked by<span class="more-insignificant"></span>
</span>
</MenuItem>
)}
{!isSizeLarge && sameInstance && (
<>
<div class="menu-horizontal">
<div class="menu-control-group-horizontal status-menu">
<MenuItem onClick={replyStatus}>
<Icon icon="comment" />
<span>
{repliesCount > 0 ? shortenNumber(repliesCount) : 'Reply'}
</span>
</MenuItem>
<MenuConfirm
subMenu
confirmLabel={
@ -729,6 +718,7 @@ function Status({
<span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
</>
}
className={`menu-reblog ${reblogged ? 'checked' : ''}`}
menuFooter={
mediaNoDesc &&
!reblogged && (
@ -752,13 +742,14 @@ function Status({
} catch (e) {}
}}
>
<Icon
icon="rocket"
style={{
color: reblogged && 'var(--reblog-color)',
}}
/>
<span>{reblogged ? 'Unboost' : 'Boost…'}</span>
<Icon icon="rocket" />
<span>
{reblogsCount > 0
? shortenNumber(reblogsCount)
: reblogged
? 'Unboost'
: 'Boost…'}
</span>
</MenuConfirm>
<MenuItem
onClick={() => {
@ -773,20 +764,16 @@ function Status({
}
} catch (e) {}
}}
className={`menu-favourite ${favourited ? 'checked' : ''}`}
>
<Icon
icon="heart"
style={{
color: favourited && 'var(--favourite-color)',
}}
/>
<span>{favourited ? 'Unlike' : 'Like'}</span>
</MenuItem>
</div>
<div class="menu-horizontal">
<MenuItem onClick={replyStatus}>
<Icon icon="comment" />
<span>Reply</span>
<Icon icon="heart" />
<span>
{favouritesCount > 0
? shortenNumber(favouritesCount)
: favourited
? 'Unlike'
: 'Like'}
</span>
</MenuItem>
<MenuItem
onClick={() => {
@ -801,18 +788,15 @@ function Status({
}
} catch (e) {}
}}
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
>
<Icon
icon="bookmark"
style={{
color: bookmarked && 'var(--link-color)',
}}
/>
<Icon icon="bookmark" />
<span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>
</MenuItem>
</div>
</>
)}
{(enableTranslate || !language || differentLanguage) && <MenuDivider />}
{enableTranslate ? (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuItem
@ -863,7 +847,46 @@ function Status({
</div>
)
)}
{((!isSizeLarge && sameInstance) || enableTranslate) && <MenuDivider />}
{!isSizeLarge ||
((enableTranslate || !language || differentLanguage) && (
<MenuDivider />
))}
{!isSizeLarge && (
<>
<MenuDivider />
<MenuLink
to={instance ? `/${instance}/s/${id}` : `/s/${id}`}
onClick={(e) => {
onStatusLinkClick(e, status);
}}
>
<Icon icon="arrows-right" />
<small>
View post by @{username || acct}
<br />
<span class="more-insignificant">
{visibilityText[visibility]} {createdDateText}
</span>
</small>
</MenuLink>
</>
)}
{!!editedAt && (
<>
<MenuItem
onClick={() => {
setShowEdited(id);
}}
>
<Icon icon="history" />
<small>
Show Edit History
<br />
<span class="more-insignificant">Edited: {editedDateText}</span>
</small>
</MenuItem>
</>
)}
<MenuItem href={url} target="_blank">
<Icon icon="external" />
<small class="menu-double-lines">{nicePostURL(url)}</small>
@ -1419,9 +1442,10 @@ function Status({
anchorRef: {
current: e.currentTarget,
},
align: 'end',
direction: 'bottom',
gap: 4,
align: 'start',
direction: 'left',
gap: 0,
shift: -8,
});
setIsContextMenuOpen('actions-bar');
}}
@ -1680,59 +1704,10 @@ function Status({
ref={contentRef}
data-read-more={readMoreText}
>
<div
lang={language}
dir="auto"
class="inner-content"
onClick={handleContentLinks({
mentions,
instance,
previewMode,
statusURL: url,
})}
dangerouslySetInnerHTML={{
__html: enhanceContent(content, {
emojis,
postEnhanceDOM: (dom) => {
// Remove target="_blank" from links
dom
.querySelectorAll('a.u-url[target="_blank"]')
.forEach((a) => {
if (!/http/i.test(a.innerText.trim())) {
a.removeAttribute('target');
}
});
// if (previewMode) return;
// Unfurl Mastodon links
// Array.from(
// dom.querySelectorAll(
// 'a[href]:not(.u-url):not(.mention):not(.hashtag)',
// ),
// )
// .filter((a) => {
// const url = a.href;
// const isPostItself =
// url === status.url || url === status.uri;
// return !isPostItself && isMastodonLinkMaybe(url);
// })
// .forEach((a, i) => {
// unfurlMastodonLink(currentInstance, a.href).then(
// (result) => {
// if (!result) return;
// a.removeAttribute('target');
// if (!sKey) return;
// if (!Array.isArray(states.statusQuotes[sKey])) {
// states.statusQuotes[sKey] = [];
// }
// if (!states.statusQuotes[sKey][i]) {
// states.statusQuotes[sKey].splice(i, 0, result);
// }
// },
// );
// });
},
}),
}}
<PostContent
post={status}
instance={instance}
previewMode={previewMode}
/>
<QuoteStatuses id={id} instance={instance} level={quoted} />
</div>
@ -1870,10 +1845,11 @@ function Status({
<span class="status-deleted-tag">Deleted</span>
) : (
<>
<Icon
{/* <Icon
icon={visibilityIconsMap[visibility]}
alt={visibilityText[visibility]}
/>{' '}
/> */}
<span>{visibilityText[visibility]}</span> &bull;{' '}
<a href={url} target="_blank" rel="noopener noreferrer">
<time
class="created"

View file

@ -1,3 +1,4 @@
import { memo } from 'preact/compat';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer';
@ -243,9 +244,9 @@ function Timeline({
({
scrollDirection,
nearReachStart,
nearReachEnd,
// nearReachEnd,
reachStart,
reachEnd,
// reachEnd,
}) => {
// setHiddenUI(scrollDirection === 'end' && !nearReachEnd);
if (headerRef.current) {
@ -516,190 +517,205 @@ function Timeline({
);
}
function TimelineItem({
status,
instance,
useItemID,
// allowFilters,
filterContext,
view,
showFollowedTags,
showReplyParent,
}) {
const { id: statusID, reblog, items, type, _pinned } = status;
if (_pinned) useItemID = false;
const actualStatusID = reblog?.id || statusID;
const url = instance
? `/${instance}/s/${actualStatusID}`
: `/s/${actualStatusID}`;
let title = '';
if (type === 'boosts') {
title = `${items.length} Boosts`;
} else if (type === 'pinned') {
title = 'Pinned posts';
}
const isCarousel = type === 'boosts' || type === 'pinned';
if (items) {
const fItems = filteredItems(items, filterContext);
if (isCarousel) {
// Here, we don't hide filtered posts, but we sort them last
fItems.sort((a, b) => {
// if (a._filtered && !b._filtered) {
// return 1;
// }
// if (!a._filtered && b._filtered) {
// return -1;
// }
const aFiltered = isFiltered(a.filtered, filterContext);
const bFiltered = isFiltered(b.filtered, filterContext);
if (aFiltered && !bFiltered) {
return 1;
}
if (!aFiltered && bFiltered) {
return -1;
}
return 0;
const TimelineItem = memo(
({
status,
instance,
useItemID,
// allowFilters,
filterContext,
view,
showFollowedTags,
showReplyParent,
}) => {
console.debug('RENDER TimelineItem', status.id);
const { id: statusID, reblog, items, type, _pinned } = status;
if (_pinned) useItemID = false;
const actualStatusID = reblog?.id || statusID;
const url = instance
? `/${instance}/s/${actualStatusID}`
: `/s/${actualStatusID}`;
let title = '';
if (type === 'boosts') {
title = `${items.length} Boosts`;
} else if (type === 'pinned') {
title = 'Pinned posts';
}
const isCarousel = type === 'boosts' || type === 'pinned';
if (items) {
const fItems = filteredItems(items, filterContext);
if (isCarousel) {
// Here, we don't hide filtered posts, but we sort them last
fItems.sort((a, b) => {
// if (a._filtered && !b._filtered) {
// return 1;
// }
// if (!a._filtered && b._filtered) {
// return -1;
// }
const aFiltered = isFiltered(a.filtered, filterContext);
const bFiltered = isFiltered(b.filtered, filterContext);
if (aFiltered && !bFiltered) {
return 1;
}
if (!aFiltered && bFiltered) {
return -1;
}
return 0;
});
return (
<li key={`timeline-${statusID}`} class="timeline-item-carousel">
<StatusCarousel title={title} class={`${type}-carousel`}>
{fItems.map((item) => {
const { id: statusID, reblog, _pinned } = item;
const actualStatusID = reblog?.id || statusID;
const url = instance
? `/${instance}/s/${actualStatusID}`
: `/s/${actualStatusID}`;
if (_pinned) useItemID = false;
return (
<li key={statusID}>
<Link
class="status-carousel-link timeline-item-alt"
to={url}
>
{useItemID ? (
<Status
statusID={statusID}
instance={instance}
size="s"
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
/>
) : (
<Status
status={item}
instance={instance}
size="s"
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
})}
</StatusCarousel>
</li>
);
}
const manyItems = fItems.length > 3;
return fItems.map((item, i) => {
const { id: statusID, _differentAuthor } = item;
const url = instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`;
const isMiddle = i > 0 && i < fItems.length - 1;
const isSpoiler = item.sensitive && !!item.spoilerText;
const showCompact =
(!_differentAuthor && isSpoiler && i > 0) ||
(manyItems &&
isMiddle &&
(type === 'thread' ||
(type === 'conversation' &&
!_differentAuthor &&
!fItems[i - 1]._differentAuthor &&
!fItems[i + 1]._differentAuthor)));
const isStart = i === 0;
const isEnd = i === fItems.length - 1;
return (
<li
key={`timeline-${statusID}`}
class={`timeline-item-container timeline-item-container-type-${type} timeline-item-container-${
isStart ? 'start' : isEnd ? 'end' : 'middle'
} ${_differentAuthor ? 'timeline-item-diff-author' : ''}`}
>
<Link class="status-link timeline-item" to={url}>
{showCompact ? (
<TimelineStatusCompact status={item} instance={instance} />
) : useItemID ? (
<Status
statusID={statusID}
instance={instance}
enableCommentHint={isEnd}
showFollowedTags={showFollowedTags}
// allowFilters={allowFilters}
/>
) : (
<Status
status={item}
instance={instance}
enableCommentHint={isEnd}
showFollowedTags={showFollowedTags}
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
});
return (
<li key={`timeline-${statusID}`} class="timeline-item-carousel">
<StatusCarousel title={title} class={`${type}-carousel`}>
{fItems.map((item) => {
const { id: statusID, reblog, _pinned } = item;
const actualStatusID = reblog?.id || statusID;
const url = instance
? `/${instance}/s/${actualStatusID}`
: `/s/${actualStatusID}`;
if (_pinned) useItemID = false;
return (
<li key={statusID}>
<Link class="status-carousel-link timeline-item-alt" to={url}>
{useItemID ? (
<Status
statusID={statusID}
instance={instance}
size="s"
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
/>
) : (
<Status
status={item}
instance={instance}
size="s"
contentTextWeight
enableCommentHint
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
})}
</StatusCarousel>
</li>
}
const itemKey = `timeline-${statusID + _pinned}`;
if (view === 'media') {
return useItemID ? (
<MediaPost
class="timeline-item"
parent="li"
key={itemKey}
statusID={statusID}
instance={instance}
// allowFilters={allowFilters}
/>
) : (
<MediaPost
class="timeline-item"
parent="li"
key={itemKey}
status={status}
instance={instance}
// allowFilters={allowFilters}
/>
);
}
const manyItems = fItems.length > 3;
return fItems.map((item, i) => {
const { id: statusID, _differentAuthor } = item;
const url = instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`;
const isMiddle = i > 0 && i < fItems.length - 1;
const isSpoiler = item.sensitive && !!item.spoilerText;
const showCompact =
(!_differentAuthor && isSpoiler && i > 0) ||
(manyItems &&
isMiddle &&
(type === 'thread' ||
(type === 'conversation' &&
!_differentAuthor &&
!fItems[i - 1]._differentAuthor &&
!fItems[i + 1]._differentAuthor)));
const isStart = i === 0;
const isEnd = i === fItems.length - 1;
return (
<li
key={`timeline-${statusID}`}
class={`timeline-item-container timeline-item-container-type-${type} timeline-item-container-${
isStart ? 'start' : isEnd ? 'end' : 'middle'
} ${_differentAuthor ? 'timeline-item-diff-author' : ''}`}
>
<Link class="status-link timeline-item" to={url}>
{showCompact ? (
<TimelineStatusCompact status={item} instance={instance} />
) : useItemID ? (
<Status
statusID={statusID}
instance={instance}
enableCommentHint={isEnd}
showFollowedTags={showFollowedTags}
// allowFilters={allowFilters}
/>
) : (
<Status
status={item}
instance={instance}
enableCommentHint={isEnd}
showFollowedTags={showFollowedTags}
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
});
}
const itemKey = `timeline-${statusID + _pinned}`;
if (view === 'media') {
return useItemID ? (
<MediaPost
class="timeline-item"
parent="li"
key={itemKey}
statusID={statusID}
instance={instance}
// allowFilters={allowFilters}
/>
) : (
<MediaPost
class="timeline-item"
parent="li"
key={itemKey}
status={status}
instance={instance}
// allowFilters={allowFilters}
/>
return (
<li key={itemKey}>
<Link class="status-link timeline-item" to={url}>
{useItemID ? (
<Status
statusID={statusID}
instance={instance}
enableCommentHint
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
/>
) : (
<Status
status={status}
instance={instance}
enableCommentHint
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
}
return (
<li key={itemKey}>
<Link class="status-link timeline-item" to={url}>
{useItemID ? (
<Status
statusID={statusID}
instance={instance}
enableCommentHint
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
/>
) : (
<Status
status={status}
instance={instance}
enableCommentHint
showFollowedTags={showFollowedTags}
showReplyParent={showReplyParent}
// allowFilters={allowFilters}
/>
)}
</Link>
</li>
);
}
},
(oldProps, newProps) => {
const oldID = (oldProps.status?.id || '').toString();
const newID = (newProps.status?.id || '').toString();
return (
oldID === newID &&
oldProps.instance === newProps.instance &&
oldProps.view === newProps.view
);
},
);
function StatusCarousel({ title, class: className, children }) {
const carouselRef = useRef();

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,7 @@ import states from '../utils/states';
import store from '../utils/store';
const DEFAULT_TEXT_SIZE = 16;
const TEXT_SIZES = [15, 16, 17, 18, 19, 20];
const TEXT_SIZES = [14, 15, 16, 17, 18, 19, 20];
const {
PHANPY_WEBSITE: WEBSITE,
PHANPY_PRIVACY_POLICY_URL: PRIVACY_POLICY_URL,

View file

@ -17,15 +17,22 @@ export default function useScrollFn(
deps,
) {
if (!callback) return;
const [scrollDirection, setScrollDirection] = useState(null);
const [reachStart, setReachStart] = useState(false);
const [reachEnd, setReachEnd] = useState(false);
const [nearReachStart, setNearReachStart] = useState(false);
const [nearReachEnd, setNearReachEnd] = useState(false);
// const [scrollDirection, setScrollDirection] = useState(null);
// const [reachStart, setReachStart] = useState(false);
// const [reachEnd, setReachEnd] = useState(false);
// const [nearReachStart, setNearReachStart] = useState(false);
// const [nearReachEnd, setNearReachEnd] = useState(false);
const isVertical = direction === 'vertical';
const previousScrollStart = useRef(null);
const scrollDirection = useRef(null);
const onScroll = useThrottledCallback(() => {
// let scrollDirection = null;
let reachStart = false;
let reachEnd = false;
let nearReachStart = false;
let nearReachEnd = false;
const scrollableElement = scrollableRef.current;
const {
scrollTop,
@ -60,18 +67,33 @@ export default function useScrollFn(
? scrollThresholdEnd
: scrollThresholdStart)
) {
setScrollDirection(
previousScrollStart.current < scrollStart ? 'end' : 'start',
);
// setScrollDirection(
// previousScrollStart.current < scrollStart ? 'end' : 'start',
// );
scrollDirection.current =
previousScrollStart.current < scrollStart ? 'end' : 'start';
previousScrollStart.current = scrollStart;
}
setReachStart(scrollStart <= 0);
setReachEnd(scrollStart + clientDimension >= scrollDimension);
setNearReachStart(scrollStart <= distanceFromStartPx);
setNearReachEnd(
scrollStart + clientDimension >= scrollDimension - distanceFromEndPx,
);
// setReachStart(scrollStart <= 0);
// setReachEnd(scrollStart + clientDimension >= scrollDimension);
// setNearReachStart(scrollStart <= distanceFromStartPx);
// setNearReachEnd(
// scrollStart + clientDimension >= scrollDimension - distanceFromEndPx,
// );
reachStart = scrollStart <= 0;
reachEnd = scrollStart + clientDimension >= scrollDimension;
nearReachStart = scrollStart <= distanceFromStartPx;
nearReachEnd =
scrollStart + clientDimension >= scrollDimension - distanceFromEndPx;
callback({
scrollDirection: scrollDirection.current,
reachStart,
reachEnd,
nearReachStart,
nearReachEnd,
});
}, 500);
useLayoutEffect(() => {
@ -88,25 +110,26 @@ export default function useScrollFn(
distanceFromEnd,
scrollThresholdStart,
scrollThresholdEnd,
]);
useEffect(() => {
callback({
scrollDirection,
reachStart,
reachEnd,
nearReachStart,
nearReachEnd,
});
}, [
scrollDirection,
reachStart,
reachEnd,
nearReachStart,
nearReachEnd,
...deps,
]);
// useEffect(() => {
// callback({
// scrollDirection,
// reachStart,
// reachEnd,
// nearReachStart,
// nearReachEnd,
// });
// }, [
// scrollDirection,
// reachStart,
// reachEnd,
// nearReachStart,
// nearReachEnd,
// ...deps,
// ]);
useEffect(() => {
if (init && scrollableRef.current) {
queueMicrotask(() => {

View file

@ -46,6 +46,9 @@ export default defineConfig({
server: {
host: true,
},
css: {
preprocessorMaxWorkers: 1,
},
plugins: [
preact(),
splitVendorChunkPlugin(),