Skip to content

Instantly share code, notes, and snippets.

@vctls
Last active February 8, 2023 15:48
Show Gist options
  • Save vctls/ee8c77a1ec712984e85b247cbf9f6459 to your computer and use it in GitHub Desktop.
Save vctls/ee8c77a1ec712984e85b247cbf9f6459 to your computer and use it in GitHub Desktop.
OpenProject : replace inline editing on click by a standard edit button.
// ==UserScript==
// @name OpenProject Standard Edit
// @namespace http://tampermonkey.net/
// @version 0.9
// @downloadURL https://gist.github.com/vctls/ee8c77a1ec712984e85b247cbf9f6459/raw
// @updateURL https://gist.github.com/vctls/ee8c77a1ec712984e85b247cbf9f6459/raw
// @description Replace inline editing on click in OpenProject by a standard edit button.
// @author vctls
// @match https://*.openproject.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=openproject.com
// @grant none
// ==/UserScript==
'use strict';
const clickHandler = event => {
const target = event.target;
const parent = target.parentElement;
if (
// If the click event is our custom one,
// or it is triggered on one of the editor buttons
// let it pass.
// Otherwise, prevent it from propagating.
// TODO This must have a cleaner solution.
event.custom !== undefined
|| target.tagName === 'A'
|| target.tagName === 'OP-ICON'
|| parent.tagName === 'OP-ICON'
|| (parent.parent && parent.parent.tagName === 'BUTTON')
|| target.classList.contains('editbutton')
// Allow form controls inside an active editor.
|| target.closest('div.inline-edit--container').classList.contains('-active') && (
(target.attributes.type?.nodeValue === "checkbox" ?? false)
|| target.closest('div.document-editor__toolbar')
)
) {
return;
}
event.preventDefault();
event.stopPropagation();
};
const buttonClickHandler = event => {
if (event.custom !== undefined) return;
const target = event.target;
const clickEvent = new MouseEvent('click', {
bubbles: true
});
clickEvent.custom = true;
target.parentElement.querySelector('.-editable').dispatchEvent(clickEvent);
}
function setupEditorButton(editor) {
const button = document.createElement('button');
button.classList.add('editbutton');
button.classList.add('icon-edit'); // Use existing edit icon.
button.classList.add('inplace-edit--controls') // Style it as existing edit controls.
const s = button.style;
// Absolute position does not work well on the "New Task" view,
// but the script isn't really useful there.
s.position = 'absolute';
s.right = '0';
s.top = '0';
s.height = '40px';
s.width = '40px';
editor.append(button);
button.addEventListener('click', buttonClickHandler);
editor.addEventListener(
'click',
clickHandler,
// This third argument is necessary
// in order to capture all events before
// they trigger other event listeners.
true
);
}
function check(changes, observer) {
// Skip irrelevant pages.
// At the moment, only work pacakage editing views should be handled.
const relevantPagesPattern = new RegExp('(?:/projects/\\w+/)?work_packages/\\d+/.*');
if (!window.location.pathname.match(relevantPagesPattern)){
return;
}
// TODO Get the editors from the changelist instead of querying them.
const editors = document.querySelectorAll('op-editable-attribute-field');
editors.forEach(
editor => {
if (
editor !== null
&& editor.querySelector('.subject, .description')
&& !editor.hasButton) {
setupEditorButton(editor);
editor.hasButton = true;
}
}
);
// Automatically dismiss HTTP response failure errors.
const errorToast = document.querySelector('op-toast')
if (errorToast && errorToast.textContent.includes('Http failure response')) {
errorToast.querySelector('button').dispatchEvent(new MouseEvent('click'));
}
}
(function () {
'use strict';
(new MutationObserver(check)).observe(document, {childList: true, subtree: true});
})();
@vctls
Copy link
Author

vctls commented Feb 2, 2023

v0.8: Fixed interference with CKEditor controls.

@vctls
Copy link
Author

vctls commented Feb 8, 2023

v0.9: Fixed interference with anchors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment