diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 281e2058a6b3..75a458424e7e 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -22,16 +22,13 @@ jobs: api-json-name: [api-base.json, api-head.json] include: - api-json-name: api-base.json - repo-name: ${{ github.event.pull_request.base.repo.full_name }} ref: ${{ github.base_ref }} - api-json-name: api-head.json - repo-name: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.head_ref }} + ref: refs/pull/${{ github.event.number }}/merge steps: - uses: actions/checkout@v4.1.1 with: - repository: ${{ matrix.repo-name }} ref: ${{ matrix.ref }} submodules: true - name: Install pnpm diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d0879e2032..db26ffc8eeef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,15 @@ - Enhance: リアクション選択時に音を鳴らせるように - Enhance: サウンドにドライブのファイルを使用できるように - Enhance: ナビゲーションバーに項目「キャッシュを削除」を追加 +- Enhance: Shareページで投稿を完了すると、親ウィンドウ(親フレーム)にpostMessageするように +- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305 +- Enhance: ノートプレビューに「内容を隠す」が反映されるように - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 - Fix: コードエディタが正しく表示されない問題を修正 - Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正 - Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正 +- Fix: 共有機能をサポートしていないブラウザの場合は共有ボタンを非表示にする #11305 - Fix: 通知のグルーピング設定を変更してもリロードされるまで表示が変わらない問題を修正 #12470 ### Server diff --git a/Dockerfile b/Dockerfile index 028a3976d2e2..38aa5bc7b314 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,8 +67,8 @@ RUN apt-get update \ && corepack enable \ && groupadd -g "${GID}" misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ - && find / -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \ - && find / -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \ + && find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \ + && find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \ && apt-get clean \ && rm -rf /var/lib/apt/lists diff --git a/locales/index.d.ts b/locales/index.d.ts index d1025c885d90..6036c6fa668b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1030,6 +1030,8 @@ export interface Locale { "sensitiveWords": string; "sensitiveWordsDescription": string; "sensitiveWordsDescription2": string; + "hiddenTags": string; + "hiddenTagsDescription": string; "notesSearchNotAvailable": string; "license": string; "unfavoriteConfirm": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f4daefa978bc..0f4164652ca9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1027,6 +1027,8 @@ resetPasswordConfirm: "パスワードリセットしますか?" sensitiveWords: "センシティブワード" sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。" sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。" +hiddenTags: "非表示ハッシュタグ" +hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。" notesSearchNotAvailable: "ノート検索は利用できません。" license: "ライセンス" unfavoriteConfirm: "お気に入り解除しますか?" diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 392fa7e1cb7e..aa749943f099 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -186,6 +186,10 @@ export const packedNoteSchema = { optional: false, nullable: false, }, }, + clippedCount: { + type: 'number', + optional: true, nullable: false, + }, myReaction: { type: 'object', diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 594fe642307f..6e216a78b479 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -202,20 +202,24 @@ export async function common(createVue: () => App) { } }, { immediate: true }); - if (defaultStore.state.keepScreenOn) { - if ('wakeLock' in navigator) { - navigator.wakeLock.request('screen') - .then(() => { - document.addEventListener('visibilitychange', async () => { - if (document.visibilityState === 'visible') { - navigator.wakeLock.request('screen'); - } - }); - }) + // Keep screen on + const onVisibilityChange = () => document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + navigator.wakeLock.request('screen'); + } + }); + if (defaultStore.state.keepScreenOn && 'wakeLock' in navigator) { + navigator.wakeLock.request('screen') + .then(onVisibilityChange) .catch(() => { - // If Permission fails on an AppleDevice such as Safari + // On WebKit-based browsers, user activation is required to send wake lock request + // https://webkit.org/blog/13862/the-user-activation-api/ + document.addEventListener( + 'click', + () => navigator.wakeLock.request('screen').then(onVisibilityChange), + { once: true }, + ); }); - } } //#region Fetch user diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue index 0cdaf7c9bda5..70b7bc8295bc 100644 --- a/packages/frontend/src/components/MkCwButton.vue +++ b/packages/frontend/src/components/MkCwButton.vue @@ -16,7 +16,22 @@ import MkButton from '@/components/MkButton.vue'; const props = defineProps<{ modelValue: boolean; - note: Misskey.entities.Note; + text: string | null; + files: Misskey.entities.DriveFile[]; + poll?: { + expiresAt: string | null; + multiple: boolean; + choices: { + isVoted: boolean; + text: string; + votes: number; + }[]; + } | { + choices: string[]; + multiple: boolean; + expiresAt: string | null; + expiredAfter: string | null; + }; }>(); const emit = defineEmits<{ @@ -25,9 +40,9 @@ const emit = defineEmits<{ const label = computed(() => { return concat([ - props.note.text ? [i18n.t('_cw.chars', { count: props.note.text.length })] : [], - props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length })] : [], - props.note.poll != null ? [i18n.ts.poll] : [], + props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [], + props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [], + props.poll != null ? [i18n.ts.poll] : [], ] as string[][]).join(' / '); }); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 04fe8d52fb03..596895efb960 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- +

diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index d8089ac36f82..31e97b6aaab6 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- +

({{ i18n.ts.private }}) diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 9b7a39b5374e..d664d88231fd 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -11,7 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+

+ + +

+
@@ -20,11 +24,23 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -53,6 +69,14 @@ const props = defineProps<{ min-width: 0; } +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} + .header { margin-bottom: 2px; font-weight: bold; diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index a40dcaf003ca..f3ab6b2723d7 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- +

diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 422e9094cc6b..1e901a1fd6e5 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- +

diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index d163ea248767..07c721320210 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index c5f247bce957..d4c3ef60aa1f 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -103,7 +103,7 @@ export default function(props: MfmProps) { case 'fn': { // TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる - let style; + let style: string | undefined; switch (token.props.name) { case 'tada': { const speed = validTime(token.props.args.speed) ?? '1s'; @@ -268,7 +268,7 @@ export default function(props: MfmProps) { ]); } } - if (style == null) { + if (style === undefined) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); } else { return h('span', { diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 580816abaab5..935ca33eb594 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -48,16 +48,12 @@ import { scrollToTop } from '@/scripts/scroll.js'; import { globalEvents } from '@/events.js'; import { injectPageMetadata } from '@/scripts/page-metadata.js'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; +import { PageHeaderItem } from '@/types/page-header.js'; const props = withDefaults(defineProps<{ tabs?: Tab[]; tab?: string; - actions?: { - text: string; - icon: string; - highlighted?: boolean; - handler: (ev: MouseEvent) => void; - }[]; + actions?: PageHeaderItem[]; thin?: boolean; displayMyAvatar?: boolean; }>(), { diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index 59ee04138617..47f46fe6cf86 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -39,6 +39,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + +
@@ -72,6 +77,7 @@ import FormLink from '@/components/form/link.vue'; let enableRegistration: boolean = $ref(false); let emailRequiredForSignup: boolean = $ref(false); let sensitiveWords: string = $ref(''); +let hiddenTags: string = $ref(''); let preservedUsernames: string = $ref(''); let tosUrl: string | null = $ref(null); let privacyPolicyUrl: string | null = $ref(null); @@ -81,6 +87,7 @@ async function init() { enableRegistration = !meta.disableRegistration; emailRequiredForSignup = meta.emailRequiredForSignup; sensitiveWords = meta.sensitiveWords.join('\n'); + hiddenTags = meta.hiddenTags.join('\n'); preservedUsernames = meta.preservedUsernames.join('\n'); tosUrl = meta.tosUrl; privacyPolicyUrl = meta.privacyPolicyUrl; @@ -93,6 +100,7 @@ function save() { tosUrl, privacyPolicyUrl, sensitiveWords: sensitiveWords.split('\n'), + hiddenTags: hiddenTags.split('\n'), preservedUsernames: preservedUsernames.split('\n'), }).then(() => { fetchInstance(); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 1d41fe7529b0..dc374e29251d 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -86,6 +86,9 @@ import { defaultStore } from '@/store.js'; import MkNote from '@/components/MkNote.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import { PageHeaderItem } from '@/types/page-header.js'; +import { isSupportShare } from '@/scripts/navigator.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const router = useRouter(); @@ -167,24 +170,40 @@ async function search() { const headerActions = $computed(() => { if (channel && channel.userId) { - const share = { - icon: 'ti ti-share', - text: i18n.ts.share, + const headerItems: PageHeaderItem[] = []; + + headerItems.push({ + icon: 'ti ti-link', + text: i18n.ts.copyUrl, handler: async (): Promise => { - navigator.share({ - title: channel.name, - text: channel.description, - url: `${url}/channels/${channel.id}`, - }); + copyToClipboard(`${url}/channels/${channel.id}`); + os.success(); }, - }; - - const canEdit = ($i && $i.id === channel.userId) || iAmModerator; - return canEdit ? [share, { - icon: 'ti ti-settings', - text: i18n.ts.edit, - handler: edit, - }] : [share]; + }); + + if (isSupportShare()) { + headerItems.push({ + icon: 'ti ti-share', + text: i18n.ts.share, + handler: async (): Promise => { + navigator.share({ + title: channel.name, + text: channel.description, + url: `${url}/channels/${channel.id}`, + }); + }, + }); + } + + if (($i && $i.id === channel.userId) || iAmModerator) { + headerItems.push({ + icon: 'ti ti-settings', + text: i18n.ts.edit, + handler: edit, + }); + } + + return headerItems.length > 0 ? headerItems : null; } else { return null; } diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 4573bbb81c95..b32c8a3864e6 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -36,6 +36,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { url } from '@/config.js'; import MkButton from '@/components/MkButton.vue'; import { clipsCache } from '@/cache'; +import { isSupportShare } from '@/scripts/navigator.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const props = defineProps<{ clipId: string, @@ -118,6 +120,13 @@ const headerActions = $computed(() => clip && isOwned ? [{ clipsCache.delete(); }, }, ...(clip.isPublic ? [{ + icon: 'ti ti-link', + text: i18n.ts.copyUrl, + handler: async (): Promise => { + copyToClipboard(`${url}/clips/${clip.id}`); + os.success(); + }, +}] : []), ...(clip.isPublic && isSupportShare() ? [{ icon: 'ti ti-share', text: i18n.ts.share, handler: async (): Promise => { diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index ebf117ffbfcb..4755eb506245 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -18,7 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ flash.likedCount }} {{ flash.likedCount }} - + +
@@ -70,6 +71,8 @@ import MkFolder from '@/components/MkFolder.vue'; import MkCode from '@/components/MkCode.vue'; import { defaultStore } from '@/store.js'; import { $i } from '@/account.js'; +import { isSupportShare } from '@/scripts/navigator.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const props = defineProps<{ id: string; @@ -89,6 +92,11 @@ function fetchFlash() { }); } +function copyLink() { + copyToClipboard(`${url}/play/${flash.id}`); + os.success(); +} + function share() { navigator.share({ title: flash.title, diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 3863348eaef5..5b551f75b571 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -29,7 +29,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
@@ -74,6 +75,8 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { defaultStore } from '@/store.js'; import { $i } from '@/account.js'; +import { isSupportShare } from '@/scripts/navigator.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const router = useRouter(); @@ -102,6 +105,11 @@ function fetchPost() { }); } +function copyLink() { + copyToClipboard(`${url}/gallery/${post.id}`); + os.success(); +} + function share() { navigator.share({ title: post.title, diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 98cbaab2bba8..2bc053ccfe0b 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -34,7 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
@@ -90,6 +91,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { pageViewInterruptors, defaultStore } from '@/store.js'; import { deepClone } from '@/scripts/clone.js'; import { $i } from '@/account.js'; +import { isSupportShare } from '@/scripts/navigator.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const props = defineProps<{ pageName: string; @@ -136,6 +139,11 @@ function share() { }); } +function copyLink() { + copyToClipboard(`${url}/@${page.user.username}/pages/${page.name}`); + os.success(); +} + function shareWithNote() { os.post({ initialText: `${page.title || page.name} ${url}/@${page.user.username}/pages/${page.name}`, diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue index d66457e8232e..1d77e5931dbf 100644 --- a/packages/frontend/src/pages/share.vue +++ b/packages/frontend/src/pages/share.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only :renote="renote" :initialVisibleUsers="visibleUsers" class="_panel" - @posted="state = 'posted'" + @posted="onPosted" />
{{ i18n.ts.close }} @@ -32,20 +32,20 @@ SPDX-License-Identifier: AGPL-3.0-only