mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-17 06:16:23 +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;
|
||||
}
|
||||
}
|
||||
|
||||
.sutty-editor-loading-image {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import {toggleMark, setBlockType, wrapIn, baseKeymap} from "prosemirror-commands
|
|||
import {toggleWrap, toggleBlockType, toggleList,
|
||||
updateMark, removeMark} from "tiptap-commands"
|
||||
|
||||
import { DirectUpload } from "@rails/activestorage"
|
||||
import {startImageUpload, placeholderPlugin} from "./imageUpload"
|
||||
|
||||
const toggleBold = toggleMark(schema.marks.strong)
|
||||
const toggleItalic = toggleMark(schema.marks.em)
|
||||
|
@ -102,6 +102,19 @@ class SuttyEditor {
|
|||
setupBtn('ol', () => this.runCommand(toggleOl))
|
||||
setupBtn('h1', () => this.runCommand(toggleH1))
|
||||
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) {
|
||||
|
@ -113,6 +126,7 @@ class SuttyEditor {
|
|||
plugins: [
|
||||
gapCursor(),
|
||||
keymap(baseKeymap),
|
||||
placeholderPlugin,
|
||||
inputRules({
|
||||
rules: [
|
||||
//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.ol-btn= "ol"
|
||||
%button.link-btn= "link"
|
||||
%button.img-btn= "image"
|
||||
%input.img-input{ type: 'file', accept: 'image/png, image/jpeg' }
|
||||
%button.h1-btn= "h1"
|
||||
%button.h2-btn= "h2"
|
||||
.sutty-editor.sutty-editor-prosemirror.content{ 'data-sutty-identifier': identifier }
|
||||
|
|
Loading…
Reference in a new issue