Fix: navigator.share未サポートの場合は共有ボタンを非表示にする(+URLのコピーボタンを設置) (#12506)

* navigator.share未サポートの場合は共有ボタンを非表示にする

* fix CHANGELOG.md

* ライセンス表示追加

* URLのコピーボタンを設置
This commit is contained in:
おさむのひと 2023-11-30 08:15:13 +09:00 committed by GitHub
parent 37cff405ed
commit 413f7bfb44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 29 deletions

View file

@ -26,11 +26,13 @@
- Enhance: リアクション選択時に音を鳴らせるように - Enhance: リアクション選択時に音を鳴らせるように
- Enhance: サウンドにドライブのファイルを使用できるように - Enhance: サウンドにドライブのファイルを使用できるように
- Enhance: Shareページで投稿を完了すると、親ウィンドウ親フレームにpostMessageするように - Enhance: Shareページで投稿を完了すると、親ウィンドウ親フレームにpostMessageするように
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Fix: コードエディタが正しく表示されない問題を修正 - Fix: コードエディタが正しく表示されない問題を修正
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正 - Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正 - Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
- Fix: 共有機能をサポートしていないブラウザの場合は共有ボタンを非表示にする #11305
- Fix: 通知のグルーピング設定を変更してもリロードされるまで表示が変わらない問題を修正 #12470 - Fix: 通知のグルーピング設定を変更してもリロードされるまで表示が変わらない問題を修正 #12470
### Server ### Server

View file

@ -48,16 +48,12 @@ import { scrollToTop } from '@/scripts/scroll.js';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
import { injectPageMetadata } from '@/scripts/page-metadata.js'; import { injectPageMetadata } from '@/scripts/page-metadata.js';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { PageHeaderItem } from '@/types/page-header.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
tabs?: Tab[]; tabs?: Tab[];
tab?: string; tab?: string;
actions?: { actions?: PageHeaderItem[];
text: string;
icon: string;
highlighted?: boolean;
handler: (ev: MouseEvent) => void;
}[];
thin?: boolean; thin?: boolean;
displayMyAvatar?: boolean; displayMyAvatar?: boolean;
}>(), { }>(), {

View file

@ -86,6 +86,9 @@ import { defaultStore } from '@/store.js';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import MkFoldableSection from '@/components/MkFoldableSection.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(); const router = useRouter();
@ -167,24 +170,40 @@ async function search() {
const headerActions = $computed(() => { const headerActions = $computed(() => {
if (channel && channel.userId) { if (channel && channel.userId) {
const share = { const headerItems: PageHeaderItem[] = [];
icon: 'ti ti-share',
text: i18n.ts.share,
handler: async (): Promise<void> => {
navigator.share({
title: channel.name,
text: channel.description,
url: `${url}/channels/${channel.id}`,
});
},
};
const canEdit = ($i && $i.id === channel.userId) || iAmModerator; headerItems.push({
return canEdit ? [share, { icon: 'ti ti-link',
icon: 'ti ti-settings', text: i18n.ts.copyUrl,
text: i18n.ts.edit, handler: async (): Promise<void> => {
handler: edit, copyToClipboard(`${url}/channels/${channel.id}`);
}] : [share]; os.success();
},
});
if (isSupportShare()) {
headerItems.push({
icon: 'ti ti-share',
text: i18n.ts.share,
handler: async (): Promise<void> => {
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 { } else {
return null; return null;
} }

View file

@ -36,6 +36,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { url } from '@/config.js'; import { url } from '@/config.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { clipsCache } from '@/cache'; import { clipsCache } from '@/cache';
import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
const props = defineProps<{ const props = defineProps<{
clipId: string, clipId: string,
@ -118,6 +120,13 @@ const headerActions = $computed(() => clip && isOwned ? [{
clipsCache.delete(); clipsCache.delete();
}, },
}, ...(clip.isPublic ? [{ }, ...(clip.isPublic ? [{
icon: 'ti ti-link',
text: i18n.ts.copyUrl,
handler: async (): Promise<void> => {
copyToClipboard(`${url}/clips/${clip.id}`);
os.success();
},
}] : []), ...(clip.isPublic && isSupportShare() ? [{
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.share, text: i18n.ts.share,
handler: async (): Promise<void> => { handler: async (): Promise<void> => {

View file

@ -18,7 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" asLike class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> <MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" asLike class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
<MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> <MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
<MkButton v-tooltip="i18n.ts.shareWithNote" class="button" rounded @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></MkButton> <MkButton v-tooltip="i18n.ts.shareWithNote" class="button" rounded @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></MkButton>
<MkButton v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton> <MkButton v-tooltip="i18n.ts.copyLink" class="button" rounded @click="copyLink"><i class="ti ti-link ti-fw"></i></MkButton>
<MkButton v-if="isSupportShare()" v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton>
</div> </div>
</div> </div>
<div v-else :class="$style.ready"> <div v-else :class="$style.ready">
@ -70,6 +71,8 @@ import MkFolder from '@/components/MkFolder.vue';
import MkCode from '@/components/MkCode.vue'; import MkCode from '@/components/MkCode.vue';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
const props = defineProps<{ const props = defineProps<{
id: string; id: string;
@ -89,6 +92,11 @@ function fetchFlash() {
}); });
} }
function copyLink() {
copyToClipboard(`${url}/play/${flash.id}`);
os.success();
}
function share() { function share() {
navigator.share({ navigator.share({
title: flash.title, title: flash.title,

View file

@ -29,7 +29,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="other"> <div class="other">
<button v-if="$i && $i.id === post.user.id" v-tooltip="i18n.ts.edit" v-click-anime class="_button" @click="edit"><i class="ti ti-pencil ti-fw"></i></button> <button v-if="$i && $i.id === post.user.id" v-tooltip="i18n.ts.edit" v-click-anime class="_button" @click="edit"><i class="ti ti-pencil ti-fw"></i></button>
<button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button> <button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button>
<button v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button> <button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ti ti-link ti-fw"></i></button>
<button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button>
</div> </div>
</div> </div>
<div class="user"> <div class="user">
@ -74,6 +75,8 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
const router = useRouter(); const router = useRouter();
@ -102,6 +105,11 @@ function fetchPost() {
}); });
} }
function copyLink() {
copyToClipboard(`${url}/gallery/${post.id}`);
os.success();
}
function share() { function share() {
navigator.share({ navigator.share({
title: post.title, title: post.title,

View file

@ -34,7 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div class="other"> <div class="other">
<button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button> <button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button>
<button v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button> <button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ti ti-link ti-fw"></i></button>
<button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button>
</div> </div>
</div> </div>
<div class="user"> <div class="user">
@ -90,6 +91,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { pageViewInterruptors, defaultStore } from '@/store.js'; import { pageViewInterruptors, defaultStore } from '@/store.js';
import { deepClone } from '@/scripts/clone.js'; import { deepClone } from '@/scripts/clone.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
const props = defineProps<{ const props = defineProps<{
pageName: string; pageName: string;
@ -136,6 +139,11 @@ function share() {
}); });
} }
function copyLink() {
copyToClipboard(`${url}/@${page.user.username}/pages/${page.name}`);
os.success();
}
function shareWithNote() { function shareWithNote() {
os.post({ os.post({
initialText: `${page.title || page.name} ${url}/@${page.user.username}/pages/${page.name}`, initialText: `${page.title || page.name} ${url}/@${page.user.username}/pages/${page.name}`,

View file

@ -18,6 +18,7 @@ import { getUserMenu } from '@/scripts/get-user-menu.js';
import { clipsCache } from '@/cache.js'; import { clipsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
export async function getNoteClipMenu(props: { export async function getNoteClipMenu(props: {
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -280,11 +281,11 @@ export function getNoteMenu(props: {
window.open(appearNote.url ?? appearNote.uri, '_blank'); window.open(appearNote.url ?? appearNote.uri, '_blank');
}, },
} : undefined, } : undefined,
{ ...(isSupportShare() ? [{
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.share, text: i18n.ts.share,
action: share, action: share,
}, }] : []),
$i && $i.policies.canUseTranslator && instance.translatorAvailable ? { $i && $i.policies.canUseTranslator && instance.translatorAvailable ? {
icon: 'ti ti-language-hiragana', icon: 'ti ti-language-hiragana',
text: i18n.ts.translate, text: i18n.ts.translate,
@ -484,7 +485,7 @@ export function getRenoteMenu(props: {
}]); }]);
} }
if (!appearNote.channel || appearNote.channel?.allowRenoteToExternal) { if (!appearNote.channel || appearNote.channel.allowRenoteToExternal) {
normalRenoteItems.push(...[{ normalRenoteItems.push(...[{
text: i18n.ts.renote, text: i18n.ts.renote,
icon: 'ti ti-repeat', icon: 'ti ti-repeat',

View file

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export function isSupportShare(): boolean {
return 'share' in navigator;
}

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type PageHeaderItem = {
text: string;
icon: string;
highlighted?: boolean;
handler: (ev: MouseEvent) => void;
};