diff --git a/app/scripts/dispatcher.ts b/app/scripts/dispatcher.ts index 57377c8..b10f758 100644 --- a/app/scripts/dispatcher.ts +++ b/app/scripts/dispatcher.ts @@ -1,9 +1,11 @@ +import { Logger } from "./logger"; export type Listener = (d: any) => any; export class Dispatcher { protected listeners = new Map>(); protected dispatch(event: string, data?: any): void { + Logger.debug(`[${this.constructor.name}]: ${event}`); if (!this.listeners.has(event)) return; this.listeners.get(event).forEach(function (listener) { listener(data); diff --git a/app/scripts/injected.js b/app/scripts/injected.js index 9f1184f..b5f61d9 100644 --- a/app/scripts/injected.js +++ b/app/scripts/injected.js @@ -1,9 +1,23 @@ -/* global document, CustomEvent, _debug_editors, Event */ -document.addEventListener("variable_query", function (evt) { - var query = evt.detail; - var res = eval(query); - var resEvent = new CustomEvent("variable_query_" + query, { detail: res }); - document.dispatchEvent(resEvent); +/* global document, CustomEvent, Event */ + +document.addEventListener("slext:initializeStoreWatchers", () => { + window.overleaf?.unstable.store.watch( + "project", + (project) => { + var resEvent = new CustomEvent("slext:setProject", { detail: project }); + document.dispatchEvent(resEvent); + }, + true + ); + + window.overleaf?.unstable.store.watch("editor.open_doc_id", (id) => { + var resEvent = new CustomEvent("slext:fileChanged", { detail: id }); + document.dispatchEvent(resEvent); + }); +}); + +document.addEventListener("slext:doFileChange", ({ detail: id }) => { + window.dispatchEvent(new CustomEvent("editor.openDoc", { detail: id })); }); // eslint-disable-next-line no-undef @@ -104,22 +118,3 @@ window.addEventListener("UNSTABLE_editor:extensions", (event) => { }); extensions.push(requestWrapInCommand); }); - -var limit = 50; -var tries = 0; -var int = setInterval(function () { - try { - if (_debug_editors && _debug_editors.length) { - clearInterval(int); - var editor = _debug_editors[0]; - editor.on("changeSession", function () { - var event = new Event("slext_editorChanged"); - document.dispatchEvent(event); - }); - } - tries++; - } catch (e) { - if (!(e instanceof ReferenceError)) throw e; - if (tries++ >= limit) clearInterval(int); - } -}, 100); diff --git a/app/scripts/loader.ts b/app/scripts/loader.ts index 83ec7bc..962350e 100644 --- a/app/scripts/loader.ts +++ b/app/scripts/loader.ts @@ -20,10 +20,11 @@ function projectLoaded(url) { const i = setInterval(() => { if (projectLoaded(window.location.href)) { clearInterval(i); - PageHook.initialize(); + PageHook.inject(); const slext: Slext = Container.get(Slext); const interval = setInterval(() => { if (!slext.isLoaded()) return; + PageHook.initialize(); clearInterval(interval); Container.get(Settings); Container.get(Shortcut); diff --git a/app/scripts/pagehook.service.ts b/app/scripts/pagehook.service.ts index bb9c07c..7d9af9b 100644 --- a/app/scripts/pagehook.service.ts +++ b/app/scripts/pagehook.service.ts @@ -2,32 +2,16 @@ import { Service } from "typedi"; @Service() export class PageHook { - public static evaluateJS(variable: string): Promise { - const promise = new Promise((accept, _resolve) => { - const query = new CustomEvent("variable_query", { detail: variable }); - const eventListener = (res: CustomEvent) => { - document.removeEventListener("variable_query_" + variable, eventListener); - accept(res.detail); - }; - document.addEventListener("variable_query_" + variable, eventListener); - document.dispatchEvent(query); - }); - return promise; - } - - public static initialize(): void { + public static inject(): void { const s = document.createElement("script"); - s.src = chrome.extension.getURL("scripts/injected.js"); + s.src = chrome.runtime.getURL("scripts/injected.js"); s.onload = function () { s.remove(); }; (document.head || document.documentElement).appendChild(s); } - // eslint-disable-next-line @typescript-eslint/ban-types - public static call(fun: Function, args?: Array): Promise { - args = args || []; - const jscode = `(${String(fun)})(${args.map((x) => '"' + x + '"').join(", ")});`; - return PageHook.evaluateJS(jscode); + public static initialize(): void { + document.dispatchEvent(new Event("slext:initializeStoreWatchers")); } } diff --git a/app/scripts/persistence.service.ts b/app/scripts/persistence.service.ts index d94d9c9..36b1cc1 100644 --- a/app/scripts/persistence.service.ts +++ b/app/scripts/persistence.service.ts @@ -19,9 +19,9 @@ export class PersistenceService { return PersistenceService.save(key + project, object); } - public static loadLocal(key: string, object: any): void { + public static loadLocal(key: string, callback: any): void { const project = PersistenceService.getProjectId(); - return PersistenceService.load(key + project, object); + return PersistenceService.load(key + project, callback); } public static save(key: string, object: any): void { diff --git a/app/scripts/slext.ts b/app/scripts/slext.ts index b371182..494be38 100644 --- a/app/scripts/slext.ts +++ b/app/scripts/slext.ts @@ -2,12 +2,13 @@ import { Dispatcher } from "./dispatcher"; import { File, FileUtils } from "./file"; import * as $ from "jquery"; import { Service } from "typedi"; -import { PageHook } from "./pagehook.service"; +import { Logger } from "./logger"; import { Utils } from "./utils"; @Service() export class Slext extends Dispatcher { private _files: Array = []; + private _currentlySelectedFile: File | null = null; private static id = 0; private loaded = false; @@ -37,7 +38,7 @@ export class Slext extends Dispatcher { } public isFullScreenPDF(): boolean { - return $(".full-size.ng-scope:not(.ng-hide)[ng-show=\"ui.view == 'pdf'\"]").length > 0; + return $(".full-size.ng-scope:not(.ng-hide)[ng-show=\"ui.view == 'pdf'\"],.pdf.full-size").length > 0; } public isFullScreenEditor(): boolean { @@ -45,7 +46,7 @@ export class Slext extends Dispatcher { } public isHistoryOpen(): boolean { - return $("#ide-body.ide-history-open").length > 0; + return $("#ide-body.ide-history-open,.history-react").length > 0; } private _toggleFullScreenPDFEditor(): void { @@ -103,36 +104,20 @@ export class Slext extends Dispatcher { } private loadingFinished(): void { - const mo = new MutationObserver((mutations, _observer) => { - if (mutations[0].addedNodes.length != 0 || mutations[0].removedNodes.length != 0) { - // Files have been added or removed from file tree - this.updateFiles(); - } - }); - const mainDocument = - document.querySelector('select[name="rootDoc_id"]') ?? document.querySelector("#settings-menu-rootDocId"); - console.log(mainDocument); - if (mainDocument) { - mo.observe(mainDocument, { - childList: true, - subtree: true, - }); - } - - this.updateFiles(); this.setupListeners(); } private setupListeners(): void { - window.addEventListener("editor.openDoc", (e: CustomEvent) => { + document.addEventListener("slext:fileChanged", (e: CustomEvent) => { const file_id = e.detail; const matches = this._files.filter((f, _i) => f.id == file_id); const file = matches.length ? matches[0] : null; + this._currentlySelectedFile = file; this.dispatch("FileSelected", file); }); - document.addEventListener("slext_editorChanged", (_e) => { - this.dispatch("editorChanged"); + document.addEventListener("slext:setProject", (e: CustomEvent) => { + this.updateFiles(e.detail); }); $(document).on( @@ -146,22 +131,19 @@ export class Slext extends Dispatcher { ); } - public updateFiles(): Promise { - return new Promise((resolve, _reject) => { - this.indexFiles().then((files: Array) => { - this._files = files; - this.dispatch("FilesChanged"); - resolve(this._files); - }); - }); - } - - private indexFiles(): Promise { - return new Promise((resolve, _reject) => { - PageHook.evaluateJS("_ide.$scope.docs").then((response: any) => { - const res = response.map((f) => FileUtils.newFile(f.doc.name, f.path, f.doc.id, "doc")); - resolve(res); - }); + public updateFiles(project: any): Promise { + function getDocsFromFolder(folder, path) { + const files = folder.docs.map((d) => FileUtils.newFile(d.name, path + "/" + d.name, d._id, "doc")); + for (const subFolder of folder.folders) { + files.push(...getDocsFromFolder(subFolder, path + "/" + subFolder.name)); + } + return files; + } + return new Promise((resolve) => { + const files = getDocsFromFolder(project.rootFolder[0], ""); + this._files = files; + this.dispatch("FilesChanged"); + resolve(this._files); }); } @@ -171,27 +153,17 @@ export class Slext extends Dispatcher { public currentFile(): Promise { return new Promise((resolve, reject) => { - PageHook.evaluateJS("_ide.editorManager.$scope.editor.open_doc_id").then((id) => { - const matches = this._files.filter((f, _i) => f.id == id); - if (matches.length == 0) { - reject(); - } - resolve(matches[0]); - }); + if (this._currentlySelectedFile) { + resolve(this._currentlySelectedFile); + } else { + reject(); + } }); } public selectFile(file: File): void { if (this._files.filter((f) => f.id == file.id && f.path == file.path).length > 0) { - PageHook.evaluateJS( - "_ide.$scope.$emit('entity:selected', {type: '" + - file.type + - "', id:'" + - file.id + - "', name:'" + - file.name + - "'})" - ); + document.dispatchEvent(new CustomEvent("slext:doFileChange", { detail: file.id })); } } } diff --git a/app/scripts/tabs.ts b/app/scripts/tabs.ts index 7de6143..c178b82 100644 --- a/app/scripts/tabs.ts +++ b/app/scripts/tabs.ts @@ -21,7 +21,7 @@ export class TabModule { private static tabsTemplate: string = require("../templates/tabs.html"); private static tabTemplate: string = require("../templates/tab.html"); protected _tabs: Tab[] = []; - protected _currentTab: Tab; + protected _currentTab: Tab | null; protected _previousTab: Tab; protected tabBar: JQuery; private currentFile: File = null; @@ -40,42 +40,60 @@ export class TabModule { this.addTabBar(); this.addCompileMainButton(); - PersistenceService.loadLocal("tabs_openFiles", (files: [any]) => { - if (!files) return; - if (files.length < 1) return; - const allfiles = this.slext.getFiles(); - files.forEach((file) => { - const fileMatch = allfiles.find((x) => x.path == file.path); - if (fileMatch == null) return; - this.openTab(fileMatch, file.favorite); + function waitForFiles() { + let checks = 100; + return new Promise((resolve, reject) => { + const interval = setInterval(() => { + if (slext.getFiles().length) { + clearInterval(interval); + resolve(); + } + if (checks-- < 0) { + clearInterval(interval); + resolve(); + } + }, 10); }); + } - PersistenceService.loadLocal("tabs_currentTab", (path: string) => { - let tab = this._tabs.findIndex((x) => x.file.path == path); - if (tab == -1) tab = 0; + waitForFiles().then(() => + PersistenceService.loadLocal("tabs_openFiles", (files: [any]) => { + if (!files) return; + if (files.length < 1) return; + const allfiles = this.slext.getFiles(); + files.forEach((file) => { + const fileMatch = allfiles.find((x) => x.path == file.path); + if (fileMatch == null) return; + this.openTab(fileMatch, file.favorite); + }); - // If no tabs are open, we cannot do anything - if (!this._tabs.length) return; + PersistenceService.loadLocal("tabs_currentTab", (path: string) => { + let tab = this._tabs.findIndex((x) => x.file.path == path); + if (tab == -1) tab = 0; - this.currentFile = this._tabs[tab].file || null; + // If no tabs are open, we cannot do anything + if (!this._tabs.length) return; - this.selectTab(tab); - }); + this.currentFile = this._tabs[tab].file || null; - PersistenceService.loadLocal("tabs_mainTab", (path: string) => { - const tab = this._tabs.findIndex((x) => x.file.path == path); - if (tab != -1) { - this.setMainTab(this._tabs[tab]); - } - }); + this.selectTab(tab); + }); - this._previousTab = null; - }); + PersistenceService.loadLocal("tabs_mainTab", (path: string) => { + const tab = this._tabs.findIndex((x) => x.file.path == path); + if (tab != -1) { + this.setMainTab(this._tabs[tab]); + } + }); + + this._previousTab = null; + }) + ); window.onbeforeunload = () => this.saveTabs(); setTimeout(() => this.ensureRightTab(), 2000); - slext.addEventListener("editorChanged", () => this.ensureRightTab()); + slext.addEventListener("FileSelected", () => this.ensureRightTab()); } protected ensureRightTab(): void { @@ -84,8 +102,8 @@ export class TabModule { .then((curFile: File) => { if (curFile.type != "doc") { this.currentFile = null; + this._currentTab?.tab.removeClass("slext-tabs__tab--active"); this._currentTab = null; - this._currentTab.tab.removeClass("slext-tabs__tab--active"); } if (this.currentFile == null || curFile.path != this.currentFile.path) { @@ -100,8 +118,7 @@ export class TabModule { }) .catch((_err) => { // No file - this._currentTab.tab.removeClass("slext-tabs__tab--active"); - this._currentTab = null; + this._currentTab?.tab.removeClass("slext-tabs__tab--active"); this.currentFile = null; }); } @@ -114,7 +131,9 @@ export class TabModule { }) ); - PersistenceService.saveLocal("tabs_currentTab", this._currentTab.file.path); + if (this._currentTab) { + PersistenceService.saveLocal("tabs_currentTab", this._currentTab.file.path); + } if (this.maintab != null && this.maintab !== undefined) { PersistenceService.saveLocal("tabs_mainTab", this.maintab.file.path); @@ -126,6 +145,8 @@ export class TabModule { protected setupListeners(): void { this.slext.addEventListener("FileSelected", (selectedFile: File) => { if (selectedFile == null) { + this._currentTab?.tab.removeClass("slext-tabs__tab--active"); + this._currentTab = null; return; } const index = this._tabs.findIndex((tab) => { @@ -264,7 +285,7 @@ export class TabModule { } private reindexTabs(): Promise { - return this.slext.updateFiles().then(() => { + return Promise.resolve(this.slext.getFiles()).then(() => { let filesRemoved = 0; for (let i = 0; i < this._tabs.length; i++) { const tab = this._tabs[i]; @@ -334,7 +355,7 @@ export class TabModule { } }; - const editorListener = $("#editor").on("keydown", (e) => { + const editorListener = $("#editor,.cm-editor").on("keydown", (e) => { removeTemp(e); }); @@ -393,6 +414,9 @@ export class TabModule { this.tabBar = $(TabModule.tabsTemplate); $("header.toolbar").after(this.tabBar); $("header.toolbar").addClass("toolbar-tabs"); + if ($(".ide-react-body").length) { + this.tabBar.addClass("slext-tabs--react"); + } $("#ide-body").addClass("ide-tabs"); } @@ -415,6 +439,12 @@ export class TabModule { if (recompile_label.length) { return recompile_label.parent(); } + // If none of those worked. Try finding a split menu button in the + // pdf toolbar. + const toolbarPdfButton = $(".toolbar-pdf .split-menu-button"); + if (toolbarPdfButton) { + return toolbarPdfButton.parent(); + } return null; }; const recompile_button = find_recompile_button(); @@ -457,7 +487,10 @@ export class TabModule { }); } - private closeTab(tab: Tab): void { + private closeTab(tab: Tab | null): void { + if (!tab) { + return; + } const overlaps = this._tabs.filter((t) => t.file.name == tab.file.name && t != tab); if (overlaps.length > 0) { this.fixOverlaps(overlaps); @@ -477,7 +510,7 @@ export class TabModule { this._tabs.splice(index, 1); this.selectTab(newIndex); - this._currentTab.tab.click(); + this._currentTab?.tab.click(); } else { const index = this._tabs.indexOf(tab); this._tabs.splice(index, 1); @@ -491,7 +524,10 @@ export class TabModule { } } - private setMainTab(tab: Tab): void { + private setMainTab(tab: Tab | null): void { + if (tab === null) { + return; + } if (this.maintab == tab) { this.maintab.tab.removeClass("slext-tabs__tab--main"); this.maintab = null; @@ -508,7 +544,10 @@ export class TabModule { } } - private setFavoriteTab(tab: Tab): void { + private setFavoriteTab(tab: Tab | null): void { + if (tab === null) { + return; + } tab.favorite = !tab.favorite; tab.tab.toggleClass("slext-tabs__tab--favorite"); } diff --git a/app/styles/contentscript.scss b/app/styles/contentscript.scss index a5b0719..9b3e568 100644 --- a/app/styles/contentscript.scss +++ b/app/styles/contentscript.scss @@ -1,5 +1,6 @@ /* CUSTOM THEMING */ +.ide-react-body.ide-tabs, #ide-body.ide-tabs { top: 80px; } @@ -31,8 +32,10 @@ font-size: 0; width: 100%; box-shadow: rgba(0, 0, 0, 0.23) 0px 10px 8px -8px; - top: 40px; - position: absolute; + &:not(.slext-tabs--react) { + top: 40px; + position: absolute; + } } &__tab { @@ -300,6 +303,7 @@ .searchbox { position: fixed; + z-index: 100; top: 10%; width: 50%; left: 0; @@ -435,6 +439,7 @@ top: 10px; right: 10px; width: 400px; + z-index: 1000; .slext__notification { display: flex;