Skip to content

Freehand drawings become uneditable in rich editor after being resized #12108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
isaiah-tapia opened this issue Apr 14, 2025 · 7 comments
Open
Labels
bug It's a bug draw JS-Draw related issues

Comments

@isaiah-tapia
Copy link

isaiah-tapia commented Apr 14, 2025

Operating system

macOS

Joplin version

3.2.13

Desktop version info

Joplin 3.2.13 (prod, darwin)

Client ID: 1ab002383b6b4f9b92d5001e797cb1d9

Current behaviour

  1. I create a freehand drawing
  2. I draw on it using my wacom screenless tablet
  3. Save
  4. Right click -> press out -> edit the size
  5. Edit -> save -> exit -> overwrite existing drawing -> save and close
  6. spits out a long piece of code into the md side but not the wysiwyg (see below)
  7. Is now uneditable

<img id="io-github-personalizedrefrigerator-js-draw-editable-svg-0" ondblclick="((contentScriptId, container, svgId) => { var _a; // Don't declare as a toplevel constant -- editImage is stringified. const debug = false; const imageElem = (_a = container.querySelector('img')) !== null &amp;&amp; _a !== void 0 ? _a : document.querySelector(img#${svgId}); if (!(imageElem === null || imageElem === void 0 ? void 0 : imageElem.src)) { throw new Error(${imageElem} lacks an src attribute. Unable to edit!); } const updateCachebreaker = (initialSrc) => { var _a; var _b; // Strip the ?t=... at the end of the image URL const cachebreakerMatch = /^(.*)\?t=(\d+)$/.exec(initialSrc); const fileUrl = cachebreakerMatch ? cachebreakerMatch[1] : initialSrc; const oldCachebreaker = cachebreakerMatch ? parseInt(cachebreakerMatch[2]) : 0; const newCachebreaker = new Date().getTime(); // Add the cachebreaker to the global list -- we may need to change cachebreakers // on future rerenders. (_a = (_b = window)['outOfDateCacheBreakers']) !== null &amp;&amp; _a !== void 0 ? _a : (_b['outOfDateCacheBreakers'] = {}); window['outOfDateCacheBreakers'][fileUrl] = { outdated: oldCachebreaker, suggested: newCachebreaker, }; return ${fileUrl}?t=${newCachebreaker}; }; // The webview api is different if we're running in the TinyMce editor vs if we're running // in the preview pane. const message = imageElem.src; const imageElemClass = imageelem-${new Date().getTime()}; imageElem.classList.add(imageElemClass); try { let postMessage; try { postMessage = webviewApi.postMessage; } catch (error) { // Don't log by default if (debug) { console.error('Unable to access webviewApi.postMessage: ', error); } } if (!postMessage) { // TODO: // This is a hack to work around the lack of a webviewApi in the rich text editor // webview. // As top.require **should not work** at some point in the future, this will fail. const PluginService = top.require('@joplin/lib/services/plugins/PluginService').default; postMessage = (contentScriptId, message) => { const pluginService = PluginService.instance(); const pluginId = pluginService.pluginIdByContentScriptId(contentScriptId); return pluginService .pluginById(pluginId) .emitContentScriptMessage(contentScriptId, message); }; } postMessage(contentScriptId, message) .then((resourceId) => { // Update all matching const toRefresh = document.querySelectorAll(
img[data-resource-id="${resourceId}"],
img[data-mce-src*="/${resourceId}.svg"]
); for (const elem of toRefresh) { const imageElem = elem; imageElem.src = updateCachebreaker(imageElem.src); } }) .catch((err) => { console.error('Error posting message!', err, '\nMessage: ', message); }); } catch (err) { console.warn('Error posting message', err); } })('jsdraw__markdownIt_editDrawingButton', this.parentElement, 'io-github-personalizedrefrigerator-js-draw-editable-svg-0')" onload="((container, buttonId) => { var _a, _b, _c, _d; let button = container.querySelector('button.jsdraw--editButton'); const imageElem = container.querySelector('img'); if (!imageElem) { throw new Error('js-draw editor: Unable to find an image in the given container!'); } // Another plugin may have moved the button if (!button) { button = document.querySelector(#${buttonId}); // In the rich text editor, an image might be reloading when the button has already // been removed: if (!button) { return; } button.remove(); container.appendChild(button); } container.classList.add('jsdraw--svgWrapper'); const outOfDateCacheBreakers = (_a = window['outOfDateCacheBreakers']) !== null &amp;&amp; _a !== void 0 ? _a : {}; const imageSrcMatch = /^(.*)\?t=(\d+)$/.exec(imageElem.src); if (!imageSrcMatch) { throw new Error(${imageElem === null || imageElem === void 0 ? void 0 : imageElem.src} doesn't have a cachebreaker! Unable to update it.); } const fileUrl = imageSrcMatch[1]; const cachebreaker = parseInt((_b = imageSrcMatch[2]) !== null &amp;&amp; _b !== void 0 ? _b : '0'); const badCachebreaker = (_c = outOfDateCacheBreakers[fileUrl]) !== null &amp;&amp; _c !== void 0 ? _c : {}; if (isNaN(cachebreaker) || cachebreaker <= (badCachebreaker === null || badCachebreaker === void 0 ? void 0 : badCachebreaker.outdated)) { imageElem.src = ${fileUrl}?t=${badCachebreaker.suggested}; } let haveWebviewApi = true; try { // Attempt to access .postMessage // Note: We can't just check window.webviewApi because webviewApi seems not to be // a property on window. haveWebviewApi = typeof webviewApi.postMessage === 'function'; } catch (_err) { haveWebviewApi = false; } const isRichTextEditor = !haveWebviewApi || ((_d = document.body) === null || _d === void 0 ? void 0 : _d.id) === 'tinymce'; if (isRichTextEditor) { button === null || button === void 0 ? void 0 : button.remove(); imageElem.style.cursor = 'pointer'; } })(this.parentElement, 'io-github-personalizedrefrigerator-js-draw-edit-button-0')" src=":/787d6e1a2fbc4bb4b26ac1a6069688da" alt="Freehand Drawing.svg" width="225" height="300" style="cursor: pointer;">

Expected behaviour

Joplin should make resizing a freehand drawing editable even after changing its size

Logs

No response

@isaiah-tapia isaiah-tapia added the bug It's a bug label Apr 14, 2025
@isaiah-tapia
Copy link
Author

Here it is but a little more readable

img id="io-github-personalizedrefrigerator-js-draw-editable-svg-0" ondblclick="((contentScriptId, container, svgId) => {
var _a;
// Don't declare as a toplevel constant -- editImage is stringified.
const debug = false;
const imageElem = (_a = container.querySelector('img')) !== null && _a !== void 0 ? _a : document.querySelector(img#${svgId});
if (!(imageElem === null || imageElem === void 0 ? void 0 : imageElem.src)) {
throw new Error(${imageElem} lacks an src attribute. Unable to edit!);
}
const updateCachebreaker = (initialSrc) => {
var _a;
var _b;
// Strip the ?t=... at the end of the image URL
const cachebreakerMatch = /^(.)?t=(\d+)$/.exec(initialSrc);
const fileUrl = cachebreakerMatch ? cachebreakerMatch[1] : initialSrc;
const oldCachebreaker = cachebreakerMatch ? parseInt(cachebreakerMatch[2]) : 0;
const newCachebreaker = new Date().getTime();
// Add the cachebreaker to the global list -- we may need to change cachebreakers
// on future rerenders.
(_a = (_b = window)['outOfDateCacheBreakers']) !== null && _a !== void 0 ? _a : (_b['outOfDateCacheBreakers'] = {});
window['outOfDateCacheBreakers'][fileUrl] = {
outdated: oldCachebreaker,
suggested: newCachebreaker,
};
return ${fileUrl}?t=${newCachebreaker};
};
// The webview api is different if we're running in the TinyMce editor vs if we're running
// in the preview pane.
const message = imageElem.src;
const imageElemClass = imageelem-${new Date().getTime()};
imageElem.classList.add(imageElemClass);
try {
let postMessage;
try {
postMessage = webviewApi.postMessage;
}
catch (error) {
// Don't log by default
if (debug) {
console.error('Unable to access webviewApi.postMessage: ', error);
}
}
if (!postMessage) {
// TODO:
// This is a hack to work around the lack of a webviewApi in the rich text editor
// webview.
// As top.require should not work at some point in the future, this will fail.
const PluginService = top.require('@joplin/lib/services/plugins/PluginService').default;
postMessage = (contentScriptId, message) => {
const pluginService = PluginService.instance();
const pluginId = pluginService.pluginIdByContentScriptId(contentScriptId);
return pluginService
.pluginById(pluginId)
.emitContentScriptMessage(contentScriptId, message);
};
}
postMessage(contentScriptId, message)
.then((resourceId) => {
// Update all matching
const toRefresh = document.querySelectorAll(img[data-resource-id=&quot;${resourceId}&quot;], img[data-mce-src*=&quot;/${resourceId}.svg&quot;]);
for (const elem of toRefresh) {
const imageElem = elem;
imageElem.src = updateCachebreaker(imageElem.src);
}
})
.catch((err) => {
console.error('Error posting message!', err, '\nMessage: ', message);
});
}
catch (err) {
console.warn('Error posting message', err);
}
})('jsdraw__markdownIt_editDrawingButton', this.parentElement, 'io-github-personalizedrefrigerator-js-draw-editable-svg-0')" onload="((container, buttonId) => {
var _a, _b, _c, _d;
let button = container.querySelector('button.jsdraw--editButton');
const imageElem = container.querySelector('img');
if (!imageElem) {
throw new Error('js-draw editor: Unable to find an image in the given container!');
}
// Another plugin may have moved the button
if (!button) {
button = document.querySelector(#${buttonId});
// In the rich text editor, an image might be reloading when the button has already
// been removed:
if (!button) {
return;
}
button.remove();
container.appendChild(button);
}
container.classList.add('jsdraw--svgWrapper');
const outOfDateCacheBreakers = (_a = window['outOfDateCacheBreakers']) !== null && _a !== void 0 ? _a : {};
const imageSrcMatch = /^(.
)?t=(\d+)$/.exec(imageElem.src);
if (!imageSrcMatch) {
throw new Error(${imageElem === null || imageElem === void 0 ? void 0 : imageElem.src} doesn't have a cachebreaker! Unable to update it.);
}
const fileUrl = imageSrcMatch[1];
const cachebreaker = parseInt((_b = imageSrcMatch[2]) !== null && _b !== void 0 ? _b : '0');
const badCachebreaker = (_c = outOfDateCacheBreakers[fileUrl]) !== null && _c !== void 0 ? _c : {};
if (isNaN(cachebreaker) || cachebreaker <= (badCachebreaker === null || badCachebreaker === void 0 ? void 0 : badCachebreaker.outdated)) {
imageElem.src = ${fileUrl}?t=${badCachebreaker.suggested};
}
let haveWebviewApi = true;
try {
// Attempt to access .postMessage
// Note: We can't just check window.webviewApi because webviewApi seems not to be
// a property on window.
haveWebviewApi = typeof webviewApi.postMessage === 'function';
}
catch (_err) {
haveWebviewApi = false;
}
const isRichTextEditor = !haveWebviewApi || ((_d = document.body) === null || _d === void 0 ? void 0 : _d.id) === 'tinymce';
if (isRichTextEditor) {
button === null || button === void 0 ? void 0 : button.remove();
imageElem.style.cursor = 'pointer';
}
})(this.parentElement, 'io-github-personalizedrefrigerator-js-draw-edit-button-0')" src=":/8dc912a12dec42e59fd3b33fd22db52c" alt="Freehand Drawing.svg" width="174" height="169" style="cursor: pointer;">

@isaiah-tapia
Copy link
Author

Also, if anyone has this same issue you can just do ![Freehand Drawing.svg](:/[hash code]) to be able to edit it again

@isaiah-tapia isaiah-tapia changed the title Freehand images become uneditable after being resized Freehand images become uneditable in rich editor after being resized Apr 14, 2025
@isaiah-tapia isaiah-tapia changed the title Freehand images become uneditable in rich editor after being resized Freehand drawings become uneditable in rich editor after being resized Apr 15, 2025
@personalizedrefrigerator personalizedrefrigerator added the draw JS-Draw related issues label Apr 15, 2025
@personalizedrefrigerator
Copy link
Collaborator

Thank you for reporting this!

I'm having trouble reproducing this (though I'm testing with Joplin 3.3.3 and Freehand Drawing 2.15.0/3.0.0). For me, the drawings created by the image editor lack resize handles. Instead, hovering over the edge of the drawing shows a "🚫" cursor:
screenshot: Drawing in the Rich Text editor, cursor near bottom right is a 🚫

Note: If the above code is the result of Joplin attempting to convert the "Edit drawing" button (usually hidden in the Rich Text Editor) into Markdown, #12103 may partially resolve this issue.

@isaiah-tapia
Copy link
Author

isaiah-tapia commented Apr 15, 2025

When I right click on the image it allows me to resize it after I click out .

Image Image

@personalizedrefrigerator
Copy link
Collaborator

personalizedrefrigerator commented Apr 15, 2025

I can reproduce the issue now! Thank you for the additional information.

Edit/clarification: I can reproduce the issue in v3.2.13, but not in v3.3.3.

@isaiah-tapia
Copy link
Author

isaiah-tapia commented Apr 15, 2025

No problem happy to help!

@personalizedrefrigerator
Copy link
Collaborator

personalizedrefrigerator commented Apr 17, 2025

This seems to be fixed in the latest 3.3.x pre-releases, which use a newer version of the Rich Text Editor (TinyMCE v6 as opposed to v5).

However, note that it currently isn't possible in the 3.3.x releases to resize drawings from the Rich Text Editor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug It's a bug draw JS-Draw related issues
Projects
None yet
Development

No branches or pull requests

2 participants