Merge pull request #6082 from harryadel/mousetrap-migration

Replace mousetrap
This commit is contained in:
Lauri Ojansivu 2026-01-21 13:33:09 +02:00 committed by GitHub
commit 8586fa8ce0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 236 additions and 147 deletions

View file

@ -55,9 +55,6 @@ http@2.0.0! # force new http package
# UI components # UI components
ostrio:i18n ostrio:i18n
reactive-var@1.0.12 reactive-var@1.0.12
mousetrap:mousetrap
mquandalle:jquery-textcomplete
mquandalle:mousetrap-bindglobal
templates:tabs templates:tabs
meteor-autosize meteor-autosize
shell-server@0.5.0 shell-server@0.5.0

View file

@ -80,13 +80,10 @@ mongo-decimal@0.1.3
mongo-dev-server@1.1.0 mongo-dev-server@1.1.0
mongo-id@1.0.8 mongo-id@1.0.8
mongo-livedata@1.0.12 mongo-livedata@1.0.12
mousetrap:mousetrap@1.4.6_1
mquandalle:autofocus@1.0.0 mquandalle:autofocus@1.0.0
mquandalle:collection-mutations@0.1.0 mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9 mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5 mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:mousetrap-bindglobal@0.0.1
msavin:usercache@1.8.0 msavin:usercache@1.8.0
npm-mongo@4.17.2 npm-mongo@4.17.2
oauth@2.2.1 oauth@2.2.1

View file

@ -1,4 +1,6 @@
// Pressing `Escape` should close the last opened “element” and only the last const hotkeys = require('hotkeys-js').default;
// Pressing `Escape` should close the last opened "element" and only the last
// one. Components can register themselves using a label a condition, and an // one. Components can register themselves using a label a condition, and an
// action. This is used by Popup or inlinedForm for instance. When we press // action. This is used by Popup or inlinedForm for instance. When we press
// escape we execute the action which have a valid condition and his the highest // escape we execute the action which have a valid condition and his the highest
@ -119,9 +121,9 @@ EscapeActions = {
}, },
}; };
// Pressing escape to execute one escape action. We use `bindGloabal` vecause // Pressing escape to execute one escape action. ESC is allowed globally
// the shortcut sould work on textarea and inputs as well. // in the hotkeys filter (keyboard.js) so it works in textarea and inputs.
Mousetrap.bindGlobal('esc', () => { hotkeys('escape', () => {
EscapeActions.executeLowest(); EscapeActions.executeLowest();
Sidebar.hide(); Sidebar.hide();
}); });

View file

@ -1,9 +1,42 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
const hotkeys = require('hotkeys-js').default;
// XXX There is no reason to define these shortcuts globally, they should be // XXX There is no reason to define these shortcuts globally, they should be
// attached to a template (most of them will go in the `board` template). // attached to a template (most of them will go in the `board` template).
// Configure hotkeys filter (replaces Mousetrap.stopCallback)
// CRITICAL: Return values are INVERTED from Mousetrap's stopCallback
// hotkeys filter: true = ALLOW shortcut, false = STOP shortcut
hotkeys.filter = (event) => {
// Are shortcuts enabled for the user?
if (ReactiveCache.getCurrentUser() && !ReactiveCache.getCurrentUser().isKeyboardShortcuts())
return false;
// Always handle escape
if (event.keyCode === 27)
return true;
// Make sure there are no selected characters
if (window.getSelection().type === "Range")
return false;
// Decide what the current element is
const currentElement = event.target || document.activeElement;
// If the current element is editable, we don't want to trigger an event
if (currentElement.isContentEditable)
return false;
// Make sure we are not in an input element
if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
return false;
// We can trigger events!
return true;
};
// Handle non-Latin keyboards
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
// Only handle event if coming from body // Only handle event if coming from body
if (e.target !== document.body) return; if (e.target !== document.body) return;
@ -11,39 +44,19 @@ window.addEventListener('keydown', (e) => {
// Only handle event if it's in another language // Only handle event if it's in another language
if (String.fromCharCode(e.which).toLowerCase() === e.key) return; if (String.fromCharCode(e.which).toLowerCase() === e.key) return;
// Trigger the corresponding action // Trigger the corresponding action by dispatching a new event with the ASCII key
Mousetrap.handleKey(String.fromCharCode(e.which).toLowerCase(), [], {type: "keypress"}); const key = String.fromCharCode(e.which).toLowerCase();
// Create a synthetic event for hotkeys to handle
const syntheticEvent = new KeyboardEvent('keydown', {
key: key,
keyCode: e.which,
which: e.which,
bubbles: true,
cancelable: true,
});
document.dispatchEvent(syntheticEvent);
}); });
// Overwrite the stopCallback to allow for more keyboard shortcut customizations
Mousetrap.stopCallback = (event, element) => {
// Are shortcuts enabled for the user?
if (ReactiveCache.getCurrentUser() && !ReactiveCache.getCurrentUser().isKeyboardShortcuts())
return true;
// Always handle escape
if (event.keyCode === 27)
return false;
// Make sure there are no selected characters
if (window.getSelection().type === "Range")
return true;
// Decide what the current element is
const currentElement = event.target || document.activeElement;
// If the current element is editable, we don't want to trigger an event
if (currentElement.isContentEditable)
return true;
// Make sure we are not in an input element
if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
return true;
// We can trigger events!
return false;
}
function getHoveredCardId() { function getHoveredCardId() {
const card = $('.js-minicard:hover').get(0); const card = $('.js-minicard:hover').get(0);
if (!card) return null; if (!card) return null;
@ -54,11 +67,13 @@ function getSelectedCardId() {
return Session.get('currentCard') || Session.get('selectedCard') || getHoveredCardId(); return Session.get('currentCard') || Session.get('selectedCard') || getHoveredCardId();
} }
Mousetrap.bind('?', () => { hotkeys('?', (event) => {
event.preventDefault();
FlowRouter.go('shortcuts'); FlowRouter.go('shortcuts');
}); });
Mousetrap.bind('w', () => { hotkeys('w', (event) => {
event.preventDefault();
if (Sidebar.isOpen() && Sidebar.getView() === 'home') { if (Sidebar.isOpen() && Sidebar.getView() === 'home') {
Sidebar.toggle(); Sidebar.toggle();
} else { } else {
@ -66,7 +81,8 @@ Mousetrap.bind('w', () => {
} }
}); });
Mousetrap.bind('q', () => { hotkeys('q', (event) => {
event.preventDefault();
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
const currentUserId = Meteor.userId(); const currentUserId = Meteor.userId();
if (currentBoardId && currentUserId) { if (currentBoardId && currentUserId) {
@ -74,7 +90,8 @@ Mousetrap.bind('q', () => {
} }
}); });
Mousetrap.bind('a', () => { hotkeys('a', (event) => {
event.preventDefault();
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
const currentUserId = Meteor.userId(); const currentUserId = Meteor.userId();
if (currentBoardId && currentUserId) { if (currentBoardId && currentUserId) {
@ -82,13 +99,15 @@ Mousetrap.bind('a', () => {
} }
}); });
Mousetrap.bind('x', () => { hotkeys('x', (event) => {
event.preventDefault();
if (Filter.isActive()) { if (Filter.isActive()) {
Filter.reset(); Filter.reset();
} }
}); });
Mousetrap.bind('f', () => { hotkeys('f', (event) => {
event.preventDefault();
if (Sidebar.isOpen() && Sidebar.getView() === 'filter') { if (Sidebar.isOpen() && Sidebar.getView() === 'filter') {
Sidebar.toggle(); Sidebar.toggle();
} else { } else {
@ -96,7 +115,8 @@ Mousetrap.bind('f', () => {
} }
}); });
Mousetrap.bind('/', () => { hotkeys('/', (event) => {
event.preventDefault();
if (Sidebar.isOpen() && Sidebar.getView() === 'search') { if (Sidebar.isOpen() && Sidebar.getView() === 'search') {
Sidebar.toggle(); Sidebar.toggle();
} else { } else {
@ -104,12 +124,13 @@ Mousetrap.bind('/', () => {
} }
}); });
Mousetrap.bind(['down', 'up'], (evt, key) => { hotkeys('down,up', (event, handler) => {
event.preventDefault();
if (!Utils.getCurrentCardId()) { if (!Utils.getCurrentCardId()) {
return; return;
} }
const nextFunc = key === 'down' ? 'next' : 'prev'; const nextFunc = handler.key === 'down' ? 'next' : 'prev';
const nextCard = $('.js-minicard.is-selected') const nextCard = $('.js-minicard.is-selected')
[nextFunc]('.js-minicard') [nextFunc]('.js-minicard')
.get(0); .get(0);
@ -119,49 +140,47 @@ Mousetrap.bind(['down', 'up'], (evt, key) => {
} }
}); });
numbArray = _.range(1,10).map(x => 'shift+'+String(x)) // Shift + number keys to remove labels in multiselect
Mousetrap.bind(numbArray, (evt, key) => { const shiftNums = _.range(1, 10).map(x => `shift+${x}`).join(',');
num = parseInt(key.substr(6, key.length)); hotkeys(shiftNums, (event, handler) => {
event.preventDefault();
const num = parseInt(handler.key.split('+')[1]);
const currentUserId = Meteor.userId(); const currentUserId = Meteor.userId();
if (currentUserId === null) { if (currentUserId === null) {
return; return;
} }
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
board = ReactiveCache.getBoard(currentBoardId); const board = ReactiveCache.getBoard(currentBoardId);
labels = board.labels; const labels = board.labels;
if(MultiSelection.isActive()) if (MultiSelection.isActive()) {
{
const cardIds = MultiSelection.getSelectedCardIds(); const cardIds = MultiSelection.getSelectedCardIds();
for (const cardId of cardIds) for (const cardId of cardIds) {
{ const card = Cards.findOne(cardId);
card = Cards.findOne(cardId); if (num <= board.labels.length) {
if(num <= board.labels.length) card.removeLabel(labels[num - 1]["_id"]);
{
card.removeLabel(labels[num-1]["_id"]);
} }
} }
} }
}); });
numArray = _.range(1,10).map(x => String(x)) // Number keys to toggle labels
Mousetrap.bind(numArray, (evt, key) => { const nums = _.range(1, 10).join(',');
num = parseInt(key); hotkeys(nums, (event, handler) => {
event.preventDefault();
const num = parseInt(handler.key);
const currentUserId = Meteor.userId(); const currentUserId = Meteor.userId();
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
if (currentUserId === null) { if (currentUserId === null) {
return; return;
} }
board = ReactiveCache.getBoard(currentBoardId); const board = ReactiveCache.getBoard(currentBoardId);
labels = board.labels; const labels = board.labels;
if(MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember()) if (MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember()) {
{
const cardIds = MultiSelection.getSelectedCardIds(); const cardIds = MultiSelection.getSelectedCardIds();
for (const cardId of cardIds) for (const cardId of cardIds) {
{ const card = Cards.findOne(cardId);
card = Cards.findOne(cardId); if (num <= board.labels.length) {
if(num <= board.labels.length) card.addLabel(labels[num - 1]["_id"]);
{
card.addLabel(labels[num-1]["_id"]);
} }
} }
return; return;
@ -173,14 +192,16 @@ Mousetrap.bind(numArray, (evt, key) => {
} }
if (ReactiveCache.getCurrentUser().isBoardMember()) { if (ReactiveCache.getCurrentUser().isBoardMember()) {
const card = Cards.findOne(cardId); const card = Cards.findOne(cardId);
if(num <= board.labels.length) if (num <= board.labels.length) {
{ card.toggleLabel(labels[num - 1]["_id"]);
card.toggleLabel(labels[num-1]["_id"]);
} }
} }
}); });
Mousetrap.bind(_.range(1, 10).map(x => `ctrl+alt+${x}`), (evt, key) => { // Ctrl+Alt + number keys to toggle assignees
const ctrlAltNums = _.range(1, 10).map(x => `ctrl+alt+${x}`).join(',');
hotkeys(ctrlAltNums, (event, handler) => {
event.preventDefault();
// Make sure the current user is defined // Make sure the current user is defined
if (!ReactiveCache.getCurrentUser()) if (!ReactiveCache.getCurrentUser())
return; return;
@ -189,7 +210,7 @@ Mousetrap.bind(_.range(1, 10).map(x => `ctrl+alt+${x}`), (evt, key) => {
if (!ReactiveCache.getCurrentUser().isBoardMember()) if (!ReactiveCache.getCurrentUser().isBoardMember())
return; return;
const memberIndex = parseInt(key.split("+").pop()) - 1; const memberIndex = parseInt(handler.key.split("+").pop()) - 1;
const currentBoard = Utils.getCurrentBoard(); const currentBoard = Utils.getCurrentBoard();
const validBoardMembers = currentBoard.memberUsers().filter(member => member.isBoardMember()); const validBoardMembers = currentBoard.memberUsers().filter(member => member.isBoardMember());
@ -211,7 +232,8 @@ Mousetrap.bind(_.range(1, 10).map(x => `ctrl+alt+${x}`), (evt, key) => {
} }
}); });
Mousetrap.bind('m', evt => { hotkeys('m', (event) => {
event.preventDefault();
const cardId = getSelectedCardId(); const cardId = getSelectedCardId();
if (!cardId) { if (!cardId) {
return; return;
@ -225,13 +247,11 @@ Mousetrap.bind('m', evt => {
if (ReactiveCache.getCurrentUser().isBoardMember()) { if (ReactiveCache.getCurrentUser().isBoardMember()) {
const card = Cards.findOne(cardId); const card = Cards.findOne(cardId);
card.toggleAssignee(currentUserId); card.toggleAssignee(currentUserId);
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
evt.preventDefault();
} }
}); });
Mousetrap.bind('space', evt => { hotkeys('space', (event) => {
event.preventDefault();
const cardId = getSelectedCardId(); const cardId = getSelectedCardId();
if (!cardId) { if (!cardId) {
return; return;
@ -245,13 +265,11 @@ Mousetrap.bind('space', evt => {
if (ReactiveCache.getCurrentUser().isBoardMember()) { if (ReactiveCache.getCurrentUser().isBoardMember()) {
const card = Cards.findOne(cardId); const card = Cards.findOne(cardId);
card.toggleMember(currentUserId); card.toggleMember(currentUserId);
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
evt.preventDefault();
} }
}); });
const archiveCard = evt => { const archiveCard = (event) => {
event.preventDefault();
const cardId = getSelectedCardId(); const cardId = getSelectedCardId();
if (!cardId) { if (!cardId) {
return; return;
@ -265,21 +283,19 @@ const archiveCard = evt => {
if (Utils.canModifyBoard()) { if (Utils.canModifyBoard()) {
const card = Cards.findOne(cardId); const card = Cards.findOne(cardId);
card.archive(); card.archive();
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
evt.preventDefault();
} }
}; };
// Archive card has multiple shortcuts // Archive card has multiple shortcuts
Mousetrap.bind('c', archiveCard); hotkeys('c', archiveCard);
Mousetrap.bind('-', archiveCard); hotkeys('-', archiveCard);
// Same as above, this time for Persian keyboard. // Same as above, this time for Persian keyboard.
// https://github.com/wekan/wekan/pull/5589#issuecomment-2516776519 // https://github.com/wekan/wekan/pull/5589#issuecomment-2516776519
Mousetrap.bind(', archiveCard); hotkeys('\xf7', archiveCard);
Mousetrap.bind('n', evt => { hotkeys('n', (event) => {
event.preventDefault();
const cardId = getSelectedCardId(); const cardId = getSelectedCardId();
if (!cardId) { if (!cardId) {
return; return;
@ -296,10 +312,6 @@ Mousetrap.bind('n', evt => {
// Find the button and click it // Find the button and click it
$(`#js-list-${card.listId} .list-body .minicards .open-minicard-composer`).click(); $(`#js-list-${card.listId} .list-body .minicards .open-minicard-composer`).click();
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
evt.preventDefault();
} }
}); });
@ -354,7 +366,7 @@ Template.keyboardShortcuts.helpers({
action: 'shortcut-assign-self', action: 'shortcut-assign-self',
}, },
{ {
keys: ['c', '÷', '-'], keys: ['c', '\xf7', '-'],
action: 'archive-card', action: 'archive-card',
}, },
{ {

View file

@ -1,54 +1,80 @@
// We “inherit” the jquery-textcomplete plugin to integrate with our // We use @textcomplete packages to integrate with our EscapeActions system.
// EscapeActions system. You should always use `escapeableTextComplete` instead // You should always use `createEscapeableTextComplete` or the jQuery extension
// of the vanilla `textcomplete`. // `escapeableTextComplete` instead of the vanilla textcomplete.
import { Textcomplete } from '@textcomplete/core';
import { TextareaEditor } from '@textcomplete/textarea';
import { ContenteditableEditor } from '@textcomplete/contenteditable';
let dropdownMenuIsOpened = false; let dropdownMenuIsOpened = false;
$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) { /**
// When the autocomplete menu is shown we want both a press of both `Tab` * Create an escapeable textcomplete instance for a textarea or contenteditable element
// or `Enter` to validation the auto-completion. We also need to stop the * @param {HTMLTextAreaElement|HTMLElement} element - The target element
// event propagation to prevent EscapeActions side effect, for instance the * @param {Array} strategies - Array of strategy objects
// minicard submission (on `Enter`) or going on the next column (on `Tab`). * @param {Object} options - Additional options
options = { * @returns {Textcomplete} The textcomplete instance
onKeydown(evt, commands) { */
if (evt.keyCode === 9 || evt.keyCode === 13) { export function createEscapeableTextComplete(element, strategies, options = {}) {
evt.stopPropagation(); // Determine the appropriate editor based on element type
return commands.KEY_ENTER; const isContentEditable = element.isContentEditable || element.contentEditable === 'true';
} const Editor = isContentEditable ? ContenteditableEditor : TextareaEditor;
return null;
const editor = new Editor(element);
// Merge default options
const mergedOptions = {
dropdown: {
className: 'textcomplete-dropdown',
maxCount: 10,
placement: 'bottom',
...options.dropdown,
}, },
...options,
}; };
// Proxy to the vanilla jQuery component const textcomplete = new Textcomplete(editor, strategies, mergedOptions);
this.textcomplete(strategies, options, ...otherArgs);
// Since commit d474017 jquery-textComplete automatically closes a potential // When the autocomplete menu is shown we want both a press of both `Tab`
// opened dropdown menu when the user press Escape. This behavior conflicts // or `Enter` to validate the auto-completion. We also need to stop the
// with our EscapeActions system, but it's too complicated and hacky to // event propagation to prevent EscapeActions side effect, for instance the
// monkey-pach textComplete to disable it -- I tried. Instead we listen to // minicard submission (on `Enter`) or going on the next column (on `Tab`).
// 'open' and 'hide' events, and create a ghost escapeAction when the dropdown element.addEventListener('keydown', (evt) => {
// is opened (and rely on textComplete to execute the actual action). if (dropdownMenuIsOpened && (evt.keyCode === 9 || evt.keyCode === 13)) {
this.on({ evt.stopPropagation();
'textComplete:show'() { }
dropdownMenuIsOpened = true; });
},
'textComplete:select'() { // Track dropdown state for EscapeActions integration
EscapeActions.preventNextClick(); // Since @textcomplete automatically closes when Escape is pressed, we
}, // integrate with our EscapeActions system by tracking open/close state.
'textComplete:hide'() { textcomplete.on('show', () => {
Tracker.afterFlush(() => { dropdownMenuIsOpened = true;
// XXX Hack. We unfortunately need to set a setTimeout here to make the });
// `noClickEscapeOn` work bellow, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the textcomplete.on('selected', () => {
// next item in the stack (for example the minicard editor) which we EscapeActions.preventNextClick();
// don't want. });
setTimeout(() => {
dropdownMenuIsOpened = false; textcomplete.on('hidden', () => {
}, 100); Tracker.afterFlush(() => {
}); // XXX Hack. We unfortunately need to set a setTimeout here to make the
}, // `noClickEscapeOn` work below, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the
// next item in the stack (for example the minicard editor) which we
// don't want.
setTimeout(() => {
dropdownMenuIsOpened = false;
}, 100);
});
});
return textcomplete;
}
// jQuery extension for backward compatibility
$.fn.escapeableTextComplete = function(strategies, options = {}) {
return this.each(function() {
createEscapeableTextComplete(this, strategies, options);
}); });
return this;
}; };
EscapeActions.register( EscapeActions.register(

51
package-lock.json generated
View file

@ -118,6 +118,37 @@
} }
} }
}, },
"@textcomplete/contenteditable": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/@textcomplete/contenteditable/-/contenteditable-0.1.13.tgz",
"integrity": "sha512-O2BNqtvP0I1lL8WIwJ/ilCVi6rEJu2Jtj7Nnx8+XSN66aoBV5pdl0c1IXFfNvGU5kJh+6EOxkDEmm2NhYCIXlw==",
"requires": {
"@textcomplete/utils": "^0.1.13"
}
},
"@textcomplete/core": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.13.tgz",
"integrity": "sha512-C4S+ihQU5HsKQ/TbsmS0e7hfPZtLZbEXj5NDUgRnhu/1Nezpu892bjNZGeErZm+R8iyDIT6wDu6EgIhng4M8eQ==",
"requires": {
"eventemitter3": "^5.0.1"
}
},
"@textcomplete/textarea": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.13.tgz",
"integrity": "sha512-GNathnXpV361YuZrBVXvVqFYZ5NQZsjGC7Bt2sCUA/RTWlIgxHxC0ruDChYyRDx4siQZiZZOO5pWz+z1x8pZFQ==",
"requires": {
"@textcomplete/utils": "^0.1.13",
"textarea-caret": "^3.1.0",
"undate": "^0.3.0"
}
},
"@textcomplete/utils": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.13.tgz",
"integrity": "sha512-5UW9Ee0WEX1s9K8MFffo5sfUjYm3YVhtqRhAor/ih7p0tnnpaMB7AwMRDKwhSIQL6O+g1fmEkxCeO8WqjPzjUA=="
},
"@tokenizer/token": { "@tokenizer/token": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@ -724,6 +755,11 @@
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
}, },
"eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
},
"events": { "events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -907,6 +943,11 @@
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
} }
}, },
"hotkeys-js": {
"version": "3.13.15",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.15.tgz",
"integrity": "sha512-gHh8a/cPTCpanraePpjRxyIlxDFrIhYqjuh01UHWEwDpglJKCnvLW8kqSx5gQtOuSsJogNZXLhOdbSExpgUiqg=="
},
"htmlparser2": { "htmlparser2": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@ -2812,6 +2853,11 @@
"readable-stream": "^3.1.1" "readable-stream": "^3.1.1"
} }
}, },
"textarea-caret": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
"integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
},
"tmp": { "tmp": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
@ -2884,6 +2930,11 @@
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}, },
"undate": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz",
"integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q=="
},
"unzipper": { "unzipper": {
"version": "0.10.14", "version": "0.10.14",
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",

View file

@ -21,12 +21,14 @@
"@mapbox/node-pre-gyp": "^2.0.3", "@mapbox/node-pre-gyp": "^2.0.3",
"@meteorjs/reify": "^0.25.4", "@meteorjs/reify": "^0.25.4",
"@rwap/jquery-ui-touch-punch": "^1.0.11", "@rwap/jquery-ui-touch-punch": "^1.0.11",
"@textcomplete/contenteditable": "^0.1.13",
"@textcomplete/core": "^0.1.13",
"@textcomplete/textarea": "^0.1.13",
"@wekanteam/dragscroll": "^0.0.9", "@wekanteam/dragscroll": "^0.0.9",
"@wekanteam/exceljs": "^4.6.0", "@wekanteam/exceljs": "^4.6.0",
"@wekanteam/html-to-markdown": "^1.0.2", "@wekanteam/html-to-markdown": "^1.0.2",
"@wekanteam/meteor-globals": "^1.1.6", "@wekanteam/meteor-globals": "^1.1.6",
"@wekanteam/meteor-reactive-cache": "^1.0.7", "@wekanteam/meteor-reactive-cache": "^1.0.7",
"meteor-node-stubs": "npm:@wekanteam/meteor-node-stubs@^1.2.7",
"ajv": "^6.12.6", "ajv": "^6.12.6",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bson": "^4.7.2", "bson": "^4.7.2",
@ -37,6 +39,7 @@
"fibers": "^5.0.3", "fibers": "^5.0.3",
"file-type": "^16.5.4", "file-type": "^16.5.4",
"filesize": "^8.0.7", "filesize": "^8.0.7",
"hotkeys-js": "^3.13.15",
"i18next": "^21.10.0", "i18next": "^21.10.0",
"i18next-sprintf-postprocessor": "^0.2.2", "i18next-sprintf-postprocessor": "^0.2.2",
"jquery": "^3.7.1", "jquery": "^3.7.1",
@ -47,6 +50,7 @@
"markdown-it-emoji": "^2.0.0", "markdown-it-emoji": "^2.0.0",
"markdown-it-mathjax3": "^4.3.2", "markdown-it-mathjax3": "^4.3.2",
"meteor-accounts-t9n": "^2.6.0", "meteor-accounts-t9n": "^2.6.0",
"meteor-node-stubs": "npm:@wekanteam/meteor-node-stubs@^1.2.7",
"os": "^0.1.2", "os": "^0.1.2",
"papaparse": "^5.5.3", "papaparse": "^5.5.3",
"pretty-ms": "^7.0.1", "pretty-ms": "^7.0.1",