Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add theme selector to advanced editor component #457

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions apps/web/components/tailwind/advanced-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ColorSelector } from "./selectors/color-selector";
import { LinkSelector } from "./selectors/link-selector";
import { NodeSelector } from "./selectors/node-selector";
import { MathSelector } from "./selectors/math-selector";
import { ThemeSelector } from "./selectors/theme-selector";
import { Separator } from "./ui/separator";

import { handleImageDrop, handleImagePaste } from "novel/plugins";
Expand All @@ -26,7 +27,7 @@ import { uploadFn } from "./image-upload";
import { TextButtons } from "./selectors/text-buttons";
import { slashCommand, suggestionItems } from "./slash-command";

const hljs = require('highlight.js');
import hljs from 'highlight.js'; // Ensure Highlight.js is imported

const extensions = [...defaultExtensions, slashCommand];

Expand All @@ -38,23 +39,12 @@ const TailwindAdvancedEditor = () => {
const [openNode, setOpenNode] = useState(false);
const [openColor, setOpenColor] = useState(false);
const [openLink, setOpenLink] = useState(false);
const [openTheme, setOpenTheme] = useState(false);
const [openAI, setOpenAI] = useState(false);

//Apply Codeblock Highlighting on the HTML from editor.getHTML()
const highlightCodeblocks = (content: string) => {
const doc = new DOMParser().parseFromString(content, 'text/html');
doc.querySelectorAll('pre code').forEach((el) => {
// @ts-ignore
// https://highlightjs.readthedocs.io/en/latest/api.html?highlight=highlightElement#highlightelement
hljs.highlightElement(el);
});
return new XMLSerializer().serializeToString(doc);
};

const debouncedUpdates = useDebouncedCallback(async (editor: EditorInstance) => {
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");
Expand All @@ -66,6 +56,24 @@ const TailwindAdvancedEditor = () => {
else setInitialContent(defaultEditorContent);
}, []);

useEffect(() => {
// Load the default theme when the component mounts
loadTheme('default');
}, []);

const loadTheme = (theme: string) => {
const existingStyle = document.getElementById('highlight-js-style');
if (existingStyle) {
existingStyle.remove();
}

const style = document.createElement('link');
style.id = 'highlight-js-style';
style.rel = 'stylesheet';
style.href = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/${theme}.min.css`;
document.head.appendChild(style);
};

if (!initialContent) return null;

return (
Expand Down Expand Up @@ -124,14 +132,15 @@ const TailwindAdvancedEditor = () => {
<Separator orientation="vertical" />
<NodeSelector open={openNode} onOpenChange={setOpenNode} />
<Separator orientation="vertical" />

<LinkSelector open={openLink} onOpenChange={setOpenLink} />
<Separator orientation="vertical" />
<MathSelector />
<Separator orientation="vertical" />
<TextButtons />
<Separator orientation="vertical" />
<ColorSelector open={openColor} onOpenChange={setOpenColor} />
<Separator orientation="vertical" />
<ThemeSelector open={openTheme} onOpenChange={setOpenTheme} />
</GenerativeMenuSwitch>
</EditorContent>
</EditorRoot>
Expand Down
79 changes: 79 additions & 0 deletions apps/web/components/tailwind/selectors/theme-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Check, ChevronDown } from "lucide-react";
import { EditorBubbleItem, useEditor } from "novel";
import { Button } from "@/components/tailwind/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/tailwind/ui/popover";
import hljs from "highlight.js";

export interface BubbleColorMenuItem {
name: string;
}

const THEMES: BubbleColorMenuItem[] = [
{ name: "default" },
{ name: "github-dark" },
{ name: "github" },
{ name: "monokai" },
{ name: "docco" },
];

interface ThemeSelectorProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}

export const ThemeSelector = ({ open, onOpenChange }: ThemeSelectorProps) => {
const { editor } = useEditor();

if (!editor) return null;

const loadTheme = (theme: string) => {
const existingStyle = document.getElementById('highlight-js-style');
if (existingStyle) {
existingStyle.remove();
}

const style = document.createElement('link');
style.id = 'highlight-js-style';
style.rel = 'stylesheet';
style.href = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/${theme}.min.css`;
document.head.appendChild(style);

// Re-highlight all code blocks in the editor
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach((block) => {
block.classList.add('hljs');
hljs.highlightElement(block as HTMLElement); // Re-highlight the code block
});
};

return (
<Popover modal={true} open={open} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>
<Button size="sm" className="gap-2 rounded-none" variant="ghost">
<span>Theme</span>
<ChevronDown className="h-4 w-4" />
</Button>
</PopoverTrigger>

<PopoverContent
sideOffset={5}
className="my-1 flex max-h-80 w-48 flex-col overflow-hidden overflow-y-auto rounded border p-1 shadow-xl"
align="start"
>
{THEMES.map(({ name }) => (
<EditorBubbleItem
key={name}
onSelect={() => {
loadTheme(name);
onOpenChange(false);
}}
className="flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-accent"
>
<span>{name}</span>
{editor.isActive("theme", { name }) && <Check className="h-4 w-4" />}
</EditorBubbleItem>
))}
</PopoverContent>
</Popover>
);
};
71 changes: 2 additions & 69 deletions apps/web/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,75 +90,8 @@
* {
@apply border-border;
}

body {
@apply bg-background text-foreground;
}
}


pre {
background: #0d0d0d;
border-radius: 0.5rem;
color: #fff;
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;

code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}

.hljs-comment,
.hljs-quote {
color: #616161;
}

.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #f98181;
}

.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #fbbc88;
}

.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #b9f18d;
}

.hljs-title,
.hljs-section {
color: #faf594;
}

.hljs-keyword,
.hljs-selector-tag {
color: #70cff8;
}

.hljs-emphasis {
font-style: italic;
}

.hljs-strong {
font-weight: 700;
}
}
}