From c9fe324993b261951a3ac4a4cd9b5a3e8edb60d2 Mon Sep 17 00:00:00 2001 From: void Date: Fri, 18 Oct 2019 20:05:59 -0300 Subject: [PATCH] feat(prosemirror): (WIP) sacar trix y agregar prosemirror (#70) --- app/assets/stylesheets/application.scss | 16 ++++ app/javascript/packs/application.js | 78 ++++++++++++++++- app/models/metadata_content.rb | 41 +-------- app/views/application/_editor.haml | 4 + app/views/posts/attributes/_content.haml | 13 ++- package.json | 7 ++ yarn.lock | 104 ++++++++++++++++++++++- 7 files changed, 215 insertions(+), 48 deletions(-) create mode 100644 app/views/application/_editor.haml diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 53501e69..29e07153 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -235,3 +235,19 @@ svg { color: $white; } } + +.sutty-editor-prosemirror { + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.2); + padding: 5px 0; + max-height: 450px; + overflow-y: auto; + + .ProseMirror { + padding: 4px 10px; + line-height: 1.2; + outline: none; + word-wrap: break-word; + white-space: pre-wrap; + } +} diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 13310f7d..10dd772a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -15,5 +15,79 @@ // const images = require.context('../images', true) // const imagePath = (name) => images(name, true) -require("trix") -require("@rails/actiontext") +import {EditorState} from "prosemirror-state" +import {EditorView} from "prosemirror-view" +import {Schema, DOMParser} from "prosemirror-model" + +// 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" + +// markdown (commonmark de markdown-it) +import {schema, defaultMarkdownParser, + defaultMarkdownSerializer} from "prosemirror-markdown" + +import { DirectUpload } from "@rails/activestorage" + +class SuttyEditor { + constructor ({element, markdown}) { + this.state = this.buildState(markdown) + this.view = this.buildView(element, this.state) + } + + buildState (markdown) { + return EditorState.create({ + schema, + ...(markdown ? { + doc: defaultMarkdownParser.parse(markdown) + } : {}), + plugins: [ + gapCursor(), + inputRules({ + rules: [ + //emDash, // -- => — + //ellipsis, // ... => … + + ], + }), + ], + }) + } + + buildView (element, state) { + return new EditorView(element, {state}) + } + + get markdown () { + return defaultMarkdownSerializer.serialize(this.view.state.doc) + } +} + +document.addEventListener('turbolinks:load', e => { + 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}"]` + ) + + // 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, + }) + + // 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...') + textareaEl.value = editor.markdown + }, true) + } +}) diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index ad78ef49..dcbb488d 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -2,44 +2,5 @@ # Se encarga del contenido del artículo y quizás otros campos que # requieran texto largo. -class MetadataContent < MetadataTemplate - include ActionView::Helpers::SanitizeHelper - - def default_value - '' - end - - def value - markdown = ReverseMarkdown.convert(self[:value]).strip - markdown = nil if markdown.blank? - - sanitize(markdown || document.content || default_value, - sanitize_options) - .tr("\r", '') # eliminar retornos de carro - .gsub(/^> /, '> ') # convertir citas de vuelta a citas - .gsub(/ \n /, "\n") # eliminar dobles saltos de línea - .gsub(/^ $/, '') # eliminar saltos de línea solitarios - end - - def front_matter? - false - end - - private - - # Etiquetas y atributos HTML a permitir - # - # No queremos permitir mucho más que cosas de las que nos falten en - # CommonMark. - # - # TODO: Permitir una lista de atributos y etiquetas en el Layout - # - # XXX: Vamos a generar un reproductor de video/audio directamente - # desde un plugin de Jekyll - def sanitize_options - { - tags: %w[span], - attributes: %w[title class lang] - } - end +class MetadataContent < MetadataString end diff --git a/app/views/application/_editor.haml b/app/views/application/_editor.haml new file mode 100644 index 00000000..cbbf736a --- /dev/null +++ b/app/views/application/_editor.haml @@ -0,0 +1,4 @@ +.sutty-editor.sutty-editor-prosemirror{'data-sutty-identifier' => identifier} +%textarea.form-control.sutty-editor.sutty-editor-content{:rows => "8", + 'data-sutty-identifier' => identifier, + :name => name}= markdown diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml index 3b9e1e94..ec5789d5 100644 --- a/app/views/posts/attributes/_content.haml +++ b/app/views/posts/attributes/_content.haml @@ -2,6 +2,13 @@ = label_tag "post_#{attribute}", post_label_t(attribute, post: post) = render 'posts/attribute_feedback', post: post, attribute: attribute, metadata: metadata - = rich_text_area_tag "post[#{attribute}]", - sanitize_markdown(metadata.value, tags: all_html_tags), - **field_options(attribute, metadata), class: '' + //= rich_text_area_tag "post[#{attribute}]", + // sanitize_markdown(metadata.value, tags: all_html_tags), + // **field_options(attribute, metadata), class: '' + = render 'application/editor', + markdown: metadata.value, + identifier: "#{post.id}-#{attribute}", name: "post[#{attribute}]" + //:javascript + // new SuttyEditor({ + // element: document.getElementById('editor') + // }) diff --git a/package.json b/package.json index 0e7986b5..526c98be 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,16 @@ "private": true, "dependencies": { "@rails/actiontext": "^6.0.0", + "@rails/activestorage": "^6.0.0", "@rails/webpacker": "^4.0.7", "commonmark": "^0.29.0", "input-tag": "https://0xacab.org/sutty/input-tag.git", + "prosemirror-gapcursor": "^1.0.4", + "prosemirror-inputrules": "^1.0.4", + "prosemirror-markdown": "^1.3.1", + "prosemirror-model": "^1.7.4", + "prosemirror-state": "^1.2.4", + "prosemirror-view": "^1.11.7", "table-dragger": "^1.0.2", "trix": "https://0xacab.org/sutty/trix.git", "zepto": "^1.2.0" diff --git a/yarn.lock b/yarn.lock index 7b39840a..2ded1188 100644 --- a/yarn.lock +++ b/yarn.lock @@ -687,7 +687,7 @@ dependencies: "@rails/activestorage" "^6.0.0-alpha" -"@rails/activestorage@^6.0.0-alpha": +"@rails/activestorage@^6.0.0", "@rails/activestorage@^6.0.0-alpha": version "6.0.0" resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.0.0.tgz#6d9cc98f3dcf195c38ca65e80acd2e759336dd7a" integrity sha512-RZqTVlUYSWcI88pSJ7ImuTtDYQvqVc2PNuOo+5R3hcVupD+d2TO8xmluqQ5TvByjPqKX4acEK0mjEIK5ip9kCg== @@ -2492,7 +2492,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== -"entities@~ 1.1.1": +"entities@~ 1.1.1", entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -3883,6 +3883,13 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +linkify-it@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" + integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== + dependencies: + uc.micro "^1.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4033,6 +4040,17 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" + integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -4052,7 +4070,7 @@ mdn-data@~1.1.0: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== -"mdurl@~ 1.0.1": +mdurl@^1.0.1, "mdurl@~ 1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -4639,6 +4657,11 @@ optimize-css-assets-webpack-plugin@^5.0.1: cssnano "^4.1.10" last-call-webpack-plugin "^3.0.0" +orderedmap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.0.0.tgz#d90fc2ba1ed085190907d601dec6e6a53f8d41ba" + integrity sha1-2Q/Cuh7QhRkJB9YB3sbmpT+NQbo= + original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" @@ -5583,6 +5606,71 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +prosemirror-gapcursor@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.0.4.tgz#4ba663fb8511616e18ad222c904403cfbf6866dc" + integrity sha512-k021MtJibWs3NaJI6S9tCXfTZ/kaugFZBndHkkWx3Zfk0QDUO6JfVATpflxADN6DUkRwJ7qWyHlLDWu71hxHFQ== + dependencies: + prosemirror-keymap "^1.0.0" + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-view "^1.0.0" + +prosemirror-inputrules@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.0.4.tgz#8ae84d9c147abb448b86063ff422e801c4b1658d" + integrity sha512-RhuBghqUgYWm8ai/P+k1lMl1ZGvt6Cs3Xeur8oN0L1Yy+Z5GmsTp3fT8RVl+vJeGkItEAxAit9Qh7yZxixX7rA== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.2.tgz#e4e8b876a586ec6a170615fce19c4450f4075bef" + integrity sha512-aq3fBT3WMbwGNacUtMbS/mxd87hjJyjtUx5/h3q/P3FiVqHxmeA9snxQsZHYe0cWRziZePun8zw6kHFKLp/DAQ== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.0.0" + +prosemirror-markdown@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.3.1.tgz#6ede0f5993758a257eb33041efed32e449b13ed4" + integrity sha512-x16Va8DN9OHEzaoxCw0mmkvo6RwQTfjZRJZpEj8/TelINsFGojay8b0U4U+Hv2HwfMVfA00i6Pk3jeGrE/vUtg== + dependencies: + markdown-it "^8.4.2" + prosemirror-model "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.4.tgz#fadefad98ef9d71ca1749d18a82005313c978e4b" + integrity sha512-yxdpPh9Uv5vAOZvmbhg4fsGUK1oHuQs69iX7cFZ0A4Y+AyMMWRCNKUt21uv84HbXb4I180l4pJE8ibaH/SwYiw== + dependencies: + orderedmap "^1.0.0" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.2.4.tgz#aab932ebb33a0f27c256abce6fc20da8ca77ad5e" + integrity sha512-ViXpXond3BbSL12ENARQGq3Y8igwFMbTcy96xUNK8kfIcfQRlYlgYrBPXIkHC5+QZtbPrYlpuJ2+QyeSlSX9Cw== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.5.tgz#e50113d8004854eca1fa0711806bca53b1f1fad9" + integrity sha512-hJyRcwykLrAAj/ziNbVK1P/ensiszWJ2fNwNiDM8ZWYiMPwM4cAd2jptj/znxJfIvaj0S0psWL1+VhKwPNJDSQ== + dependencies: + prosemirror-model "^1.0.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.11.7: + version "1.11.7" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.11.7.tgz#537020acab43e18d51e18717fb1f39466317ce21" + integrity sha512-RDll1mhrOQ5JPOsUZlISH3VP/4zyHm3RAuqh9Cg5HdpfSQow0nsx8xL5YQZ976UdhvwbkiKamLtxhSRKES9wsA== + dependencies: + prosemirror-model "^1.1.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + proxy-addr@~2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" @@ -6820,6 +6908,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -7007,6 +7100,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== +w3c-keyname@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.1.1.tgz#d234195b0a79913d06a0bc636f17f00050a5de56" + integrity sha512-8kSrsGClLiL4kb5/pTxglejUlEAPk3GXtkBblSMrQDxKz0NkMRTVTPBZm6QCNqPOCPsdNvae5XfV+RJZgeGXEA== + watchpack@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"