Context-aware compose button

pull/1176/head
Lim Chee Aun 2025-05-29 20:00:39 +08:00
rodzic 9234211542
commit 77339c2d49
5 zmienionych plików z 497 dodań i 465 usunięć

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}

76
src/locales/en.po wygenerowano
Wyświetl plik

@ -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 ? ( <> {' '} (<b>{punycode.toUnicode(accountInstance)}</b>) </> ) : 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}</0>)"
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}</0>)"

Wyświetl plik

@ -477,99 +477,70 @@ function AccountStatuses() {
const allowSwitch = !!account && !sameInstance;
return (
<Timeline
key={id}
title={`${account?.acct ? '@' + account.acct : t`Posts`}`}
titleComponent={
<h1
class="header-double-lines header-account"
// onClick={() => {
// states.showAccount = {
// account,
// instance,
// };
// }}
>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
<div>
<span class="bidi-isolate">@{acct}</span>
</div>
</h1>
}
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={
<Menu2
portal
// setDownOverflow
overflow="auto"
viewScroll="close"
position="anchor"
menuButton={
<button type="button" class="plain">
<Icon icon="more" size="l" alt={t`More`} />
</button>
}
>
<MenuItem
disabled={!allowSwitch}
onClick={() => {
(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`);
}
})();
}}
<>
<Timeline
key={id}
title={`${account?.acct ? '@' + account.acct : t`Posts`}`}
titleComponent={
<h1
class="header-double-lines header-account"
// onClick={() => {
// states.showAccount = {
// account,
// instance,
// };
// }}
>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
<div>
<span class="bidi-isolate">@{acct}</span>
</div>
</h1>
}
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={
<Menu2
portal
// setDownOverflow
overflow="auto"
viewScroll="close"
position="anchor"
menuButton={
<button type="button" class="plain">
<Icon icon="more" size="l" alt={t`More`} />
</button>
}
>
<Icon icon="transfer" />{' '}
<small class="menu-double-lines">
<Trans>
Switch to account's instance{' '}
{accountInstance ? (
<>
{' '}
(<b>{punycode.toUnicode(accountInstance)}</b>)
</>
) : null}
</Trans>
</small>
</MenuItem>
{!sameCurrentInstance && (
<MenuItem
disabled={!allowSwitch}
onClick={() => {
(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() {
<Icon icon="transfer" />{' '}
<small class="menu-double-lines">
<Trans>
Switch to my instance (<b>{currentInstance}</b>)
Switch to account's instance{' '}
{accountInstance ? (
<>
{' '}
(<b>{punycode.toUnicode(accountInstance)}</b>)
</>
) : null}
</Trans>
</small>
</MenuItem>
)}
</Menu2>
}
/>
{!sameCurrentInstance && (
<MenuItem
onClick={() => {
(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`);
}
})();
}}
>
<Icon icon="transfer" />{' '}
<small class="menu-double-lines">
<Trans>
Switch to my instance (<b>{currentInstance}</b>)
</Trans>
</small>
</MenuItem>
)}
</Menu2>
}
/>
{acct && (
<data
class="compose-data"
value={JSON.stringify({
draftStatus: {
status: `@${acct} `,
},
})}
/>
)}
</>
);
}

Wyświetl plik

@ -168,126 +168,147 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
}, []);
return (
<Timeline
key={instance + hashtagTitle}
title={title}
titleComponent={
!!instance && (
<h1 class="header-double-lines">
<b dir="auto">{hashtagTitle}</b>
<div>{instance}</div>
</h1>
)
}
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={
<Menu2
portal
setDownOverflow
overflow="auto"
// viewScroll="close"
position="anchor"
menuButton={
<button type="button" class="plain">
<Icon icon="more" size="l" alt={t`More`} />
</button>
}
>
{!!info && hashtags.length === 1 && (
<>
<MenuConfirm
subMenu
confirm={info.following}
confirmLabel={t`Unfollow #${hashtag}?`}
disabled={followUIState === 'loading' || !authenticated}
onClick={() => {
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 ? (
<>
<Icon icon="check-circle" />{' '}
<span>
<Trans>Following</Trans>
</span>
</>
) : (
<>
<Icon icon="plus" />{' '}
<span>
<Trans>Follow</Trans>
</span>
</>
)}
</MenuConfirm>
<MenuItem
type="checkbox"
checked={isFeaturedTag}
disabled={featuredUIState === 'loading' || !authenticated}
onClick={() => {
setFeaturedUIState('loading');
if (isFeaturedTag) {
const featuredTagID = featuredTags.find(
(tag) => tag.name.toLowerCase() === hashtag.toLowerCase(),
).id;
if (featuredTagID) {
masto.v1.featuredTags
.$select(featuredTagID)
.remove()
<>
<Timeline
key={instance + hashtagTitle}
title={title}
titleComponent={
!!instance && (
<h1 class="header-double-lines">
<b dir="auto">{hashtagTitle}</b>
<div>{instance}</div>
</h1>
)
}
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={
<Menu2
portal
setDownOverflow
overflow="auto"
// viewScroll="close"
position="anchor"
menuButton={
<button type="button" class="plain">
<Icon icon="more" size="l" alt={t`More`} />
</button>
}
>
{!!info && hashtags.length === 1 && (
<>
<MenuConfirm
subMenu
confirm={info.following}
confirmLabel={t`Unfollow #${hashtag}?`}
disabled={followUIState === 'loading' || !authenticated}
onClick={() => {
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 ? (
<>
<Icon icon="check-circle" />{' '}
<span>
<Trans>Following</Trans>
</span>
</>
) : (
<>
<Icon icon="plus" />{' '}
<span>
<Trans>Follow</Trans>
</span>
</>
)}
</MenuConfirm>
<MenuItem
type="checkbox"
checked={isFeaturedTag}
disabled={featuredUIState === 'loading' || !authenticated}
onClick={() => {
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 ? (
<>
<Icon icon="check-circle" />
<span>
<Trans>Featured on profile</Trans>
</span>
</>
) : (
<>
<Icon icon="check-circle" />
<span>
<Trans>Feature on profile</Trans>
</span>
</>
)}
</MenuItem>
<MenuDivider />
</>
)}
{!mediaFirst && (
<>
<MenuHeader className="plain">
<Trans>Filters</Trans>
</MenuHeader>
<MenuItem
type="checkbox"
checked={!!media}
onClick={() => {
if (media) {
searchParams.delete('media');
} else {
searchParams.set('media', '1');
}
setSearchParams(searchParams);
}}
>
<Icon icon="check-circle" alt="☑️" />{' '}
<span class="menu-grow">
<Trans>Media only</Trans>
</span>
</MenuItem>
<MenuDivider />
</>
)}
<FocusableItem className="menu-field" disabled={reachLimit}>
{({ ref }) => (
<form
onSubmit={(e) => {
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 ? (
<>
<Icon icon="check-circle" />
<span>
<Trans>Featured on profile</Trans>
</span>
</>
) : (
<>
<Icon icon="check-circle" />
<span>
<Trans>Feature on profile</Trans>
</span>
</>
)}
</MenuItem>
<MenuDivider />
</>
)}
{!mediaFirst && (
<>
<MenuHeader className="plain">
<Trans>Filters</Trans>
</MenuHeader>
<MenuItem
type="checkbox"
checked={!!media}
onClick={() => {
if (media) {
searchParams.delete('media');
} else {
searchParams.set('media', '1');
}
setSearchParams(searchParams);
}}
>
<Icon icon="check-circle" alt="☑️" />{' '}
<span class="menu-grow">
<Trans>Media only</Trans>
</span>
</MenuItem>
<MenuDivider />
</>
)}
<FocusableItem className="menu-field" disabled={reachLimit}>
{({ ref }) => (
<form
onSubmit={(e) => {
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}`;
}
}}
>
<Icon icon="hashtag" />
<input
ref={ref}
type="text"
placeholder={
reachLimit
? plural(TOTAL_TAGS_LIMIT, {
other: 'Max # tags',
})
: t`Add hashtag`
}
required
autocorrect="off"
autocapitalize="off"
spellCheck={false}
// no spaces, no hashtags
pattern="[^#][^\s#]+[^#]"
disabled={reachLimit}
dir="auto"
/>
</form>
)}
</FocusableItem>
<MenuGroup takeOverflow>
{hashtags.map((tag, i) => (
<MenuItem
key={tag}
disabled={hashtags.length === 1}
onClick={(e) => {
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}`;
}
}}
>
<Icon icon="hashtag" />
<input
ref={ref}
type="text"
placeholder={
reachLimit
? plural(TOTAL_TAGS_LIMIT, {
other: 'Max # tags',
})
: t`Add hashtag`
}
required
autocorrect="off"
autocapitalize="off"
spellCheck={false}
// no spaces, no hashtags
pattern="[^#][^\s#]+[^#]"
disabled={reachLimit}
dir="auto"
/>
</form>
)}
</FocusableItem>
<MenuGroup takeOverflow>
{hashtags.map((tag, i) => (
<MenuItem
key={tag}
disabled={hashtags.length === 1}
onClick={(e) => {
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}`;
}}
>
<Icon icon="x" alt={t`Remove hashtag`} class="danger-icon" />
<span class="bidi-isolate">
<span class="more-insignificant">#</span>
{tag}
</span>
</MenuItem>
))}
</MenuGroup>
<MenuDivider />
<MenuItem
disabled={!currentAuthenticated}
onClick={() => {
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
}}
>
<Icon icon="x" alt={t`Remove hashtag`} class="danger-icon" />
<span class="bidi-isolate">
<span class="more-insignificant">#</span>
{tag}
</span>
</MenuItem>
))}
</MenuGroup>
<MenuDivider />
<MenuItem
disabled={!currentAuthenticated}
onClick={() => {
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`);
}
}}
>
<Icon icon="shortcut" />{' '}
<span>
<Trans>Add to Shortcuts</Trans>
</span>
</MenuItem>
<MenuItem
onClick={() => {
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}`;
}
}}
>
<Icon icon="bus" />{' '}
<span>
<Trans>Go to another instance</Trans>
</span>
</MenuItem>
{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`);
}
}}
>
<Icon icon="shortcut" />{' '}
<span>
<Trans>Add to Shortcuts</Trans>
</span>
</MenuItem>
<MenuItem
onClick={() => {
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}`;
}
}}
>
<Icon icon="bus" />{' '}
<small class="menu-double-lines">
<Trans>
Go to my instance (<b>{currentInstance}</b>)
</Trans>
</small>
<span>
<Trans>Go to another instance</Trans>
</span>
</MenuItem>
)}
</Menu2>
}
/>
{currentInstance !== instance && (
<MenuItem
onClick={() => {
location.hash = `/${currentInstance}/t/${hashtags.join(
'+',
)}${linkParams}`;
}}
>
<Icon icon="bus" />{' '}
<small class="menu-double-lines">
<Trans>
Go to my instance (<b>{currentInstance}</b>)
</Trans>
</small>
</MenuItem>
)}
</Menu2>
}
/>
{!!hashtags?.length && (
<data
class="compose-data"
value={JSON.stringify({
draftStatus: {
status: `${hashtags.length > 1 ? '\n\n' : ' '}${hashtagTitle}`,
},
})}
/>
)}
</>
);
}