diff options
Diffstat (limited to 'apps/web/app/(editor)/lib')
| -rw-r--r-- | apps/web/app/(editor)/lib/content.ts | 231 | ||||
| -rw-r--r-- | apps/web/app/(editor)/lib/debouncedsave.ts | 20 | ||||
| -rw-r--r-- | apps/web/app/(editor)/lib/editorprops.ts | 16 | ||||
| -rw-r--r-- | apps/web/app/(editor)/lib/use-local-storage.ts | 27 |
4 files changed, 294 insertions, 0 deletions
diff --git a/apps/web/app/(editor)/lib/content.ts b/apps/web/app/(editor)/lib/content.ts new file mode 100644 index 00000000..6464cfa1 --- /dev/null +++ b/apps/web/app/(editor)/lib/content.ts @@ -0,0 +1,231 @@ +export const defaultEditorContent = { + type: "doc", + content: [ + { + type: "heading", + attrs: { level: 2 }, + content: [{ type: "text", text: "Introducing Novel" }], + }, + { + type: "paragraph", + content: [ + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://github.com/steven-tey/novel", + target: "_blank", + }, + }, + ], + text: "Novel", + }, + { + type: "text", + text: " is a Notion-style WYSIWYG editor with AI-powered autocompletion. Built with ", + }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://tiptap.dev/", + target: "_blank", + }, + }, + ], + text: "Tiptap", + }, + { type: "text", text: " + " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://sdk.vercel.ai/docs", + target: "_blank", + }, + }, + ], + text: "Vercel AI SDK", + }, + { type: "text", text: "." }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Installation" }], + }, + { + type: "codeBlock", + attrs: { language: null }, + content: [{ type: "text", text: "npm i novel" }], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Usage" }], + }, + { + type: "codeBlock", + attrs: { language: null }, + content: [ + { + type: "text", + text: 'import { Editor } from "novel";\n\nexport default function App() {\n return (\n <Editor />\n )\n}', + }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Features" }], + }, + { + type: "orderedList", + attrs: { tight: true, start: 1 }, + content: [ + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [{ type: "text", text: "Slash menu & bubble menu" }], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "AI autocomplete (type " }, + { type: "text", marks: [{ type: "code" }], text: "++" }, + { + type: "text", + text: " to activate, or select from slash menu)", + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Image uploads (drag & drop / copy & paste, or select from slash menu) ", + }, + ], + }, + ], + }, + ], + }, + { + type: "image", + attrs: { + src: "https://public.blob.vercel-storage.com/pJrjXbdONOnAeZAZ/banner-2wQk82qTwyVgvlhTW21GIkWgqPGD2C.png", + alt: "banner.png", + title: "banner.png", + width: null, + height: null, + }, + }, + { type: "horizontalRule" }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Learn more" }], + }, + { + type: "taskList", + content: [ + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "Star us on " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://github.com/steven-tey/novel", + target: "_blank", + }, + }, + ], + text: "GitHub", + }, + ], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "Install the " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://www.npmjs.com/package/novel", + target: "_blank", + }, + }, + ], + text: "NPM package", + }, + ], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://vercel.com/templates/next.js/novel", + target: "_blank", + }, + }, + ], + text: "Deploy your own", + }, + { type: "text", text: " to Vercel" }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/apps/web/app/(editor)/lib/debouncedsave.ts b/apps/web/app/(editor)/lib/debouncedsave.ts new file mode 100644 index 00000000..6490c6c4 --- /dev/null +++ b/apps/web/app/(editor)/lib/debouncedsave.ts @@ -0,0 +1,20 @@ +import hljs from 'highlight.js' +import { debounce } from 'tldraw'; +import { useDebouncedCallback } from "use-debounce"; + +export const Updates = debounce(({editor, setCharsCount, setSaveStatus})=> { + const json = editor.getJSON(); + setCharsCount(editor.storage.characterCount.words()); + window.localStorage.setItem("html-content", highlightCodeblocks(editor.getHTML())); + window.localStorage.setItem("novel-content", JSON.stringify(json)); + window.localStorage.setItem("markdown", editor.storage.markdown.getMarkdown()); + setSaveStatus("Saved"); +}, 500) + +export const highlightCodeblocks = (content: string) => { + const doc = new DOMParser().parseFromString(content, 'text/html'); + doc.querySelectorAll('pre code').forEach((el) => { + hljs.highlightElement(el); + }); + return new XMLSerializer().serializeToString(doc); +};
\ No newline at end of file diff --git a/apps/web/app/(editor)/lib/editorprops.ts b/apps/web/app/(editor)/lib/editorprops.ts new file mode 100644 index 00000000..00d89264 --- /dev/null +++ b/apps/web/app/(editor)/lib/editorprops.ts @@ -0,0 +1,16 @@ +import { handleCommandNavigation } from "novel/extensions"; +import { handleImageDrop, handleImagePaste } from "novel/plugins"; +import { uploadFn } from "../components/image-upload"; +import { EditorView } from "prosemirror-view"; + +export const editorProps = { + handleDOMEvents: { + keydown: (_view: EditorView, event: KeyboardEvent) => handleCommandNavigation(event), + }, + handlePaste: (view: EditorView, event: ClipboardEvent) => handleImagePaste(view, event, uploadFn), + handleDrop: (view: EditorView, event: DragEvent, slice, moved:boolean) => handleImageDrop(view, event, moved, uploadFn), + attributes: { + class: + "prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full", + }, +}
\ No newline at end of file diff --git a/apps/web/app/(editor)/lib/use-local-storage.ts b/apps/web/app/(editor)/lib/use-local-storage.ts new file mode 100644 index 00000000..5f2ebeb9 --- /dev/null +++ b/apps/web/app/(editor)/lib/use-local-storage.ts @@ -0,0 +1,27 @@ +import { useEffect, useState } from "react"; + +const useLocalStorage = <T>( + key: string, + initialValue: T, + // eslint-disable-next-line no-unused-vars +): [T, (value: T) => void] => { + const [storedValue, setStoredValue] = useState(initialValue); + + useEffect(() => { + // Retrieve from localStorage + const item = window.localStorage.getItem(key); + if (item) { + setStoredValue(JSON.parse(item)); + } + }, [key]); + + const setValue = (value: T) => { + // Save state + setStoredValue(value); + // Save to localStorage + window.localStorage.setItem(key, JSON.stringify(value)); + }; + return [storedValue, setValue]; +}; + +export default useLocalStorage; |