diff --git a/.gitignore b/.gitignore index 496b66cb..f93c69bd 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ yarn-debug.log* /yarn-error.log yarn-debug.log* .yarn-integrity + +/app/assets/builds/* +!/app/assets/builds/.keep diff --git a/Gemfile b/Gemfile index 94a55984..41d38cba 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,8 @@ source 'https://17.3.alpine.gems.sutty.nl' ruby '~> 3.1' +gem 'foreman' +gem 'jsbundling-rails' gem 'dotenv-rails', require: 'dotenv/rails-now' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' @@ -18,7 +20,7 @@ gem 'puma' # XXX: Supuestamente Rails ya soporta RAILS_GROUPS, pero Bundler no. if ENV['RAILS_GROUPS']&.split(',')&.include? 'assets' gem 'sassc-rails' - gem 'uglifier', '>= 1.3.0' +# gem 'uglifier', '>= 1.3.0' gem 'bootstrap', '~> 4' end diff --git a/Gemfile.lock b/Gemfile.lock index d0fbfdfd..2babd6dd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,6 +178,7 @@ GEM fast_jsonparser (0.6.0-x86_64-linux-musl) ffi (1.15.5-x86_64-linux-musl) flamegraph (0.9.5) + foreman (0.87.2) forwardable-extended (2.6.0) friendly_id (5.5.0) activerecord (>= 4.0.0) @@ -262,6 +263,8 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-watch (2.2.1) listen (~> 3.0) + jsbundling-rails (1.1.1) + railties (>= 6.0.0) json (2.6.3-x86_64-linux-musl) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -512,8 +515,6 @@ GEM turbolinks-source (5.2.0) tzinfo (2.0.5) concurrent-ruby (~> 1.0) - uglifier (4.2.0) - execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.8.2-x86_64-linux-musl) @@ -569,6 +570,7 @@ DEPENDENCIES fast_blank fast_jsonparser flamegraph + foreman friendly_id hairtrigger haml-lint @@ -584,6 +586,7 @@ DEPENDENCIES jekyll-data jekyll-images jekyll-include-cache + jsbundling-rails kaminari letter_opener listen @@ -627,7 +630,6 @@ DEPENDENCIES terminal-table timecop turbolinks (~> 5) - uglifier (>= 1.3.0) validates_hostname web-console webpacker diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 00000000..000e2ed4 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: unset PORT && bin/rails server +js: yarn build --watch diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b16e53d6..d333340b 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,4 @@ //= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .css +//= link_tree ../builds diff --git a/app/assets/stylesheets/styles.scss b/app/assets/stylesheets/styles.scss new file mode 100644 index 00000000..dc61b5d3 --- /dev/null +++ b/app/assets/stylesheets/styles.scss @@ -0,0 +1,547 @@ +$black: black; +$white: white; +$grey: grey; +$cyan: #13fefe; +$magenta: #f206f9; + +$colors: ( + "black": $black, + "white": $white, + "cyan": $cyan, + "magenta": $magenta +); + +// Redefinir variables de Bootstrap +$primary: $magenta; +$secondary: $black; +$jumbotron-bg: transparent; +$enable-rounded: false; +$form-feedback-valid-color: $cyan; +$form-feedback-invalid-color: $magenta; +$form-feedback-icon-valid-color: $black; +$component-active-bg: $magenta; + +$spacers: ( + 2-plus: 0.75rem +); + +@import "bootstrap"; +@import "editor"; + +.editor { + .editor-content { + figure { + border: 1px solid transparentize($magenta, 0.3) + } + } +} + +:root { + --foreground: #{$black}; + --background: #{$white}; + --color: #{$magenta}; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground: #{$white}; + --background: #{$black}; + --color: #{$cyan}; + } +} + +// TODO: Encontrar la forma de generar esto desde los locales de Rails +$custom-file-text: ( + en: 'Browse', + es: 'Buscar archivo' +); + +@font-face { + font-family: 'Saira'; + font-style: normal; + font-weight: 500; + font-display: optional; + src: local('Saira Medium'), local('Saira-Medium'), + font-url('saira/v3/SairaMedium-subset.woff2') format('woff2'), + font-url('saira/v3/SairaMedium-subset.zopfli.woff') format('woff'); +} + +@font-face { + font-family: 'Saira'; + font-style: normal; + font-weight: 700; + font-display: optional; + src: local('Saira Bold'), local('Saira-Bold'), + font-url('saira/v3/SairaBold-subset.woff2') format('woff2'), + font-url('saira/v3/SairaBold-subset.zopfli.woff') format('woff'); +} + +body { + font-family: Saira, sans-serif; + background-color: var(--background); + color: var(--foreground); +} + +* { + .rtl, + &[dir=rtl] { + text-align: right; + } +} + +a { + color: var(--color); + + &:hover { + color: var(--color); + } + + &[target=_blank] { + /* TODO: Convertir a base64 para no hacer peticiones extra */ + &:after { + content: image-url('icon_external_link.png'); + } + } +} + +$footer-height: 60px; + +/* Colores */ +$purpura: #5c004d; +$turquesa: #009389; +$azul: #0b2660; +$fucsia: #e9193e; +$celeste: #91f0ff; +$verde: #96d643; + +ol.breadcrumb { + background-color: transparent; +} + +.breadcrumb-item, +.breadcrumb-item.active, +.table, +.form-control, +.custom-file-label { + color: var(--foreground); +} + +.table tr.sticky-top, +.form-control, +.custom-file-label { + background-color: var(--background); +} + +.turbolinks-progress-bar { + height: 3px; + background-color: $magenta; +} + +.btn-text { + background-color: transparent; + border: none; +} + +.inline { + display: inline; +} + +.sindu_dragger table { + background: transparent !important; +} + +.d-none, .d-block { + transition: all 3s; +} + +.mapable, +.taggable { + .input-map, + .input-tag { + legend { + @extend .sr-only + } + + label { + margin: 0.5rem; + } + + input { + vertical-align: middle; + + &[type=text] { + @extend .form-control; + display: inline-block; + width: calc(100% - 93px); + } + + &[type=checkbox] { + } + + &[type=button] { + @extend .btn; + @extend .btn-info; + @extend .m-0; + } + } + } +} + +svg { + .is-path-magenta { + fill: var(--foreground); + } +} + +.btn { + background-color: var(--foreground); + color: var(--background); + border: none; + border-radius: 0; + margin-right: 0.3rem; + margin-bottom: 0.3rem; + + &:hover { + color: var(--background); + background-color: var(--color); + } + + &:active { + background-color: var(--color); + } + + &:focus { + box-shadow: 0 0 0 0.2rem var(--color); + } +} + +.btn-sm { + @extend .badge +} + +.black-bg { + color: $white; + background-color: $black; + + svg { + .is-path-magenta { + fill: $white + } + } + + a { + color: $magenta; + } + + .btn { + background-color: $white; + color: $black; + border: none; + + &:hover { + color: $black; + background-color: $cyan; + } + + &:active { + background-color: $cyan; + } + + &:focus { + box-shadow: 0 0 0 0.2rem $cyan; + } + } + + .breadcrumb-item { + color: $white; + } +} + +::-moz-selection, +::selection { + background: var(--color); + color: var(--background); +} + +.black-bg { + ::selection, + ::-moz-selection { + background-color: $magenta; + color: $white; + } +} + +.handle { + img { + height: 1rem; + } +} + +.custom-control-label { + font-weight: bold; +} + +.designs { + .design { + margin-top: 1rem; + } +} + +.editor { + .ProseMirror-menubar { + min-height: 32px; + color: var(--color); + background-color: var(--background); + border: none; + + .ProseMirror-menu-active { + border-radius: unset; + color: var(--color); + } + } + + .ProseMirror { + @extend .form-control; + + height: auto; + } +} + +.vh-100 { + height: 100vh !important; +} + +// 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; + } + + .line-clamp-#{$size} { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: $size; + -webkit-box-orient: vertical; + } +} + +/* + * 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}); + } + } +} + +@import "new_editor"; + +.new-editor { + .editor { + table { + @extend .table; + @extend .table-responsive; + } + } +} + +.content { + p { min-height: $font-size-base * $line-height-base; } + h1 { min-height: $h1-font-size * $headings-line-height; } + h2 { min-height: $h2-font-size * $headings-line-height; } + h3 { min-height: $h3-font-size * $headings-line-height; } + h4 { min-height: $h4-font-size * $headings-line-height; } + h5 { min-height: $h5-font-size * $headings-line-height; } + h6 { min-height: $h6-font-size * $headings-line-height; } + + iframe { border: 0; } + + audio { width: 100%; } + + img, + video, + iframe { + @extend .img-fluid; + height: auto; + } + + & > * { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + } +} diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 00000000..827e70fa --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,24 @@ +/* eslint no-console:0 */ + +import { Notifier } from '@airbrake/browser' + +window.airbrake = new Notifier({ + projectId: window.env.AIRBRAKE_SITE_ID, + projectKey: window.env.AIRBRAKE_API_KEY, + host: window.env.PANEL_URL +}) + +import './controllers' +import './editor/editor' +import 'fork-awesome/css/fork-awesome.css' +import './etc' + +import Rails from '@rails/ujs' +import Turbolinks from 'turbolinks' +import * as ActiveStorage from '@rails/activestorage' +import 'chartkick/chart.js' + +Rails.start() +Turbolinks.start() +ActiveStorage.start() +import * as bootstrap from "bootstrap" diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 6f53d84b..73605112 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -2,8 +2,12 @@ // Controller files must be named *_controller.js. import { Application } from "stimulus" -import { definitionsFromContext } from "stimulus/webpack-helpers" const application = Application.start() -const context = require.context("controllers", true, /_controller\.js$/) -application.load(definitionsFromContext(context)) + +import GeoController from "./geo_controller"; +import ReorderController from "./reorder_controller"; + +application.register("geo", GeoController); +application.register("reorder", ReorderController); + diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 85d5ab22..343e3b0b 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -13,9 +13,8 @@ %script{ type: 'text/javascript', src: '/env.js' } = csrf_meta_tags - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' - = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' - = stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' + = javascript_include_tag 'application', 'data-turbo-track': 'reload', defer: true + = stylesheet_link_tag 'styles', media: 'all', 'data-turbo-track': 'reload' = favicon_link_tag 'sutty_cuadrada.png', rel: 'apple-touch-icon', type: 'image/png' %body{ class: yield(:body) } diff --git a/bin/dev b/bin/dev new file mode 100755 index 00000000..74ade166 --- /dev/null +++ b/bin/dev @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +exec foreman start -f Procfile.dev "$@" diff --git a/config/environments/development.rb b/config/environments/development.rb index 8cca135d..94ef1727 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -61,7 +61,7 @@ Rails.application.configure do # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. - config.assets.debug = true + config.assets.debug = false # Suppress logger output for asset requests. config.assets.quiet = true diff --git a/package.json b/package.json index 0e82e2d9..a0224155 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "chart.js": "^3.5.1", "chartkick": "^4.0.5", "commonmark": "^0.30.0", + "esbuild": "^0.16.12", "fork-awesome": "^1.1.7", "input-map": "git+https://0xacab.org/sutty/input-map.git", "input-tag": "git+https://0xacab.org/sutty/input-tag.git", @@ -17,6 +18,7 @@ "prosemirror-example-setup": "^1.1.2", "prosemirror-markdown": "^1.4.5", "prosemirror-schema-basic": "^1.1.2", + "punycode": "^2.1.1", "stimulus": "^1.1.1", "turbolinks": "^5.2.0", "typescript": "^4.1.5", @@ -24,5 +26,8 @@ }, "devDependencies": { "@types/rails__activestorage": "^6.0.0" + }, + "scripts": { + "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets --loader:.png=file --loader:.jpg=file --loader:.woff2=file" } }