5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-07-03 13:26:07 +00:00

prettier + tachado

This commit is contained in:
void 2020-03-21 12:46:48 -03:00
parent d00ab183ba
commit d1cf673cc9
5 changed files with 246 additions and 159 deletions

View file

@ -7,7 +7,6 @@
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb // layout file, like app/views/layouts/application.html.erb
// Uncomment to copy all static images under ../images to the output folder and reference // Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below. // or the `imagePath` JavaScript helper below.
@ -15,78 +14,96 @@
// const images = require.context('../images', true) // const images = require.context('../images', true)
// const imagePath = (name) => images(name, true) // const imagePath = (name) => images(name, true)
import tableDragger from "table-dragger" import tableDragger from 'table-dragger'
import {EditorState} from "prosemirror-state" import { EditorState } from 'prosemirror-state'
import {EditorView} from "prosemirror-view" import { EditorView } from 'prosemirror-view'
import {Schema, DOMParser} from "prosemirror-model" import { Schema, DOMParser } from 'prosemirror-model'
import {keymap} from "prosemirror-keymap" import { keymap } from 'prosemirror-keymap'
// seleccionar cosas que no son texto // seleccionar cosas que no son texto
import {gapCursor} from "prosemirror-gapcursor" import { gapCursor } from 'prosemirror-gapcursor'
import "prosemirror-gapcursor/style/gapcursor.css" import 'prosemirror-gapcursor/style/gapcursor.css'
// convertir cosas como ">" a una quote: https://prosemirror.net/docs/ref/#inputrules // convertir cosas como ">" a una quote: https://prosemirror.net/docs/ref/#inputrules
import {inputRules, wrappingInputRule, textblockTypeInputRule, import {
emDash, ellipsis} from "prosemirror-inputrules" inputRules,
wrappingInputRule,
textblockTypeInputRule,
emDash,
ellipsis,
} from 'prosemirror-inputrules'
import serializer from './serializer.js' import serializer from './serializer.js'
import parser from './deserializer.js' import parser from './deserializer.js'
import schema from './schema.js' import schema from './schema.js'
import {toggleMark, setBlockType, wrapIn, baseKeymap} from "prosemirror-commands" import {
import {toggleWrap, toggleBlockType, toggleList, toggleMark,
updateMark, removeMark} from "tiptap-commands" setBlockType,
wrapIn,
baseKeymap,
} from 'prosemirror-commands'
import {
toggleWrap,
toggleBlockType,
toggleList,
updateMark,
removeMark,
} from 'tiptap-commands'
import {startImageUpload, placeholderPlugin} from "./imageUpload" import { startImageUpload, placeholderPlugin } from './imageUpload'
const toggleBold = toggleMark(schema.marks.strong) const toggleBold = toggleMark(schema.marks.strong)
const toggleItalic = toggleMark(schema.marks.em) const toggleItalic = toggleMark(schema.marks.em)
const toggleHighlight = toggleMark(schema.marks.mark) const toggleHighlight = toggleMark(schema.marks.mark)
const toggleStrikethrough = toggleMark(schema.marks.strikethrough)
const wrapInQuote = toggleWrap(schema.nodes.blockquote) const wrapInQuote = toggleWrap(schema.nodes.blockquote)
const toggleUl = toggleList(schema.nodes.bullet_list, schema.nodes.list_item) const toggleUl = toggleList(schema.nodes.bullet_list, schema.nodes.list_item)
const toggleOl = toggleList(schema.nodes.ordered_list, schema.nodes.list_item) const toggleOl = toggleList(schema.nodes.ordered_list, schema.nodes.list_item)
const generateHeadingToggle = level => const generateHeadingToggle = level =>
toggleBlockType( toggleBlockType(schema.nodes.heading, schema.nodes.paragraph, {
schema.nodes.heading, level: level,
schema.nodes.paragraph, })
{ level: level },
)
const toggleH1 = generateHeadingToggle(1) const toggleH1 = generateHeadingToggle(1)
const toggleH2 = generateHeadingToggle(2) const toggleH2 = generateHeadingToggle(2)
const makeLink = (state, dispatch) => { const makeLink = (state, dispatch) => {
const {tr, selection, doc} = state const { tr, selection, doc } = state
const type = schema.marks.link const type = schema.marks.link
const {from, to} = selection const { from, to } = selection
const has = doc.rangeHasMark(from, to, type) const has = doc.rangeHasMark(from, to, type)
if (has) { if (has) {
tr.removeMark(from, to, type) tr.removeMark(from, to, type)
} else { } else {
tr.addMark(from, to, type.create({ tr.addMark(
href: window.prompt('elegir url', '//sutty.nl') from,
})) to,
type.create({
href: window.prompt('elegir url', '//sutty.nl'),
}),
)
} }
return dispatch(tr) return dispatch(tr)
} }
class SuttyEditor { class SuttyEditor {
constructor ({element, markdown, toolbar}) { constructor({ element, markdown, toolbar }) {
this.state = this.buildState(markdown) this.state = this.buildState(markdown)
this.view = this.buildView(element, this.state) this.view = this.buildView(element, this.state)
this.hooks(toolbar) this.hooks(toolbar)
} }
runCommand (command) { runCommand(command) {
return command(this.view.state, this.view.dispatch, this.view) return command(this.view.state, this.view.dispatch, this.view)
} }
hooks (el) { hooks(el) {
const setupBtn = (class_, action) => { const setupBtn = (class_, action) => {
const btnEl = el.querySelector('.' + class_ + '-btn') const btnEl = el.querySelector('.' + class_ + '-btn')
btnEl.type = 'button' btnEl.type = 'button'
@ -98,6 +115,7 @@ class SuttyEditor {
setupBtn('bold', () => this.runCommand(toggleBold)) setupBtn('bold', () => this.runCommand(toggleBold))
setupBtn('italic', () => this.runCommand(toggleItalic)) setupBtn('italic', () => this.runCommand(toggleItalic))
setupBtn('highlight', () => this.runCommand(toggleHighlight)) setupBtn('highlight', () => this.runCommand(toggleHighlight))
setupBtn('strikethrough', () => this.runCommand(toggleStrikethrough))
setupBtn('quote', () => this.runCommand(wrapInQuote)) setupBtn('quote', () => this.runCommand(wrapInQuote))
setupBtn('link', () => this.runCommand(makeLink)) setupBtn('link', () => this.runCommand(makeLink))
setupBtn('ul', () => this.runCommand(toggleUl)) setupBtn('ul', () => this.runCommand(toggleUl))
@ -108,8 +126,8 @@ class SuttyEditor {
const imgInput = el.querySelector('.img-input') const imgInput = el.querySelector('.img-input')
imgInput.addEventListener('change', event => { imgInput.addEventListener('change', event => {
if ( if (
this.view.state.selection.$from.parent.inlineContent this.view.state.selection.$from.parent.inlineContent &&
&& event.target.files.length event.target.files.length
) { ) {
const file = event.target.files[0] const file = event.target.files[0]
event.target.value = '' event.target.value = ''
@ -119,12 +137,14 @@ class SuttyEditor {
}) })
} }
buildState (markdown) { buildState(markdown) {
return EditorState.create({ return EditorState.create({
schema, schema,
...(markdown ? { ...(markdown
doc: parser.parse(markdown) ? {
} : {}), doc: parser.parse(markdown),
}
: {}),
plugins: [ plugins: [
gapCursor(), gapCursor(),
keymap(baseKeymap), keymap(baseKeymap),
@ -133,18 +153,17 @@ class SuttyEditor {
rules: [ rules: [
//emDash, // -- => — //emDash, // -- => —
//ellipsis, // ... => … //ellipsis, // ... => …
], ],
}), }),
], ],
}) })
} }
buildView (element, state) { buildView(element, state) {
return new EditorView(element, {state}) return new EditorView(element, { state })
} }
getMarkdown () { getMarkdown() {
return serializer.serialize(this.view.state.doc) return serializer.serialize(this.view.state.doc)
} }
} }
@ -155,35 +174,39 @@ document.addEventListener('turbolinks:load', e => {
for (const el of editorEls) { for (const el of editorEls) {
// conseguir textarea con el markdown // conseguir textarea con el markdown
const textareaEl = document.querySelector( const textareaEl = document.querySelector(
`.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]` `.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`,
) )
// conseguir toolbar // conseguir toolbar
const toolbarEl = document.querySelector( const toolbarEl = document.querySelector(
`.sutty-editor-toolbar[data-sutty-identifier="${el.dataset.suttyIdentifier}"]` `.sutty-editor-toolbar[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`,
) )
// ocultarlo (no se oculta originalmente para usuaries noscript) // ocultarlo (no se oculta originalmente para usuaries noscript)
textareaEl.style.display = 'none' textareaEl.style.display = 'none'
// crear editor con el markdown del textarea // crear editor con el markdown del textarea
const editor = new SuttyEditor({ const editor = new SuttyEditor({
element: el, element: el,
markdown: textareaEl.value, markdown: textareaEl.value,
toolbar: toolbarEl, toolbar: toolbarEl,
}) })
// hookear a la form para inyectar el contenido del editor al textarea oculto para que se mande // hookear a la form para inyectar el contenido del editor al textarea oculto para que se mande
textareaEl.form.addEventListener('submit', e => { textareaEl.form.addEventListener(
console.log('enviando formulario: inyectando markdown en textarea...') 'submit',
try { e => {
textareaEl.value = editor.getMarkdown() console.log('enviando formulario: inyectando markdown en textarea...')
} catch (error) { try {
console.error('no se pudo inyectar markdown!', error) textareaEl.value = editor.getMarkdown()
// TODO: mostrar esto bien } catch (error) {
e.preventDefault() console.error('no se pudo inyectar markdown!', error)
} // TODO: mostrar esto bien
}, true) e.preventDefault()
}
},
true,
)
} }
} catch (error) { } catch (error) {
console.error('algo falló en la carga del editor', error) console.error('algo falló en la carga del editor', error)
@ -199,10 +222,11 @@ document.addEventListener('turbolinks:load', e => {
dragHandler: '.handle', dragHandler: '.handle',
}).on('drop', (from, to, el, mode) => { }).on('drop', (from, to, el, mode) => {
Array.from(document.querySelectorAll('.reorder')) Array.from(document.querySelectorAll('.reorder'))
.reverse() .reverse()
.map((o,i) => o.value = i) .map((o, i) => (o.value = i))
Array.from(document.querySelectorAll('.submit-reorder')) Array.from(document.querySelectorAll('.submit-reorder')).map(s =>
.map(s => s.classList.remove('d-none')) s.classList.remove('d-none'),
)
}) })
}) })

View file

@ -1,37 +1,55 @@
// From https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js // From https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js
import {MarkdownParser} from 'prosemirror-markdown' import { MarkdownParser } from 'prosemirror-markdown'
import markdownit from "markdown-it" import markdownit from 'markdown-it'
import markdownitMark from "markdown-it-mark" import markdownitMark from 'markdown-it-mark'
import schema from './schema.js' import schema from './schema.js'
export default new MarkdownParser( export default new MarkdownParser(
schema, schema,
markdownit("commonmark", {html: false}).use(markdownitMark), markdownit('commonmark', { html: false })
.use(markdownitMark)
.enable('strikethrough'),
{ {
blockquote: {block: "blockquote"}, blockquote: { block: 'blockquote' },
paragraph: {block: "paragraph"}, paragraph: { block: 'paragraph' },
list_item: {block: "list_item"}, list_item: { block: 'list_item' },
bullet_list: {block: "bullet_list"}, bullet_list: { block: 'bullet_list' },
ordered_list: {block: "ordered_list", getAttrs: tok => ({order: +tok.attrGet("start") || 1})}, ordered_list: {
heading: {block: "heading", getAttrs: tok => ({level: +tok.tag.slice(1)})}, block: 'ordered_list',
code_block: {block: "code_block"}, getAttrs: tok => ({ order: +tok.attrGet('start') || 1 }),
fence: {block: "code_block", getAttrs: tok => ({params: tok.info || ""})}, },
hr: {node: "horizontal_rule"}, heading: {
image: {node: "image", getAttrs: tok => ({ block: 'heading',
src: tok.attrGet("src"), getAttrs: tok => ({ level: +tok.tag.slice(1) }),
title: tok.attrGet("title") || null, },
alt: tok.children[0] && tok.children[0].content || null, code_block: { block: 'code_block' },
})}, fence: {
hardbreak: {node: "hard_break"}, block: 'code_block',
getAttrs: tok => ({ params: tok.info || '' }),
},
hr: { node: 'horizontal_rule' },
image: {
node: 'image',
getAttrs: tok => ({
src: tok.attrGet('src'),
title: tok.attrGet('title') || null,
alt: (tok.children[0] && tok.children[0].content) || null,
}),
},
hardbreak: { node: 'hard_break' },
em: {mark: "em"}, em: { mark: 'em' },
strong: {mark: "strong"}, strong: { mark: 'strong' },
mark: {mark: "mark"}, mark: { mark: 'mark' },
link: {mark: "link", getAttrs: tok => ({ s: { mark: 'strikethrough' },
href: tok.attrGet("href"), link: {
title: tok.attrGet("title") || null mark: 'link',
})}, getAttrs: tok => ({
code_inline: {mark: "code"}, href: tok.attrGet('href'),
title: tok.attrGet('title') || null,
}),
},
code_inline: { mark: 'code' },
}, },
) )

View file

@ -1,11 +1,15 @@
import {schema as defaultMarkdownSchema, defaultMarkdownParser} from "prosemirror-markdown" import { schema as defaultMarkdownSchema } from 'prosemirror-markdown'
import {Schema} from "prosemirror-model" import { Schema } from 'prosemirror-model'
export default new Schema({ export default new Schema({
nodes: defaultMarkdownSchema.spec.nodes, nodes: defaultMarkdownSchema.spec.nodes,
marks: defaultMarkdownSchema.spec.marks.addBefore('text', 'mark', { marks: defaultMarkdownSchema.spec.marks
toDOM: node => ['mark'], .addBefore('text', 'mark', {
parseDOM: [{ tag: 'mark' }], toDOM: node => ['mark'],
}), parseDOM: [{ tag: 'mark' }],
})
.addBefore('text', 'strikethrough', {
toDOM: node => ['s'],
parseDOM: [{ tag: 's' }],
}),
}) })

View file

@ -1,74 +1,114 @@
// From https://raw.githubusercontent.com/ProseMirror/prosemirror-markdown/master/src/to_markdown.js // From https://raw.githubusercontent.com/ProseMirror/prosemirror-markdown/master/src/to_markdown.js
import {MarkdownSerializer} from "prosemirror-markdown" import { MarkdownSerializer } from 'prosemirror-markdown'
export default new MarkdownSerializer({ export default new MarkdownSerializer(
blockquote(state, node) { {
state.wrapBlock("> ", null, node, () => state.renderContent(node)) blockquote(state, node) {
}, state.wrapBlock('> ', null, node, () => state.renderContent(node))
code_block(state, node) { },
state.write("```" + (node.attrs.params || "") + "\n") code_block(state, node) {
state.text(node.textContent, false) state.write('```' + (node.attrs.params || '') + '\n')
state.ensureNewLine() state.text(node.textContent, false)
state.write("```") state.ensureNewLine()
state.closeBlock(node) state.write('```')
}, state.closeBlock(node)
heading(state, node) { },
state.write(state.repeat("#", node.attrs.level) + " ") heading(state, node) {
state.renderInline(node) state.write(state.repeat('#', node.attrs.level) + ' ')
state.closeBlock(node) state.renderInline(node)
}, state.closeBlock(node)
horizontal_rule(state, node) { },
state.write(node.attrs.markup || "---") horizontal_rule(state, node) {
state.closeBlock(node) state.write(node.attrs.markup || '---')
}, state.closeBlock(node)
bullet_list(state, node) { },
state.renderList(node, " ", () => (node.attrs.bullet || "*") + " ") bullet_list(state, node) {
}, state.renderList(node, ' ', () => (node.attrs.bullet || '*') + ' ')
ordered_list(state, node) { },
let start = node.attrs.order || 1 ordered_list(state, node) {
let maxW = String(start + node.childCount - 1).length let start = node.attrs.order || 1
let space = state.repeat(" ", maxW + 2) let maxW = String(start + node.childCount - 1).length
state.renderList(node, space, i => { let space = state.repeat(' ', maxW + 2)
let nStr = String(start + i) state.renderList(node, space, i => {
return state.repeat(" ", maxW - nStr.length) + nStr + ". " let nStr = String(start + i)
}) return state.repeat(' ', maxW - nStr.length) + nStr + '. '
}, })
list_item(state, node) { },
state.renderContent(node) list_item(state, node) {
}, state.renderContent(node)
paragraph(state, node) { },
state.renderInline(node) paragraph(state, node) {
state.closeBlock(node) state.renderInline(node)
}, state.closeBlock(node)
},
image(state, node) {
state.write("![" + state.esc(node.attrs.alt || "") + "](" + state.esc(node.attrs.src) + image(state, node) {
(node.attrs.title ? " " + state.quote(node.attrs.title) : "") + ")") state.write(
}, '![' +
hard_break(state, node, parent, index) { state.esc(node.attrs.alt || '') +
for (let i = index + 1; i < parent.childCount; i++) '](' +
if (parent.child(i).type != node.type) { state.esc(node.attrs.src) +
state.write("\\\n") (node.attrs.title ? ' ' + state.quote(node.attrs.title) : '') +
return ')',
} )
}, },
text(state, node) { hard_break(state, node, parent, index) {
state.text(node.text) for (let i = index + 1; i < parent.childCount; i++)
} if (parent.child(i).type != node.type) {
}, { state.write('\\\n')
em: {open: "*", close: "*", mixable: true, expelEnclosingWhitespace: true}, return
strong: {open: "**", close: "**", mixable: true, expelEnclosingWhitespace: true}, }
mark: {open: "==", close: "==", mixable: true, expelEnclosingWhitespace: true}, },
link: { text(state, node) {
open(_state, mark, parent, index) { state.text(node.text)
return isPlainURL(mark, parent, index, 1) ? "<" : "["
}, },
close(state, mark, parent, index) {
return isPlainURL(mark, parent, index, -1) ? ">"
: "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"
}
}, },
code: {open(_state, _mark, parent, index) { return backticksFor(parent.child(index), -1) }, {
close(_state, _mark, parent, index) { return backticksFor(parent.child(index - 1), 1) }, em: {
escape: false} open: '*',
}) close: '*',
mixable: true,
expelEnclosingWhitespace: true,
},
strong: {
open: '**',
close: '**',
mixable: true,
expelEnclosingWhitespace: true,
},
mark: {
open: '==',
close: '==',
mixable: true,
expelEnclosingWhitespace: true,
},
strikethrough: {
open: '~~',
close: '~~',
mixable: true,
expelEnclosingWhitespace: true,
},
link: {
open(_state, mark, parent, index) {
return isPlainURL(mark, parent, index, 1) ? '<' : '['
},
close(state, mark, parent, index) {
return isPlainURL(mark, parent, index, -1)
? '>'
: '](' +
state.esc(mark.attrs.href) +
(mark.attrs.title ? ' ' + state.quote(mark.attrs.title) : '') +
')'
},
},
code: {
open(_state, _mark, parent, index) {
return backticksFor(parent.child(index), -1)
},
close(_state, _mark, parent, index) {
return backticksFor(parent.child(index - 1), 1)
},
escape: false,
},
},
)

View file

@ -3,6 +3,7 @@
%button.bold-btn= "bold" %button.bold-btn= "bold"
%button.italic-btn= "italic" %button.italic-btn= "italic"
%button.highlight-btn= "highlight" %button.highlight-btn= "highlight"
%button.strikethrough-btn= "strikethrough"
%button.quote-btn= "quote" %button.quote-btn= "quote"
%button.ul-btn= "ul" %button.ul-btn= "ul"
%button.ol-btn= "ol" %button.ol-btn= "ol"