-
-
-
-
-
-
-
-
-
-
-
- {{ i18n.ts.deletedNote }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ i18n.ts.deletedNote }}
-
-
-
-
-
{{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ i18n.ts.deletedNote }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ i18n.ts.deletedNote }}
+
+
+
+
+ {{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -125,6 +175,12 @@ import * as os from '@/os.js';
import { $i } from '@/i.js';
import { misskeyApi } from '@/utility/misskey-api';
import { Paginator } from '@/utility/paginator.js';
+import MkTabs from '@/components/MkTabs.vue';
+import MkInfo from '@/components/MkInfo.vue';
+
+const props = defineProps<{
+ scheduled?: boolean;
+}>();
const emit = defineEmits<{
(ev: 'restore', draft: Misskey.entities.NoteDraft): void;
@@ -132,8 +188,20 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const paginator = markRaw(new Paginator('notes/drafts/list', {
+const tab = ref<'drafts' | 'scheduled'>(props.scheduled ? 'scheduled' : 'drafts');
+
+const draftsPaginator = markRaw(new Paginator('notes/drafts/list', {
limit: 10,
+ params: {
+ scheduled: false,
+ },
+}));
+
+const scheduledPaginator = markRaw(new Paginator('notes/drafts/list', {
+ limit: 10,
+ params: {
+ scheduled: true,
+ },
}));
const currentDraftsCount = ref(0);
@@ -162,7 +230,17 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) {
if (canceled) return;
os.apiWithDialog('notes/drafts/delete', { draftId: draft.id }).then(() => {
- paginator.reload();
+ draftsPaginator.reload();
+ });
+}
+
+async function cancelSchedule(draft: Misskey.entities.NoteDraft) {
+ os.apiWithDialog('notes/drafts/update', {
+ draftId: draft.id,
+ isActuallyScheduled: false,
+ scheduledAt: null,
+ }).then(() => {
+ scheduledPaginator.reload();
});
}
@@ -220,4 +298,11 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) {
padding-top: 16px;
border-top: solid 1px var(--MI_THEME-divider);
}
+
+.tabs {
+ background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
+ -webkit-backdrop-filter: var(--MI-blur, blur(15px));
+ backdrop-filter: var(--MI-blur, blur(15px));
+ border-bottom: solid 0.5px var(--MI_THEME-divider);
+}
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 21104b41df..45a74e3f02 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_mention]: notification.type === 'mention',
[$style.t_quote]: notification.type === 'quote',
[$style.t_pollEnded]: notification.type === 'pollEnded',
+ [$style.t_scheduledNotePosted]: notification.type === 'scheduledNotePosted',
+ [$style.t_scheduledNotePostFailed]: notification.type === 'scheduledNotePostFailed',
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
@@ -39,6 +41,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
@@ -60,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._notification.pollEnded }}
+ {{ i18n.ts._notification.scheduledNotePosted }}
+ {{ i18n.ts._notification.scheduledNotePostFailed }}
{{ i18n.ts._notification.newNote }}:
{{ i18n.ts._notification.roleAssigned }}
{{ i18n.ts._notification.chatRoomInvitationReceived }}
@@ -103,6 +109,11 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+
+
{{ notification.role.name }}
@@ -338,6 +349,16 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
+.t_scheduledNotePosted {
+ background: var(--eventOther);
+ pointer-events: none;
+}
+
+.t_scheduledNotePostFailed {
+ background: var(--eventOther);
+ pointer-events: none;
+}
+
.t_achievementEarned {
background: var(--eventAchievement);
pointer-events: none;
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 17f93a4ec8..c1b950a6c8 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ submitText }}
-
+
@@ -61,6 +61,13 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+
+
+ -
+
{{ i18n.ts.notSpecifiedMentionWarning }} -
@@ -199,6 +206,7 @@ if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(u => pushVisibleUser(u));
}
const reactionAcceptance = ref(store.s.reactionAcceptance);
+const scheduledAt = ref(null);
const draghover = ref(false);
const quoteId = ref(null);
const hasNotSpecifiedMentions = ref(false);
@@ -262,11 +270,17 @@ const placeholder = computed((): string => {
});
const submitText = computed((): string => {
- return renoteTargetNote.value
- ? i18n.ts.quote
- : replyTargetNote.value
- ? i18n.ts.reply
- : i18n.ts.note;
+ return scheduledAt.value != null
+ ? i18n.ts.schedule
+ : renoteTargetNote.value
+ ? i18n.ts.quote
+ : replyTargetNote.value
+ ? i18n.ts.reply
+ : i18n.ts.note;
+});
+
+const submitIcon = computed((): string => {
+ return posted.value ? 'ti ti-check' : scheduledAt.value != null ? 'ti ti-calendar-time' : replyTargetNote.value ? 'ti ti-arrow-back-up' : renoteTargetNote.value ? 'ti ti-quote' : 'ti ti-send';
});
const textLength = computed((): number => {
@@ -414,6 +428,7 @@ function watchForDraft() {
watch(localOnly, () => saveDraft());
watch(quoteId, () => saveDraft());
watch(reactionAcceptance, () => saveDraft());
+ watch(scheduledAt, () => saveDraft());
}
function checkMissingMention() {
@@ -605,7 +620,13 @@ function showOtherSettings() {
action: () => {
toggleReactionAcceptance();
},
- }, { type: 'divider' }, {
+ }, ...($i.policies.scheduledNoteLimit > 0 ? [{
+ icon: 'ti ti-calendar-time',
+ text: i18n.ts.schedulePost + '...',
+ action: () => {
+ schedule();
+ },
+ }] : []), { type: 'divider' }, {
type: 'switch',
icon: 'ti ti-eye',
text: i18n.ts.preview,
@@ -654,6 +675,7 @@ function clear() {
files.value = [];
poll.value = null;
quoteId.value = null;
+ scheduledAt.value = null;
}
function onKeydown(ev: KeyboardEvent) {
@@ -809,6 +831,7 @@ function saveDraft() {
...( visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
quoteId: quoteId.value,
reactionAcceptance: reactionAcceptance.value,
+ scheduledAt: scheduledAt.value,
},
};
@@ -823,7 +846,9 @@ function deleteDraft() {
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
}
-async function saveServerDraft(clearLocal = false) {
+async function saveServerDraft(options: {
+ isActuallyScheduled?: boolean;
+} = {}) {
return await os.apiWithDialog(serverDraftId.value == null ? 'notes/drafts/create' : 'notes/drafts/update', {
...(serverDraftId.value == null ? {} : { draftId: serverDraftId.value }),
text: text.value,
@@ -831,19 +856,15 @@ async function saveServerDraft(clearLocal = false) {
visibility: visibility.value,
localOnly: localOnly.value,
hashtag: hashtags.value,
- ...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}),
+ fileIds: files.value.map(f => f.id),
poll: poll.value,
- ...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
- renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined,
- replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined,
- channelId: targetChannel.value ? targetChannel.value.id : undefined,
+ visibleUserIds: visibleUsers.value.map(x => x.id),
+ renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : null,
+ replyId: replyTargetNote.value ? replyTargetNote.value.id : null,
+ channelId: targetChannel.value ? targetChannel.value.id : null,
reactionAcceptance: reactionAcceptance.value,
- }).then(() => {
- if (clearLocal) {
- clear();
- deleteDraft();
- }
- }).catch((err) => {
+ scheduledAt: scheduledAt.value,
+ isActuallyScheduled: options.isActuallyScheduled ?? false,
});
}
@@ -878,6 +899,21 @@ async function post(ev?: MouseEvent) {
}
}
+ if (scheduledAt.value != null) {
+ if (uploader.items.value.some(x => x.uploaded == null)) {
+ await uploadFiles();
+
+ // アップロード失敗したものがあったら中止
+ if (uploader.items.value.some(x => x.uploaded == null)) {
+ return;
+ }
+ }
+
+ await postAsScheduled();
+ clear();
+ return;
+ }
+
if (props.mock) return;
if (visibility.value === 'public' && (
@@ -1049,6 +1085,14 @@ async function post(ev?: MouseEvent) {
});
}
+async function postAsScheduled() {
+ if (props.mock) return;
+
+ await saveServerDraft({
+ isActuallyScheduled: true,
+ });
+}
+
function cancel() {
emit('cancel');
}
@@ -1143,8 +1187,10 @@ function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent)
}
function showDraftMenu(ev: MouseEvent) {
- function showDraftsDialog() {
- const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {}, {
+ function showDraftsDialog(scheduled: boolean) {
+ const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {
+ scheduled,
+ }, {
restore: async (draft: Misskey.entities.NoteDraft) => {
text.value = draft.text ?? '';
useCw.value = draft.cw != null;
@@ -1175,6 +1221,7 @@ function showDraftMenu(ev: MouseEvent) {
renoteTargetNote.value = draft.renote;
replyTargetNote.value = draft.reply;
reactionAcceptance.value = draft.reactionAcceptance;
+ scheduledAt.value = draft.scheduledAt ?? null;
if (draft.channel) targetChannel.value = draft.channel as unknown as Misskey.entities.Channel;
visibleUsers.value = [];
@@ -1215,11 +1262,32 @@ function showDraftMenu(ev: MouseEvent) {
text: i18n.ts._drafts.listDrafts,
icon: 'ti ti-cloud-download',
action: () => {
- showDraftsDialog();
+ showDraftsDialog(false);
+ },
+ }, { type: 'divider' }, {
+ type: 'button',
+ text: i18n.ts._drafts.listScheduledNotes,
+ icon: 'ti ti-clock-down',
+ action: () => {
+ showDraftsDialog(true);
},
}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
}
+async function schedule() {
+ const { canceled, result } = await os.inputDatetime({
+ title: i18n.ts.schedulePost,
+ });
+ if (canceled) return;
+ if (result.getTime() <= Date.now()) return;
+
+ scheduledAt.value = result.getTime();
+}
+
+function cancelSchedule() {
+ scheduledAt.value = null;
+}
+
onMounted(() => {
if (props.autofocus) {
focus();
@@ -1255,6 +1323,7 @@ onMounted(() => {
}
quoteId.value = draft.data.quoteId;
reactionAcceptance.value = draft.data.reactionAcceptance;
+ scheduledAt.value = draft.data.scheduledAt ?? null;
}
}
@@ -1519,6 +1588,10 @@ html[data-color-scheme=light] .preview {
margin: 0 20px 16px 20px;
}
+.scheduledAt {
+ margin: 0 20px 16px 20px;
+}
+
.cw,
.hashtags,
.text {
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 6c5f04c6b5..aafa1c4b21 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -76,7 +76,7 @@ export const apiWithDialog = ((
} else if (err.code === 'ROLE_PERMISSION_DENIED') {
title = i18n.ts.permissionDeniedError;
text = i18n.ts.permissionDeniedErrorDescription;
- } else if (err.code.startsWith('TOO_MANY')) {
+ } else if (err.code.startsWith('TOO_MANY')) { // TODO: バックエンドに kind: client/contentsLimitExceeded みたいな感じで送るように統一してもらってそれで判定する
title = i18n.ts.youCannotCreateAnymore;
text = `${i18n.ts.error}: ${err.id}`;
} else if (err.message.startsWith('Unexpected token')) {
@@ -460,7 +460,7 @@ export function inputNumber(props: {
});
}
-export function inputDate(props: {
+export function inputDatetime(props: {
title?: string;
text?: string;
placeholder?: string | null;
@@ -475,13 +475,13 @@ export function inputDate(props: {
title: props.title,
text: props.text,
input: {
- type: 'date',
+ type: 'datetime-local',
placeholder: props.placeholder,
default: props.default ?? null,
},
}, {
done: result => {
- resolve(result ? { result: new Date(result.result), canceled: false } : { result: undefined, canceled: true });
+ resolve(result != null && result.result != null ? { result: new Date(result.result), canceled: false } : { result: undefined, canceled: true });
},
closed: () => dispose(),
});
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index e10eb1163e..03df01a930 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -802,6 +802,25 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+ {{ i18n.ts._role._options.scheduledNoteLimit }}
+
+ {{ i18n.ts._role.useBaseValue }}
+ {{ role.policies.scheduledNoteLimit.value }}
+
+
+
+
+ {{ i18n.ts._role.useBaseValue }}
+
+
+
+
+ {{ i18n.ts._role.priority }}
+
+
+
+
{{ i18n.ts._role._options.watermarkAvailable }}
@@ -831,6 +850,7 @@ import { watch, ref, computed } from 'vue';
import { throttle } from 'throttle-debounce';
import * as Misskey from 'misskey-js';
import RolesEditorFormula from './RolesEditorFormula.vue';
+import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkColorInput from '@/components/MkColorInput.vue';
import MkSelect from '@/components/MkSelect.vue';
@@ -842,7 +862,6 @@ import FormSlot from '@/components/form/slot.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { deepClone } from '@/utility/clone.js';
-import type { MkSelectItem, GetMkSelectValueTypesFromDef } from '@/components/MkSelect.vue';
const emit = defineEmits<{
(ev: 'update:modelValue', v: any): void;
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 7a49860b2d..eca0284be3 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -304,6 +304,13 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+ {{ i18n.ts._role._options.scheduledNoteLimit }}
+ {{ policies.scheduledNoteLimit }}
+
+
+
+
{{ i18n.ts._role._options.watermarkAvailable }}
{{ policies.watermarkAvailable ? i18n.ts.yes : i18n.ts.no }}
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index d07710fc67..fab205b939 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -3226,7 +3226,7 @@ type Notification_2 = components['schemas']['Notification'];
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
// @public (undocumented)
-export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollEnded", "scheduledNotePosted", "scheduledNotePostFailed", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
// @public (undocumented)
export function nyaize(text: string): string;
@@ -3339,7 +3339,7 @@ type QueueStats = {
type QueueStatsLog = QueueStats[];
// @public (undocumented)
-export const queueTypes: readonly ["system", "endedPollNotification", "deliver", "inbox", "db", "relationship", "objectStorage", "userWebhookDeliver", "systemWebhookDeliver"];
+export const queueTypes: readonly ["system", "endedPollNotification", "postScheduledNote", "deliver", "inbox", "db", "relationship", "objectStorage", "userWebhookDeliver", "systemWebhookDeliver"];
// @public (undocumented)
type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
@@ -3441,7 +3441,7 @@ type RoleLite = components['schemas']['RoleLite'];
type RolePolicies = components['schemas']['RolePolicies'];
// @public (undocumented)
-export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "watermarkAvailable"];
+export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "scheduledNoteLimit", "watermarkAvailable"];
// @public (undocumented)
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 22a3733fcb..2a059bdea6 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4159,6 +4159,24 @@ export type components = {
/** Format: misskey:id */
userListId: string;
};
+ scheduledNotePosted?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
+ scheduledNotePostFailed?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
receiveFollowRequest?: {
/** @enum {string} */
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
@@ -4406,42 +4424,31 @@ export type components = {
/** Format: date-time */
createdAt: string;
text: string | null;
- cw?: string | null;
+ cw: string | null;
/** Format: id */
userId: string;
user: components['schemas']['UserLite'];
- /**
- * Format: id
- * @example xxxxxxxxxx
- */
- replyId?: string | null;
- /**
- * Format: id
- * @example xxxxxxxxxx
- */
- renoteId?: string | null;
- /** @description The reply target note contents if exists. If the reply target has been deleted since the draft was created, this will be null while replyId is not null. */
+ /** Format: id */
+ replyId: string | null;
+ /** Format: id */
+ renoteId: string | null;
reply?: components['schemas']['Note'] | null;
- /** @description The renote target note contents if exists. If the renote target has been deleted since the draft was created, this will be null while renoteId is not null. */
renote?: components['schemas']['Note'] | null;
/** @enum {string} */
visibility: 'public' | 'home' | 'followers' | 'specified';
- visibleUserIds?: string[];
- fileIds?: string[];
+ visibleUserIds: string[];
+ fileIds: string[];
files?: components['schemas']['DriveFile'][];
- hashtag?: string;
- poll?: {
+ hashtag: string | null;
+ poll: {
/** Format: date-time */
expiresAt?: string | null;
expiredAfter?: number | null;
multiple: boolean;
choices: string[];
} | null;
- /**
- * Format: id
- * @example xxxxxxxxxx
- */
- channelId?: string | null;
+ /** Format: id */
+ channelId: string | null;
channel?: {
id: string;
name: string;
@@ -4450,9 +4457,11 @@ export type components = {
allowRenoteToExternal: boolean;
userId: string | null;
} | null;
- localOnly?: boolean;
+ localOnly: boolean;
/** @enum {string|null} */
reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
+ scheduledAt: number | null;
+ isActuallyScheduled: boolean;
};
NoteReaction: {
/** Format: id */
@@ -4561,6 +4570,22 @@ export type components = {
/** Format: id */
userId: string;
note: components['schemas']['Note'];
+ } | {
+ /** Format: id */
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ /** @enum {string} */
+ type: 'scheduledNotePosted';
+ note: components['schemas']['Note'];
+ } | {
+ /** Format: id */
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ /** @enum {string} */
+ type: 'scheduledNotePostFailed';
+ noteDraft: components['schemas']['NoteDraft'];
} | {
/** Format: id */
id: string;
@@ -5253,6 +5278,7 @@ export type components = {
/** @enum {string} */
chatAvailability: 'available' | 'readonly' | 'unavailable';
noteDraftLimit: number;
+ scheduledNoteLimit: number;
watermarkAvailable: boolean;
};
ReversiGameLite: {
@@ -9553,7 +9579,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
/** @enum {string} */
state: '*' | 'completed' | 'wait' | 'active' | 'paused' | 'prioritized' | 'delayed' | 'failed';
};
@@ -9740,7 +9766,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
state: ('active' | 'wait' | 'delayed' | 'completed' | 'failed' | 'paused')[];
search?: string;
};
@@ -9808,7 +9834,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
};
};
};
@@ -9871,7 +9897,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
};
};
};
@@ -9884,7 +9910,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- name: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ name: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
qualifiedName: string;
counts: {
[key: string]: number;
@@ -9974,7 +10000,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- name: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ name: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
counts: {
[key: string]: number;
};
@@ -10038,7 +10064,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
jobId: string;
};
};
@@ -10102,7 +10128,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
jobId: string;
};
};
@@ -10166,7 +10192,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
jobId: string;
};
};
@@ -10233,7 +10259,7 @@ export interface operations {
content: {
'application/json': {
/** @enum {string} */
- queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ queue: 'system' | 'endedPollNotification' | 'postScheduledNote' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
jobId: string;
};
};
@@ -11653,6 +11679,24 @@ export interface operations {
/** Format: misskey:id */
userListId: string;
};
+ scheduledNotePosted?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
+ scheduledNotePostFailed?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
receiveFollowRequest?: {
/** @enum {string} */
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
@@ -25954,8 +25998,8 @@ export interface operations {
untilDate?: number;
/** @default true */
markAsRead?: boolean;
- includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
- excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@@ -26039,8 +26083,8 @@ export interface operations {
untilDate?: number;
/** @default true */
markAsRead?: boolean;
- includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
- excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'scheduledNotePosted' | 'scheduledNotePostFailed' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'chatRoomInvitationReceived' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@@ -27314,6 +27358,24 @@ export interface operations {
/** Format: misskey:id */
userListId: string;
};
+ scheduledNotePosted?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
+ scheduledNotePostFailed?: {
+ /** @enum {string} */
+ type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+ } | {
+ /** @enum {string} */
+ type: 'list';
+ /** Format: misskey:id */
+ userListId: string;
+ };
receiveFollowRequest?: {
/** @enum {string} */
type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
@@ -29143,31 +29205,34 @@ export interface operations {
* @default public
* @enum {string}
*/
- visibility?: 'public' | 'home' | 'followers' | 'specified';
- visibleUserIds?: string[];
- cw?: string | null;
- hashtag?: string | null;
+ visibility: 'public' | 'home' | 'followers' | 'specified';
+ visibleUserIds: string[];
+ cw: string | null;
+ hashtag: string | null;
/** @default false */
- localOnly?: boolean;
+ localOnly: boolean;
/**
* @default null
* @enum {string|null}
*/
- reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
+ reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** Format: misskey:id */
- replyId?: string | null;
+ replyId: string | null;
/** Format: misskey:id */
- renoteId?: string | null;
+ renoteId: string | null;
/** Format: misskey:id */
- channelId?: string | null;
- text?: string | null;
- fileIds?: string[];
- poll?: {
+ channelId: string | null;
+ text: string | null;
+ fileIds: string[];
+ poll: {
choices: string[];
multiple?: boolean;
expiresAt?: number | null;
expiredAfter?: number | null;
} | null;
+ scheduledAt: number | null;
+ /** @default false */
+ isActuallyScheduled: boolean;
};
};
};
@@ -29314,6 +29379,7 @@ export interface operations {
untilId?: string;
sinceDate?: number;
untilDate?: number;
+ scheduled?: boolean | null;
};
};
};
@@ -29380,20 +29446,13 @@ export interface operations {
'application/json': {
/** Format: misskey:id */
draftId: string;
- /**
- * @default public
- * @enum {string}
- */
+ /** @enum {string} */
visibility?: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: string[];
cw?: string | null;
hashtag?: string | null;
- /** @default false */
localOnly?: boolean;
- /**
- * @default null
- * @enum {string|null}
- */
+ /** @enum {string|null} */
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** Format: misskey:id */
replyId?: string | null;
@@ -29409,6 +29468,8 @@ export interface operations {
expiresAt?: number | null;
expiredAfter?: number | null;
} | null;
+ scheduledAt?: number | null;
+ isActuallyScheduled?: boolean;
};
};
};
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 9afd1f8be6..148c5ed831 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -26,6 +26,8 @@ export const notificationTypes = [
'quote',
'reaction',
'pollEnded',
+ 'scheduledNotePosted',
+ 'scheduledNotePostFailed',
'receiveFollowRequest',
'followRequestAccepted',
'groupInvited',
@@ -227,12 +229,14 @@ export const rolePolicies = [
'chatAvailability',
'uploadableFileTypes',
'noteDraftLimit',
+ 'scheduledNoteLimit',
'watermarkAvailable',
] as const;
export const queueTypes = [
'system',
'endedPollNotification',
+ 'postScheduledNote',
'deliver',
'inbox',
'db',
From 0b7634b12636398c1e9429fbcca6beef4a144224 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Fri, 26 Sep 2025 15:36:25 +0900
Subject: [PATCH 238/256] =?UTF-8?q?enhance(frontend):=20=E3=83=86=E3=83=BC?=
=?UTF-8?q?=E3=83=9E=E3=82=92=E3=83=89=E3=83=A9=E3=83=83=E3=82=B0&?=
=?UTF-8?q?=E3=83=89=E3=83=AD=E3=83=83=E3=83=97=E3=81=A7=E3=81=8D=E3=82=8B?=
=?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 1 +
packages/frontend/src/drag-and-drop.ts | 20 +++++++
.../frontend/src/pages/settings/theme.vue | 53 ++++++++++++++++---
3 files changed, 66 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66837034ae..143a48ce48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました
- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加
- Enhance: ウォーターマークにアカウントのQRコードを追加できるように
+- Enhance: テーマをドラッグ&ドロップできるように
- Enhance: 絵文字ピッカーのサイズをより大きくできるように
- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上
- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正
diff --git a/packages/frontend/src/drag-and-drop.ts b/packages/frontend/src/drag-and-drop.ts
index 3c6f22f24b..670912241e 100644
--- a/packages/frontend/src/drag-and-drop.ts
+++ b/packages/frontend/src/drag-and-drop.ts
@@ -23,6 +23,15 @@ export function setDragData(
event.dataTransfer.setData(`misskey/${type}`.toLowerCase(), JSON.stringify(data));
}
+export function setPlainDragData(
+ event: DragEvent,
+ data: string,
+) {
+ if (event.dataTransfer == null) return;
+
+ event.dataTransfer.setData('text/plain', data);
+}
+
export function getDragData(
event: DragEvent,
type: T,
@@ -35,6 +44,17 @@ export function getDragData(
return JSON.parse(data);
}
+export function getPlainDragData(
+ event: DragEvent,
+): string | null {
+ if (event.dataTransfer == null) return null;
+
+ const data = event.dataTransfer.getData('text/plain');
+ if (data == null || data === '') return null;
+
+ return data;
+}
+
export function checkDragDataType(
event: DragEvent,
types: (keyof DragDataMap)[],
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index beae1224e4..0129aebe94 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -5,7 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -58,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="$style.themeRadio"
:value="instanceLightTheme.id"
/>
-