Finished update pass of all md editor actions to cm6

This commit is contained in:
Dan Brown 2023-04-13 17:18:32 +01:00
parent 32c765d0c3
commit 6f45d34bf8
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
2 changed files with 78 additions and 87 deletions

View File

@ -296,10 +296,11 @@ export class Actions {
} }
replaceLineStartForOrderedList() { replaceLineStartForOrderedList() {
// TODO const selectionRange = this.#getSelectionRange();
const cursor = this.editor.cm.getCursor(); const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
const prevLineContent = this.editor.cm.getLine(cursor.line - 1) || ''; const prevLine = this.editor.cm.state.doc.line(line.number - 1);
const listMatch = prevLineContent.match(/^(\s*)(\d)([).])\s/) || [];
const listMatch = prevLine.text.match(/^(\s*)(\d)([).])\s/) || [];
const number = (Number(listMatch[2]) || 0) + 1; const number = (Number(listMatch[2]) || 0) + 1;
const whiteSpace = listMatch[1] || ''; const whiteSpace = listMatch[1] || '';
@ -314,37 +315,28 @@ export class Actions {
* Creates a callout block if none existing, and removes it if cycling past the danger type. * Creates a callout block if none existing, and removes it if cycling past the danger type.
*/ */
cycleCalloutTypeAtSelection() { cycleCalloutTypeAtSelection() {
// TODO const selectionRange = this.#getSelectionRange();
const selectionRange = this.editor.cm.listSelections()[0]; const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
const lineContent = this.editor.cm.getLine(selectionRange.anchor.line);
const lineLength = lineContent.length;
const contentRange = {
anchor: {line: selectionRange.anchor.line, ch: 0},
head: {line: selectionRange.anchor.line, ch: lineLength},
};
const formats = ['info', 'success', 'warning', 'danger']; const formats = ['info', 'success', 'warning', 'danger'];
const joint = formats.join('|'); const joint = formats.join('|');
const regex = new RegExp(`class="((${joint})\\s+callout|callout\\s+(${joint}))"`, 'i'); const regex = new RegExp(`class="((${joint})\\s+callout|callout\\s+(${joint}))"`, 'i');
const matches = regex.exec(lineContent); const matches = regex.exec(line.text);
const format = (matches ? (matches[2] || matches[3]) : '').toLowerCase(); const format = (matches ? (matches[2] || matches[3]) : '').toLowerCase();
if (format === formats[formats.length - 1]) { if (format === formats[formats.length - 1]) {
this.wrapLine(`<p class="callout ${formats[formats.length - 1]}">`, '</p>'); this.#wrapLine(`<p class="callout ${formats[formats.length - 1]}">`, '</p>');
} else if (format === '') { } else if (format === '') {
this.wrapLine('<p class="callout info">', '</p>'); this.#wrapLine('<p class="callout info">', '</p>');
} else { } else {
const newFormatIndex = formats.indexOf(format) + 1; const newFormatIndex = formats.indexOf(format) + 1;
const newFormat = formats[newFormatIndex]; const newFormat = formats[newFormatIndex];
const newContent = lineContent.replace(matches[0], matches[0].replace(format, newFormat)); const newContent = line.text.replace(matches[0], matches[0].replace(format, newFormat));
this.editor.cm.replaceRange(newContent, contentRange.anchor, contentRange.head); const lineDiff = newContent.length - line.text.length;
this.editor.cm.dispatch({
const chDiff = newContent.length - lineContent.length; changes: {from: line.from, to: line.to, insert: newContent},
selectionRange.anchor.ch += chDiff; selection: {anchor: selectionRange.anchor + lineDiff, head: selectionRange.head + lineDiff},
if (selectionRange.anchor !== selectionRange.head) { });
selectionRange.head.ch += chDiff;
}
this.editor.cm.setSelection(selectionRange.anchor, selectionRange.head);
} }
} }
@ -372,38 +364,43 @@ export class Actions {
* @param {Number} posX * @param {Number} posX
* @param {Number} posY * @param {Number} posY
*/ */
insertTemplate(templateId, posX, posY) { async insertTemplate(templateId, posX, posY) {
// TODO const cursorPos = this.editor.cm.posAtCoords({x: posX, y: posY}, false);
const cursorPos = this.editor.cm.coordsChar({left: posX, top: posY}); const {data} = await window.$http.get(`/templates/${templateId}`);
this.editor.cm.setCursor(cursorPos); const content = data.markdown || data.html;
window.$http.get(`/templates/${templateId}`).then(resp => { this.editor.cm.dispatch({
const content = resp.data.markdown || resp.data.html; changes: {from: cursorPos, to: cursorPos, insert: content},
this.editor.cm.replaceSelection(content); selection: {anchor: cursorPos},
}); });
} }
/** /**
* Insert multiple images from the clipboard. * Insert multiple images from the clipboard from an event at the provided
* screen coordinates (Typically form a paste event).
* @param {File[]} images * @param {File[]} images
* @param {Number} posX
* @param {Number} posY
*/ */
insertClipboardImages(images) { insertClipboardImages(images, posX, posY) {
// TODO const cursorPos = this.editor.cm.posAtCoords({x: posX, y: posY}, false);
const cursorPos = this.editor.cm.coordsChar({left: event.pageX, top: event.pageY});
this.editor.cm.setCursor(cursorPos);
for (const image of images) { for (const image of images) {
this.#uploadImage(image); this.uploadImage(image, cursorPos);
} }
} }
/** /**
* Handle image upload and add image into markdown content * Handle image upload and add image into markdown content
* @param {File} file * @param {File} file
* @param {?Number} position
*/ */
#uploadImage(file) { async uploadImage(file, position= null) {
// TODO
if (file === null || file.type.indexOf('image') !== 0) return; if (file === null || file.type.indexOf('image') !== 0) return;
let ext = 'png'; let ext = 'png';
if (position === null) {
position = this.#getSelectionRange().from;
}
if (file.name) { if (file.name) {
let fileNameMatches = file.name.match(/\.(.+)$/); let fileNameMatches = file.name.match(/\.(.+)$/);
if (fileNameMatches.length > 1) ext = fileNameMatches[1]; if (fileNameMatches.length > 1) ext = fileNameMatches[1];
@ -412,25 +409,26 @@ export class Actions {
// Insert image into markdown // Insert image into markdown
const id = "image-" + Math.random().toString(16).slice(2); const id = "image-" + Math.random().toString(16).slice(2);
const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`); const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
const selectedText = this.editor.cm.getSelection(); const placeHolderText = `![](${placeholderImage})`;
const placeHolderText = `![${selectedText}](${placeholderImage})`; this.editor.cm.dispatch({
const cursor = this.editor.cm.getCursor(); changes: {from: position, to: position, insert: placeHolderText},
this.editor.cm.replaceSelection(placeHolderText); selection: {anchor: position},
this.editor.cm.setCursor({line: cursor.line, ch: cursor.ch + selectedText.length + 3}); });
const remoteFilename = "image-" + Date.now() + "." + ext; const remoteFilename = "image-" + Date.now() + "." + ext;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file, remoteFilename); formData.append('file', file, remoteFilename);
formData.append('uploaded_to', this.editor.config.pageId); formData.append('uploaded_to', this.editor.config.pageId);
window.$http.post('/images/gallery', formData).then(resp => { try {
const newContent = `[![${selectedText}](${resp.data.thumbs.display})](${resp.data.url})`; const {data} = await window.$http.post('/images/gallery', formData);
const newContent = `[![](${data.thumbs.display})](${data.url})`;
this.#findAndReplaceContent(placeHolderText, newContent); this.#findAndReplaceContent(placeHolderText, newContent);
}).catch(err => { } catch (err) {
window.$events.emit('error', this.editor.config.text.imageUploadError); window.$events.emit('error', this.editor.config.text.imageUploadError);
this.#findAndReplaceContent(placeHolderText, selectedText); this.#findAndReplaceContent(placeHolderText, '');
console.log(err); console.log(err);
}); }
} }
/** /**

View File

@ -25,7 +25,37 @@ export async function init(editor) {
const domEventHandlers = { const domEventHandlers = {
// Handle scroll to sync display view // Handle scroll to sync display view
scroll: (event) => syncActive && onScrollDebounced(event) scroll: (event) => syncActive && onScrollDebounced(event),
// Handle image & content drag n drop
drop: (event) => {
const templateId = event.dataTransfer.getData('bookstack/template');
if (templateId) {
event.preventDefault();
editor.actions.insertTemplate(templateId, event.pageX, event.pageY);
}
const clipboard = new Clipboard(event.dataTransfer);
const clipboardImages = clipboard.getImages();
if (clipboardImages.length > 0) {
event.stopPropagation();
event.preventDefault();
editor.actions.insertClipboardImages(clipboardImages, event.pageX, event.pageY);
}
},
// Handle image paste
paste: (event) => {
const clipboard = new Clipboard(event.clipboardData || event.dataTransfer);
// Don't handle the event ourselves if no items exist of contains table-looking data
if (!clipboard.hasItems() || clipboard.containsTabularData()) {
return;
}
const images = clipboard.getImages();
for (const image of images) {
editor.actions.uploadImage(image);
}
}
} }
const cm = Code.markdownEditor( const cm = Code.markdownEditor(
@ -40,42 +70,5 @@ export async function init(editor) {
// TODO // TODO
// cm.setOption('direction', 'ltr'); // cm.setOption('direction', 'ltr');
// Handle image paste
// TODO
// cm.on('paste', (cm, event) => {
// const clipboard = new Clipboard(event.clipboardData || event.dataTransfer);
//
// // Don't handle the event ourselves if no items exist of contains table-looking data
// if (!clipboard.hasItems() || clipboard.containsTabularData()) {
// return;
// }
//
// const images = clipboard.getImages();
// for (const image of images) {
// editor.actions.uploadImage(image);
// }
// });
// Handle image & content drag n drop
// TODO
// cm.on('drop', (cm, event) => {
//
// const templateId = event.dataTransfer.getData('bookstack/template');
// if (templateId) {
// event.preventDefault();
// editor.actions.insertTemplate(templateId, event.pageX, event.pageY);
// }
//
// const clipboard = new Clipboard(event.dataTransfer);
// const clipboardImages = clipboard.getImages();
// if (clipboardImages.length > 0) {
// event.stopPropagation();
// event.preventDefault();
// editor.actions.insertClipboardImages(clipboardImages);
// }
//
// });
return cm; return cm;
} }