From c2cba04d81df2146a9bbfe1c033ff509a524eedf Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:05:42 +0000 Subject: [PATCH 01/18] feat: add result format attribute in create note and store note lib --- packages/lib/src/notes/notes.services.ts | 3 +++ packages/lib/src/notes/notes.usecases.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/lib/src/notes/notes.services.ts b/packages/lib/src/notes/notes.services.ts index 02406cbc..e1024cfa 100644 --- a/packages/lib/src/notes/notes.services.ts +++ b/packages/lib/src/notes/notes.services.ts @@ -5,6 +5,7 @@ export { fetchNote, storeNote }; async function storeNote({ payload, ttlInSeconds, + resultFormat, deleteAfterReading, apiBaseUrl, serializationFormat, @@ -13,6 +14,7 @@ async function storeNote({ }: { payload: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading: boolean; apiBaseUrl?: string; serializationFormat: string; @@ -26,6 +28,7 @@ async function storeNote({ body: { payload, ttlInSeconds, + resultFormat, deleteAfterReading, serializationFormat, encryptionAlgorithm, diff --git a/packages/lib/src/notes/notes.usecases.ts b/packages/lib/src/notes/notes.usecases.ts index 1f5cf784..fe194502 100644 --- a/packages/lib/src/notes/notes.usecases.ts +++ b/packages/lib/src/notes/notes.usecases.ts @@ -13,6 +13,7 @@ async function createNote({ content, password, ttlInSeconds, + resultFormat, deleteAfterReading = false, clientBaseUrl = BASE_URL, apiBaseUrl = clientBaseUrl, @@ -25,6 +26,7 @@ async function createNote({ content: string; password?: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading?: boolean; clientBaseUrl?: string; apiBaseUrl?: string; @@ -35,6 +37,7 @@ async function createNote({ storeNote?: (params: { payload: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading: boolean; encryptionAlgorithm: EncryptionAlgorithm; serializationFormat: SerializationFormat; @@ -47,6 +50,7 @@ async function createNote({ const { noteId } = await storeNote({ payload: encryptedPayload, ttlInSeconds, + resultFormat, deleteAfterReading, encryptionAlgorithm, serializationFormat, From a86c9e023437d5b536a8f825e1757da4d65b08ff Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:07:54 +0000 Subject: [PATCH 02/18] feat: add result format to save note repository --- packages/app-server/src/modules/notes/notes.repository.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/app-server/src/modules/notes/notes.repository.ts b/packages/app-server/src/modules/notes/notes.repository.ts index 79f0c473..c3a5f826 100644 --- a/packages/app-server/src/modules/notes/notes.repository.ts +++ b/packages/app-server/src/modules/notes/notes.repository.ts @@ -34,6 +34,7 @@ async function saveNote( { payload, ttlInSeconds, + resultFormat, deleteAfterReading, storage, generateNoteId = generateId, @@ -45,6 +46,7 @@ async function saveNote( { payload: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading: boolean; storage: Storage; generateNoteId?: () => string; @@ -62,6 +64,7 @@ async function saveNote( encryptionAlgorithm, serializationFormat, isPublic, + resultFormat, }; if (!ttlInSeconds) { From f9ae082c9743c2c23dac48ec227879d9532f4c16 Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:08:23 +0000 Subject: [PATCH 03/18] feat: add result format validation in server during creating note --- packages/app-server/src/modules/notes/notes.routes.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/app-server/src/modules/notes/notes.routes.ts b/packages/app-server/src/modules/notes/notes.routes.ts index 9eb51d3c..249b5f11 100644 --- a/packages/app-server/src/modules/notes/notes.routes.ts +++ b/packages/app-server/src/modules/notes/notes.routes.ts @@ -95,6 +95,7 @@ function setupCreateNoteRoute({ app }: { app: ServerInstance }) { .min(TEN_MINUTES_IN_SECONDS) .max(ONE_MONTH_IN_SECONDS) .optional(), + resultFormat: z.enum(['raw', 'code', 'markdown']), // @ts-expect-error zod wants strict non empty array encryptionAlgorithm: z.enum(encryptionAlgorithms), @@ -107,7 +108,7 @@ function setupCreateNoteRoute({ app }: { app: ServerInstance }) { async (context, next) => { const config = context.get('config'); - const { payload, isPublic, ttlInSeconds } = context.req.valid('json'); + const { payload, isPublic, ttlInSeconds, resultFormat } = context.req.valid('json'); if (payload.length > config.notes.maxEncryptedPayloadLength) { throw createNotePayloadTooLargeError(); @@ -121,16 +122,20 @@ function setupCreateNoteRoute({ app }: { app: ServerInstance }) { throw createExpirationDelayRequiredError(); } + if (!resultFormat) { + throw createExpirationDelayRequiredError(); + } + await next(); }, async (context) => { - const { payload, ttlInSeconds, deleteAfterReading, encryptionAlgorithm, serializationFormat, isPublic } = context.req.valid('json'); + const { payload, ttlInSeconds, resultFormat, deleteAfterReading, encryptionAlgorithm, serializationFormat, isPublic } = context.req.valid('json'); const storage = context.get('storage'); const notesRepository = createNoteRepository({ storage }); - const { noteId } = await notesRepository.saveNote({ payload, ttlInSeconds, deleteAfterReading, encryptionAlgorithm, serializationFormat, isPublic }); + const { noteId } = await notesRepository.saveNote({ payload, ttlInSeconds, resultFormat, deleteAfterReading, encryptionAlgorithm, serializationFormat, isPublic }); return context.json({ noteId }); }, From 3f7e47dc8d68a9b5105151f8d7f59bd10ba5aa89 Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:08:47 +0000 Subject: [PATCH 04/18] feat: add note error in server --- packages/app-server/src/modules/notes/notes.errors.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/app-server/src/modules/notes/notes.errors.ts b/packages/app-server/src/modules/notes/notes.errors.ts index 097b710d..f2dd3c52 100644 --- a/packages/app-server/src/modules/notes/notes.errors.ts +++ b/packages/app-server/src/modules/notes/notes.errors.ts @@ -23,3 +23,9 @@ export const createExpirationDelayRequiredError = createErrorFactory({ code: 'note.expiration_delay_required', statusCode: 400, }); + +export const createResultFormatRequiredError = createErrorFactory({ + message: 'Result Format is required', + code: 'note.result_format_required', + statusCode: 400, +}); From 8e42d672df6902bb3db1b61e02358b6d5885044f Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:09:59 +0000 Subject: [PATCH 05/18] feat: add result format to params create note, encrypt note, and store note --- packages/app-client/src/modules/notes/notes.services.ts | 3 +++ packages/app-client/src/modules/notes/notes.usecases.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/app-client/src/modules/notes/notes.services.ts b/packages/app-client/src/modules/notes/notes.services.ts index 5e2acd74..11a547d4 100644 --- a/packages/app-client/src/modules/notes/notes.services.ts +++ b/packages/app-client/src/modules/notes/notes.services.ts @@ -5,6 +5,7 @@ export { fetchNoteById, fetchNoteExists, storeNote }; async function storeNote({ payload, ttlInSeconds, + resultFormat, deleteAfterReading, encryptionAlgorithm, serializationFormat, @@ -12,6 +13,7 @@ async function storeNote({ }: { payload: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading: boolean; encryptionAlgorithm: string; serializationFormat: string; @@ -23,6 +25,7 @@ async function storeNote({ body: { payload, ttlInSeconds, + resultFormat, deleteAfterReading, serializationFormat, encryptionAlgorithm, diff --git a/packages/app-client/src/modules/notes/notes.usecases.ts b/packages/app-client/src/modules/notes/notes.usecases.ts index aa782cf8..00351f70 100644 --- a/packages/app-client/src/modules/notes/notes.usecases.ts +++ b/packages/app-client/src/modules/notes/notes.usecases.ts @@ -7,6 +7,7 @@ async function encryptAndCreateNote(args: { content: string; password?: string; ttlInSeconds?: number; + resultFormat: 'raw' | 'code' | 'markdown'; deleteAfterReading: boolean; fileAssets: File[]; isPublic?: boolean; From a445f1a40feb8af0690cdb6a63c9bf99ed453a61 Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:10:31 +0000 Subject: [PATCH 06/18] feat: integrate result format to ui --- packages/app-client/src/locales/en.json | 6 ++++++ .../src/modules/config/config.constants.ts | 1 + .../src/modules/config/config.types.ts | 1 + .../modules/notes/pages/create-note.page.tsx | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/packages/app-client/src/locales/en.json b/packages/app-client/src/locales/en.json index de370e20..eacd7cfd 100644 --- a/packages/app-client/src/locales/en.json +++ b/packages/app-client/src/locales/en.json @@ -83,6 +83,12 @@ "1w": "1 week", "1m": "1 month" }, + "result-format": "Result Format", + "result-formats": { + "raw": "raw", + "code": "code", + "markdown": "markdown" + }, "delete-after-reading": { "label": "Delete after reading", "description": "Delete the note after reading" diff --git a/packages/app-client/src/modules/config/config.constants.ts b/packages/app-client/src/modules/config/config.constants.ts index ef72d18f..2cab191c 100644 --- a/packages/app-client/src/modules/config/config.constants.ts +++ b/packages/app-client/src/modules/config/config.constants.ts @@ -7,6 +7,7 @@ export const buildTimeConfig: Config = { isAuthenticationRequired: import.meta.env.VITE_IS_AUTHENTICATION_REQUIRED === 'true', defaultDeleteNoteAfterReading: import.meta.env.VITE_DEFAULT_DELETE_NOTE_AFTER_READING === 'true', defaultNoteTtlSeconds: Number(import.meta.env.VITE_DEFAULT_NOTE_TTL_SECONDS ?? 3600), + defaultNoteResultFormat: import.meta.env.VITE_DEFAULT_NOTE_RESULT_FORMAT ?? 'raw', defaultNoteNoExpiration: import.meta.env.VITE_DEFAULT_NOTE_NO_EXPIRATION === 'true', isSettingNoExpirationAllowed: import.meta.env.VITE_IS_SETTING_NO_EXPIRATION_ALLOWED === 'true', }; diff --git a/packages/app-client/src/modules/config/config.types.ts b/packages/app-client/src/modules/config/config.types.ts index e65a33a2..21a13cea 100644 --- a/packages/app-client/src/modules/config/config.types.ts +++ b/packages/app-client/src/modules/config/config.types.ts @@ -5,6 +5,7 @@ export type Config = { enclosedVersion: string; defaultDeleteNoteAfterReading: boolean; defaultNoteTtlSeconds: number; + defaultNoteResultFormat: 'raw' | 'code' | 'markdown'; isSettingNoExpirationAllowed: boolean; defaultNoteNoExpiration: boolean; }; diff --git a/packages/app-client/src/modules/notes/pages/create-note.page.tsx b/packages/app-client/src/modules/notes/pages/create-note.page.tsx index 9365307d..f3fe24ea 100644 --- a/packages/app-client/src/modules/notes/pages/create-note.page.tsx +++ b/packages/app-client/src/modules/notes/pages/create-note.page.tsx @@ -1,3 +1,4 @@ +import type { Config } from '@/modules/config/config.types'; import { authStore } from '@/modules/auth/auth.store'; import { getConfig } from '@/modules/config/config.provider'; import { getFileIcon } from '@/modules/files/files.models'; @@ -119,6 +120,7 @@ export const CreateNotePage: Component = () => { const [getIsNoteCreated, setIsNoteCreated] = createSignal(false); const [getIsPublic, setIsPublic] = createSignal(true); const [getTtlInSeconds, setTtlInSeconds] = createSignal(config.defaultNoteTtlSeconds); + const [getResultFormat, setResultFormat] = createSignal(config.defaultNoteResultFormat); const [getDeleteAfterReading, setDeleteAfterReading] = createSignal(config.defaultDeleteNoteAfterReading); const [getUploadedFiles, setUploadedFiles] = createSignal([]); const [getIsNoteCreating, setIsNoteCreating] = createSignal(false); @@ -162,6 +164,7 @@ export const CreateNotePage: Component = () => { content: getContent(), password: getPassword(), ttlInSeconds: getHasNoExpiration() ? undefined : getTtlInSeconds(), + resultFormat: getResultFormat(), deleteAfterReading: getDeleteAfterReading(), fileAssets: getUploadedFiles(), isPublic: getIsPublic(), @@ -281,6 +284,24 @@ export const CreateNotePage: Component = () => { + + + + {t('create.settings.result-format')} + + + setResultFormat(value as Config['defaultNoteResultFormat'])} + > + + + {t('create.settings.result-formats.raw')} + {t('create.settings.result-formats.code')} + {t('create.settings.result-formats.markdown')} + + + {/* {config.isAuthenticationRequired && ( From e4fbf391d26073aa16538b94dc9b74e36dd1b21a Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:13:32 +0000 Subject: [PATCH 07/18] feat: add result format type to fetch note by id --- packages/app-client/src/modules/notes/notes.services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app-client/src/modules/notes/notes.services.ts b/packages/app-client/src/modules/notes/notes.services.ts index 11a547d4..bc2478b5 100644 --- a/packages/app-client/src/modules/notes/notes.services.ts +++ b/packages/app-client/src/modules/notes/notes.services.ts @@ -43,6 +43,7 @@ async function fetchNoteById({ noteId }: { noteId: string }) { assets: string[]; serializationFormat: string; encryptionAlgorithm: string; + resultFormat: 'raw' | 'code' | 'markdown'; }; }>({ path: `/api/notes/${noteId}`, method: 'GET', From adee17201f798ce68c78b848e5e5ef99e5f2c9ef Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 09:13:53 +0000 Subject: [PATCH 08/18] feat: add result format tab in view note --- .../modules/notes/pages/view-note.page.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/app-client/src/modules/notes/pages/view-note.page.tsx b/packages/app-client/src/modules/notes/pages/view-note.page.tsx index 92bb49b3..a0e7a014 100644 --- a/packages/app-client/src/modules/notes/pages/view-note.page.tsx +++ b/packages/app-client/src/modules/notes/pages/view-note.page.tsx @@ -7,6 +7,7 @@ import { CopyButton } from '@/modules/shared/utils/copy'; import { Alert, AlertDescription } from '@/modules/ui/components/alert'; import { Button } from '@/modules/ui/components/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/modules/ui/components/card'; +import { Tabs, TabsIndicator, TabsList, TabsTrigger } from '@/modules/ui/components/tabs'; import { TextField, TextFieldLabel, TextFieldRoot } from '@/modules/ui/components/textfield'; import { formatBytes, safely, safelySync } from '@corentinth/chisels'; import { decryptNote, noteAssetsToFiles, parseNoteUrlHashFragment } from '@enclosed/lib'; @@ -67,8 +68,9 @@ export const ViewNotePage: Component = () => { const location = useLocation(); const [isPasswordEntered, setIsPasswordEntered] = createSignal(false); const [getError, setError] = createSignal<{ title: string; description: string; action?: JSX.Element } | null>(null); - const [getNote, setNote] = createSignal<{ payload: string; isPasswordProtected: boolean; encryptionAlgorithm: string; serializationFormat: string } | null>(null); + const [getNote, setNote] = createSignal<{ payload: string; isPasswordProtected: boolean; encryptionAlgorithm: string; serializationFormat: string; resultFormat: 'raw' | 'code' | 'markdown' } | null>(null); const [getDecryptedNote, setDecryptedNote] = createSignal(null); + const [getResultFormat, setResultFormat] = createSignal<'raw' | 'code' | 'markdown' | null>(null); const [getIsPasswordInvalid, setIsPasswordInvalid] = createSignal(false); const [fileAssets, setFileAssets] = createSignal([]); const [isDownloadingAllLoading, setIsDownloadingAllLoading] = createSignal(false); @@ -95,7 +97,7 @@ export const ViewNotePage: Component = () => { }; const decrypt = async ({ password }: { password?: string } = {}) => { - const { payload, encryptionAlgorithm, serializationFormat } = getNote()!; + const { payload, encryptionAlgorithm, serializationFormat, resultFormat } = getNote()!; const [decryptionResult, decryptionError] = await safely(decryptNote({ encryptedPayload: payload, @@ -123,6 +125,7 @@ export const ViewNotePage: Component = () => { const files = await noteAssetsToFiles({ noteAssets: note.assets }); setFileAssets(files); setDecryptedNote(note.content); + setResultFormat(resultFormat); setIsPasswordEntered(true); }; @@ -318,8 +321,21 @@ export const ViewNotePage: Component = () => { {getDecryptedNote() && (
-
- {t('view.note-content')} +
+
+ {t('view.note-content')} +
+ setResultFormat(value as 'raw' | 'code' | 'markdown')} + > + + + {t('create.settings.result-formats.raw')} + {t('create.settings.result-formats.code')} + {t('create.settings.result-formats.markdown')} + +
From acb6ad8019ca293485f27c5a9b788c1971dcf130 Mon Sep 17 00:00:00 2001 From: mistersupri Date: Mon, 6 Jan 2025 13:03:39 +0000 Subject: [PATCH 09/18] feat: install marked and highlight for markdown and syntax higlighting format note --- packages/app-client/index.html | 3 + packages/app-client/package.json | 5 ++ .../src/modules/notes/pages/index.d.ts | 7 +++ pnpm-lock.yaml | 59 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 packages/app-client/src/modules/notes/pages/index.d.ts diff --git a/packages/app-client/index.html b/packages/app-client/index.html index b0a540d3..8aaa417d 100644 --- a/packages/app-client/index.html +++ b/packages/app-client/index.html @@ -33,6 +33,9 @@ + + +