diff --git a/apps/desktop/src/main2/home/note-editor.css b/apps/desktop/src/main2/home/note-editor.css index 9fe0b7cd6c..4afcf4af9a 100644 --- a/apps/desktop/src/main2/home/note-editor.css +++ b/apps/desktop/src/main2/home/note-editor.css @@ -31,3 +31,7 @@ background-repeat: no-repeat; background-size: contain; } + +.main2-daily-note-editor .ProseMirror .is-empty::before { + color: #b5b5b5; +} diff --git a/apps/desktop/src/main2/home/note-editor.tsx b/apps/desktop/src/main2/home/note-editor.tsx index e314af29c9..351de460eb 100644 --- a/apps/desktop/src/main2/home/note-editor.tsx +++ b/apps/desktop/src/main2/home/note-editor.tsx @@ -13,6 +13,7 @@ import { type JSONContent, NoteEditor, type NoteEditorRef, + type PlaceholderFunction, schema, } from "@hypr/editor/note"; import { useTaskStorageOptional } from "@hypr/editor/task-storage"; @@ -41,6 +42,31 @@ type Store = NonNullable>; const emptyDoc: JSONContent = { type: "doc", content: [{ type: "paragraph" }] }; const extraNodeViews = { appLink: AppLinkView, session: SessionNodeView }; +const dailyPlaceholder: PlaceholderFunction = ({ node, pos, parent }) => { + const type = node.type.name; + const parentType = parent?.type.name; + + if (type === "heading") { + const level = typeof node.attrs.level === "number" ? node.attrs.level : 1; + return `Heading ${level}`; + } + + if (type === "codeBlock") { + return "Code"; + } + + if (type === "paragraph") { + if (parentType === "listItem") return "List"; + if (parentType === "taskItem") return "Task"; + if (parentType === "blockquote") return "Quote"; + if (parentType === "doc" && pos === 0) { + return "Type anything or press / for commands and start your Daily Note..."; + } + } + + return ""; +}; + function getSessionTitle(store: Store, sessionId: string): string { const title = store.getCell("sessions", sessionId, "title"); return typeof title === "string" ? title : ""; @@ -381,6 +407,7 @@ export function DailyNoteEditor({ initialContent={initialContentRef.current} handleChange={handleChange} linkedItemOpenBehavior="new" + placeholderComponent={dailyPlaceholder} taskSource={taskSource} extraNodeViews={extraNodeViews} /> diff --git a/apps/desktop/src/session/components/note-input/raw.tsx b/apps/desktop/src/session/components/note-input/raw.tsx index abdde54c85..94b22abfef 100644 --- a/apps/desktop/src/session/components/note-input/raw.tsx +++ b/apps/desktop/src/session/components/note-input/raw.tsx @@ -90,8 +90,28 @@ export const RawEditor = forwardRef< ); }); -const Placeholder: PlaceholderFunction = ({ node, pos }) => { - if (node.type.name !== "paragraph") { +const Placeholder: PlaceholderFunction = ({ node, pos, parent }) => { + const type = node.type.name; + const parentType = parent?.type.name; + + if (type === "heading") { + const level = typeof node.attrs.level === "number" ? node.attrs.level : 1; + return `Heading ${level}`; + } + + if (type === "codeBlock") { + return "Code"; + } + + if (type !== "paragraph") { + return ""; + } + + if (parentType === "listItem") return "List"; + if (parentType === "taskItem") return "Task"; + if (parentType === "blockquote") return "Quote"; + + if (parentType !== "doc") { return ""; } diff --git a/packages/editor/src/plugins/placeholder.ts b/packages/editor/src/plugins/placeholder.ts index 2ffd3c0d0f..bd2f3868fe 100644 --- a/packages/editor/src/plugins/placeholder.ts +++ b/packages/editor/src/plugins/placeholder.ts @@ -5,6 +5,7 @@ import { Decoration, DecorationSet } from "prosemirror-view"; export type PlaceholderFunction = (props: { node: PMNode; pos: number; + parent: PMNode | null; hasAnchor: boolean; }) => string; @@ -16,7 +17,7 @@ export function placeholderPlugin(placeholder?: PlaceholderFunction) { props: { decorations(state) { const { doc, selection } = state; - const { anchor } = selection; + const { anchor, $anchor } = selection; const decorations: Decoration[] = []; const isEmptyDoc = @@ -24,7 +25,7 @@ export function placeholderPlugin(placeholder?: PlaceholderFunction) { doc.firstChild!.isTextblock && doc.firstChild!.content.size === 0; - doc.descendants((node, pos) => { + doc.descendants((node, pos, parent) => { const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; const isEmpty = !node.isLeaf && node.content.size === 0; @@ -33,7 +34,7 @@ export function placeholderPlugin(placeholder?: PlaceholderFunction) { if (isEmptyDoc) classes.push("is-editor-empty"); const text = placeholder - ? placeholder({ node, pos, hasAnchor }) + ? placeholder({ node, pos, parent, hasAnchor }) : ""; if (text) { @@ -49,6 +50,35 @@ export function placeholderPlugin(placeholder?: PlaceholderFunction) { return false; }); + if (placeholder && $anchor.depth > 1) { + const node = $anchor.parent; + if (!node.isLeaf && node.content.size === 0) { + const parent = $anchor.node($anchor.depth - 1); + const pos = $anchor.before($anchor.depth); + const text = placeholder({ + node, + pos, + parent, + hasAnchor: true, + }); + + if (text) { + decorations.push( + Decoration.widget( + pos + 1, + () => { + const span = document.createElement("span"); + span.className = "is-empty"; + span.setAttribute("data-placeholder", text); + return span; + }, + { side: 0, key: `placeholder-${pos}-${text}` }, + ), + ); + } + } + } + return DecorationSet.create(doc, decorations); }, },