5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-07-01 21:36:07 +00:00

reordenar artículos closes #74

This commit is contained in:
f 2020-11-19 17:44:13 -03:00
parent 0a27259f1a
commit f9fdc5d400
7 changed files with 218 additions and 106 deletions

View file

@ -22,8 +22,6 @@ const application = Application.start()
const context = require.context("./controllers", true, /\.js$/)
application.load(definitionsFromContext(context))
import tableDragger from 'table-dragger'
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {schema, defaultMarkdownParser, defaultMarkdownSerializer} from "prosemirror-markdown"
@ -103,19 +101,4 @@ document.addEventListener('turbolinks:load', () => {
// Ocultar el area
textArea.style.display = 'none'
})
document.querySelectorAll('.table-draggable').forEach(table => {
tableDragger(table, {
mode: 'row',
onlyBody: true,
dragHandler: '.handle'
}).on('drop', (from, to, el, mode) => {
Array.from(document.querySelectorAll('.reorder'))
.reverse()
.map((o,i) => o.value = i);
Array.from(document.querySelectorAll('.submit-reorder'))
.map(s => s.classList.remove('d-none'));
});
});
})

View file

@ -0,0 +1,190 @@
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import { Controller } from 'stimulus'
/*
* Permite reordenar las filas de una tabla.
*
* Cada fila tiene un selector que permite decidir si la fila se
* mantiene en su lugar o se mueve al presionar las teclas de subir y
* bajar.
*
* Se pueden mover varias juntas.
*
* El controlador está en la tabla y cada fila es un objetivo. Dentro
* de cada fila tiene que haber un input[type=checkbox] que determina si
* está seleccionada o no y un input[type=hidden] que contiene la
* posición actual que luego será guardada.
*
* La tabla tiene que estar rodeada de un formulario para poder enviar
* los datos.
*
* El objetivo es poder mover filas en tablas de miles de elementos.
*/
export default class extends Controller {
static targets = [ 'row', 'unselect', 'top', 'bottom', 'direction' ]
connect () {
// Deseleccionar
this.unselectTarget.addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
for (const r of Object.values(this.selected_rows)) {
r.row.querySelector('[data-reorder-handler]').click()
}
})
// Enviar arriba de todo
this.topTarget.addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
const rows = this.sorted_rows()
const first = rows[0].row.parentElement.firstElementChild
for (const r of rows) {
const row = r.row
if (row === first) continue
row.parentElement.insertBefore(row, first)
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
rows[0].row.scrollIntoViewIfNeeded()
})
// Enviar al final
this.bottomTarget.addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
const rows = this.sorted_rows()
for (const r of rows) {
const row = r.row
row.parentElement.appendChild(row)
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
rows[0].row.scrollIntoViewIfNeeded()
})
this.rowTargets.forEach(row => {
// Al cambiar los inputs, mantener la lista de filas actualizadas.
// Necesitamos saber la posición para poder mover las filas en
// orden en lugar del orden en que fueron seleccionadas.
row.querySelector('[data-reorder-handler]').addEventListener('change', event => {
if (event.target.checked) {
this.selected_rows[row.id] = {
row,
order: this.rowTargets.indexOf(row)
}
} else {
delete this.selected_rows[row.id]
}
})
})
this.directionTargets.forEach(dir => {
dir.addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
this.move(dir.dataset.direction)
})
})
// Lo asociamos al documento porque en la tabla se pierde el foco
// luego del primer evento.
document.addEventListener('keydown', event => {
if (!this.codes.includes(event.keyCode)) return
if (this.empty) return
event.preventDefault()
this.move(event.keyCode === 38 ? 'up' : 'down')
})
}
get selected_rows () {
if (!this._selected_rows) this._selected_rows = {}
return this._selected_rows
}
// Arriba, abajo
get codes () {
if (!this._codes) this._codes = [ 38, 40 ]
return this._codes
}
get empty () {
return (Object.keys(this.selected_rows).length === 0)
}
/*
* Aplica el nuevo orden en las filas y sus campos
*/
reorder () {
for (const r of Object.values(this.selected_rows)) {
this.selected_rows[r.row.id].order = this.rowTargets.indexOf(r.row)
}
const length = this.rowTargets.length
this.rowTargets.forEach((row, i) => {
row.querySelector('[data-reorder]').value = length - i
})
}
sorted_rows () {
return Object.values(this.selected_rows).sort((a,b) => a.order - b.order)
}
move (direction) {
if (this.empty) return
const up = direction === 'up'
const down = !up
const direction_sibling = up ? 'previousElementSibling' : 'nextElementSibling'
// Los movemos en orden
const rows = this.sorted_rows()
if (down) rows.reverse()
for (const r of rows) {
const row = r.row
const sibling = row[direction_sibling]
// Estamos en el tope?
if (!sibling || sibling.tagName !== row.tagName) continue
if (up) {
row.parentElement.insertBefore(row, sibling)
} else {
row.parentElement.insertBefore(sibling, row)
}
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
rows[0].row.scrollIntoViewIfNeeded()
}
}

View file

@ -38,8 +38,6 @@
- else
= form_tag site_posts_reorder_path, method: :post do
.d-flex.justify-content-between.align-items-center
= submit_tag t('posts.reorder'), class: 'btn submit-reorder'
-#
TODO: Pensar una interfaz mejor para cuando haya más de tres
idiomas
@ -48,8 +46,17 @@
- @site.locales.each do |locale|
= link_to t("locales.#{locale}.name"), site_posts_path(@site, locale: locale),
class: "mr-2 mt-2 mb-2#{locale == @locale ? 'active font-weight-bold' : ''}"
%table.table.table-draggable
%table.table{ data: { controller: 'reorder' } }
%caption.sr-only= t('posts.caption')
%thead
%tr
%th.border-0.background-white.position-sticky{ style: 'top: 0', colspan: '4' }
= submit_tag t('posts.reorder.submit'), class: 'btn'
%button.btn{ data: { target: 'reorder.unselect' } }= t('posts.reorder.unselect')
%button.btn{ data: { target: 'reorder.direction', direction: 'up' } }= t('posts.reorder.up')
%button.btn{ data: { target: 'reorder.direction', direction: 'down' } }= t('posts.reorder.down')
%button.btn{ data: { target: 'reorder.top' } }= t('posts.reorder.top')
%button.btn{ data: { target: 'reorder.bottom' } }= t('posts.reorder.bottom')
%tbody
- dir = t("locales.#{@locale}.dir")
- @posts.each_with_index do |post, i|
@ -57,13 +64,13 @@
TODO: Solo les usuaries cachean porque tenemos que separar
les botones por permisos.
- cache_if @usuarie, post do
%tr
%tr{ id: post.uuid.value, data: { target: 'reorder.row' } }
%td
.handle
= image_tag 'arrows-alt-v.svg'
%input{ type: 'checkbox', autocomplete: 'off', data: { reorder: { handler: true } } }
-# Orden más alto es mayor prioridad
= hidden_field 'post[reorder]', post.uuid.value,
value: @posts.length - i, class: 'reorder'
value: @posts.length - i,
data: { reorder: true }
%td.w-100{ class: dir }
%small
= link_to @site.i18n.dig('layouts', post.layout.name.to_s) || post.layout.name.to_s.humanize,

View file

@ -169,8 +169,6 @@ en:
usuarie:
edit: Edit my profile
category: 'Category'
posts:
reorder: 'You can drag and drop articles by the arrow icon (<i class="fa fa-arrows-v"></i>) and then press the "Reorder posts" button to save them in different order.'
i18n:
top: 'Back to top'
index: "Here you can edit your site's texts that don't belong to a post, like its description, sections, buttons... If you change languages up there in the title to be the same, you can edit them. If they're different, you can translate from one into the other."
@ -471,7 +469,13 @@ en:
image:
label: Imagen
destroy: Remove image
reorder: 'Save posts order'
reorder:
submit: 'Save order'
unselect: 'Deselected all'
top: 'Send to top'
bottom: 'Send to bottom'
up: 'Up'
down: 'Down'
sort:
by: 'Sort by'
order: 'order'

View file

@ -165,10 +165,6 @@ es:
usuarie:
edit: Editar mi perfil
category: 'Categoría'
posts:
reorder: 'Puedes arrastrar y soltar los artículos por el ícono de
las flechas (<i class="fa fa-arrows-v"></i>) y luego presionar el
botón "Reordenar artículos" para guardarlos en ese orden.'
i18n:
top: 'Volver al principio'
index: 'Aquí puedes editar todos los textos del sitio que no se
@ -486,7 +482,13 @@ es:
image:
label: Imagen
destroy: 'Eliminar imagen'
reorder: 'Guardar orden de artículos'
reorder:
submit: 'Guardar orden'
unselect: 'Deseleccionar todos'
top: 'Enviar al principio'
bottom: 'Enviar al final'
up: 'Subir'
down: 'Bajar'
sort:
by: 'Ordenar por'
order: 'posición'

View file

@ -12,7 +12,6 @@
"prosemirror-markdown": "^1.4.5",
"prosemirror-schema-basic": "^1.1.2",
"stimulus": "^1.1.1",
"table-dragger": "git+https://0xacab.org/sutty/table-dragger.git",
"zepto": "^1.2.0"
},
"devDependencies": {

View file

@ -579,14 +579,6 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.5.4"
"@babel/polyfill@^7.4.0":
version "7.7.0"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.7.0.tgz#e1066e251e17606ec7908b05617f9b7f8180d8f3"
integrity sha512-/TS23MVvo34dFmf8mwCisCbWGrfhbiWZSwBo6HkADTBhUa2Q/jWltyY/tpofz/b6/RIhqaqQcquptCirqIhOaQ==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/preset-env@^7.4.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.5.tgz#bc470b53acaa48df4b8db24a570d6da1fef53c9a"
@ -1173,10 +1165,6 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
atoa@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/atoa/-/atoa-1.0.0.tgz#0cc0e91a480e738f923ebc103676471779b34a49"
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@ -1858,13 +1846,6 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
contra@1.9.4:
version "1.9.4"
resolved "https://registry.yarnpkg.com/contra/-/contra-1.9.4.tgz#f53bde42d7e5b5985cae4d99a8d610526de8f28d"
dependencies:
atoa "1.0.0"
ticky "1.0.1"
convert-source-map@^1.1.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
@ -1907,16 +1888,6 @@ core-js-compat@^3.1.1:
browserslist "^4.6.6"
semver "^6.3.0"
core-js@^2.6.5:
version "2.6.10"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f"
integrity sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==
core-js@^3.0.1:
version "3.3.6"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.3.6.tgz#6ad1650323c441f45379e176ed175c0d021eac92"
integrity sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA==
core-js@^3.1.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09"
@ -1992,12 +1963,6 @@ cross-spawn@^3.0.0:
lru-cache "^4.0.1"
which "^1.2.9"
crossvent@1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.4.tgz#da2c4f8f40c94782517bf2beec1044148194ab92"
dependencies:
custom-event "1.0.0"
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -2205,10 +2170,6 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
custom-event@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.0.tgz#2e4628be19dc4b214b5c02630c5971e811618062"
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@ -2434,14 +2395,6 @@ dot-prop@^4.1.1:
dependencies:
is-obj "^1.0.0"
dragula@^3.7.0:
version "3.7.2"
resolved "https://registry.yarnpkg.com/dragula/-/dragula-3.7.2.tgz#4a35c9d3981ffac1a949c29ca7285058e87393ce"
integrity sha1-SjXJ05gf+sGpScKcpyhQWOhzk84=
dependencies:
contra "1.9.4"
crossvent "1.5.4"
duplexify@^3.4.2, duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@ -5899,11 +5852,6 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
ramda@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -6222,13 +6170,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -6842,16 +6783,6 @@ svgo@^1.0.0:
unquote "~1.1.1"
util.promisify "~1.0.0"
"table-dragger@git+https://0xacab.org/sutty/table-dragger.git":
version "2.0.2"
resolved "git+https://0xacab.org/sutty/table-dragger.git#a5199975398dca9b3a849f5d56220fd11e68733c"
dependencies:
"@babel/polyfill" "^7.4.0"
core-js "^3.0.1"
dragula "^3.7.0"
ramda "^0.26.1"
rxjs "^6.5.2"
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
@ -6916,10 +6847,6 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826"
integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==
ticky@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ticky/-/ticky-1.0.1.tgz#b7cfa71e768f1c9000c497b9151b30947c50e46d"
timers-browserify@^2.0.4:
version "2.0.11"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"