Skip to content

Commit

Permalink
Merge pull request #35 from /issues/33
Browse files Browse the repository at this point in the history
Issues/33
  • Loading branch information
NekitCorp authored Jun 7, 2024
2 parents 1604848 + ee7da94 commit e2aa5fd
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [master]

jobs:
test:
e2e-test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "better-workflowy",
"version": "2.2.1",
"version": "2.2.2",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
9 changes: 6 additions & 3 deletions src/content/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AppLoader } from '../modules/app-loader';
import { DomManager } from '../modules/dom-manager';
import { HashtagColor } from '../modules/hashtag-color';
import { HashtagSwap } from '../modules/hashtag-swap';
Expand All @@ -8,14 +9,16 @@ import { storage } from '../modules/storage';
import { TimeManager } from '../modules/time-manager';

const logger = new Logger();
const domManager = new DomManager(logger);
const hotkeysManager = new HotkeysManager(logger);
const appLoader = new AppLoader(logger);

logger.log('Waiting for app loading...');

domManager.loadingApp().then(() => {
appLoader.wait().then(() => {
logger.log('App loaded! Modules initializing...');

const domManager = new DomManager(logger);
const hotkeysManager = new HotkeysManager(logger);

storage.readStorage((data) => {
logger.log('Options: ' + JSON.stringify(data));

Expand Down
76 changes: 76 additions & 0 deletions src/modules/app-loader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const PAGE_ELEMENT_CLASS_NAME = 'page';
const APP_LOAD_TIMEOUT = 30; // seconds

export class AppLoader {
private resolveLoadingPromise: (() => void) | null = null;
private observer: MutationObserver;

constructor(private logger: ILogger) {
this.observer = new MutationObserver(this.mutationCallback);
this.observer.observe(document.body, { childList: true, subtree: true });
}

public wait(): Promise<void> {
return new Promise((res) => {
if (document.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`)) {
this.observer.disconnect();
res();
} else {
this.startAppLoadWaiting();
this.resolveLoadingPromise = res;
}
});
}

private mutationCallback: MutationCallback = (
mutations: MutationRecord[],
observer: MutationObserver,
): void => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) {
return;
}

// Detect app load
if (
this.resolveLoadingPromise !== null &&
(node.matches(`.${PAGE_ELEMENT_CLASS_NAME}`) ||
node.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`))
) {
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
this.observer.disconnect();
}
}
}
};

/**
* Start a timer waiting for the app to load.
*/
private startAppLoadWaiting() {
let seconds = 0;

const interval = setInterval(() => {
seconds += 1;

if (this.resolveLoadingPromise === null) {
return clearInterval(interval);
}

if (document.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`)) {
clearInterval(interval);
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
}

if (seconds > APP_LOAD_TIMEOUT) {
clearInterval(interval);
this.logger.error(
`Failed to wait for the app to load within ${APP_LOAD_TIMEOUT} seconds.`,
);
}
}, 1 * 1000);
}
}
3 changes: 1 addition & 2 deletions src/modules/dom-manager/dom-manager.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
interface IDomManager {
loadingApp(): Promise<void>;
subscribe(callback: (node: HTMLElement) => void): void;
subscribeToContentChanges(callback: (node: HTMLElement) => void): void;
createHashtag(text: string): HTMLSpanElement;
}
65 changes: 12 additions & 53 deletions src/modules/dom-manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
const PAGE_ELEMENT_CLASS_NAME = 'page';
const PAGE_CONTAINER_SELECTOR = '.pageContainer';
const CONTENT_ROW_ELEMENT_CLASS_NAME = 'innerContentContainer';
const TAG_ELEMENT_TEXT_CLASS_NAME = 'contentTagText';
const APP_LOAD_TIMEOUT = 30; // seconds

export class DomManager implements IDomManager {
private resolveLoadingPromise: (() => void) | null = null;
private subscribers: ((node: HTMLElement) => void)[] = [];

constructor(private logger: ILogger) {
const observer = new MutationObserver(this.mutationCallback);
observer.observe(document.body, { childList: true, subtree: true });
}
const pageContainer = document.querySelector(PAGE_CONTAINER_SELECTOR);

public loadingApp(): Promise<void> {
return new Promise((res) => {
if (document.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`)) {
res();
} else {
this.startAppLoadWaiting();
this.resolveLoadingPromise = res;
}
if (!pageContainer) {
this.logger.error(`Element ${PAGE_CONTAINER_SELECTOR} not found.`);
return;
}

const observer = new MutationObserver(this.mutationCallback);
observer.observe(pageContainer, {
childList: true,
subtree: true,
});
}

Expand Down Expand Up @@ -48,38 +45,10 @@ export class DomManager implements IDomManager {
return container;
}

public subscribe(callback: (node: HTMLElement) => void) {
public subscribeToContentChanges(callback: (node: HTMLElement) => void) {
this.subscribers.push(callback);
}

/**
* Start a timer waiting for the app to load.
*/
private startAppLoadWaiting() {
let seconds = 0;

const interval = setInterval(() => {
seconds += 1;

if (this.resolveLoadingPromise === null) {
return clearInterval(interval);
}

if (document.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`)) {
clearInterval(interval);
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
}

if (seconds > APP_LOAD_TIMEOUT) {
clearInterval(interval);
this.logger.error(
`Failed to wait for the app to load within ${APP_LOAD_TIMEOUT} seconds.`,
);
}
}, 1 * 1000);
}

private mutationCallback: MutationCallback = (
mutations: MutationRecord[],
observer: MutationObserver,
Expand All @@ -96,16 +65,6 @@ export class DomManager implements IDomManager {
return;
}

// Detect app load
if (
this.resolveLoadingPromise !== null &&
(node.matches(`.${PAGE_ELEMENT_CLASS_NAME}`) ||
node.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`))
) {
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
}

// Detect any changes on content rows with hashtags
if (node.classList.contains(CONTENT_ROW_ELEMENT_CLASS_NAME)) {
const contentTag = node.querySelector(`.${TAG_ELEMENT_TEXT_CLASS_NAME}`);
Expand Down
2 changes: 1 addition & 1 deletion src/modules/hashtag-color/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export class HashtagColor {
this.paintColorHashtagLine(node as HTMLElement);
}

this.domManager.subscribe(this.paintColorHashtagLine);
this.domManager.subscribeToContentChanges(this.paintColorHashtagLine);
}

private paintColorHashtagLine = (node: HTMLElement) => {
Expand Down
26 changes: 18 additions & 8 deletions src/modules/time-manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { requestIdleInterval } from '../request-idle-interval';
import { formatTime, getTagSeconds } from './utils';

const PAGE_CONTAINER_SELECTOR = '.pageContainer';
const COUNTER_ID = 'bw-time-counter';
const HIGHLIGHT_COLOR = '#13cbd3';

export class TimeManager implements ITimeManager {
private HIGHLIGHT_COLOR = '#13cbd3';
private COUNTER_ID = 'bw-time-counter';
private timeCounterElement: HTMLDivElement | null = null;
private pageContainer: HTMLDivElement;

constructor(
private format: FormatTime,
Expand All @@ -13,17 +16,24 @@ export class TimeManager implements ITimeManager {
) {}

public init() {
this.pageContainer = document.querySelector(PAGE_CONTAINER_SELECTOR);

if (!this.pageContainer) {
this.logger.error(`Element ${PAGE_CONTAINER_SELECTOR} not found.`);
return;
}

this.createTimeCounterElement();
this.highlightTimeHashtag();
this.renderTotalTime();

this.domManager.subscribe(this.highlightTimeHashtag);
this.domManager.subscribeToContentChanges(this.highlightTimeHashtag);
requestIdleInterval(this.renderTotalTime, { interval: 1000 });
}

private createTimeCounterElement() {
// Try find already added counter
const counter = document.getElementById(this.COUNTER_ID);
const counter = document.getElementById(COUNTER_ID);

if (counter instanceof HTMLDivElement) {
this.timeCounterElement = counter;
Expand All @@ -42,7 +52,7 @@ export class TimeManager implements ITimeManager {
// Create counter div element
const div = document.createElement('div');

div.id = this.COUNTER_ID;
div.id = COUNTER_ID;
div.style.fontSize = 13 + 'px';
div.style.marginRight = 10 + 'px';

Expand All @@ -60,7 +70,7 @@ export class TimeManager implements ITimeManager {
}

// Calculate total time
const tags = [...document.querySelectorAll('.contentTag')].map(
const tags = [...this.pageContainer.querySelectorAll('.contentTag')].map(
(el: HTMLElement) => el.innerText,
);
let totalSeconds = tags.reduce((acc, val) => acc + getTagSeconds(val), 0);
Expand All @@ -73,11 +83,11 @@ export class TimeManager implements ITimeManager {
* Highlight recognized time tags
*/
private highlightTimeHashtag = () => {
const tags = document.querySelectorAll('.contentTag');
const tags = this.pageContainer.querySelectorAll('.contentTag');

for (const tag of tags) {
if (getTagSeconds((tag as HTMLElement).innerText) > 0) {
(tag as HTMLElement).style.outline = `1px dashed ${this.HIGHLIGHT_COLOR}`;
(tag as HTMLElement).style.outline = `1px dashed ${HIGHLIGHT_COLOR}`;
}
}
};
Expand Down
17 changes: 12 additions & 5 deletions src/options/components/Options.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
{/each}
</tbody>
</table>
<Button class="add-button" title="Add" variant="emoji" on:click={addSearch}>➕</Button>
<Button class="add-button" title="Add" variant="emoji" on:click={addSearch}>
➕ Add
</Button>
</Fieldset>

<Fieldset title="🔀 Swap hashtags on hotkey">
Expand Down Expand Up @@ -145,7 +147,9 @@
{/each}
</tbody>
</table>
<Button class="add-button" title="Add" variant="emoji" on:click={addSwap}>➕</Button>
<Button class="add-button" title="Add" variant="emoji" on:click={addSwap}>
➕ Add
</Button>
</Fieldset>

<Fieldset title="🕒 Calculate total time">
Expand Down Expand Up @@ -230,7 +234,9 @@
{/each}
</tbody>
</table>
<Button class="add-button" title="Add" variant="emoji" on:click={addColor}>➕</Button>
<Button class="add-button" title="Add" variant="emoji" on:click={addColor}>
➕ Add
</Button>
</Fieldset>

<div class="footer">
Expand Down Expand Up @@ -268,8 +274,9 @@
}
.container :global(.add-button) {
display: flex;
margin: 0 auto;
width: 100%;
border-radius: 0;
color: initial;
}
label {
Expand Down

0 comments on commit e2aa5fd

Please sign in to comment.