mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-15 07:51:42 +00:00
Merge branch 'editor-en-webpack' into editor
This commit is contained in:
commit
db0a32a930
7 changed files with 88 additions and 69 deletions
|
@ -1,3 +1,13 @@
|
||||||
|
import {
|
||||||
|
moveChildren,
|
||||||
|
marks,
|
||||||
|
blocks,
|
||||||
|
parentBlocks,
|
||||||
|
typesWithProperties,
|
||||||
|
setAuxiliaryToolbar,
|
||||||
|
tagNameSetFn
|
||||||
|
} from 'editor/types'
|
||||||
|
|
||||||
const origin = location.origin
|
const origin = location.origin
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -27,7 +37,7 @@ const restoreContent = (editorEl, contentEl) => {
|
||||||
|
|
||||||
/* getRangeAt puede fallar si no hay una selección
|
/* getRangeAt puede fallar si no hay una selección
|
||||||
*/
|
*/
|
||||||
function safeGetRangeAt (num) {
|
const safeGetRangeAt = (num) => {
|
||||||
try {
|
try {
|
||||||
return window.getSelection().getRangeAt(num)
|
return window.getSelection().getRangeAt(num)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -36,48 +46,27 @@ function safeGetRangeAt (num) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile (file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const upload = new ActiveStorage.DirectUpload(
|
|
||||||
file,
|
|
||||||
origin + '/rails/active_storage/direct_uploads',
|
|
||||||
)
|
|
||||||
|
|
||||||
upload.create((error, blob) => {
|
const isDirectChild = (node, supposedChild) => {
|
||||||
if (error) {
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
const url = `${origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}`
|
|
||||||
resolve(url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveChildren (from, to, toRef) {
|
|
||||||
while (from.firstChild) to.insertBefore(from.firstChild, toRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDirectChild (node, supposedChild) {
|
|
||||||
for (const child of node.childNodes) {
|
for (const child of node.childNodes) {
|
||||||
if (child == supposedChild) return true
|
if (child == supposedChild) return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isChildSelection (sel, el) {
|
const isChildSelection = (sel, el) => {
|
||||||
return (
|
return (
|
||||||
(el.contains(sel.anchorNode) || el.contains(sel.focusNode))
|
(el.contains(sel.anchorNode) || el.contains(sel.focusNode))
|
||||||
&& !(sel.anchorNode == el || sel.focusNode == el)
|
&& !(sel.anchorNode == el || sel.focusNode == el)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElementParent (node) {
|
const getElementParent = (node) => {
|
||||||
let parentEl = node
|
let parentEl = node
|
||||||
while (parentEl.nodeType != Node.ELEMENT_NODE) parentEl = parentEl.parentElement
|
while (parentEl.nodeType != Node.ELEMENT_NODE) parentEl = parentEl.parentElement
|
||||||
return parentEl
|
return parentEl
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitNode (node, range) {
|
const splitNode = (node, range) => {
|
||||||
const [left, right] = [
|
const [left, right] = [
|
||||||
{ range: document.createRange(), node: node.cloneNode(false) },
|
{ range: document.createRange(), node: node.cloneNode(false) },
|
||||||
{ range: document.createRange(), node: node.cloneNode(false) },
|
{ range: document.createRange(), node: node.cloneNode(false) },
|
||||||
|
@ -109,7 +98,7 @@ function splitNode (node, range) {
|
||||||
* * mark: un objeto que representa el tipo de acción (ver types.js)
|
* * mark: un objeto que representa el tipo de acción (ver types.js)
|
||||||
* * contentEl: el elemento de contenido del editor.
|
* * contentEl: el elemento de contenido del editor.
|
||||||
*/
|
*/
|
||||||
function setupMarkButton (button, mark, contentEl) {
|
const setupMarkButton = (button, mark, contentEl) => {
|
||||||
button.addEventListener("click", event => {
|
button.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
@ -168,7 +157,7 @@ function setupMarkButton (button, mark, contentEl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Igual que `setupMarkButton` pero para bloques. */
|
/* Igual que `setupMarkButton` pero para bloques. */
|
||||||
function setupBlockButton (button, block, contentEl, editorEl) {
|
const setupBlockButton = (button, block, contentEl, editorEl) => {
|
||||||
button.addEventListener("click", event => {
|
button.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
@ -203,7 +192,7 @@ function setupBlockButton (button, block, contentEl, editorEl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Igual que `setupBlockButton` pero para bloques parientes. */
|
/* Igual que `setupBlockButton` pero para bloques parientes. */
|
||||||
function setupParentBlockButton (button, parentBlock, contentEl) {
|
const setupParentBlockButton = (button, parentBlock, contentEl) => {
|
||||||
button.addEventListener("click", event => {
|
button.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
@ -246,7 +235,7 @@ const elementIsTypes = types => element => {
|
||||||
const elementIsBlock = elementIsTypes(blocks)
|
const elementIsBlock = elementIsTypes(blocks)
|
||||||
const elementIsParentBlock = elementIsTypes(parentBlocks)
|
const elementIsParentBlock = elementIsTypes(parentBlocks)
|
||||||
|
|
||||||
function hasContent (element) {
|
const hasContent = (element) => {
|
||||||
if (element.firstElementChild) return true
|
if (element.firstElementChild) return true
|
||||||
for (const child of element.childNodes) {
|
for (const child of element.childNodes) {
|
||||||
if (child.nodeType === Node.TEXT_NODE && child.data.length > 0) return true
|
if (child.nodeType === Node.TEXT_NODE && child.data.length > 0) return true
|
||||||
|
@ -266,7 +255,7 @@ function hasContent (element) {
|
||||||
* * Cambia el tag de los bloques no reconocidos (ver `elementIsBlock`)
|
* * Cambia el tag de los bloques no reconocidos (ver `elementIsBlock`)
|
||||||
* * Hace lo que hace cleanNode
|
* * Hace lo que hace cleanNode
|
||||||
*/
|
*/
|
||||||
function cleanContent (contentEl) {
|
const cleanContent = (contentEl) => {
|
||||||
const sel = window.getSelection()
|
const sel = window.getSelection()
|
||||||
|
|
||||||
cleanNode(contentEl, contentEl)
|
cleanNode(contentEl, contentEl)
|
||||||
|
@ -276,7 +265,10 @@ function cleanContent (contentEl) {
|
||||||
if (elementIsParentBlock(child)) {
|
if (elementIsParentBlock(child)) {
|
||||||
cleanContent(child)
|
cleanContent(child)
|
||||||
} else if (!elementIsBlock(child)) {
|
} else if (!elementIsBlock(child)) {
|
||||||
child.tagName = "P"
|
const el = document.createElement("p")
|
||||||
|
moveChildren(child, el, null)
|
||||||
|
contentEl.insertBefore(el, child)
|
||||||
|
child.parentNode.removeChild(child)
|
||||||
}
|
}
|
||||||
} else if (child.nodeType === Node.TEXT_NODE) {
|
} else if (child.nodeType === Node.TEXT_NODE) {
|
||||||
const el = document.createElement("p")
|
const el = document.createElement("p")
|
||||||
|
@ -293,7 +285,7 @@ function cleanContent (contentEl) {
|
||||||
* * Crea un p y inserta la selección si no hay elementos
|
* * Crea un p y inserta la selección si no hay elementos
|
||||||
* * Wrappea el contenido de un UL o OL en un LI si no lo está
|
* * Wrappea el contenido de un UL o OL en un LI si no lo está
|
||||||
*/
|
*/
|
||||||
function fixContent (contentEl) {
|
const fixContent = (contentEl) => {
|
||||||
for (const child of contentEl.childNodes) {
|
for (const child of contentEl.childNodes) {
|
||||||
if (child.tagName) {
|
if (child.tagName) {
|
||||||
if (elementIsParentBlock(child)) {
|
if (elementIsParentBlock(child)) {
|
||||||
|
@ -323,7 +315,7 @@ function fixContent (contentEl) {
|
||||||
* * Borra propiedades de IMG no autorizadas
|
* * Borra propiedades de IMG no autorizadas
|
||||||
* * Borra <FONT> y <STYLE>
|
* * Borra <FONT> y <STYLE>
|
||||||
*/
|
*/
|
||||||
function cleanNode (node, contentEl) {
|
const cleanNode = (node, contentEl) => {
|
||||||
for (const child of node.childNodes) {
|
for (const child of node.childNodes) {
|
||||||
if (child.nodeType === Node.TEXT_NODE) {
|
if (child.nodeType === Node.TEXT_NODE) {
|
||||||
if (child.nextSibling && child.nextSibling.nodeType === Node.TEXT_NODE) {
|
if (child.nextSibling && child.nextSibling.nodeType === Node.TEXT_NODE) {
|
||||||
|
@ -407,10 +399,10 @@ function cleanNode (node, contentEl) {
|
||||||
|
|
||||||
/* Generar el clickListener para este editor.
|
/* Generar el clickListener para este editor.
|
||||||
*/
|
*/
|
||||||
function generateClickListener (editorEl, contentEl) {
|
const generateClickListener = (editorEl, contentEl) => {
|
||||||
/* El event listener para los typesWithProperties.
|
/* El event listener para los typesWithProperties.
|
||||||
*/
|
*/
|
||||||
return function clickListener (event) {
|
return (event) => {
|
||||||
// Borrar todas las selecciones
|
// Borrar todas las selecciones
|
||||||
for (const el of contentEl.querySelectorAll(".selected")) {
|
for (const el of contentEl.querySelectorAll(".selected")) {
|
||||||
el.classList.remove("selected")
|
el.classList.remove("selected")
|
||||||
|
@ -441,7 +433,7 @@ function generateClickListener (editorEl, contentEl) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEditor (editorEl) {
|
const setupEditor = (editorEl) => {
|
||||||
// XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor?
|
// XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor?
|
||||||
document.execCommand('defaultParagraphSeparator', false, 'p')
|
document.execCommand('defaultParagraphSeparator', false, 'p')
|
||||||
|
|
||||||
|
@ -450,9 +442,8 @@ function setupEditor (editorEl) {
|
||||||
contentEl.addEventListener("paste", event => {
|
contentEl.addEventListener("paste", event => {
|
||||||
editorEl.querySelector(".editor-aviso-word").style.display = "block"
|
editorEl.querySelector(".editor-aviso-word").style.display = "block"
|
||||||
})
|
})
|
||||||
document.addEventListener("selectionchange", event => {
|
|
||||||
cleanContent(contentEl)
|
document.addEventListener("selectionchange", event => cleanContent(contentEl))
|
||||||
})
|
|
||||||
|
|
||||||
const clickListener = generateClickListener(editorEl, contentEl)
|
const clickListener = generateClickListener(editorEl, contentEl)
|
||||||
contentEl.addEventListener("click", clickListener, true)
|
contentEl.addEventListener("click", clickListener, true)
|
||||||
|
@ -474,15 +465,6 @@ function setupEditor (editorEl) {
|
||||||
|
|
||||||
const editorBtn = id => editorEl.querySelector(`*[data-button="${id}"]`)
|
const editorBtn = id => editorEl.querySelector(`*[data-button="${id}"]`)
|
||||||
|
|
||||||
// XXX: Por qué está duplicada de types.js esta función?
|
|
||||||
const tagNameSetFn = tagName => el => {
|
|
||||||
const newEl = document.createElement(tagName)
|
|
||||||
moveChildren(el, newEl, null)
|
|
||||||
el.parentNode.insertBefore(newEl, el)
|
|
||||||
el.parentNode.removeChild(el)
|
|
||||||
window.getSelection().collapse(newEl, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// == SETUP BUTTONS ==
|
// == SETUP BUTTONS ==
|
||||||
for (const [name, mark] of Object.entries(marks)) {
|
for (const [name, mark] of Object.entries(marks)) {
|
||||||
setupMarkButton(editorBtn(name), mark, contentEl)
|
setupMarkButton(editorBtn(name), mark, contentEl)
|
||||||
|
@ -529,9 +511,7 @@ function setupEditor (editorEl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: por ahora confiamos, quizás queremos filtrar estilos?
|
// TODO: por ahora confiamos, quizás queremos filtrar estilos?
|
||||||
function stringifyAllowedStyle (element) {
|
const stringifyAllowedStyle = (element) => element.style.cssText
|
||||||
return element.style.cssText
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("turbolinks:load", () => {
|
document.addEventListener("turbolinks:load", () => {
|
||||||
for (const editorEl of document.querySelectorAll(".editor")) {
|
for (const editorEl of document.querySelectorAll(".editor")) {
|
|
@ -1,4 +1,4 @@
|
||||||
function setAuxiliaryToolbar (editorEl, toolbarName) {
|
export const setAuxiliaryToolbar = (editorEl, toolbarName) => {
|
||||||
const toolbarEl = editorEl.querySelector(`*[data-editor-auxiliary-toolbar]`)
|
const toolbarEl = editorEl.querySelector(`*[data-editor-auxiliary-toolbar]`)
|
||||||
for (const otherEl of toolbarEl.childNodes) {
|
for (const otherEl of toolbarEl.childNodes) {
|
||||||
if (otherEl.nodeType !== Node.ELEMENT_NODE) continue
|
if (otherEl.nodeType !== Node.ELEMENT_NODE) continue
|
||||||
|
@ -10,7 +10,29 @@ function setAuxiliaryToolbar (editorEl, toolbarName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const marks = {
|
export const moveChildren = (from, to, toRef) => {
|
||||||
|
while (from.firstChild) to.insertBefore(from.firstChild, toRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFile = (file) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const upload = new ActiveStorage.DirectUpload(
|
||||||
|
file,
|
||||||
|
origin + '/rails/active_storage/direct_uploads',
|
||||||
|
)
|
||||||
|
|
||||||
|
upload.create((error, blob) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
const url = `${origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}`
|
||||||
|
resolve(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const marks = {
|
||||||
bold: {
|
bold: {
|
||||||
selector: "strong",
|
selector: "strong",
|
||||||
createFn: () => document.createElement("STRONG"),
|
createFn: () => document.createElement("STRONG"),
|
||||||
|
@ -45,7 +67,7 @@ const marks = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagNameSetFn = tagName => el => {
|
export const tagNameSetFn = tagName => el => {
|
||||||
const newEl = document.createElement(tagName)
|
const newEl = document.createElement(tagName)
|
||||||
moveChildren(el, newEl, null)
|
moveChildren(el, newEl, null)
|
||||||
el.parentNode.insertBefore(newEl, el)
|
el.parentNode.insertBefore(newEl, el)
|
||||||
|
@ -53,7 +75,7 @@ const tagNameSetFn = tagName => el => {
|
||||||
window.getSelection().collapse(newEl, 0)
|
window.getSelection().collapse(newEl, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocks = {
|
export const blocks = {
|
||||||
p: {
|
p: {
|
||||||
noButton: true,
|
noButton: true,
|
||||||
selector: "P",
|
selector: "P",
|
||||||
|
@ -158,7 +180,7 @@ const divWithStyleCreateFn = styleFn => () => {
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentBlocks = {
|
export const parentBlocks = {
|
||||||
left: {
|
left: {
|
||||||
selector: "div[data-align=left]",
|
selector: "div[data-align=left]",
|
||||||
createFn: divWithStyleCreateFn(el => el.dataset.align = "left"),
|
createFn: divWithStyleCreateFn(el => el.dataset.align = "left"),
|
||||||
|
@ -173,19 +195,18 @@ const parentBlocks = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hex = (x) => ("0" + parseInt(x).toString(16)).slice(-2)
|
||||||
|
|
||||||
// https://stackoverflow.com/a/3627747
|
// https://stackoverflow.com/a/3627747
|
||||||
// TODO: cambiar por una solución más copada
|
// TODO: cambiar por una solución más copada
|
||||||
function rgb2hex(rgb) {
|
const rgb2hex = (rgb) => {
|
||||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||||
function hex(x) {
|
|
||||||
return ("0" + parseInt(x).toString(16)).slice(-2);
|
|
||||||
}
|
|
||||||
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSelected = contentEl => contentEl.querySelector(".selected")
|
const getSelected = contentEl => contentEl.querySelector(".selected")
|
||||||
|
|
||||||
const typesWithProperties = {
|
export const typesWithProperties = {
|
||||||
a: {
|
a: {
|
||||||
selector: marks.a.selector,
|
selector: marks.a.selector,
|
||||||
updateInput (el, editorEl) {
|
updateInput (el, editorEl) {
|
|
@ -26,6 +26,7 @@ window.airbrake = new Notifier({
|
||||||
import 'core-js/stable'
|
import 'core-js/stable'
|
||||||
import 'regenerator-runtime/runtime'
|
import 'regenerator-runtime/runtime'
|
||||||
import 'controllers'
|
import 'controllers'
|
||||||
|
import 'editor/editor'
|
||||||
|
|
||||||
import {EditorState} from "prosemirror-state"
|
import {EditorState} from "prosemirror-state"
|
||||||
import {EditorView} from "prosemirror-view"
|
import {EditorView} from "prosemirror-view"
|
||||||
|
|
|
@ -59,6 +59,8 @@ class DeployLocal < Deploy
|
||||||
'PATH' => paths.join(':'),
|
'PATH' => paths.join(':'),
|
||||||
'SPREE_API_KEY' => site.tienda_api_key,
|
'SPREE_API_KEY' => site.tienda_api_key,
|
||||||
'SPREE_URL' => site.tienda_url,
|
'SPREE_URL' => site.tienda_url,
|
||||||
|
'AIRBRAKE_PROJECT_ID' => site.id.to_s,
|
||||||
|
'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key,
|
||||||
'JEKYLL_ENV' => Rails.env,
|
'JEKYLL_ENV' => Rails.env,
|
||||||
'LANG' => ENV['LANG']
|
'LANG' => ENV['LANG']
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,10 +141,17 @@ class Post < OpenStruct
|
||||||
end
|
end
|
||||||
alias to_param id
|
alias to_param id
|
||||||
|
|
||||||
|
# Fecha de última modificación del archivo
|
||||||
def updated_at
|
def updated_at
|
||||||
File.mtime(path.absolute)
|
File.mtime(path.absolute)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Obtiene la fecha actual de modificación y la guarda hasta la próxima
|
||||||
|
# vez.
|
||||||
|
def modified_at
|
||||||
|
@modified_at ||= Time.now
|
||||||
|
end
|
||||||
|
|
||||||
# Solo ejecuta la magia de OpenStruct si el campo existe en la
|
# Solo ejecuta la magia de OpenStruct si el campo existe en la
|
||||||
# plantilla
|
# plantilla
|
||||||
#
|
#
|
||||||
|
@ -215,6 +222,7 @@ class Post < OpenStruct
|
||||||
# Y que no se procese liquid
|
# Y que no se procese liquid
|
||||||
yaml['liquid'] = false
|
yaml['liquid'] = false
|
||||||
yaml['usuaries'] = usuaries.map(&:id).uniq
|
yaml['usuaries'] = usuaries.map(&:id).uniq
|
||||||
|
yaml['last_modified_at'] = modified_at
|
||||||
|
|
||||||
"#{yaml.to_yaml}---\n\n#{body}"
|
"#{yaml.to_yaml}---\n\n#{body}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,23 +4,30 @@ class Site
|
||||||
module Api
|
module Api
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
AIRBRAKE_SECRET = 'an api key for airbrake'
|
|
||||||
|
|
||||||
included do
|
included do
|
||||||
encrypts :api_key
|
encrypts :api_key
|
||||||
before_save :add_api_key_if_missing!
|
before_save :add_api_key_if_missing!
|
||||||
|
|
||||||
# Genera mensajes secretos que podemos usar para la API de cada sitio.
|
# Genera mensajes secretos que podemos usar para la API de cada
|
||||||
|
# sitio.
|
||||||
|
#
|
||||||
|
# XXX: Si no se configura una API key del sitio o genérica, no
|
||||||
|
# tenemos forma de verificar los mensajes, pero la generación de
|
||||||
|
# llaves no va a fallar.
|
||||||
def verifier
|
def verifier
|
||||||
@verifier ||= ActiveSupport::MessageVerifier.new api_key
|
@verifier ||= ActiveSupport::MessageVerifier.new(api_key || Rails.application.credentials.api_key || SecureRandom.hex(64))
|
||||||
end
|
end
|
||||||
|
|
||||||
def airbrake_api_key
|
def airbrake_api_key
|
||||||
@airbrake_api_key ||= verifier.generate(AIRBRAKE_SECRET, purpose: :airbrake)
|
@airbrake_api_key ||= verifier.generate(airbrake_secret, purpose: :airbrake)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def airbrake_secret
|
||||||
|
Rails.application.credentials.airbrake || SecureRandom.hex(64)
|
||||||
|
end
|
||||||
|
|
||||||
# Asegurarse que el sitio tenga una llave para la API
|
# Asegurarse que el sitio tenga una llave para la API
|
||||||
def add_api_key_if_missing!
|
def add_api_key_if_missing!
|
||||||
self.api_key ||= SecureRandom.hex(64)
|
self.api_key ||= SecureRandom.hex(64)
|
||||||
|
|
|
@ -107,7 +107,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
|
||||||
params: {
|
params: {
|
||||||
post: {
|
post: {
|
||||||
image: {
|
image: {
|
||||||
path: fixture_file_upload('files/logo.png', 'image/png'),
|
path: fixture_file_upload('logo.png', 'image/png'),
|
||||||
description: 'hola'
|
description: 'hola'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
|
||||||
params: {
|
params: {
|
||||||
post: {
|
post: {
|
||||||
image: {
|
image: {
|
||||||
path: fixture_file_upload('files/_logo.png', 'image/png'),
|
path: fixture_file_upload('_logo.png', 'image/png'),
|
||||||
description: 'hola'
|
description: 'hola'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue