Merge d6b7d24394
into a874def344
This commit is contained in:
commit
a335e1991a
|
@ -20,6 +20,12 @@
|
|||
- Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応
|
||||
- Enhance: acctに `.` が入っているユーザーのメンションに対応
|
||||
- Fix: Unicode絵文字に隣接する異体字セレクタ(`U+FE0F`)が絵文字として認識される問題を修正
|
||||
- Enhance: ノートの投稿に関するロールポリシーを強化しました
|
||||
- ノートの投稿を一切禁止できるように
|
||||
- リノート・引用の可否
|
||||
- 指名ノートの投稿の可否
|
||||
- 連合するノートの投稿の可否
|
||||
- ノートに添付できるファイルの最大数
|
||||
- Enhance: ユーザー検索をロールポリシーで制限できるように
|
||||
|
||||
### Client
|
||||
|
|
|
@ -5529,6 +5529,10 @@ export interface Locale extends ILocale {
|
|||
* ベータ版の検証にご協力いただきありがとうございます!
|
||||
*/
|
||||
"thankYouForTestingBeta": string;
|
||||
/**
|
||||
* お使いのアカウントにはノートを投稿する権限がありません。
|
||||
*/
|
||||
"youAreNotAllowedToCreateNote": string;
|
||||
"_order": {
|
||||
/**
|
||||
* 新しい順
|
||||
|
@ -7879,6 +7883,38 @@ export interface Locale extends ILocale {
|
|||
* ウォーターマーク機能の使用可否
|
||||
*/
|
||||
"watermarkAvailable": string;
|
||||
/**
|
||||
* ノートの投稿を許可
|
||||
*/
|
||||
"canNote": string;
|
||||
/**
|
||||
* リノート・引用を許可
|
||||
*/
|
||||
"renotePolicy": string;
|
||||
/**
|
||||
* リノート・引用を許可
|
||||
*/
|
||||
"renotePolicy_allow": string;
|
||||
/**
|
||||
* リノートのみ許可
|
||||
*/
|
||||
"renotePolicy_renoteOnly": string;
|
||||
/**
|
||||
* リノート・引用を禁止
|
||||
*/
|
||||
"renotePolicy_disallow": string;
|
||||
/**
|
||||
* 指名ノートの投稿を許可
|
||||
*/
|
||||
"canCreateSpecifiedNote": string;
|
||||
/**
|
||||
* 連合するノートの投稿を許可
|
||||
*/
|
||||
"canFederateNote": string;
|
||||
/**
|
||||
* ノートに添付できるファイルの最大数
|
||||
*/
|
||||
"noteFilesLimit": string;
|
||||
};
|
||||
"_condition": {
|
||||
/**
|
||||
|
|
|
@ -1377,6 +1377,7 @@ pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プ
|
|||
customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。"
|
||||
themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。"
|
||||
thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!"
|
||||
youAreNotAllowedToCreateNote: "お使いのアカウントにはノートを投稿する権限がありません。"
|
||||
|
||||
_order:
|
||||
newest: "新しい順"
|
||||
|
@ -2040,6 +2041,14 @@ _role:
|
|||
uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。"
|
||||
noteDraftLimit: "サーバーサイドのノートの下書きの作成可能数"
|
||||
watermarkAvailable: "ウォーターマーク機能の使用可否"
|
||||
canNote: "ノートの投稿を許可"
|
||||
renotePolicy: "リノート・引用を許可"
|
||||
renotePolicy_allow: "リノート・引用を許可"
|
||||
renotePolicy_renoteOnly: "リノートのみ許可"
|
||||
renotePolicy_disallow: "リノート・引用を禁止"
|
||||
canCreateSpecifiedNote: "指名ノートの投稿を許可"
|
||||
canFederateNote: "連合するノートの投稿を許可"
|
||||
noteFilesLimit: "ノートに添付できるファイルの最大数"
|
||||
_condition:
|
||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
isLocal: "ローカルユーザー"
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||
|
||||
export const MAX_NOTE_ATTACHMENTS = 16;
|
||||
|
||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
|
||||
|
||||
|
|
|
@ -229,6 +229,16 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
isBot: MiUser['isBot'];
|
||||
isCat: MiUser['isCat'];
|
||||
}, data: Option, silent = false): Promise<MiNote> {
|
||||
const userPolicies = await this.roleService.getUserPolicies(user.id);
|
||||
|
||||
if (!userPolicies.canNote) {
|
||||
throw new IdentifiableError('ebd9b2a9-4d95-4b01-8824-e701629b65e7', 'You are not allowed to create notes');
|
||||
}
|
||||
|
||||
if (data.files != null && data.files.length > userPolicies.noteFilesLimit) {
|
||||
throw new IdentifiableError('80dc1304-d910-4daa-b26f-4220b6c944ff', 'Too many files attached to note');
|
||||
}
|
||||
|
||||
// チャンネル外にリプライしたら対象のスコープに合わせる
|
||||
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
||||
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
||||
|
@ -256,7 +266,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
const sensitiveWords = this.meta.sensitiveWords;
|
||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||
data.visibility = 'home';
|
||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||
} else if (userPolicies.canPublicNote === false) {
|
||||
data.visibility = 'home';
|
||||
}
|
||||
}
|
||||
|
@ -303,8 +313,19 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
const isRenote = this.isRenote(data);
|
||||
const isQuote = isRenote ? this.isQuote(data) : false;
|
||||
|
||||
if (isRenote && userPolicies.renotePolicy === 'disallow') {
|
||||
throw new IdentifiableError('d35d80dc-02ba-4c9b-b9b8-905d306dcb67', 'You are not allowed to renote');
|
||||
}
|
||||
|
||||
if (isQuote && (userPolicies.renotePolicy === 'disallow' || userPolicies.renotePolicy === 'renoteOnly')) {
|
||||
throw new IdentifiableError('3a97010b-c338-4cdf-a567-24c54b67726e', 'You are not allowed to quote');
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (this.isRenote(data) && !this.isQuote(data)) {
|
||||
if (isRenote && !isQuote) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
|
@ -374,6 +395,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
if (data.visibility === 'specified') {
|
||||
if (data.visibleUsers == null) throw new Error('invalid param');
|
||||
if (!userPolicies.canCreateSpecifiedNote) throw new IdentifiableError('80d26afb-d466-4d86-9c01-11b9cad9da24', 'You are not allowed to send direct notes');
|
||||
|
||||
for (const u of data.visibleUsers) {
|
||||
if (!mentionedUsers.some(x => x.id === u.id)) {
|
||||
|
@ -384,6 +406,12 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) {
|
||||
data.visibleUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId }));
|
||||
}
|
||||
|
||||
if (!userPolicies.canFederateNote && data.visibleUsers.some(u => this.userEntityService.isRemoteUser(u))) {
|
||||
throw new IdentifiableError('5bbfae8d-097c-4c58-93f4-bc242d600529', 'You are not allowed to send direct notes to remote users');
|
||||
}
|
||||
} else if (!userPolicies.canFederateNote) {
|
||||
data.localOnly = true;
|
||||
}
|
||||
|
||||
if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
|||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
import { MAX_NOTE_ATTACHMENTS } from '@/const.js';
|
||||
|
||||
export type RolePolicies = {
|
||||
gtlAvailable: boolean;
|
||||
|
@ -69,6 +70,11 @@ export type RolePolicies = {
|
|||
uploadableFileTypes: string[];
|
||||
noteDraftLimit: number;
|
||||
watermarkAvailable: boolean;
|
||||
canNote: boolean;
|
||||
renotePolicy: 'allow' | 'renoteOnly' | 'disallow';
|
||||
canCreateSpecifiedNote: boolean;
|
||||
canFederateNote: boolean;
|
||||
noteFilesLimit: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_POLICIES: RolePolicies = {
|
||||
|
@ -115,6 +121,11 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
],
|
||||
noteDraftLimit: 10,
|
||||
watermarkAvailable: true,
|
||||
canNote: true,
|
||||
renotePolicy: 'allow',
|
||||
canCreateSpecifiedNote: true,
|
||||
canFederateNote: true,
|
||||
noteFilesLimit: MAX_NOTE_ATTACHMENTS,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
@ -392,6 +403,12 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
return 'unavailable';
|
||||
}
|
||||
|
||||
function aggregateRenotePolicy(vs: RolePolicies['renotePolicy'][]) {
|
||||
if (vs.some(v => v === 'allow')) return 'allow';
|
||||
if (vs.some(v => v === 'renoteOnly')) return 'renoteOnly';
|
||||
return 'disallow';
|
||||
}
|
||||
|
||||
return {
|
||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||
|
@ -439,6 +456,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
}),
|
||||
noteDraftLimit: calc('noteDraftLimit', vs => Math.max(...vs)),
|
||||
watermarkAvailable: calc('watermarkAvailable', vs => vs.some(v => v === true)),
|
||||
canNote: calc('canNote', vs => vs.some(v => v === true)),
|
||||
renotePolicy: calc('renotePolicy', aggregateRenotePolicy),
|
||||
canCreateSpecifiedNote: calc('canCreateSpecifiedNote', vs => vs.some(v => v === true)),
|
||||
canFederateNote: calc('canFederateNote', vs => vs.some(v => v === true)),
|
||||
noteFilesLimit: calc('noteFilesLimit', vs => Math.min(Math.max(...vs), MAX_NOTE_ATTACHMENTS)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -321,6 +321,27 @@ export const packedRolePoliciesSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canNote: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
renotePolicy: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['allow', 'renoteOnly', 'disallow'],
|
||||
},
|
||||
canCreateSpecifiedNote: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canFederateNote: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noteFilesLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesR
|
|||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { MAX_NOTE_ATTACHMENTS, MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
|
@ -162,14 +162,14 @@ export const paramDef = {
|
|||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
maxItems: MAX_NOTE_ATTACHMENTS,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
mediaIds: {
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
maxItems: MAX_NOTE_ATTACHMENTS,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
poll: {
|
||||
|
@ -396,10 +396,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
} catch (e) {
|
||||
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||
if (e instanceof IdentifiableError) {
|
||||
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
|
||||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
switch (e.id) {
|
||||
case '689ee33f-f97c-479a-ac49-1b9f8140af99':
|
||||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
case '9f466dab-c856-48cd-9e65-ff90ff750580':
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
@ -114,6 +114,11 @@ export const ROLE_POLICIES = [
|
|||
'uploadableFileTypes',
|
||||
'noteDraftLimit',
|
||||
'watermarkAvailable',
|
||||
'canNote',
|
||||
'renotePolicy',
|
||||
'canCreateSpecifiedNote',
|
||||
'canFederateNote',
|
||||
'noteFilesLimit',
|
||||
] as const;
|
||||
|
||||
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
|
||||
|
|
|
@ -306,7 +306,10 @@ const showSoftWordMutedWord = computed(() => prefer.s.showSoftWordMutedWord);
|
|||
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
|
||||
const translating = ref(false);
|
||||
const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.user.instance);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i?.id));
|
||||
const canRenote = computed(() => (
|
||||
(['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i?.id)) &&
|
||||
($i == null || ($i.policies.canNote && $i.policies.renotePolicy !== 'disallow'))
|
||||
));
|
||||
const renoteCollapsed = ref(
|
||||
prefer.s.collapseRenotes && isRenote && (
|
||||
($i && ($i.id === note.userId || $i.id === appearNote.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
|
||||
|
|
|
@ -326,7 +326,10 @@ const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.renot
|
|||
const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.user.instance);
|
||||
const conversation = ref<Misskey.entities.Note[]>([]);
|
||||
const replies = ref<Misskey.entities.Note[]>([]);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i?.id);
|
||||
const canRenote = computed(() => (
|
||||
(['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i?.id)) &&
|
||||
($i == null || ($i.policies.canNote && $i.policies.renotePolicy !== 'disallow'))
|
||||
));
|
||||
|
||||
useGlobalEvent('noteDeleted', (noteId) => {
|
||||
if (noteId === note.id || noteId === appearNote.id) {
|
||||
|
|
|
@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="$style.headerRightButtonText">{{ targetChannel.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="targetChannel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
||||
<button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="targetChannel != null || visibility === 'specified' || !$i.policies.canFederateNote" @click="toggleLocalOnly">
|
||||
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
||||
<span v-else><i class="ti ti-rocket-off"></i></span>
|
||||
</button>
|
||||
|
@ -61,7 +61,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button class="_buttonPrimary" style="padding: 4px; border-radius: 8px;" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||
<MkInfo v-if="!$i.policies.canNote" warn :class="$style.formWarn">{{ i18n.ts.youAreNotAllowedToCreateNote }}</MkInfo>
|
||||
<MkInfo v-else-if="hasNotSpecifiedMentions" warn :class="$style.formWarn">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||
<div v-show="useCw" :class="$style.cwOuter">
|
||||
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
|
||||
<div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
|
||||
|
@ -85,8 +86,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<footer :class="$style.footer">
|
||||
<div :class="$style.footerLeft">
|
||||
<button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.upload + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromPc"><i class="ti ti-photo-plus"></i></button>
|
||||
<button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.fromDrive + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromDrive"><i class="ti ti-cloud-download"></i></button>
|
||||
<button v-if="$i.policies.noteFilesLimit > 0" v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.upload + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromPc"><i class="ti ti-photo-plus"></i></button>
|
||||
<button v-if="$i.policies.noteFilesLimit > 0" v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.fromDrive + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromDrive"><i class="ti ti-cloud-download"></i></button>
|
||||
<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
|
||||
<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
|
||||
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
|
||||
|
@ -280,25 +281,44 @@ const cwTextLength = computed((): number => {
|
|||
const maxCwTextLength = 100;
|
||||
|
||||
const canPost = computed((): boolean => {
|
||||
return !props.mock && !posting.value && !posted.value && !uploader.uploading.value && (uploader.items.value.length === 0 || uploader.readyForUpload.value) &&
|
||||
(
|
||||
1 <= textLength.value ||
|
||||
1 <= files.value.length ||
|
||||
1 <= uploader.items.value.length ||
|
||||
poll.value != null ||
|
||||
renoteTargetNote.value != null ||
|
||||
quoteId.value != null
|
||||
) &&
|
||||
(textLength.value <= maxTextLength.value) &&
|
||||
(
|
||||
useCw.value ?
|
||||
(
|
||||
cw.value != null && cw.value.trim() !== '' &&
|
||||
cwTextLength.value <= maxCwTextLength
|
||||
) : true
|
||||
) &&
|
||||
(files.value.length <= 16) &&
|
||||
(!poll.value || poll.value.choices.length >= 2);
|
||||
const isNotMock = !props.mock;
|
||||
|
||||
const canNote = $i.policies.canNote;
|
||||
const canQuote = renoteTargetNote.value ? $i.policies.renotePolicy === 'allow' : true;
|
||||
|
||||
const isNotPosting = !posting.value && !posted.value;
|
||||
const isNotUploading = !uploader.uploading.value;
|
||||
const isUploaderReady = uploader.items.value.length === 0 || uploader.readyForUpload.value;
|
||||
|
||||
const hasContent = (
|
||||
textLength.value >= 1 ||
|
||||
files.value.length >= 1 ||
|
||||
uploader.items.value.length >= 1 ||
|
||||
poll.value != null ||
|
||||
renoteTargetNote.value != null ||
|
||||
quoteId.value != null
|
||||
);
|
||||
|
||||
const isTextLengthValid = textLength.value <= maxTextLength.value;
|
||||
const isCwValid = useCw.value
|
||||
? cw.value != null && cw.value.trim() !== '' && cwTextLength.value <= maxCwTextLength
|
||||
: true;
|
||||
const isFilesCountValid = files.value.length <= $i.policies.noteFilesLimit;
|
||||
const isPollValid = !poll.value || poll.value.choices.length >= 2;
|
||||
|
||||
return (
|
||||
isNotMock &&
|
||||
canNote &&
|
||||
canQuote &&
|
||||
isNotPosting &&
|
||||
isNotUploading &&
|
||||
isUploaderReady &&
|
||||
hasContent &&
|
||||
isTextLengthValid &&
|
||||
isCwValid &&
|
||||
isFilesCountValid &&
|
||||
isPollValid
|
||||
);
|
||||
});
|
||||
|
||||
// cannot save pure renote as draft
|
||||
|
@ -699,7 +719,7 @@ async function onPaste(ev: ClipboardEvent) {
|
|||
|
||||
const paste = ev.clipboardData.getData('text');
|
||||
|
||||
if (!renoteTargetNote.value && !quoteId.value && paste.startsWith(url + '/notes/')) {
|
||||
if (!renoteTargetNote.value && !quoteId.value && paste.startsWith(url + '/notes/') && $i.policies.renotePolicy === 'allow') {
|
||||
ev.preventDefault();
|
||||
|
||||
os.confirm({
|
||||
|
@ -1512,7 +1532,7 @@ html[data-color-scheme=light] .preview {
|
|||
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
|
||||
}
|
||||
|
||||
.hasNotSpecifiedMentions {
|
||||
.formWarn {
|
||||
margin: 0 20px 16px 20px;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</Sortable>
|
||||
<p
|
||||
:class="[$style.remain, {
|
||||
[$style.exceeded]: props.modelValue.length > 16,
|
||||
[$style.exceeded]: props.modelValue.length > $i.policies.noteFilesLimit,
|
||||
}]"
|
||||
>
|
||||
{{ props.modelValue.length }}/16
|
||||
{{ props.modelValue.length }}/{{ $i.policies.noteFilesLimit }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { defineAsyncComponent, inject } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MenuItem } from '@/types/menu';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard';
|
||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
@ -47,6 +48,8 @@ import { globalEvents } from '@/events.js';
|
|||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Misskey.entities.DriveFile[];
|
||||
detachMediaFn?: (id: string) => void;
|
||||
|
|
|
@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.followersDescription }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button key="specified" :disabled="localOnly" class="_button" :class="[$style.item, { [$style.active]: v === 'specified' }]" data-index="4" @click="choose('specified')">
|
||||
<button key="specified" :disabled="!$i.policies.canCreateSpecifiedNote || localOnly" class="_button" :class="[$style.item, { [$style.active]: v === 'specified' }]" data-index="4" @click="choose('specified')">
|
||||
<div :class="$style.icon"><i class="ti ti-mail"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.specified }}</span>
|
||||
|
@ -44,9 +44,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { nextTick, useTemplateRef, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@ -115,11 +118,16 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void {
|
|||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
&:disabled {
|
||||
opacity: 0.8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:not(:disabled):active {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canNote, 'canNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canNote }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.canNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.canNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canNote)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.canNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="role.policies.canNote.value" :disabled="role.policies.canNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
||||
<template #suffix>
|
||||
|
@ -165,6 +185,88 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.renotePolicy, 'renotePolicy'])">
|
||||
<template #label>{{ i18n.ts._role._options.renotePolicy }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.renotePolicy.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.renotePolicy.value === 'allow' ? i18n.ts._role._options.renotePolicy_allow : role.policies.renotePolicy.value === 'renoteOnly' ? i18n.ts._role._options.renotePolicy_renoteOnly : i18n.ts._role._options.renotePolicy_disallow }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.renotePolicy)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.renotePolicy.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSelect v-model="role.policies.renotePolicy.value" :disabled="role.policies.renotePolicy.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
<option value="allow">{{ i18n.ts._role._options.renotePolicy_allow }}</option>
|
||||
<option value="renoteOnly">{{ i18n.ts._role._options.renotePolicy_renoteOnly }}</option>
|
||||
<option value="disallow">{{ i18n.ts._role._options.renotePolicy_disallow }}</option>
|
||||
</MkSelect>
|
||||
<MkRange v-model="role.policies.renotePolicy.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canCreateSpecifiedNote, 'canCreateSpecifiedNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canCreateSpecifiedNote }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.canCreateSpecifiedNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.canCreateSpecifiedNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canCreateSpecifiedNote)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.canCreateSpecifiedNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="role.policies.canCreateSpecifiedNote.value" :disabled="role.policies.canCreateSpecifiedNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canCreateSpecifiedNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canFederateNote, 'canFederateNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canFederateNote }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.canFederateNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.canFederateNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canFederateNote)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.canFederateNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="role.policies.canFederateNote.value" :disabled="role.policies.canFederateNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canFederateNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.noteFilesLimit, 'noteFilesLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.noteFilesLimit }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.noteFilesLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.noteFilesLimit.value }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.noteFilesLimit)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.noteFilesLimit.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="role.policies.noteFilesLimit.value" :disabled="role.policies.noteFilesLimit.useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
<MkRange v-model="role.policies.noteFilesLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.chatAvailability, 'chatAvailability'])">
|
||||
<template #label>{{ i18n.ts._role._options.chatAvailability }}</template>
|
||||
<template #suffix>
|
||||
|
|
|
@ -41,6 +41,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canNote, 'canNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canNote }}</template>
|
||||
<template #suffix>{{ policies.canNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
<MkSwitch v-model="policies.canNote">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
|
||||
<template #suffix>{{ policies.canPublicNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
|
@ -49,6 +57,40 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.renotePolicy, 'renotePolicy'])">
|
||||
<template #label>{{ i18n.ts._role._options.renotePolicy }}</template>
|
||||
<template #suffix>{{ policies.renotePolicy.value === 'allow' ? i18n.ts._role._options.renotePolicy_allow : policies.renotePolicy.value === 'renoteOnly' ? i18n.ts._role._options.renotePolicy_renoteOnly : i18n.ts._role._options.renotePolicy_disallow }}</template>
|
||||
<MkSelect v-model="policies.renotePolicy">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
<option value="allow">{{ i18n.ts._role._options.renotePolicy_allow }}</option>
|
||||
<option value="renoteOnly">{{ i18n.ts._role._options.renotePolicy_renoteOnly }}</option>
|
||||
<option value="disallow">{{ i18n.ts._role._options.renotePolicy_disallow }}</option>
|
||||
</MkSelect>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canCreateSpecifiedNote, 'canCreateSpecifiedNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canCreateSpecifiedNote }}</template>
|
||||
<template #suffix>{{ policies.canCreateSpecifiedNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
<MkSwitch v-model="policies.canCreateSpecifiedNote">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canFederateNote, 'canFederateNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canFederateNote }}</template>
|
||||
<template #suffix>{{ policies.canFederateNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
<MkSwitch v-model="policies.canFederateNote">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.noteFilesLimit, 'noteFilesLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.noteFilesLimit }}</template>
|
||||
<template #suffix>{{ policies.noteFilesLimit }}</template>
|
||||
<MkInput v-model="policies.noteFilesLimit" type="number" max="16" min="0">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.chatAvailability, 'chatAvailability'])">
|
||||
<template #label>{{ i18n.ts._role._options.chatAvailability }}</template>
|
||||
<template #suffix>{{ policies.chatAvailability === 'available' ? i18n.ts.yes : policies.chatAvailability === 'readonly' ? i18n.ts.readonly : i18n.ts.no }}</template>
|
||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i class="ti ti-pencil" :class="$style.fileNameEditIcon"></i>
|
||||
</button>
|
||||
<div :class="$style.fileQuickActionsOthers">
|
||||
<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
|
||||
<button v-if="$i.policies.noteFilesLimit > 0" v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
|
||||
<i class="ti ti-pencil"></i>
|
||||
</button>
|
||||
<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
|
||||
|
@ -76,6 +76,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
|||
import MkMediaList from '@/components/MkMediaList.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
@ -85,6 +86,8 @@ import { globalEvents } from '@/events.js';
|
|||
|
||||
const router = useRouter();
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const props = defineProps<{
|
||||
fileId: string;
|
||||
}>();
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
import * as Misskey from 'misskey-js';
|
||||
|
||||
export function getAppearNote(note: Misskey.entities.Note) {
|
||||
return Misskey.note.isPureRenote(note) ? note.renote : note;
|
||||
return Misskey.note.isPureRenote(note) ? note.renote! : note;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import * as os from '@/os.js';
|
|||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
function rename(file: Misskey.entities.DriveFile) {
|
||||
os.inputText({
|
||||
|
@ -116,13 +117,19 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||
action: () => describe(file),
|
||||
});
|
||||
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
text: i18n.ts.createNoteFromTheFile,
|
||||
icon: 'ti ti-pencil',
|
||||
action: () => os.post({
|
||||
initialFiles: [file],
|
||||
}),
|
||||
}, {
|
||||
menuItems.push({ type: 'divider' });
|
||||
|
||||
if ($i != null && $i.policies.noteFilesLimit > 0) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.createNoteFromTheFile,
|
||||
icon: 'ti ti-pencil',
|
||||
action: () => os.post({
|
||||
initialFiles: [file],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
text: i18n.ts.copyUrl,
|
||||
icon: 'ti ti-link',
|
||||
action: () => copyUrl(file),
|
||||
|
|
|
@ -554,6 +554,12 @@ export function getRenoteMenu(props: {
|
|||
renoteButton: ShallowRef<HTMLElement | null | undefined>;
|
||||
mock?: boolean;
|
||||
}) {
|
||||
if ($i?.policies.renotePolicy === 'disallow') {
|
||||
return {
|
||||
menu: [],
|
||||
};
|
||||
}
|
||||
|
||||
const appearNote = getAppearNote(props.note);
|
||||
|
||||
const channelRenoteItems: MenuItem[] = [];
|
||||
|
@ -561,7 +567,7 @@ export function getRenoteMenu(props: {
|
|||
const normalExternalChannelRenoteItems: MenuItem[] = [];
|
||||
|
||||
if (appearNote.channel) {
|
||||
channelRenoteItems.push(...[{
|
||||
channelRenoteItems.push({
|
||||
text: i18n.ts.inChannelRenote,
|
||||
icon: 'ti ti-repeat',
|
||||
action: () => {
|
||||
|
@ -585,22 +591,26 @@ export function getRenoteMenu(props: {
|
|||
});
|
||||
}
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.inChannelQuote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
if (!props.mock) {
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
channel: appearNote.channel,
|
||||
});
|
||||
}
|
||||
},
|
||||
}]);
|
||||
});
|
||||
|
||||
if ($i?.policies.renotePolicy === 'allow') {
|
||||
channelRenoteItems.push({
|
||||
text: i18n.ts.inChannelQuote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
if (!props.mock) {
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
channel: appearNote.channel!,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!appearNote.channel || appearNote.channel.allowRenoteToExternal) {
|
||||
normalRenoteItems.push(...[{
|
||||
normalRenoteItems.push({
|
||||
text: i18n.ts.renote,
|
||||
icon: 'ti ti-repeat',
|
||||
action: () => {
|
||||
|
@ -634,15 +644,19 @@ export function getRenoteMenu(props: {
|
|||
});
|
||||
}
|
||||
},
|
||||
}, ...(props.mock ? [] : [{
|
||||
text: i18n.ts.quote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
});
|
||||
},
|
||||
}])]);
|
||||
});
|
||||
|
||||
if (!props.mock && $i?.policies.renotePolicy === 'allow') {
|
||||
normalRenoteItems.push({
|
||||
text: i18n.ts.quote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
normalExternalChannelRenoteItems.push({
|
||||
type: 'parent',
|
||||
|
|
|
@ -5247,6 +5247,12 @@ export type components = {
|
|||
chatAvailability: 'available' | 'readonly' | 'unavailable';
|
||||
noteDraftLimit: number;
|
||||
watermarkAvailable: boolean;
|
||||
canNote: boolean;
|
||||
/** @enum {string} */
|
||||
renotePolicy: 'allow' | 'renoteOnly' | 'disallow';
|
||||
canCreateSpecifiedNote: boolean;
|
||||
canFederateNote: boolean;
|
||||
noteFilesLimit: number;
|
||||
};
|
||||
ReversiGameLite: {
|
||||
/** Format: id */
|
||||
|
|
Loading…
Reference in New Issue