diff --git a/src/components/blog/PostForm.tsx b/src/components/blog/PostForm.tsx index a56aa35..7418da9 100644 --- a/src/components/blog/PostForm.tsx +++ b/src/components/blog/PostForm.tsx @@ -79,8 +79,8 @@ export default function PostForm(props: PostFormProps) { await api.database.updatePost.mutate({ id: props.postId!, title: titleVal.replaceAll(" ", "_"), - subtitle: subtitle() || null, - body: body() || null, + subtitle: subtitle() || "", + body: body() || "", banner_photo: bannerImageKey !== "" ? bannerImageKey diff --git a/src/components/blog/TagMaker.tsx b/src/components/blog/TagMaker.tsx index 829d20e..6ff0072 100644 --- a/src/components/blog/TagMaker.tsx +++ b/src/components/blog/TagMaker.tsx @@ -13,11 +13,11 @@ export default function TagMaker(props: TagMakerProps) { return (
-
-
+
+
-
+
start with # end with a space
diff --git a/src/components/blog/TextEditor.tsx b/src/components/blog/TextEditor.tsx index 3a89917..3e168ba 100644 --- a/src/components/blog/TextEditor.tsx +++ b/src/components/blog/TextEditor.tsx @@ -1,4 +1,4 @@ -import { Show, untrack, createEffect, on, createSignal } from "solid-js"; +import { Show, untrack, createEffect, on, createSignal, For } from "solid-js"; import { createTiptapEditor, useEditorHTML } from "solid-tiptap"; import StarterKit from "@tiptap/starter-kit"; import Link from "@tiptap/extension-link"; @@ -11,17 +11,95 @@ import js from "highlight.js/lib/languages/javascript"; import ts from "highlight.js/lib/languages/typescript"; import ocaml from "highlight.js/lib/languages/ocaml"; import rust from "highlight.js/lib/languages/rust"; +import python from "highlight.js/lib/languages/python"; +import java from "highlight.js/lib/languages/java"; +import go from "highlight.js/lib/languages/go"; +import c from "highlight.js/lib/languages/c"; +import cpp from "highlight.js/lib/languages/cpp"; +import csharp from "highlight.js/lib/languages/csharp"; +import sql from "highlight.js/lib/languages/sql"; +import bash from "highlight.js/lib/languages/bash"; +import json from "highlight.js/lib/languages/json"; +import yaml from "highlight.js/lib/languages/yaml"; +import markdown from "highlight.js/lib/languages/markdown"; +import xml from "highlight.js/lib/languages/xml"; +import php from "highlight.js/lib/languages/php"; +import ruby from "highlight.js/lib/languages/ruby"; +import swift from "highlight.js/lib/languages/swift"; +import kotlin from "highlight.js/lib/languages/kotlin"; +import dockerfile from "highlight.js/lib/languages/dockerfile"; // Create lowlight instance with common languages const lowlight = createLowlight(common); -// Register additional languages +// Register existing languages lowlight.register("css", css); lowlight.register("js", js); +lowlight.register("javascript", js); lowlight.register("ts", ts); +lowlight.register("typescript", ts); lowlight.register("ocaml", ocaml); lowlight.register("rust", rust); +// Register new languages +lowlight.register("python", python); +lowlight.register("py", python); +lowlight.register("java", java); +lowlight.register("go", go); +lowlight.register("golang", go); +lowlight.register("c", c); +lowlight.register("cpp", cpp); +lowlight.register("c++", cpp); +lowlight.register("csharp", csharp); +lowlight.register("cs", csharp); +lowlight.register("sql", sql); +lowlight.register("bash", bash); +lowlight.register("shell", bash); +lowlight.register("sh", bash); +lowlight.register("json", json); +lowlight.register("yaml", yaml); +lowlight.register("yml", yaml); +lowlight.register("markdown", markdown); +lowlight.register("md", markdown); +lowlight.register("xml", xml); +lowlight.register("html", xml); +lowlight.register("php", php); +lowlight.register("ruby", ruby); +lowlight.register("rb", ruby); +lowlight.register("swift", swift); +lowlight.register("kotlin", kotlin); +lowlight.register("kt", kotlin); +lowlight.register("dockerfile", dockerfile); +lowlight.register("docker", dockerfile); + +// Available languages for selector +const AVAILABLE_LANGUAGES = [ + { value: null, label: "Plain Text" }, + { value: "bash", label: "Bash/Shell" }, + { value: "c", label: "C" }, + { value: "cpp", label: "C++" }, + { value: "csharp", label: "C#" }, + { value: "css", label: "CSS" }, + { value: "dockerfile", label: "Dockerfile" }, + { value: "go", label: "Go" }, + { value: "html", label: "HTML" }, + { value: "java", label: "Java" }, + { value: "javascript", label: "JavaScript" }, + { value: "json", label: "JSON" }, + { value: "kotlin", label: "Kotlin" }, + { value: "markdown", label: "Markdown" }, + { value: "ocaml", label: "OCaml" }, + { value: "php", label: "PHP" }, + { value: "python", label: "Python" }, + { value: "ruby", label: "Ruby" }, + { value: "rust", label: "Rust" }, + { value: "sql", label: "SQL" }, + { value: "swift", label: "Swift" }, + { value: "typescript", label: "TypeScript" }, + { value: "xml", label: "XML" }, + { value: "yaml", label: "YAML" } +] as const; + // IFrame extension interface IframeOptions { allowFullscreen: boolean; @@ -112,6 +190,12 @@ export default function TextEditor(props: TextEditorProps) { left: 0 }); + const [showLanguageSelector, setShowLanguageSelector] = createSignal(false); + const [languageSelectorPosition, setLanguageSelectorPosition] = createSignal({ + top: 0, + left: 0 + }); + const editor = createTiptapEditor(() => ({ element: editorRef, extensions: [ @@ -212,6 +296,50 @@ export default function TextEditor(props: TextEditorProps) { } }; + const insertCodeBlock = (language: string | null) => { + const instance = editor(); + if (!instance) return; + + instance.chain().focus().toggleCodeBlock().run(); + + // If language specified, update the node attributes + if (language) { + instance.chain().updateAttributes("codeBlock", { language }).run(); + } + + setShowLanguageSelector(false); + }; + + const showLanguagePicker = (e: MouseEvent) => { + const buttonRect = (e.currentTarget as HTMLElement).getBoundingClientRect(); + setLanguageSelectorPosition({ + top: buttonRect.bottom + 5, + left: buttonRect.left + }); + setShowLanguageSelector(!showLanguageSelector()); + }; + + // Close language selector on outside click + createEffect(() => { + if (showLanguageSelector()) { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement; + if ( + !target.closest(".language-selector") && + !target.closest("[data-language-picker-trigger]") + ) { + setShowLanguageSelector(false); + } + }; + + setTimeout(() => { + document.addEventListener("click", handleClickOutside); + }, 0); + + return () => document.removeEventListener("click", handleClickOutside); + } + }); + return (
@@ -348,6 +476,29 @@ export default function TextEditor(props: TextEditorProps) {
+ {/* Language Selector Dropdown */} + +
+ + {(lang) => ( + + )} + +
+
+