diff --git a/src/components/compose-button.jsx b/src/components/compose-button.jsx index fdc29d11..5579c51f 100644 --- a/src/components/compose-button.jsx +++ b/src/components/compose-button.jsx @@ -63,15 +63,22 @@ export default function ComposeButton() { return; } + const composeDataElements = document.querySelectorAll('data.compose-data'); + // If there's a lot of them, ignore + const opts = + composeDataElements.length === 1 + ? JSON.parse(composeDataElements[0].value) + : undefined; + if (e.shiftKey) { - const newWin = openCompose(); + const newWin = openCompose(opts); if (!newWin) { - states.showCompose = true; + states.showCompose = opts || true; } } else { openOSK(); - states.showCompose = true; + states.showCompose = opts || true; } } diff --git a/src/components/compose.jsx b/src/components/compose.jsx index bdf6006c..98befbb8 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -287,8 +287,8 @@ function Compose({ const focusTextarea = () => { setTimeout(() => { if (!textareaRef.current) return; - // status starts with newline, focus on first position - if (draftStatus?.status?.startsWith?.('\n')) { + // status starts with newline or space, focus on first position + if (/^\n|\s/.test(draftStatus?.status)) { textareaRef.current.selectionStart = 0; textareaRef.current.selectionEnd = 0; } diff --git a/src/locales/en.po b/src/locales/en.po index 3b537ff2..ed632cb4 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -100,7 +100,7 @@ msgstr "Following" #: src/components/account-info.jsx:454 #: src/components/account-info.jsx:856 -#: src/pages/account-statuses.jsx:482 +#: src/pages/account-statuses.jsx:483 #: src/pages/search.jsx:344 #: src/pages/search.jsx:491 msgid "Posts" @@ -116,9 +116,9 @@ msgstr "" #: src/components/status.jsx:1978 #: src/components/status.jsx:2599 #: src/components/status.jsx:2602 -#: src/pages/account-statuses.jsx:526 +#: src/pages/account-statuses.jsx:527 #: src/pages/accounts.jsx:118 -#: src/pages/hashtag.jsx:202 +#: src/pages/hashtag.jsx:203 #: src/pages/list.jsx:171 #: src/pages/public.jsx:116 #: src/pages/scheduled-posts.jsx:89 @@ -314,7 +314,7 @@ msgid "Don't feature on profile" msgstr "Don't feature on profile" #: src/components/account-info.jsx:1406 -#: src/pages/hashtag.jsx:331 +#: src/pages/hashtag.jsx:333 msgid "Feature on profile" msgstr "" @@ -440,7 +440,7 @@ msgstr "" #: src/components/account-info.jsx:1885 #: src/components/account-info.jsx:1889 -#: src/pages/hashtag.jsx:264 +#: src/pages/hashtag.jsx:265 msgid "Follow" msgstr "" @@ -604,19 +604,19 @@ msgstr "" msgid "Home" msgstr "" -#: src/components/compose-button.jsx:141 +#: src/components/compose-button.jsx:148 #: src/compose.jsx:38 msgid "Compose" msgstr "" -#: src/components/compose-button.jsx:168 +#: src/components/compose-button.jsx:175 #: src/components/nav-menu.jsx:265 #: src/pages/scheduled-posts.jsx:31 #: src/pages/scheduled-posts.jsx:78 msgid "Scheduled Posts" msgstr "Scheduled Posts" -#: src/components/compose-button.jsx:181 +#: src/components/compose-button.jsx:188 msgid "Add to thread" msgstr "Add to thread" @@ -1465,7 +1465,7 @@ msgstr "" #: src/pages/account-statuses.jsx:329 #: src/pages/filters.jsx:55 #: src/pages/filters.jsx:94 -#: src/pages/hashtag.jsx:342 +#: src/pages/hashtag.jsx:344 msgid "Filters" msgstr "" @@ -1949,7 +1949,7 @@ msgid "e.g. PixelArt (Max 5, space-separated)" msgstr "" #: src/components/shortcuts-settings.jsx:117 -#: src/pages/hashtag.jsx:358 +#: src/pages/hashtag.jsx:360 msgid "Media only" msgstr "" @@ -2662,31 +2662,31 @@ msgstr "" msgid "Showing posts in {0}" msgstr "Showing posts in {0}" -#: src/pages/account-statuses.jsx:503 +#: src/pages/account-statuses.jsx:504 msgid "Nothing to see here yet." msgstr "" -#: src/pages/account-statuses.jsx:504 +#: src/pages/account-statuses.jsx:505 #: src/pages/public.jsx:99 #: src/pages/trending.jsx:452 msgid "Unable to load posts" msgstr "" -#: src/pages/account-statuses.jsx:545 -#: src/pages/account-statuses.jsx:575 +#: src/pages/account-statuses.jsx:546 +#: src/pages/account-statuses.jsx:576 msgid "Unable to fetch account info" msgstr "" #. placeholder {0}: accountInstance ? ( <> {' '} ({punycode.toUnicode(accountInstance)}) ) : null -#: src/pages/account-statuses.jsx:552 +#: src/pages/account-statuses.jsx:553 msgid "Switch to account's instance {0}" msgstr "" -#: src/pages/account-statuses.jsx:582 +#: src/pages/account-statuses.jsx:583 msgid "Switch to my instance (<0>{currentInstance})" msgstr "" -#: src/pages/account-statuses.jsx:644 +#: src/pages/account-statuses.jsx:656 msgid "Month" msgstr "" @@ -3173,90 +3173,90 @@ msgstr "" msgid "{hashtagTitle}" msgstr "" -#: src/pages/hashtag.jsx:184 +#: src/pages/hashtag.jsx:185 msgid "No one has posted anything with this tag yet." msgstr "" -#: src/pages/hashtag.jsx:185 +#: src/pages/hashtag.jsx:186 msgid "Unable to load posts with this tag" msgstr "" -#: src/pages/hashtag.jsx:211 +#: src/pages/hashtag.jsx:212 msgid "Unfollow #{hashtag}?" msgstr "Unfollow #{hashtag}?" -#: src/pages/hashtag.jsx:226 +#: src/pages/hashtag.jsx:227 msgid "Unfollowed #{hashtag}" msgstr "" -#: src/pages/hashtag.jsx:241 +#: src/pages/hashtag.jsx:242 msgid "Followed #{hashtag}" msgstr "" -#: src/pages/hashtag.jsx:257 +#: src/pages/hashtag.jsx:258 msgid "Following…" msgstr "" -#: src/pages/hashtag.jsx:285 +#: src/pages/hashtag.jsx:287 msgid "Unfeatured on profile" msgstr "" -#: src/pages/hashtag.jsx:299 +#: src/pages/hashtag.jsx:301 msgid "Unable to unfeature on profile" msgstr "" -#: src/pages/hashtag.jsx:308 -#: src/pages/hashtag.jsx:324 +#: src/pages/hashtag.jsx:310 +#: src/pages/hashtag.jsx:326 msgid "Featured on profile" msgstr "" -#: src/pages/hashtag.jsx:396 +#: src/pages/hashtag.jsx:398 msgid "{TOTAL_TAGS_LIMIT, plural, other {Max # tags}}" msgstr "" -#: src/pages/hashtag.jsx:399 +#: src/pages/hashtag.jsx:401 msgid "Add hashtag" msgstr "" -#: src/pages/hashtag.jsx:431 +#: src/pages/hashtag.jsx:433 msgid "Remove hashtag" msgstr "" -#: src/pages/hashtag.jsx:445 +#: src/pages/hashtag.jsx:447 msgid "{SHORTCUTS_LIMIT, plural, one {Max # shortcut reached. Unable to add shortcut.} other {Max # shortcuts reached. Unable to add shortcut.}}" msgstr "" -#: src/pages/hashtag.jsx:474 +#: src/pages/hashtag.jsx:476 msgid "This shortcut already exists" msgstr "" -#: src/pages/hashtag.jsx:477 +#: src/pages/hashtag.jsx:479 msgid "Hashtag shortcut added" msgstr "" -#: src/pages/hashtag.jsx:483 +#: src/pages/hashtag.jsx:485 msgid "Add to Shortcuts" msgstr "" -#: src/pages/hashtag.jsx:489 +#: src/pages/hashtag.jsx:491 #: src/pages/public.jsx:141 #: src/pages/trending.jsx:481 msgid "Enter a new instance e.g. \"mastodon.social\"" msgstr "" -#: src/pages/hashtag.jsx:492 +#: src/pages/hashtag.jsx:494 #: src/pages/public.jsx:144 #: src/pages/trending.jsx:484 msgid "Invalid instance" msgstr "" -#: src/pages/hashtag.jsx:506 +#: src/pages/hashtag.jsx:508 #: src/pages/public.jsx:158 #: src/pages/trending.jsx:496 msgid "Go to another instance…" msgstr "" -#: src/pages/hashtag.jsx:519 +#: src/pages/hashtag.jsx:521 #: src/pages/public.jsx:171 #: src/pages/trending.jsx:507 msgid "Go to my instance (<0>{currentInstance})" diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index dfba39ba..2cc2351a 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -477,99 +477,70 @@ function AccountStatuses() { const allowSwitch = !!account && !sameInstance; return ( - { - // states.showAccount = { - // account, - // instance, - // }; - // }} - > - - - -
- @{acct} -
- - } - id="account-statuses" - instance={instance} - emptyText={t`Nothing to see here yet.`} - errorText={t`Unable to load posts`} - fetchItems={fetchAccountStatuses} - useItemID - view={media || mediaFirst ? 'media' : undefined} - boostsCarousel={snapStates.settings.boostsCarousel} - timelineStart={TimelineStart} - refresh={[ - excludeReplies, - excludeBoosts, - tagged, - media, - month + account?.acct, - ].toString()} - headerEnd={ - - - - } - > - { - (async () => { - try { - const { masto } = api({ - instance: accountInstance, - }); - const acc = await masto.v1.accounts.lookup({ - acct: account.acct, - }); - const { id } = acc; - location.hash = `/${accountInstance}/a/${id}`; - } catch (e) { - console.error(e); - alert(t`Unable to fetch account info`); - } - })(); - }} + <> + - {!sameCurrentInstance && ( { (async () => { try { - const acc = await currentMasto.v1.accounts.lookup({ - acct: account.acct + '@' + instance, + const { masto } = api({ + instance: accountInstance, + }); + const acc = await masto.v1.accounts.lookup({ + acct: account.acct, }); const { id } = acc; - location.hash = `/${currentInstance}/a/${id}`; + location.hash = `/${accountInstance}/a/${id}`; } catch (e) { console.error(e); alert(t`Unable to fetch account info`); @@ -580,14 +551,55 @@ function AccountStatuses() { {' '} - Switch to my instance ({currentInstance}) + Switch to account's instance{' '} + {accountInstance ? ( + <> + {' '} + ({punycode.toUnicode(accountInstance)}) + + ) : null} - )} - - } - /> + {!sameCurrentInstance && ( + { + (async () => { + try { + const acc = await currentMasto.v1.accounts.lookup({ + acct: account.acct + '@' + instance, + }); + const { id } = acc; + location.hash = `/${currentInstance}/a/${id}`; + } catch (e) { + console.error(e); + alert(t`Unable to fetch account info`); + } + })(); + }} + > + {' '} + + + Switch to my instance ({currentInstance}) + + + + )} + + } + /> + {acct && ( + + )} + ); } diff --git a/src/pages/hashtag.jsx b/src/pages/hashtag.jsx index 1efd47e6..679d1a7e 100644 --- a/src/pages/hashtag.jsx +++ b/src/pages/hashtag.jsx @@ -168,126 +168,147 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { }, []); return ( - - {hashtagTitle} -
{instance}
- - ) - } - id="hashtag" - instance={instance} - emptyText={t`No one has posted anything with this tag yet.`} - errorText={t`Unable to load posts with this tag`} - fetchItems={fetchHashtags} - checkForUpdates={checkForUpdates} - useItemID - view={media || mediaFirst ? 'media' : undefined} - refresh={media} - // allowFilters - filterContext="public" - headerEnd={ - - - - } - > - {!!info && hashtags.length === 1 && ( - <> - { - setFollowUIState('loading'); - if (info.following) { - // const yes = confirm(`Unfollow #${hashtag}?`); - // if (!yes) { - // setFollowUIState('default'); - // return; - // } - masto.v1.tags - .$select(hashtag) - .unfollow() - .then(() => { - setInfo({ ...info, following: false }); - showToast(t`Unfollowed #${hashtag}`); - }) - .catch((e) => { - alert(e); - console.error(e); - }) - .finally(() => { - setFollowUIState('default'); - }); - } else { - masto.v1.tags - .$select(hashtag) - .follow() - .then(() => { - setInfo({ ...info, following: true }); - showToast(t`Followed #${hashtag}`); - }) - .catch((e) => { - alert(e); - console.error(e); - }) - .finally(() => { - setFollowUIState('default'); - }); - } - }} - > - {info.following ? ( - <> - {' '} - - Following… - - - ) : ( - <> - {' '} - - Follow - - - )} - - { - setFeaturedUIState('loading'); - if (isFeaturedTag) { - const featuredTagID = featuredTags.find( - (tag) => tag.name.toLowerCase() === hashtag.toLowerCase(), - ).id; - if (featuredTagID) { - masto.v1.featuredTags - .$select(featuredTagID) - .remove() + <> + + {hashtagTitle} +
{instance}
+ + ) + } + id="hashtag" + instance={instance} + emptyText={t`No one has posted anything with this tag yet.`} + errorText={t`Unable to load posts with this tag`} + fetchItems={fetchHashtags} + checkForUpdates={checkForUpdates} + useItemID + view={media || mediaFirst ? 'media' : undefined} + refresh={media} + // allowFilters + filterContext="public" + headerEnd={ + + + + } + > + {!!info && hashtags.length === 1 && ( + <> + { + setFollowUIState('loading'); + if (info.following) { + // const yes = confirm(`Unfollow #${hashtag}?`); + // if (!yes) { + // setFollowUIState('default'); + // return; + // } + masto.v1.tags + .$select(hashtag) + .unfollow() .then(() => { - setIsFeaturedTag(false); - showToast(t`Unfeatured on profile`); - setFeaturedTags( - featuredTags.filter( - (tag) => tag.id !== featuredTagID, - ), - ); + setInfo({ ...info, following: false }); + showToast(t`Unfollowed #${hashtag}`); + }) + .catch((e) => { + alert(e); + console.error(e); + }) + .finally(() => { + setFollowUIState('default'); + }); + } else { + masto.v1.tags + .$select(hashtag) + .follow() + .then(() => { + setInfo({ ...info, following: true }); + showToast(t`Followed #${hashtag}`); + }) + .catch((e) => { + alert(e); + console.error(e); + }) + .finally(() => { + setFollowUIState('default'); + }); + } + }} + > + {info.following ? ( + <> + {' '} + + Following… + + + ) : ( + <> + {' '} + + Follow + + + )} + + { + setFeaturedUIState('loading'); + if (isFeaturedTag) { + const featuredTagID = featuredTags.find( + (tag) => + tag.name.toLowerCase() === hashtag.toLowerCase(), + ).id; + if (featuredTagID) { + masto.v1.featuredTags + .$select(featuredTagID) + .remove() + .then(() => { + setIsFeaturedTag(false); + showToast(t`Unfeatured on profile`); + setFeaturedTags( + featuredTags.filter( + (tag) => tag.id !== featuredTagID, + ), + ); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + setFeaturedUIState('default'); + }); + } else { + showToast(t`Unable to unfeature on profile`); + } + } else { + masto.v1.featuredTags + .create({ + name: hashtag, + }) + .then((value) => { + setIsFeaturedTag(true); + showToast(t`Featured on profile`); + setFeaturedTags(featuredTags.concat(value)); }) .catch((e) => { console.error(e); @@ -295,86 +316,109 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { .finally(() => { setFeaturedUIState('default'); }); - } else { - showToast(t`Unable to unfeature on profile`); } - } else { - masto.v1.featuredTags - .create({ - name: hashtag, - }) - .then((value) => { - setIsFeaturedTag(true); - showToast(t`Featured on profile`); - setFeaturedTags(featuredTags.concat(value)); - }) - .catch((e) => { - console.error(e); - }) - .finally(() => { - setFeaturedUIState('default'); - }); - } - }} - > - {isFeaturedTag ? ( - <> - - - Featured on profile - - - ) : ( - <> - - - Feature on profile - - - )} - - - - )} - {!mediaFirst && ( - <> - - Filters - - { - if (media) { - searchParams.delete('media'); - } else { - searchParams.set('media', '1'); - } - setSearchParams(searchParams); - }} - > - {' '} - - Media only - - - - - )} - - {({ ref }) => ( -
{ - e.preventDefault(); - const newHashtag = e.target[0].value?.trim?.(); - // Use includes but need to be case insensitive - if ( - newHashtag && - !hashtags.some( - (t) => t.toLowerCase() === newHashtag.toLowerCase(), - ) - ) { - hashtags.push(newHashtag); + }} + > + {isFeaturedTag ? ( + <> + + + Featured on profile + + + ) : ( + <> + + + Feature on profile + + + )} + + + + )} + {!mediaFirst && ( + <> + + Filters + + { + if (media) { + searchParams.delete('media'); + } else { + searchParams.set('media', '1'); + } + setSearchParams(searchParams); + }} + > + {' '} + + Media only + + + + + )} + + {({ ref }) => ( + { + e.preventDefault(); + const newHashtag = e.target[0].value?.trim?.(); + // Use includes but need to be case insensitive + if ( + newHashtag && + !hashtags.some( + (t) => t.toLowerCase() === newHashtag.toLowerCase(), + ) + ) { + hashtags.push(newHashtag); + hashtags.sort(); + // navigate( + // instance + // ? `/${instance}/t/${hashtags.join('+')}` + // : `/t/${hashtags.join('+')}`, + // ); + location.hash = instance + ? `/${instance}/t/${hashtags.join('+')}${linkParams}` + : `/t/${hashtags.join('+')}${linkParams}`; + } + }} + > + + + + )} + + + {hashtags.map((tag, i) => ( + { + hashtags.splice(i, 1); hashtags.sort(); // navigate( // instance @@ -384,147 +428,116 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { location.hash = instance ? `/${instance}/t/${hashtags.join('+')}${linkParams}` : `/t/${hashtags.join('+')}${linkParams}`; - } - }} - > - - - - )} -
- - {hashtags.map((tag, i) => ( - { - hashtags.splice(i, 1); - hashtags.sort(); - // navigate( - // instance - // ? `/${instance}/t/${hashtags.join('+')}` - // : `/t/${hashtags.join('+')}`, - // ); - location.hash = instance - ? `/${instance}/t/${hashtags.join('+')}${linkParams}` - : `/t/${hashtags.join('+')}${linkParams}`; - }} - > - - - # - {tag} - - - ))} - - - { - if (states.shortcuts.length >= SHORTCUTS_LIMIT) { - alert( - plural(SHORTCUTS_LIMIT, { - one: 'Max # shortcut reached. Unable to add shortcut.', - other: 'Max # shortcuts reached. Unable to add shortcut.', - }), - ); - return; - } - const shortcut = { - type: 'hashtag', - hashtag: hashtags.join(' '), - instance, - media: media ? 'on' : undefined, - }; - // Check if already exists - const exists = states.shortcuts.some( - (s) => - s.type === shortcut.type && - s.hashtag - .split(/[\s+]+/) - .sort() - .join(' ') === - shortcut.hashtag + }} + > + + + # + {tag} + + + ))} + + + { + if (states.shortcuts.length >= SHORTCUTS_LIMIT) { + alert( + plural(SHORTCUTS_LIMIT, { + one: 'Max # shortcut reached. Unable to add shortcut.', + other: 'Max # shortcuts reached. Unable to add shortcut.', + }), + ); + return; + } + const shortcut = { + type: 'hashtag', + hashtag: hashtags.join(' '), + instance, + media: media ? 'on' : undefined, + }; + // Check if already exists + const exists = states.shortcuts.some( + (s) => + s.type === shortcut.type && + s.hashtag .split(/[\s+]+/) .sort() - .join(' ') && - (s.instance ? s.instance === shortcut.instance : true) && - (s.media ? !!s.media === !!shortcut.media : true), - ); - if (exists) { - alert(t`This shortcut already exists`); - } else { - states.shortcuts.push(shortcut); - showToast(t`Hashtag shortcut added`); - } - }} - > - {' '} - - Add to Shortcuts - - - { - let newInstance = prompt( - t`Enter a new instance e.g. "mastodon.social"`, - ); - if (!/\./.test(newInstance)) { - if (newInstance) alert(t`Invalid instance`); - return; - } - if (newInstance) { - newInstance = newInstance.toLowerCase().trim(); - // navigate(`/${newInstance}/t/${hashtags.join('+')}`); - location.hash = `/${newInstance}/t/${hashtags.join( - '+', - )}${linkParams}`; - } - }} - > - {' '} - - Go to another instance… - - - {currentInstance !== instance && ( + .join(' ') === + shortcut.hashtag + .split(/[\s+]+/) + .sort() + .join(' ') && + (s.instance ? s.instance === shortcut.instance : true) && + (s.media ? !!s.media === !!shortcut.media : true), + ); + if (exists) { + alert(t`This shortcut already exists`); + } else { + states.shortcuts.push(shortcut); + showToast(t`Hashtag shortcut added`); + } + }} + > + {' '} + + Add to Shortcuts + +
{ - location.hash = `/${currentInstance}/t/${hashtags.join( - '+', - )}${linkParams}`; + let newInstance = prompt( + t`Enter a new instance e.g. "mastodon.social"`, + ); + if (!/\./.test(newInstance)) { + if (newInstance) alert(t`Invalid instance`); + return; + } + if (newInstance) { + newInstance = newInstance.toLowerCase().trim(); + // navigate(`/${newInstance}/t/${hashtags.join('+')}`); + location.hash = `/${newInstance}/t/${hashtags.join( + '+', + )}${linkParams}`; + } }} > {' '} - - - Go to my instance ({currentInstance}) - - + + Go to another instance… + - )} -
- } - /> + {currentInstance !== instance && ( + { + location.hash = `/${currentInstance}/t/${hashtags.join( + '+', + )}${linkParams}`; + }} + > + {' '} + + + Go to my instance ({currentInstance}) + + + + )} + + } + /> + {!!hashtags?.length && ( + 1 ? '\n\n' : ' '}${hashtagTitle}`, + }, + })} + /> + )} + ); }