From 83689699325685052e53a2e65cb47e991530c11f Mon Sep 17 00:00:00 2001 From: Wael Nasreddine Date: Thu, 25 Dec 2025 22:06:30 -0800 Subject: [PATCH 01/75] implement the second part of the sharejs --- apps/server/src/services/export/zip.ts | 8 ++++++++ apps/server/src/share/content_renderer.ts | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/server/src/services/export/zip.ts b/apps/server/src/services/export/zip.ts index 53e0eb540a..4a9f140411 100644 --- a/apps/server/src/services/export/zip.ts +++ b/apps/server/src/services/export/zip.ts @@ -295,6 +295,14 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch, return url ? `href="${url}"` : match; }); + if (format === "share") { + content = content.replace(/src="[^"]*api\/notes\/([a-zA-Z0-9_]+)\/download"/g, (match, targetNoteId) => { + const url = getNoteTargetUrl(targetNoteId, noteMeta); + + return url ? `src="${url}"` : match; + }); + } + return content; function findAttachment(targetAttachmentId: string) { diff --git a/apps/server/src/share/content_renderer.ts b/apps/server/src/share/content_renderer.ts index 897f103941..1316af4ceb 100644 --- a/apps/server/src/share/content_renderer.ts +++ b/apps/server/src/share/content_renderer.ts @@ -73,6 +73,14 @@ export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath note: parentBranch.getNote() }; + // Determine JS to load. + const jsToLoad: string[] = [ + `${basePath}assets/scripts.js` + ]; + for (const jsRelation of note.getRelations("shareJs")) { + jsToLoad.push(`api/notes/${jsRelation.value}/download`); + } + return renderNoteContentInternal(note, { subRoot, rootNoteId: parentBranch.noteId, @@ -80,9 +88,7 @@ export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath `${basePath}assets/styles.css`, `${basePath}assets/scripts.css`, ], - jsToLoad: [ - `${basePath}assets/scripts.js` - ], + jsToLoad, logoUrl: `${basePath}icon-color.svg`, faviconUrl: `${basePath}favicon.ico`, ancestors, From 3d1f6c4f91b2c0d936c95535e5be50e6b44f53c2 Mon Sep 17 00:00:00 2001 From: Wael Nasreddine Date: Thu, 25 Dec 2025 22:54:14 -0800 Subject: [PATCH 02/75] be loosy and honor startsWith application/javascript --- apps/server/src/services/export/zip/share_theme.ts | 3 +-- apps/server/src/share/content_renderer.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/server/src/services/export/zip/share_theme.ts b/apps/server/src/services/export/zip/share_theme.ts index 7775b52d6d..2f1784c132 100644 --- a/apps/server/src/services/export/zip/share_theme.ts +++ b/apps/server/src/services/export/zip/share_theme.ts @@ -116,8 +116,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider { return null; } - // TODO: Should we allow mime to also include backend, i.e loosely check that it starts with application/javascript and ignore the rest? - if (type === "code" && mime === "application/javascript;env=frontend"){ + if (mime.startsWith("application/javascript")) { return "js"; } diff --git a/apps/server/src/share/content_renderer.ts b/apps/server/src/share/content_renderer.ts index 1316af4ceb..7a92d2728b 100644 --- a/apps/server/src/share/content_renderer.ts +++ b/apps/server/src/share/content_renderer.ts @@ -155,7 +155,7 @@ interface RenderArgs { } function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs) { - if (renderArgs.isStatic && note.type == "code" && note.mime === "application/javascript;env=frontend") { + if (renderArgs.isStatic && note.mime.startsWith("application/javascript")) { if (note.isProtected) { // TODO: how to handle this case here? throw new Error(`note ${note.noteId} is protected and cannot be exported`); From 9098bfb63a61e12130088b1c2148ad258ef767b9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 29 Dec 2025 21:26:52 +0200 Subject: [PATCH 03/75] chore(client): prototype implementation to communicate data through note context --- CONTEXT_DATA_EXAMPLE.md | 194 +++++++++++++++++++++ apps/client/src/components/app_context.ts | 5 + apps/client/src/components/note_context.ts | 64 +++++++ apps/client/src/widgets/react/hooks.tsx | 110 ++++++++++++ 4 files changed, 373 insertions(+) create mode 100644 CONTEXT_DATA_EXAMPLE.md diff --git a/CONTEXT_DATA_EXAMPLE.md b/CONTEXT_DATA_EXAMPLE.md new file mode 100644 index 0000000000..ecd383eac1 --- /dev/null +++ b/CONTEXT_DATA_EXAMPLE.md @@ -0,0 +1,194 @@ +# Context Data Pattern - Usage Examples + +This document shows how to use the new context data pattern to communicate between type widgets (inside splits) and shared components (sidebars, toolbars). + +## Architecture + +Data is stored directly on the `NoteContext` object: +- **Type widgets** (PDF, Text, Code) publish data using `useSetContextData()` +- **Sidebar/toolbar components** read data using `useGetContextData()` +- Data is automatically cleared when switching notes +- Components automatically re-render when data changes + +## Example 1: PDF Page Navigation + +### Publishing from PDF Viewer (inside split) + +```tsx +// In Pdf.tsx +import { useSetContextData } from "../../react/hooks"; + +interface PdfPageInfo { + pageNumber: number; + title: string; +} + +export default function PdfPreview({ note, blob }) { + const { noteContext } = useActiveNoteContext(); + const [pages, setPages] = useState([]); + + useEffect(() => { + // When PDF loads, extract page information + const pageInfo = extractPagesFromPdf(); + setPages(pageInfo); + }, [blob]); + + // Publish pages to context data + useSetContextData(noteContext, "pdfPages", pages); + + return