misc: configure and fix eslint formatting (#66719)
This commit is contained in:
parent
7ee0085317
commit
253620ecd0
|
@ -6,7 +6,8 @@ parserOptions:
|
|||
ecmaVersion: 13
|
||||
sourceType: module
|
||||
rules:
|
||||
# https://standardjs.com/rules.html
|
||||
# Follow Standard JS guidelines : https://standardjs.com/rules.html, except rules
|
||||
# annotated with a 'custom' comment
|
||||
|
||||
# Linting
|
||||
array-callback-return: error
|
||||
|
@ -78,3 +79,51 @@ rules:
|
|||
no-with: error
|
||||
use-isnan: error
|
||||
valid-typeof: error
|
||||
|
||||
# Style / Formatting
|
||||
accessor-pairs: error
|
||||
block-spacing: error
|
||||
brace-style: [error, 1tbs, {allowSingleLine: true}]
|
||||
camelcase: error
|
||||
comma-dangle: [error, always-multiline] # custom : Adding a dangling comma make patches shorter
|
||||
comma-spacing: error
|
||||
comma-style: error
|
||||
curly: [error, multi-line]
|
||||
dot-location: [error, property]
|
||||
eol-last: [error, always]
|
||||
func-call-spacing: error
|
||||
indent: [error, 2]
|
||||
key-spacing: error
|
||||
keyword-spacing: error
|
||||
max-len: [error, {code: 110}] # custom: configured like this on python projects
|
||||
new-cap: [error, { newIsCap: true, capIsNew: false}]
|
||||
new-parens: error
|
||||
no-extra-parens: [error, functions]
|
||||
no-floating-decimal: error
|
||||
no-irregular-whitespace: error
|
||||
no-lone-blocks: error
|
||||
no-mixed-spaces-and-tabs: error
|
||||
no-multi-spaces: error
|
||||
no-multi-str: error
|
||||
no-multiple-empty-lines: error
|
||||
no-tabs: error
|
||||
no-trailing-spaces: error
|
||||
no-undef-init: error
|
||||
no-useless-rename: error
|
||||
no-whitespace-before-property: error
|
||||
object-property-newline: [error, { allowMultiplePropertiesPerLine: true }]
|
||||
one-var: [error, never]
|
||||
operator-linebreak: [error, before]
|
||||
padded-blocks: [error, never]
|
||||
quotes: [error, single]
|
||||
rest-spread-spacing: error
|
||||
semi-spacing: error
|
||||
semi: [error, never]
|
||||
space-before-function-paren: error
|
||||
space-in-parens: error
|
||||
space-infix-ops: error
|
||||
space-unary-ops: error
|
||||
spaced-comment: error
|
||||
template-curly-spacing: error
|
||||
wrap-iife: [error, any]
|
||||
yield-star-spacing: [error, {after: true, before: true}]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import buble from '@rollup/plugin-buble';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import buble from '@rollup/plugin-buble'
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
||||
import { terser } from 'rollup-plugin-terser'
|
||||
|
||||
export default {
|
||||
input: './src-js/godo.js',
|
||||
|
@ -8,14 +8,14 @@ export default {
|
|||
{
|
||||
file: 'dist/js/godo.js',
|
||||
format: 'es',
|
||||
sourcemap: true
|
||||
sourcemap: true,
|
||||
},
|
||||
{
|
||||
file: 'dist/js/godo.min.js',
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
plugins: [terser()],
|
||||
}
|
||||
},
|
||||
],
|
||||
plugins: [nodeResolve(), buble()]
|
||||
plugins: [nodeResolve(), buble()],
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {findWrapping, liftTarget, canSplit, ReplaceAroundStep} from "prosemirror-transform"
|
||||
import {Slice, Fragment, NodeRange} from "prosemirror-model"
|
||||
import {findWrapping, liftTarget, canSplit, ReplaceAroundStep} from 'prosemirror-transform'
|
||||
import {Slice, Fragment, NodeRange} from 'prosemirror-model'
|
||||
|
||||
|
||||
// wrapInList, doWrapInList, splitListItem, liftToOuterList, liftOutOfList, sinkListItem
|
||||
|
@ -10,19 +10,21 @@ import {Slice, Fragment, NodeRange} from "prosemirror-model"
|
|||
// the given type an attributes. If `dispatch` is null, only return a
|
||||
// value to indicate whether this is possible, but don't actually
|
||||
// perform the change.
|
||||
export function wrapInList(listType, attrs) {
|
||||
return function(state, dispatch) {
|
||||
export function wrapInList (listType, attrs) {
|
||||
return function (state, dispatch) {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to), doJoin = false, outerRange = range
|
||||
let range = $from.blockRange($to); let doJoin = false; let outerRange = range
|
||||
if (!range) return false
|
||||
// This is at the top of an existing list item
|
||||
if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex === 0) {
|
||||
if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType)
|
||||
&& range.startIndex === 0) {
|
||||
// Don't do anything if this is the top of the list
|
||||
if ($from.index(range.depth - 1) === 0) return false
|
||||
let $insert = state.doc.resolve(range.start - 2)
|
||||
outerRange = new NodeRange($insert, $insert, range.depth)
|
||||
if (range.endIndex < range.parent.childCount)
|
||||
if (range.endIndex < range.parent.childCount) {
|
||||
range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth)
|
||||
}
|
||||
doJoin = true
|
||||
}
|
||||
let wrap = findWrapping(outerRange, listType, attrs, range)
|
||||
|
@ -32,19 +34,20 @@ export function wrapInList(listType, attrs) {
|
|||
}
|
||||
}
|
||||
|
||||
function doWrapInList(tr, range, wrappers, joinBefore, listType) {
|
||||
function doWrapInList (tr, range, wrappers, joinBefore, listType) {
|
||||
let content = Fragment.empty
|
||||
for (let i = wrappers.length - 1; i >= 0; i--)
|
||||
for (let i = wrappers.length - 1; i >= 0; i--) {
|
||||
content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))
|
||||
}
|
||||
|
||||
tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end,
|
||||
new Slice(content, 0, 0), wrappers.length, true))
|
||||
new Slice(content, 0, 0), wrappers.length, true))
|
||||
|
||||
let found = 0
|
||||
for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type === listType) found = i + 1
|
||||
let splitDepth = wrappers.length - found
|
||||
|
||||
let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent
|
||||
let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0); let parent = range.parent
|
||||
for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
|
||||
if (!first && canSplit(tr.doc, splitPos, splitDepth)) {
|
||||
tr.split(splitPos, splitDepth)
|
||||
|
@ -58,8 +61,8 @@ function doWrapInList(tr, range, wrappers, joinBefore, listType) {
|
|||
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
|
||||
// Build a command that splits a non-empty textblock at the top level
|
||||
// of a list item by also splitting that list item.
|
||||
export function splitListItem(itemType) {
|
||||
return function(state, dispatch) {
|
||||
export function splitListItem (itemType) {
|
||||
return function (state, dispatch) {
|
||||
let {$from, $to, node} = state.selection
|
||||
if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
|
||||
let grandParent = $from.node(-1)
|
||||
|
@ -68,17 +71,18 @@ export function splitListItem(itemType) {
|
|||
// In an empty block. If this is a nested list, the wrapping
|
||||
// list item should be split. Otherwise, bail out and let next
|
||||
// command handle lifting.
|
||||
if ($from.depth === 2 || $from.node(-3).type !== itemType ||
|
||||
$from.index(-2) !== $from.node(-2).childCount - 1) return false
|
||||
if ($from.depth === 2 || $from.node(-3).type !== itemType
|
||||
|| $from.index(-2) !== $from.node(-2).childCount - 1) return false
|
||||
if (dispatch) {
|
||||
let wrap = Fragment.empty
|
||||
let depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3
|
||||
// Build a fragment containing empty versions of the structure
|
||||
// from the outer list item to the parent node of the cursor
|
||||
for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d--)
|
||||
for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d--) {
|
||||
wrap = Fragment.from($from.node(d).copy(wrap))
|
||||
}
|
||||
let depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1
|
||||
: $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3
|
||||
: $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3
|
||||
// Add a second list item with an empty default start node
|
||||
wrap = wrap.append(Fragment.from(itemType.createAndFill()))
|
||||
let start = $from.before($from.depth - (depthBefore - 1))
|
||||
|
@ -105,54 +109,54 @@ export function splitListItem(itemType) {
|
|||
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
|
||||
// Create a command to lift the list item around the selection up into
|
||||
// a wrapping list.
|
||||
export function liftListItem(itemType) {
|
||||
return function(state, dispatch) {
|
||||
export function liftListItem (itemType) {
|
||||
return function (state, dispatch) {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to, node => node.childCount && node.firstChild.type === itemType)
|
||||
if (!range) return false
|
||||
if (!dispatch) return true
|
||||
if ($from.node(range.depth - 1).type === itemType) // Inside a parent list
|
||||
if ($from.node(range.depth - 1).type === itemType) { // Inside a parent list
|
||||
return liftToOuterList(state, dispatch, itemType, range)
|
||||
else // Outer list node
|
||||
} else { // Outer list node
|
||||
return liftOutOfList(state, dispatch, range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function liftToOuterList(state, dispatch, itemType, range) {
|
||||
let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth)
|
||||
function liftToOuterList (state, dispatch, itemType, range) {
|
||||
let tr = state.tr; let end = range.end; let endOfList = range.$to.end(range.depth)
|
||||
if (end < endOfList) {
|
||||
// There are siblings after the lifted items, which must become
|
||||
// children of the last item
|
||||
tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList,
|
||||
new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true))
|
||||
new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true))
|
||||
range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth)
|
||||
}
|
||||
dispatch(tr.lift(range, liftTarget(range)).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
function liftOutOfList(state, dispatch, range) {
|
||||
let tr = state.tr, list = range.parent
|
||||
function liftOutOfList (state, dispatch, range) {
|
||||
let tr = state.tr; let list = range.parent
|
||||
// Merge the list items into a single big item
|
||||
for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
|
||||
pos -= list.child(i).nodeSize
|
||||
tr.delete(pos - 1, pos + 1)
|
||||
}
|
||||
let $start = tr.doc.resolve(range.start), item = $start.nodeAfter
|
||||
let $start = tr.doc.resolve(range.start); let item = $start.nodeAfter
|
||||
if (tr.mapping.map(range.end) !== range.start + $start.nodeAfter.nodeSize) return false
|
||||
let atStart = range.startIndex === 0, atEnd = range.endIndex === list.childCount
|
||||
let parent = $start.node(-1), indexBefore = $start.index(-1)
|
||||
let atStart = range.startIndex === 0; let atEnd = range.endIndex === list.childCount
|
||||
let parent = $start.node(-1); let indexBefore = $start.index(-1)
|
||||
if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
|
||||
item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
|
||||
return false
|
||||
let start = $start.pos, end = start + item.nodeSize
|
||||
item.content.append(atEnd ? Fragment.empty : Fragment.from(list)))) { return false }
|
||||
let start = $start.pos; let end = start + item.nodeSize
|
||||
// Strip off the surrounding list. At the sides where we're not at
|
||||
// the end of the list, the existing list is closed. At sides where
|
||||
// this is the end, it is overwritten to its end.
|
||||
tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
|
||||
new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
|
||||
.append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))),
|
||||
atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1))
|
||||
new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
|
||||
.append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))),
|
||||
atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1))
|
||||
dispatch(tr.scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
@ -160,25 +164,26 @@ function liftOutOfList(state, dispatch, range) {
|
|||
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
|
||||
// Create a command to sink the list item around the selection down
|
||||
// into an inner list.
|
||||
export function sinkListItem(itemType) {
|
||||
return function(state, dispatch) {
|
||||
export function sinkListItem (itemType) {
|
||||
return function (state, dispatch) {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to, node => node.childCount && node.firstChild.type === itemType)
|
||||
if (!range) return false
|
||||
let startIndex = range.startIndex
|
||||
if (startIndex === 0) return false
|
||||
let parent = range.parent, nodeBefore = parent.child(startIndex - 1)
|
||||
let parent = range.parent; let nodeBefore = parent.child(startIndex - 1)
|
||||
if (nodeBefore.type !== itemType) return false
|
||||
|
||||
if (dispatch) {
|
||||
let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type === parent.type
|
||||
let inner = Fragment.from(nestedBefore ? itemType.create() : null)
|
||||
let slice = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))),
|
||||
nestedBefore ? 3 : 1, 0)
|
||||
let before = range.start, after = range.end
|
||||
let slice = new Slice(Fragment.from(itemType.create(null,
|
||||
Fragment.from(parent.type.create(null, inner)))),
|
||||
nestedBefore ? 3 : 1, 0)
|
||||
let before = range.start; let after = range.end
|
||||
dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
|
||||
before, after, slice, 1, true))
|
||||
.scrollIntoView())
|
||||
before, after, slice, 1, true))
|
||||
.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
|
||||
joinUp, joinDown, lift, selectParentNode} from "prosemirror-commands"
|
||||
import {splitListItem, liftListItem} from "./godo-additional-commands.mjs"
|
||||
import {undo, redo} from "prosemirror-history"
|
||||
joinUp, joinDown, lift, selectParentNode} from 'prosemirror-commands'
|
||||
import {splitListItem, liftListItem} from './godo-additional-commands.mjs'
|
||||
import {undo, redo} from 'prosemirror-history'
|
||||
|
||||
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false
|
||||
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false
|
||||
|
||||
// :: (Schema, ?Object) → Object
|
||||
// Inspect the given schema looking for marks and nodes from the
|
||||
|
@ -31,9 +31,9 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
|
|||
// You can suppress or map these bindings by passing a `mapKeys`
|
||||
// argument, which maps key names (say `"Mod-B"` to either `false`, to
|
||||
// remove the binding, or a new key name string.
|
||||
export function buildKeymap(schema, mapKeys, options) {
|
||||
let keys = {}, type;
|
||||
function bind(key, cmd) {
|
||||
export function buildKeymap (schema, mapKeys, options) {
|
||||
let keys = {}; let type
|
||||
function bind (key, cmd) {
|
||||
if (mapKeys) {
|
||||
let mapped = mapKeys[key]
|
||||
if (mapped === false) return
|
||||
|
@ -42,59 +42,53 @@ export function buildKeymap(schema, mapKeys, options) {
|
|||
keys[key] = cmd
|
||||
}
|
||||
|
||||
bind("Mod-z", undo)
|
||||
bind("Shift-Mod-z", redo)
|
||||
if (!mac) bind("Mod-y", redo)
|
||||
bind('Mod-z', undo)
|
||||
bind('Shift-Mod-z', redo)
|
||||
if (!mac) bind('Mod-y', redo)
|
||||
|
||||
bind("Alt-ArrowUp", joinUp)
|
||||
bind("Alt-ArrowDown", joinDown)
|
||||
bind("Mod-BracketLeft", lift)
|
||||
bind("Escape", selectParentNode)
|
||||
bind('Alt-ArrowUp', joinUp)
|
||||
bind('Alt-ArrowDown', joinDown)
|
||||
bind('Mod-BracketLeft', lift)
|
||||
bind('Escape', selectParentNode)
|
||||
|
||||
if ((type = schema.marks.strong)) {
|
||||
bind("Mod-b", toggleMark(type))
|
||||
bind("Mod-B", toggleMark(type))
|
||||
bind('Mod-b', toggleMark(type))
|
||||
bind('Mod-B', toggleMark(type))
|
||||
}
|
||||
if ((type = schema.marks.em)) {
|
||||
bind("Mod-i", toggleMark(type))
|
||||
bind("Mod-I", toggleMark(type))
|
||||
bind('Mod-i', toggleMark(type))
|
||||
bind('Mod-I', toggleMark(type))
|
||||
}
|
||||
if ((type = schema.marks.code))
|
||||
bind("Mod-`", toggleMark(type))
|
||||
|
||||
if ((type = schema.nodes.blockquote))
|
||||
bind("Ctrl->", wrapIn(type))
|
||||
|
||||
if ((type = schema.marks.code)) bind('Mod-`', toggleMark(type))
|
||||
if ((type = schema.nodes.blockquote)) bind('Ctrl->', wrapIn(type))
|
||||
if ((type = schema.nodes.list_item)) {
|
||||
bind("Enter", splitListItem(type))
|
||||
bind("Mod-[", liftListItem(type))
|
||||
bind('Enter', splitListItem(type))
|
||||
bind('Mod-[', liftListItem(type))
|
||||
}
|
||||
|
||||
if ((type = schema.nodes.hard_break)) {
|
||||
let br = type, cmd = chainCommands(exitCode, (state, dispatch) => {
|
||||
let br = type; let cmd = chainCommands(exitCode, (state, dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView())
|
||||
return true
|
||||
})
|
||||
bind("Mod-Enter", cmd)
|
||||
bind("Shift-Enter", cmd)
|
||||
if (mac) bind("Ctrl-Enter", cmd)
|
||||
bind('Mod-Enter', cmd)
|
||||
bind('Shift-Enter', cmd)
|
||||
if (mac) bind('Ctrl-Enter', cmd)
|
||||
}
|
||||
|
||||
if ((type = schema.nodes.paragraph))
|
||||
bind("Shift-Ctrl-0", setBlockType(type))
|
||||
if ((type = schema.nodes.paragraph)) bind('Shift-Ctrl-0', setBlockType(type))
|
||||
|
||||
if ((type = schema.nodes.code_block))
|
||||
bind("Shift-Ctrl-\\", setBlockType(type))
|
||||
if ((type = schema.nodes.code_block)) bind('Shift-Ctrl-\\', setBlockType(type))
|
||||
|
||||
if ((type = schema.nodes.heading)) {
|
||||
options.heading_levels.forEach( (level, index) => {
|
||||
bind(`Shift-Ctrl-${index+1}`, setBlockType(type, {level: level}))
|
||||
});
|
||||
options.heading_levels.forEach((level, index) => {
|
||||
bind(`Shift-Ctrl-${index + 1}`, setBlockType(type, {level: level}))
|
||||
})
|
||||
}
|
||||
|
||||
if ((type = schema.nodes.horizontal_rule)) {
|
||||
let hr = type
|
||||
bind("Mod-_", (state, dispatch) => {
|
||||
bind('Mod-_', (state, dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())
|
||||
return true
|
||||
})
|
||||
|
|
|
@ -1,92 +1,92 @@
|
|||
// menus language
|
||||
const dLanguage = document.documentElement.lang.split('-')[0];
|
||||
const nLanguage = navigator.languages ? navigator.languages[0] : navigator.language || navigator.userLanguage;
|
||||
const language = (dLanguage) ? dLanguage : nLanguage;
|
||||
const godoLanguage = (language === "fr") ? language : "en";
|
||||
const dLanguage = document.documentElement.lang.split('-')[0]
|
||||
const nLanguage = navigator.languages ? navigator.languages[0] : navigator.language || navigator.userLanguage
|
||||
const language = (dLanguage) ? dLanguage : nLanguage
|
||||
const godoLanguage = (language === 'fr') ? language : 'en'
|
||||
|
||||
const languageContent = {
|
||||
"a": {
|
||||
en: {
|
||||
icon: "a",
|
||||
text: "link"
|
||||
},
|
||||
fr: {
|
||||
icon: "a",
|
||||
text: 'lien'
|
||||
}
|
||||
},
|
||||
"b": {
|
||||
en: {
|
||||
icon: "B",
|
||||
text: "bold"
|
||||
},
|
||||
fr: {
|
||||
icon: "G",
|
||||
text: "gras"
|
||||
}
|
||||
},
|
||||
"h": {
|
||||
en: {
|
||||
icon: "H",
|
||||
text: "Headline"
|
||||
},
|
||||
fr: {
|
||||
icon: "T",
|
||||
text: "Titre"
|
||||
}
|
||||
},
|
||||
"hh": {
|
||||
en: {
|
||||
icon: "h",
|
||||
text: "Subhead"
|
||||
},
|
||||
fr: {
|
||||
icon: "t",
|
||||
text: "Sous-titre"
|
||||
}
|
||||
},
|
||||
"hhh": {
|
||||
en: {
|
||||
icon: "hh",
|
||||
text: 'Crosshead'
|
||||
},
|
||||
fr: {
|
||||
icon: "tt",
|
||||
text: "Intertitre"
|
||||
}
|
||||
},
|
||||
"i": {
|
||||
en: {
|
||||
icon: "i",
|
||||
text: "italic",
|
||||
},
|
||||
fr: {
|
||||
icon: "i",
|
||||
text: "italique"
|
||||
}
|
||||
},
|
||||
"p": {
|
||||
en: {
|
||||
icon: "p",
|
||||
text: "paragraph"
|
||||
},
|
||||
fr: {
|
||||
icon: "p",
|
||||
text: "paragraphe"
|
||||
}
|
||||
},
|
||||
"ul": {
|
||||
en: {
|
||||
icon: "•",
|
||||
text: "bullet list"
|
||||
},
|
||||
fr: {
|
||||
icon: "•",
|
||||
text: "Liste à puce"
|
||||
}
|
||||
}
|
||||
'a': {
|
||||
en: {
|
||||
icon: 'a',
|
||||
text: 'link',
|
||||
},
|
||||
fr: {
|
||||
icon: 'a',
|
||||
text: 'lien',
|
||||
},
|
||||
},
|
||||
'b': {
|
||||
en: {
|
||||
icon: 'B',
|
||||
text: 'bold',
|
||||
},
|
||||
fr: {
|
||||
icon: 'G',
|
||||
text: 'gras',
|
||||
},
|
||||
},
|
||||
'h': {
|
||||
en: {
|
||||
icon: 'H',
|
||||
text: 'Headline',
|
||||
},
|
||||
fr: {
|
||||
icon: 'T',
|
||||
text: 'Titre',
|
||||
},
|
||||
},
|
||||
'hh': {
|
||||
en: {
|
||||
icon: 'h',
|
||||
text: 'Subhead',
|
||||
},
|
||||
fr: {
|
||||
icon: 't',
|
||||
text: 'Sous-titre',
|
||||
},
|
||||
},
|
||||
'hhh': {
|
||||
en: {
|
||||
icon: 'hh',
|
||||
text: 'Crosshead',
|
||||
},
|
||||
fr: {
|
||||
icon: 'tt',
|
||||
text: 'Intertitre',
|
||||
},
|
||||
},
|
||||
'i': {
|
||||
en: {
|
||||
icon: 'i',
|
||||
text: 'italic',
|
||||
},
|
||||
fr: {
|
||||
icon: 'i',
|
||||
text: 'italique',
|
||||
},
|
||||
},
|
||||
'p': {
|
||||
en: {
|
||||
icon: 'p',
|
||||
text: 'paragraph',
|
||||
},
|
||||
fr: {
|
||||
icon: 'p',
|
||||
text: 'paragraphe',
|
||||
},
|
||||
},
|
||||
'ul': {
|
||||
en: {
|
||||
icon: '•',
|
||||
text: 'bullet list',
|
||||
},
|
||||
fr: {
|
||||
icon: '•',
|
||||
text: 'Liste à puce',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default function getLanguage(content) {
|
||||
return languageContent[content][godoLanguage];
|
||||
export default function getLanguage (content) {
|
||||
return languageContent[content][godoLanguage]
|
||||
}
|
||||
|
|
|
@ -1,201 +1,198 @@
|
|||
import {toggleMark, setBlockType, chainCommands} from "prosemirror-commands";
|
||||
import {wrapInList, liftListItem} from "./godo-additional-commands.mjs";
|
||||
import getLanguage from "./godo-menus-language.mjs";
|
||||
import {toggleMark, setBlockType, chainCommands} from 'prosemirror-commands'
|
||||
import {wrapInList, liftListItem} from './godo-additional-commands.mjs'
|
||||
import getLanguage from './godo-menus-language.mjs'
|
||||
|
||||
class Menu {
|
||||
constructor(menu, editorView) {
|
||||
this.items = menu.items;
|
||||
this.editorView = editorView;
|
||||
this.name = menu.name;
|
||||
constructor (menu, editorView) {
|
||||
this.items = menu.items
|
||||
this.editorView = editorView
|
||||
this.name = menu.name
|
||||
|
||||
this.el = document.createElement("div");
|
||||
this.el.className = `godo-${this.name}-menu`;
|
||||
this.el.wrapper = document.createElement("div");
|
||||
this.el.wrapper.className = "menuicons";
|
||||
this.el = document.createElement('div')
|
||||
this.el.className = `godo-${this.name}-menu`
|
||||
this.el.wrapper = document.createElement('div')
|
||||
this.el.wrapper.className = 'menuicons'
|
||||
for (const item in this.items) {
|
||||
this.el.wrapper.appendChild(this.items[item].dom);
|
||||
this.el.wrapper.appendChild(this.items[item].dom)
|
||||
}
|
||||
this.el.appendChild(this.el.wrapper);
|
||||
this.el.appendChild(this.el.wrapper)
|
||||
|
||||
this.update(editorView, null);
|
||||
this.update(editorView, null)
|
||||
|
||||
this.el.addEventListener("mousedown", e => {
|
||||
e.preventDefault();
|
||||
this.el.addEventListener('mousedown', e => {
|
||||
e.preventDefault()
|
||||
for (const item in this.items) {
|
||||
const {dom, run} = this.items[item];
|
||||
const {dom, run} = this.items[item]
|
||||
if (dom.contains(e.target)) {
|
||||
run(editorView.state, editorView.dispatch, editorView);
|
||||
run(editorView.state, editorView.dispatch, editorView)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
update( view, lastState ) {
|
||||
let state = view.state;
|
||||
update (view, lastState) {
|
||||
let state = view.state
|
||||
|
||||
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (this.name === "marks") {
|
||||
this.update_marks(view, state);
|
||||
if (this.name === 'marks') {
|
||||
this.updateMarks(view, state)
|
||||
}
|
||||
|
||||
if (this.name === "blocks") {
|
||||
this.update_blocks(view, state);
|
||||
if (this.name === 'blocks') {
|
||||
this.udpateBlocks(view, state)
|
||||
}
|
||||
}
|
||||
|
||||
set_menu_item( menuItem, is_active, is_disabled = true ) {
|
||||
if (is_active) {
|
||||
menuItem.classList.add('active');
|
||||
setMenuItem (menuItem, isActive, isDisabled = true) {
|
||||
if (isActive) {
|
||||
menuItem.classList.add('active')
|
||||
} else {
|
||||
menuItem.classList.remove('active');
|
||||
menuItem.classList.remove('active')
|
||||
}
|
||||
menuItem.disabled = is_disabled;
|
||||
menuItem.disabled = isDisabled
|
||||
}
|
||||
|
||||
update_blocks(view, state) {
|
||||
udpateBlocks (view, state) {
|
||||
for (const item in this.items) {
|
||||
const {dom, run, type, active, options} = this.items[item];
|
||||
const active_default = () => {
|
||||
let {$from, to} = state.selection;
|
||||
const {dom, run, type, active, options} = this.items[item]
|
||||
const activeDefault = () => {
|
||||
let {$from, to} = state.selection
|
||||
return to <= $from.end() && $from.parent.hasMarkup(type, options)
|
||||
};
|
||||
const is_active = (active) ? active(state) : active_default();
|
||||
const is_disabled = !run(state, null, view);
|
||||
this.set_menu_item(dom, is_active, is_disabled);
|
||||
}
|
||||
const isActive = (active) ? active(state) : activeDefault()
|
||||
const isDisabled = !run(state, null, view)
|
||||
this.setMenuItem(dom, isActive, isDisabled)
|
||||
}
|
||||
}
|
||||
|
||||
update_marks( view, state ) {
|
||||
let $sel = state.selection;
|
||||
const $cursor = $sel.$cursor;
|
||||
const ranges = $sel.ranges;
|
||||
let get_is_active;
|
||||
updateMarks (view, state) {
|
||||
let $sel = state.selection
|
||||
const $cursor = $sel.$cursor
|
||||
const ranges = $sel.ranges
|
||||
let getIsActive
|
||||
|
||||
// if single cursor
|
||||
if ($cursor && $cursor.marks()) {
|
||||
const activeMarks = $cursor.marks().map( m => m.type.name);
|
||||
get_is_active = (type) => activeMarks.includes(type.name);
|
||||
this.el.classList.add("fade");
|
||||
|
||||
const activeMarks = $cursor.marks().map(m => m.type.name)
|
||||
getIsActive = (type) => activeMarks.includes(type.name)
|
||||
this.el.classList.add('fade')
|
||||
|
||||
// else = select range
|
||||
} else {
|
||||
const range = ranges[0];
|
||||
const range = ranges[0]
|
||||
if (range && ranges.length === 1){
|
||||
get_is_active = (type) => state.doc.rangeHasMark(range.$from.pos, range.$to.pos, type);
|
||||
this.el.classList.remove("fade");
|
||||
getIsActive = (type) => state.doc.rangeHasMark(range.$from.pos, range.$to.pos, type)
|
||||
this.el.classList.remove('fade')
|
||||
}
|
||||
}
|
||||
|
||||
for (const item in this.items) {
|
||||
const {dom, type, run} = this.items[item];
|
||||
const is_active = get_is_active(type);
|
||||
let is_disabled = !run(state, null, view);
|
||||
this.set_menu_item(dom, is_active, is_disabled);
|
||||
const {dom, type, run} = this.items[item]
|
||||
const isActive = getIsActive(type)
|
||||
let isDisabled = !run(state, null, view)
|
||||
this.setMenuItem(dom, isActive, isDisabled)
|
||||
}
|
||||
|
||||
// move marks nav above selection
|
||||
let {from, to} = $sel;
|
||||
let start = view.coordsAtPos(from);
|
||||
let end = view.coordsAtPos(to);
|
||||
let left = Math.max((start.left + end.left) / 2, start.left + 3);
|
||||
this.el.style.transform = `translate(${left}px, ${end.bottom}px)`;
|
||||
let {from, to} = $sel
|
||||
let start = view.coordsAtPos(from)
|
||||
let end = view.coordsAtPos(to)
|
||||
let left = Math.max((start.left + end.left) / 2, start.left + 3)
|
||||
this.el.style.transform = `translate(${left}px, ${end.bottom}px)`
|
||||
|
||||
// if (view.hasFocus()) this.el.hidden = false;
|
||||
this.el.hidden = view.hasFocus();
|
||||
this.el.hidden = view.hasFocus()
|
||||
}
|
||||
|
||||
destroy() { this.el.remove(); }
|
||||
destroy () { this.el.remove() }
|
||||
}
|
||||
|
||||
|
||||
function linkItem( type ) {
|
||||
function linkItem (type) {
|
||||
return {
|
||||
dom: icon("a"),
|
||||
run( state, dispatch ) {
|
||||
let chref = prompt("href");
|
||||
toggleMark(type, {href: chref})(state, dispatch);
|
||||
dom: icon('a'),
|
||||
run (state, dispatch) {
|
||||
let chref = prompt('href')
|
||||
toggleMark(type, {href: chref})(state, dispatch)
|
||||
},
|
||||
type
|
||||
type,
|
||||
}
|
||||
}
|
||||
|
||||
function setHeader(type, level, icon_id) {
|
||||
function setHeader (type, level, iconId) {
|
||||
return {
|
||||
run: setBlockType(type, {level}),
|
||||
dom: icon(icon_id),
|
||||
run: setBlockType(type, {level}),
|
||||
dom: icon(iconId),
|
||||
type,
|
||||
options: {level}
|
||||
options: {level},
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create menu icons
|
||||
// id: defined in languageContent
|
||||
function icon(id) {
|
||||
let menuicon = document.createElement("button")
|
||||
menuicon.className = "menuicon menuicon-" + id;
|
||||
menuicon.setAttribute("type", "button");
|
||||
menuicon.title = getLanguage(id).text;
|
||||
menuicon.textContent = getLanguage(id).icon;
|
||||
return menuicon;
|
||||
function icon (id) {
|
||||
let menuicon = document.createElement('button')
|
||||
menuicon.className = 'menuicon menuicon-' + id
|
||||
menuicon.setAttribute('type', 'button')
|
||||
menuicon.title = getLanguage(id).text
|
||||
menuicon.textContent = getLanguage(id).icon
|
||||
return menuicon
|
||||
}
|
||||
|
||||
function blocks( schema, h_levels ) {
|
||||
function blocks (schema, hLevels) {
|
||||
let menu = {
|
||||
name : "blocks",
|
||||
h_levels
|
||||
};
|
||||
let type, i = {};
|
||||
name: 'blocks',
|
||||
hLevels,
|
||||
}
|
||||
let type; let i = {}
|
||||
|
||||
if ((type = schema.nodes.heading)) {
|
||||
let icon_id = 'h';
|
||||
h_levels.forEach( (level) => {
|
||||
i["setHeader"+level] = setHeader(type, level, icon_id);
|
||||
icon_id = icon_id + "h";
|
||||
});
|
||||
let iconId = 'h'
|
||||
hLevels.forEach((level) => {
|
||||
i['setHeader' + level] = setHeader(type, level, iconId)
|
||||
iconId = iconId + 'h'
|
||||
})
|
||||
}
|
||||
if ((type = schema.nodes.paragraph)) {
|
||||
i.setP = {run: setBlockType(type), dom: icon("p"), type};
|
||||
i.setP = {run: setBlockType(type), dom: icon('p'), type}
|
||||
}
|
||||
if ((type = schema.nodes.bullet_list)) {
|
||||
i.setList = {
|
||||
run: chainCommands(wrapInList(type), liftListItem(schema.nodes.list_item)),
|
||||
dom: icon("ul"),
|
||||
run: chainCommands(wrapInList(type), liftListItem(schema.nodes.list_item)),
|
||||
dom: icon('ul'),
|
||||
type,
|
||||
active(state) {
|
||||
active (state) {
|
||||
let {$from, $to} = state.selection
|
||||
let range = $from.blockRange($to)
|
||||
switch (range.parent.type) {
|
||||
case type:
|
||||
case schema.nodes.list_item:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
case type:
|
||||
case schema.nodes.list_item:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
menu.items = i;
|
||||
return menu;
|
||||
menu.items = i
|
||||
return menu
|
||||
}
|
||||
|
||||
function marks( schema ) {
|
||||
function marks (schema) {
|
||||
let menu = {
|
||||
name : "marks",
|
||||
};
|
||||
let type, i = {};
|
||||
name: 'marks',
|
||||
}
|
||||
let type; let i = {}
|
||||
|
||||
if ((type = schema.marks.strong))
|
||||
i.toggleStrong = {run: toggleMark(type), dom: icon("b"), type};
|
||||
if ((type = schema.marks.em))
|
||||
i.toggleEm = {run: toggleMark(type), dom: icon("i"), type};
|
||||
if ((type = schema.marks.link))
|
||||
i.toggleLink = linkItem(type);
|
||||
if ((type = schema.marks.strong)) i.toggleStrong = {run: toggleMark(type), dom: icon('b'), type}
|
||||
if ((type = schema.marks.em)) i.toggleEm = {run: toggleMark(type), dom: icon('i'), type}
|
||||
if ((type = schema.marks.link)) i.toggleLink = linkItem(type)
|
||||
|
||||
menu.items = i;
|
||||
return menu
|
||||
menu.items = i
|
||||
return menu
|
||||
}
|
||||
|
||||
export { Menu, blocks, marks };
|
||||
export { Menu, blocks, marks }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Schema} from "prosemirror-model"
|
||||
import {Schema} from 'prosemirror-model'
|
||||
|
||||
// :: Schema
|
||||
// This schema roughly corresponds to the document schema used by
|
||||
|
@ -10,124 +10,125 @@ import {Schema} from "prosemirror-model"
|
|||
// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
|
||||
|
||||
// BASIC
|
||||
const basicSchema = {
|
||||
nodes : {
|
||||
const basicSchemaContent = {
|
||||
nodes: {
|
||||
// :: NodeSpec The top level document node.
|
||||
doc: {
|
||||
content: "block+"
|
||||
content: 'block+',
|
||||
},
|
||||
// :: NodeSpec A plain paragraph textblock. Represented in the DOM
|
||||
// as a `<p>` element.
|
||||
paragraph: {
|
||||
content: "inline*",
|
||||
group: "block",
|
||||
parseDOM: [{tag: "p"}],
|
||||
toDOM() { return ["p", 0] }
|
||||
content: 'inline*',
|
||||
group: 'block',
|
||||
parseDOM: [{tag: 'p'}],
|
||||
toDOM () { return ['p', 0] },
|
||||
},
|
||||
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
|
||||
hard_break: {
|
||||
hardBreak: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
group: 'inline',
|
||||
selectable: false,
|
||||
parseDOM: [{tag: "br"}],
|
||||
toDOM() { return ["br"] }
|
||||
parseDOM: [{tag: 'br'}],
|
||||
toDOM () { return ['br'] },
|
||||
},
|
||||
// :: NodeSpec The text node.
|
||||
text: {
|
||||
group: "inline"
|
||||
group: 'inline',
|
||||
},
|
||||
},
|
||||
marks : {
|
||||
marks: {
|
||||
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
|
||||
// Has parse rules that also match `<i>` and `font-style: italic`.
|
||||
em: {
|
||||
parseDOM: [{tag: "i"}, {tag: "em"}, {style: "font-style=italic"}],
|
||||
toDOM() { return ["em", 0] }
|
||||
parseDOM: [{tag: 'i'}, {tag: 'em'}, {style: 'font-style=italic'}],
|
||||
toDOM () { return ['em', 0] },
|
||||
},
|
||||
// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
|
||||
// also match `<b>` and `font-weight: bold`.
|
||||
strong: {
|
||||
parseDOM: [{tag: "strong"},
|
||||
// This works around a Google Docs misbehavior where
|
||||
// pasted content will be inexplicably wrapped in `<b>`
|
||||
// tags with a font-weight normal.
|
||||
{tag: "b", getAttrs: node => node.style.fontWeight !== "normal" && null},
|
||||
{style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}],
|
||||
toDOM() { return ["strong", 0] }
|
||||
}
|
||||
}
|
||||
parseDOM: [{tag: 'strong'},
|
||||
// This works around a Google Docs misbehavior where
|
||||
// pasted content will be inexplicably wrapped in `<b>`
|
||||
// tags with a font-weight normal.
|
||||
{tag: 'b', getAttrs: node => node.style.fontWeight !== 'normal' && null},
|
||||
{style: 'font-weight', getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}],
|
||||
toDOM () { return ['strong', 0] },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// HEADERS
|
||||
const headersSchema = {
|
||||
const headersSchemaContent = {
|
||||
nodes: {
|
||||
// :: NodeSpec A heading textblock, with a `level` attribute that
|
||||
// should hold the number 1 to 6. Parsed and serialized as `<h1>` to
|
||||
// `<h6>` elements.
|
||||
heading: {
|
||||
attrs: {level: {default: 1}},
|
||||
content: "inline*",
|
||||
marks: "em",
|
||||
group: "block",
|
||||
content: 'inline*',
|
||||
marks: 'em',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
parseDOM: [{tag: "h1", attrs: {level: 1}},
|
||||
{tag: "h2", attrs: {level: 2}},
|
||||
{tag: "h3", attrs: {level: 3}},
|
||||
{tag: "h4", attrs: {level: 4}},
|
||||
{tag: "h5", attrs: {level: 5}},
|
||||
{tag: "h6", attrs: {level: 6}}],
|
||||
toDOM(node) { return ["h" + node.attrs.level, 0] }
|
||||
}
|
||||
}
|
||||
parseDOM: [{tag: 'h1', attrs: {level: 1}},
|
||||
{tag: 'h2', attrs: {level: 2}},
|
||||
{tag: 'h3', attrs: {level: 3}},
|
||||
{tag: 'h4', attrs: {level: 4}},
|
||||
{tag: 'h5', attrs: {level: 5}},
|
||||
{tag: 'h6', attrs: {level: 6}}],
|
||||
toDOM (node) { return ['h' + node.attrs.level, 0] },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// LISTS
|
||||
const listSchema = {
|
||||
const listSchemaContent = {
|
||||
nodes: {
|
||||
// A bullet list node spec, represented in the DOM as `<ul>`.
|
||||
bullet_list: {
|
||||
content: "list_item+",
|
||||
group: "block",
|
||||
parseDOM: [{tag: "ul"}],
|
||||
toDOM() { return ["ul", 0] }
|
||||
bulletList: {
|
||||
content: 'listItem+',
|
||||
group: 'block',
|
||||
parseDOM: [{tag: 'ul'}],
|
||||
toDOM () { return ['ul', 0] },
|
||||
},
|
||||
// A list item (`<li>`) spec.
|
||||
list_item: {
|
||||
content: "paragraph+",
|
||||
parseDOM: [{tag: "li"}],
|
||||
toDOM() { return ["li", 0] },
|
||||
defining: true
|
||||
}
|
||||
}
|
||||
listItem: {
|
||||
content: 'paragraph+',
|
||||
parseDOM: [{tag: 'li'}],
|
||||
toDOM () { return ['li', 0] },
|
||||
defining: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
const linkSchema = { // eslint-disable-line no-unused-vars
|
||||
const linkSchemaContent = { // eslint-disable-line no-unused-vars
|
||||
marks: {
|
||||
link: {
|
||||
attrs: {
|
||||
href: {},
|
||||
title: {default: null}
|
||||
title: {default: null},
|
||||
},
|
||||
inclusive: false,
|
||||
parseDOM: [{tag: "a[href]", getAttrs(dom) {
|
||||
return {href: dom.getAttribute("href"), title: dom.getAttribute("title")}
|
||||
}}],
|
||||
toDOM(node) { let {href, title} = node.attrs; return ["a", {href, title}, 0] }
|
||||
parseDOM: [{tag: 'a[href]',
|
||||
getAttrs (dom) {
|
||||
return {href: dom.getAttribute('href'), title: dom.getAttribute('title')}
|
||||
}}],
|
||||
toDOM (node) { let {href, title} = node.attrs; return ['a', {href, title}, 0] },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const basic_schema = new Schema(basicSchema);
|
||||
const basicSchema = new Schema(basicSchemaContent)
|
||||
|
||||
const headers_schema = new Schema({
|
||||
nodes: basic_schema.spec.nodes.append(headersSchema.nodes),
|
||||
marks: basic_schema.spec.marks
|
||||
});
|
||||
const headersSchema = new Schema({
|
||||
nodes: basicSchema.spec.nodes.append(headersSchemaContent.nodes),
|
||||
marks: basicSchema.spec.marks,
|
||||
})
|
||||
|
||||
const list_schema = new Schema({
|
||||
nodes: headers_schema.spec.nodes.append(listSchema.nodes),
|
||||
marks: headers_schema.spec.marks
|
||||
});
|
||||
const listSchema = new Schema({
|
||||
nodes: headersSchema.spec.nodes.append(listSchemaContent.nodes),
|
||||
marks: headersSchema.spec.marks,
|
||||
})
|
||||
|
||||
const full_schema = list_schema;
|
||||
const fullSchema = listSchema
|
||||
|
||||
export { basic_schema, headers_schema, list_schema, full_schema };
|
||||
export { basicSchema, headersSchema, listSchema, fullSchema }
|
||||
|
|
135
src-js/godo.js
135
src-js/godo.js
|
@ -1,101 +1,100 @@
|
|||
import {EditorState, Plugin} from "prosemirror-state";
|
||||
import {EditorView} from "prosemirror-view";
|
||||
import {history} from "prosemirror-history";
|
||||
import {keymap} from "prosemirror-keymap";
|
||||
import {baseKeymap} from "prosemirror-commands";
|
||||
import {DOMParser, DOMSerializer} from "prosemirror-model";
|
||||
import {EditorState, Plugin} from 'prosemirror-state'
|
||||
import {EditorView} from 'prosemirror-view'
|
||||
import {history} from 'prosemirror-history'
|
||||
import {keymap} from 'prosemirror-keymap'
|
||||
import {baseKeymap} from 'prosemirror-commands'
|
||||
import {DOMParser, DOMSerializer} from 'prosemirror-model'
|
||||
|
||||
import {buildKeymap} from "./godo-additional-keymap.mjs";
|
||||
import {basic_schema, headers_schema, list_schema, full_schema} from "./godo-schemas.mjs";
|
||||
import {Menu, blocks, marks} from "./godo-menus.mjs";
|
||||
import {buildKeymap} from './godo-additional-keymap.mjs'
|
||||
import {basicSchema, headersSchema, listSchema, fullSchema} from './godo-schemas.mjs'
|
||||
import {Menu, blocks, marks} from './godo-menus.mjs'
|
||||
|
||||
function menuPlugin(menu) {
|
||||
function menuPlugin (menu) {
|
||||
return new Plugin({
|
||||
view(editorView) {
|
||||
let menuView = new Menu(menu, editorView);
|
||||
editorView.dom.parentNode.insertBefore(menuView.el, editorView.dom);
|
||||
return menuView;
|
||||
}
|
||||
});
|
||||
view (editorView) {
|
||||
let menuView = new Menu(menu, editorView)
|
||||
editorView.dom.parentNode.insertBefore(menuView.el, editorView.dom)
|
||||
return menuView
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default class Godo {
|
||||
|
||||
constructor (textarea, options) {
|
||||
this.textarea = textarea;
|
||||
this.textarea.hidden = true;
|
||||
this.textarea.style.display = "none";
|
||||
let displayBlocksMenu = true;
|
||||
this.textarea = textarea
|
||||
this.textarea.hidden = true
|
||||
this.textarea.style.display = 'none'
|
||||
let displayBlocksMenu = true
|
||||
|
||||
this.default_options = {
|
||||
schema: "full",
|
||||
heading_levels: [3, 4]
|
||||
this.defaultOptions = {
|
||||
schema: 'full',
|
||||
headingLevels: [3, 4],
|
||||
}
|
||||
this.options = Object.assign(this.default_options, options);
|
||||
this.options.heading_levels = this.options.heading_levels.slice(0, 3);
|
||||
this.options = Object.assign(this.defaultOptions, options)
|
||||
this.options.headingLevels = this.options.headingLevels.slice(0, 3)
|
||||
|
||||
this.schema = (() => {
|
||||
switch(this.options.schema) {
|
||||
case 'basic':
|
||||
displayBlocksMenu = false;
|
||||
return basic_schema;
|
||||
case 'headers':
|
||||
return headers_schema;
|
||||
case 'list':
|
||||
return list_schema
|
||||
case 'full':
|
||||
default:
|
||||
return full_schema
|
||||
switch (this.options.schema) {
|
||||
case 'basic':
|
||||
displayBlocksMenu = false
|
||||
return basicSchema
|
||||
case 'headers':
|
||||
return headersSchema
|
||||
case 'list':
|
||||
return listSchema
|
||||
case 'full':
|
||||
default:
|
||||
return fullSchema
|
||||
}
|
||||
})();
|
||||
})()
|
||||
|
||||
this.editor_wrapper = document.createElement('div');
|
||||
this.editor_wrapper.className = "godo";
|
||||
textarea.insertAdjacentElement('afterend', this.editor_wrapper);
|
||||
this.editorWrapper = document.createElement('div')
|
||||
this.editorWrapper.className = 'godo'
|
||||
textarea.insertAdjacentElement('afterend', this.editorWrapper)
|
||||
|
||||
this.marksMenu = menuPlugin(marks(this.schema));
|
||||
this.blocksMenu = menuPlugin(blocks(this.schema, this.options.heading_levels));
|
||||
this.marksMenu = menuPlugin(marks(this.schema))
|
||||
this.blocksMenu = menuPlugin(blocks(this.schema, this.options.headingLevels))
|
||||
|
||||
let plugins_list = [
|
||||
history(),
|
||||
keymap(buildKeymap(this.schema, null, this.options)),
|
||||
keymap(baseKeymap),
|
||||
this.marksMenu
|
||||
];
|
||||
let pluginsList = [
|
||||
history(),
|
||||
keymap(buildKeymap(this.schema, null, this.options)),
|
||||
keymap(baseKeymap),
|
||||
this.marksMenu,
|
||||
]
|
||||
|
||||
if (displayBlocksMenu && !textarea.readOnly) plugins_list.push(this.blocksMenu);
|
||||
if (displayBlocksMenu && !textarea.readOnly) pluginsList.push(this.blocksMenu)
|
||||
|
||||
this.state = EditorState.create({
|
||||
schema: this.schema,
|
||||
doc: DOMParser.fromSchema(this.schema).parse(this.get_textarea_content()),
|
||||
plugins: plugins_list
|
||||
});
|
||||
doc: DOMParser.fromSchema(this.schema).parse(this.getTextAreaContent()),
|
||||
plugins: pluginsList,
|
||||
})
|
||||
|
||||
this.view = new EditorView(this.editor_wrapper, {
|
||||
this.view = new EditorView(this.editorWrapper, {
|
||||
state: this.state,
|
||||
editable: () => !textarea.readOnly,
|
||||
attributes: {
|
||||
class: "godo--editor"
|
||||
}
|
||||
});
|
||||
class: 'godo--editor',
|
||||
},
|
||||
})
|
||||
|
||||
this.textarea.form.addEventListener('submit', () => {
|
||||
textarea.value = this.getHTML();
|
||||
});
|
||||
textarea.value = this.getHTML()
|
||||
})
|
||||
}
|
||||
|
||||
get_textarea_content() {
|
||||
const w = document.createElement('div');
|
||||
w.innerHTML = this.textarea.value;
|
||||
return w;
|
||||
getTextAreaContent () {
|
||||
const w = document.createElement('div')
|
||||
w.innerHTML = this.textarea.value
|
||||
return w
|
||||
}
|
||||
|
||||
getHTML() {
|
||||
const div = document.createElement('div');
|
||||
getHTML () {
|
||||
const div = document.createElement('div')
|
||||
const fragment = DOMSerializer
|
||||
.fromSchema(this.schema)
|
||||
.serializeFragment(this.view.state.doc.content);
|
||||
div.appendChild(fragment);
|
||||
return div.innerHTML;
|
||||
.serializeFragment(this.view.state.doc.content)
|
||||
div.appendChild(fragment)
|
||||
return div.innerHTML
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue