241 lines
7.5 KiB
TypeScript
241 lines
7.5 KiB
TypeScript
import { mergeAttributes, Node } from "@tiptap/core"
|
|
import { ReactNodeViewRenderer } from "@tiptap/react"
|
|
import { DocsGraphicNodeView } from "@/components/drive/richtext/docs-graphic-node-view"
|
|
import {
|
|
buildGradientCss,
|
|
DOCS_GRAPHIC_DEFAULTS,
|
|
type DocsGraphicAttrs,
|
|
type DocsGraphicFloatSide,
|
|
type DocsGraphicPlacement,
|
|
type DocsGraphicType,
|
|
type DocsGraphicWrap,
|
|
type DocsShapeType,
|
|
parseGraphicAttrs,
|
|
} from "@/lib/drive/docs-graphic-types"
|
|
|
|
declare module "@tiptap/core" {
|
|
interface Commands<ReturnType> {
|
|
docsGraphic: {
|
|
insertDocsGraphic: (attrs: Partial<DocsGraphicAttrs>) => ReturnType
|
|
updateDocsGraphic: (attrs: Partial<DocsGraphicAttrs>) => ReturnType
|
|
setDocsGraphicWrap: (wrap: DocsGraphicWrap) => ReturnType
|
|
setDocsGraphicPlacement: (placement: DocsGraphicPlacement) => ReturnType
|
|
setDocsGraphicFloatSide: (floatSide: DocsGraphicFloatSide) => ReturnType
|
|
bringDocsGraphicForward: () => ReturnType
|
|
sendDocsGraphicBackward: () => ReturnType
|
|
}
|
|
}
|
|
}
|
|
|
|
const graphicAttributes = {
|
|
graphicType: { default: DOCS_GRAPHIC_DEFAULTS.graphicType },
|
|
src: { default: null as string | null },
|
|
alt: { default: "" },
|
|
shapeType: { default: DOCS_GRAPHIC_DEFAULTS.shapeType },
|
|
fill: { default: DOCS_GRAPHIC_DEFAULTS.fill },
|
|
stroke: { default: DOCS_GRAPHIC_DEFAULTS.stroke },
|
|
strokeWidth: { default: DOCS_GRAPHIC_DEFAULTS.strokeWidth },
|
|
gradientCss: { default: "" },
|
|
gradientAngle: { default: DOCS_GRAPHIC_DEFAULTS.gradientAngle },
|
|
gradientColor1: { default: DOCS_GRAPHIC_DEFAULTS.gradientColor1 },
|
|
gradientColor2: { default: DOCS_GRAPHIC_DEFAULTS.gradientColor2 },
|
|
width: { default: DOCS_GRAPHIC_DEFAULTS.width },
|
|
height: { default: DOCS_GRAPHIC_DEFAULTS.height },
|
|
placement: { default: DOCS_GRAPHIC_DEFAULTS.placement },
|
|
wrap: { default: DOCS_GRAPHIC_DEFAULTS.wrap },
|
|
floatSide: { default: DOCS_GRAPHIC_DEFAULTS.floatSide },
|
|
x: { default: 0 },
|
|
y: { default: 0 },
|
|
rotationDeg: { default: 0 },
|
|
zIndex: { default: 0 },
|
|
cropX: { default: 0 },
|
|
cropY: { default: 0 },
|
|
cropWidth: { default: 1 },
|
|
cropHeight: { default: 1 },
|
|
cropShape: { default: "rect" },
|
|
assetId: { default: null as string | null },
|
|
opacity: { default: 1 },
|
|
shadow: { default: "" },
|
|
}
|
|
|
|
function mergeGraphicAttrs(partial: Partial<DocsGraphicAttrs>): DocsGraphicAttrs {
|
|
const merged = parseGraphicAttrs({ ...DOCS_GRAPHIC_DEFAULTS, ...partial })
|
|
if (merged.graphicType === "gradient" && !partial.gradientCss) {
|
|
merged.gradientCss = buildGradientCss(
|
|
merged.gradientAngle,
|
|
merged.gradientColor1,
|
|
merged.gradientColor2
|
|
)
|
|
}
|
|
return merged
|
|
}
|
|
|
|
function graphicCommands() {
|
|
return {
|
|
insertDocsGraphic:
|
|
(partial: Partial<DocsGraphicAttrs>) =>
|
|
({ chain }: { chain: () => { insertContent: (content: unknown) => { run: () => boolean } } }) => {
|
|
const attrs = mergeGraphicAttrs(partial)
|
|
const type =
|
|
attrs.placement === "inline" || attrs.wrap === "inline"
|
|
? "docsInlineGraphic"
|
|
: "docsGraphic"
|
|
return chain()
|
|
.insertContent({ type, attrs })
|
|
.run()
|
|
},
|
|
updateDocsGraphic:
|
|
(partial: Partial<DocsGraphicAttrs>) =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: Partial<DocsGraphicAttrs>) => { run: () => boolean }
|
|
}
|
|
editor: { isActive: (name: string) => boolean }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
return chain().updateAttributes(name, partial).run()
|
|
},
|
|
setDocsGraphicWrap:
|
|
(wrap: DocsGraphicWrap) =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: { wrap: DocsGraphicWrap }) => { run: () => boolean }
|
|
}
|
|
editor: { isActive: (name: string) => boolean }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
return chain().updateAttributes(name, { wrap }).run()
|
|
},
|
|
setDocsGraphicPlacement:
|
|
(placement: DocsGraphicPlacement) =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: { placement: DocsGraphicPlacement }) => {
|
|
run: () => boolean
|
|
}
|
|
}
|
|
editor: { isActive: (name: string) => boolean }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
return chain().updateAttributes(name, { placement }).run()
|
|
},
|
|
setDocsGraphicFloatSide:
|
|
(floatSide: DocsGraphicFloatSide) =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: { floatSide: DocsGraphicFloatSide }) => {
|
|
run: () => boolean
|
|
}
|
|
}
|
|
editor: { isActive: (name: string) => boolean }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
return chain().updateAttributes(name, { floatSide }).run()
|
|
},
|
|
bringDocsGraphicForward:
|
|
() =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: { zIndex: number }) => { run: () => boolean }
|
|
}
|
|
editor: { isActive: (name: string) => boolean; getAttributes: (name: string) => Record<string, unknown> }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
const z = Number(editor.getAttributes(name).zIndex ?? 0)
|
|
return chain().updateAttributes(name, { zIndex: z + 1 }).run()
|
|
},
|
|
sendDocsGraphicBackward:
|
|
() =>
|
|
({
|
|
chain,
|
|
editor,
|
|
}: {
|
|
chain: () => {
|
|
updateAttributes: (name: string, attrs: { zIndex: number }) => { run: () => boolean }
|
|
}
|
|
editor: { isActive: (name: string) => boolean; getAttributes: (name: string) => Record<string, unknown> }
|
|
}) => {
|
|
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
|
|
const z = Number(editor.getAttributes(name).zIndex ?? 0)
|
|
return chain().updateAttributes(name, { zIndex: Math.max(0, z - 1) }).run()
|
|
},
|
|
}
|
|
}
|
|
|
|
export const DocsGraphic = Node.create({
|
|
name: "docsGraphic",
|
|
group: "block",
|
|
atom: true,
|
|
draggable: true,
|
|
selectable: true,
|
|
|
|
addAttributes() {
|
|
return graphicAttributes
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: 'div[data-type="docs-graphic"]' }]
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ["div", mergeAttributes(HTMLAttributes, { "data-type": "docs-graphic" })]
|
|
},
|
|
|
|
addNodeView() {
|
|
return ReactNodeViewRenderer(DocsGraphicNodeView)
|
|
},
|
|
|
|
addCommands() {
|
|
return graphicCommands()
|
|
},
|
|
})
|
|
|
|
export const DocsInlineGraphic = Node.create({
|
|
name: "docsInlineGraphic",
|
|
group: "inline",
|
|
inline: true,
|
|
atom: true,
|
|
draggable: true,
|
|
selectable: true,
|
|
|
|
addAttributes() {
|
|
return graphicAttributes
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: 'span[data-type="docs-inline-graphic"]' }]
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ["span", mergeAttributes(HTMLAttributes, { "data-type": "docs-inline-graphic" })]
|
|
},
|
|
|
|
addNodeView() {
|
|
return ReactNodeViewRenderer(DocsGraphicNodeView)
|
|
},
|
|
})
|
|
|
|
export function buildInsertGraphicAttrs(
|
|
graphicType: DocsGraphicType,
|
|
partial: Partial<DocsGraphicAttrs> = {}
|
|
): DocsGraphicAttrs {
|
|
return mergeGraphicAttrs({ ...DOCS_GRAPHIC_DEFAULTS, graphicType, ...partial })
|
|
}
|
|
|
|
export { mergeGraphicAttrs }
|