|
|
|
@@ -4,32 +4,23 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
async onload() {
|
|
|
|
|
this.settings = Object.assign({ order: [] }, await this.loadData());
|
|
|
|
|
this.enforcing = false;
|
|
|
|
|
this.alive = true;
|
|
|
|
|
this.register(() => { this.alive = false; });
|
|
|
|
|
// icons can attach within milliseconds of each other on startup
|
|
|
|
|
// (resetTimer, or it fires 200ms after the first one instead of the last)
|
|
|
|
|
this.debouncedEnforce = debounce(() => this.enforceOrder(), 200, true);
|
|
|
|
|
// disconnecting the observer doesn't stop an already-scheduled call
|
|
|
|
|
this.register(() => this.debouncedEnforce.cancel());
|
|
|
|
|
|
|
|
|
|
this.app.workspace.onLayoutReady(() => {
|
|
|
|
|
// not tied to our lifecycle; still fires if we were disabled during boot
|
|
|
|
|
if (this.unloaded) return;
|
|
|
|
|
if (!this.alive) return;
|
|
|
|
|
this.patchRibbon();
|
|
|
|
|
this.enforceOrder();
|
|
|
|
|
this.observeRibbon();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onunload() {
|
|
|
|
|
this.unloaded = true;
|
|
|
|
|
this.observer?.disconnect();
|
|
|
|
|
// disconnect doesn't stop an already-scheduled call
|
|
|
|
|
this.debouncedEnforce?.cancel();
|
|
|
|
|
|
|
|
|
|
const ribbon = this.getRibbon();
|
|
|
|
|
// don't restore over someone else's patch, just go inert instead
|
|
|
|
|
if (ribbon && ribbon.onChange === this.wrapper) {
|
|
|
|
|
ribbon.onChange = this.originalOnChange;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getRibbon() {
|
|
|
|
|
return this.app.workspace.leftRibbon ?? null;
|
|
|
|
|
}
|
|
|
|
@@ -44,8 +35,9 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
const containerEl = this.getRibbonContainerEl(ribbon);
|
|
|
|
|
if (!containerEl) return;
|
|
|
|
|
|
|
|
|
|
this.observer = new MutationObserver(() => this.debouncedEnforce());
|
|
|
|
|
this.observer.observe(containerEl, { childList: true });
|
|
|
|
|
const observer = new MutationObserver(() => this.debouncedEnforce());
|
|
|
|
|
observer.observe(containerEl, { childList: true });
|
|
|
|
|
this.register(() => observer.disconnect());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// hook the ribbon's own onChange so drags and hide/show toggles update
|
|
|
|
@@ -57,13 +49,18 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
const original = ribbon.onChange.bind(ribbon);
|
|
|
|
|
this.originalOnChange = original;
|
|
|
|
|
|
|
|
|
|
this.wrapper = (save) => {
|
|
|
|
|
const wrapper = (save) => {
|
|
|
|
|
original(save);
|
|
|
|
|
if (save && !this.enforcing && !this.unloaded) {
|
|
|
|
|
if (save && !this.enforcing && this.alive) {
|
|
|
|
|
this.recordOrder(ribbon);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ribbon.onChange = this.wrapper;
|
|
|
|
|
ribbon.onChange = wrapper;
|
|
|
|
|
|
|
|
|
|
this.register(() => {
|
|
|
|
|
// don't restore over someone else's patch, just go inert instead
|
|
|
|
|
if (ribbon.onChange === wrapper) ribbon.onChange = original;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enforceOrder() {
|
|
|
|
@@ -76,13 +73,12 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// anything we haven't seen before sorts to the bottom, in the order it came in
|
|
|
|
|
const rank = new Map(order.map((id, i) => [id, i]));
|
|
|
|
|
const sorted = [...ribbon.items].sort((a, b) => {
|
|
|
|
|
const ia = order.indexOf(a.id);
|
|
|
|
|
const ib = order.indexOf(b.id);
|
|
|
|
|
if (ia === -1 && ib === -1) return 0;
|
|
|
|
|
if (ia === -1) return 1;
|
|
|
|
|
if (ib === -1) return -1;
|
|
|
|
|
return ia - ib;
|
|
|
|
|
const ia = rank.get(a.id) ?? Infinity;
|
|
|
|
|
const ib = rank.get(b.id) ?? Infinity;
|
|
|
|
|
return ia === ib ? 0 : ia - ib;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const changed = sorted.some((item, i) => ribbon.items[i] !== item);
|
|
|
|
@@ -100,13 +96,25 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
this.mergeNewItems(ribbon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a drag saves, and the dom change it makes wakes the observer up, which can
|
|
|
|
|
// save again before the first one has finished writing. queue them instead.
|
|
|
|
|
save() {
|
|
|
|
|
this.writing = Promise.resolve(this.writing)
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
.then(() => this.saveData(this.settings));
|
|
|
|
|
return this.writing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
recordOrder(ribbon) {
|
|
|
|
|
const ids = ribbon.items.map((item) => item.id);
|
|
|
|
|
if (this.sameOrder(ids, this.settings.order)) return;
|
|
|
|
|
this.settings.order = ids;
|
|
|
|
|
this.saveData(this.settings);
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ids of plugins that are gone stay in the list on purpose -- nothing prunes
|
|
|
|
|
// them, so reinstalling one puts its icon back where it used to be
|
|
|
|
|
//
|
|
|
|
|
// only append icons we haven't seen before instead of overwriting the
|
|
|
|
|
// whole order -- enforceOrder() runs during boot too, before every
|
|
|
|
|
// plugin has attached its icon yet, and a full overwrite there was
|
|
|
|
@@ -116,7 +124,7 @@ module.exports = class RibbonClasp extends Plugin {
|
|
|
|
|
const newIds = ribbon.items.map((item) => item.id).filter((id) => !known.has(id));
|
|
|
|
|
if (newIds.length === 0) return;
|
|
|
|
|
this.settings.order = [...this.settings.order, ...newIds];
|
|
|
|
|
this.saveData(this.settings);
|
|
|
|
|
this.save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sameOrder(a, b) {
|
|
|
|
|