/* eslint no-console:0 */ // This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. // // 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. // // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) 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' // seleccionar cosas que no son texto 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 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 { 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, }) const toggleH1 = generateHeadingToggle(1) const toggleH2 = generateHeadingToggle(2) const makeLink = (state, dispatch) => { const { tr, selection, doc } = state const type = schema.marks.link 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'), }), ) } return dispatch(tr) } class SuttyEditor { constructor({ element, markdown, toolbar }) { this.state = this.buildState(markdown) this.view = this.buildView(element, this.state) this.hooks(toolbar) } runCommand(command) { return command(this.view.state, this.view.dispatch, this.view) } hooks(el) { const setupBtn = (class_, action) => { const btnEl = el.querySelector('.' + class_ + '-btn') btnEl.type = 'button' btnEl.addEventListener('click', e => { action() return false }) } 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)) setupBtn('ol', () => this.runCommand(toggleOl)) setupBtn('h1', () => this.runCommand(toggleH1)) setupBtn('h2', () => this.runCommand(toggleH2)) const imgInput = el.querySelector('.img-input') imgInput.addEventListener('change', event => { if ( this.view.state.selection.$from.parent.inlineContent && event.target.files.length ) { const file = event.target.files[0] event.target.value = '' startImageUpload(this.view, file, schema) } this.view.focus() }) } buildState(markdown) { return EditorState.create({ schema, ...(markdown ? { doc: parser.parse(markdown), } : {}), plugins: [ gapCursor(), keymap(baseKeymap), placeholderPlugin, inputRules({ rules: [ //emDash, // -- => — //ellipsis, // ... => … ], }), ], }) } buildView(element, state) { return new EditorView(element, { state }) } getMarkdown() { return serializer.serialize(this.view.state.doc) } } document.addEventListener('turbolinks:load', e => { try { const editorEls = document.querySelectorAll('.sutty-editor-prosemirror') for (const el of editorEls) { // conseguir textarea con el markdown const textareaEl = document.querySelector( `.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`, ) // conseguir toolbar const toolbarEl = document.querySelector( `.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, }) // 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, ) } } catch (error) { console.error('algo falló en la carga del editor', error) } const table = document.querySelector('.table-draggable') if (table == null) return tableDragger(table, { mode: 'row', onlyBody: true, dragHandler: '.handle', }).on('drop', (from, to, el, mode) => { Array.from(document.querySelectorAll('.reorder')) .reverse() .map((o, i) => (o.value = i)) Array.from(document.querySelectorAll('.submit-reorder')).map(s => s.classList.remove('d-none'), ) }) })