From d1cf673cc99b2dd462bfdf5b7fa90a20edaa7815 Mon Sep 17 00:00:00 2001 From: void Date: Sat, 21 Mar 2020 12:46:48 -0300 Subject: [PATCH] prettier + tachado --- app/javascript/packs/application.js | 134 ++++++++++++-------- app/javascript/packs/deserializer.js | 72 +++++++---- app/javascript/packs/schema.js | 18 +-- app/javascript/packs/serializer.js | 180 ++++++++++++++++----------- app/views/application/_editor.haml | 1 + 5 files changed, 246 insertions(+), 159 deletions(-) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index b4fcc3a7..bf2a07ed 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -7,7 +7,6 @@ // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate // layout file, like app/views/layouts/application.html.erb - // 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' %>) // or the `imagePath` JavaScript helper below. @@ -15,78 +14,96 @@ // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) -import tableDragger from "table-dragger" +import tableDragger from 'table-dragger' -import {EditorState} from "prosemirror-state" -import {EditorView} from "prosemirror-view" -import {Schema, DOMParser} from "prosemirror-model" -import {keymap} from "prosemirror-keymap" +import { EditorState } from 'prosemirror-state' +import { EditorView } from 'prosemirror-view' +import { Schema, DOMParser } from 'prosemirror-model' +import { keymap } from 'prosemirror-keymap' // seleccionar cosas que no son texto -import {gapCursor} from "prosemirror-gapcursor" -import "prosemirror-gapcursor/style/gapcursor.css" +import { gapCursor } from 'prosemirror-gapcursor' +import 'prosemirror-gapcursor/style/gapcursor.css' // convertir cosas como ">" a una quote: https://prosemirror.net/docs/ref/#inputrules -import {inputRules, wrappingInputRule, textblockTypeInputRule, - emDash, ellipsis} from "prosemirror-inputrules" +import { + inputRules, + wrappingInputRule, + textblockTypeInputRule, + emDash, + ellipsis, +} from 'prosemirror-inputrules' import serializer from './serializer.js' import parser from './deserializer.js' import schema from './schema.js' -import {toggleMark, setBlockType, wrapIn, baseKeymap} from "prosemirror-commands" -import {toggleWrap, toggleBlockType, toggleList, - updateMark, removeMark} from "tiptap-commands" +import { + toggleMark, + 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 toggleItalic = toggleMark(schema.marks.em) const toggleHighlight = toggleMark(schema.marks.mark) +const toggleStrikethrough = toggleMark(schema.marks.strikethrough) const wrapInQuote = toggleWrap(schema.nodes.blockquote) const toggleUl = toggleList(schema.nodes.bullet_list, schema.nodes.list_item) const toggleOl = toggleList(schema.nodes.ordered_list, schema.nodes.list_item) const generateHeadingToggle = level => - toggleBlockType( - schema.nodes.heading, - schema.nodes.paragraph, - { level: level }, - ) + toggleBlockType(schema.nodes.heading, schema.nodes.paragraph, { + level: level, + }) const toggleH1 = generateHeadingToggle(1) const toggleH2 = generateHeadingToggle(2) const makeLink = (state, dispatch) => { - const {tr, selection, doc} = state + const { tr, selection, doc } = state const type = schema.marks.link - const {from, to} = selection + const { from, to } = selection const has = doc.rangeHasMark(from, to, type) if (has) { tr.removeMark(from, to, type) } else { - tr.addMark(from, to, type.create({ - href: window.prompt('elegir url', '//sutty.nl') - })) + tr.addMark( + from, + to, + type.create({ + href: window.prompt('elegir url', '//sutty.nl'), + }), + ) } return dispatch(tr) } class SuttyEditor { - constructor ({element, markdown, toolbar}) { + constructor({ element, markdown, toolbar }) { this.state = this.buildState(markdown) this.view = this.buildView(element, this.state) this.hooks(toolbar) } - runCommand (command) { + runCommand(command) { return command(this.view.state, this.view.dispatch, this.view) } - hooks (el) { + hooks(el) { const setupBtn = (class_, action) => { const btnEl = el.querySelector('.' + class_ + '-btn') btnEl.type = 'button' @@ -98,6 +115,7 @@ class SuttyEditor { setupBtn('bold', () => this.runCommand(toggleBold)) setupBtn('italic', () => this.runCommand(toggleItalic)) setupBtn('highlight', () => this.runCommand(toggleHighlight)) + setupBtn('strikethrough', () => this.runCommand(toggleStrikethrough)) setupBtn('quote', () => this.runCommand(wrapInQuote)) setupBtn('link', () => this.runCommand(makeLink)) setupBtn('ul', () => this.runCommand(toggleUl)) @@ -108,8 +126,8 @@ class SuttyEditor { const imgInput = el.querySelector('.img-input') imgInput.addEventListener('change', event => { if ( - this.view.state.selection.$from.parent.inlineContent - && event.target.files.length + this.view.state.selection.$from.parent.inlineContent && + event.target.files.length ) { const file = event.target.files[0] event.target.value = '' @@ -119,12 +137,14 @@ class SuttyEditor { }) } - buildState (markdown) { + buildState(markdown) { return EditorState.create({ schema, - ...(markdown ? { - doc: parser.parse(markdown) - } : {}), + ...(markdown + ? { + doc: parser.parse(markdown), + } + : {}), plugins: [ gapCursor(), keymap(baseKeymap), @@ -133,18 +153,17 @@ class SuttyEditor { rules: [ //emDash, // -- => — //ellipsis, // ... => … - ], }), ], }) } - buildView (element, state) { - return new EditorView(element, {state}) + buildView(element, state) { + return new EditorView(element, { state }) } - getMarkdown () { + getMarkdown() { return serializer.serialize(this.view.state.doc) } } @@ -155,35 +174,39 @@ document.addEventListener('turbolinks:load', e => { for (const el of editorEls) { // conseguir textarea con el markdown const textareaEl = document.querySelector( - `.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]` + `.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`, ) // conseguir toolbar 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) textareaEl.style.display = 'none' - + // crear editor con el markdown del textarea const editor = new SuttyEditor({ element: el, markdown: textareaEl.value, - toolbar: toolbarEl, + toolbar: toolbarEl, }) // hookear a la form para inyectar el contenido del editor al textarea oculto para que se mande - textareaEl.form.addEventListener('submit', e => { - console.log('enviando formulario: inyectando markdown en textarea...') - try { - textareaEl.value = editor.getMarkdown() - } catch (error) { - console.error('no se pudo inyectar markdown!', error) - // TODO: mostrar esto bien - e.preventDefault() - } - }, true) + textareaEl.form.addEventListener( + 'submit', + e => { + console.log('enviando formulario: inyectando markdown en textarea...') + try { + textareaEl.value = editor.getMarkdown() + } catch (error) { + console.error('no se pudo inyectar markdown!', error) + // TODO: mostrar esto bien + e.preventDefault() + } + }, + true, + ) } } catch (error) { console.error('algo falló en la carga del editor', error) @@ -199,10 +222,11 @@ document.addEventListener('turbolinks:load', e => { dragHandler: '.handle', }).on('drop', (from, to, el, mode) => { Array.from(document.querySelectorAll('.reorder')) - .reverse() - .map((o,i) => o.value = i) + .reverse() + .map((o, i) => (o.value = i)) - Array.from(document.querySelectorAll('.submit-reorder')) - .map(s => s.classList.remove('d-none')) + Array.from(document.querySelectorAll('.submit-reorder')).map(s => + s.classList.remove('d-none'), + ) }) }) diff --git a/app/javascript/packs/deserializer.js b/app/javascript/packs/deserializer.js index 73772ccb..2b934555 100644 --- a/app/javascript/packs/deserializer.js +++ b/app/javascript/packs/deserializer.js @@ -1,37 +1,55 @@ // From https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js -import {MarkdownParser} from 'prosemirror-markdown' -import markdownit from "markdown-it" -import markdownitMark from "markdown-it-mark" +import { MarkdownParser } from 'prosemirror-markdown' +import markdownit from 'markdown-it' +import markdownitMark from 'markdown-it-mark' import schema from './schema.js' export default new MarkdownParser( schema, - markdownit("commonmark", {html: false}).use(markdownitMark), + markdownit('commonmark', { html: false }) + .use(markdownitMark) + .enable('strikethrough'), { - blockquote: {block: "blockquote"}, - paragraph: {block: "paragraph"}, - list_item: {block: "list_item"}, - bullet_list: {block: "bullet_list"}, - ordered_list: {block: "ordered_list", getAttrs: tok => ({order: +tok.attrGet("start") || 1})}, - heading: {block: "heading", getAttrs: tok => ({level: +tok.tag.slice(1)})}, - code_block: {block: "code_block"}, - fence: {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"}, + blockquote: { block: 'blockquote' }, + paragraph: { block: 'paragraph' }, + list_item: { block: 'list_item' }, + bullet_list: { block: 'bullet_list' }, + ordered_list: { + block: 'ordered_list', + getAttrs: tok => ({ order: +tok.attrGet('start') || 1 }), + }, + heading: { + block: 'heading', + getAttrs: tok => ({ level: +tok.tag.slice(1) }), + }, + code_block: { block: 'code_block' }, + fence: { + 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"}, - strong: {mark: "strong"}, - mark: {mark: "mark"}, - link: {mark: "link", getAttrs: tok => ({ - href: tok.attrGet("href"), - title: tok.attrGet("title") || null - })}, - code_inline: {mark: "code"}, + em: { mark: 'em' }, + strong: { mark: 'strong' }, + mark: { mark: 'mark' }, + s: { mark: 'strikethrough' }, + link: { + mark: 'link', + getAttrs: tok => ({ + href: tok.attrGet('href'), + title: tok.attrGet('title') || null, + }), + }, + code_inline: { mark: 'code' }, }, ) diff --git a/app/javascript/packs/schema.js b/app/javascript/packs/schema.js index 77fc142d..bfd0a0ed 100644 --- a/app/javascript/packs/schema.js +++ b/app/javascript/packs/schema.js @@ -1,11 +1,15 @@ -import {schema as defaultMarkdownSchema, defaultMarkdownParser} from "prosemirror-markdown" -import {Schema} from "prosemirror-model" +import { schema as defaultMarkdownSchema } from 'prosemirror-markdown' +import { Schema } from 'prosemirror-model' export default new Schema({ nodes: defaultMarkdownSchema.spec.nodes, - marks: defaultMarkdownSchema.spec.marks.addBefore('text', 'mark', { - toDOM: node => ['mark'], - parseDOM: [{ tag: 'mark' }], - }), + marks: defaultMarkdownSchema.spec.marks + .addBefore('text', 'mark', { + toDOM: node => ['mark'], + parseDOM: [{ tag: 'mark' }], + }) + .addBefore('text', 'strikethrough', { + toDOM: node => ['s'], + parseDOM: [{ tag: 's' }], + }), }) - diff --git a/app/javascript/packs/serializer.js b/app/javascript/packs/serializer.js index 67570f3d..8f8d00db 100644 --- a/app/javascript/packs/serializer.js +++ b/app/javascript/packs/serializer.js @@ -1,74 +1,114 @@ // 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({ - blockquote(state, node) { - state.wrapBlock("> ", null, node, () => state.renderContent(node)) - }, - code_block(state, node) { - state.write("```" + (node.attrs.params || "") + "\n") - state.text(node.textContent, false) - state.ensureNewLine() - state.write("```") - state.closeBlock(node) - }, - heading(state, node) { - state.write(state.repeat("#", node.attrs.level) + " ") - state.renderInline(node) - state.closeBlock(node) - }, - horizontal_rule(state, node) { - state.write(node.attrs.markup || "---") - state.closeBlock(node) - }, - bullet_list(state, node) { - state.renderList(node, " ", () => (node.attrs.bullet || "*") + " ") - }, - ordered_list(state, node) { - let start = node.attrs.order || 1 - let maxW = String(start + node.childCount - 1).length - let space = state.repeat(" ", maxW + 2) - state.renderList(node, space, i => { - let nStr = String(start + i) - return state.repeat(" ", maxW - nStr.length) + nStr + ". " - }) - }, - list_item(state, node) { - state.renderContent(node) - }, - paragraph(state, node) { - state.renderInline(node) - state.closeBlock(node) - }, - - image(state, node) { - state.write("![" + state.esc(node.attrs.alt || "") + "](" + state.esc(node.attrs.src) + - (node.attrs.title ? " " + state.quote(node.attrs.title) : "") + ")") - }, - hard_break(state, node, parent, index) { - for (let i = index + 1; i < parent.childCount; i++) - if (parent.child(i).type != node.type) { - state.write("\\\n") - return - } - }, - text(state, node) { - state.text(node.text) - } -}, { - em: {open: "*", close: "*", mixable: true, expelEnclosingWhitespace: true}, - strong: {open: "**", close: "**", mixable: true, expelEnclosingWhitespace: true}, - mark: {open: "==", close: "==", mixable: true, expelEnclosingWhitespace: true}, - link: { - open(_state, mark, parent, index) { - return isPlainURL(mark, parent, index, 1) ? "<" : "[" +export default new MarkdownSerializer( + { + blockquote(state, node) { + state.wrapBlock('> ', null, node, () => state.renderContent(node)) + }, + code_block(state, node) { + state.write('```' + (node.attrs.params || '') + '\n') + state.text(node.textContent, false) + state.ensureNewLine() + state.write('```') + state.closeBlock(node) + }, + heading(state, node) { + state.write(state.repeat('#', node.attrs.level) + ' ') + state.renderInline(node) + state.closeBlock(node) + }, + horizontal_rule(state, node) { + state.write(node.attrs.markup || '---') + state.closeBlock(node) + }, + bullet_list(state, node) { + state.renderList(node, ' ', () => (node.attrs.bullet || '*') + ' ') + }, + ordered_list(state, node) { + let start = node.attrs.order || 1 + let maxW = String(start + node.childCount - 1).length + let space = state.repeat(' ', maxW + 2) + state.renderList(node, space, i => { + let nStr = String(start + i) + return state.repeat(' ', maxW - nStr.length) + nStr + '. ' + }) + }, + list_item(state, node) { + state.renderContent(node) + }, + paragraph(state, node) { + state.renderInline(node) + state.closeBlock(node) + }, + + image(state, node) { + state.write( + '![' + + state.esc(node.attrs.alt || '') + + '](' + + state.esc(node.attrs.src) + + (node.attrs.title ? ' ' + state.quote(node.attrs.title) : '') + + ')', + ) + }, + hard_break(state, node, parent, index) { + for (let i = index + 1; i < parent.childCount; i++) + if (parent.child(i).type != node.type) { + state.write('\\\n') + return + } + }, + text(state, node) { + state.text(node.text) }, - 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} -}) + { + em: { + 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, + }, + }, +) diff --git a/app/views/application/_editor.haml b/app/views/application/_editor.haml index 9b35f4d4..23518cc2 100644 --- a/app/views/application/_editor.haml +++ b/app/views/application/_editor.haml @@ -3,6 +3,7 @@ %button.bold-btn= "bold" %button.italic-btn= "italic" %button.highlight-btn= "highlight" + %button.strikethrough-btn= "strikethrough" %button.quote-btn= "quote" %button.ul-btn= "ul" %button.ol-btn= "ol"