mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 20:44:34 +00:00
merge: Allow unauthenticated (logged-out) users to translate notes (!1055)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1055 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
commit
39fcdcae25
13 changed files with 49 additions and 40 deletions
|
@ -86,7 +86,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canManageCustomEmojis: false,
|
canManageCustomEmojis: false,
|
||||||
canManageAvatarDecorations: false,
|
canManageAvatarDecorations: false,
|
||||||
canSearchNotes: false,
|
canSearchNotes: false,
|
||||||
canUseTranslator: true,
|
canUseTranslator: false,
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
driveCapacityMb: 100,
|
driveCapacityMb: 100,
|
||||||
maxFileSizeMb: 25,
|
maxFileSizeMb: 25,
|
||||||
|
|
|
@ -344,14 +344,14 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin) {
|
if (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin) {
|
||||||
if (user == null) {
|
if (user == null && ep.meta.requireCredential !== 'optional') {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
message: 'Credential required.',
|
message: 'Credential required.',
|
||||||
code: 'CREDENTIAL_REQUIRED',
|
code: 'CREDENTIAL_REQUIRED',
|
||||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||||
httpStatusCode: 401,
|
httpStatusCode: 401,
|
||||||
});
|
});
|
||||||
} else if (user!.isSuspended) {
|
} else if (user?.isSuspended) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
message: 'Your account has been suspended.',
|
message: 'Your account has been suspended.',
|
||||||
code: 'YOUR_ACCOUNT_SUSPENDED',
|
code: 'YOUR_ACCOUNT_SUSPENDED',
|
||||||
|
@ -372,8 +372,8 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
|
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user?.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = user ? await this.roleService.getUserRoles(user) : [];
|
||||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
message: 'You are not assigned to a moderator role.',
|
message: 'You are not assigned to a moderator role.',
|
||||||
|
@ -392,9 +392,9 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user?.id)) {
|
||||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
const myRoles = user ? await this.roleService.getUserRoles(user) : [];
|
||||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
const policies = await this.roleService.getUserPolicies(user ?? null);
|
||||||
if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
message: 'You are not assigned to a required role.',
|
message: 'You are not assigned to a required role.',
|
||||||
|
@ -418,7 +418,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
// Cast non JSON input
|
// Cast non JSON input
|
||||||
if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
|
if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
|
||||||
for (const k of Object.keys(ep.params.properties)) {
|
for (const k of Object.keys(ep.params.properties)) {
|
||||||
const param = ep.params.properties![k];
|
const param = ep.params.properties[k];
|
||||||
if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
|
if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
|
||||||
try {
|
try {
|
||||||
data[k] = JSON.parse(data[k]);
|
data[k] = JSON.parse(data[k]);
|
||||||
|
|
|
@ -92,7 +92,7 @@ export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requir
|
||||||
}) | (Omit<IEndpointMetaBase, 'secure'> & {
|
}) | (Omit<IEndpointMetaBase, 'secure'> & {
|
||||||
secure: true,
|
secure: true,
|
||||||
}) | (Omit<IEndpointMetaBase, 'requireCredential' | 'kind'> & {
|
}) | (Omit<IEndpointMetaBase, 'requireCredential' | 'kind'> & {
|
||||||
requireCredential: true,
|
requireCredential: true | 'optional',
|
||||||
kind: (typeof permissions)[number],
|
kind: (typeof permissions)[number],
|
||||||
}) | (Omit<IEndpointMetaBase, 'requireModerator' | 'kind'> & {
|
}) | (Omit<IEndpointMetaBase, 'requireModerator' | 'kind'> & {
|
||||||
requireModerator: true,
|
requireModerator: true,
|
||||||
|
|
|
@ -20,11 +20,9 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
// TODO allow unauthenticated if default template allows?
|
requireCredential: 'optional',
|
||||||
// Maybe a value 'optional' that allows unauthenticated OR a token w/ appropriate role.
|
|
||||||
// This will allow unauthenticated requests without leaking post data to restricted clients.
|
|
||||||
requireCredential: true,
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
requiredRolePolicy: 'canUseTranslator',
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -88,17 +86,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private readonly loggerService: ApiLoggerService,
|
private readonly loggerService: ApiLoggerService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const policies = await this.roleService.getUserPolicies(me.id);
|
|
||||||
if (!policies.canUseTranslator) {
|
|
||||||
throw new ApiError(meta.errors.unavailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) {
|
if (!(await this.noteEntityService.isVisibleForMe(note, me?.id ?? null))) {
|
||||||
throw new ApiError(meta.errors.cannotTranslateInvisibleNote);
|
throw new ApiError(meta.errors.cannotTranslateInvisibleNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
||||||
|
@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
||||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||||
import { focusPrev, focusNext } from '@/utility/focus.js';
|
import { focusPrev, focusNext } from '@/utility/focus.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
@ -360,7 +360,7 @@ const keymap = {
|
||||||
clip();
|
clip();
|
||||||
},
|
},
|
||||||
't': () => {
|
't': () => {
|
||||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
translate();
|
translate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -169,7 +169,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
||||||
|
@ -278,7 +278,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
|
@ -388,7 +388,7 @@ const keymap = {
|
||||||
clip();
|
clip();
|
||||||
},
|
},
|
||||||
't': () => {
|
't': () => {
|
||||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
translate();
|
translate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
||||||
|
@ -113,7 +113,7 @@ import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||||
import { instance } from '@/instance';
|
import { instance, policies } from '@/instance';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
|
|
@ -158,7 +158,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
||||||
|
@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
||||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||||
import { focusPrev, focusNext } from '@/utility/focus.js';
|
import { focusPrev, focusNext } from '@/utility/focus.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
@ -360,7 +360,7 @@ const keymap = {
|
||||||
clip();
|
clip();
|
||||||
},
|
},
|
||||||
't': () => {
|
't': () => {
|
||||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
translate();
|
translate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -174,7 +174,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
||||||
|
@ -283,7 +283,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
|
@ -394,7 +394,7 @@ const keymap = {
|
||||||
clip();
|
clip();
|
||||||
},
|
},
|
||||||
't': () => {
|
't': () => {
|
||||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
translate();
|
translate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,7 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
||||||
<i class="ti ti-paperclip"></i>
|
<i class="ti ti-paperclip"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||||
<i class="ti ti-language-hiragana"></i>
|
<i class="ti ti-language-hiragana"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
||||||
|
@ -121,7 +121,7 @@ import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||||
import { instance } from '@/instance';
|
import { instance, policies } from '@/instance';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reactive } from 'vue';
|
import { computed, reactive } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, reactive } from 'vue';
|
import { computed, nextTick, reactive } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js';
|
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js';
|
||||||
|
import { $i } from '@/i';
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
||||||
|
@ -38,6 +39,8 @@ export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFA
|
||||||
|
|
||||||
export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true);
|
export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true);
|
||||||
|
|
||||||
|
export const policies = computed<Misskey.entities.RolePolicies>(() => $i?.policies ?? instance.policies);
|
||||||
|
|
||||||
export async function fetchInstance(force = false): Promise<Misskey.entities.MetaDetailed> {
|
export async function fetchInstance(force = false): Promise<Misskey.entities.MetaDetailed> {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
|
const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
|
||||||
|
@ -60,3 +63,8 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// instance export can be empty sometimes, which causes problems.
|
||||||
|
await fetchInstance().catch(err => {
|
||||||
|
console.warn('Initial meta fetch failed:', err);
|
||||||
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { Ref, ShallowRef } from 'vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance, policies } from '@/instance.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
|
@ -342,7 +342,7 @@ export function getNoteMenu(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($i.policies.canUseTranslator && instance.translatorAvailable) {
|
if (policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
icon: 'ti ti-language-hiragana',
|
icon: 'ti ti-language-hiragana',
|
||||||
text: i18n.ts.translate,
|
text: i18n.ts.translate,
|
||||||
|
@ -497,6 +497,14 @@ export function getNoteMenu(props: {
|
||||||
} else {
|
} else {
|
||||||
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed));
|
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||||
|
menuItems.push({
|
||||||
|
icon: 'ti ti-language-hiragana',
|
||||||
|
text: i18n.ts.translate,
|
||||||
|
action: () => translateNote(appearNote.id, props.translation, props.translating),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteActions = getPluginHandlers('note_action');
|
const noteActions = getPluginHandlers('note_action');
|
||||||
|
|
Loading…
Add table
Reference in a new issue