references and spinner fixes
This commit is contained in:
23
src/app.css
23
src/app.css
@@ -348,26 +348,6 @@ label.underlinedInputLabel {
|
||||
color: var(--color-surface1);
|
||||
}
|
||||
|
||||
.logoSpinner:hover {
|
||||
animation: spinner 1.5s ease;
|
||||
}
|
||||
@keyframes spinner {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
@keyframes spinReverse {
|
||||
to {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
.animate-spin-reverse {
|
||||
animation: spinReverse 1s linear infinite;
|
||||
}
|
||||
|
||||
.vertical-rule-around {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1221,3 +1201,6 @@ svg.mermaid text {
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
}
|
||||
.reference-item > span.ml-2 {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
export default function LoadingSpinner(props: {
|
||||
height: number;
|
||||
width: number;
|
||||
}) {
|
||||
return (
|
||||
<picture class="animate-spin-reverse flex w-full justify-center">
|
||||
<source srcset="/WhiteLogo.png" media="(prefers-color-scheme: dark)" />
|
||||
<img
|
||||
src="/BlackLogo.png"
|
||||
alt="logo"
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
/>
|
||||
</picture>
|
||||
<div class="flex w-full justify-center">
|
||||
<Spinner size={props.height} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,68 +1,42 @@
|
||||
import { onMount, onCleanup, createSignal, JSX } from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
import { JSX } from "solid-js";
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
interface SkeletonProps {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
function useSpinner() {
|
||||
const [showing, setShowing] = createSignal(0);
|
||||
|
||||
onMount(() => {
|
||||
if (isServer) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setShowing((prev) => (prev + 1) % spinnerChars.length);
|
||||
}, 50);
|
||||
|
||||
onCleanup(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
});
|
||||
|
||||
return () => spinnerChars[showing()];
|
||||
}
|
||||
|
||||
export function SkeletonBox(props: SkeletonProps) {
|
||||
const spinner = useSpinner();
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`bg-surface0 text-overlay0 flex items-center justify-center rounded font-mono ${props.class || ""}`}
|
||||
class={`bg-surface0 text-overlay0 flex items-center justify-center rounded ${props.class || ""}`}
|
||||
aria-label="Loading..."
|
||||
role="status"
|
||||
>
|
||||
{spinner()}
|
||||
<Spinner size="md" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonText(props: SkeletonProps) {
|
||||
const spinner = useSpinner();
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`bg-surface0 text-overlay0 inline-flex h-4 items-center rounded px-2 font-mono text-sm ${props.class || ""}`}
|
||||
class={`bg-surface0 text-overlay0 inline-flex h-4 items-center rounded px-2 ${props.class || ""}`}
|
||||
aria-label="Loading..."
|
||||
role="status"
|
||||
>
|
||||
{spinner()}
|
||||
<Spinner size="sm" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonCircle(props: SkeletonProps) {
|
||||
const spinner = useSpinner();
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`bg-surface0 text-overlay0 flex items-center justify-center rounded-full font-mono ${props.class || ""}`}
|
||||
class={`bg-surface0 text-overlay0 flex items-center justify-center rounded-full ${props.class || ""}`}
|
||||
aria-label="Loading..."
|
||||
role="status"
|
||||
>
|
||||
{spinner()}
|
||||
<Spinner size="md" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
58
src/components/Spinner.tsx
Normal file
58
src/components/Spinner.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { onMount, onCleanup, createSignal, JSX } from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
|
||||
export interface SpinnerProps {
|
||||
size?: "sm" | "md" | "lg" | "xl" | number;
|
||||
class?: string;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
const sizeMap = {
|
||||
sm: "text-base",
|
||||
md: "text-2xl",
|
||||
lg: "text-4xl",
|
||||
xl: "text-6xl"
|
||||
};
|
||||
|
||||
export function Spinner(props: SpinnerProps) {
|
||||
const [showing, setShowing] = createSignal(0);
|
||||
|
||||
onMount(() => {
|
||||
if (isServer) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setShowing((prev) => (prev + 1) % spinnerChars.length);
|
||||
}, 50);
|
||||
|
||||
onCleanup(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
});
|
||||
|
||||
const sizeClass = () => {
|
||||
if (typeof props.size === "number") {
|
||||
return "";
|
||||
}
|
||||
return sizeMap[props.size || "md"];
|
||||
};
|
||||
|
||||
const style = () => {
|
||||
if (typeof props.size === "number") {
|
||||
return { "font-size": `${props.size}px`, "line-height": "1" };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
class={`font-mono ${sizeClass()} ${props.class || ""}`}
|
||||
style={style()}
|
||||
aria-label={props["aria-label"] || "Loading..."}
|
||||
role="status"
|
||||
>
|
||||
{spinnerChars[showing()]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +1,11 @@
|
||||
import { onMount, onCleanup, createSignal } from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
||||
const spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
export function TerminalSplash() {
|
||||
const [showing, setShowing] = createSignal(0);
|
||||
|
||||
onMount(() => {
|
||||
// Only run animation on client
|
||||
if (isServer) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setShowing((prev) => (prev + 1) % spinnerChars.length);
|
||||
}, 50);
|
||||
|
||||
onCleanup(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="bg-base flex min-h-screen w-full flex-col items-center justify-center overflow-hidden">
|
||||
<div class="text-text max-w-3xl p-8 font-mono text-4xl whitespace-pre-wrap">
|
||||
<div class="text-text max-w-3xl p-8">
|
||||
<div class="flex items-center justify-center">
|
||||
{spinnerChars[showing()]}
|
||||
<Spinner size="xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createEffect } from "solid-js";
|
||||
import { createSignal } from "solid-js";
|
||||
import { createEffect, createSignal, onMount } from "solid-js";
|
||||
import type { HLJSApi } from "highlight.js";
|
||||
import MermaidRenderer from "./MermaidRenderer";
|
||||
|
||||
@@ -98,6 +97,161 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
let contentRef: HTMLDivElement | undefined;
|
||||
const [hljs, setHljs] = createSignal<HLJSApi | null>(null);
|
||||
|
||||
// Process superscript references and enhance the References section
|
||||
const processReferences = () => {
|
||||
if (!contentRef) return;
|
||||
|
||||
const foundRefs = new Map<string, HTMLElement>();
|
||||
|
||||
// Find all <sup> elements with [n] pattern
|
||||
const supElements = contentRef.querySelectorAll("sup");
|
||||
|
||||
supElements.forEach((sup) => {
|
||||
const text = sup.textContent?.trim() || "";
|
||||
// Match patterns like [1], [2], [a], [*], etc.
|
||||
const match = text.match(/^\[(.+?)\]$/);
|
||||
|
||||
if (match) {
|
||||
const refNumber = match[1];
|
||||
const refId = `ref-${refNumber}`;
|
||||
const refBackId = `ref-${refNumber}-back`;
|
||||
|
||||
// Add ID to the sup element itself for back navigation
|
||||
sup.id = refBackId;
|
||||
|
||||
// Replace sup content with a clickable link
|
||||
sup.innerHTML = "";
|
||||
const link = document.createElement("a");
|
||||
link.href = `#${refId}`;
|
||||
link.textContent = `[${refNumber}]`;
|
||||
link.className =
|
||||
"reference-link text-blue hover:text-sky no-underline cursor-pointer";
|
||||
link.style.cssText =
|
||||
"text-decoration: none; font-size: 0.75em; vertical-align: super;";
|
||||
|
||||
// Add smooth scroll behavior
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const target = document.getElementById(refId);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
// Highlight the reference briefly
|
||||
target.style.backgroundColor = "rgba(137, 180, 250, 0.2)";
|
||||
setTimeout(() => {
|
||||
target.style.backgroundColor = "";
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
sup.appendChild(link);
|
||||
}
|
||||
});
|
||||
|
||||
// Find and enhance the References section
|
||||
const headings = contentRef.querySelectorAll("h2");
|
||||
let referencesSection: HTMLElement | null = null;
|
||||
|
||||
headings.forEach((heading) => {
|
||||
if (heading.textContent?.trim() === "References") {
|
||||
referencesSection = heading;
|
||||
}
|
||||
});
|
||||
|
||||
if (referencesSection) {
|
||||
// Style the References heading
|
||||
referencesSection.className = "text-2xl font-bold mb-4 text-text";
|
||||
|
||||
// Find the parent container and add styling
|
||||
const parentDiv = referencesSection.parentElement;
|
||||
if (parentDiv) {
|
||||
// Add top border and padding
|
||||
parentDiv.style.cssText =
|
||||
"border-top: 1px solid var(--surface2); margin-top: 4rem; padding-top: 2rem;";
|
||||
}
|
||||
|
||||
// Find all paragraphs after the References heading that start with [n]
|
||||
let currentElement = referencesSection.nextElementSibling;
|
||||
|
||||
while (currentElement) {
|
||||
if (currentElement.tagName === "P") {
|
||||
const text = currentElement.textContent?.trim() || "";
|
||||
const match = text.match(/^\[(.+?)\]\s*/);
|
||||
|
||||
if (match) {
|
||||
const refNumber = match[1];
|
||||
const refId = `ref-${refNumber}`;
|
||||
|
||||
// Set the ID for linking
|
||||
currentElement.id = refId;
|
||||
|
||||
// Add styling
|
||||
currentElement.className =
|
||||
"reference-item transition-colors duration-500 text-sm mb-3";
|
||||
currentElement.style.cssText = "scroll-margin-top: 100px;";
|
||||
|
||||
// Parse and style the content - get everything after [n]
|
||||
let refText = text.substring(match[0].length);
|
||||
|
||||
// Remove any existing "↑ Back" text (including various Unicode arrow variants)
|
||||
refText = refText.replace(/[↑⬆️]\s*Back\s*$/i, "").trim();
|
||||
|
||||
// Create styled content
|
||||
currentElement.innerHTML = "";
|
||||
|
||||
// Add bold reference number
|
||||
const refNumSpan = document.createElement("span");
|
||||
refNumSpan.className = "text-blue font-semibold";
|
||||
refNumSpan.textContent = `[${refNumber}]`;
|
||||
currentElement.appendChild(refNumSpan);
|
||||
|
||||
// Add reference text
|
||||
if (refText) {
|
||||
const refTextSpan = document.createElement("span");
|
||||
refTextSpan.className = "ml-2";
|
||||
refTextSpan.textContent = refText;
|
||||
currentElement.appendChild(refTextSpan);
|
||||
} else {
|
||||
const refTextSpan = document.createElement("span");
|
||||
refTextSpan.className = "ml-2 text-subtext0 italic";
|
||||
refTextSpan.textContent = "Add your reference text here";
|
||||
currentElement.appendChild(refTextSpan);
|
||||
}
|
||||
|
||||
// Add back button
|
||||
const backLink = document.createElement("a");
|
||||
backLink.href = `#ref-${refNumber}-back`;
|
||||
backLink.className =
|
||||
"text-mauve hover:text-pink ml-2 text-xs cursor-pointer";
|
||||
backLink.textContent = "↑ Back";
|
||||
backLink.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const target = document.getElementById(`ref-${refNumber}-back`);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
// Highlight the reference link briefly
|
||||
target.style.backgroundColor = "rgba(203, 166, 247, 0.2)";
|
||||
setTimeout(() => {
|
||||
target.style.backgroundColor = "";
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
currentElement.appendChild(backLink);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we've reached another heading (end of references)
|
||||
if (
|
||||
currentElement.tagName.match(/^H[1-6]$/) &&
|
||||
currentElement !== referencesSection
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentElement = currentElement.nextElementSibling;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Load highlight.js only when needed
|
||||
createEffect(() => {
|
||||
if (props.hasCodeBlock && !hljs()) {
|
||||
@@ -115,6 +269,22 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
}
|
||||
});
|
||||
|
||||
// Process references after content is mounted and when body changes
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
processReferences();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
// Re-process when body changes
|
||||
if (props.body && contentRef) {
|
||||
setTimeout(() => {
|
||||
processReferences();
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="mx-auto max-w-4xl px-4 pt-32 md:pt-40">
|
||||
<div
|
||||
|
||||
@@ -17,6 +17,8 @@ import { Node } from "@tiptap/core";
|
||||
import { createLowlight, common } from "lowlight";
|
||||
import { Mermaid } from "./extensions/Mermaid";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import Superscript from "@tiptap/extension-superscript";
|
||||
import Subscript from "@tiptap/extension-subscript";
|
||||
import css from "highlight.js/lib/languages/css";
|
||||
import js from "highlight.js/lib/languages/javascript";
|
||||
import ts from "highlight.js/lib/languages/typescript";
|
||||
@@ -196,7 +198,9 @@ const KEYBOARD_SHORTCUTS: ShortcutCategory[] = [
|
||||
{ keys: "⌘ B", keysAlt: "Ctrl B", description: "Bold" },
|
||||
{ keys: "⌘ I", keysAlt: "Ctrl I", description: "Italic" },
|
||||
{ keys: "⌘ ⇧ X", keysAlt: "Ctrl Shift X", description: "Strikethrough" },
|
||||
{ keys: "⌘ E", keysAlt: "Ctrl E", description: "Inline Code" }
|
||||
{ keys: "⌘ E", keysAlt: "Ctrl E", description: "Inline Code" },
|
||||
{ keys: "⌘ .", keysAlt: "Ctrl .", description: "Superscript" },
|
||||
{ keys: "⌘ ,", keysAlt: "Ctrl ,", description: "Subscript" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -432,7 +436,9 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
types: ["heading", "paragraph"],
|
||||
alignments: ["left", "center", "right", "justify"],
|
||||
defaultAlignment: "left"
|
||||
})
|
||||
}),
|
||||
Superscript,
|
||||
Subscript
|
||||
],
|
||||
content: props.preSet || `<p><em><b>Hello!</b> World</em></p>`,
|
||||
editorProps: {
|
||||
@@ -474,6 +480,8 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
onUpdate: ({ editor }) => {
|
||||
untrack(() => {
|
||||
props.updateContent(editor.getHTML());
|
||||
// Auto-manage references section
|
||||
setTimeout(() => updateReferencesSection(editor), 100);
|
||||
});
|
||||
},
|
||||
onSelectionUpdate: ({ editor }) => {
|
||||
@@ -505,13 +513,179 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
(newContent) => {
|
||||
const instance = editor();
|
||||
if (instance && newContent && instance.getHTML() !== newContent) {
|
||||
instance.commands.setContent(newContent, false); // false = don't emit update event
|
||||
instance.commands.setContent(newContent, { emitUpdate: false });
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
);
|
||||
|
||||
// Auto-manage references section
|
||||
const updateReferencesSection = (editorInstance: any) => {
|
||||
if (!editorInstance) return;
|
||||
|
||||
const doc = editorInstance.state.doc;
|
||||
const foundRefs = new Set<string>();
|
||||
|
||||
// Scan document for superscript marks containing [n] patterns
|
||||
doc.descendants((node: any) => {
|
||||
if (node.isText && node.marks) {
|
||||
const hasSuperscript = node.marks.some(
|
||||
(mark: any) => mark.type.name === "superscript"
|
||||
);
|
||||
if (hasSuperscript) {
|
||||
const text = node.text || "";
|
||||
const match = text.match(/^\[(.+?)\]$/);
|
||||
if (match) {
|
||||
foundRefs.add(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no references found, remove references section if it exists
|
||||
if (foundRefs.size === 0) {
|
||||
let hasReferencesSection = false;
|
||||
let hrPos = -1;
|
||||
let sectionStartPos = -1;
|
||||
|
||||
doc.descendants((node: any, pos: number) => {
|
||||
if (node.type.name === "heading" && node.textContent === "References") {
|
||||
hasReferencesSection = true;
|
||||
sectionStartPos = pos;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasReferencesSection && sectionStartPos > 0) {
|
||||
// Find the HR before References heading
|
||||
doc.nodesBetween(
|
||||
Math.max(0, sectionStartPos - 50),
|
||||
sectionStartPos,
|
||||
(node: any, pos: number) => {
|
||||
if (node.type.name === "horizontalRule") {
|
||||
hrPos = pos;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Delete from HR to end of document
|
||||
if (hrPos >= 0) {
|
||||
const tr = editorInstance.state.tr;
|
||||
tr.delete(hrPos, doc.content.size);
|
||||
editorInstance.view.dispatch(tr);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert Set to sorted array
|
||||
const refNumbers = Array.from(foundRefs).sort((a, b) => {
|
||||
const numA = parseInt(a);
|
||||
const numB = parseInt(b);
|
||||
if (!isNaN(numA) && !isNaN(numB)) {
|
||||
return numA - numB;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
// Check if References section already exists
|
||||
let referencesHeadingPos = -1;
|
||||
let existingRefs = new Set<string>();
|
||||
|
||||
doc.descendants((node: any, pos: number) => {
|
||||
if (node.type.name === "heading" && node.textContent === "References") {
|
||||
referencesHeadingPos = pos;
|
||||
}
|
||||
// Check for existing reference list items
|
||||
if (referencesHeadingPos >= 0 && node.type.name === "paragraph") {
|
||||
const match = node.textContent.match(/^\[(.+?)\]/);
|
||||
if (match) {
|
||||
existingRefs.add(match[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If references section doesn't exist, create it
|
||||
if (referencesHeadingPos === -1) {
|
||||
const content: any[] = [
|
||||
{ type: "horizontalRule" },
|
||||
{
|
||||
type: "heading",
|
||||
attrs: { level: 2 },
|
||||
content: [{ type: "text", text: "References" }]
|
||||
}
|
||||
];
|
||||
|
||||
// Add each reference as a paragraph
|
||||
refNumbers.forEach((refNum) => {
|
||||
content.push({
|
||||
type: "paragraph",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `[${refNum}] `,
|
||||
marks: [{ type: "bold" }]
|
||||
} as any,
|
||||
{
|
||||
type: "text",
|
||||
text: "Add your reference text here"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Insert at the end
|
||||
const tr = editorInstance.state.tr;
|
||||
tr.insert(
|
||||
doc.content.size,
|
||||
editorInstance.schema.nodeFromJSON({ type: "doc", content }).content
|
||||
);
|
||||
editorInstance.view.dispatch(tr);
|
||||
} else {
|
||||
// Update existing references section - add missing refs
|
||||
const newRefs = refNumbers.filter((ref) => !existingRefs.has(ref));
|
||||
|
||||
if (newRefs.length > 0) {
|
||||
// Find position after References heading to insert new refs
|
||||
let insertPos = referencesHeadingPos;
|
||||
doc.nodesBetween(
|
||||
referencesHeadingPos,
|
||||
doc.content.size,
|
||||
(node: any, pos: number) => {
|
||||
if (pos > insertPos) {
|
||||
insertPos = pos + node.nodeSize;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const content: any[] = [];
|
||||
newRefs.forEach((refNum) => {
|
||||
content.push({
|
||||
type: "paragraph",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `[${refNum}] `,
|
||||
marks: [{ type: "bold" }]
|
||||
} as any,
|
||||
{
|
||||
type: "text",
|
||||
text: "Add your reference text here"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
const tr = editorInstance.state.tr;
|
||||
content.forEach((item) => {
|
||||
tr.insert(insertPos, editorInstance.schema.nodeFromJSON(item));
|
||||
insertPos += editorInstance.schema.nodeFromJSON(item).nodeSize;
|
||||
});
|
||||
editorInstance.view.dispatch(tr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setLink = () => {
|
||||
const instance = editor();
|
||||
if (!instance) return;
|
||||
@@ -1057,6 +1231,34 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
>
|
||||
Code
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
instance().chain().focus().toggleSuperscript().run()
|
||||
}
|
||||
class={`${
|
||||
instance().isActive("superscript")
|
||||
? "bg-crust"
|
||||
: "hover:bg-crust"
|
||||
} bg-opacity-30 hover:bg-opacity-30 rounded px-2 py-1`}
|
||||
title="Superscript (Reference)"
|
||||
>
|
||||
X<sup>n</sup>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
instance().chain().focus().toggleSubscript().run()
|
||||
}
|
||||
class={`${
|
||||
instance().isActive("subscript")
|
||||
? "bg-crust"
|
||||
: "hover:bg-crust"
|
||||
} bg-opacity-30 hover:bg-opacity-30 rounded px-2 py-1`}
|
||||
title="Subscript"
|
||||
>
|
||||
X<sub>n</sub>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@@ -1321,6 +1523,34 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
>
|
||||
<s>S</s>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
instance().chain().focus().toggleSuperscript().run()
|
||||
}
|
||||
class={`${
|
||||
instance().isActive("superscript")
|
||||
? "bg-surface2"
|
||||
: "hover:bg-surface1"
|
||||
} rounded px-2 py-1 text-xs`}
|
||||
title="Superscript (for references)"
|
||||
>
|
||||
X<sup class="text-[0.6em]">n</sup>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
instance().chain().focus().toggleSubscript().run()
|
||||
}
|
||||
class={`${
|
||||
instance().isActive("subscript")
|
||||
? "bg-surface2"
|
||||
: "hover:bg-surface1"
|
||||
} rounded px-2 py-1 text-xs`}
|
||||
title="Subscript"
|
||||
>
|
||||
X<sub class="text-[0.6em]">n</sub>
|
||||
</button>
|
||||
<div class="border-surface2 mx-1 border-l"></div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user