diff --git a/packages/apps/client/src/PrompterStyles.css b/packages/apps/client/src/PrompterStyles.css
new file mode 100644
index 0000000..e0f788a
--- /dev/null
+++ b/packages/apps/client/src/PrompterStyles.css
@@ -0,0 +1,48 @@
+.Prompter {
+ --background-color: #000;
+ --foreground-color: #fff;
+ --header-color: #999;
+
+ color: var(--foreground-color);
+ background: var(--background-color);
+
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ white-space: break-spaces;
+}
+
+.Prompter p {
+ margin: 0;
+ padding: 0;
+}
+
+.Prompter b {
+ font-weight: 700;
+}
+
+.Prompter i {
+ font-style: italic;
+}
+
+.Prompter u {
+ text-decoration: underline;
+}
+
+.Prompter s {
+ text-decoration: none;
+ opacity: 0.3;
+}
+
+.Prompter rev {
+ background-color: var(--foreground-color);
+ color: var(--background-color);
+}
+
+.Prompter h1,
+.Prompter h2,
+.Prompter h3 {
+ background-color: var(--header-color);
+ color: var(--background-color);
+ user-select: none;
+ margin: 1em 0;
+}
diff --git a/packages/apps/client/src/ScriptEditor/Editor.tsx b/packages/apps/client/src/ScriptEditor/Editor.tsx
index efda6b1..7cceaae 100644
--- a/packages/apps/client/src/ScriptEditor/Editor.tsx
+++ b/packages/apps/client/src/ScriptEditor/Editor.tsx
@@ -7,6 +7,10 @@ import { baseKeymap } from 'prosemirror-commands'
import { schema } from './scriptSchema'
import 'prosemirror-view/style/prosemirror.css'
import { updateModel } from './plugins/updateModel'
+import { readOnlyNodeFilter } from './plugins/readOnlyNodeFilter'
+import { randomId } from '../lib/lib'
+import { formatingKeymap } from './keymaps'
+import { deselectAll } from './commands/deselectAll'
export function Editor({
initialValue,
@@ -22,12 +26,81 @@ export function Editor({
void initialValue
+ useEffect(() => {
+ function onKeyDown(ev: KeyboardEvent) {
+ console.log(ev)
+ if (ev.key === 'w' && ev.ctrlKey) {
+ ev.preventDefault()
+ }
+ }
+
+ function onBeforeUnload(ev: BeforeUnloadEvent) {
+ ev.stopPropagation()
+ ev.preventDefault()
+ return false
+ }
+
+ window.addEventListener('keydown', onKeyDown)
+ window.addEventListener('beforeunload', onBeforeUnload, { capture: true })
+
+ return () => {
+ window.removeEventListener('keydown', onKeyDown)
+ window.removeEventListener('beforeunload', onBeforeUnload, { capture: true })
+ }
+ }, [])
+
useEffect(() => {
if (!containerEl.current) return
+ const rundown = schema.node(schema.nodes.rundown, undefined, [
+ schema.node(schema.nodes.rundownTitle, undefined, schema.text('Rundown Title')),
+ schema.node(schema.nodes.segment, undefined, [
+ schema.node(schema.nodes.segmentTitle, undefined, schema.text('Segment Title')),
+ schema.node(
+ schema.nodes.part,
+ {
+ partId: randomId(),
+ },
+ [
+ schema.node(schema.nodes.partTitle, undefined, schema.text('Part title')),
+ schema.node(schema.nodes.paragraph, undefined, schema.text('Script...')),
+ ]
+ ),
+ schema.node(
+ schema.nodes.part,
+ {
+ partId: randomId(),
+ },
+ [
+ schema.node(schema.nodes.partTitle, undefined, schema.text('Part title')),
+ schema.node(schema.nodes.paragraph, undefined, schema.text('Script...')),
+ ]
+ ),
+ schema.node(
+ schema.nodes.part,
+ {
+ partId: randomId(),
+ },
+ [
+ schema.node(schema.nodes.partTitle, undefined, schema.text('Part title')),
+ schema.node(schema.nodes.paragraph, undefined, schema.text('Script...')),
+ ]
+ ),
+ ]),
+ ])
+ const doc = schema.node(schema.nodes.doc, undefined, [rundown])
+
const state = EditorState.create({
- schema,
- plugins: [history(), keymap({ 'Mod-z': undo, 'Mod-y': redo }), keymap(baseKeymap), updateModel()],
+ plugins: [
+ history(),
+ keymap({ 'Mod-z': undo, 'Mod-y': redo }),
+ keymap({ Escape: deselectAll }),
+ keymap(formatingKeymap),
+ keymap(baseKeymap),
+ readOnlyNodeFilter(),
+ updateModel(),
+ ],
+ doc,
})
const view = new EditorView(containerEl.current, {
state,
@@ -41,7 +114,7 @@ export function Editor({
}
}, [])
- return
+ return
}
type OnChangeEvent = {
diff --git a/packages/apps/client/src/ScriptEditor/ScriptEditor.tsx b/packages/apps/client/src/ScriptEditor/ScriptEditor.tsx
index 4e4bf06..5ed7553 100644
--- a/packages/apps/client/src/ScriptEditor/ScriptEditor.tsx
+++ b/packages/apps/client/src/ScriptEditor/ScriptEditor.tsx
@@ -1,10 +1,12 @@
import React from 'react'
import { Editor } from './Editor'
+import '../PrompterStyles.css'
+
export function ScriptEditor(): React.JSX.Element {
return (
<>
-
+
>
)
}
diff --git a/packages/apps/client/src/ScriptEditor/commands/deselectAll.ts b/packages/apps/client/src/ScriptEditor/commands/deselectAll.ts
new file mode 100644
index 0000000..690e72e
--- /dev/null
+++ b/packages/apps/client/src/ScriptEditor/commands/deselectAll.ts
@@ -0,0 +1,9 @@
+import { Command, TextSelection } from 'prosemirror-state'
+
+export const deselectAll: Command = (state, dispatch): boolean => {
+ const newSelection = new TextSelection(state.selection.$to)
+ const transaction = state.tr.setSelection(newSelection)
+ if (dispatch) dispatch(transaction)
+
+ return true
+}
diff --git a/packages/apps/client/src/ScriptEditor/keymaps.ts b/packages/apps/client/src/ScriptEditor/keymaps.ts
new file mode 100644
index 0000000..e3daf5a
--- /dev/null
+++ b/packages/apps/client/src/ScriptEditor/keymaps.ts
@@ -0,0 +1,11 @@
+import { Command } from 'prosemirror-state'
+import { toggleMark } from 'prosemirror-commands'
+import { schema } from './scriptSchema'
+
+export const formatingKeymap: Record = {
+ 'Mod-b': toggleMark(schema.marks.bold),
+ 'Mod-i': toggleMark(schema.marks.italic),
+ 'Mod-u': toggleMark(schema.marks.underline),
+ 'Mod-q': toggleMark(schema.marks.reverse),
+ 'Mod-F10': toggleMark(schema.marks.hidden),
+}
diff --git a/packages/apps/client/src/ScriptEditor/plugins/readOnlyNodeFilter.ts b/packages/apps/client/src/ScriptEditor/plugins/readOnlyNodeFilter.ts
new file mode 100644
index 0000000..766dfea
--- /dev/null
+++ b/packages/apps/client/src/ScriptEditor/plugins/readOnlyNodeFilter.ts
@@ -0,0 +1,22 @@
+import { Plugin } from 'prosemirror-state'
+
+export function readOnlyNodeFilter() {
+ return new Plugin({
+ filterTransaction: (tr, state): boolean => {
+ if (!tr.docChanged) return true
+
+ let editAllowed = true
+ for (const step of tr.steps) {
+ step.getMap().forEach((oldStart, oldEnd) => {
+ state.doc.nodesBetween(oldStart, oldEnd, (node, _index, parent) => {
+ if (node.type.spec.locked || parent?.type.spec.locked) {
+ editAllowed = false
+ }
+ })
+ })
+ }
+
+ return editAllowed
+ },
+ })
+}
diff --git a/packages/apps/client/src/ScriptEditor/plugins/updateModel.ts b/packages/apps/client/src/ScriptEditor/plugins/updateModel.ts
index 370cb91..e712b78 100644
--- a/packages/apps/client/src/ScriptEditor/plugins/updateModel.ts
+++ b/packages/apps/client/src/ScriptEditor/plugins/updateModel.ts
@@ -2,8 +2,8 @@ import { Plugin } from 'prosemirror-state'
export function updateModel() {
return new Plugin({
- appendTransaction: (_tr, _oldState, newState) => {
- console.log(newState.doc.toJSON())
+ appendTransaction: () => {
+ // console.log(newState)
return null
},
})
diff --git a/packages/apps/client/src/ScriptEditor/scriptSchema.ts b/packages/apps/client/src/ScriptEditor/scriptSchema.ts
index ca51085..9762023 100644
--- a/packages/apps/client/src/ScriptEditor/scriptSchema.ts
+++ b/packages/apps/client/src/ScriptEditor/scriptSchema.ts
@@ -3,34 +3,112 @@ import { nodes } from 'prosemirror-schema-basic'
export const schema = new Schema({
nodes: {
- unmarkedText: {
- inline: true,
- marks: '',
- },
text: nodes.text,
+
paragraph: nodes.paragraph,
- segmentHeading: {
- group: 'block',
- content: 'unmarkedText',
+
+ partTitle: {
+ group: 'title',
+ content: 'text*',
atom: true,
+ marks: '',
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ locked: true,
+ toDOM() {
+ return ['h2', { class: 'PartSlug', contenteditable: 'false' }, 0]
+ },
},
- partHeading: {
+ part: {
group: 'block',
- content: 'unmarkedText',
+ content: 'partTitle paragraph*',
+ partId: {
+ default: null,
+ },
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ toDOM() {
+ return ['div', { class: 'part' }, 0]
+ },
+ },
+
+ segmentTitle: {
+ group: 'title',
+ content: 'text*',
atom: true,
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ locked: true,
+ marks: '',
+ toDOM() {
+ return ['h2', { class: 'SegmentTitle', contenteditable: 'false' }, 0]
+ },
},
- rundownHeading: {
+ segment: {
group: 'block',
- content: 'unmarkedText',
+ content: 'segmentTitle part*',
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ toDOM() {
+ return ['div', { class: 'segment' }, 0]
+ },
+ },
+
+ rundownTitle: {
+ group: 'title',
+ content: 'text*',
atom: true,
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ locked: true,
+ marks: '',
+ toDOM() {
+ return ['h1', { class: 'RundownTitle', contenteditable: 'false' }, 0]
+ },
},
- doc: { content: 'block*' },
+ rundown: {
+ group: 'block',
+ content: 'rundownTitle segment*',
+ isolating: true,
+ draggable: false,
+ selectable: false,
+ toDOM() {
+ return ['div', { class: 'rundown' }, 0]
+ },
+ },
+
+ doc: { content: 'rundown*' },
},
marks: {
- bold: {},
- italic: {},
- underline: {},
- hidden: {},
- reverse: {},
+ bold: {
+ toDOM() {
+ return ['b', 0]
+ },
+ },
+ italic: {
+ toDOM() {
+ return ['i', 0]
+ },
+ },
+ underline: {
+ toDOM() {
+ return ['u', 0]
+ },
+ },
+ hidden: {
+ toDOM() {
+ return ['s', 0]
+ },
+ },
+ reverse: {
+ toDOM() {
+ return ['rev', 0]
+ },
+ },
},
})