@rimelight/cms 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/package.json +69 -0
- package/src/astro/BlockRenderer.astro +111 -0
- package/src/astro/PageRenderer.astro +23 -0
- package/src/astro/blocks/CalloutBlock.astro +78 -0
- package/src/astro/blocks/CardBlock.astro +43 -0
- package/src/astro/blocks/CardsBlock.astro +24 -0
- package/src/astro/blocks/CodeBlock.astro +16 -0
- package/src/astro/blocks/DialogueBlock.astro +23 -0
- package/src/astro/blocks/FileTreeBlock.astro +45 -0
- package/src/astro/blocks/ImageBlock.astro +17 -0
- package/src/astro/blocks/OrderedListBlock.astro +55 -0
- package/src/astro/blocks/ParagraphBlock.astro +65 -0
- package/src/astro/blocks/SceneBlock.astro +65 -0
- package/src/astro/blocks/ScriptBlock.astro +59 -0
- package/src/astro/blocks/SectionBlock.astro +46 -0
- package/src/astro/blocks/StepItemBlock.astro +25 -0
- package/src/astro/blocks/StepsBlock.astro +14 -0
- package/src/astro/blocks/TabItemBlock.astro +19 -0
- package/src/astro/blocks/TableBlock.astro +49 -0
- package/src/astro/blocks/TabsBlock.astro +67 -0
- package/src/astro/blocks/UnorderedListBlock.astro +55 -0
- package/src/auth/editor-guards.ts +148 -0
- package/src/auth/filter-blocks.ts +44 -0
- package/src/auth/filter-templates.ts +81 -0
- package/src/auth/permissions.ts +40 -0
- package/src/cache/purge.ts +37 -0
- package/src/client/components/RimelightBlock.tsx +487 -0
- package/src/client/components/RimelightBlockPicker.tsx +305 -0
- package/src/client/components/RimelightEditor.tsx +131 -0
- package/src/client/components/RimelightEditorLayout.tsx +143 -0
- package/src/client/components/RimelightPreview.tsx +354 -0
- package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
- package/src/client/components/RimelightTemplateEditor.tsx +245 -0
- package/src/client/components/editors/CalloutEditor.tsx +185 -0
- package/src/client/components/editors/CardEditor.tsx +60 -0
- package/src/client/components/editors/CardsEditor.tsx +132 -0
- package/src/client/components/editors/CodeEditor.tsx +101 -0
- package/src/client/components/editors/DialogueEditor.tsx +52 -0
- package/src/client/components/editors/FileTreeEditor.tsx +65 -0
- package/src/client/components/editors/ImageEditor.tsx +70 -0
- package/src/client/components/editors/ParagraphEditor.tsx +314 -0
- package/src/client/components/editors/SceneEditor.tsx +245 -0
- package/src/client/components/editors/ScriptEditor.tsx +245 -0
- package/src/client/components/editors/SectionEditor.tsx +139 -0
- package/src/client/components/editors/StepItemEditor.tsx +117 -0
- package/src/client/components/editors/StepsEditor.tsx +105 -0
- package/src/client/components/editors/TabItemEditor.tsx +113 -0
- package/src/client/components/editors/TableEditor.tsx +181 -0
- package/src/client/components/editors/TabsEditor.tsx +105 -0
- package/src/client/components/index.ts +7 -0
- package/src/client/loader.ts +19 -0
- package/src/client/state/autosave.ts +122 -0
- package/src/client/state/editor-store.ts +411 -0
- package/src/client/state/history.ts +251 -0
- package/src/client/state/lock-manager.ts +89 -0
- package/src/client/styles/cms-editor.css +653 -0
- package/src/client/toast.ts +12 -0
- package/src/client/utils/sortable.ts +184 -0
- package/src/client/utils/splitpane.ts +87 -0
- package/src/client/utils/tree-sanitizer.ts +33 -0
- package/src/core/page-definitions.ts +502 -0
- package/src/core/registry.ts +99 -0
- package/src/core/template-sync.ts +75 -0
- package/src/core/types/blocks.ts +367 -0
- package/src/core/types/inline.ts +23 -0
- package/src/core/types/pages.ts +36 -0
- package/src/core/types/templates.ts +32 -0
- package/src/core/types/versioning.ts +44 -0
- package/src/core/validator.ts +28 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +47 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/parser.ts +347 -0
- package/src/markdown/serializer.ts +219 -0
- package/src/migration.ts +155 -0
- package/src/schema/index.ts +8 -0
- package/src/schema/page_draft_locks.ts +13 -0
- package/src/schema/page_drafts.ts +18 -0
- package/src/schema/page_templates.ts +27 -0
- package/src/schema/page_version_approvals.ts +12 -0
- package/src/schema/page_version_comments.ts +14 -0
- package/src/schema/page_versions.ts +34 -0
- package/src/schema/pages.ts +42 -0
- package/src/schema/search.ts +23 -0
- package/src/search/query.ts +10 -0
- package/src/search/sync.ts +32 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import Sortable from "sortablejs"
|
|
2
|
+
|
|
3
|
+
const sortableProto: Record<string, any> | undefined = Sortable?.prototype
|
|
4
|
+
|
|
5
|
+
if (typeof window !== "undefined" && sortableProto && !sortableProto.patchedParentNodeGuard) {
|
|
6
|
+
sortableProto.patchedParentNodeGuard = true
|
|
7
|
+
const originalOnDragOver = sortableProto["_onDragOver"]
|
|
8
|
+
if (typeof originalOnDragOver === "function") {
|
|
9
|
+
sortableProto["_onDragOver"] = function (evt: Event) {
|
|
10
|
+
if (evt && evt.target instanceof Element) {
|
|
11
|
+
const el = evt.target
|
|
12
|
+
if (typeof document !== "undefined" && !document.contains(el)) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
originalOnDragOver.call(this, evt)
|
|
18
|
+
} catch (err: unknown) {
|
|
19
|
+
if (
|
|
20
|
+
err instanceof TypeError ||
|
|
21
|
+
(err instanceof Error && err.message.includes("parentNode"))
|
|
22
|
+
) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
throw err
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SortableOptions {
|
|
32
|
+
onReorder: (oldIndex: number, newIndex: number) => void
|
|
33
|
+
onAdd?: (
|
|
34
|
+
blockId: string,
|
|
35
|
+
newIndex: number,
|
|
36
|
+
fromContainerId?: string,
|
|
37
|
+
itemEl?: HTMLElement
|
|
38
|
+
) => void
|
|
39
|
+
onRemove?: (blockId: string, oldIndex: number) => void
|
|
40
|
+
handle?: string
|
|
41
|
+
filter?: string
|
|
42
|
+
group?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function initBlockSortable(container: HTMLElement, options: SortableOptions): Sortable {
|
|
46
|
+
const runCleanup = (specificItem?: HTMLElement | Element) => {
|
|
47
|
+
const doClean = () => cleanAllSortableClasses()
|
|
48
|
+
cleanAllSortableClasses(specificItem)
|
|
49
|
+
doClean()
|
|
50
|
+
requestAnimationFrame(doClean)
|
|
51
|
+
setTimeout(doClean, 50)
|
|
52
|
+
setTimeout(doClean, 150)
|
|
53
|
+
setTimeout(doClean, 300)
|
|
54
|
+
setTimeout(doClean, 600)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Sortable.create(container, {
|
|
58
|
+
group: {
|
|
59
|
+
name: options.group || "cms-blocks",
|
|
60
|
+
pull: true,
|
|
61
|
+
put: (to, _from, dragEl) => {
|
|
62
|
+
if (!to || !to.el || !dragEl) return false
|
|
63
|
+
// Prevent a container block from being dropped into itself or its own descendants
|
|
64
|
+
if (dragEl === to.el || dragEl.contains(to.el)) return false
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
animation: 150,
|
|
69
|
+
handle: options.handle || ".cms-drag-handle, .cms-grip-btn",
|
|
70
|
+
filter: options.filter || ".locked", // Templated or read-only blocks filtered out from drag
|
|
71
|
+
forceFallback: false,
|
|
72
|
+
fallbackOnBody: false,
|
|
73
|
+
fallbackTolerance: 3,
|
|
74
|
+
ghostClass: "cms-sortable-ghost",
|
|
75
|
+
chosenClass: "cms-sortable-chosen",
|
|
76
|
+
dragClass: "cms-sortable-drag",
|
|
77
|
+
swapThreshold: 0.65,
|
|
78
|
+
emptyInsertThreshold: 50,
|
|
79
|
+
onStart: () => {},
|
|
80
|
+
onUnchoose: () => {
|
|
81
|
+
runCleanup()
|
|
82
|
+
},
|
|
83
|
+
onSpill: () => {
|
|
84
|
+
runCleanup()
|
|
85
|
+
},
|
|
86
|
+
onEnd: (evt) => {
|
|
87
|
+
runCleanup(evt.item)
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
evt.from === evt.to &&
|
|
91
|
+
evt.oldIndex !== undefined &&
|
|
92
|
+
evt.newIndex !== undefined &&
|
|
93
|
+
evt.oldIndex !== evt.newIndex
|
|
94
|
+
) {
|
|
95
|
+
const oldIdx = evt.oldIndex
|
|
96
|
+
const newIdx = evt.newIndex
|
|
97
|
+
// Remove SortableJS-placed node; SolidJS will re-render from state
|
|
98
|
+
evt.item.parentNode?.removeChild(evt.item)
|
|
99
|
+
requestAnimationFrame(() => {
|
|
100
|
+
options.onReorder(oldIdx, newIdx)
|
|
101
|
+
runCleanup(evt.item)
|
|
102
|
+
})
|
|
103
|
+
} else {
|
|
104
|
+
requestAnimationFrame(() => {
|
|
105
|
+
runCleanup(evt.item)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
onAdd: (evt) => {
|
|
110
|
+
runCleanup(evt.item)
|
|
111
|
+
const itemEl = evt.item
|
|
112
|
+
const blockId = itemEl.dataset.id || itemEl.getAttribute("data-id")
|
|
113
|
+
if (blockId && evt.newIndex !== undefined && options.onAdd) {
|
|
114
|
+
const target = evt.from.closest("[data-block-id]")
|
|
115
|
+
const containerBlockId = target instanceof HTMLElement ? target.dataset.blockId : undefined
|
|
116
|
+
// Remove the physically-moved DOM node before calling the callback.
|
|
117
|
+
// The reactive framework (SolidJS) will re-render the authoritative element
|
|
118
|
+
// from state, so leaving the SortableJS-placed node causes a visual duplicate.
|
|
119
|
+
itemEl.parentNode?.removeChild(itemEl)
|
|
120
|
+
options.onAdd(blockId, evt.newIndex, containerBlockId, itemEl)
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
onRemove: (evt) => {
|
|
124
|
+
runCleanup(evt.item)
|
|
125
|
+
const itemEl = evt.item
|
|
126
|
+
const blockId = itemEl.dataset.id || itemEl.getAttribute("data-id")
|
|
127
|
+
if (blockId && evt.oldIndex !== undefined && options.onRemove) {
|
|
128
|
+
options.onRemove(blockId, evt.oldIndex)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cleanElement(el: Element) {
|
|
135
|
+
if (el instanceof HTMLElement) {
|
|
136
|
+
el.classList.remove(
|
|
137
|
+
"cms-sortable-ghost",
|
|
138
|
+
"cms-sortable-chosen",
|
|
139
|
+
"cms-sortable-drag",
|
|
140
|
+
"sortable-fallback"
|
|
141
|
+
)
|
|
142
|
+
el.style.pointerEvents = ""
|
|
143
|
+
el.style.opacity = ""
|
|
144
|
+
el.style.transform = ""
|
|
145
|
+
el.style.boxShadow = ""
|
|
146
|
+
el.style.borderColor = ""
|
|
147
|
+
el.style.backgroundColor = ""
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function cleanAllSortableClasses(specificItem?: HTMLElement | Element) {
|
|
152
|
+
if (specificItem) {
|
|
153
|
+
cleanElement(specificItem)
|
|
154
|
+
specificItem.querySelectorAll("*").forEach(cleanElement)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (typeof document !== "undefined") {
|
|
158
|
+
document
|
|
159
|
+
.querySelectorAll(
|
|
160
|
+
".cms-sortable-ghost, .cms-sortable-chosen, .cms-sortable-drag, .sortable-fallback"
|
|
161
|
+
)
|
|
162
|
+
.forEach((el) => {
|
|
163
|
+
cleanElement(el)
|
|
164
|
+
el.querySelectorAll("*").forEach(cleanElement)
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const customWin: Record<string, any> | null = typeof window !== "undefined" ? window : null
|
|
170
|
+
if (customWin && !customWin.cmsSortableGlobalListenersAttached) {
|
|
171
|
+
customWin.cmsSortableGlobalListenersAttached = true
|
|
172
|
+
const globalClean = () => {
|
|
173
|
+
const doClean = () => cleanAllSortableClasses()
|
|
174
|
+
doClean()
|
|
175
|
+
setTimeout(doClean, 50)
|
|
176
|
+
setTimeout(doClean, 150)
|
|
177
|
+
setTimeout(doClean, 300)
|
|
178
|
+
}
|
|
179
|
+
customWin.addEventListener("mouseup", globalClean, { capture: true })
|
|
180
|
+
customWin.addEventListener("pointerup", globalClean, { capture: true })
|
|
181
|
+
customWin.addEventListener("touchend", globalClean, { capture: true })
|
|
182
|
+
customWin.addEventListener("dragend", globalClean, { capture: true })
|
|
183
|
+
customWin.addEventListener("drop", globalClean, { capture: true })
|
|
184
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const SNAP_POINTS = [25, 33.333, 50, 66.667, 75]
|
|
2
|
+
const SNAP_THRESHOLD = 2.0
|
|
3
|
+
|
|
4
|
+
export function initSplitPane(
|
|
5
|
+
container: HTMLElement,
|
|
6
|
+
leftPane: HTMLElement,
|
|
7
|
+
rightPane: HTMLElement,
|
|
8
|
+
handle: HTMLElement,
|
|
9
|
+
onResize?: (leftPct: number) => void
|
|
10
|
+
) {
|
|
11
|
+
let isDragging = false
|
|
12
|
+
let animationFrameId: number | null = null
|
|
13
|
+
let pendingPercentage: number | null = null
|
|
14
|
+
|
|
15
|
+
const applyResize = () => {
|
|
16
|
+
if (pendingPercentage === null) return
|
|
17
|
+
leftPane.style.flex = `0 0 ${pendingPercentage}%`
|
|
18
|
+
leftPane.style.width = `${pendingPercentage}%`
|
|
19
|
+
rightPane.style.flex = `0 0 ${100 - pendingPercentage}%`
|
|
20
|
+
rightPane.style.width = `${100 - pendingPercentage}%`
|
|
21
|
+
onResize?.(pendingPercentage)
|
|
22
|
+
animationFrameId = null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const onMouseDown = (e: MouseEvent) => {
|
|
26
|
+
isDragging = true
|
|
27
|
+
handle.classList.add("active")
|
|
28
|
+
container.classList.add("split-dragging")
|
|
29
|
+
leftPane.style.transition = "none"
|
|
30
|
+
rightPane.style.transition = "none"
|
|
31
|
+
document.body.style.cursor = "col-resize"
|
|
32
|
+
document.body.style.userSelect = "none"
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
37
|
+
if (!isDragging) return
|
|
38
|
+
const containerRect = container.getBoundingClientRect()
|
|
39
|
+
const offset = e.clientX - containerRect.left
|
|
40
|
+
let percentage = (offset / containerRect.width) * 100
|
|
41
|
+
|
|
42
|
+
// Multi-point snapping (25%, 33.33%, 50%, 66.67%, 75%)
|
|
43
|
+
for (const snap of SNAP_POINTS) {
|
|
44
|
+
if (Math.abs(percentage - snap) <= SNAP_THRESHOLD) {
|
|
45
|
+
percentage = snap
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Keep within bounds 20% - 80%
|
|
51
|
+
percentage = Math.max(20, Math.min(80, percentage))
|
|
52
|
+
|
|
53
|
+
pendingPercentage = percentage
|
|
54
|
+
if (!animationFrameId) {
|
|
55
|
+
animationFrameId = requestAnimationFrame(applyResize)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const onMouseUp = () => {
|
|
60
|
+
if (isDragging) {
|
|
61
|
+
isDragging = false
|
|
62
|
+
handle.classList.remove("active")
|
|
63
|
+
container.classList.remove("split-dragging")
|
|
64
|
+
leftPane.style.transition = ""
|
|
65
|
+
rightPane.style.transition = ""
|
|
66
|
+
document.body.style.cursor = ""
|
|
67
|
+
document.body.style.userSelect = ""
|
|
68
|
+
if (animationFrameId) {
|
|
69
|
+
cancelAnimationFrame(animationFrameId)
|
|
70
|
+
animationFrameId = null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
handle.addEventListener("mousedown", onMouseDown)
|
|
76
|
+
document.addEventListener("mousemove", onMouseMove)
|
|
77
|
+
document.addEventListener("mouseup", onMouseUp)
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
handle.removeEventListener("mousedown", onMouseDown)
|
|
81
|
+
document.removeEventListener("mousemove", onMouseMove)
|
|
82
|
+
document.removeEventListener("mouseup", onMouseUp)
|
|
83
|
+
if (animationFrameId) {
|
|
84
|
+
cancelAnimationFrame(animationFrameId)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
2
|
+
|
|
3
|
+
export function getBlockChildren(b: BaseBlock): BaseBlock[] {
|
|
4
|
+
if (!b) return []
|
|
5
|
+
if (Array.isArray(b.props?.children)) return b.props.children
|
|
6
|
+
if (Array.isArray(b.children)) return b.children
|
|
7
|
+
return []
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function sanitizeBlockTree(blocks: BaseBlock[]): BaseBlock[] {
|
|
11
|
+
if (!Array.isArray(blocks)) return []
|
|
12
|
+
|
|
13
|
+
const childIds = new Set<string>()
|
|
14
|
+
|
|
15
|
+
for (const b of blocks) {
|
|
16
|
+
if (!b) continue
|
|
17
|
+
const children = getBlockChildren(b)
|
|
18
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
19
|
+
const collect = (list: BaseBlock[]) => {
|
|
20
|
+
for (const child of list) {
|
|
21
|
+
if (child && child.id) {
|
|
22
|
+
childIds.add(child.id)
|
|
23
|
+
const sub = getBlockChildren(child)
|
|
24
|
+
if (Array.isArray(sub) && sub.length > 0) collect(sub)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
collect(children)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return blocks.filter((b) => b && b.id && !childIds.has(b.id))
|
|
33
|
+
}
|