Fix: navigator.share未サポートの場合は共有ボタンを非表示にする(+URLのコピーボタンを設置) (#12506)
* navigator.share未サポートの場合は共有ボタンを非表示にする * fix CHANGELOG.md * ライセンス表示追加 * URLのコピーボタンを設置
This commit is contained in:
parent
37cff405ed
commit
413f7bfb44
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}`,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
Loading…
Reference in New Issue