116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import type { Editor } from "@tiptap/react"
|
|
import type { Mark } from "@tiptap/pm/model"
|
|
|
|
export const DOCS_FONT_FAMILIES = [
|
|
{ name: "Arial", stack: "Arial, Helvetica, sans-serif" },
|
|
{ name: "Calibri", stack: "Calibri, Candara, Segoe, sans-serif" },
|
|
{ name: "Comic Sans MS", stack: '"Comic Sans MS", cursive, sans-serif' },
|
|
{ name: "Courier New", stack: '"Courier New", Courier, monospace' },
|
|
{ name: "Georgia", stack: "Georgia, serif" },
|
|
{ name: "Times New Roman", stack: '"Times New Roman", Times, serif' },
|
|
{ name: "Trebuchet MS", stack: '"Trebuchet MS", Helvetica, sans-serif' },
|
|
{ name: "Verdana", stack: "Verdana, Geneva, sans-serif" },
|
|
] as const
|
|
|
|
export type DocsFontFamilyName = (typeof DOCS_FONT_FAMILIES)[number]["name"]
|
|
|
|
export const DOCS_DEFAULT_FONT_FAMILY: DocsFontFamilyName = "Arial"
|
|
|
|
let lastToolbarFontFamily: DocsFontFamilyName = DOCS_DEFAULT_FONT_FAMILY
|
|
|
|
export type FontFamilyToolbarState =
|
|
| { kind: "single"; name: DocsFontFamilyName }
|
|
| { kind: "unset" }
|
|
|
|
function textStyleFontFamilyStack(
|
|
marks: ReadonlyArray<{ type: { name: string }; attrs: Record<string, unknown> }>
|
|
): string | null {
|
|
const mark = marks.find((m) => m.type.name === "textStyle")
|
|
const raw = mark?.attrs.fontFamily as string | undefined
|
|
return raw?.trim() ? raw : null
|
|
}
|
|
|
|
export function fontFamilyNameForStack(stack: string | null | undefined): DocsFontFamilyName | null {
|
|
if (!stack) return null
|
|
const normalized = stack.trim().toLowerCase()
|
|
for (const family of DOCS_FONT_FAMILIES) {
|
|
if (
|
|
family.stack.toLowerCase() === normalized ||
|
|
family.name.toLowerCase() === normalized ||
|
|
normalized.startsWith(`${family.name.toLowerCase()},`) ||
|
|
normalized.startsWith(`"${family.name.toLowerCase()}"`)
|
|
) {
|
|
return family.name
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function fontFamilyStackForName(name: string): string {
|
|
return DOCS_FONT_FAMILIES.find((f) => f.name === name)?.stack ?? name
|
|
}
|
|
|
|
function resolveCursorFontFamilyName(editor: Editor): DocsFontFamilyName {
|
|
const { state } = editor
|
|
const { $from } = state.selection
|
|
|
|
if (state.storedMarks) {
|
|
const stored = fontFamilyNameForStack(textStyleFontFamilyStack(state.storedMarks))
|
|
if (stored) return stored
|
|
}
|
|
|
|
const atCursor = fontFamilyNameForStack(textStyleFontFamilyStack($from.marks()))
|
|
if (atCursor) return atCursor
|
|
|
|
if ($from.parent.content.size === 0) return lastToolbarFontFamily
|
|
return DOCS_DEFAULT_FONT_FAMILY
|
|
}
|
|
|
|
function effectiveFontFamilyNameAtPos(marks: readonly Mark[]): DocsFontFamilyName {
|
|
return fontFamilyNameForStack(textStyleFontFamilyStack(marks)) ?? DOCS_DEFAULT_FONT_FAMILY
|
|
}
|
|
|
|
function collectEffectiveFontFamiliesInRange(
|
|
editor: Editor,
|
|
from: number,
|
|
to: number
|
|
): Set<DocsFontFamilyName> {
|
|
const names = new Set<DocsFontFamilyName>()
|
|
const { state } = editor
|
|
|
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
if (!node.isText || !node.text) return
|
|
const nodeStart = pos
|
|
const nodeEnd = pos + node.text.length
|
|
const start = Math.max(from, nodeStart)
|
|
const end = Math.min(to, nodeEnd)
|
|
if (start >= end) return
|
|
|
|
names.add(effectiveFontFamilyNameAtPos(node.marks))
|
|
})
|
|
|
|
return names
|
|
}
|
|
|
|
export function readFontFamilyToolbarState(editor: Editor): FontFamilyToolbarState {
|
|
const { from, to, empty } = editor.state.selection
|
|
|
|
if (empty) {
|
|
return { kind: "single", name: resolveCursorFontFamilyName(editor) }
|
|
}
|
|
|
|
const names = collectEffectiveFontFamiliesInRange(editor, from, to)
|
|
if (names.size === 0) {
|
|
return { kind: "single", name: resolveCursorFontFamilyName(editor) }
|
|
}
|
|
if (names.size === 1) {
|
|
return { kind: "single", name: [...names][0]! }
|
|
}
|
|
return { kind: "unset" }
|
|
}
|
|
|
|
export function applyFontFamily(editor: Editor, name: DocsFontFamilyName) {
|
|
lastToolbarFontFamily = name
|
|
editor.chain().focus().setFontFamily(fontFamilyStackForName(name)).run()
|
|
}
|