diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-12-12 13:04:51 +0000 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-12-12 13:04:51 +0000 |
| commit | 6d4ae935921ef1dcc5c908f216f6e8affc6e60c3 (patch) | |
| tree | 6c1f7724b63efa8f48f1c9e21f2789f9d9c000b2 /packages/frontend/src | |
| parent | fix(backend): アドレス入力で直接ユーザのプロフィールペー... (diff) | |
| parent | merge: Schedule Notes (!804) (diff) | |
| download | sharkey-6d4ae935921ef1dcc5c908f216f6e8affc6e60c3.tar.gz sharkey-6d4ae935921ef1dcc5c908f216f6e8affc6e60c3.tar.bz2 sharkey-6d4ae935921ef1dcc5c908f216f6e8affc6e60c3.zip | |
Merge branch 'develop' into feature/2024.10
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/components/MkMediaList.vue | 6 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNote.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteHeader.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteSimple.vue | 53 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNotification.vue | 19 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkPostForm.vue | 54 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkScheduleEditor.vue | 65 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkSchedulePostListDialog.vue | 62 | ||||
| -rw-r--r-- | packages/frontend/src/components/SkNote.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/index.html | 4 | ||||
| -rw-r--r-- | packages/frontend/src/os.ts | 1 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/roles.editor.vue | 19 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/roles.vue | 7 | ||||
| -rw-r--r-- | packages/frontend/src/pages/user/home.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/types/post-form.ts | 4 |
15 files changed, 294 insertions, 11 deletions
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 313b3b0165..487cf509d6 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -195,6 +195,12 @@ onMounted(() => { textBox.textContent = pswp.currSlide?.data.comment; }); + + // `passive: true` is for Safari compatibility, apparently + const stopEvent = name => textBox.addEventListener(name, event => event.stopPropagation(), { passive: true }); + stopEvent('wheel'); + stopEvent('pointerdown'); + stopEvent('pointercancel'); }, }); }); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 7b3cd84c04..39f4806f0c 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkA>: <Mfm :text="getNoteSummary(appearNote.reply)" :plain="true" :nowrap="true" :author="appearNote.reply.user" :nyaize="'respect'" :class="$style.collapsedInReplyToText" @click="inReplyToCollapsed = false"/> </div> - <MkNoteSub v-if="appearNote.reply && !renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> + <MkNoteSub v-if="appearNote.reply" v-show="!renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> <div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> <!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> <!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>--> diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index b01f87a121..3b15242685 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -46,7 +46,10 @@ import { popupMenu } from '@/os.js'; import { defaultStore } from '@/store.js'; const props = defineProps<{ - note: Misskey.entities.Note; + note: Misskey.entities.Note & { + isSchedule?: boolean + }; + scheduled?: boolean; }>(); const menuVersionsButton = shallowRef<HTMLElement>(); diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 273d49efd6..aabe666041 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.root"> +<div v-if="!isDeleted" :class="$style.root"> <MkAvatar :class="$style.avatar" :user="note.user" link preview/> <div :class="$style.main"> <MkNoteHeader :class="$style.header" :note="note" :mini="true"/> @@ -15,6 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only </p> <div v-show="note.cw == null || showContent"> <MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/> + <div v-if="note.isSchedule" style="margin-top: 10px;"> + <MkButton :class="$style.button" inline @click.stop.prevent="editScheduleNote()"><i class="ti ti-eraser"></i> {{ i18n.ts.deleteAndEdit }}</MkButton> + <MkButton :class="$style.button" inline danger @click.stop.prevent="deleteScheduleNote()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> </div> </div> </div> @@ -24,18 +28,58 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; +import * as os from '@/os.js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkCwButton from '@/components/MkCwButton.vue'; +import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ - note: Misskey.entities.Note; + note: Misskey.entities.Note & { + isSchedule? : boolean, + scheduledNoteId?: string + }; expandAllCws?: boolean; hideFiles?: boolean; }>(); let showContent = ref(defaultStore.state.uncollapseCW); +const isDeleted = ref(false); + +const emit = defineEmits<{ + (ev: 'editScheduleNote'): void; +}>(); + +async function deleteScheduleNote() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteConfirm, + okText: i18n.ts.delete, + cancelText: i18n.ts.cancel, + }); + if (canceled) return; + await os.apiWithDialog('notes/schedule/delete', { noteId: props.note.id }) + .then(() => { + isDeleted.value = true; + }); +} + +async function editScheduleNote() { + await os.apiWithDialog('notes/schedule/delete', { noteId: props.note.id }) + .then(() => { + isDeleted.value = true; + }); + + await os.post({ + initialNote: props.note, + renote: props.note.renote, + reply: props.note.reply, + channel: props.note.channel, + }); + emit('editScheduleNote'); +} watch(() => props.expandAllCws, (expandAllCws) => { if (expandAllCws !== showContent.value) showContent.value = expandAllCws; @@ -50,6 +94,11 @@ watch(() => props.expandAllCws, (expandAllCws) => { font-size: 0.95em; } +.button{ + margin-right: var(--margin); + margin-bottom: var(--margin); +} + .avatar { flex-shrink: 0; display: block; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 7976cdab5b..4620b966af 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="$style.root"> <div :class="$style.head"> - <MkAvatar v-if="['pollEnded', 'note', 'edited'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/> - <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> + <MkAvatar v-if="['pollEnded', 'note', 'edited', 'scheduledNotePosted'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/> + <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> @@ -29,6 +29,8 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.t_login]: notification.type === 'login', [$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null, [$style.t_pollEnded]: notification.type === 'edited', + [$style.t_roleAssigned]: notification.type === 'scheduledNoteFailed', + [$style.t_pollEnded]: notification.type === 'scheduledNotePosted', }]" > <!-- we re-use t_pollEnded for "edited" instead of making an identical style --> <i v-if="notification.type === 'follow'" class="ti ti-plus"></i> @@ -47,6 +49,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else class="ti ti-badges"></i> </template> <i v-else-if="notification.type === 'edited'" class="ph-pencil ph-bold ph-lg"></i> + <i v-else-if="notification.type === 'scheduledNoteFailed'" class="ti ti-calendar-event"></i> + <i v-else-if="notification.type === 'scheduledNotePosted'" class="ti ti-calendar-event"></i> <!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 --> <MkReactionIcon v-else-if="notification.type === 'reaction'" @@ -72,6 +76,8 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span> <span v-else-if="notification.type === 'app'">{{ notification.header }}</span> <span v-else-if="notification.type === 'edited'">{{ i18n.ts._notification.edited }}</span> + <span v-else-if="notification.type === 'scheduledNoteFailed'">{{ i18n.ts._notification.scheduledNoteFailed }}</span> + <span v-else-if="notification.type === 'scheduledNotePosted'">{{ i18n.ts._notification.scheduledNotePosted }}</span> <MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/> </header> <div> @@ -111,6 +117,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`"> {{ i18n.ts.showFile }} </MkA> + <div v-else-if="notification.type === 'scheduledNoteFailed'" :class="$style.text"> + {{ notification.reason }} + </div> <template v-else-if="notification.type === 'follow'"> <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span> </template> @@ -158,6 +167,12 @@ SPDX-License-Identifier: AGPL-3.0-only <Mfm :text="getNoteSummary(notification.note)" :isBlock="true" :plain="true" :nowrap="true" :author="notification.note.user"/> <i class="ph-quotes ph-bold ph-lg" :class="$style.quote"></i> </MkA> + + <MkA v-else-if="notification.type === 'scheduledNotePosted'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> + <i class="ph-quotes ph-bold ph-lg" :class="$style.quote"></i> + <Mfm :text="getNoteSummary(notification.note)" :isBlock="true" :plain="true" :nowrap="true" :author="notification.note.user"/> + <i class="ph-quotes ph-bold ph-lg" :class="$style.quote"></i> + </MkA> </div> </div> </div> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 3bdc197cc6..11ae6dbd6a 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -77,6 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> <XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/> <MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> + <MkScheduleEditor v-if="scheduleNote" v-model="scheduleNote" @destroyed="scheduleNote = null"/> <MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/> <div v-if="showingOptions" style="padding: 8px 16px;"> </div> @@ -90,6 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button> <button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button> <button v-if="showAddMfmFunction" v-tooltip="i18n.ts.addMfmFunction" :class="['_button', $style.footerButton]" @click="insertMfmFunction"><i class="ti ti-palette"></i></button> + <button v-tooltip="i18n.ts.otherSettings" :class="['_button', $style.footerButton]" @click="showOtherMenu"><i class="ti ti-dots"></i></button> </div> <div :class="$style.footerRight"> <button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button> @@ -110,6 +112,7 @@ import * as Misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { toASCII } from 'punycode/'; import { host, url } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNotePreview from '@/components/MkNotePreview.vue'; import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; @@ -134,6 +137,7 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js'; import type { PostFormProps } from '@/types/post-form.js'; +import MkScheduleEditor from '@/components/MkScheduleEditor.vue'; const $i = signinRequired(); @@ -195,6 +199,9 @@ const imeText = ref(''); const showingOptions = ref(false); const textAreaReadOnly = ref(false); const justEndedComposition = ref(false); +const scheduleNote = ref<{ + scheduledAt: number | null; +} | null>(null); const draftKey = computed((): string => { let key = props.channel ? `channel:${props.channel.id}` : ''; @@ -367,6 +374,7 @@ function watchForDraft() { watch(localOnly, () => saveDraft()); watch(quoteId, () => saveDraft()); watch(reactionAcceptance, () => saveDraft()); + watch(scheduleNote, () => saveDraft()); } function MFMWindow() { @@ -575,6 +583,7 @@ function clear() { files.value = []; poll.value = null; quoteId.value = null; + scheduleNote.value = null; } function onKeydown(ev: KeyboardEvent) { @@ -732,6 +741,7 @@ function saveDraft() { visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(x => x.id) : undefined, quoteId: quoteId.value, reactionAcceptance: reactionAcceptance.value, + scheduleNote: scheduleNote.value, }, }; @@ -839,6 +849,7 @@ async function post(ev?: MouseEvent) { visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined, reactionAcceptance: reactionAcceptance.value, editId: props.editId ? props.editId : undefined, + scheduleNote: scheduleNote.value ?? undefined, }; if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { @@ -875,7 +886,7 @@ async function post(ev?: MouseEvent) { } posting.value = true; - misskeyApi(postData.editId ? 'notes/edit' : 'notes/create', postData, token).then(() => { + misskeyApi(postData.editId ? 'notes/edit' : (postData.scheduleNote ? 'notes/schedule/create' : 'notes/create'), postData, token).then(() => { if (props.freezeAfterPosted) { posted.value = true; } else { @@ -1026,6 +1037,42 @@ function openAccountMenu(ev: MouseEvent) { }, ev); } +function toggleScheduleNote() { + if (scheduleNote.value) { + scheduleNote.value = null; + } else { + scheduleNote.value = { + scheduledAt: null, + }; + } +} + +function showOtherMenu(ev: MouseEvent) { + const menuItems: MenuItem[] = []; + + if ($i.policies.scheduleNoteMax > 0) { + menuItems.push({ + type: 'button', + text: i18n.ts.schedulePost, + icon: 'ti ti-calendar-time', + action: toggleScheduleNote, + }, { + type: 'button', + text: i18n.ts.schedulePostList, + icon: 'ti ti-calendar-event', + action: () => { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSchedulePostListDialog.vue')), {}, { + closed: () => { + dispose(); + }, + }); + }, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); +} + onMounted(() => { if (props.autofocus) { focus(); @@ -1095,6 +1142,11 @@ onMounted(() => { } quoteId.value = init.renote ? init.renote.id : null; reactionAcceptance.value = init.reactionAcceptance; + if (init.isSchedule) { + scheduleNote.value = { + scheduledAt: new Date(init.createdAt).getTime(), + }; + } } nextTick(() => watchForDraft()); diff --git a/packages/frontend/src/components/MkScheduleEditor.vue b/packages/frontend/src/components/MkScheduleEditor.vue new file mode 100644 index 0000000000..60a60bed28 --- /dev/null +++ b/packages/frontend/src/components/MkScheduleEditor.vue @@ -0,0 +1,65 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div style="padding: 8px 16px;"> + <section> + <MkInput v-model="atDate" small type="date" class="input"> + <template #label>{{ i18n.ts._poll.deadlineDate }}</template> + </MkInput> + <MkInput v-model="atTime" small type="time" class="input"> + <template #label>{{ i18n.ts._poll.deadlineTime }}</template> + </MkInput> + </section> +</div> +</template> + +<script lang="ts" setup> +import { onMounted, ref, watch } from 'vue'; +import MkInput from '@/components/MkInput.vue'; +import { formatDateTimeString } from '@/scripts/format-time-string.js'; +import { addTime } from '@/scripts/time.js'; +import { i18n } from '@/i18n.js'; + +const props = defineProps<{ + modelValue: { + scheduledAt: number | null; + }; +}>(); + +const emit = defineEmits<{ + (ev: 'update:modelValue', v: { + scheduledAt: number | null; + }): void; +}>(); + +const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd')); +const atTime = ref('00:00'); + +if (props.modelValue.scheduledAt) { + const date = new Date(props.modelValue.scheduledAt); + atDate.value = formatDateTimeString(date, 'yyyy-MM-dd'); + atTime.value = formatDateTimeString(date, 'HH:mm'); +} + +function get() { + const calcAt = () => { + return new Date(`${ atDate.value } ${ atTime.value }`).getTime(); + }; + + return { scheduledAt: calcAt() }; +} + +watch([ + atDate, + atTime, +], () => emit('update:modelValue', get()), { + deep: true, +}); + +onMounted(() => { + emit('update:modelValue', get()); +}); +</script> diff --git a/packages/frontend/src/components/MkSchedulePostListDialog.vue b/packages/frontend/src/components/MkSchedulePostListDialog.vue new file mode 100644 index 0000000000..d0716ead79 --- /dev/null +++ b/packages/frontend/src/components/MkSchedulePostListDialog.vue @@ -0,0 +1,62 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModalWindow + ref="dialogEl" + :withOkButton="false" + @click="cancel()" + @close="cancel()" +> + <template #header>{{ i18n.ts.schedulePostList }}</template> + <MkSpacer :marginMin="14" :marginMax="16"> + <MkPagination ref="paginationEl" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img :src="infoImageUrl" class="_ghost"/> + <div>{{ i18n.ts.nothing }}</div> + </div> + </template> + + <template #default="{ items }"> + <div class="_gaps"> + <MkNoteSimple v-for="item in items" :key="item.id" :scheduled="true" :note="item.note" @editScheduleNote="listUpdate"/> + </div> + </template> + </MkPagination> + </MkSpacer> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import type { Paging } from '@/components/MkPagination.vue'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkPagination from '@/components/MkPagination.vue'; +import MkNoteSimple from '@/components/MkNoteSimple.vue'; +import { i18n } from '@/i18n.js'; +import { infoImageUrl } from '@/instance.js'; + +const emit = defineEmits<{ + (ev: 'cancel'): void; +}>(); + +const dialogEl = ref(); +const cancel = () => { + emit('cancel'); + dialogEl.value.close(); +}; + +const paginationEl = ref(); +const pagination: Paging = { + endpoint: 'notes/schedule/list', + limit: 10, + offsetMode: true, +}; + +function listUpdate() { + paginationEl.value.reload(); +} +</script> diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index 3f376f8287..528e86646b 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]" :tabindex="isDeleted ? '-1' : '0'" > - <SkNoteSub v-if="appearNote.reply && !renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> + <SkNoteSub v-if="appearNote.reply" v-show="!renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> <div v-if="appearNote.reply && inReplyToCollapsed && !renoteCollapsed" :class="$style.collapsedInReplyTo"> <div :class="$style.collapsedInReplyToLine"></div> <MkAvatar :class="$style.collapsedInReplyToAvatar" :user="appearNote.reply.user" link preview/> diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html index c03902f505..f9ce113687 100644 --- a/packages/frontend/src/index.html +++ b/packages/frontend/src/index.html @@ -25,9 +25,11 @@ connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.listenbrainz.org https://api.friendlycaptcha.com https://raw.esm.sh; frame-src *;" /> - <meta property="og:site_name" content="[DEV BUILD] Misskey" /> + <meta property="og:site_name" content="[DEV BUILD] Sharkey" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color-orig" content="#86b300"> + <link rel='stylesheet' href='/assets/phosphor-icons/bold/style.css'> + <link rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css'> </head> <body> diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 7887662506..a81f67aef3 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -740,3 +740,4 @@ export function checkExistence(fileData: ArrayBuffer): Promise<any> { }); }); }*/ + diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 1763db2323..5d896db98c 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -200,6 +200,25 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.scheduleNoteMax, 'scheduleNoteMax'])"> + <template #label>{{ i18n.ts._role._options.scheduleNoteMax }}</template> + <template #suffix> + <span v-if="role.policies.scheduleNoteMax.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.scheduleNoteMax.value }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.scheduleNoteMax)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.scheduleNoteMax.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkInput v-model="role.policies.scheduleNoteMax.value" :disabled="role.policies.scheduleNoteMax.useDefault" type="number" :readonly="readonly"> + </MkInput> + <MkRange v-model="role.policies.scheduleNoteMax.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.mentionMax, 'mentionLimit'])"> <template #label>{{ i18n.ts._role._options.mentionMax }}</template> <template #suffix> diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 00a25446ab..036f18fe0d 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -70,6 +70,13 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.scheduleNoteMax, 'scheduleNoteMax'])"> + <template #label>{{ i18n.ts._role._options.scheduleNoteMax }}</template> + <template #suffix>{{ policies.scheduleNoteMax }}</template> + <MkInput v-model="policies.scheduleNoteMax" type="number"> + </MkInput> + </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.mentionMax, 'mentionLimit'])"> <template #label>{{ i18n.ts._role._options.mentionMax }}</template> <template #suffix>{{ policies.mentionLimit }}</template> diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 92434213a7..5565555ca4 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -264,7 +264,7 @@ const memoDraft = ref(props.user.memo); const isEditingMemo = ref(false); const moderationNote = ref(props.user.moderationNote); const editModerationNote = ref(false); -const noteview = ref<string | null>('pinned'); +const noteview = ref<string | null>(props.user.pinnedNotes.length ? 'pinned' : null); const listenbrainzdata = ref(false); if (props.user.listenbrainz) { diff --git a/packages/frontend/src/types/post-form.ts b/packages/frontend/src/types/post-form.ts index 4a56cb0456..94958b6623 100644 --- a/packages/frontend/src/types/post-form.ts +++ b/packages/frontend/src/types/post-form.ts @@ -17,6 +17,8 @@ export interface PostFormProps { initialFiles?: Misskey.entities.DriveFile[]; initialLocalOnly?: boolean; initialVisibleUsers?: Misskey.entities.UserDetailed[]; - initialNote?: Misskey.entities.Note; + initialNote?: Misskey.entities.Note & { + isSchedule?: boolean, + }; instant?: boolean; } |