From 4326613edd88260812e236979bcb9f6e34ad3a43 Mon Sep 17 00:00:00 2001 From: Gickass Date: Sat, 17 Jan 2026 22:40:56 +0800 Subject: [PATCH 01/13] aa --- src/pages/syncPage.ts | 53 ++++++++++++ src/utils/mangaProgress/MangaProgress.ts | 101 ++++++++++++++++++++++- 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/pages/syncPage.ts b/src/pages/syncPage.ts index 3476760431..efa3fbb6d7 100644 --- a/src/pages/syncPage.ts +++ b/src/pages/syncPage.ts @@ -337,6 +337,59 @@ export class SyncPage { }); }); } + if (this.page.type === 'manga' && this.page.sync.readerConfig) { + // Update MangaProgress to use construct identifer and chapter + const waitForChapIden = setInterval(() => { + const identifier = this.page.sync.getIdentifier(this.url); + const chapter = this.page.sync.getEpisode(this.url); + + if (!identifier || chapter === null) return; + + this.mangaProgress?.setIdentifier(identifier); + this.mangaProgress?.setChapter(chapter); + const savedProgress = this.mangaProgress?.loadMangaPage(); + logger.log('Send Progress', savedProgress); + + clearInterval(waitForChapIden); + }, 300); + + const mangaPage = new MangaProgress( + this.page.sync.readerConfig || [], + this.page.name, + this.curState.identifier, + this.curState.episode, + ); + + const savedProgress = mangaPage?.loadMangaPage(); + logger.log('Load Progress', savedProgress); + if (!savedProgress) return; + + if (savedProgress) { + const resumeMsg = utils.flashm( + ` +
+ `, + { + permanent: true, + error: false, + type: 'resume', + minimized: false, + position: 'bottom', + }, + ); + + resumeMsg.find('.sync').on('click', () => { + this.mangaProgress?.resume(savedProgress.current); + j.$(resumeMsg).remove(); + }); + + resumeMsg.find('.resumeClose').on('click', () => { + j.$(resumeMsg).remove(); + }); + } + } logger.m('Sync', 'green').log(state); } else { if (typeof this.page.overview === 'undefined') { diff --git a/src/utils/mangaProgress/MangaProgress.ts b/src/utils/mangaProgress/MangaProgress.ts index f69127fd99..c767121c90 100644 --- a/src/utils/mangaProgress/MangaProgress.ts +++ b/src/utils/mangaProgress/MangaProgress.ts @@ -34,6 +34,10 @@ export class MangaProgress { protected page: string; + protected identifier?: string; + + protected chapter?: number; + protected result: mangaProgress | null = null; protected interval; @@ -42,9 +46,11 @@ export class MangaProgress { // do nothing }; - constructor(configs: mangaProgressConfig[], page: string) { + constructor(configs: mangaProgressConfig[], page: string, identifier?: string, chapter?: number) { this.configs = [...alternativeReader, ...configs]; this.page = page; + this.identifier = identifier; + this.chapter = chapter; logger.log('config', this.configs); } @@ -141,10 +147,103 @@ export class MangaProgress { setProgress() { j.$('.ms-progress').css('width', `${this.progressPercentage()! * 100}%`); j.$('#malSyncProgress').removeClass('ms-loading').removeClass('ms-done'); + + this.saveMangaPage(); + if (this.finished() && j.$('#malSyncProgress').length) { j.$('#malSyncProgress').addClass('ms-done'); j.$('.flash.type-update .sync').trigger('click'); clearInterval(this.interval); } } + + setIdentifier(identifier: string) { + if (!this.identifier && identifier) { + this.identifier = identifier; + } + } + + setChapter(chapter: number) { + if (this.chapter !== null) { + this.chapter = chapter; + } + } + + public getConfigs() { + return this.configs; + } + + saveMangaPage() { + if (!this.result) return; + + const data = { + current: this.result.current, + total: this.result.total, + }; + + localStorage.setItem( + `mangaProgress-${this.page}-${this.identifier}-${this.chapter}`, + JSON.stringify(data), + ); + + logger.log(`Saved manga progress at ${this.page}-${this.identifier}-${this.chapter}`, data); + } + + loadMangaPage() { + const saved = localStorage.getItem( + `mangaProgress-${this.page}-${this.identifier}-${this.chapter}`, + ); + if (!saved) return null; + + try { + const data = JSON.parse(saved); + + // Do NOT scroll here; scroll will happen when user clicks button + return data; + } catch (e) { + console.warn('Failed to load manga progress', e); + return null; + } + } + + resume(savedIndex: number) { + const scrollEl = this.getScrollableElement(); + logger.log('manga scroll element', scrollEl); + if (!scrollEl) return; + + const scrollLoop = () => { + const current = this.getProgress()?.current ?? 0; + + // Stop when we reach saved progress + if (current >= savedIndex) { + logger.log('Reached saved progress', current); + return; + } + + // Scroll continuously by a chunk + scrollEl.scrollBy({ top: 40, behavior: 'instant' }); + + // Keep looping every frame + requestAnimationFrame(scrollLoop); + }; + + scrollLoop(); + } + + private getScrollableElement(): HTMLElement { + const all = Array.from(document.body.querySelectorAll('*')) as HTMLElement[]; + + const container = all.find(el => { + const style = getComputedStyle(el); + return ( + (style.overflowY === 'auto' || style.overflowY === 'scroll') && + el.scrollHeight > el.clientHeight + ); + }); + + return (container || + document.scrollingElement || + document.documentElement || + document.body) as HTMLElement; + } } From 61af3a672d293801764cb1855856149e0f484080 Mon Sep 17 00:00:00 2001 From: Gickass Date: Tue, 20 Jan 2026 07:41:42 +0800 Subject: [PATCH 02/13] Finally working --- src/pages/syncPage.ts | 94 +++++------- src/utils/mangaProgress/MangaProgress.ts | 187 ++++++++++++++++++----- 2 files changed, 188 insertions(+), 93 deletions(-) diff --git a/src/pages/syncPage.ts b/src/pages/syncPage.ts index efa3fbb6d7..aa286bc2b2 100644 --- a/src/pages/syncPage.ts +++ b/src/pages/syncPage.ts @@ -243,6 +243,46 @@ export class SyncPage { } } + private handleMangaResume() { + if ( + !this.page.sync.readerConfig || + this.curState === 'undefined' || + this.curState.identifier === 'undefined' || + this.curState.episode === 'undefined' + ) + return; + this.mangaProgress?.setChapter(this.page.sync.getEpisode(this.url)); + this.mangaProgress?.setIdentifier(this.page.sync.getIdentifier(this.url)); + const savedProgress = this.mangaProgress?.loadMangaPage(); + + if (savedProgress) { + logger.log('Manga Progress Found', this.mangaProgress); + const resumeMsg = utils.flashm( + ` +
+ `, + { + permanent: true, + error: false, + type: 'resume', + minimized: false, + position: 'bottom', + }, + ); + + resumeMsg.find('.sync').on('click', () => { + this.mangaProgress?.resume(savedProgress); + j.$(resumeMsg).remove(); + }); + + resumeMsg.find('.resumeClose').on('click', () => { + j.$(resumeMsg).remove(); + }); + } + } + curState: any = undefined; tempPlayer: any = undefined; @@ -337,58 +377,8 @@ export class SyncPage { }); }); } - if (this.page.type === 'manga' && this.page.sync.readerConfig) { - // Update MangaProgress to use construct identifer and chapter - const waitForChapIden = setInterval(() => { - const identifier = this.page.sync.getIdentifier(this.url); - const chapter = this.page.sync.getEpisode(this.url); - - if (!identifier || chapter === null) return; - - this.mangaProgress?.setIdentifier(identifier); - this.mangaProgress?.setChapter(chapter); - const savedProgress = this.mangaProgress?.loadMangaPage(); - logger.log('Send Progress', savedProgress); - - clearInterval(waitForChapIden); - }, 300); - - const mangaPage = new MangaProgress( - this.page.sync.readerConfig || [], - this.page.name, - this.curState.identifier, - this.curState.episode, - ); - - const savedProgress = mangaPage?.loadMangaPage(); - logger.log('Load Progress', savedProgress); - if (!savedProgress) return; - - if (savedProgress) { - const resumeMsg = utils.flashm( - ` -
- `, - { - permanent: true, - error: false, - type: 'resume', - minimized: false, - position: 'bottom', - }, - ); - - resumeMsg.find('.sync').on('click', () => { - this.mangaProgress?.resume(savedProgress.current); - j.$(resumeMsg).remove(); - }); - - resumeMsg.find('.resumeClose').on('click', () => { - j.$(resumeMsg).remove(); - }); - } + if (this.page.type === 'manga') { + this.handleMangaResume(); } logger.m('Sync', 'green').log(state); } else { diff --git a/src/utils/mangaProgress/MangaProgress.ts b/src/utils/mangaProgress/MangaProgress.ts index c767121c90..6a596d87ee 100644 --- a/src/utils/mangaProgress/MangaProgress.ts +++ b/src/utils/mangaProgress/MangaProgress.ts @@ -148,7 +148,7 @@ export class MangaProgress { j.$('.ms-progress').css('width', `${this.progressPercentage()! * 100}%`); j.$('#malSyncProgress').removeClass('ms-loading').removeClass('ms-done'); - this.saveMangaPage(); + if (this.page && this.identifier && this.chapter) this.saveMangaPage(); if (this.finished() && j.$('#malSyncProgress').length) { j.$('#malSyncProgress').addClass('ms-done'); @@ -158,38 +158,110 @@ export class MangaProgress { } setIdentifier(identifier: string) { - if (!this.identifier && identifier) { + if (this.identifier !== identifier) { this.identifier = identifier; } } setChapter(chapter: number) { - if (this.chapter !== null) { + if (this.chapter !== chapter) { this.chapter = chapter; } } - public getConfigs() { - return this.configs; + // Page saving/loading logic + + private isLongStrip(): boolean { + const threshold = window.innerHeight * 2; + + const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); + if (docHeight > threshold) return true; + + const containers = Array.from(document.querySelectorAll('div, section, article')); + const hasBigContainer = containers.some(el => el.scrollHeight > threshold); + + return hasBigContainer; } saveMangaPage() { - if (!this.result) return; + if (!this.isLongStrip()) return; + + const elements = Array.from(document.body.querySelectorAll('*')); + const viewportCenter = window.innerHeight / 2; + + const result = elements.reduce( + (closest, el) => { + const style = getComputedStyle(el); + + // Skip invisible elements + if (style.display === 'none' || style.opacity === '0') return closest; + + // Filter for OR divs with background images + const hasImage = + el.tagName.toLowerCase() === 'img' || + (style.backgroundImage && style.backgroundImage !== 'none'); + + if (!hasImage) return closest; + + const rect = el.getBoundingClientRect(); + + // Filters (Off-screen or too small elements) + if (rect.bottom < 0 || rect.top > window.innerHeight) return closest; + if (rect.height < 100) return closest; + + const elementCenter = rect.top + rect.height / 2; + const distance = Math.abs(elementCenter - viewportCenter); + + // comparison If we have no 'closest' yet, or the current 'distance' is smaller than the filtered distance + if (!closest || distance < closest.distance) { + return { el, distance }; + } + + return closest; + }, + null as { el: HTMLElement; distance: number } | null, + ); + + let relativeOffset = 0.5; + const nearestElement = result?.el; + if (nearestElement) { + const pixelPos = nearestElement.getBoundingClientRect(); + if (pixelPos.height > 0) { + relativeOffset = (viewportCenter - pixelPos.top) / pixelPos.height; + } + } + + function getElementSelector(el: HTMLElement): number[] { + const path: number[] = []; + let current: HTMLElement | null = el; + + while (current && current !== document.body) { + const parent = current.parentElement; + if (!parent) break; + + const index = Array.from(parent.children).indexOf(current); + path.unshift(index); + current = parent; + } + return path; + } const data = { - current: this.result.current, - total: this.result.total, + current: this.result?.current, + total: this.result?.total, + nearestElementPath: nearestElement ? getElementSelector(nearestElement) : null, + pixelOffsets: relativeOffset, }; localStorage.setItem( `mangaProgress-${this.page}-${this.identifier}-${this.chapter}`, JSON.stringify(data), ); - logger.log(`Saved manga progress at ${this.page}-${this.identifier}-${this.chapter}`, data); } loadMangaPage() { + if (!this.isLongStrip()) return null; const saved = localStorage.getItem( `mangaProgress-${this.page}-${this.identifier}-${this.chapter}`, ); @@ -198,52 +270,85 @@ export class MangaProgress { try { const data = JSON.parse(saved); - // Do NOT scroll here; scroll will happen when user clicks button return data; } catch (e) { - console.warn('Failed to load manga progress', e); + logger.warn('Failed to load manga progress', e); return null; } } - resume(savedIndex: number) { - const scrollEl = this.getScrollableElement(); - logger.log('manga scroll element', scrollEl); - if (!scrollEl) return; + resume(saved) { + if (!saved) return; - const scrollLoop = () => { - const current = this.getProgress()?.current ?? 0; + function getElementFromPath(path: number[]): HTMLElement | null { + return path.reduce((current, index) => { + if (!current) return null; + const nextChild = current.children[index] as HTMLElement; + return nextChild || current; + }, document.body); + } + + const el = getElementFromPath(saved.nearestElementPath); + if (!el) return; + + function getScrollElement(el) { + let current = el.parentElement; - // Stop when we reach saved progress - if (current >= savedIndex) { - logger.log('Reached saved progress', current); - return; + while (current && current !== document.body) { + const style = window.getComputedStyle(current); + const hasScrollbar = current.scrollHeight > current.clientHeight; + const isScrollable = /(auto|scroll)/.test(style.overflowY + style.overflow); + + if (hasScrollbar && isScrollable) { + return current; + } + current = current.parentElement; } + return document.documentElement; + } - // Scroll continuously by a chunk - scrollEl.scrollBy({ top: 40, behavior: 'instant' }); + const performPrecisionScroll = () => { + const container = getScrollElement(el); + const rect = el.getBoundingClientRect(); - // Keep looping every frame - requestAnimationFrame(scrollLoop); - }; + const isMainOrDiv = container === document.documentElement || container === document.body; - scrollLoop(); - } + const currentScroll = isMainOrDiv + ? window.pageYOffset || document.documentElement.scrollTop + : container.scrollTop; - private getScrollableElement(): HTMLElement { - const all = Array.from(document.body.querySelectorAll('*')) as HTMLElement[]; + // If div, find container's offset from the top of the screen + const containerTop = isMainOrDiv ? 0 : container.getBoundingClientRect().top; + const elementTopInContainer = currentScroll + (rect.top - containerTop); + const absoluteTargetPoint = elementTopInContainer + rect.height * (saved.pixelOffsets || 0.5); + const finalTarget = absoluteTargetPoint - window.innerHeight / 2; - const container = all.find(el => { - const style = getComputedStyle(el); - return ( - (style.overflowY === 'auto' || style.overflowY === 'scroll') && - el.scrollHeight > el.clientHeight - ); - }); + container.scrollTo({ + top: finalTarget, + behavior: 'smooth', + }); + }; - return (container || - document.scrollingElement || - document.documentElement || - document.body) as HTMLElement; + performPrecisionScroll(); + let checks = 0; + const scrollInterval = setInterval(() => { + performPrecisionScroll(); + checks++; + + // Stop after few second or if the user starts scrolling manually + if (checks > 6) { + clearInterval(scrollInterval); + } + }, 500); + + // Stop the scroll if the user scrolls manually + const stopOnUserScroll = () => { + clearInterval(scrollInterval); + window.removeEventListener('wheel', stopOnUserScroll); + window.removeEventListener('touchmove', stopOnUserScroll); + }; + window.addEventListener('wheel', stopOnUserScroll); + window.addEventListener('touchmove', stopOnUserScroll); + logger.log('Resumed manga progress', saved); } } From 231890474940f7b3ed6802e9c9d6e35e44f0e7f3 Mon Sep 17 00:00:00 2001 From: Gickass Date: Tue, 20 Jan 2026 19:27:21 +0800 Subject: [PATCH 03/13] probably usable --- src/pages/syncPage.ts | 84 +++++++++++- src/utils/mangaProgress/MangaProgress.ts | 157 ++++++++--------------- 2 files changed, 135 insertions(+), 106 deletions(-) diff --git a/src/pages/syncPage.ts b/src/pages/syncPage.ts index aa286bc2b2..dcc65f1c21 100644 --- a/src/pages/syncPage.ts +++ b/src/pages/syncPage.ts @@ -243,7 +243,85 @@ export class SyncPage { } } - private handleMangaResume() { + private async handleMangaResume() { + // handle resume button + function resume(saved) { + if (!saved) return; + + function getElementFromPath(path: number[]): HTMLElement | null { + return path.reduce((current, index) => { + if (!current) return null; + const nextChild = current.children[index] as HTMLElement; + return nextChild || current; + }, document.body); + } + + function getScrollElement(el: HTMLElement) { + let current = el.parentElement; + + while (current && current !== document.body) { + const style = window.getComputedStyle(current); + const hasScrollbar = current.scrollHeight > current.clientHeight; + const isScrollable = /(auto|scroll)/.test(style.overflowY + style.overflow); + if (hasScrollbar && isScrollable) { + return current; + } + current = current.parentElement; + } + return document.documentElement; + } + + const el = getElementFromPath(saved.nearestElementPath); + if (!el) return; + + const performPrecisionScroll = () => { + const container = getScrollElement(el); + logger.debug('Using scroll container', container); + const rect = el.getBoundingClientRect(); + + const isMainOrDiv = container === document.documentElement || container === document.body; + + const currentScroll = isMainOrDiv + ? window.pageYOffset || document.documentElement.scrollTop + : container.scrollTop; + + // If div, find container's offset from the top of the screen + const containerTop = isMainOrDiv ? 0 : container.getBoundingClientRect().top; + const elementTopInContainer = currentScroll + (rect.top - containerTop); + const absoluteTargetPoint = + elementTopInContainer + rect.height * (saved.pixelOffsets || 0.5); + const finalTarget = absoluteTargetPoint - window.innerHeight / 2; + + container.scrollTo({ + top: finalTarget, + behavior: 'smooth', + }); + }; + + performPrecisionScroll(); + let checks = 0; + const scrollInterval = setInterval(() => { + performPrecisionScroll(); + checks++; + + // Stop after few second or if the user starts scrolling manually + if (checks > 6) { + clearInterval(scrollInterval); + } + }, 500); + + // Stop the scroll + const stopOnUserScroll = () => { + clearInterval(scrollInterval); + window.removeEventListener('wheel', stopOnUserScroll); + window.removeEventListener('touchmove', stopOnUserScroll); + }; + window.addEventListener('wheel', stopOnUserScroll); + window.addEventListener('touchmove', stopOnUserScroll); + logger.log('Resumed manga progress', saved); + } + + // start of checking if ( !this.page.sync.readerConfig || this.curState === 'undefined' || @@ -253,7 +331,7 @@ export class SyncPage { return; this.mangaProgress?.setChapter(this.page.sync.getEpisode(this.url)); this.mangaProgress?.setIdentifier(this.page.sync.getIdentifier(this.url)); - const savedProgress = this.mangaProgress?.loadMangaPage(); + const savedProgress = await this.mangaProgress?.loadMangaPage(); if (savedProgress) { logger.log('Manga Progress Found', this.mangaProgress); @@ -273,7 +351,7 @@ export class SyncPage { ); resumeMsg.find('.sync').on('click', () => { - this.mangaProgress?.resume(savedProgress); + resume(savedProgress); j.$(resumeMsg).remove(); }); diff --git a/src/utils/mangaProgress/MangaProgress.ts b/src/utils/mangaProgress/MangaProgress.ts index 6a596d87ee..3552773fe1 100644 --- a/src/utils/mangaProgress/MangaProgress.ts +++ b/src/utils/mangaProgress/MangaProgress.ts @@ -148,7 +148,8 @@ export class MangaProgress { j.$('.ms-progress').css('width', `${this.progressPercentage()! * 100}%`); j.$('#malSyncProgress').removeClass('ms-loading').removeClass('ms-done'); - if (this.page && this.identifier && this.chapter) this.saveMangaPage(); + if (this.page && this.identifier && this.chapter && (this.result?.current || 0) > 1) + this.saveMangaPage(); if (this.finished() && j.$('#malSyncProgress').length) { j.$('#malSyncProgress').addClass('ms-done'); @@ -172,28 +173,46 @@ export class MangaProgress { // Page saving/loading logic private isLongStrip(): boolean { - const threshold = window.innerHeight * 2; - const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); + const threshold = window.innerHeight * 3; + // Return true if page already has long base scroll if (docHeight > threshold) return true; - const containers = Array.from(document.querySelectorAll('div, section, article')); - const hasBigContainer = containers.some(el => el.scrollHeight > threshold); - - return hasBigContainer; + return Array.from(document.querySelectorAll('div, section, main, article')).some(el => { + const htmlEl = el as HTMLElement; + const style = window.getComputedStyle(htmlEl); + const isScrollableCSS = /(auto|scroll|overlay)/.test(style.overflowY + style.overflow); + if (isScrollableCSS) { + return htmlEl.scrollHeight > threshold; + } + return false; + }); } saveMangaPage() { if (!this.isLongStrip()) return; - const elements = Array.from(document.body.querySelectorAll('*')); + function getElementSelector(el: HTMLElement): number[] { + const path: number[] = []; + let current: HTMLElement | null = el; + + while (current && current !== document.body) { + const parent = current.parentElement; + if (!parent) break; + + const index = Array.from(parent.children).indexOf(current); + path.unshift(index); + current = parent; + } + return path; + } + const viewportCenter = window.innerHeight / 2; + const elements = Array.from(document.body.querySelectorAll('*')); const result = elements.reduce( (closest, el) => { const style = getComputedStyle(el); - - // Skip invisible elements if (style.display === 'none' || style.opacity === '0') return closest; // Filter for OR divs with background images @@ -207,7 +226,11 @@ export class MangaProgress { // Filters (Off-screen or too small elements) if (rect.bottom < 0 || rect.top > window.innerHeight) return closest; - if (rect.height < 100) return closest; + if (rect.height < 100 || rect.width < 200) return closest; + + const siblingImages = el.parentElement?.querySelectorAll('img').length || 0; + const cousinImages = el.parentElement?.parentElement?.querySelectorAll('img').length || 0; + if (!(siblingImages > 2 || cousinImages > 5)) return closest; const elementCenter = rect.top + rect.height / 2; const distance = Math.abs(elementCenter - viewportCenter); @@ -231,21 +254,6 @@ export class MangaProgress { } } - function getElementSelector(el: HTMLElement): number[] { - const path: number[] = []; - let current: HTMLElement | null = el; - - while (current && current !== document.body) { - const parent = current.parentElement; - if (!parent) break; - - const index = Array.from(parent.children).indexOf(current); - path.unshift(index); - current = parent; - } - return path; - } - const data = { current: this.result?.current, total: this.result?.total, @@ -260,8 +268,26 @@ export class MangaProgress { logger.log(`Saved manga progress at ${this.page}-${this.identifier}-${this.chapter}`, data); } - loadMangaPage() { - if (!this.isLongStrip()) return null; + async loadMangaPage() { + const isReady = await new Promise(resolve => { + let attempts = 0; + const interval = setInterval(() => { + if (this.isLongStrip()) { + clearInterval(interval); + resolve(true); + } + if (++attempts > 10) { + // Check only for 5 seconds after page load + clearInterval(interval); + resolve(false); + } + }, 500); + }); + if (!isReady) { + logger.log('Page save only support for long strip as of now'); + return null; + } + const saved = localStorage.getItem( `mangaProgress-${this.page}-${this.identifier}-${this.chapter}`, ); @@ -276,79 +302,4 @@ export class MangaProgress { return null; } } - - resume(saved) { - if (!saved) return; - - function getElementFromPath(path: number[]): HTMLElement | null { - return path.reduce((current, index) => { - if (!current) return null; - const nextChild = current.children[index] as HTMLElement; - return nextChild || current; - }, document.body); - } - - const el = getElementFromPath(saved.nearestElementPath); - if (!el) return; - - function getScrollElement(el) { - let current = el.parentElement; - - while (current && current !== document.body) { - const style = window.getComputedStyle(current); - const hasScrollbar = current.scrollHeight > current.clientHeight; - const isScrollable = /(auto|scroll)/.test(style.overflowY + style.overflow); - - if (hasScrollbar && isScrollable) { - return current; - } - current = current.parentElement; - } - return document.documentElement; - } - - const performPrecisionScroll = () => { - const container = getScrollElement(el); - const rect = el.getBoundingClientRect(); - - const isMainOrDiv = container === document.documentElement || container === document.body; - - const currentScroll = isMainOrDiv - ? window.pageYOffset || document.documentElement.scrollTop - : container.scrollTop; - - // If div, find container's offset from the top of the screen - const containerTop = isMainOrDiv ? 0 : container.getBoundingClientRect().top; - const elementTopInContainer = currentScroll + (rect.top - containerTop); - const absoluteTargetPoint = elementTopInContainer + rect.height * (saved.pixelOffsets || 0.5); - const finalTarget = absoluteTargetPoint - window.innerHeight / 2; - - container.scrollTo({ - top: finalTarget, - behavior: 'smooth', - }); - }; - - performPrecisionScroll(); - let checks = 0; - const scrollInterval = setInterval(() => { - performPrecisionScroll(); - checks++; - - // Stop after few second or if the user starts scrolling manually - if (checks > 6) { - clearInterval(scrollInterval); - } - }, 500); - - // Stop the scroll if the user scrolls manually - const stopOnUserScroll = () => { - clearInterval(scrollInterval); - window.removeEventListener('wheel', stopOnUserScroll); - window.removeEventListener('touchmove', stopOnUserScroll); - }; - window.addEventListener('wheel', stopOnUserScroll); - window.addEventListener('touchmove', stopOnUserScroll); - logger.log('Resumed manga progress', saved); - } } From 34ed795f9d4c68867635c60ea6282f17c10b7d8b Mon Sep 17 00:00:00 2001 From: Gickass Date: Tue, 20 Jan 2026 19:42:43 +0800 Subject: [PATCH 04/13] move helper to bottom --- src/pages/syncPage.ts | 121 +++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/src/pages/syncPage.ts b/src/pages/syncPage.ts index dcc65f1c21..7a2d8bdd91 100644 --- a/src/pages/syncPage.ts +++ b/src/pages/syncPage.ts @@ -244,32 +244,47 @@ export class SyncPage { } private async handleMangaResume() { - // handle resume button - function resume(saved) { - if (!saved) return; + if ( + !this.page.sync.readerConfig || + this.curState === 'undefined' || + this.curState.identifier === 'undefined' || + this.curState.episode === 'undefined' + ) + return; + this.mangaProgress?.setChapter(this.page.sync.getEpisode(this.url)); + this.mangaProgress?.setIdentifier(this.page.sync.getIdentifier(this.url)); + const savedProgress = await this.mangaProgress?.loadMangaPage(); - function getElementFromPath(path: number[]): HTMLElement | null { - return path.reduce((current, index) => { - if (!current) return null; - const nextChild = current.children[index] as HTMLElement; - return nextChild || current; - }, document.body); - } + if (savedProgress) { + logger.log('Manga Progress Found', this.mangaProgress); + const resumeMsg = utils.flashm( + ` +
+ `, + { + permanent: true, + error: false, + type: 'resume', + minimized: false, + position: 'bottom', + }, + ); - function getScrollElement(el: HTMLElement) { - let current = el.parentElement; + resumeMsg.find('.sync').on('click', () => { + resume(savedProgress); + j.$(resumeMsg).remove(); + }); - while (current && current !== document.body) { - const style = window.getComputedStyle(current); - const hasScrollbar = current.scrollHeight > current.clientHeight; - const isScrollable = /(auto|scroll)/.test(style.overflowY + style.overflow); - if (hasScrollbar && isScrollable) { - return current; - } - current = current.parentElement; - } - return document.documentElement; - } + resumeMsg.find('.resumeClose').on('click', () => { + j.$(resumeMsg).remove(); + }); + } + + // resume button handling + function resume(saved) { + if (!saved) return; const el = getElementFromPath(saved.nearestElementPath); if (!el) return; @@ -285,7 +300,7 @@ export class SyncPage { ? window.pageYOffset || document.documentElement.scrollTop : container.scrollTop; - // If div, find container's offset from the top of the screen + // If scroll in div, find container's offset from the top of the screen const containerTop = isMainOrDiv ? 0 : container.getBoundingClientRect().top; const elementTopInContainer = currentScroll + (rect.top - containerTop); const absoluteTargetPoint = @@ -310,7 +325,6 @@ export class SyncPage { } }, 500); - // Stop the scroll const stopOnUserScroll = () => { clearInterval(scrollInterval); window.removeEventListener('wheel', stopOnUserScroll); @@ -319,45 +333,30 @@ export class SyncPage { window.addEventListener('wheel', stopOnUserScroll); window.addEventListener('touchmove', stopOnUserScroll); logger.log('Resumed manga progress', saved); - } - // start of checking - if ( - !this.page.sync.readerConfig || - this.curState === 'undefined' || - this.curState.identifier === 'undefined' || - this.curState.episode === 'undefined' - ) - return; - this.mangaProgress?.setChapter(this.page.sync.getEpisode(this.url)); - this.mangaProgress?.setIdentifier(this.page.sync.getIdentifier(this.url)); - const savedProgress = await this.mangaProgress?.loadMangaPage(); - - if (savedProgress) { - logger.log('Manga Progress Found', this.mangaProgress); - const resumeMsg = utils.flashm( - ` -
- `, - { - permanent: true, - error: false, - type: 'resume', - minimized: false, - position: 'bottom', - }, - ); + // Function handling + function getElementFromPath(path: number[]): HTMLElement | null { + return path.reduce((current, index) => { + if (!current) return null; + const nextChild = current.children[index] as HTMLElement; + return nextChild || current; + }, document.body); + } - resumeMsg.find('.sync').on('click', () => { - resume(savedProgress); - j.$(resumeMsg).remove(); - }); + function getScrollElement(el: HTMLElement) { + let current = el.parentElement; - resumeMsg.find('.resumeClose').on('click', () => { - j.$(resumeMsg).remove(); - }); + while (current && current !== document.body) { + const style = window.getComputedStyle(current); + const hasScrollbar = current.scrollHeight > current.clientHeight; + const isScrollable = /(auto|scroll)/.test(style.overflowY + style.overflow); + if (hasScrollbar && isScrollable) { + return current; + } + current = current.parentElement; + } + return document.documentElement; + } } } From d0bdf2093af96665090f1bb2cadd1ccd63b1500b Mon Sep 17 00:00:00 2001 From: Gickass Date: Tue, 20 Jan 2026 23:24:22 +0800 Subject: [PATCH 05/13] polish a bit --- src/pages/syncPage.ts | 75 ++++++++++++++++-------- src/utils/mangaProgress/MangaProgress.ts | 1 + 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/pages/syncPage.ts b/src/pages/syncPage.ts index 7a2d8bdd91..5a404a268f 100644 --- a/src/pages/syncPage.ts +++ b/src/pages/syncPage.ts @@ -257,6 +257,9 @@ export class SyncPage { if (savedProgress) { logger.log('Manga Progress Found', this.mangaProgress); + + const autoCloseUI = getElementFromPath(savedProgress.nearestElementPath); + const resumeMsg = utils.flashm( `