diff --git a/app/assets/javascripts/02-editor.js b/app/assets/javascripts/02-editor.js index ad9dc42c..0a839b3f 100644 --- a/app/assets/javascripts/02-editor.js +++ b/app/assets/javascripts/02-editor.js @@ -1,4 +1,4 @@ -const origin = "http://panel.sutty.local:3000" || location.origin +const origin = location.origin function uploadFile (file) { return new Promise((resolve, reject) => { @@ -439,7 +439,7 @@ function stringifyAllowedStyle (element) { return element.style.cssText } -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("turbolinks:load", () => { for (const editorEl of document.querySelectorAll(".editor")) { setupEditor(editorEl) } diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c2cd6fd6..ce33dee6 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1,11 +1,16 @@ -//= require_tree . - $black: black; $white: white; $grey: grey; $cyan: #13fefe; $magenta: #f206f9; +$colors: ( + "black": $black, + "white": $white, + "cyan": $cyan, + "magenda": $magenta +); + // Redefinir variables de Bootstrap $primary: $magenta; $jumbotron-bg: transparent; @@ -16,6 +21,9 @@ $form-feedback-icon-valid-color: $black; $component-active-bg: $magenta; @import "bootstrap"; +@import "editor"; +@import "actiontext"; +@import "helpers"; :root { --foreground: #{$black}; diff --git a/app/assets/stylesheets/helpers.scss b/app/assets/stylesheets/helpers.scss new file mode 100644 index 00000000..22a1f966 --- /dev/null +++ b/app/assets/stylesheets/helpers.scss @@ -0,0 +1,187 @@ +// Viene de sutty-base-jekyll-theme +$prefixes: ("", "-webkit-", "-ms-", "-o-", "-moz-"); +$overflows: auto, hidden, scroll; + +/* + * Usar en animaciones, empiezan rápido y desaceleran hacia el final. + */ +$bezier: cubic-bezier(0.75, 0, 0.25, 1); + +/* + * Ocultar la barra de scroll, útil para sliders horizontales. + */ +.no-scrollbar { + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { display: none; } +} + +@each $cursor in (pointer none) { + .cursor-#{$cursor} { + cursor: $cursor; + } +} + +@each $direction in (top, right, bottom, left) { + .#{$direction}-0 { + #{$direction}: 0 + } +} + +@each $value in $overflows { + .overflow-#{$value} { overflow: $value !important; } +} + +@each $axis in (y, x) { + @each $value in $overflows { + .overflow-#{$axis}-#{$value} { overflow-#{$axis}: $value !important; } + } +} + +/* + * Poder aumentar o disminuir el alto de la tipografía, se usa de la + * misma forma que los modificadores de padding y margin. + */ +@each $size, $length in $spacers { + .f-#{$size} { + font-size: $length !important; + } + + .text-column-#{$size} { + column-count: $size; + } +} + +/* + * Modificadores de Bootstrap que no tienen versión responsive. + */ +@each $grid-breakpoint, $_ in $grid-breakpoints { + @include media-breakpoint-up($grid-breakpoint) { + // border + .border-#{$grid-breakpoint} { border: $border-width solid $border-color !important; } + .border-#{$grid-breakpoint}-top { border-top: $border-width solid $border-color !important; } + .border-#{$grid-breakpoint}-right { border-right: $border-width solid $border-color !important; } + .border-#{$grid-breakpoint}-bottom { border-bottom: $border-width solid $border-color !important; } + .border-#{$grid-breakpoint}-left { border-left: $border-width solid $border-color !important; } + .border-#{$grid-breakpoint}-0 { border: 0 !important; } + .border-#{$grid-breakpoint}-top-0 { border-top: 0 !important; } + .border-#{$grid-breakpoint}-right-0 { border-right: 0 !important; } + .border-#{$grid-breakpoint}-bottom-0 { border-bottom: 0 !important; } + .border-#{$grid-breakpoint}-left-0 { border-left: 0 !important; } + + // alineación + .text-#{$grid-breakpoint}-left { text-align: left !important; } + .text-#{$grid-breakpoint}-right { text-align: right !important; } + .text-#{$grid-breakpoint}-center { text-align: center !important; } + + // posición + @each $position in $positions { + .position-#{$grid-breakpoint}-#{$position} { position: $position !important; } + } + + // anchos y altos + @each $prop, $abbrev in (width: w, height: h) { + @each $size, $length in $sizes { + .#{$abbrev}-#{$grid-breakpoint}-#{$size} { #{$prop}: $length !important; } + } + } + + // versión responsive de f + @each $size, $length in $spacers { + .f-#{$grid-breakpoint}-#{$size} { + font-size: $length !important; + } + + .text-column-#{$grid-breakpoint}-#{$size} { + column-count: $size; + } + } + } +} + +/* + * Crea una propiedad con prefijos de navegador + */ +@mixin vendor-prefix($property, $definition...) { + @each $prefix in $prefixes { + #{$prefix}$property: $definition; + } +} + +/* + * Crea clases para asignar colores según la lista de colores. + */ +@each $color, $_ in $colors { + .background-#{$color} { + background-color: var(--#{$color}); + + &:focus { + background-color: var(--#{$color}); + } + } + + .scrollbar-#{$color} { + scrollbar-color: var(--#{$color}) transparent; + scrollbar-width: thin; + + &::-webkit-scrollbar { + width: 5px; + height: 8px; + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--#{$color}); + } + } + + .border-#{$color} { + border-color: var(--#{$color}) !important; + } + + .hover-bg-#{$color} { + &:hover { + background-color: var(--#{$color}); + } + } + + .hover-#{$color} { + &:hover { + color: var(--#{$color}); + } + } + + .#{$color} { + color: var(--#{$color}); + + &:focus { + color: var(--#{$color}); + } + + ::-moz-selection, + ::selection { + background: var(--#{$color}); + color: white; + } + + svg { + * { + fill: var(--#{$color}); + } + } + + .form-control { + border-color: var(--#{$color}); + color: var(--#{$color}); + } + + hr { + border-color: var(--#{$color}); + } + + a { + color: var(--#{$color}); + } + } +} diff --git a/app/views/application/markdown.haml b/app/views/application/markdown.haml deleted file mode 100644 index 64297077..00000000 --- a/app/views/application/markdown.haml +++ /dev/null @@ -1,63 +0,0 @@ -= form_with do - .editor - .editor-toolbar - .editor-primary-toolbar - %button.btn{:data => {:button => "bold"}} Bold - %button.btn{:data => {:button => "italic"}} Italic - %button.btn{:data => {:button => "deleted"}} Deleted - %button.btn{:data => {:button => "underline"}} Underline - %button.btn{:data => {:button => "mark"}} Subrayar - %button.btn{:data => {:button => "h1"}} H1 - %button.btn{:data => {:button => "h2"}} H2 - %button.btn{:data => {:button => "h3"}} H3 - %button.btn{:data => {:button => "h4"}} H4 - %button.btn{:data => {:button => "h5"}} H5 - %button.btn{:data => {:button => "h6"}} H6 - %button.btn{:data => {:button => "ul"}} Lista desordenada - %button.btn{:data => {:button => "ol"}} Lista ordenada - %button.btn{:data => {:button => "left"}} Left - %button.btn{:data => {:button => "center"}} Center - %button.btn{:data => {:button => "right"}} Right - %button.btn{:data => {:button => "img"}} Imágen - %button.btn{:data => {:button => "video"}} Video - %button.btn{:data => {:button => "audio"}} Audio - %button.btn{:data => {:button => "pdf"}} PDF - // TODO: generar IDs para labels - - // HAML cringe - .editor-auxiliary-toolbar{:data => {:editor => {:auxiliary => {:toolbar => ""}}}} - %div{:data => {:editor => {:auxiliary => "mark"}}} - %label{:for => "mark-color"} Color de resaltado: - %input{:type => "color", :data => {:prop => "mark-color"}}/ - - %div{:data => {:editor => {:auxiliary => "img"}}} - %label{:for => "img-file"} Archivo de la imágen: - %input{:type => "file", :data => {:prop => "img-file"}}/ - %label{:for => "img-alt"} Descripción de imágen: - %input{:placeholder => "Un álbum", :type => "text", :data => {:prop => "img-alt"}}/ - - %div{:data => {:editor => {:auxiliary => "audio"}}} - %label{:for => "audio-file"} Archivo de la audio: - %input{:type => "file", :data => {:prop => "audio-file"}}/ - - %div{:data => {:editor => {:auxiliary => "video"}}} - %label{:for => "video-file"} Archivo de la video: - %input{:type => "file", :data => {:prop => "video-file"}}/ - - %div{:data => {:editor => {:auxiliary => "pdf"}}} - %label{:for => "pdf-file"} Archivo de la PDF: - %input{:type => "file", :data => {:prop => "pdf-file"}}/ - - %div{:data => {:editor => {:auxiliary => "link"}}} - %label{:for => "link-href"} URL de link: - %input{:type => "url", :data => {:prop => "link-href"}}/ - - .editor-content{:contenteditable => "true"} - %h1 - Hola - %em mundo - %p Como te va? - %div{:data => {:align => "right"}} - %p Esto está a la derecha - %textarea{:cols => "80", :disabled => "disabled", :rows => "15"} - diff --git a/app/views/posts/attributes/_markdown_content.haml b/app/views/posts/attributes/_markdown_content.haml index 45bc5bf5..81bc702c 100644 --- a/app/views/posts/attributes/_markdown_content.haml +++ b/app/views/posts/attributes/_markdown_content.haml @@ -1,8 +1,72 @@ -.form-group.markdown-content +.form-group = label_tag "post_#{attribute}", post_label_t(attribute, post: post) = render 'posts/attribute_feedback', post: post, attribute: attribute, metadata: metadata - = text_area_tag "post[#{attribute}]", metadata.value, - dir: dir, lang: locale, - **field_options(attribute, metadata, class: 'content') - .editor.mt-1 + + .editor + = text_area_tag "post[#{attribute}]", metadata.value, + dir: dir, lang: locale, + **field_options(attribute, metadata), class: 'd-none' + + .editor-toolbar + .editor-primary-toolbar.scrollbar-black + %button.btn{ data: { button: 'bold' } }= t('editor.bold') + %button.btn{ data: { button: 'italic' } }= t('editor.italic') + %button.btn{ data: { button: 'deleted' } }= t('editor.deleted') + %button.btn{ data: { button: 'underline' } }= t('editor.underline') + %button.btn{ data: { button: 'mark' } }= t('editor.mark') + %button.btn{ data: { button: 'h1' } }= t('editor.h1') + %button.btn{ data: { button: 'h2' } }= t('editor.h2') + %button.btn{ data: { button: 'h3' } }= t('editor.h3') + %button.btn{ data: { button: 'h4' } }= t('editor.h4') + %button.btn{ data: { button: 'h5' } }= t('editor.h5') + %button.btn{ data: { button: 'h6' } }= t('editor.h6') + %button.btn{ data: { button: 'ul' } }= t('editor.ul') + %button.btn{ data: { button: 'ol' } }= t('editor.ol') + %button.btn{ data: { button: 'left' } }= t('editor.left') + %button.btn{ data: { button: 'center' } }= t('editor.center') + %button.btn{ data: { button: 'right' } }= t('editor.right') + %button.btn{ data: { button: 'img' } }= t('editor.img') + %button.btn{ data: { button: 'video' } }= t('editor.video') + %button.btn{ data: { button: 'audio' } }= t('editor.audio') + %button.btn{ data: { button: 'pdf' } }= t('editor.pdf') + + -# + HAML cringe + TODO: generar IDs para labels + .editor-auxiliary-toolbar.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' } }/ + + %div{ data: { editor: { auxiliary: 'img' } } } + .row + .col.form-group.d-flex.align-items-end + .custom-file + %input.custom-file-input{ type: 'file', data: { prop: 'img-file' }, accept: 'image/*' }/ + %label.custom-file-label{ for: 'img-file' }= t('editor.file.img') + .col.form-group + %label{ for: 'img-alt' }= t('editor.description') + %input.form-control{ type: 'text', data: { prop: 'img-alt' } }/ + + -# 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' }/ + %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' }/ + %label.custom-file-label{ for: 'video-file' }= t('editor.file.video') + + .form-group{ data: { editor: { auxiliary: 'pdf' } } } + .custom-file + %input.custom-file-input{ type: 'file', data: { prop: 'pdf-file' }, accept: 'application/pdf' }/ + %label.custom-file-label{ for: 'pdf-file' }= t('editor.file.pdf') + + .form-group{ data: { editor: { auxiliary: 'link' } } } + %label{ for: 'link-href' }= t('editor.url') + %input.form-control{ type: 'url', data: { prop: 'link-href' } }/ + + .editor-content.form-control.h-auto{ contenteditable: 'true' } diff --git a/config/locales/en.yml b/config/locales/en.yml index f5b254eb..b7bac8b5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -551,3 +551,33 @@ en: title: Encrypted content description: The field contents are encrypted before being stored and won't be available on the public website or its source code. You can save private information here and it will only be readable to this site's users through Sutty's panel. decryption_error: There was an error trying to decrypt the content, Sutty's team has been notified! + editor: + bold: Bold + italic: Emphasis + deleted: Strikethrough + underline: Underline + mark: Mark + h1: Heading 1 + h2: Heading 2 + h3: Heading 3 + h4: Heading 4 + h5: Heading 5 + h6: Heading 6 + ul: Unordered list + ol: Ordered list + left: Left + right: Right + center: Center + img: Image + video: Video + audio: Audio + pdf: PDF + color: Color + img: Image + file: + img: Select and upload image + video: Select and upload video + audio: Select and upload audio + pdf: Select and upload PDF + description: Description for blind people and search engines + url: Address diff --git a/config/locales/es.yml b/config/locales/es.yml index b68c6a8b..25986771 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -564,3 +564,33 @@ es: title: Contenido cifrado description: El contenido de este campo se guarda cifrado y no estará disponible en el sitio ni en su código fuente. Puedes guardar información privada aquí y sólo estará disponible para quienes tengan acceso a ese sitio en el panel de Sutty. decryption_error: Hubo un error al decifrar la información, ¡el equipo de Sutty ya fue notificado! + editor: + bold: Fuerte + italic: Énfasis + deleted: Tachado + underline: Subrayado + mark: Resaltado + h1: Título 1 + h2: Título 2 + h3: Título 3 + h4: Título 4 + h5: Título 5 + h6: Título 6 + ul: Lista itemizada + ol: Lista numerada + left: Izquierda + right: Derecha + center: Centro + img: Imágen + video: Video + audio: Audio + pdf: PDF + color: Color + img: Imágen + file: + img: Seleccionar y subir imágen + video: Seleccionar y subir video + audio: Seleccionar y subir audio + pdf: Seleccionar y subir archivo PDF + description: Descripción para personas no videntes y buscadores + url: Dirección