diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-05-11 13:11:07 +0100 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-05-11 13:11:07 +0100 |
| commit | 30bd7768d6d892629cd924da38bbc7ec0d2a117a (patch) | |
| tree | 1bf440d1c4df5ecb7a765fedbe26bab41d6b53cc /packages/frontend/src | |
| parent | fix some icons (diff) | |
| parent | merge: bump develop after 2024.3.3 (!512) (diff) | |
| download | sharkey-30bd7768d6d892629cd924da38bbc7ec0d2a117a.tar.gz sharkey-30bd7768d6d892629cd924da38bbc7ec0d2a117a.tar.bz2 sharkey-30bd7768d6d892629cd924da38bbc7ec0d2a117a.zip | |
Merge branch 'develop' into future-2024-04-25-post
Diffstat (limited to 'packages/frontend/src')
30 files changed, 120 insertions, 49 deletions
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 8b665bfacd..b04a1c92e7 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -238,7 +238,7 @@ function exec() { return; } - emojis.value = searchEmoji(props.q.toLowerCase(), emojiDb.value); + emojis.value = searchEmoji(props.q.normalize('NFC').toLowerCase(), emojiDb.value); } else if (props.type === 'mfmTag') { if (!props.q || props.q === '') { mfmTags.value = MFM_TAGS; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 1219a29d85..53fca97fc0 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -205,7 +205,7 @@ watch(q, () => { return; } - const newQ = q.value.replace(/:/g, '').toLowerCase(); + const newQ = q.value.replace(/:/g, '').normalize('NFC').toLowerCase(); const searchCustom = () => { const max = 100; diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index f3305e9f54..bab844c27f 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }"> + <MkPagination v-slot="{items}" :pagination="pagination" :displayLimit="50" class="urempief" :class="{ grid: viewMode === 'grid' }"> <MkA v-for="file in (items as Misskey.entities.DriveFile[])" :key="file.id" diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index c3fa724a7a..d664dc9731 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header> <template v-if="pageMetadata"> <i v-if="pageMetadata.icon" :class="pageMetadata.icon" style="margin-right: 0.5em;"></i> - <span>{{ pageMetadata.title }}</span> + <span><MkUserName v-if="pageMetadata.userName?.name" :user="pageMetadata.userName" />{{ pageMetadata.title }}</span> </template> </template> @@ -43,6 +43,7 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { getScrollContainer } from '@/scripts/scroll.js'; import { useRouterFactory } from '@/router/supplier.js'; import { mainRouter } from '@/router/main.js'; +import MkUserName from './global/MkUserName.vue'; const props = defineProps<{ initialPath: string; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 62a85389ad..6f6007d432 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -395,10 +395,10 @@ const prepend = (item: MisskeyEntity): void => { * @param newItems 新しいアイテムの配列 */ function unshiftItems(newItems: MisskeyEntity[]) { - const length = newItems.length + items.value.size; - items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit)); - - if (length >= props.displayLimit) more.value = true; + const prevLength = items.value.size; + items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, newItems.length + props.displayLimit)); + // if we truncated, mark that there are more values to fetch + if (items.value.size < prevLength) more.value = true; } /** @@ -406,10 +406,10 @@ function unshiftItems(newItems: MisskeyEntity[]) { * @param oldItems 古いアイテムの配列 */ function concatItems(oldItems: MisskeyEntity[]) { - const length = oldItems.length + items.value.size; - items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit)); - - if (length >= props.displayLimit) more.value = true; + const prevLength = items.value.size; + items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, oldItems.length + props.displayLimit)); + // if we truncated, mark that there are more values to fetch + if (items.value.size < prevLength) more.value = true; } function executeQueue() { @@ -418,7 +418,7 @@ function executeQueue() { } function prependQueue(newItem: MisskeyEntity) { - queue.value = new Map([[newItem.id, newItem], ...queue.value].slice(0, props.displayLimit) as [string, MisskeyEntity][]); + queue.value = new Map([[newItem.id, newItem], ...queue.value] as [string, MisskeyEntity][]); } /* diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index 18a9eeda23..c2435b308f 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import { instance } from '@/instance.js'; import { i18n } from '@/i18n.js'; -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from '@/scripts/sanitize-html.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index 57216058fd..725cfcdc33 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -16,9 +16,20 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div v-else-if="phase === 'howToReact'" class="_gaps"> <div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._reaction.description }}</div> - <div>{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}</div> + <I18n :src="i18n.ts._initialTutorial._reaction.letsTryReacting" tag="div"> + <template #reaction> + <i class="ph-smiley ph-bold ph-lg"></i> + </template> + </I18n> <MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/> - <div v-if="onceReacted"><b style="color: var(--accent);"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>{{ i18n.ts._initialTutorial._reaction.reactDone }}</div> + <div v-if="onceReacted"> + <b style="color: var(--accent);"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br> + <I18n :src="i18n.ts._initialTutorial._reaction.reactDone"> + <template #undo> + <i class="ph-minus ph-bold ph-lg"></i> + </template> + </I18n> + </div> </div> </template> diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 17a9254d01..ac82ecc3d6 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkPagination :pagination="pagination"> +<MkPagination :pagination="pagination" :displayLimit="50"> <template #empty> <div class="_fullinfo"> <img :src="infoImageUrl" class="_ghost"/> diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index d8e6ba9a09..f9f16c594e 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import * as Misskey from 'misskey-js'; -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from '@/scripts/sanitize-html.js'; import XSigninDialog from '@/components/MkSigninDialog.vue'; import XSignupDialog from '@/components/MkSignupDialog.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index b57a311c0c..8ba4e396b7 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -85,6 +85,7 @@ const errored = ref(url.value == null); function onClick(ev: MouseEvent) { if (props.menu) { + ev.stopPropagation(); os.popupMenu([{ type: 'label', text: `:${props.name}:`, diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 2e7a0c5bb7..b6b5aaba32 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -4,8 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick" v-on:click.stop/> -<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick" v-on:click.stop>{{ colorizedNativeEmoji }}</span> +<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/> +<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ colorizedNativeEmoji }}</span> </template> <script lang="ts" setup> @@ -39,6 +39,7 @@ function computeTitle(event: PointerEvent): void { function onClick(ev: MouseEvent) { if (props.menu) { + ev.stopPropagation(); os.popupMenu([{ type: 'label', text: props.emoji, diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index c7f2315faa..b8f2fcc883 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSplit> </div> - <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> + <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :displayLimit="50"> <div :class="$style.items"> <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`"> <MkInstanceCardMini :instance="instance"/> diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index f2aceada7d..23960d39d9 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -130,7 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import sanitizeHtml from 'sanitize-html'; +import sanitizeHtml from '@/scripts/sanitize-html.js'; import { computed, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; import XEmojis from './about.emojis.vue'; diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 42fcc3a598..6e1f5f9cec 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> --> - <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> + <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);"> <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> </MkPagination> </div> diff --git a/packages/frontend/src/pages/admin/approvals.vue b/packages/frontend/src/pages/admin/approvals.vue index 998e16681a..03420232c8 100644 --- a/packages/frontend/src/pages/admin/approvals.vue +++ b/packages/frontend/src/pages/admin/approvals.vue @@ -4,7 +4,7 @@ <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="900"> <div class="_gaps_m"> - <MkPagination ref="paginationComponent" :pagination="pagination"> + <MkPagination ref="paginationComponent" :pagination="pagination" :displayLimit="50"> <template #default="{ items }"> <div class="_gaps_s"> <SkApprovalUser v-for="item in items" :key="item.id" :user="(item as any)" :onDeleted="deleted"/> diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue index f8c4a3b272..db9211929f 100644 --- a/packages/frontend/src/pages/admin/federation.vue +++ b/packages/frontend/src/pages/admin/federation.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSplit> </div> - <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> + <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination" :displayLimit="50"> <div :class="$style.instances"> <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`"> <MkInstanceCardMini :instance="instance"/> diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue index 7b8a1e1d4e..4dc8ded7a2 100644 --- a/packages/frontend/src/pages/admin/invites.vue +++ b/packages/frontend/src/pages/admin/invites.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only <option value="-usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.descendingOrder }})</option> </MkSelect> </div> - <MkPagination ref="pagingComponent" :pagination="pagination"> + <MkPagination ref="pagingComponent" :pagination="pagination" :displayLimit="50"> <template #default="{ items }"> <div class="_gaps_s"> <MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/> diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue index 4651bb4516..850d36063e 100644 --- a/packages/frontend/src/pages/admin/modlog.vue +++ b/packages/frontend/src/pages/admin/modlog.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> </div> - <MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);"> + <MkPagination v-slot="{items}" ref="logs" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);"> <div class="_gaps_s"> <XModLog v-for="item in items" :key="item.id" :log="item"/> </div> diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index cda524f787..c084d5f8df 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps"> <MkButton primary rounded @click="assign"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.assign }}</MkButton> - <MkPagination :pagination="usersPagination"> + <MkPagination :pagination="usersPagination" :displayLimit="50"> <template #empty> <div class="_fullinfo"> <img :src="infoImageUrl" class="_ghost"/> diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index 626346a998..001b7dc82d 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> </div> - <MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination"> + <MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" :displayLimit="50"> <div :class="$style.users"> <MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`"> <MkUserCardMini :user="user"/> diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 1a745d6626..1f9a99d4f5 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton inline @click="setLicenseBulk">Set License</MkButton> <MkButton inline danger @click="delBulk">Delete</MkButton> </div> - <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> + <MkPagination ref="emojisPaginationComponent" :pagination="pagination" :displayLimit="50"> <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> <template #default="{items}"> <div class="ldhfsamy"> @@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.host }}</template> </MkInput> </FormSplit> - <MkPagination :pagination="remotePagination"> + <MkPagination :pagination="remotePagination" :displayLimit="50"> <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> <template #default="{items}"> <div class="ldhfsamy"> @@ -352,6 +352,7 @@ definePageMetadata(() => ({ > .img { width: 42px; height: 42px; + object-fit: contain; } > .body { @@ -398,6 +399,7 @@ definePageMetadata(() => ({ > .img { width: 32px; height: 32px; + object-fit: contain; } > .body { diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 64960fd063..d03d3d9128 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton> - <MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off"> + <MkInput v-model="name" autocapitalize="off"> <template #label>{{ i18n.ts.name }}</template> </MkInput> <MkInput v-model="category" :datalist="customEmojiCategories"> diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue index 6dde006106..8d78ce7031 100644 --- a/packages/frontend/src/pages/settings/notifications.notification-config.vue +++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue @@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <MkSelect v-model="type"> <option value="all">{{ i18n.ts.all }}</option> - <option value="following" v-if="hasSender">{{ i18n.ts.following }}</option> - <option value="follower" v-if="hasSender">{{ i18n.ts.followers }}</option> - <option value="mutualFollow" v-if="hasSender">{{ i18n.ts.mutualFollow }}</option> - <option value="followingOrFollower" v-if="hasSender">{{ i18n.ts.followingOrFollower }}</option> - <option value="list" v-if="hasSender">{{ i18n.ts.userList }}</option> + <option v-if="hasSender" value="following">{{ i18n.ts.following }}</option> + <option v-if="hasSender" value="follower">{{ i18n.ts.followers }}</option> + <option v-if="hasSender" value="mutualFollow">{{ i18n.ts.mutualFollow }}</option> + <option v-if="hasSender" value="followingOrFollower">{{ i18n.ts.followingOrFollower }}</option> + <option v-if="hasSender" value="list">{{ i18n.ts.userList }}</option> <option value="never">{{ i18n.ts.none }}</option> </MkSelect> diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 0facfc6631..c774ab5367 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -140,6 +140,7 @@ type Profile = { hot: Record<keyof typeof defaultStoreSaveKeys, unknown>; cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>; fontSize: string | null; + lang: string | null; cornerRadius: string | null; useSystemFont: 't' | null; wallpaper: string | null; @@ -198,6 +199,7 @@ function getSettings(): Profile['settings'] { hot, cold, fontSize: miLocalStorage.getItem('fontSize'), + lang: miLocalStorage.getItem('lang'), cornerRadius: miLocalStorage.getItem('cornerRadius'), useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null, wallpaper: miLocalStorage.getItem('wallpaper'), @@ -313,6 +315,13 @@ async function applyProfile(id: string): Promise<void> { miLocalStorage.removeItem('fontSize'); } + // lang + if (settings.lang) { + miLocalStorage.setItem('lang', settings.lang); + } else { + miLocalStorage.removeItem('lang'); + } + // cornerRadius if (settings.cornerRadius) { miLocalStorage.setItem('cornerRadius', settings.cornerRadius); diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 7f20e941d3..ebe4176196 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -130,7 +130,7 @@ definePageMetadata(() => ({ title: i18n.ts.user, icon: 'ph-user ph-bold ph-lg', ...user.value ? { - title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`, + title: user.value.name ? ` (@${user.value.username})` : `@${user.value.username}`, subtitle: `@${getAcct(user.value)}`, userName: user.value, avatar: user.value, diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts index 9fc8f7843e..d942402ffc 100644 --- a/packages/frontend/src/scripts/autocomplete.ts +++ b/packages/frontend/src/scripts/autocomplete.ts @@ -99,7 +99,7 @@ export class Autocomplete { const isHashtag = hashtagIndex !== -1; const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' '); const isMfmTag = mfmTagIndex !== -1 && !isMfmParam; - const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':'); let opened = false; @@ -125,7 +125,7 @@ export class Autocomplete { if (isEmoji && !opened && this.onlyType.includes('emoji')) { const emoji = text.substring(emojiIndex + 1); if (!emoji.includes(' ')) { - this.open('emoji', emoji); + this.open('emoji', emoji.normalize('NFC')); opened = true; } } diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index 67e896b4b9..8d3e96cea5 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -3,12 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean { +import type { Note, MeDetailed } from "misskey-js/entities.js"; + +export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array<string | string[]>): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; if (mutedWords.length > 0) { - const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim(); + const text = getNoteText(note); if (text === '') return false; @@ -40,3 +42,25 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any> return false; } + +function getNoteText(note: Note): string { + const textParts: string[] = []; + + if (note.cw) + textParts.push(note.cw); + + if (note.text) + textParts.push(note.text); + + if (note.files) + for (const file of note.files) + if (file.comment) + textParts.push(file.comment); + + if (note.poll) + for (const choice of note.poll.choices) + if (choice.text) + textParts.push(choice.text); + + return textParts.join('\n').trim(); +} diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/scripts/chiptune2.ts index 3cc34c0040..56afb9b708 100644 --- a/packages/frontend/src/scripts/chiptune2.ts +++ b/packages/frontend/src/scripts/chiptune2.ts @@ -1,4 +1,3 @@ -// @ts-nocheck /* eslint-disable */ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; @@ -6,6 +5,11 @@ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; let libopenmpt let libopenmptLoadPromise +type ChiptuneJsConfig = { + repeatCount: number | null; + context: AudioContext | null; +}; + export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) { this.repeatCount = repeatCount; this.context = context; @@ -13,7 +17,7 @@ export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig; -export function ChiptuneJsPlayer (config: object) { +export function ChiptuneJsPlayer (config: ChiptuneJsConfig) { this.config = config; this.audioContext = config.context || new ChiptuneAudioContext(); this.context = this.audioContext.createGain(); @@ -27,7 +31,7 @@ ChiptuneJsPlayer.prototype.initialize = function() { if (libopenmptLoadPromise) return libopenmptLoadPromise; if (libopenmpt) return Promise.resolve(); - libopenmptLoadPromise = new Promise(async (resolve, reject) => { + libopenmptLoadPromise = new Promise<void>(async (resolve, reject) => { try { const { Module } = await import('./libopenmpt/libopenmpt.js'); await new Promise((resolve) => { diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/frontend/src/scripts/nyaize.ts index 58ed88fed1..5e6fa298d1 100644 --- a/packages/frontend/src/scripts/nyaize.ts +++ b/packages/frontend/src/scripts/nyaize.ts @@ -9,9 +9,9 @@ const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm; function ifAfter(prefix, fn) { const preLen = prefix.length; - const regex = new RegExp(prefix,'i'); - return (x,pos,string) => { - return pos > 0 && string.substring(pos-preLen,pos).match(regex) ? fn(x) : x; + const regex = new RegExp(prefix, 'i'); + return (x, pos, string) => { + return pos > 0 && string.substring(pos - preLen, pos).match(regex) ? fn(x) : x; }; } @@ -25,7 +25,7 @@ export function nyaize(text: string): string { .replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan')) // ko-KR .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), )) .replace(koRegex2, '다냥') .replace(koRegex3, '냥'); diff --git a/packages/frontend/src/scripts/sanitize-html.ts b/packages/frontend/src/scripts/sanitize-html.ts new file mode 100644 index 0000000000..6e1a46c746 --- /dev/null +++ b/packages/frontend/src/scripts/sanitize-html.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: dakkar and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only +*/ + +import original from 'sanitize-html'; + +export default function sanitizeHtml(str: string | null): string | null { + if (str == null) return str; + return original(str, { + allowedTags: original.defaults.allowedTags.concat(['img', 'audio', 'video', 'center', 'details', 'summary']), + allowedAttributes: { + ...original.defaults.allowedAttributes, + a: original.defaults.allowedAttributes.a.concat(['style']), + img: original.defaults.allowedAttributes.img.concat(['style']), + }, + }); +} |