From f2dbbc20b9ee3bcb4ba72dfe72a54ef06506343b Mon Sep 17 00:00:00 2001 From: blinko-space Date: Sat, 7 Dec 2024 15:47:26 +0800 Subject: [PATCH] feat: support trash bin --- public/locales/ar/translation.json | 3 +- public/locales/de/translation.json | 3 +- public/locales/en/translation.json | 3 +- public/locales/es/translation.json | 3 +- public/locales/fr/translation.json | 3 +- public/locales/ja/translation.json | 3 +- public/locales/ko/translation.json | 3 +- public/locales/pt/translation.json | 3 +- public/locales/ru/translation.json | 3 +- public/locales/zh-TW/translation.json | 3 +- public/locales/zh/translation.json | 3 +- src/components/BlinkoAddButton/index.tsx | 11 +-- src/components/BlinkoCard/index.tsx | 2 + src/components/BlinkoEditor/index.tsx | 1 - src/components/BlinkoRightClickMenu/index.tsx | 70 ++++++++++++++++--- .../Editor/Toolbar/SendButton/index.tsx | 18 +++-- .../Editor/Toolbar/ViewModeButton/index.tsx | 6 -- src/components/Common/Editor/editorUtils.tsx | 5 +- src/components/Layout/MobileNavBar.tsx | 2 +- src/pages/index.tsx | 9 +-- src/pages/resources.tsx | 1 - src/pages/trash.tsx | 8 +++ src/server/routers/note.ts | 23 ++++-- src/store/baseStore.ts | 6 ++ src/store/blinkoStore.tsx | 12 ++-- 25 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 src/pages/trash.tsx diff --git a/public/locales/ar/translation.json b/public/locales/ar/translation.json index 4ce4376b..fc2e379e 100644 --- a/public/locales/ar/translation.json +++ b/public/locales/ar/translation.json @@ -201,5 +201,6 @@ "remove-bold": "إزالة الخط العريض", "remove-underline": "إزالة التسطير", "select-block-type": "حدد نوع الكتلة", - "block-type-select-placeholder": "نوع المربع" + "block-type-select-placeholder": "نوع المربع", + "trash": "قمامة" } diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index ede0229a..fd024f4e 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -195,5 +195,6 @@ "remove-bold": "Fettdruck entfernen", "remove-underline": "Unterstrich entfernen", "select-block-type": "Blocktyp auswählen", - "block-type-select-placeholder": "Block Typ" + "block-type-select-placeholder": "Block Typ", + "trash": "Müll" } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index c76f44d8..6b6d6e3c 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -200,5 +200,6 @@ "underline": "Underline", "remove-underline": "Remove Underline", "select-block-type": "Select Block Type", - "block-type-select-placeholder": "Block Type" + "block-type-select-placeholder": "Block Type", + "trash": "Recycle Bin" } diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 8fbdc7ad..8ad7cfa3 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -195,5 +195,6 @@ "remove-bold": "Eliminar negrita", "remove-underline": "Eliminar subrayado", "select-block-type": "Seleccione el tipo de bloque", - "block-type-select-placeholder": "Tipo de bloque" + "block-type-select-placeholder": "Tipo de bloque", + "trash": "basura" } diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 41b29cd8..25d48c2f 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -196,5 +196,6 @@ "remove-bold": "Supprimer les caractères gras", "remove-underline": "Supprimer le soulignement", "select-block-type": "Sélectionner le type de bloc", - "block-type-select-placeholder": "Type de bloc" + "block-type-select-placeholder": "Type de bloc", + "trash": "poubelle" } diff --git a/public/locales/ja/translation.json b/public/locales/ja/translation.json index ad9af611..e5b50d8f 100644 --- a/public/locales/ja/translation.json +++ b/public/locales/ja/translation.json @@ -193,5 +193,6 @@ "remove-bold": "太字を削除", "remove-underline": "アンダーラインの削除", "select-block-type": "ブロックタイプを選択", - "block-type-select-placeholder": "ブロックタイプ" + "block-type-select-placeholder": "ブロックタイプ", + "trash": "ゴミ" } diff --git a/public/locales/ko/translation.json b/public/locales/ko/translation.json index b15bfb63..a41ee9e6 100644 --- a/public/locales/ko/translation.json +++ b/public/locales/ko/translation.json @@ -195,5 +195,6 @@ "italic": "이탤릭체", "remove-underline": "밑줄 제거", "select-block-type": "블록 유형 선택", - "block-type-select-placeholder": "블록 유형" + "block-type-select-placeholder": "블록 유형", + "trash": "쓰레기" } diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json index 7d7eef73..5f8e558d 100644 --- a/public/locales/pt/translation.json +++ b/public/locales/pt/translation.json @@ -192,5 +192,6 @@ "italic": "Itálico", "remove-underline": "Remover o sublinhado", "select-block-type": "Selecionar o tipo de bloco", - "block-type-select-placeholder": "Tipo de bloco" + "block-type-select-placeholder": "Tipo de bloco", + "trash": "lixo" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index d3c0b174..5e3ef058 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -196,5 +196,6 @@ "italic": "Курсив", "remove-underline": "Удалить подчеркивание", "select-block-type": "Выберите тип блока", - "block-type-select-placeholder": "Тип блока" + "block-type-select-placeholder": "Тип блока", + "trash": "мусор" } diff --git a/public/locales/zh-TW/translation.json b/public/locales/zh-TW/translation.json index 865e873d..04c1b35c 100644 --- a/public/locales/zh-TW/translation.json +++ b/public/locales/zh-TW/translation.json @@ -196,5 +196,6 @@ "italic": "斜体", "remove-underline": "删除下划线", "select-block-type": "选择区块类型", - "block-type-select-placeholder": "区块类型" + "block-type-select-placeholder": "区块类型", + "trash": "回收站" } diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 2ff47bd9..96ebb4d1 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -199,5 +199,6 @@ "italic": "斜体", "remove-underline": "删除下划线", "select-block-type": "选择区块类型", - "block-type-select-placeholder": "区块类型" + "block-type-select-placeholder": "区块类型", + "trash": "回收站" } diff --git a/src/components/BlinkoAddButton/index.tsx b/src/components/BlinkoAddButton/index.tsx index 6340ee96..54f7cb00 100644 --- a/src/components/BlinkoAddButton/index.tsx +++ b/src/components/BlinkoAddButton/index.tsx @@ -5,7 +5,7 @@ import { Icon } from '@iconify/react'; import { observer } from 'mobx-react-lite'; import { useMediaQuery } from 'usehooks-ts'; import { ShowEditBlinkoModel } from '../BlinkoRightClickMenu'; -import { getEditorElements } from '../Common/Editor/editorUtils'; +import { FocusEditor, getEditorElements } from '../Common/Editor/editorUtils'; import { RootStore } from '@/store'; import { DialogStore } from '@/store/module/Dialog'; import { BlinkoAiChat } from '../BlinkoAi'; @@ -99,14 +99,7 @@ export const BlinkoAddButton = observer(() => { // Handle write action const handleWriteAction = () => { ShowEditBlinkoModel('2xl', 'create') - requestAnimationFrame(() => { - const editorElements = getEditorElements() - if (editorElements.length > 0) { - editorElements.forEach(editorElement => { - editorElement.focus() - }) - } - }) + FocusEditor() setShowActions(false); }; diff --git a/src/components/BlinkoCard/index.tsx b/src/components/BlinkoCard/index.tsx index f1a5a023..3b71cadc 100644 --- a/src/components/BlinkoCard/index.tsx +++ b/src/components/BlinkoCard/index.tsx @@ -16,6 +16,7 @@ import { CardHeader } from "./cardHeader"; import { CardFooter } from "./cardFooter"; import { useHistoryBack } from "@/lib/hooks"; import { useRouter } from "next/router"; +import { FocusEditor } from "../Common/Editor/editorUtils"; interface BlinkoCardProps { blinkoItem: Note & { @@ -70,6 +71,7 @@ export const BlinkoCard = observer(({ blinkoItem, isShareMode = false }: BlinkoC const handleDoubleClick = (e: React.MouseEvent) => { blinko.curSelectedNote = _.cloneDeep(blinkoItem); ShowEditBlinkoModel(); + FocusEditor(true) }; return ( diff --git a/src/components/BlinkoEditor/index.tsx b/src/components/BlinkoEditor/index.tsx index b20c594a..7e143e81 100644 --- a/src/components/BlinkoEditor/index.tsx +++ b/src/components/BlinkoEditor/index.tsx @@ -21,7 +21,6 @@ export const BlinkoEditor = observer(({ mode, onSended, onHeightChange }: IProps const router = useRouter() useEffect(() => { blinko.isCreateMode = mode == 'create' - FocusEditor() }, [mode]) return
{ blinko.isCreateMode = mode == 'create' diff --git a/src/components/BlinkoRightClickMenu/index.tsx b/src/components/BlinkoRightClickMenu/index.tsx index 91268919..c7e9873c 100644 --- a/src/components/BlinkoRightClickMenu/index.tsx +++ b/src/components/BlinkoRightClickMenu/index.tsx @@ -14,6 +14,7 @@ import { useEffect, useState } from "react"; import { NoteType } from "@/server/types"; import { useRouter } from "next/router"; import { AiStore } from "@/store/aiStore"; +import { FocusEditor } from "../Common/Editor/editorUtils"; export const ShowEditBlinkoModel = (size: string = '2xl', mode: 'create' | 'edit' = 'edit') => { const blinko = RootStore.Get(BlinkoStore) @@ -31,7 +32,6 @@ export const ShowEditBlinkoModel = (size: string = '2xl', mode: 'create' | 'edit } export const EditItem = observer(() => { const { t } = useTranslation(); - const blinko = RootStore.Get(BlinkoStore) const [isDetailPage, setIsDetailPage] = useState(false) const router = useRouter() useEffect(() => { @@ -105,10 +105,32 @@ export const ArchivedItem = observer(() => { const { t } = useTranslation(); const blinko = RootStore.Get(BlinkoStore) return
{ - blinko.upsertNote.call({ id: blinko.curSelectedNote?.id, isArchived: !blinko.curSelectedNote?.isArchived }) + if (blinko.curSelectedNote?.isRecycle) { + return blinko.upsertNote.call({ + id: blinko.curSelectedNote?.id, + isRecycle: false, + isArchived: false + }) + } + + if (blinko.curSelectedNote?.isArchived) { + return blinko.upsertNote.call({ + id: blinko.curSelectedNote?.id, + isArchived: false, + }) + } + + if (!blinko.curSelectedNote?.isArchived) { + return blinko.upsertNote.call({ + id: blinko.curSelectedNote?.id, + isArchived: true + }) + } + + }}> - {blinko.curSelectedNote?.isArchived ? t('recovery') : t('archive')} + {blinko.curSelectedNote?.isArchived || blinko.curSelectedNote?.isRecycle ? t('recovery') : t('archive')}
}) @@ -120,12 +142,23 @@ export const AITagItem = observer(() => {
{ aiStore.autoTag.call(blinko.curSelectedNote?.id!, blinko.curSelectedNote?.content!) }}> - +
{t('ai-tag')}
); }); +export const TrashItem = observer(() => { + const { t } = useTranslation(); + const blinko = RootStore.Get(BlinkoStore) + return
{ + PromiseCall(api.notes.trashMany.mutate({ ids: [blinko.curSelectedNote?.id!] })) + }}> + +
{t('trash')}
+
+}) + export const DeleteItem = observer(() => { const { t } = useTranslation(); const blinko = RootStore.Get(BlinkoStore) @@ -182,9 +215,18 @@ export const BlinkoRightClickMenu = observer(() => { - - - + + {!blinko.curSelectedNote?.isRecycle ? ( + + + + ) : <>} + + {blinko.curSelectedNote?.isRecycle ? ( + + + + ) : <>} }) @@ -217,9 +259,17 @@ export const LeftCickMenu = observer(({ onTrigger, className }: { onTrigger: () ) : <>} - - - + {!blinko.curSelectedNote?.isRecycle ? ( + + + + ) : <>} + + {blinko.curSelectedNote?.isRecycle ? ( + + + + ) : <>} }) \ No newline at end of file diff --git a/src/components/Common/Editor/Toolbar/SendButton/index.tsx b/src/components/Common/Editor/Toolbar/SendButton/index.tsx index 404523c9..be7300c8 100644 --- a/src/components/Common/Editor/Toolbar/SendButton/index.tsx +++ b/src/components/Common/Editor/Toolbar/SendButton/index.tsx @@ -12,10 +12,9 @@ interface Props { } export const SendButton = ({ store, isSendLoading }: Props) => { + const isPc = useMediaQuery('(min-width: 768px)') return ( -
{ - store.handleSend() - }}> +
-
+
); }; \ No newline at end of file diff --git a/src/components/Common/Editor/Toolbar/ViewModeButton/index.tsx b/src/components/Common/Editor/Toolbar/ViewModeButton/index.tsx index b2e863ec..de9c4a61 100644 --- a/src/components/Common/Editor/Toolbar/ViewModeButton/index.tsx +++ b/src/components/Common/Editor/Toolbar/ViewModeButton/index.tsx @@ -2,7 +2,6 @@ import { IconButton } from '../IconButton'; import { useTranslation } from 'react-i18next'; import { eventBus } from '@/lib/event'; import { ViewMode } from '@mdxeditor/editor' -import { FocusEditor, FocusSourceEditor } from '../../editorUtils'; interface Props { viewMode: ViewMode; @@ -16,11 +15,6 @@ export const ViewModeButton = ({ viewMode }: Props) => { onClick={() => { const nextMode = viewMode === 'source' ? 'rich-text' : 'source'; eventBus.emit('editor:setViewMode', nextMode); - if (nextMode === 'source') { - FocusSourceEditor() - } else { - FocusEditor() - } }} > { } -export const FocusEditor = () => { +export const FocusEditor = (focusToEnd: boolean = false) => { requestAnimationFrame(() => { const editorElements = getEditorElements() if (editorElements.length > 0) { editorElements.forEach(editorElement => { editorElement.focus() + if(!focusToEnd) return const range = document.createRange() range.selectNodeContents(editorElement) range.collapse(false) @@ -82,4 +83,4 @@ export const FocusEditor = () => { }) } }) -} +} \ No newline at end of file diff --git a/src/components/Layout/MobileNavBar.tsx b/src/components/Layout/MobileNavBar.tsx index 6d252579..a1b5a8eb 100644 --- a/src/components/Layout/MobileNavBar.tsx +++ b/src/components/Layout/MobileNavBar.tsx @@ -25,7 +25,7 @@ export const MobileNavBar = observer(({ onItemClick }: MobileNavBarProps) => { return (
- {base.routerList.map(i => ( + {base.routerList.filter(i => !i.hiddenMobile).map(i => ( { @@ -23,7 +20,7 @@ const Home = observer(() => { const store = RootStore.Local(() => ({ editorHeight: 65, get showEditor() { - return !blinko.noteListFilterConfig.isArchived + return !blinko.noteListFilterConfig.isArchived && !blinko.noteListFilterConfig.isRecycle }, get showLoadAll() { return blinko.noteList.isLoadAll @@ -73,8 +70,6 @@ const Home = observer(() => { {store.showLoadAll &&
{t('all-notes-have-been-loaded', { items: blinko.noteList.value?.length })}
} } - -
); }); diff --git a/src/pages/resources.tsx b/src/pages/resources.tsx index 07f702ad..11ffd5cf 100644 --- a/src/pages/resources.tsx +++ b/src/pages/resources.tsx @@ -4,7 +4,6 @@ import { BlinkoStore } from "@/store/blinkoStore"; import { Card, Image } from "@nextui-org/react"; import { observer } from "mobx-react-lite"; import { useEffect } from "react"; -import { FileIcon, defaultStyles } from 'react-file-icon'; import { filesize } from "filesize"; import dayjs from "@/lib/dayjs"; import { PhotoProvider, PhotoView } from "react-photo-view"; diff --git a/src/pages/trash.tsx b/src/pages/trash.tsx new file mode 100644 index 00000000..56316169 --- /dev/null +++ b/src/pages/trash.tsx @@ -0,0 +1,8 @@ +import { observer } from "mobx-react-lite"; +import Home from "."; + +const Page = observer(() => { + return +}); + +export default Page \ No newline at end of file diff --git a/src/server/routers/note.ts b/src/server/routers/note.ts index 7c50ef20..258295a6 100644 --- a/src/server/routers/note.ts +++ b/src/server/routers/note.ts @@ -59,7 +59,6 @@ export const noteRouter = router({ } } let where: Prisma.notesWhereInput = { - isRecycle, OR: [ { accountId: Number(ctx.id) }, { accountId: null } @@ -72,7 +71,10 @@ export const noteRouter = router({ content: { contains: searchText, mode: 'insensitive' } } } else { - where.isArchived = isArchived + where.isRecycle = isRecycle + if (!isRecycle) { + where.isArchived = isArchived + } if (type != -1) { where.type = type } @@ -231,11 +233,12 @@ export const noteRouter = router({ isArchived: z.union([z.boolean(), z.null()]).default(null), isTop: z.union([z.boolean(), z.null()]).default(null), isShare: z.union([z.boolean(), z.null()]).default(null), - references: z.array(z.number()).optional(), + isRecycle: z.union([z.boolean(), z.null()]).default(null), + references: z.array(z.number()).optional() })) .output(z.any()) .mutation(async function ({ input, ctx }) { - let { id, isArchived, type, attachments, content, isTop, isShare, references } = input + let { id, isArchived, isRecycle, type, attachments, content, isTop, isShare, references } = input if (content != null) { content = content?.replace(/ /g, ' ')?.replace(/ \\/g, '')?.replace(/\\([#<>{}[\]|`*-_.])/g, '$1'); } @@ -263,6 +266,7 @@ export const noteRouter = router({ ...(isArchived !== null && { isArchived }), ...(isTop !== null && { isTop }), ...(isShare !== null && { isShare }), + ...(isRecycle !== null && { isRecycle }), ...(content != null && { content }) } @@ -300,6 +304,7 @@ export const noteRouter = router({ } }) } + if (needTobeAddedRelationTags.length != 0) { await prisma.tagsToNote.createMany({ data: needTobeAddedRelationTags.map(i => { @@ -389,8 +394,16 @@ export const noteRouter = router({ } return await prisma.notes.updateMany({ where: { id: { in: ids } }, data: update }) }), + trashMany: authProcedure + .meta({ openapi: { method: 'POST', path: '/v1/note/batch-trash', summary: 'Batch trash note', protect: true, tags: ['Note'] } }) + .input(z.object({ ids: z.array(z.number()) })) + .output(z.any()) + .mutation(async function ({ input }) { + const { ids } = input + return await prisma.notes.updateMany({ where: { id: { in: ids } }, data: { isRecycle: true } }) + }), deleteMany: authProcedure.use(demoAuthMiddleware) - .meta({ openapi: { method: 'POST', path: '/v1/note/batch-delete', summary: 'Batch delete update note', protect: true, tags: ['Note'] } }) + .meta({ openapi: { method: 'POST', path: '/v1/note/batch-delete', summary: 'Batch delete note', protect: true, tags: ['Note'] } }) // .output(z.union([z.null(), notesSchema])) .input(z.object({ ids: z.array(z.number()) diff --git a/src/store/baseStore.ts b/src/store/baseStore.ts index 7ed93d45..a5bf8708 100644 --- a/src/store/baseStore.ts +++ b/src/store/baseStore.ts @@ -37,6 +37,12 @@ export class BaseStore implements Store { shallow: true, icon: 'solar:box-broken' }, + { + title: "trash", + href: '/trash', + hiddenMobile: true, + icon: 'formkit:trash' + }, { title: "settings", href: '/settings', diff --git a/src/store/blinkoStore.tsx b/src/store/blinkoStore.tsx index 12a04b33..7e98777e 100644 --- a/src/store/blinkoStore.tsx +++ b/src/store/blinkoStore.tsx @@ -36,6 +36,7 @@ export class BlinkoStore implements Store { } noteListFilterConfig = { isArchived: false, + isRecycle: false, type: 0, tagId: null as number | null, searchText: "", @@ -49,12 +50,12 @@ export class BlinkoStore implements Store { updateTicker = 0 fullNoteList: Note[] = [] upsertNote = new PromiseState({ - function: async ({ content = null, isArchived, type, id, attachments = [], refresh = true, isTop, isShare, showToast = true, references = [] }: - { content?: string | null, isArchived?: boolean, type?: NoteType, id?: number, attachments?: Attachment[], refresh?: boolean, isTop?: boolean, isShare?: boolean, showToast?: boolean, references?: number[] }) => { + function: async ({ content = null, isArchived, isRecycle, type, id, attachments = [], refresh = true, isTop, isShare, showToast = true, references = [] }: + { content?: string | null, isArchived?: boolean, isRecycle?: boolean, type?: NoteType, id?: number, attachments?: Attachment[], refresh?: boolean, isTop?: boolean, isShare?: boolean, showToast?: boolean, references?: number[] }) => { if (type == undefined) { type = this.noteTypeDefault } - const res = await api.notes.upsert.mutate({ content, type, isArchived, id, attachments, isTop, isShare, references }) + const res = await api.notes.upsert.mutate({ content, type, isArchived, isRecycle, id, attachments, isTop, isShare, references }) if (this.config.value?.isUseAI) { if (res?.id) { api.ai.embeddingUpsert.mutate({ id: res!.id, content: res!.content, type: id ? 'update' : 'insert' }, { context: { skipBatch: true } }) @@ -70,7 +71,6 @@ export class BlinkoStore implements Store { } }) - noteList = new PromisePageState({ function: async ({ page, size }) => { const notes = await api.notes.list.mutate({ ...this.noteListFilterConfig, page, size }) @@ -228,6 +228,7 @@ export class BlinkoStore implements Store { this.noteListFilterConfig.withLink = false this.noteListFilterConfig.withFile = false this.noteListFilterConfig.searchText = searchText ?? '' + this.noteListFilterConfig.isRecycle = false if (router.pathname == '/notes') { this.noteListFilterConfig.type = NoteType.NOTE @@ -253,6 +254,9 @@ export class BlinkoStore implements Store { this.noteListFilterConfig.type = -1 this.noteListFilterConfig.isArchived = true } + if (router.pathname == '/trash') { + this.noteListFilterConfig.isRecycle = true + } this.noteList.resetAndCall({}) }, [router.isReady, this.forceQuery]) }