kopia lustrzana https://github.com/cheeaun/phanpy
Context-aware compose button
rodzic
9234211542
commit
77339c2d49
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>)"
|
||||
|
|
|
|||
|
|
@ -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} `,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue