5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-07-05 22:15:45 +00:00
panel/app/javascript/packs/application.js

233 lines
6.7 KiB
JavaScript
Raw Permalink Normal View History

/* 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)
2020-03-21 15:46:48 +00:00
import tableDragger from 'table-dragger'
2019-11-15 15:31:40 +00:00
2020-03-21 15:46:48 +00:00
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
2020-03-21 15:46:48 +00:00
import { gapCursor } from 'prosemirror-gapcursor'
import 'prosemirror-gapcursor/style/gapcursor.css'
// convertir cosas como ">" a una quote: https://prosemirror.net/docs/ref/#inputrules
2020-03-21 15:46:48 +00:00
import {
inputRules,
wrappingInputRule,
textblockTypeInputRule,
emDash,
ellipsis,
} from 'prosemirror-inputrules'
import serializer from './serializer.js'
import parser from './deserializer.js'
import schema from './schema.js'
2020-03-21 15:46:48 +00:00
import {
toggleMark,
setBlockType,
wrapIn,
baseKeymap,
} from 'prosemirror-commands'
import {
toggleWrap,
toggleBlockType,
toggleList,
updateMark,
removeMark,
} from 'tiptap-commands'
import { startImageUpload, placeholderPlugin } from './imageUpload'
2019-11-20 03:35:58 +00:00
const toggleBold = toggleMark(schema.marks.strong)
const toggleItalic = toggleMark(schema.marks.em)
const toggleHighlight = toggleMark(schema.marks.mark)
2020-03-21 15:46:48 +00:00
const toggleStrikethrough = toggleMark(schema.marks.strikethrough)
2019-11-20 03:35:58 +00:00
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 =>
2020-03-21 15:46:48 +00:00
toggleBlockType(schema.nodes.heading, schema.nodes.paragraph, {
level: level,
})
2019-11-20 03:35:58 +00:00
const toggleH1 = generateHeadingToggle(1)
const toggleH2 = generateHeadingToggle(2)
const makeLink = (state, dispatch) => {
2020-03-21 15:46:48 +00:00
const { tr, selection, doc } = state
2019-11-20 03:35:58 +00:00
const type = schema.marks.link
2020-03-21 15:46:48 +00:00
const { from, to } = selection
2019-11-20 03:35:58 +00:00
const has = doc.rangeHasMark(from, to, type)
if (has) {
tr.removeMark(from, to, type)
} else {
2020-03-21 15:46:48 +00:00
tr.addMark(
from,
to,
type.create({
href: window.prompt('elegir url', '//sutty.nl'),
}),
)
2019-11-20 03:35:58 +00:00
}
return dispatch(tr)
}
class SuttyEditor {
2020-03-21 15:46:48 +00:00
constructor({ element, markdown, toolbar }) {
this.state = this.buildState(markdown)
this.view = this.buildView(element, this.state)
2019-11-20 03:35:58 +00:00
this.hooks(toolbar)
}
2020-03-21 15:46:48 +00:00
runCommand(command) {
2019-11-20 03:35:58 +00:00
return command(this.view.state, this.view.dispatch, this.view)
}
2020-03-21 15:46:48 +00:00
hooks(el) {
2019-11-20 03:35:58 +00:00
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))
2020-03-21 15:46:48 +00:00
setupBtn('strikethrough', () => this.runCommand(toggleStrikethrough))
2019-11-20 03:35:58 +00:00
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))
2019-11-20 15:01:09 +00:00
const imgInput = el.querySelector('.img-input')
imgInput.addEventListener('change', event => {
if (
2020-03-21 15:46:48 +00:00
this.view.state.selection.$from.parent.inlineContent &&
event.target.files.length
2019-11-20 15:01:09 +00:00
) {
const file = event.target.files[0]
event.target.value = ''
startImageUpload(this.view, file, schema)
}
this.view.focus()
})
}
2020-03-21 15:46:48 +00:00
buildState(markdown) {
return EditorState.create({
schema,
2020-03-21 15:46:48 +00:00
...(markdown
? {
doc: parser.parse(markdown),
}
: {}),
plugins: [
gapCursor(),
2019-11-20 03:35:58 +00:00
keymap(baseKeymap),
2019-11-20 15:01:09 +00:00
placeholderPlugin,
inputRules({
rules: [
//emDash, // -- => —
//ellipsis, // ... => …
],
}),
],
})
}
2020-03-21 15:46:48 +00:00
buildView(element, state) {
return new EditorView(element, { state })
}
2020-03-21 15:46:48 +00:00
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(
2020-03-21 15:46:48 +00:00
`.sutty-editor-content[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`,
)
// conseguir toolbar
const toolbarEl = document.querySelector(
2020-03-21 15:46:48 +00:00
`.sutty-editor-toolbar[data-sutty-identifier="${el.dataset.suttyIdentifier}"]`,
)
// ocultarlo (no se oculta originalmente para usuaries noscript)
textareaEl.style.display = 'none'
2020-03-21 15:46:48 +00:00
// crear editor con el markdown del textarea
const editor = new SuttyEditor({
element: el,
markdown: textareaEl.value,
2020-03-21 15:46:48 +00:00
toolbar: toolbarEl,
})
// hookear a la form para inyectar el contenido del editor al textarea oculto para que se mande
2020-03-21 15:46:48 +00:00
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
2019-11-15 15:31:40 +00:00
tableDragger(table, {
mode: 'row',
onlyBody: true,
dragHandler: '.handle',
2019-11-15 15:31:40 +00:00
}).on('drop', (from, to, el, mode) => {
Array.from(document.querySelectorAll('.reorder'))
2020-03-21 15:46:48 +00:00
.reverse()
.map((o, i) => (o.value = i))
2020-03-21 15:46:48 +00:00
Array.from(document.querySelectorAll('.submit-reorder')).map(s =>
s.classList.remove('d-none'),
)
})
})