mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-03 03:15:58 +02:00
feat: ノートの下書き(draft of note) (#15298)
* WIp (backend) * Remove unused * 下書きbackend 続き * fix(backedn): visibilityが下書きに反映されない * Update packages/backend/src/postgres.ts Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> * Fix : import order * fix(backend) : createでcwが効かない * FIX FOREGIN KEY * wip: frontend(既存の下書きを挿入) まだ:チャンネル表示、下書きの作成、削除 * WIP: ノート選択ダイアログ 投稿時に下書きを削除 * Promiseに変更 * 連合なし、チャンネルも表示 * Hashtagの値抜け漏れ * hasthagを0文字でも作成可能に * 下書きの保存機構 * chore(misskey-js): build types * localOnly抜け漏れ * チャンネル情報の書き換え * enhance(frontend): ヘッダ部の表示改善 * fix(frontend): ファイル添付できない * fix: no file * fix(frontend): 投票が反映されない * ハッシュタグの展開(コメントアウト外し忘れ) * fix: visibleUserIdsが反映されない * enhance: APIの型を整備 * refactor: 型が整備できたのでasを削除 * Add userhost * fix * enhance: paginationを使う * fix * fix: 自分のアカウントでの投稿でしか下書きを利用できないように 完全に塞ぐことはできないが一応 * 🎨 * APIのエラーIDを追加 * enhance: スタイル調整 * remove unused code * 🎨 * fix: ロールポリシーの型 * ロールの編集画面 * ダイアログの挙動改善 * 下書き機能が利用できない場合は表示しないように * refactor * fix: ダブルクリックが効かない問題を修正 * add comments * fix * fix: 保存時のエラーの種別にかかわらずmodalを閉じないように * fix()backend: NoteDraftのreply, renoteの型が間違ってたので修正 (migtrationはあってた) * fix: 投稿フォームを空白にして通常リノートできるやつは下書きとしては弾くように * fix(backend): テキストが0文字でも下書きは保存できるように * Fix(backend): replyIdの型定義がミスっているのを修正 * chore(misskey-js): update types * Add CHANGELOG * lint * 常にサーバー下書きに保存し、上限を超えた場合のみ尋ねるように * NoteDraftServiceにcreate, updateの処理を移譲 * Fix typeerror * remove tooltip * Remove Mkbutton:short and use iconOnly * 不要なコメントの削除 * Remove Short Completely * wip * escキーまわりの挙動を改善 * 下書き選択時に下書き可能数と現在の量が分かるように * cleanUp * wip * wi * wip * Update MkPostForm.vue --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
218
packages/frontend/src/components/MkNoteDraftsDialog.vue
Normal file
218
packages/frontend/src/components/MkNoteDraftsDialog.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialogEl"
|
||||
:width="600"
|
||||
:height="650"
|
||||
:withOkButton="false"
|
||||
@click="cancel()"
|
||||
@close="cancel()"
|
||||
@closed="emit('closed')"
|
||||
@esc="cancel()"
|
||||
>
|
||||
<template #header>
|
||||
{{ i18n.ts.drafts }} ({{ currentDraftsCount }}/{{ $i?.policies.noteDraftLimit }})
|
||||
</template>
|
||||
<div :class="$style.drafts" class="_gaps">
|
||||
<MkPagination ref="pagingEl" :pagination="paging">
|
||||
<template #empty>
|
||||
<MkResult type="empty" :text="i18n.ts._drafts.noDrafts"/>
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<div class="_spacer _gaps_s">
|
||||
<div
|
||||
v-for="draft in (items as unknown as Misskey.entities.NoteDraft[])"
|
||||
:key="draft.id"
|
||||
v-panel
|
||||
:class="[$style.draft]"
|
||||
>
|
||||
<div :class="$style.draftBody" class="_gaps_s">
|
||||
<div :class="$style.draftInfo">
|
||||
<div :class="$style.draftMeta">
|
||||
<div v-if="draft.reply" class="_nowrap">
|
||||
<i class="ti ti-arrow-back-up"></i> <I18n :src="i18n.ts._drafts.replyTo" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.reply.user.name != null" :text="draft.reply.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.reply.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.renote && draft.text != null" class="_nowrap">
|
||||
<i class="ti ti-quote"></i> <I18n :src="i18n.ts._drafts.quoteOf" tag="span">
|
||||
<template #user>
|
||||
<Mfm v-if="draft.renote.user.name != null" :text="draft.renote.user.name" :plain="true" :nowrap="true"/>
|
||||
<MkAcct v-else :user="draft.renote.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="draft.channel" class="_nowrap">
|
||||
<i class="ti ti-device-tv"></i> {{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.draftContent">
|
||||
<Mfm :text="getNoteSummary(draft, { showRenote: false, showReply: false })" :plain="true" :author="draft.user"/>
|
||||
</div>
|
||||
<div :class="$style.draftFooter">
|
||||
<div :class="$style.draftVisibility">
|
||||
<span :title="i18n.ts._visibility[draft.visibility]">
|
||||
<i v-if="draft.visibility === 'public'" class="ti ti-world"></i>
|
||||
<i v-else-if="draft.visibility === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="draft.visibility === 'followers'" class="ti ti-lock"></i>
|
||||
<i v-else-if="draft.visibility === 'specified'" class="ti ti-mail"></i>
|
||||
</span>
|
||||
<span v-if="draft.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
<MkTime :time="draft.createdAt" :class="$style.draftCreatedAt" mode="detail" colored/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.draftActions" class="_buttons">
|
||||
<MkButton
|
||||
:class="$style.itemButton"
|
||||
small
|
||||
@click="restoreDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-corner-up-left"></i>
|
||||
{{ i18n.ts._drafts.restore }}
|
||||
</MkButton>
|
||||
<MkButton
|
||||
v-tooltip="i18n.ts._drafts.delete"
|
||||
danger
|
||||
small
|
||||
:iconOnly="true"
|
||||
:class="$style.itemButton"
|
||||
@click="deleteDraft(draft)"
|
||||
>
|
||||
<i class="ti ti-trash"></i>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import { getNoteSummary } from '@/utility/get-note-summary.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'restore', draft: Misskey.entities.NoteDraft): void;
|
||||
(ev: 'cancel'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const paging = {
|
||||
endpoint: 'notes/drafts/list',
|
||||
limit: 10,
|
||||
} satisfies PagingCtx;
|
||||
|
||||
const pagingComponent = useTemplateRef('pagingEl');
|
||||
|
||||
const currentDraftsCount = ref(0);
|
||||
misskeyApi('notes/drafts/count').then((count) => {
|
||||
currentDraftsCount.value = count;
|
||||
});
|
||||
|
||||
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
dialogEl.value?.close();
|
||||
}
|
||||
|
||||
function restoreDraft(draft: Misskey.entities.NoteDraft) {
|
||||
emit('restore', draft);
|
||||
dialogEl.value?.close();
|
||||
}
|
||||
|
||||
async function deleteDraft(draft: Misskey.entities.NoteDraft) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts._drafts.deleteAreYouSure,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('notes/drafts/delete', { draftId: draft.id }).then(() => {
|
||||
pagingComponent.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.drafts {
|
||||
overflow-x: hidden;
|
||||
overflow-x: clip;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.draft {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.draftBody {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.draftInfo {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
font-size: 0.85em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.draftMeta {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.draftContent {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.draftFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.draftVisibility {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.draftCreatedAt {
|
||||
font-size: 85%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.draftActions {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: solid 1px var(--MI_THEME-divider);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user