fix saving, expand code select
This commit is contained in:
@@ -79,8 +79,8 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
await api.database.updatePost.mutate({
|
await api.database.updatePost.mutate({
|
||||||
id: props.postId!,
|
id: props.postId!,
|
||||||
title: titleVal.replaceAll(" ", "_"),
|
title: titleVal.replaceAll(" ", "_"),
|
||||||
subtitle: subtitle() || null,
|
subtitle: subtitle() || "",
|
||||||
body: body() || null,
|
body: body() || "",
|
||||||
banner_photo:
|
banner_photo:
|
||||||
bannerImageKey !== ""
|
bannerImageKey !== ""
|
||||||
? bannerImageKey
|
? bannerImageKey
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ export default function TagMaker(props: TagMakerProps) {
|
|||||||
return (
|
return (
|
||||||
<div class="flex w-full flex-col justify-center md:flex-row md:justify-between">
|
<div class="flex w-full flex-col justify-center md:flex-row md:justify-between">
|
||||||
<div class="absolute -mt-12 mb-8 flex w-full justify-center md:mt-0 md:mb-0 md:w-full md:justify-normal">
|
<div class="absolute -mt-12 mb-8 flex w-full justify-center md:mt-0 md:mb-0 md:w-full md:justify-normal">
|
||||||
<div class="tooltip md:-ml-8">
|
<div class="tooltip">
|
||||||
<div class="md:mt-12">
|
<div class="md:mt-2">
|
||||||
<InfoIcon height={24} width={24} strokeWidth={1} />
|
<InfoIcon height={24} width={24} strokeWidth={1} />
|
||||||
</div>
|
</div>
|
||||||
<div class="tooltip-text -ml-20 w-40">
|
<div class="tooltip-text -ml-4 w-40">
|
||||||
<div class="px-1">start with # end with a space</div>
|
<div class="px-1">start with # end with a space</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 { createTiptapEditor, useEditorHTML } from "solid-tiptap";
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import Link from "@tiptap/extension-link";
|
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 ts from "highlight.js/lib/languages/typescript";
|
||||||
import ocaml from "highlight.js/lib/languages/ocaml";
|
import ocaml from "highlight.js/lib/languages/ocaml";
|
||||||
import rust from "highlight.js/lib/languages/rust";
|
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
|
// Create lowlight instance with common languages
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
|
|
||||||
// Register additional languages
|
// Register existing languages
|
||||||
lowlight.register("css", css);
|
lowlight.register("css", css);
|
||||||
lowlight.register("js", js);
|
lowlight.register("js", js);
|
||||||
|
lowlight.register("javascript", js);
|
||||||
lowlight.register("ts", ts);
|
lowlight.register("ts", ts);
|
||||||
|
lowlight.register("typescript", ts);
|
||||||
lowlight.register("ocaml", ocaml);
|
lowlight.register("ocaml", ocaml);
|
||||||
lowlight.register("rust", rust);
|
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
|
// IFrame extension
|
||||||
interface IframeOptions {
|
interface IframeOptions {
|
||||||
allowFullscreen: boolean;
|
allowFullscreen: boolean;
|
||||||
@@ -112,6 +190,12 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
left: 0
|
left: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [showLanguageSelector, setShowLanguageSelector] = createSignal(false);
|
||||||
|
const [languageSelectorPosition, setLanguageSelectorPosition] = createSignal({
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
});
|
||||||
|
|
||||||
const editor = createTiptapEditor(() => ({
|
const editor = createTiptapEditor(() => ({
|
||||||
element: editorRef,
|
element: editorRef,
|
||||||
extensions: [
|
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 (
|
return (
|
||||||
<div class="border-surface2 text-text w-full rounded-md border px-4 py-2">
|
<div class="border-surface2 text-text w-full rounded-md border px-4 py-2">
|
||||||
<Show when={editor()}>
|
<Show when={editor()}>
|
||||||
@@ -348,6 +476,29 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* Language Selector Dropdown */}
|
||||||
|
<Show when={showLanguageSelector()}>
|
||||||
|
<div
|
||||||
|
class="language-selector bg-mantle text-text border-surface2 fixed z-50 max-h-64 w-48 overflow-y-auto rounded border shadow-lg"
|
||||||
|
style={{
|
||||||
|
top: `${languageSelectorPosition().top}px`,
|
||||||
|
left: `${languageSelectorPosition().left}px`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={AVAILABLE_LANGUAGES}>
|
||||||
|
{(lang) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => insertCodeBlock(lang.value)}
|
||||||
|
class="hover:bg-surface1 w-full px-3 py-2 text-left text-sm transition-colors"
|
||||||
|
>
|
||||||
|
{lang.label}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
<div class="border-surface2 mb-2 flex flex-wrap gap-1 border-b pb-2">
|
<div class="border-surface2 mb-2 flex flex-wrap gap-1 border-b pb-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -474,9 +625,8 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
<div class="border-surface2 mx-1 border-l"></div>
|
<div class="border-surface2 mx-1 border-l"></div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={showLanguagePicker}
|
||||||
instance().chain().focus().toggleCodeBlock().run()
|
data-language-picker-trigger
|
||||||
}
|
|
||||||
class={`${
|
class={`${
|
||||||
instance().isActive("codeBlock")
|
instance().isActive("codeBlock")
|
||||||
? "bg-surface2"
|
? "bg-surface2"
|
||||||
|
|||||||
Reference in New Issue
Block a user