Improve: unclip (#8823)
* Refactor clip page to use Composition API * Refactor clip page * Refactor clip page * Refactor clip page * Improve: unclip * Fix unclip * Fix unclip * chore: better type and name * Fix * Fix clipPage vue provider Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
d7bab7cf0b
commit
5b7595d9d7
7 changed files with 110 additions and 9 deletions
|
@ -643,6 +643,8 @@ clip: "クリップ"
|
||||||
createNew: "新規作成"
|
createNew: "新規作成"
|
||||||
optional: "任意"
|
optional: "任意"
|
||||||
createNewClip: "新しいクリップを作成"
|
createNewClip: "新しいクリップを作成"
|
||||||
|
unclip: "クリップ解除"
|
||||||
|
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?"
|
||||||
public: "パブリック"
|
public: "パブリック"
|
||||||
i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
|
i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
|
||||||
manageAccessTokens: "アクセストークンの管理"
|
manageAccessTokens: "アクセストークンの管理"
|
||||||
|
|
|
@ -99,6 +99,7 @@ import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
|
||||||
import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
|
import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
|
||||||
import * as ep___charts_users from './endpoints/charts/users.js';
|
import * as ep___charts_users from './endpoints/charts/users.js';
|
||||||
import * as ep___clips_addNote from './endpoints/clips/add-note.js';
|
import * as ep___clips_addNote from './endpoints/clips/add-note.js';
|
||||||
|
import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
|
||||||
import * as ep___clips_create from './endpoints/clips/create.js';
|
import * as ep___clips_create from './endpoints/clips/create.js';
|
||||||
import * as ep___clips_delete from './endpoints/clips/delete.js';
|
import * as ep___clips_delete from './endpoints/clips/delete.js';
|
||||||
import * as ep___clips_list from './endpoints/clips/list.js';
|
import * as ep___clips_list from './endpoints/clips/list.js';
|
||||||
|
@ -409,6 +410,7 @@ const eps = [
|
||||||
['charts/user/reactions', ep___charts_user_reactions],
|
['charts/user/reactions', ep___charts_user_reactions],
|
||||||
['charts/users', ep___charts_users],
|
['charts/users', ep___charts_users],
|
||||||
['clips/add-note', ep___clips_addNote],
|
['clips/add-note', ep___clips_addNote],
|
||||||
|
['clips/remove-note', ep___clips_removeNote],
|
||||||
['clips/create', ep___clips_create],
|
['clips/create', ep___clips_create],
|
||||||
['clips/delete', ep___clips_delete],
|
['clips/delete', ep___clips_delete],
|
||||||
['clips/list', ep___clips_list],
|
['clips/list', ep___clips_list],
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import define from '../../define.js';
|
||||||
|
import { ClipNotes, Clips } from '@/models/index.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
import { getNote } from '../../common/getters.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['account', 'notes', 'clips'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:account',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchClip: {
|
||||||
|
message: 'No such clip.',
|
||||||
|
code: 'NO_SUCH_CLIP',
|
||||||
|
id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52',
|
||||||
|
},
|
||||||
|
|
||||||
|
noSuchNote: {
|
||||||
|
message: 'No such note.',
|
||||||
|
code: 'NO_SUCH_NOTE',
|
||||||
|
id: 'aff017de-190e-434b-893e-33a9ff5049d8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
clipId: { type: 'string', format: 'misskey:id' },
|
||||||
|
noteId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['clipId', 'noteId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
const clip = await Clips.findOneBy({
|
||||||
|
id: ps.clipId,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clip == null) {
|
||||||
|
throw new ApiError(meta.errors.noSuchClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = await getNote(ps.noteId).catch(e => {
|
||||||
|
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
await ClipNotes.delete({
|
||||||
|
noteId: note.id,
|
||||||
|
clipId: clip.id,
|
||||||
|
});
|
||||||
|
});
|
|
@ -251,12 +251,12 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
|
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), ev).then(focus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
|
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), menuButton.value, {
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
}).then(focus);
|
}).then(focus);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue';
|
import { computed, inject, onMounted, onUnmounted, reactive, ref, Ref } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import MkNoteSub from './MkNoteSub.vue';
|
import MkNoteSub from './MkNoteSub.vue';
|
||||||
|
@ -225,6 +225,8 @@ function undoReact(note): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cullentClipPage = inject<Ref<misskey.entities.Clip>>('cullentClipPage');
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent): void {
|
function onContextmenu(ev: MouseEvent): void {
|
||||||
const isLink = (el: HTMLElement) => {
|
const isLink = (el: HTMLElement) => {
|
||||||
if (el.tagName === 'A') return true;
|
if (el.tagName === 'A') return true;
|
||||||
|
@ -239,12 +241,12 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
|
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, cullentClipPage }), ev).then(focus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
|
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, cullentClipPage }), menuButton.value, {
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
}).then(focus);
|
}).then(focus);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch } from 'vue';
|
import { computed, watch, provide } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import XNotes from '@/components/notes.vue';
|
import XNotes from '@/components/notes.vue';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
|
@ -47,6 +47,8 @@ watch(() => props.clipId, async () => {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
provide('cullentClipPage', $$(clip));
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
[symbols.PAGE_INFO]: computed(() => clip ? {
|
[symbols.PAGE_INFO]: computed(() => clip ? {
|
||||||
title: clip.name,
|
title: clip.name,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineAsyncComponent, Ref } from 'vue';
|
import { defineAsyncComponent, Ref, inject } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
@ -14,6 +14,8 @@ export function getNoteMenu(props: {
|
||||||
menuButton: Ref<HTMLElement>;
|
menuButton: Ref<HTMLElement>;
|
||||||
translation: Ref<any>;
|
translation: Ref<any>;
|
||||||
translating: Ref<boolean>;
|
translating: Ref<boolean>;
|
||||||
|
isDeleted: Ref<boolean>;
|
||||||
|
cullentClipPage?: Ref<misskey.entities.Clip>;
|
||||||
}) {
|
}) {
|
||||||
const isRenote = (
|
const isRenote = (
|
||||||
props.note.renote != null &&
|
props.note.renote != null &&
|
||||||
|
@ -125,12 +127,37 @@ export function getNoteMenu(props: {
|
||||||
}, null, ...clips.map(clip => ({
|
}, null, ...clips.map(clip => ({
|
||||||
text: clip.name,
|
text: clip.name,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
|
os.promiseDialog(
|
||||||
|
os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
|
||||||
|
null,
|
||||||
|
async (err) => {
|
||||||
|
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
|
||||||
|
const confirm = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
|
||||||
|
});
|
||||||
|
if (!confirm.canceled) {
|
||||||
|
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
|
||||||
|
if (props.cullentClipPage?.value.id === clip.id) props.isDeleted.value = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: err.message + '\n' + err.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}))], props.menuButton.value, {
|
}))], props.menuButton.value, {
|
||||||
}).then(focus);
|
}).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function unclip(): Promise<void> {
|
||||||
|
os.apiWithDialog('clips/remove-note', { clipId: props.cullentClipPage.value.id, noteId: appearNote.id });
|
||||||
|
props.isDeleted.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
async function promote(): Promise<void> {
|
async function promote(): Promise<void> {
|
||||||
const { canceled, result: days } = await os.inputNumber({
|
const { canceled, result: days } = await os.inputNumber({
|
||||||
title: i18n.ts.numberOfDays,
|
title: i18n.ts.numberOfDays,
|
||||||
|
@ -169,7 +196,16 @@ export function getNoteMenu(props: {
|
||||||
noteId: appearNote.id
|
noteId: appearNote.id
|
||||||
});
|
});
|
||||||
|
|
||||||
menu = [{
|
menu = [
|
||||||
|
...(
|
||||||
|
props.cullentClipPage?.value.userId === $i.id ? [{
|
||||||
|
icon: 'fas fa-circle-minus',
|
||||||
|
text: i18n.ts.unclip,
|
||||||
|
danger: true,
|
||||||
|
action: unclip,
|
||||||
|
}, null] : []
|
||||||
|
),
|
||||||
|
{
|
||||||
icon: 'fas fa-copy',
|
icon: 'fas fa-copy',
|
||||||
text: i18n.ts.copyContent,
|
text: i18n.ts.copyContent,
|
||||||
action: copyContent
|
action: copyContent
|
||||||
|
|
Loading…
Reference in a new issue