From 382e966d7aca142e4bb670866a705eaf740c0783 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 19 Mar 2025 00:15:13 -0500 Subject: [PATCH 01/13] force text wrapping, fix latex parsing --- .../file-builders/EPubFileBuilder.js | 10 ++++--- .../file-builders/HTMLFileBuilder.js | 30 ++++++++++++------- .../file-builders/LatexFileBuilder.js | 30 ++++++++++++------- .../file-templates/html/style.css | 9 +++++- .../file-templates/styles/root.css | 15 +++++----- .../views/ViewAndDownload/DownloadOptions.js | 2 +- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js index f3222b3e7..bb9839b87 100644 --- a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js @@ -117,7 +117,7 @@ class EPubFileBuilder { this.buildTocNCX(chapters); } convertChapter(chapter) { - const text = HTMLFileBuilder.convertChapter(chapter); + const text = HTMLFileBuilder.convertChapter(chapter, this.data.includeRawLatex); let content = dedent(`
${text} @@ -151,6 +151,11 @@ class EPubFileBuilder { // OEBPS/prism.css zip.addFile('OEBPS/prism.css', Buffer.from(PRISM_CSS)); + // OEBPS/chapter-id.xhtml + // Note: convertEPub populates the chapter ids, so it has to be done first + this.convertEPub(); + this.convertTableOfContents(); + // OEBPS/content.opf const contentOPF = this.getContentOPF( title, @@ -161,9 +166,6 @@ class EPubFileBuilder { chapters, ); zip.addFile('OEBPS/content.opf', Buffer.from(contentOPF)); - // OEBPS/chapter-id.xhtml - this.convertEPub(); - this.convertTableOfContents(); return zip.toBuffer(); } diff --git a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js index a1be46a38..852b1469c 100644 --- a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js @@ -42,12 +42,16 @@ class HTMLFileBuilder { return html_buffer; } - static convertText(content) { - return [ + static convertText(content, includeRawLatex) { + if (includeRawLatex) { + content = content.replace(/\$\$(.*?)\$\$/g, `$$$$$1$$$$ \`($1)\``); + } + let text = [ '

', html.markdown(content), '

' ].join("") + return text }; static convertImage(content) { if (content.descriptions.length !== 0) { @@ -66,18 +70,20 @@ class HTMLFileBuilder { ].join('\n'); } - static convertContent(content) { + static convertContent(content, includeRawLatex) { if (typeof content === 'string') { - return HTMLFileBuilder.convertText(content) + return HTMLFileBuilder.convertText(content, includeRawLatex); } - return HTMLFileBuilder.convertImage(content) + return HTMLFileBuilder.convertImage(content); } - static convertChapter(chapter) { + static convertChapter(chapter, includeRawLatex = false) { chapter.id = _buildID(); return [ `\n

${chapter.title}

`, - _.map(chapter.contents, (c) => HTMLFileBuilder.convertContent(c)).join("\n"), + `
`, + _.map(chapter.contents, (c) => HTMLFileBuilder.convertContent(c, includeRawLatex)).join("\n"), + `
` ].join("\n\n"); } @@ -85,7 +91,7 @@ class HTMLFileBuilder { const chapters = this.data.chapters return [ '
', - _.map(chapters, (ch) => HTMLFileBuilder.convertChapter(ch)).join("\n"), + _.map(chapters, (ch) => HTMLFileBuilder.convertChapter(ch, this.data.includeRawLatex)).join("\n"), '
', ].join("\n"); } @@ -98,7 +104,7 @@ class HTMLFileBuilder { '' ].join("\n"); @@ -138,7 +144,7 @@ class HTMLFileBuilder { toc = this.convertTOC(); } return INDEX_HTML_LOCAL({ - title: this.title, + title: this.data.title, navContents: toc, content: conversion, author: this.data.author, @@ -147,10 +153,12 @@ class HTMLFileBuilder { visualTOC: this.data.visualTOC }) + this.convertGlossary(); - // TODO: test glossary, add table of contents } async getHTMLBuffer() { + // eslint-disable-next-line no-console + console.log(this.data); + const zip = this.zip; // styles diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index e8c14d59a..b11efdcda 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -35,8 +35,8 @@ class LatexFileBuilder { if (_.isEmpty(glossary)) { return "" } - // console.log("glossary", glossary); - return _.map(glossary, (value, key) => { + let text = `\\section{Glossary}`; + return text + _.map(glossary, (value, key) => { return `\\textbf{${key}}: ${value.description}` }).join("\n\n"); } @@ -52,7 +52,7 @@ class LatexFileBuilder { ${author} } \\vfill - \\includegraphics[width=4cm] + \\includegraphics[width=8cm] {${this.saveImage(cover)}} \\vfill \\vfill @@ -149,7 +149,7 @@ class LatexFileBuilder { latex = latex.replace(/(.*?)<\/u>/gs, '\\underline{$1}'); // Convert block quote - latex = latex.replace(/
(.*?)<\/blockquote>/gs, '\\\\$1\\\\'); + latex = latex.replace(/
(.*?)<\/blockquote>/gs, '\\begin{quote}$1\\end{quote}'); // Convert code block latex = latex.replace(/(.*?)<\/code>/gs, '\\begin{verbatim}$1\\end{verbatim}'); @@ -158,8 +158,16 @@ class LatexFileBuilder { latex = latex.replace(/

(.*?)<\/p>/gs, '\n$1\n'); // Convert headings (h1, h2, h3...) - latex = latex.replace(/(.*?)<\/h\1>/g, (match, level, content) => { - return `\\${'section'.repeat(level)}{${content}}`; + const level_maps = { + 1: `section`, + 2: `subsection`, + 3: `subsubsection`, + 4: `paragraph`, + 5: `subparagraph`, + 6: `subparagraph` + } + latex = latex.replace(/(.*?)<\/h\1>/gs, (match, level, content) => { + return `\\${level_maps[level]}{${content}}`; }); // Convert links @@ -179,13 +187,13 @@ class LatexFileBuilder { latex = latex.replace(/(.*?)?<\/latex>/gs, '$$$1$$') // Convert newline - latex = latex.replace(/
/, "\\newline") + latex = latex.replace(/
/gs, "\\newline") return latex; } static substituteSpecialChar(str) { - str = str.replace(/\\/g, '\\textbackslash'); + str = str.replace(/\\/g, '\\textbackslash '); str = str.replace(/\$/g, '\\$'); str = str.replace(/\{/g, '\\{'); str = str.replace(/\}/g, '\\}'); @@ -199,15 +207,15 @@ class LatexFileBuilder { } static substituteSpecialChars(htmlString) { // Parse the HTML string into a DOM structure - const excludeTags = ['code', 'math'] + const excludeTags = ['code', 'latex'] const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); // Recursive function to traverse and apply text substitution to text nodes function traverseAndReplace(node) { // If the node is a text node, apply the substitution - if (node.nodeType === Node.TEXT_NODE && !excludeTags.includes(node.tagName)) { - LatexFileBuilder.substituteSpecialChar(node.textContent); + if (node.nodeType === Node.TEXT_NODE) { + node.textContent = LatexFileBuilder.substituteSpecialChar(node.textContent); } // If the node is an element, and it's not in the exclude list, traverse its children diff --git a/src/screens/EPub/controllers/file-builders/file-templates/html/style.css b/src/screens/EPub/controllers/file-builders/file-templates/html/style.css index 7291e7ee1..42e2f4fb7 100644 --- a/src/screens/EPub/controllers/file-builders/file-templates/html/style.css +++ b/src/screens/EPub/controllers/file-builders/file-templates/html/style.css @@ -34,6 +34,12 @@ html { text-align: center; } +.wrap-text { + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; +} + #toc_container li, #toc_container ol, #toc_container ol li { @@ -56,8 +62,9 @@ html { } #epub_cover { - display: none; + /* display: none; */ justify-content: center; + text-align: center; } @media print { diff --git a/src/screens/EPub/controllers/file-builders/file-templates/styles/root.css b/src/screens/EPub/controllers/file-builders/file-templates/styles/root.css index 4a95af020..7778785bb 100644 --- a/src/screens/EPub/controllers/file-builders/file-templates/styles/root.css +++ b/src/screens/EPub/controllers/file-builders/file-templates/styles/root.css @@ -20,7 +20,7 @@ #root img, #root audio, -#root video{ +#root video { margin: 15px 0; max-width: 100%; height: auto; @@ -55,14 +55,14 @@ width: 100%; max-width: 100%; overflow: auto; + table-layout: fixed; } -#root table, -#root th, +#root th, #root td { - padding: 10px 20px; - border: 1px solid rgb(209, 209, 209); - white-space: nowrap; + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; } #root thead { @@ -84,5 +84,4 @@ #root .img-block img { margin-bottom: 5px; -} - +} \ No newline at end of file diff --git a/src/screens/EPub/views/ViewAndDownload/DownloadOptions.js b/src/screens/EPub/views/ViewAndDownload/DownloadOptions.js index 66455f648..4fbc68804 100644 --- a/src/screens/EPub/views/ViewAndDownload/DownloadOptions.js +++ b/src/screens/EPub/views/ViewAndDownload/DownloadOptions.js @@ -37,7 +37,7 @@ function DownloadOptions(props) { } - description="Save latex with all the screenshots of the I-Note data" + description="Save as .tex file with bundled images" onClick={() => epub.download.downloadLatex(downloadOptions)} > {filename} - latex.zip From 3e53c6576bccc2413d3e06f6072e030a0d9b6084 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Sat, 22 Mar 2025 20:23:35 -0500 Subject: [PATCH 02/13] add some latex cleaning to prevent escape, and remove random delay --- .../file-builders/LatexFileBuilder.js | 36 +- src/screens/EPub/model.js | 454 +++++++++--------- 2 files changed, 257 insertions(+), 233 deletions(-) diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index b11efdcda..437b50f41 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -205,6 +205,18 @@ class LatexFileBuilder { str = str.replace(/~/g, '\\~'); return str } + static removeVerbatimEscape(str) { + const regex = /\\end\{verbatim\}/g + while (regex.test(str)) { + str = str.replace(regex, ''); + } + return str; + } + static removeUnescapedDollar(str) { + // catches all $ with an odd number of slashes in front + const regex = /((? item?.image) }; - }, - setCurrChIndex(state, { payload }) { - return { ...state, currChIndex: payload }; - }, - setFoldedIds(state, { payload }) { - return { ...state, foldedIds: payload }; - }, - foldChapter(state, { payload: { folded, id } }) { - return { - ...state, - foldedIds: folded ? [...state.foldedIds, id] : _.filter(state.foldedIds, (fid) => fid !== id) - } - }, - setSaved(state, { payload }) { - return { ...state, saved: payload }; - }, + setError(state, { payload }) { + return { ...state, error: payload }; + }, + setView(state, { payload }) { + return { + ...state, + view: payload, + showNav: true, + showPreview: false, + // navId: null, + currChIndex: payload === Constants.EpbReadOnly ? 0 : state.currChIndex + }; + }, + setMedia(state, { payload }) { + return { ...state, media: payload }; + }, + setEPub(state, { payload }) { + const items = getAllItemsInChapters(payload.chapters); + if (!payload.chapters) { + payload.chapters = [] + } + return { ...state, epub: payload, items, images: _.map(items, item => item?.image) }; + }, + setCurrChIndex(state, { payload }) { + return { ...state, currChIndex: payload }; + }, + setFoldedIds(state, { payload }) { + return { ...state, foldedIds: payload }; + }, + foldChapter(state, { payload: { folded, id } }) { + return { + ...state, + foldedIds: folded ? [...state.foldedIds, id] : _.filter(state.foldedIds, (fid) => fid !== id) + } + }, + setSaved(state, { payload }) { + return { ...state, saved: payload }; + }, - setNavId(state, { payload }) { - return { ...state, navId: payload }; - }, - setShowNav(state, { payload }) { - return { ...state, showNav: payload }; - }, - toggleNav(state) { - return { ...state, showNav: !state.showNav } - }, - setImgPickerData(state, { payload }) { - return { ...state, imgPickerData: payload }; - }, - setPlayerData(state, { payload }) { - return { ...state, playerData: payload }; - }, - setShowPreview(state, { payload }) { - return { ...state, showPreview: payload }; - }, - togglePreview(state) { - return { ...state, showPreview: !state.showPreview } - }, - setShowFileSettings(state, { payload }) { - return { ...state, showFileSettings: payload }; - }, - setShowPrefSettings(state, { payload }) { - return { ...state, showPrefSettings: payload }; - }, - setShowShortcuts(state, { payload }) { - return { ...state, showShortcuts: payload }; - }, - toggleShortcuts(state) { - return { ...state, showShortcuts: !state.showShortcuts } - }, - resetStates() { - return { ...initState }; - }, - ...model_data_reducer + setNavId(state, { payload }) { + return { ...state, navId: payload }; + }, + setShowNav(state, { payload }) { + return { ...state, showNav: payload }; + }, + toggleNav(state) { + return { ...state, showNav: !state.showNav } + }, + setImgPickerData(state, { payload }) { + return { ...state, imgPickerData: payload }; + }, + setPlayerData(state, { payload }) { + return { ...state, playerData: payload }; + }, + setShowPreview(state, { payload }) { + return { ...state, showPreview: payload }; + }, + togglePreview(state) { + return { ...state, showPreview: !state.showPreview } + }, + setShowFileSettings(state, { payload }) { + return { ...state, showFileSettings: payload }; }, - effects: { - *setupEPub({ payload: ePubId }, { call, put }) { - /* - if (this.ePubId === ePubId) { - epubState.resetStates(); - return; - } - */ - let _epub = yield call(getEPubById, ePubId); - const { view, h } = uurl.useHash(); - if (Constants.EPubViews.includes(view)) { - yield put({ type: 'setView', payload: view }); - } + setShowPrefSettings(state, { payload }) { + return { ...state, showPrefSettings: payload }; + }, + setShowShortcuts(state, { payload }) { + return { ...state, showShortcuts: payload }; + }, + toggleShortcuts(state) { + return { ...state, showShortcuts: !state.showShortcuts } + }, + resetStates() { + return { ...initState }; + }, + ...model_data_reducer + }, + effects: { + *setupEPub({ payload: ePubId }, { call, put }) { + /* + if (this.ePubId === ePubId) { + epubState.resetStates(); + return; + } + */ + let _epub = yield call(getEPubById, ePubId); + const { view, h } = uurl.useHash(); + if (Constants.EPubViews.includes(view)) { + yield put({ type: 'setView', payload: view }); + } - if (h) { - elem.scrollIntoView(h); - } + if (h) { + elem.scrollIntoView(h); + } - api.contentLoaded(100); + api.contentLoaded(100); - if (ErrorTypes.isError(_epub)) { - prompt.error('Failed to load I-Note data.', 5000); - return; - } - yield put({ type: 'setEPub', payload: _epub }); + if (ErrorTypes.isError(_epub)) { + prompt.error('Failed to load I-Note data.', 5000); + return; + } + yield put({ type: 'setEPub', payload: _epub }); - links.title(_epub.title); + links.title(_epub.title); - if (_epub.sourceType === SourceTypes.Media) { - const media = yield call(getMediaById, _epub.sourceId); - yield put({ type: 'setMedia', payload: media }); - } - }, - *openPlayer({ payload: { title, start, end } }, { put, select }) { - const { epub } = yield select(); - if (!epub.media) return; - yield put({ - type: 'setPlayerData', payload: { - title, - begin: timestr.toSeconds(start), - end: timestr.toSeconds(end) - } - }); - }, - *duplicateEPub({ payload: { newData, copyChapterStructure } }, { call, select }) { - prompt.addOne({ text: 'Copying I-Note data...', timeout: 4000 }); - const { epub } = yield select(); - const oldData = epub.epub; - const newLanguage = newData.language; - const isDifferentLanguage = newLanguage !== epub.epub.language; - if (!newData.chapters) { - newData.chapters = epub.epub.chapters; - } + if (_epub.sourceType === SourceTypes.Media) { + const media = yield call(getMediaById, _epub.sourceId); + yield put({ type: 'setMedia', payload: media }); + } + }, + *openPlayer({ payload: { title, start, end } }, { put, select }) { + const { epub } = yield select(); + if (!epub.media) return; + yield put({ + type: 'setPlayerData', payload: { + title, + begin: timestr.toSeconds(start), + end: timestr.toSeconds(end) + } + }); + }, + *duplicateEPub({ payload: { newData, copyChapterStructure } }, { call, select }) { + prompt.addOne({ text: 'Copying I-Note data...', timeout: 4000 }); + const { epub } = yield select(); + const oldData = epub.epub; + const newLanguage = newData.language; + const isDifferentLanguage = newLanguage !== epub.epub.language; + if (!newData.chapters) { + newData.chapters = epub.epub.chapters; + } - if (isDifferentLanguage) { - const rawEPubData = yield EPubListCtrl.getRawEPubData( - oldData.sourceType, oldData.sourceId, newLanguage - ); + if (isDifferentLanguage) { + const rawEPubData = yield EPubListCtrl.getRawEPubData( + oldData.sourceType, oldData.sourceId, newLanguage + ); - newData = EPubData.create(rawEPubData, newData, copyChapterStructure).toObject(); - } + newData = EPubData.create(rawEPubData, newData, copyChapterStructure).toObject(); + } - delete newData.id; + delete newData.id; - const newEPubData = yield call(EPubListCtrl.postEPubData, newData); - if (!newEPubData) { - prompt.error('Failed to create the I-Note.'); - return; - } + const newEPubData = yield call(EPubListCtrl.postEPubData, newData); + if (!newEPubData) { + prompt.error('Failed to create the I-Note.'); + return; + } - uurl.openNewTab(links.epub(newEPubData.id, Constants.EditINote)); - }, - *deleteEPub({ payload: ePubId }, { call }) { - try { - yield call(api.deleteEPub, ePubId); - window.close(); - } catch (error) { - console.error(error); - prompt.error('Failed to delete the I-Note.'); - } - }, - *updateEPubBasicInfo({ payload }, { put }) { - yield put.resolve({ type: 'setEPub', payload }) - yield put({ type: 'updateEPub', payload: 0 }) - }, - // Debounce - updateEPub: [ - // eslint-disable-next-line func-names - function* ({ payload: timeout = 3000 }, { put }) { - yield delay(timeout); - yield put({ type: 'updateEPub_Internal' }) - }, - { type: "takeLatest" } - ], - *updateEPub_Internal(action, { call, put, select }) { - yield put.resolve({ type: 'setSaved', payload: (Constants.EpbSaving) }); - const { epub } = yield select(); - try { - yield call(api.updateEPub, epub.epub); - yield put({ type: 'setSaved', payload: (Constants.EpbSaved) }); - /* - if (this.__notifyOnce) { - this.__notifyOnce = false; - prompt.addOne({ status: 'success', text: 'Saved!', timeout: 4000 }); - } - */ // NOT IMPLEMENTED - } catch (error) { - prompt.error('Failed to update I-Note'); - yield put({ type: 'setSaved', payload: (Constants.EpbSaveFailed) }); - } - }, - *updateEpubData({ payload: { action, payload } }, { put }) { - yield put.resolve({ type: action, payload }) - yield put({ type: 'updateEPub' }) - }, - // eslint-disable-next-line no-unused-vars - *splitChaptersByScreenshots({ payload }, { put }) { - prompt.addOne({ - text: 'Split chapters by screenshots.', - position: 'left bottom', - timeout: 2000, - }); - yield put({ type: 'updateEPub' }) - }, - *resetToDefaultChapters(_unused, { put }) { - // this.updateAll('Reset to the default chapters', 0); - prompt.addOne({ - text: 'Reset to the default chapters.', - position: 'left bottom', - timeout: 2000, - }); - yield put({ type: 'updateEPub' }) - }, - ...model_nav_effects + uurl.openNewTab(links.epub(newEPubData.id, Constants.EditINote)); + }, + *deleteEPub({ payload: ePubId }, { call }) { + try { + yield call(api.deleteEPub, ePubId); + window.close(); + } catch (error) { + console.error(error); + prompt.error('Failed to delete the I-Note.'); + } + }, + *updateEPubBasicInfo({ payload }, { put }) { + yield put.resolve({ type: 'setEPub', payload }) + yield put({ type: 'updateEPub', payload: 0 }) + }, + // Debounce + updateEPub: [ + // eslint-disable-next-line func-names + function* (payload, { put }) { + // yield delay(timeout); + yield put({ type: 'updateEPub_Internal' }) + }, + { type: "takeLatest" } + ], + *updateEPub_Internal(action, { call, put, select }) { + yield put.resolve({ type: 'setSaved', payload: (Constants.EpbSaving) }); + const { epub } = yield select(); + try { + yield call(api.updateEPub, epub.epub); + yield put({ type: 'setSaved', payload: (Constants.EpbSaved) }); + /* + if (this.__notifyOnce) { + this.__notifyOnce = false; + prompt.addOne({ status: 'success', text: 'Saved!', timeout: 4000 }); + } + */ // NOT IMPLEMENTED + } catch (error) { + prompt.error('Failed to update I-Note'); + yield put({ type: 'setSaved', payload: (Constants.EpbSaveFailed) }); + } + }, + *updateEpubData({ payload: { action, payload } }, { put }) { + yield put.resolve({ type: action, payload }) + yield put({ type: 'updateEPub' }) + }, + // eslint-disable-next-line no-unused-vars + *splitChaptersByScreenshots({ payload }, { put }) { + prompt.addOne({ + text: 'Split chapters by screenshots.', + position: 'left bottom', + timeout: 2000, + }); + yield put({ type: 'updateEPub' }) + }, + *resetToDefaultChapters(_unused, { put }) { + // this.updateAll('Reset to the default chapters', 0); + prompt.addOne({ + text: 'Reset to the default chapters.', + position: 'left bottom', + timeout: 2000, + }); + yield put({ type: 'updateEPub' }) }, - subscriptions: { - setup({ dispatch, history }) { - history.listen((event) => { - const pathname = event.pathname ? event.pathname : event.location?.pathname - const match = pathToRegexp('/epub/:id/:option?').exec(pathname); - if (match) { - dispatch({ type: 'setupEPub', payload: match[1] }); - } - }) + ...model_nav_effects + }, + subscriptions: { + setup({ dispatch, history }) { + history.listen((event) => { + const pathname = event.pathname ? event.pathname : event.location?.pathname + const match = pathToRegexp('/epub/:id/:option?').exec(pathname); + if (match) { + dispatch({ type: 'setupEPub', payload: match[1] }); } + }) } + } } export default EPubModel \ No newline at end of file From 34b529a05075345bcb4ab5aabc0ab14f70e6c867 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Sat, 22 Mar 2025 21:25:29 -0500 Subject: [PATCH 03/13] add visualTOC support for latex --- .../controllers/file-builders/EPubParser.js | 60 ++++++++++--------- .../file-builders/LatexFileBuilder.js | 42 +++++++++---- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/screens/EPub/controllers/file-builders/EPubParser.js b/src/screens/EPub/controllers/file-builders/EPubParser.js index 9603f3a39..c6f6fa986 100644 --- a/src/screens/EPub/controllers/file-builders/EPubParser.js +++ b/src/screens/EPub/controllers/file-builders/EPubParser.js @@ -18,6 +18,7 @@ class EPubParser { * @param {EPubData} ePubData */ async init(epubData, options) { + this.img_id = 1 this.options = options this.data = JSON.parse(JSON.stringify(epubData)); this.data.chapters = await this.parseChapters(epubData.chapters); @@ -70,6 +71,36 @@ class EPubParser { return content; } + async parseImage(content) { + let new_content = JSON.parse(JSON.stringify(content)); + let img_buffer = await EPubParser.loadImageBuffer(content.src); + let img_blob = new Blob([img_buffer]); + + if (this.options.invertColors) { + img_blob = await EPubParser.invertImageIfDim(img_blob); + const arr_buf = await img_blob.arrayBuffer(); + img_buffer = new Uint8Array(arr_buf); + } + + if (this.options.replaceImageSrc) { + new_content.src = await EPubParser.blobToDataUrl(img_blob); + } else { + new_content.blob = img_blob; + new_content.buffer = img_buffer; + } + + + if (new_content.src !== "") { + const { height, width } = await EPubParser.getImageDimensions(img_blob); + new_content.height = height; + new_content.width = width; + } + new_content.descriptions = _.filter(content.descriptions, (desc) => desc.trim() !== ""); + new_content.id = this.img_id; + this.img_id += 1; + return new_content; + } + async parseText(text) { if (!this.options.replaceLatex) { return text; @@ -120,35 +151,6 @@ class EPubParser { } } - - async parseImage(content) { - let new_content = JSON.parse(JSON.stringify(content)); - let img_buffer = await EPubParser.loadImageBuffer(content.src); - let img_blob = new Blob([img_buffer]); - - if (this.options.invertColors) { - img_blob = await EPubParser.invertImageIfDim(img_blob); - const arr_buf = await img_blob.arrayBuffer(); - img_buffer = new Uint8Array(arr_buf); - } - - if (this.options.replaceImageSrc) { - new_content.src = await EPubParser.blobToDataUrl(img_blob); - } else { - new_content.blob = img_blob; - new_content.buffer = img_buffer; - } - - - if (new_content.src !== "") { - const { height, width } = await EPubParser.getImageDimensions(img_blob); - new_content.height = height; - new_content.width = width; - } - new_content.descriptions = _.filter(content.descriptions, (desc) => desc.trim() !== ""); - return new_content; - } - /** * Create an EPubParser * @param {EPubData} ePubData diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index 437b50f41..e4ca96588 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -10,13 +10,14 @@ class LatexFileBuilder { * @param {Boolean} forPreview */ constructor() { - this.image_idx = 0 this.zip = new AdmZip(); } async init(parsedData) { this.data = parsedData; this.glossary = parsedData.glossary + // eslint-disable-next-line no-console + console.log(this.data); } /** @@ -60,13 +61,8 @@ class LatexFileBuilder { ` } - getImageId() { - this.image_idx += 1 - return this.image_idx - } - saveImage(content) { - const img_path = `images/${this.getImageId()}.jpeg` + const img_path = `images/${content.id}.jpeg` this.zip.addFile(img_path, content.buffer); return img_path } @@ -82,7 +78,7 @@ class LatexFileBuilder { return [ `\\begin{figure}`, `\\centering`, - `\\includegraphics[alt={${content.alt}}, width=.5\\textwidth]{${img_path}}`, + `\\includegraphics[alt={${content.alt}}, width=.8\\textwidth]{${img_path}}`, captions, `\\end{figure}` ].join("\n") @@ -91,10 +87,32 @@ class LatexFileBuilder { convertChapter(chapter) { return [ `\\section{${chapter.title}}`, + `\\label{sec:${chapter.title}}`, _.map(chapter.contents, (c) => this.convertContent(c)).join("\n") ].join("\n"); } + + convertVisualTOC(visualTOC) { + let all_imgs = _.flatMap(visualTOC, (imgs, chapter) => imgs.map(img => ({ ...img, chapter }))) + return ` + \\section{Contents} + \\begin{center} + ${_.chunk(all_imgs, 2).map((pair) => { + return _.map(pair, (img) => { + const split_alt = img.alt.replace(/(.{10})/g, `$1\\hspace{0pt}`); + return ` + \\begin{minipage}{0.45\\textwidth} + \\centering + \\includegraphics[width =\\linewidth]{images/${img.id}.jpeg} + \\hyperref[sec:${this.data.chapters[img.chapter].title}]{${split_alt}} + \\end{minipage} + `}).join("\\hfill"); + }).join("\\vspace{1em}")} + \\end{center} + `; + } getMainText() { + const TOC = this.data.visualTOC ? this.convertVisualTOC(this.data.visualTOC) : `\\tableofcontents`; return [ "\\documentclass{article}", "\\usepackage{caption}", @@ -103,7 +121,7 @@ class LatexFileBuilder { "\\usepackage[T1]{fontenc}", "\\begin{document}", this.getTitlePage(this.data.title, this.data.author, this.data.cover), - "\\tableofcontents", + TOC, _.map(this.data.chapters, (ch) => this.convertChapter(ch)).join("\n"), this.convertGlossary(this.glossary), "\\end{document}" @@ -167,7 +185,7 @@ class LatexFileBuilder { 6: `subparagraph` } latex = latex.replace(/(.*?)<\/h\1>/gs, (match, level, content) => { - return `\\${level_maps[level]}{${content}}`; + return `\\${level_maps[level]} {${content} } `; }); // Convert links @@ -175,12 +193,12 @@ class LatexFileBuilder { // Convert ordered lists latex = latex.replace(/

    (.*?)<\/ol>/gs, (match, content) => { - return `\\begin{enumerate}\n${content.replace(/
  1. (.*?)<\/li>/gs, '\\item $1')}\n\\end{enumerate}`; + return `\\begin{ enumerate } \n${content.replace(/
  2. (.*?)<\/li>/gs, '\\item $1')} \n\\end{ enumerate } `; }); // Convert unordered lists latex = latex.replace(/
      (.*?)<\/ul>/gs, (match, content) => { - return `\\begin{itemize}\n${content.replace(/
    • (.*?)<\/li>/gs, '\\item $1')}\n\\end{itemize}`; + return `\\begin{ itemize } \n${content.replace(/
    • (.*?)<\/li>/gs, '\\item $1')} \n\\end{ itemize } `; }); // Convert custom math tag From db9526e7f2c6693487c5aac94c3e15a9d51f8325 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Sat, 22 Mar 2025 21:44:28 -0500 Subject: [PATCH 04/13] delete unused preview code, change shortcuts to use shift key --- .../EPub/components/EPubHeader/EPubToolbar.js | 6 -- .../EPub/components/PlayerModal/index.js | 2 +- .../EPub/components/PreviewModal/index.js | 60 ------------------- .../EPub/components/PreviewModal/index.scss | 16 ----- src/screens/EPub/components/index.js | 1 - .../EPub/controllers/constants/shortcuts.js | 13 ++-- src/screens/EPub/index.js | 21 +------ src/screens/EPub/model.js | 9 --- 8 files changed, 8 insertions(+), 120 deletions(-) delete mode 100644 src/screens/EPub/components/PreviewModal/index.js delete mode 100644 src/screens/EPub/components/PreviewModal/index.scss diff --git a/src/screens/EPub/components/EPubHeader/EPubToolbar.js b/src/screens/EPub/components/EPubHeader/EPubToolbar.js index 98bbe2810..cbcccc92f 100644 --- a/src/screens/EPub/components/EPubHeader/EPubToolbar.js +++ b/src/screens/EPub/components/EPubHeader/EPubToolbar.js @@ -32,11 +32,6 @@ function EPubToolbar({ view, dispatch, epub }) { // const saveEPub = () => dispatch({ type: 'epub/updateEPub_Internal' }) // const saveBtnEl = _makeTBtn('cloud_upload', 'Save', '⌘S', saveEPub, false, true); - const openPreview = () => dispatch({ type: 'epub/setShowPreview', payload: true }); - const previewBtnEl = _makeTBtn( - 'preview', 'Preview I-Note', '⌘⇧P', openPreview, false, !isReadOnly - ); - const prefBtnEl = null// _makeTBtn('tune', 'Preference', null, null, false, true); const openShortcuts = () => dispatch({ type: 'epub/setShowShortcuts', payload: true }); @@ -61,7 +56,6 @@ function EPubToolbar({ view, dispatch, epub }) { return ( - {null && previewBtnEl} {/* The preview button causes a crash when clicked (cause unknown) */} {settingsBtn} {null && undoBtnEl} diff --git a/src/screens/EPub/components/PlayerModal/index.js b/src/screens/EPub/components/PlayerModal/index.js index b9de665df..2a6dd4e64 100644 --- a/src/screens/EPub/components/PlayerModal/index.js +++ b/src/screens/EPub/components/PlayerModal/index.js @@ -15,7 +15,7 @@ function PlayerModal({ dispatch }) { const isOpen = Boolean(playerData) && media; - if(!isOpen) { + if (!isOpen) { return null; } const { title, begin, end } = playerData; diff --git a/src/screens/EPub/components/PreviewModal/index.js b/src/screens/EPub/components/PreviewModal/index.js deleted file mode 100644 index e0293d49e..000000000 --- a/src/screens/EPub/components/PreviewModal/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { CTModal, CTFragment, CTSelect } from 'layout'; -import { connect } from 'dva' -import { buildHTMLFromChapter } from 'entities/EPubs/html-converters' -import { MDPreviewer } from '../Markdown'; -import './index.scss' - -function PreviewModal({ - showPreview, - currChIndex, - chapters = [], - dispatch -}) { - const onClose = () => dispatch({ type: 'epub/setShowPreview', payload: false }); - - const [previewChIdx, setPreviewChIdx] = useState(currChIndex); - const chapter = chapters[previewChIdx] || {}; - - useEffect(() => { - if (showPreview) { - setPreviewChIdx(currChIndex); - } - }, [showPreview]); - - const handleSelect = ({ target: { value } }) => setPreviewChIdx(value); - const chapterOptions = chapters.map( - (ch, idx) => ({ value: idx, text: `Chapter ${idx + 1}: ${ch.title}` }) - ); - const chapterSelector = ( - - - - ) - - return ( - - - - - - ) -} - -export default connect(({ epub: { showPreview, currChIndex, epub: { chapters } } }) => ({ - showPreview, currChIndex, chapters -}))(PreviewModal); diff --git a/src/screens/EPub/components/PreviewModal/index.scss b/src/screens/EPub/components/PreviewModal/index.scss deleted file mode 100644 index 484275747..000000000 --- a/src/screens/EPub/components/PreviewModal/index.scss +++ /dev/null @@ -1,16 +0,0 @@ -#epb-preview-ch-sel { - padding-top: 0 !important; - padding-bottom: 0 !important; - padding-left: 5px !important; - border: none !important; - - .MuiListItemText-root { - .MuiTypography-body1 { - font-weight: bold !important; - font-size: 16px !important; - color: rgb(71, 71, 71); - overflow: hidden; - text-overflow: ellipsis; - } - } -} \ No newline at end of file diff --git a/src/screens/EPub/components/index.js b/src/screens/EPub/components/index.js index bc7baada7..95b79aec3 100644 --- a/src/screens/EPub/components/index.js +++ b/src/screens/EPub/components/index.js @@ -12,6 +12,5 @@ export { default as ImagePickerModal } from './ImagePickerModal'; export { default as MDEditorModal } from './MDEditorModal'; export { default as PlayerModal } from './PlayerModal'; export { default as ShortcutModal } from './ShortcutModal'; -export { default as PreviewModal } from './PreviewModal'; export { default as EPubFileInfoModal } from './EPubFileInfoModal'; export { default as EPubCopyModal } from './EPubCopyModal'; \ No newline at end of file diff --git a/src/screens/EPub/controllers/constants/shortcuts.js b/src/screens/EPub/controllers/constants/shortcuts.js index 7457c10f9..6d41e5b76 100644 --- a/src/screens/EPub/controllers/constants/shortcuts.js +++ b/src/screens/EPub/controllers/constants/shortcuts.js @@ -4,22 +4,19 @@ export const shortcuts = [ actions: [ { name: 'Save Changes', - keys: [['⌘', 's']] - }, { - name: 'Open I-Note Preview', - keys: [['⌘', 'Shift⇧', 's']] + keys: [['Shift⇧', 's']] }, { name: 'Open Keyboard Shortcuts', - keys: [['⌘', '/']] + keys: [['Shift⇧', '/']] }, { name: 'Toggle Chapter Navigation Menu', - keys: [['⌘', 'b']] + keys: [['Shift⇧', 'b']] }, { name: 'View or Download I-Note', - keys: [['⌘', '1']] + keys: [['Shift⇧', '1']] }, { name: 'Edit I-Note', - keys: [['⌘', '2']] + keys: [['Shift⇧', '2']] } ] } diff --git a/src/screens/EPub/index.js b/src/screens/EPub/index.js index 78fdf47a6..1397db584 100644 --- a/src/screens/EPub/index.js +++ b/src/screens/EPub/index.js @@ -8,7 +8,6 @@ import Constants from './controllers/constants/EPubConstants'; import { EPubHeader, PlayerModal, - PreviewModal, ShortcutModal, EPubFileInfoModal, ImagePickerModal @@ -32,7 +31,6 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { const readOnlyView = altEl(ViewAndDownload, view === epubController.const.EpbReadOnly); const editINoteView = altEl(EditINote, view === epubController.const.EditINote); - const previewModal = makeEl(PreviewModal); const shortcutModal = makeEl(ShortcutModal); const fileSettingsModal = makeEl(EPubFileInfoModal); useEffect(() => { @@ -56,12 +54,11 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { // eslint-disable-next-line complexity const onKeyDown = (e) => { - const { keyCode, metaKey, shiftKey } = e; + const { keyCode, shiftKey } = e; if (shouldDisable()) { return; } - - if (!metaKey) return; + if (!shiftKey) return; // Meta key actions switch (keyCode) { case KeyCode.KEY_1: // 1 @@ -84,19 +81,6 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { default: break; } - - if (!shiftKey) return; - // Shift + Meta key actions - switch (keyCode) { - case KeyCode.KEY_P: // p - e.preventDefault(); - return dispatch({ type: 'epub/togglePreview' }) - case KeyCode.KEY_Z: // z - e.preventDefault(); - return 0// this.onRedo(event); - default: - break; - } } return ( @@ -112,7 +96,6 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { - {previewModal} {shortcutModal} {fileSettingsModal} diff --git a/src/screens/EPub/model.js b/src/screens/EPub/model.js index 91d2b5eac..ecdf829cb 100644 --- a/src/screens/EPub/model.js +++ b/src/screens/EPub/model.js @@ -1,7 +1,6 @@ /* eslint-disable no-console */ import _ from 'lodash'; import { api, prompt, links, uurl, elem, timestr } from 'utils'; -import { delay } from 'dva/saga' import pathToRegexp from 'path-to-regexp'; import { EPubListCtrl } from 'components/CTEPubListScreen/controllers/EPubListController'; import ErrorTypes from 'entities/ErrorTypes'; @@ -25,7 +24,6 @@ const initState = { showNav: true, imgPickerData: null, playerData: null, - showPreview: false, showFileSettings: false, showPrefSettings: false, showShortcuts: false, @@ -44,7 +42,6 @@ const EPubModel = { ...state, view: payload, showNav: true, - showPreview: false, // navId: null, currChIndex: payload === Constants.EpbReadOnly ? 0 : state.currChIndex }; @@ -90,12 +87,6 @@ const EPubModel = { setPlayerData(state, { payload }) { return { ...state, playerData: payload }; }, - setShowPreview(state, { payload }) { - return { ...state, showPreview: payload }; - }, - togglePreview(state) { - return { ...state, showPreview: !state.showPreview } - }, setShowFileSettings(state, { payload }) { return { ...state, showFileSettings: payload }; }, From a0a2f562249ae2c96fd7e0bee6a85a6606dd08d9 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 26 Mar 2025 14:19:00 -0500 Subject: [PATCH 05/13] fix latex parsing by escaping special chars in all text --- .../file-builders/LatexFileBuilder.js | 48 ++++++++++++------- src/screens/EPub/index.js | 6 +-- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index e4ca96588..043eed210 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -11,13 +11,12 @@ class LatexFileBuilder { */ constructor() { this.zip = new AdmZip(); + this.ch_id = 0; } async init(parsedData) { this.data = parsedData; - this.glossary = parsedData.glossary - // eslint-disable-next-line no-console - console.log(this.data); + this.glossary = parsedData.glossary; } /** @@ -36,13 +35,17 @@ class LatexFileBuilder { if (_.isEmpty(glossary)) { return "" } - let text = `\\section{Glossary}`; + let text = `\\section{Glossary}\n`; return text + _.map(glossary, (value, key) => { - return `\\textbf{${key}}: ${value.description}` + const new_key = LatexFileBuilder.escapeSpecialChars(key); + const new_desc = LatexFileBuilder.escapeSpecialChars(value.description); + return `\\textbf{${new_key}}: ${new_desc}`; }).join("\n\n"); } getTitlePage(title, author, cover) { + title = LatexFileBuilder.escapeSpecialChars(title); + author = LatexFileBuilder.escapeSpecialChars(author); return ` \\begin{titlepage} \\centering @@ -75,19 +78,24 @@ class LatexFileBuilder { const new_desc = LatexFileBuilder.markdownToLatex(d); return `\\caption*{${new_desc}}` }).join("\n"); + + const new_alt = LatexFileBuilder.escapeSpecialChars(content.alt); return [ `\\begin{figure}`, `\\centering`, - `\\includegraphics[alt={${content.alt}}, width=.8\\textwidth]{${img_path}}`, + `\\includegraphics[alt={${new_alt}}, width=.8\\textwidth]{${img_path}}`, captions, `\\end{figure}` ].join("\n") } convertChapter(chapter) { + chapter.title = LatexFileBuilder.escapeSpecialChars(chapter.title) + chapter.id = this.ch_id; + this.ch_id += 1; return [ `\\section{${chapter.title}}`, - `\\label{sec:${chapter.title}}`, + `\\label{sec:${chapter.id}}`, _.map(chapter.contents, (c) => this.convertContent(c)).join("\n") ].join("\n"); } @@ -99,12 +107,14 @@ class LatexFileBuilder { \\begin{center} ${_.chunk(all_imgs, 2).map((pair) => { return _.map(pair, (img) => { + // evil hack to allow latex to split a long word. const split_alt = img.alt.replace(/(.{10})/g, `$1\\hspace{0pt}`); + return ` \\begin{minipage}{0.45\\textwidth} \\centering \\includegraphics[width =\\linewidth]{images/${img.id}.jpeg} - \\hyperref[sec:${this.data.chapters[img.chapter].title}]{${split_alt}} + \\hyperref[sec:${this.data.chapters[img.chapter].id}]{${split_alt}} \\end{minipage} `}).join("\\hfill"); }).join("\\vspace{1em}")} @@ -112,7 +122,12 @@ class LatexFileBuilder { `; } getMainText() { + // note, chapters must be converted first, since it also populates the chapter.id field + const chapters = _.map(this.data.chapters, (ch) => this.convertChapter(ch)).join("\n"); + const TOC = this.data.visualTOC ? this.convertVisualTOC(this.data.visualTOC) : `\\tableofcontents`; + const titlepage = this.getTitlePage(this.data.title, this.data.author, this.data.cover); + const glossary = this.convertGlossary(this.glossary) return [ "\\documentclass{article}", "\\usepackage{caption}", @@ -120,10 +135,10 @@ class LatexFileBuilder { "\\usepackage{hyperref}", "\\usepackage[T1]{fontenc}", "\\begin{document}", - this.getTitlePage(this.data.title, this.data.author, this.data.cover), + titlepage, TOC, - _.map(this.data.chapters, (ch) => this.convertChapter(ch)).join("\n"), - this.convertGlossary(this.glossary), + chapters, + glossary, "\\end{document}" ].join("\n"); } @@ -148,7 +163,6 @@ class LatexFileBuilder { static htmlToLatex(html_text) { let latex = html_text; - // Replace special characters without affecting code blocks or latex sections latex = LatexFileBuilder.substituteSpecialChars(latex); @@ -185,7 +199,7 @@ class LatexFileBuilder { 6: `subparagraph` } latex = latex.replace(/(.*?)<\/h\1>/gs, (match, level, content) => { - return `\\${level_maps[level]} {${content} } `; + return `\\${level_maps[level]}{${content}}`; }); // Convert links @@ -193,12 +207,12 @@ class LatexFileBuilder { // Convert ordered lists latex = latex.replace(/
        (.*?)<\/ol>/gs, (match, content) => { - return `\\begin{ enumerate } \n${content.replace(/
      1. (.*?)<\/li>/gs, '\\item $1')} \n\\end{ enumerate } `; + return `\\begin{enumerate} \n${content.replace(/
      2. (.*?)<\/li>/gs, '\\item $1')} \n\\end{enumerate} `; }); // Convert unordered lists latex = latex.replace(/
          (.*?)<\/ul>/gs, (match, content) => { - return `\\begin{ itemize } \n${content.replace(/
        • (.*?)<\/li>/gs, '\\item $1')} \n\\end{ itemize } `; + return `\\begin{itemize} \n${content.replace(/
        • (.*?)<\/li>/gs, '\\item $1')} \n\\end{itemize} `; }); // Convert custom math tag @@ -210,7 +224,7 @@ class LatexFileBuilder { return latex; } - static substituteSpecialChar(str) { + static escapeSpecialChars(str) { str = str.replace(/\\/g, '\\textbackslash '); str = str.replace(/\$/g, '\\$'); str = str.replace(/\{/g, '\\{'); @@ -246,7 +260,7 @@ class LatexFileBuilder { // If the node is a text node, apply the substitution if (node.nodeType === Node.TEXT_NODE) { if (state === "") { - node.textContent = LatexFileBuilder.substituteSpecialChar(node.textContent); + node.textContent = LatexFileBuilder.escapeSpecialChars(node.textContent); } else if (state === 'code') { node.textContent = LatexFileBuilder.removeVerbatimEscape(node.textContent); } else if (state === 'latex') { diff --git a/src/screens/EPub/index.js b/src/screens/EPub/index.js index 1397db584..b657d1771 100644 --- a/src/screens/EPub/index.js +++ b/src/screens/EPub/index.js @@ -12,7 +12,7 @@ import { EPubFileInfoModal, ImagePickerModal } from './components'; -import { EditEPubStructure, EditEPubChapter, ViewAndDownload, EditINote } from './views'; +import { ViewAndDownload, EditINote } from './views'; import './index.scss'; function shouldDisable() { @@ -26,8 +26,6 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { const loading = chapters === ARRAY_INIT || epub === null; const headerElement = altEl(EPubHeader, !loading); - const editStructView = altEl(EditEPubStructure, view === epubController.const.EpbEditStructure); - const editChapterView = altEl(EditEPubChapter, view === epubController.const.EpbEditChapter); const readOnlyView = altEl(ViewAndDownload, view === epubController.const.EpbReadOnly); const editINoteView = altEl(EditINote, view === epubController.const.EditINote); @@ -89,8 +87,6 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { {editINoteView} - {editStructView} - {editChapterView} {readOnlyView} From 4b0df15e3582a5103656c1259be9fec7bb2f11fb Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 26 Mar 2025 15:18:27 -0500 Subject: [PATCH 06/13] rename image inversion option --- src/screens/EPub/index.js | 2 +- src/screens/EPub/views/ViewAndDownload/EditOptions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/EPub/index.js b/src/screens/EPub/index.js index b657d1771..272976de9 100644 --- a/src/screens/EPub/index.js +++ b/src/screens/EPub/index.js @@ -41,7 +41,7 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { epubData.history.undo(); NOT IMPLEMENTED } } - + onRedo(e) { this.preventDefault(e); if (epubState.view !== Constants.EpbReadOnly && epubData.history.canRedo) { diff --git a/src/screens/EPub/views/ViewAndDownload/EditOptions.js b/src/screens/EPub/views/ViewAndDownload/EditOptions.js index 343792cfd..ce464a836 100644 --- a/src/screens/EPub/views/ViewAndDownload/EditOptions.js +++ b/src/screens/EPub/views/ViewAndDownload/EditOptions.js @@ -35,7 +35,7 @@ function EditOptions({ setDownloadOptions, downloadOptions }) { color="primary" /> } - label="Force Light Image Backgrounds" + label="Automatically Invert Dark Images" /> Date: Fri, 28 Mar 2025 22:04:55 -0500 Subject: [PATCH 07/13] add per chapter glossary (plus EPub glossary support) --- .../file-builders/EPubFileBuilder.js | 28 +++- .../controllers/file-builders/EPubParser.js | 30 +++- .../file-builders/GlossaryCreator.js | 6 +- .../file-builders/HTMLFileBuilder.js | 36 +++-- .../file-builders/LatexFileBuilder.js | 23 ++-- .../file-builders/PDFFileBuilder.js | 24 +++- .../file-builders/ScreenshotsBuilder.js | 128 ------------------ .../EPub/controllers/file-builders/index.js | 3 +- .../EPub/controllers/file-builders/utils.js | 7 + .../EPub/views/ViewAndDownload/EditOptions.js | 19 ++- .../EPub/views/ViewAndDownload/index.js | 3 +- 11 files changed, 127 insertions(+), 180 deletions(-) delete mode 100644 src/screens/EPub/controllers/file-builders/ScreenshotsBuilder.js create mode 100644 src/screens/EPub/controllers/file-builders/utils.js diff --git a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js index bb9839b87..51dc44a55 100644 --- a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import AdmZip from 'adm-zip'; import { dedent } from 'dentist'; import { KATEX_MIN_CSS, PRISM_CSS } from './file-templates/styles'; +import { glossaryToHTMLString } from './GlossaryCreator'; import { MIMETYPE, @@ -29,7 +30,7 @@ class EPubFileBuilder { async init(parsedData) { this.data = parsedData; this.language = this.data.language; - this.glossaryData = parsedData.glossary; + this.glossary = parsedData.glossary; } /** @@ -47,15 +48,20 @@ class EPubFileBuilder { getContentOPF() { const { title, author, language, publisher, chapters } = this.data; // content items - const contentItems = _.map( + let contentItems = _.map( chapters, (ch) => ``, ).join('\n\t\t'); // content itemrefs - const contentItemsRefs = _.map(chapters, (ch) => `` + let contentItemsRefs = _.map(chapters, (ch) => `` ).join('\n\t\t'); + if (this.glossary && !_.isEmpty(this.glossary) && !this.data.chapterGlossary) { + contentItems += ``; + contentItemsRefs += `` + } + return OEBPS_CONTENT_OPF({ title, author, @@ -116,8 +122,12 @@ class EPubFileBuilder { } this.buildTocNCX(chapters); } - convertChapter(chapter) { - const text = HTMLFileBuilder.convertChapter(chapter, this.data.includeRawLatex); + convertGlossary(glossary) { + return OEBPS_CONTENT_XHTML({ title: "Glossary", content: glossaryToHTMLString(glossary), language: this.language }); + } + + convertChapter(idx, chapter, chapterGlossary) { + const text = HTMLFileBuilder.convertChapter(idx, chapter, chapterGlossary, this.data.includeRawLatex); let content = dedent(`
          ${text} @@ -128,10 +138,14 @@ class EPubFileBuilder { } convertEPub() { - _.forEach(this.data.chapters, (ch) => { - const contentXHTML = this.convertChapter(ch); + _.forEach(this.data.chapters, (ch, idx) => { + const contentXHTML = this.convertChapter(idx, ch, this.data.chapterGlossary ? this.data.chapterGlossary[idx] : false); this.zip.addFile(`OEBPS/${ch.id}.xhtml`, Buffer.from(contentXHTML)); }); + if (this.glossary && !_.isEmpty(this.glossary) && !this.data.chapterGlossary) { + const glossaryXHTML = this.convertGlossary(this.glossary); + this.zip.addFile(`OEBPS/glossary.xhtml`, Buffer.from(glossaryXHTML)); + } } getEPubBuffer() { diff --git a/src/screens/EPub/controllers/file-builders/EPubParser.js b/src/screens/EPub/controllers/file-builders/EPubParser.js index c6f6fa986..e075f2d1e 100644 --- a/src/screens/EPub/controllers/file-builders/EPubParser.js +++ b/src/screens/EPub/controllers/file-builders/EPubParser.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import { uurl, api, CTError, html } from 'utils'; import html2canvas from 'html2canvas'; import { getGlossaryData } from './GlossaryCreator'; +import { epubIsImage, epubIsText } from './utils'; /** * The error which occurred while loading the images for an ePub @@ -24,23 +25,44 @@ class EPubParser { this.data.chapters = await this.parseChapters(epubData.chapters); this.data.glossary = {} if (this.options.includeGlossary) { - this.data.glossary = await getGlossaryData(epubData.sourceId); + let glossaryData = await getGlossaryData(epubData.sourceId); + if (this.options.chapterGlossary) { + this.data.chapterGlossary = this.getChapterGlossary(glossaryData, epubData.chapters); + } else { + this.data.glossary = glossaryData; + } } if (this.options.visualTOC) { this.data.visualTOC = this.getVisualTOC(this.data.chapters); } + this.data.cover = await this.parseContent(epubData.cover); this.data.includeRawLatex = options.includeRawLatex; + + // eslint-disable-next-line no-console + console.log("parsed data", this.data); } getVisualTOC(chapters) { let visualTOC = _.map(chapters, (chapter) => { return _.filter(chapter.contents, (content) => { - return typeof content === "object" && "src" in content; + return epubIsImage(content); }) }) return visualTOC; } + getChapterGlossary(glossary, chapters) { + return _.map(chapters, (chapter) => { + const chapter_text = _.filter(chapter.contents, epubIsText).join("\n").toLowerCase(); + const desc_text = _.filter(chapter.contents, epubIsImage) + .map((content) => content.descriptions.join("\n") + content.alt) + .join("\n") + .toLowerCase(); + return _.pickBy(glossary, (value, word) => + chapter_text.includes(word.toLowerCase()) || desc_text.includes(word.toLowerCase()) + ); + }) + } async parseChapters(chapters) { let new_chapters = await Promise.all(_.map(chapters, async (ch) => { return this.parseChapter(ch) @@ -63,9 +85,9 @@ class EPubParser { } async parseContent(content) { - if (typeof content !== "string") { + if (epubIsImage(content)) { return this.parseImage(content); - } if (typeof content === "string") { + } if (epubIsText(content)) { return this.parseText(content); } return content; diff --git a/src/screens/EPub/controllers/file-builders/GlossaryCreator.js b/src/screens/EPub/controllers/file-builders/GlossaryCreator.js index 0de62fe5d..b649db6e2 100644 --- a/src/screens/EPub/controllers/file-builders/GlossaryCreator.js +++ b/src/screens/EPub/controllers/file-builders/GlossaryCreator.js @@ -24,8 +24,6 @@ import { cthttp } from 'utils/cthttp/request'; export async function getGlossaryData(mediaId) { try { const response = await cthttp.get(`EPubs/GetGlossaryData?mediaId=${mediaId}`); - // eslint-disable-next-line no-console - console.log("getGlossaryData response", response); const glossaryData = {}; for (const term of response.data.Glossary) { @@ -138,8 +136,7 @@ export function glossaryToHTMLString(glossary) { return ''; } - let html = '
          '; - html += '

          Glossary

          '; + let html = '

          Glossary

          '; html += '
            '; // sort the words alphabetically @@ -159,7 +156,6 @@ export function glossaryToHTMLString(glossary) { }); html += '
          '; - html += '
          '; return html; } diff --git a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js index 852b1469c..9532661f2 100644 --- a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js @@ -6,6 +6,7 @@ import { glossaryToHTMLString, } from './GlossaryCreator'; import { INDEX_HTML_LOCAL, STYLE_CSS/* , PRISM_JS */ } from './file-templates/html'; +import { epubIsText } from './utils'; class HTMLFileBuilder { /** @@ -71,19 +72,22 @@ class HTMLFileBuilder { } static convertContent(content, includeRawLatex) { - if (typeof content === 'string') { + if (epubIsText(content)) { return HTMLFileBuilder.convertText(content, includeRawLatex); } return HTMLFileBuilder.convertImage(content); } - static convertChapter(chapter, includeRawLatex = false) { + static convertChapter(idx, chapter, chapterGlossary, includeRawLatex = false) { + let glossaryText = chapterGlossary ? glossaryToHTMLString(chapterGlossary) : ""; chapter.id = _buildID(); return [ - `\n

          ${chapter.title}

          `, + // the first chapter has idx 0, but is chapter 1. Thus, we use idx+1 + `\n

          ${idx + 1}: ${chapter.title}

          `, `
          `, _.map(chapter.contents, (c) => HTMLFileBuilder.convertContent(c, includeRawLatex)).join("\n"), - `
          ` + `
          `, + glossaryText ].join("\n\n"); } @@ -91,7 +95,7 @@ class HTMLFileBuilder { const chapters = this.data.chapters return [ '
          ', - _.map(chapters, (ch) => HTMLFileBuilder.convertChapter(ch, this.data.includeRawLatex)).join("\n"), + _.map(chapters, (ch, idx) => HTMLFileBuilder.convertChapter(idx, ch, this.data.chapterGlossary ? this.data.chapterGlossary[idx] : false, this.data.includeRawLatex,)).join("\n"), '
          ', ].join("\n"); } @@ -131,19 +135,22 @@ class HTMLFileBuilder { ).join('\n'); } - convertGlossary() { - return glossaryToHTMLString(this.glossary); + static convertGlossary(glossary) { + return `
          ${glossaryToHTMLString(glossary)}
          `; } getIndexHTML() { const conversion = this.convertChapters(); + // eslint-disable-next-line no-console + console.log("conversion", conversion) let toc = ""; if (this.data.visualTOC) { toc = this.convertVisualTOC(); } else { toc = this.convertTOC(); } - return INDEX_HTML_LOCAL({ + // eslint-disable-next-line no-console + console.log("all vals", { title: this.data.title, navContents: toc, content: conversion, @@ -152,13 +159,18 @@ class HTMLFileBuilder { createLinks: this.createLinks, visualTOC: this.data.visualTOC }) - + this.convertGlossary(); + return INDEX_HTML_LOCAL({ + title: this.data.title, + navContents: toc, + content: conversion, + author: this.data.author, + cover: this.data.cover, + createLinks: this.createLinks, + visualTOC: this.data.visualTOC + }) + (this.data.chapterGlossary ? "" : HTMLFileBuilder.convertGlossary(this.data.glossary)); } async getHTMLBuffer() { - // eslint-disable-next-line no-console - console.log(this.data); - const zip = this.zip; // styles diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index 043eed210..5f87e44de 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import AdmZip from 'adm-zip'; import { html } from 'utils'; +import { epubIsText } from './utils'; class LatexFileBuilder { /** @@ -31,11 +32,11 @@ class LatexFileBuilder { return buffer; } - convertGlossary(glossary) { + convertGlossary(glossary, is_section = true) { if (_.isEmpty(glossary)) { return "" } - let text = `\\section{Glossary}\n`; + let text = is_section ? `\\section{Glossary}\n` : `\\subsection{Glossary}\n`; return text + _.map(glossary, (value, key) => { const new_key = LatexFileBuilder.escapeSpecialChars(key); const new_desc = LatexFileBuilder.escapeSpecialChars(value.description); @@ -70,7 +71,7 @@ class LatexFileBuilder { return img_path } convertContent(content) { - if (typeof content === "string") { + if (epubIsText(content)) { return LatexFileBuilder.markdownToLatex(content); } const img_path = this.saveImage(content); @@ -89,14 +90,13 @@ class LatexFileBuilder { ].join("\n") } - convertChapter(chapter) { + convertChapter(idx, chapter) { chapter.title = LatexFileBuilder.escapeSpecialChars(chapter.title) - chapter.id = this.ch_id; - this.ch_id += 1; return [ `\\section{${chapter.title}}`, - `\\label{sec:${chapter.id}}`, - _.map(chapter.contents, (c) => this.convertContent(c)).join("\n") + `\\label{sec:${idx}}`, + _.map(chapter.contents, (c) => this.convertContent(c)).join("\n"), + this.data.chapterGlossary ? this.convertGlossary(this.data.chapterGlossary[idx], false) : "" ].join("\n"); } @@ -114,7 +114,7 @@ class LatexFileBuilder { \\begin{minipage}{0.45\\textwidth} \\centering \\includegraphics[width =\\linewidth]{images/${img.id}.jpeg} - \\hyperref[sec:${this.data.chapters[img.chapter].id}]{${split_alt}} + \\hyperref[sec:${img.chapter}]{${split_alt}} \\end{minipage} `}).join("\\hfill"); }).join("\\vspace{1em}")} @@ -122,12 +122,11 @@ class LatexFileBuilder { `; } getMainText() { - // note, chapters must be converted first, since it also populates the chapter.id field - const chapters = _.map(this.data.chapters, (ch) => this.convertChapter(ch)).join("\n"); + const chapters = _.map(this.data.chapters, (ch, idx) => this.convertChapter(idx, ch)).join("\n"); const TOC = this.data.visualTOC ? this.convertVisualTOC(this.data.visualTOC) : `\\tableofcontents`; const titlepage = this.getTitlePage(this.data.title, this.data.author, this.data.cover); - const glossary = this.convertGlossary(this.glossary) + const glossary = this.data.chapterGlossary ? "" : this.convertGlossary(this.glossary) return [ "\\documentclass{article}", "\\usepackage{caption}", diff --git a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js index 9313dcce3..71875da88 100644 --- a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js @@ -119,25 +119,32 @@ class PDFFileBuilder { } } - convertChapter({ contents, title }) { - this.writeTextToPDF(title, STYLE_SHEET.font.chapterTitle); + convertChapter(idx, { contents, title }) { + this.writeTextToPDF(`${idx}: ${title}`, STYLE_SHEET.font.chapterTitle); this.chapter_page_indexes.push(this.currentPageNumber); this.incrementYLoc(STYLE_SHEET.spacing); _.forEach(contents, (content) => { this.convertContent(content) }); + if (this.data.chapterGlossary) { + this.incrementYLoc(20); + this.convertGlossary(this.data.chapterGlossary[idx], false); + } this.nextPage(); } convertChapters() { - _.forEach(this.data.chapters, (chapter) => this.convertChapter(chapter)) + _.forEach(this.data.chapters, (chapter, idx) => this.convertChapter(idx, chapter)) } writeGlossaryEntry(key, value) { this.writeTextToPDF(`${key}: ${value.description}`, STYLE_SHEET.font.glossary); } - convertGlossary(glossary) { - this.doc.outline.add(null, "Glossary", { pageNumber: this.currentPageNumber }) - this.writeTextToPDF("Glossary", STYLE_SHEET.font.title); + convertGlossary(glossary, add_outline = true) { + if (add_outline) { + this.glossary_page = this.currentPageNumber; + } + this.writeTextToPDF("Glossary", add_outline ? STYLE_SHEET.font.title : STYLE_SHEET.font.chapterTitle); for (const [key, value] of Object.entries(glossary)) { this.writeGlossaryEntry(key, value); + this.incrementYLoc(5); } } nextPageTOC(offset = 0) { @@ -238,6 +245,9 @@ class PDFFileBuilder { for (let i = 0; i < this.chapter_page_indexes.length; i += 1) { this.doc.outline.add(null, this.data.chapters[i].title, { pageNumber: this.chapter_page_indexes[i] + this.pageOffset }); } + if (this.glossary_page) { + this.doc.outline.add(null, "Glossary", { pageNumber: this.glossary_page + this.pageOffset }); + } } createPDF() { @@ -250,7 +260,7 @@ class PDFFileBuilder { this.nextPage(); this.convertChapters(); - if (this.glossary && !_.isEmpty(this.glossary)) { + if (this.glossary && !_.isEmpty(this.glossary) && !this.data.chapterGlossary) { this.convertGlossary(this.glossary); } diff --git a/src/screens/EPub/controllers/file-builders/ScreenshotsBuilder.js b/src/screens/EPub/controllers/file-builders/ScreenshotsBuilder.js deleted file mode 100644 index 2f6f5e91b..000000000 --- a/src/screens/EPub/controllers/file-builders/ScreenshotsBuilder.js +++ /dev/null @@ -1,128 +0,0 @@ -/* eslint-disable complexity */ -import AdmZip from 'adm-zip'; -// import { EPubData } from 'entities/EPubs'; -import EPubParser from './EPubParser'; -import {INDEX_LATEX} from './file-templates/latex' - -class ScreenshotsBuilder { - /** - * Create an ScreenshotsBuilder - * @param {EPubData} ePubData - */ - constructor(ePubData) { - this.zip = new AdmZip(); - this.screenshots = ePubData.images; - } - - async init(ePubData) { - this.data = await EPubParser.parse(ePubData); - } - - /** - * Insert all the screenshots of an EPubData object to a zipped file buffer - * @param {EPubData} ePubData - * @returns {Buffer} zipped file buffer - */ - static async toBuffer(ePubData) { - const builder = new ScreenshotsBuilder(ePubData); - await builder.init(ePubData); - const buffer = await builder.getScreenshotBuffers(); - return buffer; - } - - async generateLatex(epub) { - const { chapters, condition, title, author } = epub; - let selectedChapters = []; - for (let i = 0; i < chapters.length; i+= 1) { - for (const [key, value] of Object.entries(condition)) { - if (key === 'default') { - if (value === true && (!chapters[i].condition || chapters[i].condition.find(elem => elem === key) !== undefined)) { - selectedChapters.push(chapters[i]); - break; - } - } else if (value === true && (chapters[i].condition && chapters[i].condition.find(elem => elem === key) !== undefined)) { - selectedChapters.push(chapters[i]); - break; - } - } - } - let content = ''; - for (let i = 0; i < selectedChapters.length; i += 1) { - let chapter = selectedChapters[i]; - let chapterContent = `\\section{${chapter.title}}\n`; - let imgName = `images/image-${i + 1}.jpeg` - let imageInfo = `\\includegraphics[scale=0.2]{${imgName}}\\newline\n`; - chapterContent = chapterContent.concat(imageInfo); - let parser = new DOMParser(); - // let transcriptStart = chapter.text.indexOf("

          "); - // let transcriptEnd = chapter.text.indexOf("

          "); - let all_text = parser.parseFromString(chapter.text, "text/html"); - - // Replace MathML with P tags containing the LaTeX - let latex = Array.prototype.slice.call(all_text.getElementsByTagName("span")); - for (let latex_idx = 0; latex_idx < latex.length; latex_idx+=1) { - let curr_tag = latex[latex_idx]; - if (curr_tag.getAttribute("title")) { - let annotation = `$$${curr_tag.getAttribute("title")}$$`; - let new_p_tag = document.createElement('p'); - new_p_tag.innerHTML = annotation; - curr_tag.replaceWith(new_p_tag); - } else if (curr_tag.parentElement.tagName === "P") { - let annotation = curr_tag.getElementsByTagName("annotation"); - if (annotation) { - annotation = `$${annotation[0].innerHTML}$`; - curr_tag.replaceWith(annotation); - } - } - } - - let all_paragraphs = Array.prototype.slice.call(all_text.getElementsByTagName("p"),0); - - for(let ind = 0; ind < all_paragraphs.length; ind+=1) { - let transcript = all_paragraphs[ind].innerHTML; // get the inner html of each paragraph transcript - transcript = transcript.replaceAll("%", "\\%"); - chapterContent = chapterContent.concat(transcript, '\n'); - } - - for (let j = 0; j < chapter.subChapters.length; j += 1) { - let subChapter = chapter.subChapters[j]; - let subContent = `\\subsection{${subChapter.title}}`; - for (let k = 0; k < subChapter.contents.length; k += 1) { - let subContents = subChapter.contents[k]; - // unwrap __data__ for correct image loading in subchapters - if (typeof subContents === 'object' && "__data__" in subContents) { - subContents = JSON.parse(JSON.stringify(subContents.__data__)); - } - if (typeof subContents === 'string') { - subContents = subContents.replace('#### Transcript', ''); - subContents = subContents.trim(); - subContents = subContents.replaceAll("%", "\\%"); - subContent = subContent.concat(subContents, '\n'); - } else if (subContents.src) { - imgName = `images/image-${i + 1}.jpeg` - imageInfo = `\\includegraphics[scale=0.3]{${imgName}}\\newline\n`; - subContent = subContent.concat(imageInfo); - } - } - chapterContent = chapterContent.concat(subContent, '\\newpage\n'); - } - content = content.concat(chapterContent, '\\newpage\n'); - } - return INDEX_LATEX({title, content, author}); - } - - async getScreenshotBuffers() { - const images = this.screenshots; - /* eslint-disable no-await-in-loop */ - for (let i = 0; i < images.length; i += 1) { - let data = await EPubParser.loadImageBuffer(images[i]); - this.zip.addFile(`images/image-${i + 1}.jpeg`, data); - } - /* eslint-enable no-await-in-loop */ - const latex = await this.generateLatex(this.data); - this.zip.addFile('index.tex', Buffer.from(latex)); - return this.zip.toBuffer(); - } -} - -export default ScreenshotsBuilder; \ No newline at end of file diff --git a/src/screens/EPub/controllers/file-builders/index.js b/src/screens/EPub/controllers/file-builders/index.js index ce6129528..1397af812 100644 --- a/src/screens/EPub/controllers/file-builders/index.js +++ b/src/screens/EPub/controllers/file-builders/index.js @@ -3,5 +3,4 @@ export { LoadImageError } from './EPubParser'; export { default as HTMLFileBuilder } from './HTMLFileBuilder'; export { default as EPubFileBuilder } from './EPubFileBuilder'; export { default as LatexFileBuilder } from './LatexFileBuilder'; -export { default as PDFFileBuilder } from './PDFFileBuilder'; -export { default as ScreenshotsBuilder } from './ScreenshotsBuilder'; \ No newline at end of file +export { default as PDFFileBuilder } from './PDFFileBuilder'; \ No newline at end of file diff --git a/src/screens/EPub/controllers/file-builders/utils.js b/src/screens/EPub/controllers/file-builders/utils.js new file mode 100644 index 000000000..cc3b2e61c --- /dev/null +++ b/src/screens/EPub/controllers/file-builders/utils.js @@ -0,0 +1,7 @@ +export function epubIsText(content) { + return typeof content === "string" || (typeof content === "object" && "text" in content); +} + +export function epubIsImage(content) { + return typeof content === "object" && "src" in content; +} \ No newline at end of file diff --git a/src/screens/EPub/views/ViewAndDownload/EditOptions.js b/src/screens/EPub/views/ViewAndDownload/EditOptions.js index ce464a836..099a68287 100644 --- a/src/screens/EPub/views/ViewAndDownload/EditOptions.js +++ b/src/screens/EPub/views/ViewAndDownload/EditOptions.js @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'dva' import { CTFragment, CTHeading } from 'layout'; -import { FormControlLabel, Checkbox } from '@material-ui/core'; +import { FormControlLabel, Checkbox, Box } from '@material-ui/core'; function EditOptions({ setDownloadOptions, downloadOptions }) { const handleCheckboxChange = (event) => { @@ -13,7 +13,7 @@ function EditOptions({ setDownloadOptions, downloadOptions }) { }; return ( - + Download Options } label="Include Glossary" + sx={{ marginBottom: 0 }} /> + + + } + label="Glossary Per Chapter" + /> + Date: Thu, 3 Apr 2025 20:17:44 -0500 Subject: [PATCH 08/13] add links to video per image --- .../controllers/EPubListController.js | 11 +- .../CTEPubListScreen/controllers/helpers.js | 21 +- .../CTImagePickerModal/ImagesTab.js | 13 +- src/entities/EPubs/html-converters.js | 28 +- src/entities/EPubs/structs/EPubChapterData.js | 99 ++++--- .../EPubs/structs/EPubChapterLikeData.js | 244 ------------------ src/entities/EPubs/structs/EPubData.js | 151 ++++------- src/entities/EPubs/structs/EPubImageData.js | 57 +--- .../EPubs/structs/EPubSubChapterData.js | 4 +- src/entities/EPubs/utils.js | 7 +- .../ChapterImage/ImageDescription.js | 2 + .../components/ChapterImage/ImageWrapper.js | 26 +- .../EPub/components/ChapterImage/index.js | 7 +- .../EPub/components/ImagePickerModal.js | 3 +- .../file-builders/EPubFileBuilder.js | 18 +- .../controllers/file-builders/EPubParser.js | 4 +- .../file-builders/HTMLFileBuilder.js | 40 +-- .../file-builders/LatexFileBuilder.js | 3 + .../file-builders/PDFFileBuilder.js | 8 +- src/screens/EPub/model.js | 5 +- .../EditINote/INoteEditor/INoteChapter.js | 1 - .../EPub/views/ViewAndDownload/EPubPreview.js | 2 +- .../EPub/views/ViewAndDownload/EditOptions.js | 45 ++-- .../EPub/views/ViewAndDownload/index.js | 3 +- src/screens/Watch/Utils/helpers.js | 9 +- src/utils/links.js | 24 +- 26 files changed, 291 insertions(+), 544 deletions(-) delete mode 100644 src/entities/EPubs/structs/EPubChapterLikeData.js diff --git a/src/components/CTEPubListScreen/controllers/EPubListController.js b/src/components/CTEPubListScreen/controllers/EPubListController.js index a53aa39e4..37465c51a 100644 --- a/src/components/CTEPubListScreen/controllers/EPubListController.js +++ b/src/components/CTEPubListScreen/controllers/EPubListController.js @@ -1,6 +1,7 @@ +/* eslint-disable no-unreachable */ import SourceTypes from 'entities/SourceTypes'; import ErrorTypes from 'entities/ErrorTypes'; -import { api, prompt, links } from 'utils'; +import { api, prompt, links, uurl } from 'utils'; import { EPubData } from 'entities/EPubs/structs'; import Constants from 'screens/EPub/controllers/constants/EPubConstants'; import { LanguageConstants } from '../../CTPlayer'; @@ -22,6 +23,8 @@ class EPubListController { async createEPub(sourceType, sourceId, data) { prompt.addOne({ text: 'Creating I-Note...', timeout: 4000 }); const rawEPubData = await this.getRawEPubData(sourceType, sourceId, data.language); + + if (rawEPubData === ErrorTypes.NotFound404) { prompt.error('Failed to create the I-Note.'); return false; @@ -32,6 +35,8 @@ class EPubListController { sourceType, sourceId, ...data }).toObject(); + // throw Error(); + delete ePubData.id; // console.log(ePubData); @@ -43,9 +48,9 @@ class EPubListController { } const url = links.epub(newEPubData.id, Constants.EditINote, Constants.HFromNew); - window.location.href = url; + // window.location.href = url; // Don't open in new tab; the user may not have enabled that. - // nope: uurl.openNewTab(url) + uurl.openNewTab(url) return newEPubData; } diff --git a/src/components/CTEPubListScreen/controllers/helpers.js b/src/components/CTEPubListScreen/controllers/helpers.js index b13c6cbe9..f2260bb26 100644 --- a/src/components/CTEPubListScreen/controllers/helpers.js +++ b/src/components/CTEPubListScreen/controllers/helpers.js @@ -7,17 +7,18 @@ export function _filterTrivalItems(epubData) { return [...epubData]; // return _.filter(epubData, (item) => Boolean(_.trim(item.text))); } -function getLastPunctuationIndex(sentence) { - let lastPunctuationIndex = -1; - for (let i = sentence.length - 1; i >= 0; i -= 1) { - if (sentence[i] === '.' || sentence[i] === '?' || sentence[i] === '!') { - lastPunctuationIndex = i; - break; - } - } - return lastPunctuationIndex; -} +// function getLastPunctuationIndex(sentence) { +// let lastPunctuationIndex = -1; +// for (let i = sentence.length - 1; i >= 0; i -= 1) { +// if (sentence[i] === '.' || sentence[i] === '?' || sentence[i] === '!') { +// lastPunctuationIndex = i; +// break; +// } +// } +// return lastPunctuationIndex; +// } +// eslint-disable-next-line no-unused-vars function _parseRawEPubDataSplittingOnPunctuation(rawEPubData) { return null; // let buffer = ""; diff --git a/src/components/CTImagePickerModal/ImagesTab.js b/src/components/CTImagePickerModal/ImagesTab.js index a5caf50c5..4ec717ad3 100644 --- a/src/components/CTImagePickerModal/ImagesTab.js +++ b/src/components/CTImagePickerModal/ImagesTab.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { CTText } from 'layout'; import { uurl } from 'utils/use-url'; import Image from 'components/Image'; +import _ from 'lodash'; import ImagePreview from './ImagePreview'; function ImagesTab(props) { @@ -19,18 +20,18 @@ function ImagesTab(props) { {description && {description}}
          {images.map(img => ( -
          setImgUrl(img)} + data-current={img.src === imgUrl} + onClick={() => setImgUrl(img.src)} role="listitem" > - Chapter Cover + Chapter Cover
          { - img === imgUrl + img.src === imgUrl && check_circle } diff --git a/src/entities/EPubs/html-converters.js b/src/entities/EPubs/html-converters.js index b2374f7bc..84419c4c5 100644 --- a/src/entities/EPubs/html-converters.js +++ b/src/entities/EPubs/html-converters.js @@ -27,27 +27,21 @@ export async function buildMDFromContent(content) { // Most likely cause is CORS policy when running local dev server } + let link_url = content.link; + // if (content.timestamp) { + // link_url = links.watch(epub.sourceId, { begin: TimeString.toSeconds(timestamp) }) + // } + if (img_data_url === null) { - let despId = _buildID(); - return [ - '
          ', - `\t${content.alt}`, - `\t
          ${html.markdown(content.descriptions.join("\n"))}
          `, - '
          ' - ].join('\n'); - } - if (content.descriptions.length !== 0) { - let despId = _buildID(); - return [ - '
          ', - `\t${content.alt}`, - `\t
          ${html.markdown(content.descriptions.join("\n"))}
          `, - '
          ' - ].join('\n'); + img_data_url = ""; } + let despId = _buildID(); return [ '
          ', - `\t${content.alt}`, + (link_url && link_url !== "") ? `` : "", + `\t${content.alt}`, + (link_url && link_url !== "") ? `` : "", + content.descriptions.length !== 0 ? `\t
          ${html.markdown(content.descriptions.join("\n"))}
          ` : "", '
          ' ].join('\n'); } diff --git a/src/entities/EPubs/structs/EPubChapterData.js b/src/entities/EPubs/structs/EPubChapterData.js index c991ef1b2..37cda3894 100644 --- a/src/entities/EPubs/structs/EPubChapterData.js +++ b/src/entities/EPubs/structs/EPubChapterData.js @@ -1,31 +1,58 @@ +import { _buildID } from 'utils'; +import _ from 'lodash'; import { buildHTMLFromChapter } from '../html-converters'; -import EPubChapterLikeData from './EPubChapterLikeData'; -import EPubSubChapterData from './EPubSubChapterData'; - -let untitledChapterNum = 0; -function _createChapterTitle() { - untitledChapterNum += 1; - let chapterNum = untitledChapterNum > 0 ? ` (${untitledChapterNum})` : ''; - return `Untitled Chapter${chapterNum}`; -} +import EPubImageData from './EPubImageData'; + +class EPubChapterData { + __data__ = { + id: '', + title: '', + start: '00:00:00', + end: '00:00:00', + timemerge: '00:00:00', + condition: ['default'], + subChapters: [], + contents: [] + }; + + constructor(data, resetText = true, sourceId) { + if (data instanceof EPubChapterData) { + this.__data__ = data.__data__; + return; + } + + let { + id, + title, + items, + contents = [] + } = data; + + // const { start, end } = findChapterTimeSpan(data); // TODO + + this.__data__ = { + ...this.__data__, + id: id || _buildID(), + title: title || EPubChapterData.createChapterTitle(), + condition: ['default'], + contents: resetText + ? EPubChapterData.buildContentsFromItems(items, sourceId) + : contents.map(con => typeof con === 'string' ? con : { ...con }) + }; + } -class EPubChapterData extends EPubChapterLikeData { - constructor(chapterLike, resetText) { - super(chapterLike, resetText, _createChapterTitle); + contentToObject(content) { + return content instanceof EPubImageData ? content.toObject() : content; + } - const { subChapters = [] } = chapterLike; - this.subChapters = subChapters.map(sch => { - const subchapter = new EPubSubChapterData(sch, resetText); - return subchapter.__data__ ? subchapter.__data__ : subchapter.toObject(); - }); + contentsToObject(contents) { + return contents.map(this.contentToObject); } toObject() { return { ...this.__data__, - items: this.itemsToObject(), - contents: this.contentsToObject(), - subChapters: this.subChapters.map(subChapter => subChapter.__data__ ? subChapter.__data__ : subChapter) + contents: this.contentsToObject(this.__data__.contents), }; } @@ -33,18 +60,30 @@ class EPubChapterData extends EPubChapterLikeData { return buildHTMLFromChapter(this.__data__); } - /** - * @returns {EPubSubChapterData[]} - */ - get subChapters() { - return this.__data__.subChapters; + static untitledChapterNum = 0; + static buildContentsFromItems(items, sourceId) { + const content = []; + for (const item of items) { + if (item !== undefined) { + if (item.image) { // if there is an image + const imageData = (sourceId ? EPubImageData.createWithTimestamp(item, sourceId) : EPubImageData.create(item)); + content.push(imageData) + } + if (item.text) { // if there is text + const text = item.text + if (_.trim(text)) { + content.push(text); + } + } + } + } + return content; } - - set subChapters(subChapters) { - this.__data__.subChapters = subChapters; + static createChapterTitle() { + EPubChapterData.untitledChapterNum += 1; + let chapterNum = EPubChapterData.untitledChapterNum > 0 ? ` (${EPubChapterData.untitledChapterNum})` : ''; + return `Untitled Chapter${chapterNum}`; } - - static __buildHTMLFromChapter = buildHTMLFromChapter; } export default EPubChapterData; diff --git a/src/entities/EPubs/structs/EPubChapterLikeData.js b/src/entities/EPubs/structs/EPubChapterLikeData.js deleted file mode 100644 index 35409f4f0..000000000 --- a/src/entities/EPubs/structs/EPubChapterLikeData.js +++ /dev/null @@ -1,244 +0,0 @@ -import _ from 'lodash'; -import { timestr, _buildID } from 'utils'; -import { findChapterTimeSpan, getAllImagesInChapter, getAllItemsInChapter } from '../utils'; -import EPubImageData from './EPubImageData'; - -function _buildContentsFromItems(items) { - const content = []; - for (const item of items) { - if (item !== undefined) { - if (item.image) { // if there is an image - const altText = item.title - const imageData = new EPubImageData({ src: item.image, alt: altText, descriptions: item.ocrElements }); - content.push(imageData) - } - if (item.text) { // if there is text - const text = item.text - if (_.trim(text)) { - content.push(text); - } - } - } - } - - return content; -} - -class EPubChapterLikeData { - __data__ = { - id: '', - title: '', - start: '00:00:00', - end: '00:00:00', - timemerge: '00:00:00', - items: [], - condition: ['default'], - contents: [] - }; - - constructor(data, resetText = true, getTitle) { - if (data instanceof EPubChapterLikeData) { - this.__data__ = data.__data__; - return; - } - - let { - id, - title, - items, - contents = [] - } = data; - - const { start, end } = findChapterTimeSpan(data); // TODO - - if (!title && typeof getTitle === 'function') { - title = getTitle(); - } - this.__data__ = { - id: id || _buildID(), - start, - end, - title: title || 'Untitled', - items, - condition: ['default'], - contents: resetText - ? _buildContentsFromItems(items) - : contents.map(con => typeof con === 'string' ? con : (new EPubImageData(con)).toObject()) - // you have to explicitly cast EPubImageData to plain object to maintain consistency with - // _buildContentsFromItems, which implicitly makes the cast - }; - } - - toShallowObject() { - return { ...this.__data__ }; - } - - /** - * @returns {String} - */ - get id() { - return this.__data__.id; - } - - set id(id) { - this.__data__.id = id; - } - - /** - * @returns {String} - */ - get title() { - return this.__data__.title; - } - - set title(title) { - this.__data__.title = title; - } - - /** - * @returns {String} - */ - get start() { - return this.__data__.start; - } - - set start(start) { - this.__data__.start = start; - } - - /** - * @returns {Number} - */ - get startSec() { - return timestr.toSeconds(this.start); - } - - /** - * @returns {String} - */ - get end() { - return this.__data__.end; - } - - set end(end) { - this.__data__.end = end; - } - - /** - * @returns {Number} - */ - get endSec() { - return timestr.toSeconds(this.end); - } - - /** - * @returns {{image:string,id:string,start:string,end:string,text:string}[]} - */ - get items() { - return this.__data__.items; - } - - set items(items) { - this.__data__.items = items; - } - - get condition() { - return this.__data__.condition; - } - - set condition(condition) { - this.__data__.condition.add(condition); - } - - itemsToObject() { - return [...this.items]; - } - - get allItemsWithIn() { - return getAllImagesInChapter(this.__data__); - } - - get allImagesWithIn() { - return getAllImagesInChapter(this.__data__); - } - - /** - * @returns {[String|EPubImageData]} - */ - get contents() { - return this.__data__.contents; - } - - set contents(contents) { - this.__data__.contents = contents; - } - - contentToObject(content) { - return content instanceof EPubImageData ? content.toObject() : content; - } - - contentsToObject() { - return this.contents.map(this.contentToObject); - } - - /** - * - * @param {Number} index - * @returns {String|EPubImageData} content at index - */ - getContent(index) { - return this.contents[index]; - } - - /** - * - * @param {Number} index - * @param {String|EPubImageData} content - */ - setContent(index, content) { - this.contents[index] = content; - } - - /** - * - * @returns {String|EPubImageData} content at index - */ - get last() { - return this.getContent(this.contents.length - 1); - } - - /** - * - * @param {String|EPubImageData} content - */ - push(content) { - this.contents.push(content) - } - - /** - * @param {Number} index - * @param {String|EPubImageData} content - */ - insert(index, content) { - if (index >= this.contents.length) { - this.push(content); - } else { - this.contents = [ - ...this.contents.slice(0, index), - content, - ...this.contents.slice(index) - ]; - } - } - - /** - * - * @param {Number|String|EPubImageData} predictor - */ - - - static __getAllImagesInChapter = getAllImagesInChapter; - static __getAllItemsInChapter = getAllItemsInChapter; -} - -export default EPubChapterLikeData; diff --git a/src/entities/EPubs/structs/EPubData.js b/src/entities/EPubs/structs/EPubData.js index 29fdd7543..3eea4e812 100644 --- a/src/entities/EPubs/structs/EPubData.js +++ b/src/entities/EPubs/structs/EPubData.js @@ -2,23 +2,10 @@ import _ from 'lodash'; import { v4 as uuid } from 'uuid'; import CTError from 'utils/use-error'; -import { getAllItemsInChapters } from '../utils'; import { buildMDFromChapters } from '../html-converters'; import EPubChapterData from './EPubChapterData'; -import EPubSubChapterData from './EPubSubChapterData'; import EPubImageData from './EPubImageData'; -function _buildEPubDataFromArray(rawEPubData) { - let a = [ - new EPubChapterData({ - items: _.cloneDeep(rawEPubData), - title: 'Default Chapter', - }).toObject() - ]; - - return a; -} - /** * The error which occurred when the required information * for creating an ePub file is invalid @@ -40,11 +27,9 @@ export default class EPubData { cover: null, chapters: [], h3: true, - condition: { 'default': true } + condition: { 'default': true }, }; - images = []; - /** * Create a ePub data instance * @param {Any} data - the initial data for the ePub @@ -57,10 +42,6 @@ export default class EPubData { if (data instanceof EPubData) { this.__data__ = data.__data__; } - // if the input data is the raw epub data - else if (data.rawEPubData && Array.isArray(data.rawEPubData)) { - this.chapters = _buildEPubDataFromArray(data.rawEPubData); - } // if the input data is the epub-like else if (typeof data === 'object') { this.__data__ = { @@ -92,24 +73,28 @@ export default class EPubData { this.h3 = true; } - this.chapters = _.map(this.chapters, chapter => new EPubChapterData(chapter, false)); - // this.condition = ['default']; - this.condition.default = true; - // extract all the items and images from the chapters - this.items = getAllItemsInChapters(this.chapters); - this.images = _.map(this.items, item => item.image); - // set up cover image if (!this.cover) { this.cover = new EPubImageData(); - } if (!(this.cover instanceof EPubImageData)) { + } else { this.cover = new EPubImageData(this.cover); } + } - if (!this.cover.src && this.images.length > 0) { - this.cover = new EPubImageData({ - src: this.images[0], alt: `Cover for ${this.title}` - }); + initFromRawData(rawEPubData) { + this.chapters = [ + new EPubChapterData({ + title: 'Default Chapter', + items: rawEPubData, + }, true, this.sourceId).toObject() + ]; + // this.condition = ['default']; + this.condition.default = true; + + this.items = rawEPubData.map((item) => (EPubImageData.createWithTimestamp(item, this.sourceId))); + + if (!this.cover.src && this.items.length > 0) { + this.cover = { ...this.items[0], descriptions: [], alt: "cover image" }; } } @@ -208,6 +193,14 @@ export default class EPubData { return this.__data__.language; } + set items(items) { + this.__data__.chapters[0].items = items; + } + + get items() { + return this.__data__.chapters[0].items; + } + set chapters(chapters) { this.__data__.chapters = chapters; } @@ -222,8 +215,8 @@ export default class EPubData { toObject() { return { ...this.__data__, - cover: this.cover.toObject(), - chapters: this.chapters.map(chapter => chapter.toObject()) + cover: this.cover instanceof EPubImageData ? this.cover.toObject() : this.cover, + chapters: this.chapters.map(chapter => (chapter instanceof EPubChapterData ? chapter.toObject() : chapter)) }; } @@ -239,15 +232,6 @@ export default class EPubData { return epub.chapters[chapterIndex]; } - getSubChapter(chapterIndex, subChapterIndex) { - let currChapter = this.getChapter(chapterIndex); - if (currChapter) { - return currChapter.subChapters[subChapterIndex]; - } - - return null; - } - rebuildChapter(chapterIndex, chapterLike, resetText) { let chapters = this.chapters; // if there is such a chapter in the epub data @@ -258,32 +242,6 @@ export default class EPubData { } } - rebuildSubChapter(chapterIndex, subChapterIndex, subChapterLike, resetText) { - let chapters = this.chapters; - let currChapter = chapters[chapterIndex]; - if (currChapter) { - let subChapters = currChapter.subChapters; - // if there is such a subchapter in the epub data - // update the subchapter item - if (subChapters[subChapterIndex]) { - let toBuild = subChapterLike || subChapters[subChapterIndex]; - subChapters[subChapterIndex] = new EPubSubChapterData(toBuild, resetText); - } - } - } - - insertSubChapter(chapterIndex, subChapterIndex, subChapterLike) { - let newSubChapter = new EPubSubChapterData(subChapterLike); - let chapter = this.getChapter(chapterIndex); - chapter.subChapters = [ - ...chapter.subChapters.slice(0, subChapterIndex), - newSubChapter, - ...chapter.subChapters.slice(subChapterIndex) - ]; - - return newSubChapter; - } - removeChapter(index) { let chapters = this.chapters; let chapter = chapters[index]; @@ -295,44 +253,29 @@ export default class EPubData { return chapter; } - removeSubChapter(chapterIndex, subChapterIndex) { - let chapter = this.getChapter(chapterIndex); - let subChapter = chapter.subChapters[subChapterIndex]; - chapter.subChapters = [ - ...chapter.subChapters.slice(0, subChapterIndex), - ...chapter.subChapters.slice(subChapterIndex + 1) - ]; - - return subChapter; - } - static create(rawEPubData, data, copyChapterStructure) { - return new EPubData({ - ...data, - chapters: copyChapterStructure - ? EPubData.copyChapterStructure(rawEPubData, data.chapters) - : _buildEPubDataFromArray(rawEPubData) + static create(rawEPubData, data) { + const newData = new EPubData({ + ...data }); - } + newData.initFromRawData(rawEPubData); - static copyChapterStructure(rawEPubData, chapters) { - let lastIdx = 0; - return _.map(chapters, (chapter) => { - let chItems = rawEPubData.slice(lastIdx, lastIdx + chapter.items.length); - lastIdx += chapter.items.length; - let newSubChapters = _.map(chapter.subChapters, (subChapter) => { - let schItems = rawEPubData.slice(lastIdx, lastIdx + subChapter.items.length); - lastIdx += subChapter.items.length; - return new EPubSubChapterData({ title: subChapter.title, items: schItems }); - }); - - return new EPubChapterData({ - title: chapter.title, - items: chItems, - subChapters: newSubChapters - }) - }); + // eslint-disable-next-line no-console + console.log("created data", newData.chapters); + return newData; } - static __buildEPubDataFromArray = _buildEPubDataFromArray; + + // static copyChapterStructure(rawEPubData, chapters) { + // let lastIdx = 0; + // return _.map(chapters, (chapter) => { + // let chItems = rawEPubData.slice(lastIdx, lastIdx + chapter.items.length); + // lastIdx += chapter.items.length; + + // return new EPubChapterData({ + // title: chapter.title, + // items: chItems, + // }) + // }); + // } } \ No newline at end of file diff --git a/src/entities/EPubs/structs/EPubImageData.js b/src/entities/EPubs/structs/EPubImageData.js index 7ef2e988b..f578edd3f 100644 --- a/src/entities/EPubs/structs/EPubImageData.js +++ b/src/entities/EPubs/structs/EPubImageData.js @@ -1,54 +1,25 @@ -class EPubImageData { - __data__ = { - src: '', - alt: 'Video screenshot', - descriptions: [''] - }; - - constructor(imageLike) { - if (typeof imageLike === 'string') { - this.src = imageLike; - } else if (imageLike) { - if (imageLike.src) { - this.src = imageLike.src; - } - - if (imageLike.alt) { - this.alt = imageLike.alt; - } +import { getShareableVideoURL } from 'screens/Watch/Utils'; +import TimeString from 'utils/use-time'; - if (imageLike.descriptions) { - this.descriptions = [...imageLike.descriptions]; - } - } +class EPubImageData { + constructor(imageLike = {}) { + this.src = typeof imageLike === 'string' ? imageLike : imageLike.src || ''; + this.alt = imageLike.alt || 'Video screenshot'; + this.descriptions = Array.isArray(imageLike.descriptions) ? [...imageLike.descriptions] : ['']; + this.timestamp = imageLike.timestamp || ''; + this.link = imageLike.link && typeof imageLike.link === 'string' ? imageLike.link : ''; } toObject() { - return { ...this.__data__ }; - } - - get src() { - return this.__data__.src; - } - - set src(src) { - this.__data__.src = src; - } - - get alt() { - return this.__data__.alt; - } - - set alt(alt) { - this.__data__.alt = alt; + return { src: this.src, alt: this.alt, descriptions: this.descriptions, timestamp: this.timestamp, link: this.link }; } - get descriptions() { - return this.__data__.descriptions; + static create(raw) { + return { src: raw.image, alt: raw.title, descriptions: raw.ocrElements, timestamp: raw.start } } - set descriptions(descriptions) { - this.__data__.descriptions = descriptions; + static createWithTimestamp(raw, sourceId) { + return { ...EPubImageData.create(raw), link: getShareableVideoURL(sourceId, TimeString.toSeconds(raw.start)) } } } diff --git a/src/entities/EPubs/structs/EPubSubChapterData.js b/src/entities/EPubs/structs/EPubSubChapterData.js index c76a98665..5344f3421 100644 --- a/src/entities/EPubs/structs/EPubSubChapterData.js +++ b/src/entities/EPubs/structs/EPubSubChapterData.js @@ -1,4 +1,4 @@ -import EPubChapterLikeData from './EPubChapterLikeData'; +import EPubChapterData from "./EPubChapterData"; let untitledSubChapterNum = 0; function _createSubChapterTitle() { @@ -7,7 +7,7 @@ function _createSubChapterTitle() { return `Untitled Sub-Chapter${chapterNum}`; } -class EPubSubChapterData extends EPubChapterLikeData { +class EPubSubChapterData extends EPubChapterData { constructor(subChapterLike, resetText) { super(subChapterLike, resetText, _createSubChapterTitle); } diff --git a/src/entities/EPubs/utils.js b/src/entities/EPubs/utils.js index abca7fd91..90d8e2e3c 100644 --- a/src/entities/EPubs/utils.js +++ b/src/entities/EPubs/utils.js @@ -37,7 +37,7 @@ export function getAllItemsInChapter(chapter) { } export function getAllItemsInChapters(chapters) { - return _.flatten(_.map(chapters, (chapter) => getAllItemsInChapter(chapter))); + return _.flatMap(chapters, ch => ch.items); } export function getAllImagesInChapter(chapter) { @@ -46,6 +46,9 @@ export function getAllImagesInChapter(chapter) { } export function getAllImagesInChapters(chapters) { - return _.flatten(_.map(chapters, (chapter) => getAllImagesInChapter(chapter))); + const filteredContents = _.flatMap(chapters, 'contents') + .filter(val => typeof val === 'object' && 'src' in val); // Keep only images + + return _.union(_.map(filteredContents, 'src')); // Extract 'src' and remove duplicates } diff --git a/src/screens/EPub/components/ChapterImage/ImageDescription.js b/src/screens/EPub/components/ChapterImage/ImageDescription.js index bca4d463b..849b4b807 100644 --- a/src/screens/EPub/components/ChapterImage/ImageDescription.js +++ b/src/screens/EPub/components/ChapterImage/ImageDescription.js @@ -1,4 +1,5 @@ import React from 'react' +import { _buildID } from 'utils'; import DescriptionText from '../DescriptionText'; function ImageDescription({ @@ -15,6 +16,7 @@ function ImageDescription({ { descriptions.map((item, index) => { + uurl.openNewTab(videoLink); + } return disabled ? null : ( @@ -68,15 +75,16 @@ function ImageWrapper({ > Choose Image - {/* */} + {videoLink && + } { showLink &&
          diff --git a/src/screens/EPub/components/ChapterImage/index.js b/src/screens/EPub/components/ChapterImage/index.js index 935dd9539..3fbafe63c 100644 --- a/src/screens/EPub/components/ChapterImage/index.js +++ b/src/screens/EPub/components/ChapterImage/index.js @@ -27,7 +27,7 @@ function ChapterImage({ images, dispatch }) { - const { alt, src, descriptions } = image; + const { alt, src, descriptions, timestamp, link } = image; const onSave = (newImage) => { if (onChooseImage) { @@ -36,7 +36,7 @@ function ChapterImage({ }; const handleImageChange = (imgLike) => { - onSave({ src, alt, descriptions, ...imgLike }); + onSave({ src, alt, descriptions, link, ...imgLike }); }; const onSrcChange = (val) => { @@ -81,6 +81,7 @@ function ChapterImage({ // console.log(newEpub); dispatch({ type: 'epub/setEPub', payload: newEpub }); } + return ( <> {image ? ( @@ -91,6 +92,8 @@ function ChapterImage({ epub={epub} id={id} imageAlt={alt} + videoLink={link} + timestamp={timestamp} chapter={epub.chapters[currChIndex]} onChooseImage={openImagePicker} onRemoveImage={onRemoveImage} diff --git a/src/screens/EPub/components/ImagePickerModal.js b/src/screens/EPub/components/ImagePickerModal.js index 768eea511..1b4eba7a4 100644 --- a/src/screens/EPub/components/ImagePickerModal.js +++ b/src/screens/EPub/components/ImagePickerModal.js @@ -4,11 +4,10 @@ import { CTImagePickerModal } from 'components'; function ImagePickerModal({ imgPickerData, dispatch, epub, ...playerData }) { const show = Boolean(imgPickerData); - if(!show) { + if (!show) { return null; } const { screenshots = [], chapterScreenshots = [] } = imgPickerData; - let tabs = [ { name: 'All Screenshots', diff --git a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js index 51dc44a55..387db637a 100644 --- a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js @@ -31,6 +31,7 @@ class EPubFileBuilder { this.data = parsedData; this.language = this.data.language; this.glossary = parsedData.glossary; + this.videoLinks = parsedData.videoLinks; } /** @@ -82,21 +83,34 @@ class EPubFileBuilder { `} ) + // eslint-disable-next-line no-console + console.log("toc navcontents", navContents); + const toc_xhtml = OEBPS_TOC_XHTML({ title: this.data.title, language: this.language, navContents }); + // eslint-disable-next-line no-console + console.log("add toc xhtml"); this.zip.addFile('OEBPS/toc.xhtml', toc_xhtml); } buildVisualTocXHTML(visualTOC) { let navContents = _.map(visualTOC, (ch, chIdx) => { return _.map(ch, (img) => { + // eslint-disable-next-line no-console + console.log(img); return `
          - ${img.alt} +
          ` }).join("\n") }).join("\n"); + + // eslint-disable-next-line no-console + console.log("vtoc navcontents", navContents); const toc_xhtml = OEBPS_TOC_XHTML({ title: this.data.title, language: this.language, navContents }); + // eslint-disable-next-line no-console + console.log("add visual toc xhtml", toc_xhtml); + this.zip.addFile('OEBPS/toc.xhtml', toc_xhtml); } buildTocNCX(chapters) { @@ -127,7 +141,7 @@ class EPubFileBuilder { } convertChapter(idx, chapter, chapterGlossary) { - const text = HTMLFileBuilder.convertChapter(idx, chapter, chapterGlossary, this.data.includeRawLatex); + const text = HTMLFileBuilder.convertChapter(idx, chapter, chapterGlossary, this.data.includeRawLatex, this.videoLinks); let content = dedent(`
          ${text} diff --git a/src/screens/EPub/controllers/file-builders/EPubParser.js b/src/screens/EPub/controllers/file-builders/EPubParser.js index e075f2d1e..9e78ab9b7 100644 --- a/src/screens/EPub/controllers/file-builders/EPubParser.js +++ b/src/screens/EPub/controllers/file-builders/EPubParser.js @@ -39,9 +39,7 @@ class EPubParser { this.data.cover = await this.parseContent(epubData.cover); this.data.includeRawLatex = options.includeRawLatex; - - // eslint-disable-next-line no-console - console.log("parsed data", this.data); + this.data.videoLinks = options.videoLinks; } getVisualTOC(chapters) { let visualTOC = _.map(chapters, (chapter) => { diff --git a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js index 9532661f2..ba0d69b36 100644 --- a/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/HTMLFileBuilder.js @@ -22,6 +22,7 @@ class HTMLFileBuilder { this.createLinks = createLinks; this.data = parsedData; this.glossary = parsedData.glossary + this.videoLinks = parsedData.videoLinks; } /** @@ -54,38 +55,33 @@ class HTMLFileBuilder { ].join("") return text }; - static convertImage(content) { - if (content.descriptions.length !== 0) { - let despId = _buildID(); - return [ - '
          ', - `\t${content.alt}`, - `\t
          ${html.markdown(content.descriptions.join("\n"))}
          `, - '
          ' - ].join('\n'); - } + static convertImage(content, videoLinks) { + let despId = _buildID(); return [ '
          ', - `\t${content.alt}`, + videoLinks && content.link && content.link !== "" ? `` : "", + `\t${content.alt}`, + videoLinks && content.link && content.link !== "" ? `` : "", + content.descriptions.length !== 0 ? `\t
          ${html.markdown(content.descriptions.join("\n"))}
          ` : "", '
          ' ].join('\n'); } - static convertContent(content, includeRawLatex) { + static convertContent(content, includeRawLatex, videoLinks) { if (epubIsText(content)) { return HTMLFileBuilder.convertText(content, includeRawLatex); } - return HTMLFileBuilder.convertImage(content); + return HTMLFileBuilder.convertImage(content, videoLinks); } - static convertChapter(idx, chapter, chapterGlossary, includeRawLatex = false) { + static convertChapter(idx, chapter, chapterGlossary, includeRawLatex = false, videoLinks = false) { let glossaryText = chapterGlossary ? glossaryToHTMLString(chapterGlossary) : ""; chapter.id = _buildID(); return [ // the first chapter has idx 0, but is chapter 1. Thus, we use idx+1 `\n

          ${idx + 1}: ${chapter.title}

          `, `
          `, - _.map(chapter.contents, (c) => HTMLFileBuilder.convertContent(c, includeRawLatex)).join("\n"), + _.map(chapter.contents, (c) => HTMLFileBuilder.convertContent(c, includeRawLatex, videoLinks)).join("\n"), `
          `, glossaryText ].join("\n\n"); @@ -95,7 +91,7 @@ class HTMLFileBuilder { const chapters = this.data.chapters return [ '
          ', - _.map(chapters, (ch, idx) => HTMLFileBuilder.convertChapter(idx, ch, this.data.chapterGlossary ? this.data.chapterGlossary[idx] : false, this.data.includeRawLatex,)).join("\n"), + _.map(chapters, (ch, idx) => HTMLFileBuilder.convertChapter(idx, ch, this.data.chapterGlossary ? this.data.chapterGlossary[idx] : false, this.data.includeRawLatex, this.videoLinks)).join("\n"), '
          ', ].join("\n"); } @@ -141,24 +137,12 @@ class HTMLFileBuilder { getIndexHTML() { const conversion = this.convertChapters(); - // eslint-disable-next-line no-console - console.log("conversion", conversion) let toc = ""; if (this.data.visualTOC) { toc = this.convertVisualTOC(); } else { toc = this.convertTOC(); } - // eslint-disable-next-line no-console - console.log("all vals", { - title: this.data.title, - navContents: toc, - content: conversion, - author: this.data.author, - cover: this.data.cover, - createLinks: this.createLinks, - visualTOC: this.data.visualTOC - }) return INDEX_HTML_LOCAL({ title: this.data.title, navContents: toc, diff --git a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js index 5f87e44de..00b96dce0 100644 --- a/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/LatexFileBuilder.js @@ -18,6 +18,7 @@ class LatexFileBuilder { async init(parsedData) { this.data = parsedData; this.glossary = parsedData.glossary; + this.videoLinks = parsedData.videoLinks; } /** @@ -84,7 +85,9 @@ class LatexFileBuilder { return [ `\\begin{figure}`, `\\centering`, + this.videoLinks && content.link && content.link !== "" ? `\\href{${content.link}}{` : "", `\\includegraphics[alt={${new_alt}}, width=.8\\textwidth]{${img_path}}`, + this.videoLinks && content.link && content.link !== "" ? `}` : "", captions, `\\end{figure}` ].join("\n") diff --git a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js index 71875da88..98ac73265 100644 --- a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js @@ -26,6 +26,7 @@ class PDFFileBuilder { this.data = parsedData; this.glossary = parsedData.glossary; this.includeRawLatex = parsedData.includeRawLatex; + this.videoLinks = parsedData.videoLinks; } /** @@ -95,11 +96,14 @@ class PDFFileBuilder { } } - convertImage({ src, descriptions, alt, height = 100, width = 100 }) { + convertImage({ src, descriptions, alt, height = 100, width = 100, link }) { const scale = this.max_text_width / width; const curr_y_loc = this.getYLoc(height); this.doc.addImage(src === "" ? placeholderImg : src, STYLE_SHEET.edgeMargin, curr_y_loc, width * scale, height * scale); - this.incrementYLoc(height * scale + STYLE_SHEET.image.imageAltGap); + if (this.videoLinks && link && link !== "") { + this.doc.link(STYLE_SHEET.edgeMargin, curr_y_loc, width * scale, height * scale, { url: link }); + } + this.incrementYLoc(height * scale); if (this.writeTextToPDF(alt, STYLE_SHEET.font.altText)) { this.incrementYLoc(STYLE_SHEET.image.AltDescGap); diff --git a/src/screens/EPub/model.js b/src/screens/EPub/model.js index ecdf829cb..8931d3c2b 100644 --- a/src/screens/EPub/model.js +++ b/src/screens/EPub/model.js @@ -27,7 +27,8 @@ const initState = { showFileSettings: false, showPrefSettings: false, showShortcuts: false, - images: null + images: null, + items: [] } const EPubModel = { namespace: 'epub', @@ -54,7 +55,7 @@ const EPubModel = { if (!payload.chapters) { payload.chapters = [] } - return { ...state, epub: payload, items, images: _.map(items, item => item?.image) }; + return { ...state, epub: payload, images: items }; }, setCurrChIndex(state, { payload }) { return { ...state, currChIndex: payload }; diff --git a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js index 8e18d28fd..3862c795a 100644 --- a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js +++ b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js @@ -80,7 +80,6 @@ function INoteChapter({ onSave: handleSaveImage(itemIdx), chapterScreenshots: epub.chapters[chIdx].allImagesWithIn }; - dispatch({ type: 'epub/setImgPickerData', payload: imgData }); } diff --git a/src/screens/EPub/views/ViewAndDownload/EPubPreview.js b/src/screens/EPub/views/ViewAndDownload/EPubPreview.js index d5b082640..8c4e8d6e3 100644 --- a/src/screens/EPub/views/ViewAndDownload/EPubPreview.js +++ b/src/screens/EPub/views/ViewAndDownload/EPubPreview.js @@ -15,7 +15,7 @@ function EPubPreview(props) { }, [epubData]) return ( - + } - label="Include Visual Table of Contents" + label="Automatically Invert Dark Images" /> } - label="Automatically Invert Dark Images" + label="Include Raw Latex" + /> + + } + label="Include Links to Video" + /> + + } + label="Include Visual Table of Contents" /> - - } - label="Include Raw Latex" - /> ); } diff --git a/src/screens/EPub/views/ViewAndDownload/index.js b/src/screens/EPub/views/ViewAndDownload/index.js index da49162a7..07bc5eb1e 100644 --- a/src/screens/EPub/views/ViewAndDownload/index.js +++ b/src/screens/EPub/views/ViewAndDownload/index.js @@ -17,7 +17,8 @@ function ViewAndDownload({ dispatch }) { invertColors: false, includeGlossary: true, includeRawLatex: false, - chapterGlossary: false + chapterGlossary: false, + videoLinks: true }); return ( diff --git a/src/screens/Watch/Utils/helpers.js b/src/screens/Watch/Utils/helpers.js index b64cfde7f..66af32722 100644 --- a/src/screens/Watch/Utils/helpers.js +++ b/src/screens/Watch/Utils/helpers.js @@ -78,7 +78,7 @@ export function prettierTimeStr(time, showMilliseconds = false) { // export function prettierTimeStr(str) { // if (typeof str !== 'string') return ''; - + // const strs = str.split(':'); // if (strs.length !== 3) return ''; // Ensure the input is in HH:MM:SS format @@ -119,3 +119,10 @@ export function getShareableURL(begin = 0) { return origin + pathname; } + +export function getShareableVideoURL(id, begin = 0) { + const { origin } = window.location; + const pathname = links.watch(id, { begin, from: 'sharedlink' }); + + return origin + pathname; +} diff --git a/src/utils/links.js b/src/utils/links.js index 7d98fa8d3..63edea875 100644 --- a/src/utils/links.js +++ b/src/utils/links.js @@ -39,7 +39,7 @@ export class ClassTranscribeLinks { const { redirect = window.location.href, method, - aspopup + aspopup } = config || {}; return `/sign-in${uurl.createSearch({ redirect, method, aspopup })}` } @@ -64,12 +64,12 @@ export class ClassTranscribeLinks { home() { return '/'; } - + /** * to `/search` */ search(query) { - return `/search${ uurl.createSearch({ q: query })}`; + return `/search${uurl.createSearch({ q: query })}`; } /** @@ -116,9 +116,9 @@ export class ClassTranscribeLinks { * @param {Object} params - search query */ watch(id, params = {}) { - if (params.begin) { + if (params.begin !== undefined) { params.begin = Math.floor(Number(params.begin)); - if (params.begin <= 0) { + if (params.begin < 0) { params.begin = undefined; } } @@ -130,8 +130,8 @@ export class ClassTranscribeLinks { * @param {String} tab - admin tab */ admin(tab = '') { - if (tab) tab = `/${ tab}`; - return `/admin${ tab}`; + if (tab) tab = `/${tab}`; + return `/admin${tab}`; } /** @@ -206,7 +206,7 @@ export class ClassTranscribeLinks { instMediaSettings(mediaId, tab) { return `/media-settings/${mediaId}${tab ? `/${tab}` : ''}`; } - + /** * to `/media-settings//epub` * @param {String} mediaId - media id @@ -245,19 +245,19 @@ export class ClassTranscribeLinks { notfound404() { return '/404'; } - + pgadmin() { return `${env.baseURL}/pgadmin/`; } - + rabbitmq() { return `${env.baseURL}/rabbitmq/`; } - + traefik() { return `${env.baseURL}/traefik/`; } - + swag() { return `${env.baseURL}/swag/`; } From 51195175c96f0fb6866d0cbe92469be7e38b9786 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 9 Apr 2025 15:35:49 -0500 Subject: [PATCH 09/13] refactored changing images to actually work --- .../CTImagePickerModal/ImagesTab.js | 10 +- src/entities/EPubs/utils.js | 2 +- .../EPub/components/ChapterImage/index.js | 10 +- .../EPub/components/ImagePickerModal.js | 2 + src/screens/EPub/model.js | 2 + src/screens/EPub/models/data_reducer.js | 973 +++++++++--------- .../EditINote/INoteEditor/INoteChapter.js | 12 +- 7 files changed, 514 insertions(+), 497 deletions(-) diff --git a/src/components/CTImagePickerModal/ImagesTab.js b/src/components/CTImagePickerModal/ImagesTab.js index 4ec717ad3..1895b735b 100644 --- a/src/components/CTImagePickerModal/ImagesTab.js +++ b/src/components/CTImagePickerModal/ImagesTab.js @@ -21,17 +21,17 @@ function ImagesTab(props) {
          {images.map(img => (
          setImgUrl(img.src)} + data-current={img === imgUrl} + onClick={() => setImgUrl(img)} role="listitem" > - Chapter Cover + Chapter Cover
          { - img.src === imgUrl + img === imgUrl && check_circle } diff --git a/src/entities/EPubs/utils.js b/src/entities/EPubs/utils.js index 90d8e2e3c..20b2490ef 100644 --- a/src/entities/EPubs/utils.js +++ b/src/entities/EPubs/utils.js @@ -37,7 +37,7 @@ export function getAllItemsInChapter(chapter) { } export function getAllItemsInChapters(chapters) { - return _.flatMap(chapters, ch => ch.items); + return _.flatMap(chapters.filter(ch => "items" in ch), ch => ch.items); } export function getAllImagesInChapter(chapter) { diff --git a/src/screens/EPub/components/ChapterImage/index.js b/src/screens/EPub/components/ChapterImage/index.js index 3fbafe63c..402598c6a 100644 --- a/src/screens/EPub/components/ChapterImage/index.js +++ b/src/screens/EPub/components/ChapterImage/index.js @@ -36,11 +36,15 @@ function ChapterImage({ }; const handleImageChange = (imgLike) => { - onSave({ src, alt, descriptions, link, ...imgLike }); + // eslint-disable-next-line no-console + console.log("imglike", imgLike); + onSave({ src, alt, descriptions, timestamp, link, ...imgLike }); }; const onSrcChange = (val) => { - if (val !== src) handleImageChange({ src: val }); + if (val !== src) { + handleImageChange({ src: val }); + } }; const onAltChange = (val) => { @@ -62,7 +66,7 @@ function ChapterImage({ const openImagePicker = () => { const imgData = { - screenshots: images, + screenshots: images.map(img => img.src), onSave: onSrcChange, defaultImage: src }; diff --git a/src/screens/EPub/components/ImagePickerModal.js b/src/screens/EPub/components/ImagePickerModal.js index 1b4eba7a4..0119e9424 100644 --- a/src/screens/EPub/components/ImagePickerModal.js +++ b/src/screens/EPub/components/ImagePickerModal.js @@ -8,6 +8,8 @@ function ImagePickerModal({ imgPickerData, dispatch, epub, ...playerData }) { return null; } const { screenshots = [], chapterScreenshots = [] } = imgPickerData; + // eslint-disable-next-line no-console + console.log("imgPickerData", imgPickerData) let tabs = [ { name: 'All Screenshots', diff --git a/src/screens/EPub/model.js b/src/screens/EPub/model.js index 8931d3c2b..94bbfcd6d 100644 --- a/src/screens/EPub/model.js +++ b/src/screens/EPub/model.js @@ -52,6 +52,8 @@ const EPubModel = { }, setEPub(state, { payload }) { const items = getAllItemsInChapters(payload.chapters); + console.log("setEPub items", items); + if (!payload.chapters) { payload.chapters = [] } diff --git a/src/screens/EPub/models/data_reducer.js b/src/screens/EPub/models/data_reducer.js index 60a3094c1..38ff8aae8 100644 --- a/src/screens/EPub/models/data_reducer.js +++ b/src/screens/EPub/models/data_reducer.js @@ -6,531 +6,532 @@ import { getAllItemsInChapters } from 'entities/EPubs/utils' /* eslint-disable no-console */ function insertSubChapter(chapter, subChapterIndex, subChapterLike) { - let newSubChapter = new EPubSubChapterData(subChapterLike); - newSubChapter = newSubChapter.__data__ ? newSubChapter.__data__ : newSubChapter.toObject(); + let newSubChapter = new EPubSubChapterData(subChapterLike); + newSubChapter = newSubChapter.__data__ ? newSubChapter.__data__ : newSubChapter.toObject(); - console.log(`Inserting subchapter at ${subChapterIndex}: `, subChapterLike); + console.log(`Inserting subchapter at ${subChapterIndex}: `, subChapterLike); - chapter.subChapters = [ - ...chapter.subChapters.slice(0, subChapterIndex), - newSubChapter, - ...chapter.subChapters.slice(subChapterIndex) - ]; + chapter.subChapters = [ + ...chapter.subChapters.slice(0, subChapterIndex), + newSubChapter, + ...chapter.subChapters.slice(subChapterIndex) + ]; - return newSubChapter; + return newSubChapter; } function removeSubChapter(chapter, subChapterIndex) { - console.log(`Removing chapter at ${subChapterIndex}`); - let subChapter = chapter.subChapters[subChapterIndex]; - chapter.subChapters = [ - ...chapter.subChapters.slice(0, subChapterIndex), - ...chapter.subChapters.slice(subChapterIndex + 1) - ]; - return subChapter; + console.log(`Removing chapter at ${subChapterIndex}`); + let subChapter = chapter.subChapters[subChapterIndex]; + chapter.subChapters = [ + ...chapter.subChapters.slice(0, subChapterIndex), + ...chapter.subChapters.slice(subChapterIndex + 1) + ]; + return subChapter; } function insertChapter(chapters, index, chapterLike, resetText = true) { - let newChapter = new EPubChapterData(chapterLike, resetText); - newChapter = newChapter.__data__ ? newChapter.__data__ : newChapter.toObject(); + let newChapter = new EPubChapterData(chapterLike, resetText); + newChapter = newChapter.__data__ ? newChapter.__data__ : newChapter.toObject(); - console.log(`Inserting chapter at ${index}: `, chapterLike); - console.log(`insertChapter`, newChapter); + console.log(`Inserting chapter at ${index}: `, chapterLike); + console.log(`insertChapter`, newChapter); - return [ - ...chapters.slice(0, index), - newChapter, - ...chapters.slice(index) - ]; + return [ + ...chapters.slice(0, index), + newChapter, + ...chapters.slice(index) + ]; } function removeChapter(chapters, index) { - console.log(`Removing chapter at ${index}`); - // let chapter = chapters[index]; - return [ - ...chapters.slice(0, index), - ...chapters.slice(index + 1) - ]; + console.log(`Removing chapter at ${index}`); + // let chapter = chapters[index]; + return [ + ...chapters.slice(0, index), + ...chapters.slice(index + 1) + ]; } function rebuildChapter(chapters, chapterIndex, chapterLike, resetText) { - // if there is such a chapter in the epub data - // update the subchapter item - console.log(`Rebuilding chapter ${chapterIndex}: `, chapterLike); - if (chapters[chapterIndex]) { - let toBuild = chapterLike || chapters[chapterIndex]; - const rebuilt = new EPubChapterData(toBuild, resetText).toObject(); - console.log(`Rebuilt chapter ${chapterIndex}: `, rebuilt); - chapters[chapterIndex] = rebuilt; - } + // if there is such a chapter in the epub data + // update the subchapter item + console.log(`Rebuilding chapter ${chapterIndex}: `, chapterLike); + if (chapters[chapterIndex]) { + let toBuild = chapterLike || chapters[chapterIndex]; + const rebuilt = new EPubChapterData(toBuild, resetText).toObject(); + console.log(`Rebuilt chapter ${chapterIndex}: `, rebuilt); + chapters[chapterIndex] = rebuilt; + } } function insertContentChapter(chapter, index, content) { - console.log(`Inserting chapter contents ${index}: `, content); - if (index >= chapter.contents.length) { - chapter.contents.push(content); - } else { - chapter.contents = [ - ...chapter.contents.slice(0, index), - content, - ...chapter.contents.slice(index) - ]; - } + console.log(`Inserting chapter contents ${index}: `, content); + if (index >= chapter.contents.length) { + chapter.contents.push(content); + } else { + chapter.contents = [ + ...chapter.contents.slice(0, index), + content, + ...chapter.contents.slice(index) + ]; + } } function appendChapterAsSubChapter(chapters, chapterIdx) { - let currChp = chapters[chapterIdx]; - let prevChp = chapters[chapterIdx - 1]; - - console.log(`Appending chapter ${chapterIdx} as subchapter`); - // if no items in curr chapter - // push its sub chapters to the prev chapter.subChapters - if (currChp?.items?.length === 0) { - prevChp.subChapters = _.concat(prevChp.subChapters, currChp.subChapters); - } - // otherwise, convert itself as a sub chapter - // and append to the prev chapter.subChapters along w/ its sub chapters - else { - prevChp.subChapters = _.concat( - prevChp.subChapters, - new EPubSubChapterData(currChp).toObject(), - currChp.subChapters - ); - } - rebuildChapter(chapters, chapterIdx - 1, null, false); + let currChp = chapters[chapterIdx]; + let prevChp = chapters[chapterIdx - 1]; + + console.log(`Appending chapter ${chapterIdx} as subchapter`); + // if no items in curr chapter + // push its sub chapters to the prev chapter.subChapters + if (currChp?.items?.length === 0) { + prevChp.subChapters = _.concat(prevChp.subChapters, currChp.subChapters); + } + // otherwise, convert itself as a sub chapter + // and append to the prev chapter.subChapters along w/ its sub chapters + else { + prevChp.subChapters = _.concat( + prevChp.subChapters, + new EPubSubChapterData(currChp).toObject(), + currChp.subChapters + ); + } + rebuildChapter(chapters, chapterIdx - 1, null, false); - // remove appended chapter - chapters = removeChapter(chapters, chapterIdx); + // remove appended chapter + chapters = removeChapter(chapters, chapterIdx); - // this.updateAll('Append to above sub-chapters', chapterIdx - 1); - return chapters; + // this.updateAll('Append to above sub-chapters', chapterIdx - 1); + return chapters; } function removeContent(chapter, predictor) { - if (typeof predictor === 'number') { - console.log(`Removing chapter contents at ${predictor}`); - if (predictor >= 0 && predictor < chapter.contents.length) { - chapter.contents = [ - ...chapter.contents.slice(0, predictor), - ...chapter.contents.slice(predictor + 1) - ]; - } - } else { - console.log(`Removing chapter contents: `, predictor); - let currIndex = _.findIndex(chapter.contents, (val) => val === predictor); - removeContent(chapter, currIndex); + if (typeof predictor === 'number') { + console.log(`Removing chapter contents at ${predictor}`); + if (predictor >= 0 && predictor < chapter.contents.length) { + chapter.contents = [ + ...chapter.contents.slice(0, predictor), + ...chapter.contents.slice(predictor + 1) + ]; } + } else { + console.log(`Removing chapter contents: `, predictor); + let currIndex = _.findIndex(chapter.contents, (val) => val === predictor); + removeContent(chapter, currIndex); + } } function rebuildSubChapter(chapters, chapterIndex, subChapterIndex, subChapterLike, resetText) { - let currChapter = chapters[chapterIndex]; - console.log(`Rebuilding subchapter ${chapterIndex}-${subChapterIndex}: `, subChapterLike); - if (currChapter) { - let subChapters = currChapter.subChapters; - // if there is such a subchapter in the epub data - // update the subchapter item - if (subChapters[subChapterIndex]) { - let toBuild = subChapterLike || subChapters[subChapterIndex]; - const rebuilt = new EPubSubChapterData(toBuild, resetText).toObject(); - console.log(`Rebuilt subchapter ${chapterIndex}-${subChapterIndex}: `, rebuilt); - subChapters[subChapterIndex] = rebuilt; - } + let currChapter = chapters[chapterIndex]; + console.log(`Rebuilding subchapter ${chapterIndex}-${subChapterIndex}: `, subChapterLike); + if (currChapter) { + let subChapters = currChapter.subChapters; + // if there is such a subchapter in the epub data + // update the subchapter item + if (subChapters[subChapterIndex]) { + let toBuild = subChapterLike || subChapters[subChapterIndex]; + const rebuilt = new EPubSubChapterData(toBuild, resetText).toObject(); + console.log(`Rebuilt subchapter ${chapterIndex}-${subChapterIndex}: `, rebuilt); + subChapters[subChapterIndex] = rebuilt; } + } } function nextStateOfChapters(chapters) { - console.log(`Building next state of chapters...`); - const items = getAllItemsInChapters(chapters); - return { chapters, items, images: _.map(items, item => item?.image) } + console.log(`Building next state of chapters...`); + const items = getAllItemsInChapters(chapters); + return { chapters, items, images: _.map(items, item => item?.image) } } export default { - subdivideChapter(state, { payload: { chapterIdx, itemIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx] - console.log(`Subdividing chapter at ${chapterIdx}: `, itemIdx); - // create a sub chp based on items after itemIdx in this chp - insertSubChapter(chapter, 0, { - items: _.slice(chapter.items, itemIdx) - }); - - // remove the items after itemIdx - chapter.items = _.slice(chapter.items, 0, itemIdx); - rebuildChapter(chapters, chapterIdx); - - // this.updateAll('Subdivide the chapter'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } - }, - splitChapterFromChaptersItems(state, { payload: { chapterIdx, itemIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx] - let subChapters = chapter.subChapters; - let items = _.slice(chapter.items, itemIdx, chapter.items.length); - console.log(`Splitting chapter from chapter items at ${chapterIdx}: `, itemIdx); - - // remove items and sub chapters from curr chapter - chapter.subChapters = []; - chapter.items = _.slice(chapter.items, 0, itemIdx); - rebuildChapter(chapters, chapterIdx); - - // insert the new chapter - const newChapters = insertChapter(chapters, chapterIdx + 1, { subChapters, items }); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - // this.updateAll('Split chapter', chapterIdx + 1); - }, - undoSubdivideChapter(state, { payload: { chapterIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx] - console.log(`Undoing subdivide chapter at ${chapterIdx}`); - // push the items in the first sub chapter - // to this chapter's items - chapter.items = _.concat(chapter.items, chapter.subChapters[0].items); - // remove this sub chapter - removeSubChapter(chapter, 0); - - rebuildChapter(chapters, chapterIdx); - // this.updateAll('Undo subdivide the chapter'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } - }, - splitSubChapter(state, { payload: { chapterIdx, subChapterIdx, itemIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx]; - const subChapter = chapter.subChapters[subChapterIdx]; - console.log(`Splitting subchapter at ${chapterIdx}-${subChapterIdx}: `, itemIdx); - - // create a new sub chp based on the items after itemIdx - insertSubChapter(chapter, subChapterIdx + 1, { - items: _.slice(subChapter.items, itemIdx) - }); - // remove relocated items from curr sub chp - subChapter.items = _.slice(subChapter.items, 0, itemIdx); - - rebuildSubChapter(chapters, chapterIdx, subChapterIdx); - rebuildChapter(chapters, chapterIdx, null, false); - // this.updateAll('Subdivide the chapter'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } - }, - undoSplitSubChapter(state, { payload: { chapterIdx, subChapterIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx]; - let currSubChp = chapter.subChapters[subChapterIdx]; - let prevSubChp = chapter.subChapters[subChapterIdx - 1]; - - console.log(`Undoing split subchapter at ${chapterIdx}-${subChapterIdx}`); - // push curr sub chp's items to the prev sub chp - prevSubChp.items = _.concat(prevSubChp.items, currSubChp.items); - // then delete the curr sub chp - removeSubChapter(chapter, subChapterIdx); - - rebuildSubChapter(chapters, chapterIdx, subChapterIdx - 1); - rebuildChapter(chapters, chapterIdx, null, false); - // this.updateAll('Undo subdivide the chapter'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } - }, - splitChapterFromSubChapter(state, { payload: { chapterIdx, subChapterIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx]; - const subChapter = chapter.subChapters[subChapterIdx]; - - console.log(`Splitting chapter from subchapter at ${chapterIdx}-${subChapterIdx}`); - // create a new chp w/ items after itemIdx of this subChapter.items - // and sub chps of the rest of curr chp - const newChapters = insertChapter(chapters, chapterIdx + 1, { - ...subChapter, - image: undefined, - subChapters: _.slice(chapter.subChapters, subChapterIdx + 1) - }); - - // remove the subchps after subChapterIdx - chapter.subChapters = _.slice(chapter.subChapters, 0, subChapterIdx); - - rebuildChapter(chapters, chapterIdx, null, false); - // this.updateAll('Split chapters', chapterIdx + 1); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - }, - splitChapterFromSubChaptersItems(state, { payload: { chapterIdx, subChapterIdx, itemIdx } }) { - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx]; - const subChapter = chapter.subChapters[subChapterIdx]; - - // create a new chp w/ items after itemIdx of this subChapter.items - // and sub chps of the rest of curr chp - - console.log(`Splitting chapter from subchapter items at ${chapterIdx}-${subChapterIdx}`); - const newChapters = insertChapter(chapters, chapterIdx + 1, { - items: _.slice(subChapter.items, itemIdx), - subChapters: _.slice(chapter.subChapters, subChapterIdx + 1) - }); - // remove relocated items/subchps - subChapter.items = _.slice(subChapter.items, 0, itemIdx); - chapter.subChapters = _.slice(chapter.subChapters, 0, subChapterIdx + 1); - - rebuildSubChapter(chapters, chapterIdx, subChapterIdx); - rebuildChapter(chapters, chapterIdx, null, false); - // this.updateAll('Split chapters', chapterIdx + 1); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - }, - undoSplitChapter(state, { payload: { chapterIdx } }) { - const chapters = state.epub.chapters; - const currChp = chapters[chapterIdx]; - const prevChp = chapters[chapterIdx - 1]; - console.log(`Undoing split-chapter at ${chapterIdx}`); - // if the prev chapter has sub-chapters - // append curr chapter and its sub-chapters to prev's sub-chapters - let newChapters = null; - if (prevChp?.subChapters?.length > 0) { - newChapters = appendChapterAsSubChapter(chapters, chapterIdx) - } else { - // otherwise, append items & sub-chapters of this chapter to the prev - prevChp.items = _.concat(prevChp?.items, currChp?.items); - prevChp.subChapters = currChp?.subChapters; - // remove combined chapter - newChapters = removeChapter(chapters, chapterIdx); - } - rebuildChapter(chapters, chapterIdx - 1); - // this.updateAll('Undo split chapters', chapterIdx - 1); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - }, - sliceChapter(state, { payload: { chapterIdx, itemIdx } }) { - console.log(`Splitting Chapter ${chapterIdx} at ItemIdx ${itemIdx}`); - const chapters = state.epub.chapters; - const chapter = chapters[chapterIdx] - // remove contents from current chapter and add to new chapter - let contents = _.slice(chapter.contents, itemIdx, chapter.contents.length); - chapter.contents = _.slice(chapter.contents, 0, itemIdx); - - // insert the new chapter - const newChapters = insertChapter(chapters, chapterIdx + 1, { contents }, false); - - if (newChapters[chapterIdx].timemerge > '00:00:00') { - newChapters[chapterIdx + 1].end = newChapters[chapterIdx].end; - newChapters[chapterIdx].end = newChapters[chapterIdx].timemerge; - newChapters[chapterIdx + 1].start = newChapters[chapterIdx].timemerge; - newChapters[chapterIdx].timemerge = '00:00:00'; - } + subdivideChapter(state, { payload: { chapterIdx, itemIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx] + console.log(`Subdividing chapter at ${chapterIdx}: `, itemIdx); + // create a sub chp based on items after itemIdx in this chp + insertSubChapter(chapter, 0, { + items: _.slice(chapter.items, itemIdx) + }); + + // remove the items after itemIdx + chapter.items = _.slice(chapter.items, 0, itemIdx); + rebuildChapter(chapters, chapterIdx); + + // this.updateAll('Subdivide the chapter'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } + }, + splitChapterFromChaptersItems(state, { payload: { chapterIdx, itemIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx] + let subChapters = chapter.subChapters; + let items = _.slice(chapter.items, itemIdx, chapter.items.length); + console.log(`Splitting chapter from chapter items at ${chapterIdx}: `, itemIdx); + + // remove items and sub chapters from curr chapter + chapter.subChapters = []; + chapter.items = _.slice(chapter.items, 0, itemIdx); + rebuildChapter(chapters, chapterIdx); + + // insert the new chapter + const newChapters = insertChapter(chapters, chapterIdx + 1, { subChapters, items }); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + // this.updateAll('Split chapter', chapterIdx + 1); + }, + undoSubdivideChapter(state, { payload: { chapterIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx] + console.log(`Undoing subdivide chapter at ${chapterIdx}`); + // push the items in the first sub chapter + // to this chapter's items + chapter.items = _.concat(chapter.items, chapter.subChapters[0].items); + // remove this sub chapter + removeSubChapter(chapter, 0); + + rebuildChapter(chapters, chapterIdx); + // this.updateAll('Undo subdivide the chapter'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } + }, + splitSubChapter(state, { payload: { chapterIdx, subChapterIdx, itemIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx]; + const subChapter = chapter.subChapters[subChapterIdx]; + console.log(`Splitting subchapter at ${chapterIdx}-${subChapterIdx}: `, itemIdx); + + // create a new sub chp based on the items after itemIdx + insertSubChapter(chapter, subChapterIdx + 1, { + items: _.slice(subChapter.items, itemIdx) + }); + // remove relocated items from curr sub chp + subChapter.items = _.slice(subChapter.items, 0, itemIdx); + + rebuildSubChapter(chapters, chapterIdx, subChapterIdx); + rebuildChapter(chapters, chapterIdx, null, false); + // this.updateAll('Subdivide the chapter'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } + }, + undoSplitSubChapter(state, { payload: { chapterIdx, subChapterIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx]; + let currSubChp = chapter.subChapters[subChapterIdx]; + let prevSubChp = chapter.subChapters[subChapterIdx - 1]; + + console.log(`Undoing split subchapter at ${chapterIdx}-${subChapterIdx}`); + // push curr sub chp's items to the prev sub chp + prevSubChp.items = _.concat(prevSubChp.items, currSubChp.items); + // then delete the curr sub chp + removeSubChapter(chapter, subChapterIdx); + + rebuildSubChapter(chapters, chapterIdx, subChapterIdx - 1); + rebuildChapter(chapters, chapterIdx, null, false); + // this.updateAll('Undo subdivide the chapter'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } } + }, + splitChapterFromSubChapter(state, { payload: { chapterIdx, subChapterIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx]; + const subChapter = chapter.subChapters[subChapterIdx]; + + console.log(`Splitting chapter from subchapter at ${chapterIdx}-${subChapterIdx}`); + // create a new chp w/ items after itemIdx of this subChapter.items + // and sub chps of the rest of curr chp + const newChapters = insertChapter(chapters, chapterIdx + 1, { + ...subChapter, + image: undefined, + subChapters: _.slice(chapter.subChapters, subChapterIdx + 1) + }); + + // remove the subchps after subChapterIdx + chapter.subChapters = _.slice(chapter.subChapters, 0, subChapterIdx); + + rebuildChapter(chapters, chapterIdx, null, false); + // this.updateAll('Split chapters', chapterIdx + 1); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + }, + splitChapterFromSubChaptersItems(state, { payload: { chapterIdx, subChapterIdx, itemIdx } }) { + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx]; + const subChapter = chapter.subChapters[subChapterIdx]; + + // create a new chp w/ items after itemIdx of this subChapter.items + // and sub chps of the rest of curr chp + + console.log(`Splitting chapter from subchapter items at ${chapterIdx}-${subChapterIdx}`); + const newChapters = insertChapter(chapters, chapterIdx + 1, { + items: _.slice(subChapter.items, itemIdx), + subChapters: _.slice(chapter.subChapters, subChapterIdx + 1) + }); + // remove relocated items/subchps + subChapter.items = _.slice(subChapter.items, 0, itemIdx); + chapter.subChapters = _.slice(chapter.subChapters, 0, subChapterIdx + 1); + + rebuildSubChapter(chapters, chapterIdx, subChapterIdx); + rebuildChapter(chapters, chapterIdx, null, false); + // this.updateAll('Split chapters', chapterIdx + 1); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + }, + undoSplitChapter(state, { payload: { chapterIdx } }) { + const chapters = state.epub.chapters; + const currChp = chapters[chapterIdx]; + const prevChp = chapters[chapterIdx - 1]; + console.log(`Undoing split-chapter at ${chapterIdx}`); + // if the prev chapter has sub-chapters + // append curr chapter and its sub-chapters to prev's sub-chapters + let newChapters = null; + if (prevChp?.subChapters?.length > 0) { + newChapters = appendChapterAsSubChapter(chapters, chapterIdx) + } else { + // otherwise, append items & sub-chapters of this chapter to the prev + prevChp.items = _.concat(prevChp?.items, currChp?.items); + prevChp.subChapters = currChp?.subChapters; + // remove combined chapter + newChapters = removeChapter(chapters, chapterIdx); + } + rebuildChapter(chapters, chapterIdx - 1); + // this.updateAll('Undo split chapters', chapterIdx - 1); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + }, + sliceChapter(state, { payload: { chapterIdx, itemIdx } }) { + console.log(`Splitting Chapter ${chapterIdx} at ItemIdx ${itemIdx}`); + const chapters = state.epub.chapters; + const chapter = chapters[chapterIdx] + // remove contents from current chapter and add to new chapter + let contents = _.slice(chapter.contents, itemIdx, chapter.contents.length); + chapter.contents = _.slice(chapter.contents, 0, itemIdx); + + // insert the new chapter + const newChapters = insertChapter(chapters, chapterIdx + 1, { contents }, false); + + if (newChapters[chapterIdx].timemerge > '00:00:00') { + newChapters[chapterIdx + 1].end = newChapters[chapterIdx].end; + newChapters[chapterIdx].end = newChapters[chapterIdx].timemerge; + newChapters[chapterIdx + 1].start = newChapters[chapterIdx].timemerge; + newChapters[chapterIdx].timemerge = '00:00:00'; + } - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - }, - mergeChapter(state, { payload: { chapterIdx } }) { - console.log(`Merging Contents of Chapters ${chapterIdx - 1} and ${chapterIdx}`); - const chapters = state.epub.chapters; - const currChp = chapters[chapterIdx]; - const prevChp = chapters[chapterIdx - 1]; - - // TODO account for sub chapters - prevChp.contents = _.concat(prevChp?.contents, currChp?.contents); - // prevChp.contents - if (state.epub.chapters[chapterIdx].end > '00:00:00') { - state.epub.chapters[chapterIdx - 1].timemerge = state.epub.chapters[chapterIdx].start; - state.epub.chapters[chapterIdx - 1].end = state.epub.chapters[chapterIdx].end; - } - let newChapters = removeChapter(chapters, chapterIdx); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; - }, - appendChapterAsSubChapter(state, { payload: { chapterIdx } }) { - console.log(`Appending chapter ${chapterIdx} as subchapter`); - const chapters = state.epub.chapters; - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(appendChapterAsSubChapter(chapters, chapterIdx)) } }; - }, - saveSubChapterTitle(state, { payload: { chapterIdx, subChapterIdx, value } }) { - console.log(`Saving chapter ${chapterIdx} subchapter ${subChapterIdx} title`, value); - const chapters = state.epub.chapters; - if (chapters?.[chapterIdx]?.subChapters[subChapterIdx]) { - chapters[chapterIdx].subChapters[subChapterIdx].title = value; - } - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, - saveChapterTitle(state, { payload: { chapterIdx, value } }) { - console.log(`Saving chapter ${chapterIdx} title`, value); - const chapters = state.epub.chapters; - chapters[chapterIdx].title = value; - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, - splitChaptersByScreenshots(state, { payload: { wc } }) { // Enforces Word Count - console.log(`Splitting chapters by screenshots`); - const new_items = []; // duplicating some sentences (sentences with less than wc words) - // min word count that each chapter should have - const default_word_count = 25; - let min_word_count = wc; - if (wc === "") { // if there was no input, set wc min wc to default - min_word_count = default_word_count; - } - // find total word count in i-note and make sure user input is not larger than total wc - const total_word_count = state.items.reduce((wordCount, a) => { - if (a === undefined || a === null) { - return wordCount; - } - return wordCount + a.text.split(' ').length; - }, 0); - if (min_word_count > total_word_count) { - min_word_count = default_word_count; - } - // loop through chapters and enforce minimum wc - (state.items).forEach((elem) => { - if (elem !== undefined && elem !== null) { - if (new_items.length !== 0) { - const oldelem = new_items.pop(); - let words = (oldelem.text).split(' ').length; - if (words < min_word_count) { - // append shorter text to previous chapter - oldelem.text += " "; - oldelem.text += elem.text; - oldelem.end = elem.end; - new_items.push(oldelem); - } - else { - new_items.push(oldelem); - new_items.push(elem) - } - } else { - new_items.push(elem); - } - } - }); - // makes sure the first element also has a min of min_word_count words - const last_elem = new_items.pop(); - let words = (last_elem.text).split(' ').length; - if (words !== 0) { - if (words > min_word_count) { - new_items.push(last_elem); - } - else { - const oldelem = new_items.pop(); - oldelem.text += " "; - oldelem.text += last_elem.text; - oldelem.end = last_elem.end; - new_items.push(oldelem); - } + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + }, + mergeChapter(state, { payload: { chapterIdx } }) { + console.log(`Merging Contents of Chapters ${chapterIdx - 1} and ${chapterIdx}`); + const chapters = state.epub.chapters; + const currChp = chapters[chapterIdx]; + const prevChp = chapters[chapterIdx - 1]; + + // TODO account for sub chapters + prevChp.contents = _.concat(prevChp?.contents, currChp?.contents); + // prevChp.contents + if (state.epub.chapters[chapterIdx].end > '00:00:00') { + state.epub.chapters[chapterIdx - 1].timemerge = state.epub.chapters[chapterIdx].start; + state.epub.chapters[chapterIdx - 1].end = state.epub.chapters[chapterIdx].end; + } + let newChapters = removeChapter(chapters, chapterIdx); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(newChapters) } }; + }, + appendChapterAsSubChapter(state, { payload: { chapterIdx } }) { + console.log(`Appending chapter ${chapterIdx} as subchapter`); + const chapters = state.epub.chapters; + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(appendChapterAsSubChapter(chapters, chapterIdx)) } }; + }, + saveSubChapterTitle(state, { payload: { chapterIdx, subChapterIdx, value } }) { + console.log(`Saving chapter ${chapterIdx} subchapter ${subChapterIdx} title`, value); + const chapters = state.epub.chapters; + if (chapters?.[chapterIdx]?.subChapters[subChapterIdx]) { + chapters[chapterIdx].subChapters[subChapterIdx].title = value; + } + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, + saveChapterTitle(state, { payload: { chapterIdx, value } }) { + console.log(`Saving chapter ${chapterIdx} title`, value); + const chapters = state.epub.chapters; + chapters[chapterIdx].title = value; + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, + splitChaptersByScreenshots(state, { payload: { wc } }) { // Enforces Word Count + console.log(`Splitting chapters by screenshots`); + const new_items = []; // duplicating some sentences (sentences with less than wc words) + // min word count that each chapter should have + const default_word_count = 25; + let min_word_count = wc; + if (wc === "") { // if there was no input, set wc min wc to default + min_word_count = default_word_count; + } + // find total word count in i-note and make sure user input is not larger than total wc + const total_word_count = state.items.reduce((wordCount, a) => { + if (a === undefined || a === null) { + return wordCount; + } + return wordCount + a.text.split(' ').length; + }, 0); + if (min_word_count > total_word_count) { + min_word_count = default_word_count; + } + // loop through chapters and enforce minimum wc + (state.items).forEach((elem) => { + if (elem !== undefined && elem !== null) { + if (new_items.length !== 0) { + const oldelem = new_items.pop(); + let words = (oldelem.text).split(' ').length; + if (words < min_word_count) { + // append shorter text to previous chapter + oldelem.text += " "; + oldelem.text += elem.text; + oldelem.end = elem.end; + new_items.push(oldelem); + } + else { + new_items.push(oldelem); + new_items.push(elem) + } + } else { + new_items.push(elem); } - state.items = new_items; - let splitChapters = _.map(new_items, (data) => { - if (data === undefined || data === null) { - return null; - } - - return new EPubChapterData({ + } + }); + // makes sure the first element also has a min of min_word_count words + const last_elem = new_items.pop(); + let words = (last_elem.text).split(' ').length; + if (words !== 0) { + if (words > min_word_count) { + new_items.push(last_elem); + } + else { + const oldelem = new_items.pop(); + oldelem.text += " "; + oldelem.text += last_elem.text; + oldelem.end = last_elem.end; + new_items.push(oldelem); + } + } + state.items = new_items; + let splitChapters = _.map(new_items, (data) => { + if (data === undefined || data === null) { + return null; + } + + return new EPubChapterData({ + items: [data], + title: data.title, + }).toObject(); + }); + splitChapters = _.compact(splitChapters); + + /* + let splitChapters = _.map( + new_items, + (data, idx) => + new EPubChapterData({ items: [data], title: data.title, - }).toObject(); - }); - splitChapters = _.compact(splitChapters); - - /* - let splitChapters = _.map( - new_items, - (data, idx) => - new EPubChapterData({ - items: [data], - title: data.title, - }).toObject(), - ); */ - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(splitChapters) } }; - }, - resetToDefaultChapters(state) { - console.log(`Resetting to default chapters`); - const defaultChapters = EPubData.__buildEPubDataFromArray(state.items); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(defaultChapters) }, currChIndex: 0 }; - }, - insertChapterContent(state, { payload: { type = 'text', contentIdx, subChapterIdx, value } }) { - if (type === 'image') { - value = new EPubImageData(value).toObject(); - } - const chapters = state.epub.chapters; + }).toObject(), + ); */ + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(splitChapters) } }; + }, + resetToDefaultChapters(state) { + console.log(`Resetting to default chapters`); + const defaultChapters = EPubData.__buildEPubDataFromArray(state.items); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(defaultChapters) }, currChIndex: 0 }; + }, + insertChapterContent(state, { payload: { type = 'text', contentIdx, subChapterIdx, value } }) { + if (type === 'image') { + value = new EPubImageData(value).toObject(); + } + const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - console.log(`Inserting chapter ${state.currChIndex} content: `, value); - insertContentChapter(chapters[state.currChIndex], contentIdx, value); - } else { - console.log(`Inserting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); - insertContentChapter(chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx], contentIdx, value); - } - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - // this.updateAll('Insert chapter content'); - }, - insertChapterContentAtChapterIdx(state, { payload: { type = 'text', contentIdx, chapterIdx, subChapterIdx, value } }) { - if (type === 'image') { - value = new EPubImageData(value).toObject(); - } - const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - console.log(`Inserting chapter ${state.currChIndex} content: `, value); - insertContentChapter(chapters[chapterIdx], contentIdx, value); - } else { - console.log(`Inserting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); - insertContentChapter(chapters?.[chapterIdx]?.subChapters?.[subChapterIdx], contentIdx, value); - } - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - // this.updateAll('Insert chapter content'); - }, - setChapterContent(state, { payload: { type = 'text', contentIdx, subChapterIdx, value } }) { - if (type === 'image') { - value = new EPubImageData(value).toObject(); - } - const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - const chapter = chapters[state.currChIndex]; - if (type === 'condition') { - chapter.condition = value; - console.log(`Setting chapter ${state.currChIndex} condition: `, value); - } else { - chapter.contents[contentIdx] = value; - console.log(`Setting chapter ${state.currChIndex} content: `, value); - } - } else if (chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx]) { - chapters[state.currChIndex].subChapters[subChapterIdx] = value - console.log(`Setting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); - } - // this.updateAll('Update the chapter content'); - // this.__feed(); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, - - setChapterContentAtChapterIdx(state, { payload: { type = 'text', contentIdx, chapterIdx, subChapterIdx, value } }) { - if (type === 'image') { - value = new EPubImageData(value).toObject(); - } - const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - const chapter = chapters[chapterIdx]; - if (type === 'condition') { - chapter.condition = value; - console.log(`Setting chapter ${state.currChIndex} condition: `, value); - } else { - chapter.contents[contentIdx] = value; - console.log(`Setting chapter ${state.currChIndex} content: `, value); - } - } else if (chapters?.[chapterIdx]?.subChapters?.[subChapterIdx]) { - chapters[chapterIdx].subChapters[subChapterIdx] = value - console.log(`Setting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); - } - // this.updateAll('Update the chapter content'); - // this.__feed(); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, - // eslint-disable-next-line no-unused-vars - removeChapterContent(state, { payload: { _type = 'text', contentIdx, subChapterIdx } }) { - const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - const chapter = chapters[state.currChIndex]; - console.log(`Removing chapter ${state.currChIndex} content`); - removeContent(chapter, contentIdx); - } else if (chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx]) { - console.log(`Removing chapter ${state.currChIndex} subchapter ${subChapterIdx} content`); - removeContent(chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx], contentIdx) - } - // this.updateAll('Remove the chapter content'); - // this.__feed('Removed.'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, - removeChapterContentAtChapterIdx(state, { payload: { contentIdx, chapterIdx, subChapterIdx } }) { - const chapters = state.epub.chapters; - if (subChapterIdx === undefined) { - const chapter = chapters[chapterIdx]; - console.log(`Removing chapter ${chapterIdx} content`); - removeContent(chapter, contentIdx); - } else if (chapters?.[chapterIdx]?.subChapters?.[subChapterIdx]) { - console.log(`Removing chapter ${chapterIdx} subchapter ${subChapterIdx} content`); - removeContent(chapters?.[chapterIdx]?.subChapters?.[subChapterIdx], contentIdx) - } - // this.updateAll('Remove the chapter content'); - // this.__feed('Removed.'); - return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; - }, + if (subChapterIdx === undefined) { + console.log(`Inserting chapter ${state.currChIndex} content: `, value); + insertContentChapter(chapters[state.currChIndex], contentIdx, value); + } else { + console.log(`Inserting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); + insertContentChapter(chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx], contentIdx, value); + } + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + // this.updateAll('Insert chapter content'); + }, + insertChapterContentAtChapterIdx(state, { payload: { type = 'text', contentIdx, chapterIdx, subChapterIdx, value } }) { + if (type === 'image') { + value = new EPubImageData(value).toObject(); + } + const chapters = state.epub.chapters; + if (subChapterIdx === undefined) { + console.log(`Inserting chapter ${state.currChIndex} content: `, value); + insertContentChapter(chapters[chapterIdx], contentIdx, value); + } else { + console.log(`Inserting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); + insertContentChapter(chapters?.[chapterIdx]?.subChapters?.[subChapterIdx], contentIdx, value); + } + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + // this.updateAll('Insert chapter content'); + }, + setChapterContent(state, { payload: { type = 'text', contentIdx, subChapterIdx, value } }) { + if (type === 'image') { + value = new EPubImageData(value).toObject(); + } + const chapters = state.epub.chapters; + if (subChapterIdx === undefined) { + const chapter = chapters[state.currChIndex]; + if (type === 'condition') { + chapter.condition = value; + console.log(`Setting chapter ${state.currChIndex} condition: `, value); + } else { + chapter.contents[contentIdx] = value; + console.log(`Setting chapter ${state.currChIndex} content: `, value); + } + } else if (chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx]) { + chapters[state.currChIndex].subChapters[subChapterIdx] = value + console.log(`Setting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); + } + // this.updateAll('Update the chapter content'); + // this.__feed(); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, + + setChapterContentAtChapterIdx(state, { payload: { type = 'text', contentIdx, chapterIdx, subChapterIdx, value } }) { + if (type === 'image') { + value = new EPubImageData(value).toObject(); + } + // console.log("setchcontent", value); + const chapters = state.epub.chapters; + if (subChapterIdx === undefined) { + const chapter = chapters[chapterIdx]; + if (type === 'condition') { + chapter.condition = value; + console.log(`Setting chapter ${state.currChIndex} condition: `, value); + } else { + chapter.contents[contentIdx] = value; + console.log(`Setting chapter ${state.currChIndex} content: `, value); + } + } else if (chapters?.[chapterIdx]?.subChapters?.[subChapterIdx]) { + chapters[chapterIdx].subChapters[subChapterIdx] = value + console.log(`Setting chapter ${state.currChIndex} subchapter ${subChapterIdx} content: `, value); + } + // this.updateAll('Update the chapter content'); + // this.__feed(); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, + // eslint-disable-next-line no-unused-vars + removeChapterContent(state, { payload: { _type = 'text', contentIdx, subChapterIdx } }) { + const chapters = state.epub.chapters; + if (subChapterIdx === undefined) { + const chapter = chapters[state.currChIndex]; + console.log(`Removing chapter ${state.currChIndex} content`); + removeContent(chapter, contentIdx); + } else if (chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx]) { + console.log(`Removing chapter ${state.currChIndex} subchapter ${subChapterIdx} content`); + removeContent(chapters?.[state.currChIndex]?.subChapters?.[subChapterIdx], contentIdx) + } + // this.updateAll('Remove the chapter content'); + // this.__feed('Removed.'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, + removeChapterContentAtChapterIdx(state, { payload: { contentIdx, chapterIdx, subChapterIdx } }) { + const chapters = state.epub.chapters; + if (subChapterIdx === undefined) { + const chapter = chapters[chapterIdx]; + console.log(`Removing chapter ${chapterIdx} content`); + removeContent(chapter, contentIdx); + } else if (chapters?.[chapterIdx]?.subChapters?.[subChapterIdx]) { + console.log(`Removing chapter ${chapterIdx} subchapter ${subChapterIdx} content`); + removeContent(chapters?.[chapterIdx]?.subChapters?.[subChapterIdx], contentIdx) + } + // this.updateAll('Remove the chapter content'); + // this.__feed('Removed.'); + return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; + }, } /* __feed(mesg = 'Saved.') { diff --git a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js index 3862c795a..1f24e8884 100644 --- a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js +++ b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js @@ -68,18 +68,23 @@ function INoteChapter({ const handleSaveImage = (itemIdx) => (val) => { - let imageval = new EPubImageData(val).toObject(); + const val_obj = images.filter(img => img.src === val)[0]; + let imageval = new EPubImageData(val_obj || val).toObject(); if (typeof onInsert === 'function' && imageval) { onInsert(itemIdx)(imageval); } }; const handleOpenImgPicker = (itemIdx) => { + // eslint-disable-next-line no-console + console.log("screenshots", images); const imgData = { - screenshots: images, + screenshots: images.map(img => img.src), onSave: handleSaveImage(itemIdx), chapterScreenshots: epub.chapters[chIdx].allImagesWithIn }; + // eslint-disable-next-line no-console + console.log("imgData", imgData); dispatch({ type: 'epub/setImgPickerData', payload: imgData }); } @@ -191,6 +196,9 @@ function INoteChapter({ // Chapter Image Functions const onImageChange = (index) => (val) => { + // const val_obj = typeof val === 'string' ? images.filter(img => img.src === val)[0] : undefined; + // eslint-disable-next-line no-console + console.log("onimgchange", val); dispatch({ type: 'epub/updateEpubData', payload: { action: 'setChapterContentAtChapterIdx', payload: { chapterIdx: chIdx, contentIdx: index, value: val, type: 'image' } From 4b04f21bbec80285d167c47c4ddbf9a8bccf7199 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 9 Apr 2025 15:43:22 -0500 Subject: [PATCH 10/13] removed logging --- .../EPub/components/ChapterImage/index.js | 2 -- src/screens/EPub/components/ImagePickerModal.js | 2 -- .../controllers/file-builders/EPubFileBuilder.js | 9 --------- src/screens/EPub/model.js | 1 - .../views/EditINote/INoteEditor/INoteChapter.js | 7 ------- src/utils/cthttp/entities/Captions.js | 16 ++++++++-------- 6 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/screens/EPub/components/ChapterImage/index.js b/src/screens/EPub/components/ChapterImage/index.js index 402598c6a..331828f97 100644 --- a/src/screens/EPub/components/ChapterImage/index.js +++ b/src/screens/EPub/components/ChapterImage/index.js @@ -36,8 +36,6 @@ function ChapterImage({ }; const handleImageChange = (imgLike) => { - // eslint-disable-next-line no-console - console.log("imglike", imgLike); onSave({ src, alt, descriptions, timestamp, link, ...imgLike }); }; diff --git a/src/screens/EPub/components/ImagePickerModal.js b/src/screens/EPub/components/ImagePickerModal.js index 0119e9424..1b4eba7a4 100644 --- a/src/screens/EPub/components/ImagePickerModal.js +++ b/src/screens/EPub/components/ImagePickerModal.js @@ -8,8 +8,6 @@ function ImagePickerModal({ imgPickerData, dispatch, epub, ...playerData }) { return null; } const { screenshots = [], chapterScreenshots = [] } = imgPickerData; - // eslint-disable-next-line no-console - console.log("imgPickerData", imgPickerData) let tabs = [ { name: 'All Screenshots', diff --git a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js index 387db637a..2962bded9 100644 --- a/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/EPubFileBuilder.js @@ -83,12 +83,8 @@ class EPubFileBuilder { `} ) - // eslint-disable-next-line no-console - console.log("toc navcontents", navContents); const toc_xhtml = OEBPS_TOC_XHTML({ title: this.data.title, language: this.language, navContents }); - // eslint-disable-next-line no-console - console.log("add toc xhtml"); this.zip.addFile('OEBPS/toc.xhtml', toc_xhtml); } @@ -96,7 +92,6 @@ class EPubFileBuilder { let navContents = _.map(visualTOC, (ch, chIdx) => { return _.map(ch, (img) => { // eslint-disable-next-line no-console - console.log(img); return `
          @@ -105,11 +100,7 @@ class EPubFileBuilder { }).join("\n") }).join("\n"); - // eslint-disable-next-line no-console - console.log("vtoc navcontents", navContents); const toc_xhtml = OEBPS_TOC_XHTML({ title: this.data.title, language: this.language, navContents }); - // eslint-disable-next-line no-console - console.log("add visual toc xhtml", toc_xhtml); this.zip.addFile('OEBPS/toc.xhtml', toc_xhtml); } diff --git a/src/screens/EPub/model.js b/src/screens/EPub/model.js index 94bbfcd6d..b54a1beb4 100644 --- a/src/screens/EPub/model.js +++ b/src/screens/EPub/model.js @@ -52,7 +52,6 @@ const EPubModel = { }, setEPub(state, { payload }) { const items = getAllItemsInChapters(payload.chapters); - console.log("setEPub items", items); if (!payload.chapters) { payload.chapters = [] diff --git a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js index 1f24e8884..e70e2b66d 100644 --- a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js +++ b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js @@ -76,15 +76,11 @@ function INoteChapter({ }; const handleOpenImgPicker = (itemIdx) => { - // eslint-disable-next-line no-console - console.log("screenshots", images); const imgData = { screenshots: images.map(img => img.src), onSave: handleSaveImage(itemIdx), chapterScreenshots: epub.chapters[chIdx].allImagesWithIn }; - // eslint-disable-next-line no-console - console.log("imgData", imgData); dispatch({ type: 'epub/setImgPickerData', payload: imgData }); } @@ -196,9 +192,6 @@ function INoteChapter({ // Chapter Image Functions const onImageChange = (index) => (val) => { - // const val_obj = typeof val === 'string' ? images.filter(img => img.src === val)[0] : undefined; - // eslint-disable-next-line no-console - console.log("onimgchange", val); dispatch({ type: 'epub/updateEpubData', payload: { action: 'setChapterContentAtChapterIdx', payload: { chapterIdx: chIdx, contentIdx: index, value: val, type: 'image' } diff --git a/src/utils/cthttp/entities/Captions.js b/src/utils/cthttp/entities/Captions.js index 6e4aab1b1..904aba3d3 100644 --- a/src/utils/cthttp/entities/Captions.js +++ b/src/utils/cthttp/entities/Captions.js @@ -8,7 +8,7 @@ import { cthttp } from '../request'; // GET -export function getTranscriptionFile(transcriptionId,format) { +export function getTranscriptionFile(transcriptionId, format) { // {vtt,srt,txt} return cthttp.get(`Captions/TranscriptionFile/${transcriptionId}/${format}`); } @@ -31,19 +31,19 @@ export function searchCaptionInOffering(offeringId, query, filterLanguage = 'en- export function updateCaptionLine(data) { // eslint-disable-next-line no-console console.log("Preparing to update caption line with data:", data); - + // Check if all required fields are present if (!data.id || !data.text || !data.begin || !data.end) { // eslint-disable-next-line no-console console.error("Missing required data fields:", data); throw new Error("Required data fields are missing."); } - - return cthttp.post('Captions', { - id: data.id, - text: data.text, - begin: data.begin, - end: data.end + + return cthttp.post('Captions', { + id: data.id, + text: data.text, + begin: data.begin, + end: data.end }).then(response => { // eslint-disable-next-line no-console console.log("Caption line updated successfully:", response); From aba0b6dae2a2174b76662824cc0f9c8b8d627016 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Fri, 18 Apr 2025 22:01:37 -0500 Subject: [PATCH 11/13] change image link data to match with source --- src/screens/EPub/model.js | 1 - src/screens/EPub/models/data_reducer.js | 7 ++++++- src/screens/EPub/views/ViewAndDownload/EditOptions.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/screens/EPub/model.js b/src/screens/EPub/model.js index b54a1beb4..db2a5b8e5 100644 --- a/src/screens/EPub/model.js +++ b/src/screens/EPub/model.js @@ -28,7 +28,6 @@ const initState = { showPrefSettings: false, showShortcuts: false, images: null, - items: [] } const EPubModel = { namespace: 'epub', diff --git a/src/screens/EPub/models/data_reducer.js b/src/screens/EPub/models/data_reducer.js index 38ff8aae8..b2a357060 100644 --- a/src/screens/EPub/models/data_reducer.js +++ b/src/screens/EPub/models/data_reducer.js @@ -482,7 +482,12 @@ export default { setChapterContentAtChapterIdx(state, { payload: { type = 'text', contentIdx, chapterIdx, subChapterIdx, value } }) { if (type === 'image') { - value = new EPubImageData(value).toObject(); + const base_img = _.find(state.images, { src: value.src }) + if (base_img && value.src !== state.epub.chapters[chapterIdx].contents[contentIdx].src) { + value = new EPubImageData(base_img).toObject(); + } else { + value = new EPubImageData(value).toObject(); + } } // console.log("setchcontent", value); const chapters = state.epub.chapters; diff --git a/src/screens/EPub/views/ViewAndDownload/EditOptions.js b/src/screens/EPub/views/ViewAndDownload/EditOptions.js index 04fff5aca..9bfd914bd 100644 --- a/src/screens/EPub/views/ViewAndDownload/EditOptions.js +++ b/src/screens/EPub/views/ViewAndDownload/EditOptions.js @@ -46,7 +46,7 @@ function EditOptions({ setDownloadOptions, downloadOptions }) { color="primary" /> } - label="Include Links to Video" + label="Include Image Links to Video" /> Date: Fri, 18 Apr 2025 23:02:32 -0500 Subject: [PATCH 12/13] implement estimates of chapter start time --- src/entities/EPubs/structs/EPubChapterData.js | 4 +- src/screens/EPub/models/data_reducer.js | 97 ++++++------------- .../EditINote/INoteEditor/INoteChapter.js | 8 +- .../views/EditINote/QuickActionsEditNote.js | 97 ++++++++----------- 4 files changed, 82 insertions(+), 124 deletions(-) diff --git a/src/entities/EPubs/structs/EPubChapterData.js b/src/entities/EPubs/structs/EPubChapterData.js index 37cda3894..4355e9c2b 100644 --- a/src/entities/EPubs/structs/EPubChapterData.js +++ b/src/entities/EPubs/structs/EPubChapterData.js @@ -25,7 +25,8 @@ class EPubChapterData { id, title, items, - contents = [] + contents = [], + start } = data; // const { start, end } = findChapterTimeSpan(data); // TODO @@ -33,6 +34,7 @@ class EPubChapterData { this.__data__ = { ...this.__data__, id: id || _buildID(), + start, title: title || EPubChapterData.createChapterTitle(), condition: ['default'], contents: resetText diff --git a/src/screens/EPub/models/data_reducer.js b/src/screens/EPub/models/data_reducer.js index b2a357060..31d1e7677 100644 --- a/src/screens/EPub/models/data_reducer.js +++ b/src/screens/EPub/models/data_reducer.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import { EPubData, EPubChapterData, EPubSubChapterData, EPubImageData } from 'entities/EPubs'; import { getAllItemsInChapters } from 'entities/EPubs/utils' +import { epubIsImage } from '../controllers/file-builders/utils'; // DEBUG ONLY: remove this once debugging is complete /* eslint-disable no-console */ @@ -136,7 +137,7 @@ function rebuildSubChapter(chapters, chapterIndex, subChapterIndex, subChapterLi function nextStateOfChapters(chapters) { console.log(`Building next state of chapters...`); const items = getAllItemsInChapters(chapters); - return { chapters, items, images: _.map(items, item => item?.image) } + return { chapters, images: _.map(items, item => item?.image) } } export default { subdivideChapter(state, { payload: { chapterIdx, itemIdx } }) { @@ -293,8 +294,10 @@ export default { let contents = _.slice(chapter.contents, itemIdx, chapter.contents.length); chapter.contents = _.slice(chapter.contents, 0, itemIdx); + // new chapter start time estimate + const firstImg = _.find(contents, epubIsImage); // insert the new chapter - const newChapters = insertChapter(chapters, chapterIdx + 1, { contents }, false); + const newChapters = insertChapter(chapters, chapterIdx + 1, { contents, start: firstImg ? firstImg.timestamp : chapter.start }, false); if (newChapters[chapterIdx].timemerge > '00:00:00') { newChapters[chapterIdx + 1].end = newChapters[chapterIdx].end; @@ -341,74 +344,38 @@ export default { chapters[chapterIdx].title = value; return { ...state, epub: { ...state.epub, ...nextStateOfChapters([...chapters]) } }; }, - splitChaptersByScreenshots(state, { payload: { wc } }) { // Enforces Word Count + // eslint-disable-next-line no-unused-vars + splitChaptersByScreenshots(state, { payload }) { console.log(`Splitting chapters by screenshots`); - const new_items = []; // duplicating some sentences (sentences with less than wc words) - // min word count that each chapter should have - const default_word_count = 25; - let min_word_count = wc; - if (wc === "") { // if there was no input, set wc min wc to default - min_word_count = default_word_count; - } - // find total word count in i-note and make sure user input is not larger than total wc - const total_word_count = state.items.reduce((wordCount, a) => { - if (a === undefined || a === null) { - return wordCount; - } - return wordCount + a.text.split(' ').length; - }, 0); - if (min_word_count > total_word_count) { - min_word_count = default_word_count; - } - // loop through chapters and enforce minimum wc - (state.items).forEach((elem) => { - if (elem !== undefined && elem !== null) { - if (new_items.length !== 0) { - const oldelem = new_items.pop(); - let words = (oldelem.text).split(' ').length; - if (words < min_word_count) { - // append shorter text to previous chapter - oldelem.text += " "; - oldelem.text += elem.text; - oldelem.end = elem.end; - new_items.push(oldelem); - } - else { - new_items.push(oldelem); - new_items.push(elem) - } - } else { - new_items.push(elem); + const all_contents = _.flatMap(state.epub.chapters, ch => ch.contents); + + const result = []; + let currentChunk = []; + all_contents.forEach((item) => { + if (epubIsImage(item)) { + if (currentChunk.length) { + const firstImg = _.find(currentChunk, epubIsImage); + + result.push((new EPubChapterData({ + contents: currentChunk, + title: `Chapter ${result.length + 1}`, + start: firstImg ? firstImg.timestamp : undefined + }, false)).toObject()); } + currentChunk = [item]; // start new chunk with the match + } else { + currentChunk.push(item); } }); - // makes sure the first element also has a min of min_word_count words - const last_elem = new_items.pop(); - let words = (last_elem.text).split(' ').length; - if (words !== 0) { - if (words > min_word_count) { - new_items.push(last_elem); - } - else { - const oldelem = new_items.pop(); - oldelem.text += " "; - oldelem.text += last_elem.text; - oldelem.end = last_elem.end; - new_items.push(oldelem); - } + + if (currentChunk.length) { + result.push((new EPubChapterData({ + contents: currentChunk, + title: `Chapter ${result.length + 1}` + }, false)).toObject()); } - state.items = new_items; - let splitChapters = _.map(new_items, (data) => { - if (data === undefined || data === null) { - return null; - } - return new EPubChapterData({ - items: [data], - title: data.title, - }).toObject(); - }); - splitChapters = _.compact(splitChapters); + result[0].items = state.images; /* let splitChapters = _.map( @@ -419,7 +386,7 @@ export default { title: data.title, }).toObject(), ); */ - return { ...state, epub: { ...state.epub, ...nextStateOfChapters(splitChapters) } }; + return { ...state, epub: { ...state.epub, ...nextStateOfChapters(result) } }; }, resetToDefaultChapters(state) { console.log(`Resetting to default chapters`); diff --git a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js index e70e2b66d..6dd66fb55 100644 --- a/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js +++ b/src/screens/EPub/views/EditINote/INoteEditor/INoteChapter.js @@ -25,7 +25,7 @@ function INoteChapter({ const { start, end, title } = chapter; // const btnStyles = useButtonStyles(); const startTimeStr = timestr.toPrettierTimeString(start); - const endTimeStr = timestr.toPrettierTimeString(end); + // const endTimeStr = timestr.toPrettierTimeString(end); const [insertType, setInsertType] = useState(null); const openMDEditor = insertType === 'md'; const [openModalIndex, setOpenModalIndex] = useState(null); @@ -152,7 +152,7 @@ function INoteChapter({ function watchVideoElement(itemIdx) { return altEl(Button, itemIdx === 0, { ...btnProps, - text: Watch {startTimeStr} - {endTimeStr}, + text: Watch {startTimeStr}, icon: play_circle_filled, onClick: watchInPlayer }) @@ -263,7 +263,7 @@ function INoteChapter({ {splitBtnElement(itemIdx)} {addImgElement(itemIdx)} {addTextElement(itemIdx)} - {watchVideoElement(itemIdx)} + {start ? watchVideoElement(itemIdx) : ""} {typeof content === "object" ? ( // image @@ -310,7 +310,7 @@ function INoteChapter({ {splitBtnElement(chapter.contents.length)} {addImgElement(chapter.contents.length)} {addTextElement(chapter.contents.length)} - {watchVideoElement(chapter.contents.length)} + {start ? watchVideoElement(chapter.contents.length) : ""} )} ) diff --git a/src/screens/EPub/views/EditINote/QuickActionsEditNote.js b/src/screens/EPub/views/EditINote/QuickActionsEditNote.js index f5090aba9..5e6ca7b00 100644 --- a/src/screens/EPub/views/EditINote/QuickActionsEditNote.js +++ b/src/screens/EPub/views/EditINote/QuickActionsEditNote.js @@ -1,70 +1,59 @@ -import React, {useState} from 'react'; +import React from 'react'; import cx from 'classnames'; import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; import ButtonGroup from '@material-ui/core/ButtonGroup'; -import { CTHeading, CTFragment, useButtonStyles } from 'layout'; +import { CTFragment, useButtonStyles } from 'layout'; import { connect } from 'dva' -function QuickActionsEditNote({ chapters = {}, items, currChIndex = 0, dispatch }) { +function QuickActionsEditNote({ chapters = {}, images, currChIndex = 0, dispatch }) { const btnStyles = useButtonStyles(); const btnClasses = cx(btnStyles.tealLink, 'justify-content-start'); - if (currChIndex >= chapters.length) {currChIndex = 0;} - const showResetBtn = chapters.length > 1; // || chapters[0].subChapters.length > 0; - const showSplitAllBtn = chapters.length !== items.length; - - - // default state is min word count of 25 for split by screenshots - const [wordInput, setWordInput] = useState("25"); - const handleOnSubmit = (event) => { - event.preventDefault(); - dispatch({type: 'epub/splitChaptersByScreenshots', payload:{wc: wordInput}}); - }; - const handleOnWcChange = (event) => { - setWordInput(event.target.value); - }; + if (currChIndex >= chapters.length) { currChIndex = 0; } + // const showResetBtn = chapters.length > 1; // || chapters[0].subChapters.length > 0; + const showResetBtn = false; // currently disabled, since there are no default chapters stored + const showSplitAllBtn = chapters.length !== images.length; return ( - Quick Split + {/* Quick Split */} + - { - showResetBtn - && - - - - - - } + showResetBtn + && + + + + + + } { - showSplitAllBtn - && - - - - - - } + showSplitAllBtn + && + + + + + + } - - + + {/* -
          + + /> -
          +
          */} - + ); } -export default connect(({ epub: { currChIndex, epub: { chapters }, items } }) => ({ - currChIndex, chapters, items +export default connect(({ epub: { currChIndex, epub: { chapters }, images } }) => ({ + currChIndex, chapters, images }))(QuickActionsEditNote); From 79aa4ac35880ae406e6da91b5789971c05bd8155 Mon Sep 17 00:00:00 2001 From: dsding2 Date: Wed, 30 Apr 2025 20:00:35 -0500 Subject: [PATCH 13/13] minor fixes --- .../CTEPubListScreen/controllers/helpers.js | 50 +++++++++---------- .../controllers/file-builders/EPubParser.js | 5 +- .../file-builders/PDFFileBuilder.js | 21 ++++---- .../file-templates/pdf/textbox.js | 5 +- src/screens/EPub/index.js | 1 + 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/components/CTEPubListScreen/controllers/helpers.js b/src/components/CTEPubListScreen/controllers/helpers.js index f2260bb26..f3a1b61a9 100644 --- a/src/components/CTEPubListScreen/controllers/helpers.js +++ b/src/components/CTEPubListScreen/controllers/helpers.js @@ -4,36 +4,36 @@ import { ARRAY_INIT } from 'utils/constants'; import { LanguageConstants } from '../../CTPlayer'; export function _filterTrivalItems(epubData) { - return [...epubData]; - // return _.filter(epubData, (item) => Boolean(_.trim(item.text))); + // return [...epubData]; + return _.filter(epubData, (item) => Boolean(_.trim(item.text))); +} +function getLastPunctuationIndex(sentence) { + let lastPunctuationIndex = -1; + for (let i = sentence.length - 1; i >= 0; i -= 1) { + if (sentence[i] === '.' || sentence[i] === '?' || sentence[i] === '!') { + lastPunctuationIndex = i; + break; + } + } + return lastPunctuationIndex; } -// function getLastPunctuationIndex(sentence) { -// let lastPunctuationIndex = -1; -// for (let i = sentence.length - 1; i >= 0; i -= 1) { -// if (sentence[i] === '.' || sentence[i] === '?' || sentence[i] === '!') { -// lastPunctuationIndex = i; -// break; -// } -// } -// return lastPunctuationIndex; -// } // eslint-disable-next-line no-unused-vars function _parseRawEPubDataSplittingOnPunctuation(rawEPubData) { - return null; - // let buffer = ""; - // for (let i = 0; i < rawEPubData.length; i += 1) { - // let curr = (buffer + rawEPubData[i].text).trim(); - // let idx = getLastPunctuationIndex(curr); - // if (idx === curr.length - 1) { - // rawEPubData[i].text = curr; - // buffer = ""; - // } else { - // buffer = `${curr.substring(getLastPunctuationIndex(curr) + 1, curr.length)} `; - // rawEPubData[i].text = curr.substring(0, getLastPunctuationIndex(curr) + 1); - // } - // } // return null; + let buffer = ""; + for (let i = 0; i < rawEPubData.length; i += 1) { + let curr = (buffer + rawEPubData[i].text).trim(); + let idx = getLastPunctuationIndex(curr); + if (idx === curr.length - 1) { + rawEPubData[i].text = curr; + buffer = ""; + } else { + buffer = `${curr.substring(getLastPunctuationIndex(curr) + 1, curr.length)} `; + rawEPubData[i].text = curr.substring(0, getLastPunctuationIndex(curr) + 1); + } + } + return null; } export function _parseRawEPubData(rawEPubData) { let a = _.map(_filterTrivalItems(rawEPubData), item => ({ ...item, id: _buildID() })); diff --git a/src/screens/EPub/controllers/file-builders/EPubParser.js b/src/screens/EPub/controllers/file-builders/EPubParser.js index 9e78ab9b7..52255eec8 100644 --- a/src/screens/EPub/controllers/file-builders/EPubParser.js +++ b/src/screens/EPub/controllers/file-builders/EPubParser.js @@ -115,7 +115,8 @@ class EPubParser { new_content.height = height; new_content.width = width; } - new_content.descriptions = _.filter(content.descriptions, (desc) => desc.trim() !== ""); + new_content.descriptions = await Promise.all(content.descriptions.filter((desc) => desc.trim() !== "") + .map((desc) => this.parseText(desc))); new_content.id = this.img_id; this.img_id += 1; return new_content; @@ -190,6 +191,8 @@ class EPubParser { const parser = new EPubParser(); await parser.init(ePubData.epub, options) + // eslint-disable-next-line no-console + console.log("parsed data", parser.data); return parser.data; } diff --git a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js index 98ac73265..5bc61bc13 100644 --- a/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js +++ b/src/screens/EPub/controllers/file-builders/PDFFileBuilder.js @@ -98,12 +98,12 @@ class PDFFileBuilder { convertImage({ src, descriptions, alt, height = 100, width = 100, link }) { const scale = this.max_text_width / width; - const curr_y_loc = this.getYLoc(height); + const curr_y_loc = this.getYLoc(height * scale); this.doc.addImage(src === "" ? placeholderImg : src, STYLE_SHEET.edgeMargin, curr_y_loc, width * scale, height * scale); if (this.videoLinks && link && link !== "") { this.doc.link(STYLE_SHEET.edgeMargin, curr_y_loc, width * scale, height * scale, { url: link }); } - this.incrementYLoc(height * scale); + this.incrementYLoc(height * scale + STYLE_SHEET.font.altText.size); if (this.writeTextToPDF(alt, STYLE_SHEET.font.altText)) { this.incrementYLoc(STYLE_SHEET.image.AltDescGap); @@ -124,7 +124,8 @@ class PDFFileBuilder { } convertChapter(idx, { contents, title }) { - this.writeTextToPDF(`${idx}: ${title}`, STYLE_SHEET.font.chapterTitle); + this.incrementYLoc(STYLE_SHEET.spacing); + this.writeTextToPDF(`${idx + 1}: ${title}`, STYLE_SHEET.font.chapterTitle); this.chapter_page_indexes.push(this.currentPageNumber); this.incrementYLoc(STYLE_SHEET.spacing); _.forEach(contents, (content) => { this.convertContent(content) }); @@ -142,6 +143,7 @@ class PDFFileBuilder { this.writeTextToPDF(`${key}: ${value.description}`, STYLE_SHEET.font.glossary); } convertGlossary(glossary, add_outline = true) { + this.incrementYLoc(STYLE_SHEET.spacing); if (add_outline) { this.glossary_page = this.currentPageNumber; } @@ -164,12 +166,11 @@ class PDFFileBuilder { const style = STYLE_SHEET.visualTOC; const all_imgs = visualTOC.flat() - const min_width = _.minBy(all_imgs, (img) => { return img.width }).width; - const col_width = (this.max_text_width / style.imagesPerRow); - const max_scale = (col_width - 2 * style.hMargin) / min_width; - const max_height = _.maxBy(all_imgs, (img) => { return img.height }).height; - const rowsPerPage = Math.floor((this.max_height - STYLE_SHEET.visualTOC.topMargin) / (max_height * max_scale + style.vSpacing)); + const img_width = (col_width - 2 * style.hMargin); + const tallest_img = _.maxBy(all_imgs, img => img.height / img.width); + const tallest_aspect_ratio = tallest_img.height / tallest_img.width + const rowsPerPage = Math.floor((this.max_height - STYLE_SHEET.visualTOC.topMargin) / (tallest_aspect_ratio * img_width + style.vSpacing)); this.pageOffset = Math.ceil(all_imgs.length / (style.imagesPerRow * rowsPerPage)); this.currentPageNumber = 2; @@ -189,7 +190,7 @@ class PDFFileBuilder { const img = visualTOC[chapter][img_idx]; const x_loc = STYLE_SHEET.edgeMargin + (entry_idx % style.imagesPerRow) * col_width; - const scale = (col_width - 2 * style.hMargin) / img.width; + const scale = img_width / img.width; this.doc.addImage(img.src, 'jpeg', x_loc + style.hMargin, this.y_loc, scale * img.width, scale * img.height); const split_text = this.doc.splitTextToSize(`${chapter + 1}. ${img.alt}`, scale * img.width); _.forEach(split_text, (val, idx) => { @@ -204,7 +205,7 @@ class PDFFileBuilder { entry_idx += 1; if (entry_idx % style.imagesPerRow === 0) { - this.y_loc += max_height * scale + style.vSpacing; + this.y_loc += tallest_aspect_ratio * img_width + style.vSpacing; } if (entry_idx % (style.imagesPerRow * rowsPerPage) === 0 && entry_idx < all_imgs.length - 1) { this.nextPageTOC(style.topMargin); diff --git a/src/screens/EPub/controllers/file-builders/file-templates/pdf/textbox.js b/src/screens/EPub/controllers/file-builders/file-templates/pdf/textbox.js index d9228009f..b9e800033 100644 --- a/src/screens/EPub/controllers/file-builders/file-templates/pdf/textbox.js +++ b/src/screens/EPub/controllers/file-builders/file-templates/pdf/textbox.js @@ -1,5 +1,3 @@ -/* eslint-disable no-console */ -/* eslint-disable no-unreachable */ import _ from "lodash"; import { html } from "utils"; import { STYLE_SHEET } from "./pdfstyle"; @@ -79,9 +77,8 @@ export class TextBox { } else if (this.numbered_list) { this.writeTextSimple(` ${this.list_index + 1}. `); this.list_index += 1; - } else { - console.log("error, not bullet or numbered list"); } + // invalid list }, 'end': () => { this.nextLine(); } }, diff --git a/src/screens/EPub/index.js b/src/screens/EPub/index.js index 272976de9..452a9b691 100644 --- a/src/screens/EPub/index.js +++ b/src/screens/EPub/index.js @@ -57,6 +57,7 @@ function EPubWithRedux({ view, chapters, epub, dispatch }) { return; } if (!shiftKey) return; + if (document.activeElement.getAttribute("role") === "textbox") return; // Meta key actions switch (keyCode) { case KeyCode.KEY_1: // 1