mirror of
https://0xacab.org/sutty/sutty
synced 2025-02-23 21:51:46 +00:00
feat(prosemirror): subir imágenes
This commit is contained in:
parent
0498a48b8c
commit
578ad08ac4
4 changed files with 124 additions and 2 deletions
|
@ -275,4 +275,8 @@ svg {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sutty-editor-loading-image {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import {toggleMark, setBlockType, wrapIn, baseKeymap} from "prosemirror-commands
|
||||||
import {toggleWrap, toggleBlockType, toggleList,
|
import {toggleWrap, toggleBlockType, toggleList,
|
||||||
updateMark, removeMark} from "tiptap-commands"
|
updateMark, removeMark} from "tiptap-commands"
|
||||||
|
|
||||||
import { DirectUpload } from "@rails/activestorage"
|
import {startImageUpload, placeholderPlugin} from "./imageUpload"
|
||||||
|
|
||||||
const toggleBold = toggleMark(schema.marks.strong)
|
const toggleBold = toggleMark(schema.marks.strong)
|
||||||
const toggleItalic = toggleMark(schema.marks.em)
|
const toggleItalic = toggleMark(schema.marks.em)
|
||||||
|
@ -102,6 +102,19 @@ class SuttyEditor {
|
||||||
setupBtn('ol', () => this.runCommand(toggleOl))
|
setupBtn('ol', () => this.runCommand(toggleOl))
|
||||||
setupBtn('h1', () => this.runCommand(toggleH1))
|
setupBtn('h1', () => this.runCommand(toggleH1))
|
||||||
setupBtn('h2', () => this.runCommand(toggleH2))
|
setupBtn('h2', () => this.runCommand(toggleH2))
|
||||||
|
|
||||||
|
const imgInput = el.querySelector('.img-input')
|
||||||
|
imgInput.addEventListener('change', event => {
|
||||||
|
if (
|
||||||
|
this.view.state.selection.$from.parent.inlineContent
|
||||||
|
&& event.target.files.length
|
||||||
|
) {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
event.target.value = ''
|
||||||
|
startImageUpload(this.view, file, schema)
|
||||||
|
}
|
||||||
|
this.view.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
buildState (markdown) {
|
buildState (markdown) {
|
||||||
|
@ -113,6 +126,7 @@ class SuttyEditor {
|
||||||
plugins: [
|
plugins: [
|
||||||
gapCursor(),
|
gapCursor(),
|
||||||
keymap(baseKeymap),
|
keymap(baseKeymap),
|
||||||
|
placeholderPlugin,
|
||||||
inputRules({
|
inputRules({
|
||||||
rules: [
|
rules: [
|
||||||
//emDash, // -- => —
|
//emDash, // -- => —
|
||||||
|
|
104
app/javascript/packs/imageUpload.js
Normal file
104
app/javascript/packs/imageUpload.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { DirectUpload } from "@rails/activestorage"
|
||||||
|
|
||||||
|
import {Plugin} from "prosemirror-state"
|
||||||
|
import {Decoration, DecorationSet} from "prosemirror-view"
|
||||||
|
|
||||||
|
let placeholderPlugin = new Plugin({
|
||||||
|
state: {
|
||||||
|
init() { return DecorationSet.empty },
|
||||||
|
apply(tr, set) {
|
||||||
|
// Adjust decoration positions to changes made by the transaction
|
||||||
|
set = set.map(tr.mapping, tr.doc)
|
||||||
|
// See if the transaction adds or removes any placeholders
|
||||||
|
let action = tr.getMeta(this)
|
||||||
|
if (action && action.add) {
|
||||||
|
let widget = document.createElement(
|
||||||
|
action.add.blobUrl ? 'img' : 'placeholder'
|
||||||
|
)
|
||||||
|
|
||||||
|
// mostrar imágen en cache mientras tanto
|
||||||
|
if (action.add.blobUrl) {
|
||||||
|
widget.src = action.add.blobUrl
|
||||||
|
widget.classList.add('sutty-editor-loading-image')
|
||||||
|
}
|
||||||
|
|
||||||
|
let deco = Decoration.widget(action.add.pos, widget, {id: action.add.id})
|
||||||
|
set = set.add(tr.doc, [deco])
|
||||||
|
} else if (action && action.remove) {
|
||||||
|
set = set.remove(
|
||||||
|
set.find(
|
||||||
|
null, null,
|
||||||
|
spec => spec.id == action.remove.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
decorations(state) { return this.getState(state) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export {placeholderPlugin}
|
||||||
|
|
||||||
|
function findPlaceholder(state, id) {
|
||||||
|
let decos = placeholderPlugin.getState(state)
|
||||||
|
let found = decos.find(null, null, spec => spec.id == id)
|
||||||
|
return found.length ? found[0].from : null
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: buscar una manera mejor de pasar el schema
|
||||||
|
export function startImageUpload (view, file, schema) {
|
||||||
|
const altText = window.prompt('descripción de imágen', '')
|
||||||
|
|
||||||
|
let id = {}
|
||||||
|
|
||||||
|
const blobUrl = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
// Replace the selection with a placeholder
|
||||||
|
let tr = view.state.tr
|
||||||
|
if (!tr.selection.empty) tr.deleteSelection()
|
||||||
|
tr.setMeta(placeholderPlugin, {
|
||||||
|
add: {id, pos: tr.selection.from, blobUrl},
|
||||||
|
})
|
||||||
|
view.dispatch(tr)
|
||||||
|
|
||||||
|
uploadFile(file).then(url => {
|
||||||
|
let pos = findPlaceholder(view.state, id)
|
||||||
|
// If the content around the placeholder has been deleted, drop
|
||||||
|
// the image
|
||||||
|
if (pos == null) return
|
||||||
|
|
||||||
|
// Otherwise, insert it at the placeholder's position, and remove
|
||||||
|
// the placeholder
|
||||||
|
view.dispatch(
|
||||||
|
view.state.tr
|
||||||
|
.replaceWith(pos, pos, schema.nodes.image.create({
|
||||||
|
src: url,
|
||||||
|
alt: altText.length ? altText : undefined,
|
||||||
|
}))
|
||||||
|
.setMeta(placeholderPlugin, {remove: {id}})
|
||||||
|
)
|
||||||
|
}, () => {
|
||||||
|
// On failure, just clean up the placeholder
|
||||||
|
view.dispatch(tr.setMeta(placeholderPlugin, {remove: {id}}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uploadFile (file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const upload = new DirectUpload(
|
||||||
|
file,
|
||||||
|
location.origin + '/rails/active_storage/direct_uploads',
|
||||||
|
)
|
||||||
|
|
||||||
|
upload.create((error, blob) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
const url = `${location.origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}`
|
||||||
|
resolve(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
%button.ul-btn= "ul"
|
%button.ul-btn= "ul"
|
||||||
%button.ol-btn= "ol"
|
%button.ol-btn= "ol"
|
||||||
%button.link-btn= "link"
|
%button.link-btn= "link"
|
||||||
%button.img-btn= "image"
|
%input.img-input{ type: 'file', accept: 'image/png, image/jpeg' }
|
||||||
%button.h1-btn= "h1"
|
%button.h1-btn= "h1"
|
||||||
%button.h2-btn= "h2"
|
%button.h2-btn= "h2"
|
||||||
.sutty-editor.sutty-editor-prosemirror.content{ 'data-sutty-identifier': identifier }
|
.sutty-editor.sutty-editor-prosemirror.content{ 'data-sutty-identifier': identifier }
|
||||||
|
|
Loading…
Reference in a new issue