From 1f28ea77a044f414ea161fb9d7fb0dd3be04b489 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 17:36:55 -0300 Subject: [PATCH 1/8] poder editar videos --- app/assets/javascripts/01-types.js | 10 ++++++++-- app/assets/javascripts/02-editor.js | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/01-types.js b/app/assets/javascripts/01-types.js index 260f8072..28a8afdd 100644 --- a/app/assets/javascripts/01-types.js +++ b/app/assets/javascripts/01-types.js @@ -92,7 +92,6 @@ const blocks = { selector: "AUDIO", createFn: editorEl => { const el = document.createElement("AUDIO") - el.controls = true return el }, }, @@ -100,7 +99,13 @@ const blocks = { selector: "VIDEO", createFn: editorEl => { const el = document.createElement("VIDEO") - el.controls = true + el.poster = "/public/placeholder.png" + // Para poder seleccionar el video tenemos que sacarle los + // controles, pero queremos poder verlos para reproducir el video. + // Al hacer click le damos los controles y al salir se los sacamos + // para poder hacer click de vuelta + el.addEventListener('click', event => event.target.controls = true) + el.addEventListener('focusout', event => event.target.controls = false) return el }, }, @@ -283,6 +288,7 @@ const typesWithProperties = { const file = videoFileEl.files[0] + videoEl.poster = "" videoEl.src = URL.createObjectURL(file) videoEl.dataset.editorLoading = true uploadFile(file) diff --git a/app/assets/javascripts/02-editor.js b/app/assets/javascripts/02-editor.js index e01132ef..c814896d 100644 --- a/app/assets/javascripts/02-editor.js +++ b/app/assets/javascripts/02-editor.js @@ -462,6 +462,11 @@ function setupEditor (editorEl) { document.addEventListener(editorBtn("video"), () => setAuxiliaryToolbar(editorEl, "video")) document.addEventListener(editorBtn("pdf"), () => setAuxiliaryToolbar(editorEl, "pdf")) + for (const video of document.querySelectorAll('.editor .editor-content video')) { + video.addEventListener('click', event => event.target.controls = true) + video.addEventListener('focusout', event => event.target.controls = false) + } + cleanContent(contentEl) htmlEl.value = contentEl.innerHTML fixContent(contentEl) From bf73479c4a859cc544d06295d00a49c1422f7e72 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 17:41:15 -0300 Subject: [PATCH 2/8] alto del editor --- app/assets/stylesheets/editor.scss | 2 ++ app/views/posts/attributes/_content.haml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index e5eced70..b6c928ac 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -50,6 +50,8 @@ div[data-align="left"] { text-align: left; } div[data-align="center"] { text-align: center; } div[data-align="right"] { text-align: right; } + + min-height: 480px; } *[data-editor-loading] { diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml index 1e6fd414..5300f284 100644 --- a/app/views/posts/attributes/_content.haml +++ b/app/views/posts/attributes/_content.haml @@ -69,5 +69,5 @@ %label{ for: 'link-href' }= t('editor.url') %input.form-control{ type: 'url', data: { prop: 'link-href' } }/ - .editor-content.form-control.h-auto{ contenteditable: 'true' } + .editor-content.form-control.h-auto.mt-1{ contenteditable: 'true' } = metadata.value.html_safe From cae38f684f0b9201f101c4eed1305ecdcf51cf18 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 19:00:02 -0300 Subject: [PATCH 3/8] poder seleccionar audio y pdf --- app/assets/javascripts/01-types.js | 51 ++++++++++++++++++++++--- app/assets/stylesheets/application.scss | 8 ++++ app/assets/stylesheets/editor.scss | 7 +++- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/01-types.js b/app/assets/javascripts/01-types.js index 28a8afdd..9b0abd79 100644 --- a/app/assets/javascripts/01-types.js +++ b/app/assets/javascripts/01-types.js @@ -88,10 +88,25 @@ const blocks = { return el }, }, + figure: { + selector: "FIGURE", + noButton: true + }, + figcaption: { + selector: "FIGCAPTION", + noButton: true, + }, audio: { selector: "AUDIO", createFn: editorEl => { - const el = document.createElement("AUDIO") + const el = document.createElement("FIGURE") + + el.appendChild(document.createElement("AUDIO")) + el.appendChild(document.createElement("FIGCAPTION")) + + el.children[0].controls = true + el.children[1].innerText = "Toca el borde para subir un archivo de audio" + return el }, }, @@ -113,7 +128,13 @@ const blocks = { pdf: { selector: "IFRAME", createFn: editorEl => { - const el = document.createElement("IFRAME") + const el = document.createElement("FIGURE") + + el.appendChild(document.createElement("IFRAME")) + el.appendChild(document.createElement("FIGCAPTION")) + + el.children[1].innerText = "Toca el borde para subir un archivo PDF" + return el }, }, @@ -228,6 +249,21 @@ const typesWithProperties = { }, false) }, }, + figure: { + selector: blocks.figure.selector, + actualInput (el) { + // TODO: Cuando tengamos otros iframes hay que seleccionarlos de + // otra forma. + const tag = el.children[0].tagName.toLowerCase() + + return typesWithProperties[(tag === 'iframe' ? 'pdf' : tag)] + }, + updateInput (el, editorEl) { + typesWithProperties.figure.actualInput(el).updateInput(el.children[0], editorEl) + }, + disableInput (editorEl) {}, + setupInput (editorEl, contentEl) {}, + }, audio: { selector: blocks.audio.selector, updateInput (el, editorEl) { @@ -244,11 +280,13 @@ const typesWithProperties = { setupInput (editorEl, contentEl) { const audioFileEl = editorEl.querySelector(`*[data-prop="audio-file"]`) audioFileEl.addEventListener("input", event => { - const audioEl = getSelected(contentEl) - if (!audioEl) return + const figureEl = getSelected(contentEl) + if (!figureEl) return const file = audioFileEl.files[0] + const audioEl = figureEl.querySelector('audio') + audioEl.src = URL.createObjectURL(file) audioEl.dataset.editorLoading = true uploadFile(file) @@ -323,10 +361,11 @@ const typesWithProperties = { setupInput (editorEl, contentEl) { const pdfFileEl = editorEl.querySelector(`*[data-prop="pdf-file"]`) pdfFileEl.addEventListener("input", event => { - const pdfEl = getSelected(contentEl) - if (!pdfEl) return + const figureEl = getSelected(contentEl) + if (!figureEl) return const file = pdfFileEl.files[0] + const pdfEl = figureEl.children[0] pdfEl.src = URL.createObjectURL(file) pdfEl.dataset.editorLoading = true diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 537f57a3..a3fe5eec 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -23,6 +23,14 @@ $component-active-bg: $magenta; @import "bootstrap"; @import "editor"; +.editor { + .editor-content { + figure { + border: 1px solid transparentize($magenta, 0.3) + } + } +} + :root { --foreground: #{$black}; --background: #{$white}; diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index b6c928ac..3f60486a 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -14,9 +14,14 @@ .selected { outline: #f206f9 solid medium; } iframe { + border: 0; min-height: 480px; } + figure { + padding: .5rem; + } + img, video, iframe, audio { width: 100%; max-width: 600px; @@ -51,7 +56,7 @@ div[data-align="center"] { text-align: center; } div[data-align="right"] { text-align: right; } - min-height: 480px; + min-height: 480px; } *[data-editor-loading] { From bdaf3f16044cee2bf90fe3e9904aaf05d2260627 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 19:03:32 -0300 Subject: [PATCH 4/8] gui del editor --- app/views/posts/attributes/_content.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml index 5300f284..b4d1ad2a 100644 --- a/app/views/posts/attributes/_content.haml +++ b/app/views/posts/attributes/_content.haml @@ -34,10 +34,10 @@ -# HAML cringe TODO: generar IDs para labels - .editor-auxiliary-toolbar.scrollbar-black{ data: { editor: { auxiliary: { toolbar: '' } } } } + .editor-auxiliary-toolbar.mt-1.scrollbar-black{ data: { editor: { auxiliary: { toolbar: '' } } } } .form-group{ data: { editor: { auxiliary: 'mark' } } } %label{ for: 'mark-color' }= t('editor.color') - %input{ type: 'color', data: { prop: 'mark-color' } }/ + %input.form-control{ type: 'color', data: { prop: 'mark-color' } }/ %div{ data: { editor: { auxiliary: 'img' } } } .row @@ -52,12 +52,12 @@ -# https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers .form-group{ data: { editor: { auxiliary: 'audio' } } } .custom-file - %input.custom-file-input{ type: 'file', data: { prop: 'audio-file' }, accept: 'audio/flac,audio/mp4,audio/ogg,audio/webm,audio/mp3' }/ + %input.custom-file-input{ type: 'file', data: { prop: 'audio-file' }, accept: 'audio/*' }/ %label.custom-file-label{ for: 'audio-file' }= t('editor.file.audio') .form-group{ data: { editor: { auxiliary: 'video' } } } .custom-file - %input.custom-file-input{ type: 'file', data: { prop: 'video-file' }, accept: 'video/mp4,video/ogg,video/webm' }/ + %input.custom-file-input{ type: 'file', data: { prop: 'video-file' }, accept: 'video/*' }/ %label.custom-file-label{ for: 'video-file' }= t('editor.file.video') .form-group{ data: { editor: { auxiliary: 'pdf' } } } From c01284bb5e3a056df030146213a2685b4256ee58 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 19:18:53 -0300 Subject: [PATCH 5/8] ver lo mismo en la vista previa --- app/assets/javascripts/02-editor.js | 2 ++ app/assets/stylesheets/editor.scss | 12 +++++------- app/views/posts/show.haml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/02-editor.js b/app/assets/javascripts/02-editor.js index c814896d..dfbb3f7a 100644 --- a/app/assets/javascripts/02-editor.js +++ b/app/assets/javascripts/02-editor.js @@ -479,6 +479,8 @@ function stringifyAllowedStyle (element) { document.addEventListener("turbolinks:load", () => { for (const editorEl of document.querySelectorAll(".editor")) { + if (!editorEl.querySelector('.editor-toolbar')) continue + setupEditor(editorEl) } }) diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index 3f60486a..4b89cf1a 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -50,14 +50,12 @@ } } - .editor-content { - word-wrap: break-word; - div[data-align="left"] { text-align: left; } - div[data-align="center"] { text-align: center; } - div[data-align="right"] { text-align: right; } + word-wrap: break-word; + div[data-align="left"] { text-align: left; } + div[data-align="center"] { text-align: center; } + div[data-align="right"] { text-align: right; } - min-height: 480px; - } + min-height: 480px; *[data-editor-loading] { opacity: 0.5; diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index 405b9c7e..9b4fb8a0 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -33,5 +33,5 @@ - @post.attributes.each do |attr| - next if @post.send(attr).front_matter? - %section{ id: attr, dir: dir } + %section.editor{ id: attr, dir: dir } = @post.send(attr).to_s.html_safe From 1ff5d0ab7eb798b649cd1d0b91379d2df86ae31e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 19:19:29 -0300 Subject: [PATCH 6/8] guardar figure y figcaption --- app/models/metadata_template.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index a85b84a5..1a8f93e4 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -7,7 +7,6 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, :value, :help, :required, :errors, :post, :layout, keyword_init: true) do - attr_reader :value_was def value=(new_value) @@ -133,7 +132,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, end def allowed_tags - @allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div].freeze + @allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure figcaption].freeze end # Decifra el valor From dba39dfc28f849542d5e09114e328468dee6be62 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 20:29:22 -0300 Subject: [PATCH 7/8] limpiar el contenido antes de guardarlo --- app/models/metadata_content.rb | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index a14e19f7..4105ffcc 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -14,4 +14,44 @@ class MetadataContent < MetadataTemplate def front_matter? false end + + private + + # Limpiar el HTML que recibimos + # + # TODO: En lugar de comprobar el Content Type acá, restringir los + # tipos de archivo a aceptar en ActiveStorage. + def sanitize(html_string) + html = Nokogiri::HTML.fragment(super html_string) + elements = 'img,audio,video,iframe' + + # Eliminar elementos sin src y comprobar su origen + html.css(elements).each do |element| + unless element['src'] + element.remove + next + end + + begin + uri = URI element['src'] + + # No permitimos recursos externos + element.remove unless uri.hostname.end_with? Site.domain + rescue URI::Error + element.remove + end + end + + # Eliminar figure sin contenido + html.css('figure').each do |figure| + figure.remove if figure.css(elements).empty? + end + + # Los videos y audios necesitan controles + html.css('audio,video').each do |resource| + resource['controls'] = true + end + + html.to_s.html_safe + end end From 7b1b2437084d65618e88908c955f8987db351a6f Mon Sep 17 00:00:00 2001 From: f Date: Tue, 17 Nov 2020 20:30:06 -0300 Subject: [PATCH 8/8] deshabilitar los controles de los videos para poder seleccionarlos --- app/assets/javascripts/02-editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/02-editor.js b/app/assets/javascripts/02-editor.js index dfbb3f7a..2d6b9f66 100644 --- a/app/assets/javascripts/02-editor.js +++ b/app/assets/javascripts/02-editor.js @@ -465,6 +465,7 @@ function setupEditor (editorEl) { for (const video of document.querySelectorAll('.editor .editor-content video')) { video.addEventListener('click', event => event.target.controls = true) video.addEventListener('focusout', event => event.target.controls = false) + video.controls = false } cleanContent(contentEl) @@ -479,7 +480,7 @@ function stringifyAllowedStyle (element) { document.addEventListener("turbolinks:load", () => { for (const editorEl of document.querySelectorAll(".editor")) { - if (!editorEl.querySelector('.editor-toolbar')) continue + if (!editorEl.querySelector('.editor-toolbar')) continue setupEditor(editorEl) }