Skip to content

Commit

Permalink
feat(ScriptEditor): readOnlyNodeFilter, formatting, custom command
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl committed Nov 21, 2023
1 parent 3b9e163 commit 6403f77
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 23 deletions.
48 changes: 48 additions & 0 deletions packages/apps/client/src/PrompterStyles.css
Original file line number Diff line number Diff line change
@@ -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;
}
79 changes: 76 additions & 3 deletions packages/apps/client/src/ScriptEditor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -41,7 +114,7 @@ export function Editor({
}
}, [])

return <div ref={containerEl} className={className}></div>
return <div ref={containerEl} className={className} spellCheck="false"></div>
}

type OnChangeEvent = {
Expand Down
4 changes: 3 additions & 1 deletion packages/apps/client/src/ScriptEditor/ScriptEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react'
import { Editor } from './Editor'

import '../PrompterStyles.css'

export function ScriptEditor(): React.JSX.Element {
return (
<>
<Editor className="bg-black" />
<Editor className="bg-black Prompter" />
</>
)
}
9 changes: 9 additions & 0 deletions packages/apps/client/src/ScriptEditor/commands/deselectAll.ts
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions packages/apps/client/src/ScriptEditor/keymaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command } from 'prosemirror-state'
import { toggleMark } from 'prosemirror-commands'
import { schema } from './scriptSchema'

export const formatingKeymap: Record<string, Command> = {
'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),
}
Original file line number Diff line number Diff line change
@@ -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
},
})
}
4 changes: 2 additions & 2 deletions packages/apps/client/src/ScriptEditor/plugins/updateModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
})
Expand Down
112 changes: 95 additions & 17 deletions packages/apps/client/src/ScriptEditor/scriptSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
},
},
},
})

0 comments on commit 6403f77

Please sign in to comment.