From 30e89dc7c9f17e867f63e4e46988659c68113d6c Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sat, 17 Feb 2024 18:14:29 +0100 Subject: [PATCH 01/18] Fix the spacing for the implicit line breaks in odt --- src/odt/postprocess/mergeParagraphs.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index b8c69924..29847b0c 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -67,20 +67,20 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const findFirstTextAfterPos = (start: number): string | null => { for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { const currentChunk = markdownChunks.chunks[pos]; - if ('text' in currentChunk && currentChunk.text.trim() !== '') { + if ('text' in currentChunk && currentChunk.text !== '') { return currentChunk.text; } } return null; }; - const nextText = findFirstTextAfterPos(position); + const nextText = findFirstTextAfterPos(nextParaClosing); if (nextText === '* ' || nextText?.trim().length === 0) { markdownChunks.chunks.splice(position, 2, { isTag: false, text: '\n', mode: 'md', - comment: 'End of line, but next line is list' + comment: 'End of line, but next line is list' + nextText + ' ' + nextParaClosing }); position--; previousParaPosition = 0; From 3f2fa275fc10ce1913b9edb2065bfae629fe067a Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sat, 17 Feb 2024 18:44:55 +0100 Subject: [PATCH 02/18] Fix the spacing for the implicit line breaks in odt again See #434 --- src/odt/postprocess/mergeParagraphs.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index 29847b0c..b8c69924 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -67,20 +67,20 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const findFirstTextAfterPos = (start: number): string | null => { for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { const currentChunk = markdownChunks.chunks[pos]; - if ('text' in currentChunk && currentChunk.text !== '') { + if ('text' in currentChunk && currentChunk.text.trim() !== '') { return currentChunk.text; } } return null; }; - const nextText = findFirstTextAfterPos(nextParaClosing); + const nextText = findFirstTextAfterPos(position); if (nextText === '* ' || nextText?.trim().length === 0) { markdownChunks.chunks.splice(position, 2, { isTag: false, text: '\n', mode: 'md', - comment: 'End of line, but next line is list' + nextText + ' ' + nextParaClosing + comment: 'End of line, but next line is list' }); position--; previousParaPosition = 0; From 4f7a39010262bffa2411211c50582da4f89fc8dc Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Wed, 28 Feb 2024 20:21:06 +0100 Subject: [PATCH 03/18] Fix list indentation --- package-lock.json | 43 +++-- package.json | 4 +- .../google_folder/UserConfigService.ts | 2 +- src/odt/MarkdownChunks.ts | 28 ++- src/odt/OdtToMarkdown.ts | 60 ++++--- src/odt/StateMachine.ts | 167 +++--------------- src/odt/postprocess/addEmptyLines.ts | 68 ++++++- src/odt/postprocess/addIndentsAndBullets.ts | 9 +- src/odt/postprocess/fixBold.ts | 2 +- src/odt/postprocess/fixListParagraphs.ts | 2 +- .../fixSpacesInsideInlineFormatting.ts | 2 +- src/odt/postprocess/hideSuggestedChanges.ts | 2 +- src/odt/postprocess/mergeParagraphs.ts | 77 +++++--- src/odt/postprocess/postProcessHeaders.ts | 2 +- src/odt/postprocess/postProcessPreMacros.ts | 6 +- src/odt/postprocess/postProcessText.ts | 4 +- .../postprocess/processListsAndNumbering.ts | 162 +++++++++++++++++ .../removeInsideDoubleCodeBegin.ts | 2 +- .../removePreWrappingAroundMacros.ts | 4 +- src/odt/postprocess/trimEndOfParagraphs.ts | 2 +- src/utils/FileContentService.ts | 2 +- test/odt_md/Issues.test.ts | 16 +- test/odt_md/RewriteRules.test.ts | 2 +- test/odt_md/example-document.md | 6 +- test/odt_md/issue-434-2.md | 11 +- test/odt_md/issue-435-436.md | 4 +- test/odt_md/issue-443.md | 25 +++ test/odt_md/issue-443.odt | Bin 0 -> 14109 bytes test/odt_md/line-breaks.md | 2 +- test/utils.ts | 40 +++-- 30 files changed, 505 insertions(+), 251 deletions(-) create mode 100644 src/odt/postprocess/processListsAndNumbering.ts create mode 100644 test/odt_md/issue-443.md create mode 100644 test/odt_md/issue-443.odt diff --git a/package-lock.json b/package-lock.json index 93aab765..0234dbf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "relateurl": "0.2.7", "sharp": "0.32.6", "sharp-phash": "^2.0.0", - "slugify": "1.6.5", + "slugify": "1.6.6", "stream": "^0.0.2", "ts-node": "10.9.2", "typescript": "5.3.3", @@ -90,7 +90,7 @@ "@typescript-eslint/eslint-plugin": "6.12.0", "@typescript-eslint/parser": "6.12.0", "chai": "5.0.0", - "diff": "5.0.0", + "diff": "5.2.0", "eslint": "8.54.0", "husky": "7.0.4", "jshint": "2.13.4", @@ -2665,9 +2665,9 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -4617,6 +4617,15 @@ "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -5722,9 +5731,9 @@ } }, "node_modules/slugify": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.5.tgz", - "integrity": "sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", "engines": { "node": ">=8.0.0" } @@ -8218,9 +8227,9 @@ "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "dir-glob": { @@ -9705,6 +9714,12 @@ "balanced-match": "^1.0.0" } }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -10506,9 +10521,9 @@ "dev": true }, "slugify": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.5.tgz", - "integrity": "sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ==" + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==" }, "source-map": { "version": "0.6.1", diff --git a/package.json b/package.json index 8f81a177..6a50db45 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "relateurl": "0.2.7", "sharp": "0.32.6", "sharp-phash": "^2.0.0", - "slugify": "1.6.5", + "slugify": "1.6.6", "stream": "^0.0.2", "ts-node": "10.9.2", "typescript": "5.3.3", @@ -137,7 +137,7 @@ "@typescript-eslint/eslint-plugin": "6.12.0", "@typescript-eslint/parser": "6.12.0", "chai": "5.0.0", - "diff": "5.0.0", + "diff": "5.2.0", "eslint": "8.54.0", "husky": "7.0.4", "jshint": "2.13.4", diff --git a/src/containers/google_folder/UserConfigService.ts b/src/containers/google_folder/UserConfigService.ts index 2edb498b..904fc416 100644 --- a/src/containers/google_folder/UserConfigService.ts +++ b/src/containers/google_folder/UserConfigService.ts @@ -6,7 +6,7 @@ import {FileContentService} from '../../utils/FileContentService.ts'; import {HugoTheme} from '../server/routes/ConfigController.ts'; import {FRONTMATTER_DUMP_OPTS} from '../transform/frontmatters/frontmatter.ts'; import {DEFAULT_ACTIONS} from '../action/ActionRunnerContainer.ts'; -import {RewriteRule} from '../../odt/applyRewriteRule.js'; +import {RewriteRule} from '../../odt/applyRewriteRule.ts'; async function execAsync(command: string) { const err = new Error(); diff --git a/src/odt/MarkdownChunks.ts b/src/odt/MarkdownChunks.ts index 20feaf94..d68fa6a3 100644 --- a/src/odt/MarkdownChunks.ts +++ b/src/odt/MarkdownChunks.ts @@ -4,7 +4,7 @@ import {applyRewriteRule, RewriteRule} from './applyRewriteRule.ts'; export type OutputMode = 'md' | 'html' | 'raw'; -export type TAG = 'HR/' | 'BR/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | +export type TAG = 'HR/' | 'BR/' | 'EMPTY_LINE/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | 'H1' | 'H2' | 'H3' | 'H4' | '/H1' | '/H2' | '/H3' | '/H4' | 'P' | '/P' | 'CODE' | '/CODE' | 'PRE' | '/PRE' | 'UL' | '/UL' | 'LI' | '/LI' | 'A' | '/A' | @@ -69,6 +69,14 @@ function debugChunkToText(chunk: MarkdownChunk) { return chunk.tag; } +export function addComment(chunk: MarkdownTagChunk, comment: string) { + if (chunk.comment) { + chunk.comment += ' ' + comment; + } else { + chunk.comment = comment; + } +} + export function textStyleToString(textProperty: TextProperty) { if (!textProperty) { return ''; @@ -144,6 +152,8 @@ function chunkToText(chunk: MarkdownChunk) { return '\n'; case 'BR/': return '\n'; + case 'EMPTY_LINE/': + return '\n'; } break; case 'md': @@ -154,6 +164,8 @@ function chunkToText(chunk: MarkdownChunk) { return '\n'; case 'BR/': return ' \n'; + case 'EMPTY_LINE/': + return '\n'; case 'PRE': return '\n```'+ (chunk.payload?.lang || '') +'\n'; case '/PRE': @@ -182,6 +194,14 @@ function chunkToText(chunk: MarkdownChunk) { return '### '; case 'H4': return '#### '; + case '/H1': + return '\n'; + case '/H2': + return '\n'; + case '/H3': + return '\n'; + case '/H4': + return '\n'; case 'HR/': return '\n___\n '; case 'A': @@ -202,6 +222,8 @@ function chunkToText(chunk: MarkdownChunk) { switch (chunk.tag) { case 'BR/': return '\n'; + case 'EMPTY_LINE/': + return '
'; case 'HR/': return '
'; case 'B': @@ -455,6 +477,10 @@ export class MarkdownChunks { if (chunk.isTag === true) { line += chunk.tag; + + if (chunk.tag === 'UL') { + line += ` (Level: ${chunk.payload.listLevel})`; + } } if (chunk.isTag === false) { line += chunk.text diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index ff67a7e2..d1829d30 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -23,7 +23,7 @@ import {inchesToPixels, inchesToSpaces, spaces} from './utils.ts'; import {extractPath} from './extractPath.ts'; import {mergeDeep} from './mergeDeep.ts'; import {RewriteRule} from './applyRewriteRule.ts'; -import {postProcessText} from './postprocess/postProcessText.js'; +import {postProcessText} from './postprocess/postProcessText.ts'; function getBaseFileName(fileName) { return fileName.replace(/.*\//, ''); @@ -65,7 +65,6 @@ export class OdtToMarkdown { private readonly chunks: MarkdownChunks = new MarkdownChunks(); private picturesDir = ''; private rewriteRules: RewriteRule[] = []; - private counters: { [key: string]: number } = {}; constructor(private document: DocumentContent, private documentStyles: DocumentStyles, private fileNameMap: FileNameMap = {}) { this.stateMachine = new StateMachine(this.chunks); @@ -101,12 +100,27 @@ export class OdtToMarkdown { } async convert(): Promise { + const listLevelsObj = {}; + const listMargins = {}; + if (this.document.automaticStyles) { for (const namedStyle of this.document.automaticStyles.styles) { this.styles[namedStyle.name] = namedStyle; } + + for (const namedStyle of this.document.automaticStyles.styles) { + if (namedStyle.listStyleName) { + listLevelsObj[namedStyle.paragraphProperties?.marginLeft] = true; + listMargins[namedStyle.listStyleName] = namedStyle.paragraphProperties?.marginLeft; + } + } } + const listLevels = Object.keys(listLevelsObj); + listLevels.sort((a, b) => inchesToPixels(a) - inchesToPixels(b)); + + this.stateMachine.setListLevels(listLevels); + for (const tableOfContent of this.document.body.text.list) { if (tableOfContent.type === 'toc') { await this.tocToText(tableOfContent); @@ -440,6 +454,18 @@ export class OdtToMarkdown { const listStyle = this.getListStyle(style.listStyleName); const bookmarkName = paragraph.bookmark?.name || null; + if (this.hasStyle(paragraph, 'Heading_20_1')) { + this.stateMachine.pushTag('H1', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + } else + if (this.hasStyle(paragraph, 'Heading_20_2')) { + this.stateMachine.pushTag('H2', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + } else + if (this.hasStyle(paragraph, 'Heading_20_3')) { + this.stateMachine.pushTag('H3', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + } else + if (this.hasStyle(paragraph, 'Heading_20_4')) { + this.stateMachine.pushTag('H4', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + } else if (this.isCourier(paragraph.styleName)) { this.stateMachine.pushTag('PRE', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); } else { @@ -485,19 +511,6 @@ export class OdtToMarkdown { } }*/ - if (this.hasStyle(paragraph, 'Heading_20_1')) { - this.stateMachine.pushTag('H1'); - } - if (this.hasStyle(paragraph, 'Heading_20_2')) { - this.stateMachine.pushTag('H2'); - } - if (this.hasStyle(paragraph, 'Heading_20_3')) { - this.stateMachine.pushTag('H3'); - } - if (this.hasStyle(paragraph, 'Heading_20_4')) { - this.stateMachine.pushTag('H4'); - } - if (!this.isCourier(paragraph.styleName)) { if (style.textProperties?.fontWeight === 'bold') { this.stateMachine.pushTag('B'); @@ -578,23 +591,22 @@ export class OdtToMarkdown { } } + if (onlyCodeChildren) { + this.stateMachine.pushTag('/PRE'); + } + if (this.hasStyle(paragraph, 'Heading_20_1')) { this.stateMachine.pushTag('/H1'); - } + } else if (this.hasStyle(paragraph, 'Heading_20_2')) { this.stateMachine.pushTag('/H2'); - } + } else if (this.hasStyle(paragraph, 'Heading_20_3')) { this.stateMachine.pushTag('/H3'); - } + } else if (this.hasStyle(paragraph, 'Heading_20_4')) { this.stateMachine.pushTag('/H4'); - } - - if (onlyCodeChildren) { - this.stateMachine.pushTag('/PRE'); - } - + } else if (this.isCourier(paragraph.styleName)) { this.stateMachine.pushTag('/PRE'); } else { diff --git a/src/odt/StateMachine.ts b/src/odt/StateMachine.ts index 069e283d..64c01499 100644 --- a/src/odt/StateMachine.ts +++ b/src/odt/StateMachine.ts @@ -3,18 +3,19 @@ import slugify from 'slugify'; import {isClosing, isOpening, MarkdownChunks, OutputMode, TAG, TagPayload} from './MarkdownChunks.ts'; import {fixCharacters} from './utils.ts'; import {RewriteRule} from './applyRewriteRule.ts'; -import {postProcessHeaders} from './postprocess/postProcessHeaders.js'; -import {postProcessPreMacros} from './postprocess/postProcessPreMacros.js'; -import {addIndentsAndBullets} from './postprocess/addIndentsAndBullets.js'; -import {fixBold} from './postprocess/fixBold.js'; -import {hideSuggestedChanges} from './postprocess/hideSuggestedChanges.js'; -import {addEmptyLines} from './postprocess/addEmptyLines.js'; -import {mergeParagraphs} from './postprocess/mergeParagraphs.js'; -import {removePreWrappingAroundMacros} from './postprocess/removePreWrappingAroundMacros.js'; -import {fixListParagraphs} from './postprocess/fixListParagraphs.js'; -import {fixSpacesInsideInlineFormatting} from './postprocess/fixSpacesInsideInlineFormatting.js'; -import {removeInsideDoubleCodeBegin} from './postprocess/removeInsideDoubleCodeBegin.js'; -import {trimEndOfParagraphs} from './postprocess/trimEndOfParagraphs.js'; +import {postProcessHeaders} from './postprocess/postProcessHeaders.ts'; +import {postProcessPreMacros} from './postprocess/postProcessPreMacros.ts'; +import {addIndentsAndBullets} from './postprocess/addIndentsAndBullets.ts'; +import {fixBold} from './postprocess/fixBold.ts'; +import {hideSuggestedChanges} from './postprocess/hideSuggestedChanges.ts'; +import {addEmptyLines} from './postprocess/addEmptyLines.ts'; +import {mergeParagraphs} from './postprocess/mergeParagraphs.ts'; +import {removePreWrappingAroundMacros} from './postprocess/removePreWrappingAroundMacros.ts'; +import {fixListParagraphs} from './postprocess/fixListParagraphs.ts'; +import {fixSpacesInsideInlineFormatting} from './postprocess/fixSpacesInsideInlineFormatting.ts'; +import {removeInsideDoubleCodeBegin} from './postprocess/removeInsideDoubleCodeBegin.ts'; +import {trimEndOfParagraphs} from './postprocess/trimEndOfParagraphs.ts'; +import {processListsAndNumbering} from './postprocess/processListsAndNumbering.ts'; interface TagLeaf { mode: OutputMode; @@ -63,48 +64,15 @@ export function stripMarkdownMacro(innerTxt) { export class StateMachine { public errors: string[] = []; private readonly tagsTree: TagLeaf[] = []; - private listLevel = 0; private rewriteRules: RewriteRule[] = []; currentMode: OutputMode = 'md'; headersMap: { [id: string]: string } = {}; - - counters: { [id: string]: number } = {}; - private preserveMinLevel = 999; + private listLevels: string[] = []; constructor(public markdownChunks: MarkdownChunks) { } - fetchListNo(styleName: string) { - if (this.counters[styleName]) { - return this.counters[styleName]; - } - return 0; - } - - storeListNo(styleName: string, val: number) { - if (!styleName) { - return; - } - this.counters[styleName] = val; - } - - clearListsNo(styleNamePrefix: string, minLevel) { - for (const k in this.counters) { - if (!k.startsWith(styleNamePrefix)) { - continue; - } - const level = parseInt(k.substring(styleNamePrefix.length)); - if (level < minLevel) { - continue; - } - if (level > minLevel + 1) { - // continue; - } - this.counters[k] = 0; - } - } - get parentLevel() { if (this.tagsTree.length > 1) { return this.tagsTree[this.tagsTree.length - 2]; @@ -117,59 +85,9 @@ export class StateMachine { } } - getParentListStyleName(): string { - for (let i = this.tagsTree.length - 1; i >=0; i--) { - const leaf = this.tagsTree[i]; - if (leaf.tag === 'UL') { - if (leaf.payload.listStyle?.name) { - return leaf.payload.listStyle.name; - } - } - } - return null; - } - pushTag(tag: TAG, payload: TagPayload = {}) { payload.position = this.markdownChunks.length; - if (tag === 'UL') { - this.listLevel++; - payload.listLevel = this.listLevel; - - if (payload.continueNumbering || payload.continueList) { - this.preserveMinLevel = this.listLevel; - } - - if (!(this.preserveMinLevel <= this.listLevel)) { - this.clearListsNo((payload.listStyle?.name || this.getParentListStyleName()) + '_', this.listLevel); - } - } - if (tag === 'LI') { - if (this.currentLevel?.tag === 'UL') { - payload.listLevel = this.currentLevel.payload.listLevel; - const listStyleName = (payload.listStyle?.name || this.getParentListStyleName()) + '_' + payload.listLevel; - payload.number = payload.number || this.fetchListNo(listStyleName); - payload.number++; - this.storeListNo(listStyleName, payload.number); - } - } -/* List indents should be determined from marginLefts, which are different per doc. Damnit !!! - if (tag === 'P') { - if (this.currentLevel?.tag === 'LI') { - // payload.listLevel = this.currentLevel.payload.listLevel; - const listStyleName = (payload.listStyle?.name || this.getParentListStyleName()) + '_' + payload.listLevel; - payload.listLevel = inchesToSpaces(payload.style?.paragraphProperties?.marginLeft) / 2; - - if (payload.listLevel !== this.currentLevel.payload.listLevel) { - console.log('EEEEEEEEEEEEEEE', payload.listLevel, this.currentLevel.payload.listLevel, payload.style?.paragraphProperties?.marginLeft); - } - payload.number = payload.number || this.fetchListNo(listStyleName); - payload.number++; - this.storeListNo(listStyleName, payload.number); - } - } -*/ - // PRE-PUSH-PRE-TREEPUSH if (isOpening(tag)) { @@ -217,29 +135,6 @@ export class StateMachine { // } // } - if (tag === 'P') { - switch (this.currentMode) { - case 'md': - if (this.parentLevel?.tag === 'TOC') { - payload.bullet = true; - } - if (this.parentLevel?.tag === 'LI') { - const level = this.parentLevel.payload.listLevel; - const listStyle = this.parentLevel.payload.listStyle || this.currentLevel.payload.listStyle; - const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); - - payload.listLevel = level; - - if (isNumeric) { - payload.number = this.parentLevel.payload.number; - } else { - payload.bullet = true; - } - } - break; - } - } - // Inside list item tags like needs to be html tags if (this.currentMode === 'md' && tag === '/P' && this.parentLevel?.tag === 'LI') { for (let pos = this.currentLevel.payload.position + 1; pos < payload.position; pos++) { @@ -314,6 +209,16 @@ export class StateMachine { this.currentMode = 'md'; } + if (['/H1', '/H2', '/H3', '/H4'].includes(tag) && 'md' === this.currentMode) { + if (this.currentLevel.payload.bookmarkName) { + const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); + const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); + if (slug) { + this.headersMap[this.currentLevel.payload.bookmarkName] = slug; + } + } + } + if (tag === '/P' || tag === '/PRE') { const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); switch (this.currentMode) { @@ -333,13 +238,6 @@ export class StateMachine { break; case 'md': { - if (this.currentLevel.payload.bookmarkName) { - const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); - if (slug) { - this.headersMap[this.currentLevel.payload.bookmarkName] = slug; - } - } - switch (innerTxt) { case '{{rawhtml}}': // this.markdownChunks[payload.position].comment = 'Switching to raw - {{rawhtml}}'; @@ -375,18 +273,6 @@ export class StateMachine { } // POST-PUSH-AFTER-TREEPOP - - if (tag === '/LI') { - if (this.currentLevel?.tag === 'UL') { - // this.currentLevel.payload.number++; - // const listStyleName = (payload.listStyle?.name || this.getParentListStyleName()) + '_' + this.listLevel; - // this.storeListNo(listStyleName, this.currentLevel.payload.number); - } - } - if (tag === '/UL') { - this.listLevel--; - this.preserveMinLevel = 999; - } } pushText(txt: string) { @@ -400,6 +286,7 @@ export class StateMachine { } postProcess() { + processListsAndNumbering(this.markdownChunks); postProcessHeaders(this.markdownChunks); removePreWrappingAroundMacros(this.markdownChunks); removeInsideDoubleCodeBegin(this.markdownChunks); @@ -425,4 +312,8 @@ export class StateMachine { setRewriteRules(rewriteRules: RewriteRule[]) { this.rewriteRules = rewriteRules; } + + setListLevels(listLevels: string[]) { + this.listLevels = listLevels; + } } diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index 7e3a55c3..1160f9d5 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -1,26 +1,60 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function addEmptyLines(markdownChunks: MarkdownChunks) { for (let position = 0; position < markdownChunks.length; position++) { const chunk = markdownChunks.chunks[position]; - if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['/H1', '/H2', '/H3', '/H4', 'SVG/', '/UL'].indexOf(chunk.tag) > -1) { + if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['/H1', '/H2', '/H3', '/H4', 'IMG/', 'SVG/', '/UL'].indexOf(chunk.tag) > -1) { const nextTag = markdownChunks.chunks[position + 1]; + if (chunk.tag === '/UL' && chunk.payload.listLevel !== 1) { + continue; + } + if (chunk.tag === '/UL' && nextTag.isTag && nextTag.tag === 'UL') { + continue; + } + if (chunk.tag === '/UL' && nextTag.isTag && nextTag.tag === 'P') { + // continue; + } + + // if (nextTag.isTag && nextTag.tag === 'IMG/') { + // markdownChunks.chunks.splice(position + 1, 0, { + // isTag: true, + // mode: 'md', + // tag: 'EMPTY_LINE/', + // payload: {}, + // comment: 'Between images' + // }); + // // position+=2; + // continue; + // } if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD')) { markdownChunks.chunks.splice(position + 1, 0, { isTag: true, mode: 'md', tag: 'BR/', payload: {}, - comment: 'Next tag is not BR/' + comment: 'Next tag is not BR/, current is ' + chunk.tag }); } } - if (position > 1 && chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'SVG/', 'UL'].indexOf(chunk.tag) > -1) { + // listLevel + + if (position > 1 && chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'IMG/', 'SVG/', 'UL'].indexOf(chunk.tag) > -1) { const prevTag = markdownChunks.chunks[position - 1]; + + if (chunk.tag === 'UL' && chunk.payload.listLevel !== 1) { + continue; + } + if (chunk.tag === 'UL' && prevTag.isTag && prevTag.tag === '/UL') { + continue; + } + if (chunk.tag === 'UL' && prevTag.isTag && prevTag.tag === '/P') { + // continue; + } + if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD')) { markdownChunks.chunks.splice(position, 0, { isTag: false, @@ -34,4 +68,30 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { } } + for (let position = 0; position < markdownChunks.length; position++) { + const chunk = markdownChunks.chunks[position]; + if (!(chunk.isTag && chunk.tag === 'IMG/' && chunk.mode === 'md')) { + continue; + } + + const nextTag = markdownChunks.chunks[position + 1]; + if (!(nextTag.isTag && nextTag.tag === 'BR/' && nextTag.mode === 'md')) { + continue; + } + + markdownChunks.chunks.splice(position + 1, 1, { + isTag: true, + mode: 'md', + tag: 'BR/', + payload: {}, + comment: 'Between images /P' + }, { + isTag: true, + mode: 'md', + tag: 'EMPTY_LINE/', + payload: {}, + comment: 'Between images /P' + }); + } + } diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts index 5cb7ed1c..583ae0e4 100644 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ b/src/odt/postprocess/addIndentsAndBullets.ts @@ -1,5 +1,5 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; -import {spaces} from '../utils.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {spaces} from '../utils.ts'; export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { // ADD indents and bullets @@ -7,6 +7,11 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { const chunk = markdownChunks.chunks[position]; if (chunk.isTag === true && chunk.tag === 'P' && chunk.mode === 'md') { const level = (chunk.payload.listLevel || 1) - 1; + + if (!chunk.payload.listLevel) { + continue; + } + // let indent = spaces(level * 4); GDocs not fully compatible // if (chunk.payload.style?.paragraphProperties?.marginLeft) { // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); diff --git a/src/odt/postprocess/fixBold.ts b/src/odt/postprocess/fixBold.ts index 8625aac4..b097a166 100644 --- a/src/odt/postprocess/fixBold.ts +++ b/src/odt/postprocess/fixBold.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function fixBold(markdownChunks: MarkdownChunks) { diff --git a/src/odt/postprocess/fixListParagraphs.ts b/src/odt/postprocess/fixListParagraphs.ts index 1b2e5aac..02df4f07 100644 --- a/src/odt/postprocess/fixListParagraphs.ts +++ b/src/odt/postprocess/fixListParagraphs.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function fixListParagraphs(markdownChunks: MarkdownChunks) { let nextPara = null; diff --git a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts index 6dcd64c5..1d0fa722 100644 --- a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts +++ b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownChunks) { for (let position = 1; position < markdownChunks.length; position++) { diff --git a/src/odt/postprocess/hideSuggestedChanges.ts b/src/odt/postprocess/hideSuggestedChanges.ts index 8960a13f..e76c0267 100644 --- a/src/odt/postprocess/hideSuggestedChanges.ts +++ b/src/odt/postprocess/hideSuggestedChanges.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function hideSuggestedChanges(markdownChunks: MarkdownChunks) { let inChange = false; diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index b8c69924..92b2ca65 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,5 +1,5 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; -import {RewriteRule} from '../applyRewriteRule.js'; +import {addComment, MarkdownChunks, TAG} from '../MarkdownChunks.ts'; +import {RewriteRule} from '../applyRewriteRule.ts'; export function isBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% ') && !innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); @@ -10,13 +10,36 @@ export function isEndMacro(innerTxt: string) { } export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: RewriteRule[]) { - let previousParaPosition = 0; + let previousParaOpening = 0; const macros = []; + + function findNext(tag: TAG, start: number) { + let nextParaClosing = 0; + for (let idx = start + 1; idx < markdownChunks.length; idx++) { + const chunk = markdownChunks.chunks[idx]; + if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { + nextParaClosing = idx; + break; + } + } + return nextParaClosing; + } + + function findFirstTextAfterPos(start: number): string | null { + for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { + const currentChunk = markdownChunks.chunks[pos]; + if ('text' in currentChunk && currentChunk.text.trim() !== '') { + return currentChunk.text; + } + } + return null; + } + for (let position = 0; position < markdownChunks.length - 1; position++) { const chunk = markdownChunks.chunks[position]; if (chunk.isTag && chunk.mode === 'md' && chunk.tag === 'P') { - previousParaPosition = position; + previousParaOpening = position; continue; } @@ -33,47 +56,41 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const nextChunk = markdownChunks.chunks[position + 1]; if (macros.length > 0) { + addComment(chunk, 'macros.length > 0'); continue; } if (nextChunk.isTag && nextChunk.mode === 'md' && nextChunk.tag === 'P') { + const nextParaOpening = findNext('P', position); + const nextParaClosing = findNext('/P', position); - let nextParaClosing = 0; - for (let position2 = position + 1; position2 < markdownChunks.length; position2++) { - const chunk2 = markdownChunks.chunks[position2]; - if (chunk2.isTag && chunk2.mode === 'md' && chunk2.tag === '/P') { - nextParaClosing = position2; - break; - } - } - - if (nextParaClosing > 0) { - const innerText = markdownChunks.extractText(position, nextParaClosing, rewriteRules); + if (nextParaOpening > 0 && nextParaOpening < nextParaClosing) { + const innerText = markdownChunks.extractText(nextParaOpening, nextParaClosing, rewriteRules); if (innerText.length === 0) { + // markdownChunks.chunks.splice(nextParaOpening, nextParaClosing - nextParaOpening + 1, { + // isTag: true, + // tag: 'BR/', + // mode: 'md', + // comment: 'Converted empty paragraph to BR/', + // payload: {} + // }); + // position--; continue; } } - if (previousParaPosition > 0) { - const innerText = markdownChunks.extractText(previousParaPosition, position, rewriteRules); + if (previousParaOpening > 0) { + const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); if (innerText.length === 0) { + addComment(chunk, 'innerText.length === 0'); continue; } if (innerText.endsWith(' %}}')) { + addComment(chunk, 'innerText.endsWith(\' %}}\')'); continue; } } - const findFirstTextAfterPos = (start: number): string | null => { - for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { - const currentChunk = markdownChunks.chunks[pos]; - if ('text' in currentChunk && currentChunk.text.trim() !== '') { - return currentChunk.text; - } - } - return null; - }; - const nextText = findFirstTextAfterPos(position); if (nextText === '* ' || nextText?.trim().length === 0) { markdownChunks.chunks.splice(position, 2, { @@ -83,7 +100,7 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re comment: 'End of line, but next line is list' }); position--; - previousParaPosition = 0; + previousParaOpening = 0; } else { markdownChunks.chunks.splice(position, 2, { isTag: true, @@ -93,9 +110,11 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re comment: 'End of line, two paras merge together' }); position--; - previousParaPosition = 0; + previousParaOpening = 0; } + } else { + addComment(chunk, 'nextChunk is not P'); } } } diff --git a/src/odt/postprocess/postProcessHeaders.ts b/src/odt/postprocess/postProcessHeaders.ts index fffc4b59..f30d0630 100644 --- a/src/odt/postprocess/postProcessHeaders.ts +++ b/src/odt/postprocess/postProcessHeaders.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function postProcessHeaders(markdownChunks: MarkdownChunks) { for (let position = 0; position < markdownChunks.length; position++) { diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index 04f5dc32..a1f69efe 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; function isPreBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}'); @@ -24,7 +24,7 @@ export function postProcessPreMacros(markdownChunks: MarkdownChunks) { if (chunk.isTag === false && isPreBeginMacro(chunk.text)) { const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag && prevChunk.tag === 'PRE') { + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'PRE') { markdownChunks.chunks.splice(position + 1, 0, { isTag: true, tag: 'PRE', @@ -39,7 +39,7 @@ export function postProcessPreMacros(markdownChunks: MarkdownChunks) { if (chunk.isTag === false && isPreEndMacro(chunk.text)) { const postChunk = markdownChunks.chunks[position + 1]; - if (postChunk.isTag && postChunk.tag === '/PRE') { + if (postChunk && postChunk.isTag && postChunk.tag === '/PRE') { markdownChunks.removeChunk(position + 1); markdownChunks.chunks.splice(position, 0, { isTag: true, diff --git a/src/odt/postprocess/postProcessText.ts b/src/odt/postprocess/postProcessText.ts index 9aa5930c..13440f1a 100644 --- a/src/odt/postprocess/postProcessText.ts +++ b/src/odt/postprocess/postProcessText.ts @@ -1,5 +1,5 @@ -import {} from '../StateMachine.js'; -import {isBeginMacro, isEndMacro} from './mergeParagraphs.js'; +import {} from '../StateMachine.ts'; +import {isBeginMacro, isEndMacro} from './mergeParagraphs.ts'; export function emptyString(str: string) { return str.trim().length === 0; diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts new file mode 100644 index 00000000..8ffcbbe7 --- /dev/null +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -0,0 +1,162 @@ +import {MarkdownChunks, TagPayload} from '../MarkdownChunks.ts'; + +export function processListsAndNumbering(markdownChunks: MarkdownChunks) { + + const counters: { [id: string]: number } = {}; + let preserveMinLevel = 999; + + function fetchListNo(styleName: string) { + if (counters[styleName]) { + return counters[styleName]; + } + return 0; + } + + function storeListNo(styleName: string, val: number) { + if (!styleName) { + return; + } + counters[styleName] = val; + } + + function clearListsNo(styleNamePrefix: string, minLevel: number) { + for (const k in counters) { + if (!k.startsWith(styleNamePrefix)) { + continue; + } + const level = parseInt(k.substring(styleNamePrefix.length)); + if (level < minLevel) { + continue; + } + if (level > minLevel + 1) { + // continue; + } + counters[k] = 0; + } + } + + function getTopListStyleName(): string { + for (let i = stack.length - 1; i >=0; i--) { + const leaf = stack[i]; + if (leaf?.payload?.listStyle?.name) { + return leaf.payload.listStyle.name; + } + } + return null; + } + + function topElement(stack: A[]): A { + if (stack.length === 0) { + return null; + } + + return stack[stack.length - 1]; + } + + const stack: Array<{ tag: string, payload: TagPayload }> = []; + stack.push({ + tag: 'BODY', + payload: {} + }); + + const margins = {}; + + for (let position = 0; position < markdownChunks.length; position++) { + const chunk = markdownChunks.chunks[position]; + if (!chunk.isTag) { + continue; + } + + if (chunk.mode !== 'md') { + continue; + } + + const tag = chunk.tag; + + if (['/TOC', '/UL', '/LI', '/P'].includes(tag)) { + const currentLevel = topElement(stack); + chunk.payload.listLevel = currentLevel.payload.listLevel; + stack.pop(); + + if (tag === '/UL') { + // this.listLevel--; + preserveMinLevel = 999; + } + + continue; + } + + const parentLevel = topElement(stack); + + if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { + stack.push({ + tag, + payload: chunk.payload + }); + } else { + continue; + } + + const listLevel = stack.filter(item => item.tag === 'UL').length; + + const currentElement = topElement(stack); + + if (currentElement.tag === 'UL') { + currentElement.payload.listLevel = listLevel; + + if (currentElement.payload.continueNumbering || currentElement.payload.continueList) { + preserveMinLevel = listLevel; + } + + if (!(preserveMinLevel <= listLevel)) { + clearListsNo((currentElement.payload.listStyle?.name || getTopListStyleName()) + '_', listLevel); + } + } + + if (tag === 'LI') { + if (parentLevel?.tag === 'UL') { + currentElement.payload.listLevel = parentLevel.payload.listLevel; + const listStyleName = (currentElement.payload.listStyle?.name || getTopListStyleName()) + '_' + currentElement.payload.listLevel; + currentElement.payload.number = currentElement.payload.number || fetchListNo(listStyleName); + currentElement.payload.number++; + storeListNo(listStyleName, currentElement.payload.number); + } + } + + if (tag === 'P') { + const currentMode = chunk.mode; + switch (currentMode) { + case 'md': + if (parentLevel?.tag === 'TOC') { + currentElement.payload.bullet = true; + } + + if (parentLevel?.tag === 'LI') { + + if (!margins[currentElement.payload.marginLeft]) { + margins[currentElement.payload.marginLeft] = listLevel; + } + + let level = parentLevel.payload.listLevel; + if (margins[currentElement.payload.marginLeft]) { + level = margins[currentElement.payload.marginLeft]; + } + + + const listStyle = parentLevel.payload.listStyle || currentElement.payload.listStyle; + const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); + + currentElement.payload.listLevel = level; + + if (isNumeric) { + currentElement.payload.number = parentLevel.payload.number; + } else { + currentElement.payload.bullet = true; + } + } + break; + } + } + } + +} diff --git a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts index db7563eb..89f95897 100644 --- a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts +++ b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function removeInsideDoubleCodeBegin(markdownChunks: MarkdownChunks) { for (let position = 0; position < markdownChunks.length; position++) { diff --git a/src/odt/postprocess/removePreWrappingAroundMacros.ts b/src/odt/postprocess/removePreWrappingAroundMacros.ts index 231fa890..822ffd31 100644 --- a/src/odt/postprocess/removePreWrappingAroundMacros.ts +++ b/src/odt/postprocess/removePreWrappingAroundMacros.ts @@ -1,5 +1,5 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; -import {isMarkdownBeginMacro, isMarkdownEndMacro} from '../StateMachine.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {isMarkdownBeginMacro, isMarkdownEndMacro} from '../StateMachine.ts'; export function removePreWrappingAroundMacros(markdownChunks: MarkdownChunks) { for (let position = 0; position < markdownChunks.length; position++) { diff --git a/src/odt/postprocess/trimEndOfParagraphs.ts b/src/odt/postprocess/trimEndOfParagraphs.ts index a19931bf..8ff5f87d 100644 --- a/src/odt/postprocess/trimEndOfParagraphs.ts +++ b/src/odt/postprocess/trimEndOfParagraphs.ts @@ -1,4 +1,4 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; +import {MarkdownChunks} from '../MarkdownChunks.ts'; export function trimEndOfParagraphs(markdownChunks: MarkdownChunks) { for (let position = 1; position < markdownChunks.length; position++) { diff --git a/src/utils/FileContentService.ts b/src/utils/FileContentService.ts index 03b59759..32655989 100644 --- a/src/utils/FileContentService.ts +++ b/src/utils/FileContentService.ts @@ -1,5 +1,5 @@ -import {FileService, pathResolve} from './FileService'; import identify from 'identify-filetype'; +import {FileService, pathResolve} from './FileService.ts'; export class FileContentService extends FileService { diff --git a/test/odt_md/Issues.test.ts b/test/odt_md/Issues.test.ts index c79782d6..696eae47 100644 --- a/test/odt_md/Issues.test.ts +++ b/test/odt_md/Issues.test.ts @@ -20,14 +20,14 @@ describe('MarkDownTransformTest', () => { // https://github.com/mieweb/wikiGDrive/issues/431 const testMarkdown = fs.readFileSync(__dirname + '/issue-431.md').toString(); const markdown = await transformOdt('issue-431'); - assert.ok(compareTexts(testMarkdown, markdown, false)); + assert.ok(compareTexts(testMarkdown, markdown, false, 'issue-431.md')); }); it('test ./issue-432', async () => { // https://github.com/mieweb/wikiGDrive/issues/432 const testMarkdown = fs.readFileSync(__dirname + '/issue-432.md').toString(); const markdown = await transformOdt('issue-432'); - assert.ok(compareTexts(testMarkdown, markdown, false)); + assert.ok(compareTexts(testMarkdown, markdown, false, 'issue-432.md')); }); it('test ./issue-434', async () => { @@ -41,7 +41,7 @@ describe('MarkDownTransformTest', () => { // https://github.com/mieweb/wikiGDrive/issues/434 const testMarkdown = fs.readFileSync(__dirname + '/issue-434-2.md').toString(); const markdown = await transformOdt('issue-434-2'); - assert.ok(compareTexts(testMarkdown, markdown, false)); + assert.ok(compareTexts(testMarkdown, markdown, false, 'issue-434-2.md')); }); it('test ./issue-435-436', async () => { @@ -49,10 +49,18 @@ describe('MarkDownTransformTest', () => { // https://github.com/mieweb/wikiGDrive/issues/436 const testMarkdown = fs.readFileSync(__dirname + '/issue-435-436.md').toString(); const markdown = await transformOdt('issue-435-436'); - assert.ok(compareTexts(testMarkdown, markdown, false)); + assert.ok(compareTexts(testMarkdown, markdown, false, 'issue-435-436.md')); + }); + + it('test ./issue-443', async () => { + // https://github.com/mieweb/wikiGDrive/issues/443 + const testMarkdown = fs.readFileSync(__dirname + '/issue-443.md').toString(); + const markdown = await transformOdt('issue-443'); + assert.ok(compareTexts(testMarkdown, markdown, false, 'issue-443.md')); }); }); + async function transformOdt(id: string) { const folder = new FileContentService(__dirname); const odtPath = folder.getRealPath() + '/' + id + '.odt'; diff --git a/test/odt_md/RewriteRules.test.ts b/test/odt_md/RewriteRules.test.ts index 6dea2745..1ae71d24 100644 --- a/test/odt_md/RewriteRules.test.ts +++ b/test/odt_md/RewriteRules.test.ts @@ -8,7 +8,7 @@ import {OdtProcessor} from '../../src/odt/OdtProcessor.ts'; import {UnMarshaller} from '../../src/odt/UnMarshaller.ts'; import {DocumentContent, DocumentStyles, LIBREOFFICE_CLASSES} from '../../src/odt/LibreOffice.ts'; import {OdtToMarkdown} from '../../src/odt/OdtToMarkdown.ts'; -import {RewriteRule} from '../../src/odt/applyRewriteRule.js'; +import {RewriteRule} from '../../src/odt/applyRewriteRule.ts'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/test/odt_md/example-document.md b/test/odt_md/example-document.md index eca4781c..f6d6d473 100644 --- a/test/odt_md/example-document.md +++ b/test/odt_md/example-document.md @@ -169,9 +169,9 @@ This is after the horizontal line. * SubBullet 1 * SubBullet 2 * Bullet 3 -1. SubNumeric 1 -2. SubNumeric 2 -3. SubNumeric 3 + 1. SubNumeric 1 + 2. SubNumeric 2 + 3. SubNumeric 3 1. Alpha 1 2. Alpha 2 3. Alpha 3 diff --git a/test/odt_md/issue-434-2.md b/test/odt_md/issue-434-2.md index da8d8e59..6958c506 100644 --- a/test/odt_md/issue-434-2.md +++ b/test/odt_md/issue-434-2.md @@ -12,4 +12,13 @@ This is a cool new wikiGDrive! With a change. -![](1000000000000801000006000FC688CE4398B42C.jpg)![](100000000000080100000600C22E7BC1728488D6.jpg)![](10000000000009C4000007537CA7AEBC30C18882.jpg)![](10000000000009C400000753E2645860B6CEB342.jpg) + +![](1000000000000801000006000FC688CE4398B42C.jpg) + +![](100000000000080100000600C22E7BC1728488D6.jpg) + +![](10000000000009C4000007537CA7AEBC30C18882.jpg) + +![](10000000000009C400000753E2645860B6CEB342.jpg) + + diff --git a/test/odt_md/issue-435-436.md b/test/odt_md/issue-435-436.md index 026c682b..9cd8dcd7 100644 --- a/test/odt_md/issue-435-436.md +++ b/test/odt_md/issue-435-436.md @@ -1,6 +1,7 @@ ## Editing Existing Questions Similar to adding a new question, users must have the proper permission to modify existing questions. + 1. Locate the order to add questions to 2. Checkmark the order in the far left column 3. Click the ?Show Questions button @@ -10,4 +11,5 @@ Similar to adding a new question, users must have the proper permission to modif 5. From here, you may update the Description, the process order, the question code, or the response. 1. You may not change this from one "type" of response (like Text) to another type (like Yes/No). For this type of change, create a new question. - 2. Be sure to click the Submit button at the bottom to save your changes.![](10000001000003C90000010BE8F0122D00285531.png) + 2. Be sure to click the Submit button at the bottom to save your changes. + ![](10000001000003C90000010BE8F0122D00285531.png) diff --git a/test/odt_md/issue-443.md b/test/odt_md/issue-443.md new file mode 100644 index 00000000..53af8499 --- /dev/null +++ b/test/odt_md/issue-443.md @@ -0,0 +1,25 @@ +## Purpose + +Cover the [Fax Manager Review Session](gdoc:19WO9_gjSofplsd2pn11Y7OiNOIKuyVHk5yumdEaBJHM) and the [Reports Review Session](gdoc:1HBDLPvHAyvO8K1g2gk8mlzBn76G84bBxUUiFlgbFzbQ) to gather necessary information and identify any gaps. + +## Objective + +Review the standard  {{% system-name %}} workflows and gather information needed for system setup of the Fax Manager and any needed reports. + +### Agenda - 2 Hours + +* Demonstrate Faxing + * System Configuration + * Load Fax Coversheet(s) + * Inbound Fax Queue + * Outbound Faxing + * Configure Fax Queue Permissions, User Preferences, and Security Settings +* Demonstrate Reports + * System Configuration + * Configure Reports + * Pivot Report Access + * Report Restrictions + +## Afterward + +Document action items, distribute meeting minutes, create Jira tickets for configuration projects that are in scope, perform system configuration, update percentage complete and dates on project plan, and prepare for subsequent meetings. diff --git a/test/odt_md/issue-443.odt b/test/odt_md/issue-443.odt new file mode 100644 index 0000000000000000000000000000000000000000..da2a6c1932fa56ab095141eee56448fd8066f1f9 GIT binary patch literal 14109 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 E4|S@PdH?_b literal 0 HcmV?d00001 diff --git a/test/odt_md/line-breaks.md b/test/odt_md/line-breaks.md index ba1f2e50..69b41c44 100644 --- a/test/odt_md/line-breaks.md +++ b/test/odt_md/line-breaks.md @@ -15,7 +15,6 @@ occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim - @@ -28,3 +27,4 @@ Another line
1cell
+ diff --git a/test/utils.ts b/test/utils.ts index be8a3b43..39c919a0 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,8 +1,8 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import { diffLines } from 'diff'; -import {ansi_colors} from '../src/utils/logger/colors'; +import { diffLines, createPatch } from 'diff'; +import {ansi_colors} from '../src/utils/logger/colors.ts'; export function createTmpDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'wg-')); @@ -13,33 +13,53 @@ function trailSpacesReplacer(x) { return '\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7'.substring(0, x.length); } -export function compareTexts(input, output, ignoreWhitespace = true) { +export function compareTexts(input, output, ignoreWhitespace = true, fileName = 'file.txt') { + if (!ignoreWhitespace) { + const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: false, newlineIsToken: true }); + if (patch.indexOf('@@') > -1) { + console.log(patch); + return false; + } + return true; + } + if (ignoreWhitespace) { input = input.split('\n').map(line => line.replace(/[\s]+$/, '')).join('\n'); output = output.split('\n').map(line => line.replace(/[\s]+$/, '')).join('\n'); } - const diff = diffLines(input, output, { - ignoreWhitespace - }).filter(row => (row.added || row.removed) && row.value.replace(/\n/g, '').length > 0); + + const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: true, newlineIsToken: true }); + if (patch.indexOf('@@') > -1) { + console.log(patch); + return false; + } + return true; +/* + + let diff = diffLines(input, output, { + ignoreWhitespace, + newlineIsToken: true + }); + if (ignoreWhitespace) { + diff = diff.filter(row => (row.added || row.removed) && row.value.replace(/\n/g, '').length > 0); + } for (const part of diff) { if (part.added) { - console.log(ansi_colors.green(part.value.replace(/[\s]+$/, trailSpacesReplacer))); continue; } if (part.removed) { - console.log(ansi_colors.red(part.value.replace(/[\s]+$/, trailSpacesReplacer))); continue; } - console.log(part.value); } return diff.length === 0; +*/ } export function compareTextsWithLines(input, output) { const diff = diffLines(input, output, { - ignoreWhitespace: true, + ignoreWhitespace: true }).filter(row => (row.added || row.removed)); diff.forEach(function(part) { From 7a7aeccc720e97901b93f5689edf7ba24fd45540 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sun, 3 Mar 2024 20:06:31 +0100 Subject: [PATCH 04/18] Improve debuggability of markdown generation --- src/odt/MarkdownChunks.ts | 39 +++++++++++++++-- src/odt/OdtToMarkdown.ts | 2 +- src/odt/StateMachine.ts | 13 +++--- src/odt/postprocess/addEmptyLines.ts | 36 +++++++++------ .../postprocess/addEmptyLinesAfterParas.ts | 38 ++++++++++++++++ src/odt/postprocess/addIndentsAndBullets.ts | 41 +++++++++++++++++- .../fixSpacesInsideInlineFormatting.ts | 3 +- src/odt/postprocess/mergeParagraphs.ts | 37 ++++++---------- .../postprocess/processListsAndNumbering.ts | 19 ++++++++ test/odt_md/example-document.md | 28 ------------ test/odt_md/issue-434.odt | Bin 35655 -> 35378 bytes test/odt_md/list-indent.md | 5 +-- test/odt_md/pre-mie.md | 2 - test/utils.ts | 40 ++++++++--------- 14 files changed, 197 insertions(+), 106 deletions(-) create mode 100644 src/odt/postprocess/addEmptyLinesAfterParas.ts diff --git a/src/odt/MarkdownChunks.ts b/src/odt/MarkdownChunks.ts index d68fa6a3..1cd84c96 100644 --- a/src/odt/MarkdownChunks.ts +++ b/src/odt/MarkdownChunks.ts @@ -1,10 +1,14 @@ import {ListStyle, Style, TextProperty} from './LibreOffice.ts'; import {inchesToPixels} from './utils.ts'; import {applyRewriteRule, RewriteRule} from './applyRewriteRule.ts'; +import {ansi_colors} from '../utils/logger/colors.js'; export type OutputMode = 'md' | 'html' | 'raw'; -export type TAG = 'HR/' | 'BR/' | 'EMPTY_LINE/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | +export type TAG = 'HR/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | + 'BR/' | // BR/ is intentional line break (2 spaces at the end of line) - shift+enter + 'EOL/' | // EOL/ is line ending + 'EMPTY_LINE/' | // EMPTY_LINE/ is blank line (it can be merged or removed) 'H1' | 'H2' | 'H3' | 'H4' | '/H1' | '/H2' | '/H3' | '/H4' | 'P' | '/P' | 'CODE' | '/CODE' | 'PRE' | '/PRE' | 'UL' | '/UL' | 'LI' | '/LI' | 'A' | '/A' | @@ -152,6 +156,8 @@ function chunkToText(chunk: MarkdownChunk) { return '\n'; case 'BR/': return '\n'; + case 'EOL/': + return '\n'; case 'EMPTY_LINE/': return '\n'; } @@ -161,9 +167,11 @@ function chunkToText(chunk: MarkdownChunk) { case 'P': break; case '/P': - return '\n'; + return ''; case 'BR/': return ' \n'; + case 'EOL/': + return '\n'; case 'EMPTY_LINE/': return '\n'; case 'PRE': @@ -222,6 +230,8 @@ function chunkToText(chunk: MarkdownChunk) { switch (chunk.tag) { case 'BR/': return '\n'; + case 'EOL/': + return '\n'; case 'EMPTY_LINE/': return '
'; case 'HR/': @@ -337,6 +347,7 @@ function chunkToText(chunk: MarkdownChunk) { break; } + return ''; } @@ -489,10 +500,32 @@ export class MarkdownChunks { } if (chunk.comment) { - line += ' // ' + chunk.comment; + line += '\t// ' + chunk.comment; + } + + if (logger === console) { + if (line.indexOf('StateMachine.ts:') > -1) { + console.log(ansi_colors.gray(line)); + continue; + } + console.log(line); + continue; } logger.log(line); + + } + } + + findNext(tag: TAG, start: number) { + let nextTagPosition = -1; + for (let idx = start + 1; idx < this.chunks.length; idx++) { + const chunk = this.chunks[idx]; + if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { + nextTagPosition = idx; + break; + } } + return nextTagPosition; } } diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index d1829d30..c239e983 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -377,7 +377,7 @@ export class OdtToMarkdown { this.stateMachine.pushTag('/EMB_SVG'); this.stateMachine.pushTag('MD_MODE/'); - this.stateMachine.pushTag('BR/'); + this.stateMachine.pushTag('EMPTY_LINE/'); this.stateMachine.pushTag('B'); this.stateMachine.pushText('INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); this.stateMachine.pushError('INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); diff --git a/src/odt/StateMachine.ts b/src/odt/StateMachine.ts index 64c01499..3fb1efc3 100644 --- a/src/odt/StateMachine.ts +++ b/src/odt/StateMachine.ts @@ -16,6 +16,7 @@ import {fixSpacesInsideInlineFormatting} from './postprocess/fixSpacesInsideInli import {removeInsideDoubleCodeBegin} from './postprocess/removeInsideDoubleCodeBegin.ts'; import {trimEndOfParagraphs} from './postprocess/trimEndOfParagraphs.ts'; import {processListsAndNumbering} from './postprocess/processListsAndNumbering.ts'; +import {addEmptyLinesAfterParas} from './postprocess/addEmptyLinesAfterParas.js'; interface TagLeaf { mode: OutputMode; @@ -108,7 +109,7 @@ export class StateMachine { mode: this.currentMode, tag: tag, payload, - comment: 'pushTag' + comment: 'StateMachine.ts: pushTag' }); // POST-PUSH-BEFORE-TREEPOP @@ -164,7 +165,7 @@ export class StateMachine { mode: this.currentMode, tag: 'BR/', payload: {}, - comment: 'Merging PRE tags' + comment: 'StateMachine.ts: Merging PRE tags' }; } } @@ -262,7 +263,8 @@ export class StateMachine { this.markdownChunks.replace(this.currentLevel.payload.position, payload.position, { isTag: false, mode: this.currentMode, - text: stripMarkdownMacro(innerTxt) + text: stripMarkdownMacro(innerTxt), + comment: 'StateMachine.ts: replace code part with stripped macro' }); } } @@ -281,7 +283,7 @@ export class StateMachine { isTag: false, mode: this.currentMode, text: txt, - comment: 'pushText' + comment: 'StateMachine.ts: pushText' }); } @@ -294,11 +296,12 @@ export class StateMachine { fixBold(this.markdownChunks); fixListParagraphs(this.markdownChunks); hideSuggestedChanges(this.markdownChunks); + trimEndOfParagraphs(this.markdownChunks); + addEmptyLinesAfterParas(this.markdownChunks); addEmptyLines(this.markdownChunks); addIndentsAndBullets(this.markdownChunks); postProcessPreMacros(this.markdownChunks); mergeParagraphs(this.markdownChunks, this.rewriteRules); - trimEndOfParagraphs(this.markdownChunks); if (process.env.DEBUG_COLORS) { this.markdownChunks.dump(); diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index 1160f9d5..e3d669f5 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -24,18 +24,18 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // mode: 'md', // tag: 'EMPTY_LINE/', // payload: {}, - // comment: 'Between images' + // comment: 'addEmptyLines.ts: Between images' // }); // // position+=2; // continue; // } - if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD')) { + if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { markdownChunks.chunks.splice(position + 1, 0, { isTag: true, mode: 'md', - tag: 'BR/', + tag: 'EMPTY_LINE/', payload: {}, - comment: 'Next tag is not BR/, current is ' + chunk.tag + comment: 'addEmptyLines.ts: Add empty line after: ' + chunk.tag }); } } @@ -55,14 +55,22 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // continue; } - if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD')) { + if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD') && !(prevTag.isTag && prevTag.tag === 'EMPTY_LINE/')) { markdownChunks.chunks.splice(position, 0, { - isTag: false, + isTag: true, mode: 'md', - text: '\n', + tag: 'EMPTY_LINE/', // payload: {}, - comment: 'Add empty line before: ' + chunk.tag + comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag, + payload: {} }); + // markdownChunks.chunks.splice(position, 0, { + // isTag: false, + // mode: 'md', + // text: '\n', + // // payload: {}, + // comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag + // }); position++; } } @@ -82,15 +90,15 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { markdownChunks.chunks.splice(position + 1, 1, { isTag: true, mode: 'md', - tag: 'BR/', + tag: 'EMPTY_LINE/', payload: {}, - comment: 'Between images /P' + comment: 'addEmptyLines.ts: Between images /P' }, { isTag: true, - mode: 'md', - tag: 'EMPTY_LINE/', - payload: {}, - comment: 'Between images /P' + mode: 'md', + tag: 'EMPTY_LINE/', + payload: {}, + comment: 'addEmptyLines.ts: Between images /P' }); } diff --git a/src/odt/postprocess/addEmptyLinesAfterParas.ts b/src/odt/postprocess/addEmptyLinesAfterParas.ts new file mode 100644 index 00000000..bf6e1138 --- /dev/null +++ b/src/odt/postprocess/addEmptyLinesAfterParas.ts @@ -0,0 +1,38 @@ +import {MarkdownChunks} from '../MarkdownChunks.js'; + +export function addEmptyLinesAfterParas(markdownChunks: MarkdownChunks) { + + for (let position = 0; position < markdownChunks.length; position++) { + const chunk = markdownChunks.chunks[position]; + const nextChunk = markdownChunks.chunks[position + 1] || null; + const prevChunk = markdownChunks.chunks[position - 1] || null; + + if (chunk.mode !== 'md') { + continue; + } + + if (!chunk.isTag) { + continue; + } + + if ('/P' === chunk.tag) { + if (nextChunk && nextChunk.isTag && nextChunk.tag === 'EMPTY_LINE/') { + // continue; + } + + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'EMPTY_LINE/') { + continue; + } + + markdownChunks.chunks.splice(position + 1, 0, { + mode: 'md', + isTag: true, + tag: 'EOL/', + comment: 'addEmptyLinesAfterParas.ts: break after para', + payload: {} + }); + position++; + } + } + +} diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts index 583ae0e4..3454d2b7 100644 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ b/src/odt/postprocess/addIndentsAndBullets.ts @@ -2,9 +2,9 @@ import {MarkdownChunks} from '../MarkdownChunks.ts'; import {spaces} from '../utils.ts'; export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { -// ADD indents and bullets for (let position = 0; position < markdownChunks.length; position++) { const chunk = markdownChunks.chunks[position]; + if (chunk.isTag === true && chunk.tag === 'P' && chunk.mode === 'md') { const level = (chunk.payload.listLevel || 1) - 1; @@ -12,6 +12,10 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { continue; } + if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { + continue; + } + // let indent = spaces(level * 4); GDocs not fully compatible // if (chunk.payload.style?.paragraphProperties?.marginLeft) { // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); @@ -46,7 +50,7 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { mode: 'md', isTag: false, text: prevEmptyLine === 1 ? firstStr : otherStr, - comment: 'Indent or bullet, level: ' + level + comment: `addIndentsAndBullets.ts: Indent (${chunk.payload.bullet ? 'bullet' : 'number ' + chunk.payload.number}), level: ` + level + ', prevEmptyLine: ' + (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) }); prevEmptyLine = 0; position2++; @@ -55,4 +59,37 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { } } + let lastItem = null; + for (let position = 0; position < markdownChunks.length; position++) { + const chunk = markdownChunks.chunks[position]; + + if (chunk.isTag === true && chunk.tag === 'LI' && chunk.mode === 'md') { + lastItem = chunk; + } + + if (chunk.isTag === true && chunk.tag === 'IMG/' && chunk.mode === 'md') { + const level = (chunk.payload.listLevel || 1) - 1; + + if (level > 0) { + let indent = spaces(level * 3); + + if (lastItem.payload.bullet) { + indent += ' '; + } else + if (lastItem.payload.number > 0) { + indent += ' '; + } + + markdownChunks.chunks.splice(position, 0, { + mode: 'md', + isTag: false, + text: indent, + comment: 'ddIndentsAndBullets.ts: Indent image, level: ' + level + }); + } + position++; + continue; + } + } + } diff --git a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts index 1d0fa722..e13ef2be 100644 --- a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts +++ b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts @@ -14,7 +14,8 @@ export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownChunks) markdownChunks.chunks.splice(position + 1, 0, { isTag: false, mode: 'md', - text: spaces + text: spaces, + comment: 'fixSpacesInsideInlineFormatting.ts: spaces.length > 0' }); position++; } diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index 92b2ca65..a544ff74 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,4 +1,4 @@ -import {addComment, MarkdownChunks, TAG} from '../MarkdownChunks.ts'; +import {addComment, MarkdownChunks} from '../MarkdownChunks.ts'; import {RewriteRule} from '../applyRewriteRule.ts'; export function isBeginMacro(innerTxt: string) { @@ -13,18 +13,6 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re let previousParaOpening = 0; const macros = []; - function findNext(tag: TAG, start: number) { - let nextParaClosing = 0; - for (let idx = start + 1; idx < markdownChunks.length; idx++) { - const chunk = markdownChunks.chunks[idx]; - if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { - nextParaClosing = idx; - break; - } - } - return nextParaClosing; - } - function findFirstTextAfterPos(start: number): string | null { for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { const currentChunk = markdownChunks.chunks[pos]; @@ -56,13 +44,13 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const nextChunk = markdownChunks.chunks[position + 1]; if (macros.length > 0) { - addComment(chunk, 'macros.length > 0'); + addComment(chunk, 'mergeParagraphs.ts: macros.length > 0'); continue; } if (nextChunk.isTag && nextChunk.mode === 'md' && nextChunk.tag === 'P') { - const nextParaOpening = findNext('P', position); - const nextParaClosing = findNext('/P', position); + const nextParaOpening = markdownChunks.findNext('P', position); + const nextParaClosing = markdownChunks.findNext('/P', position); if (nextParaOpening > 0 && nextParaOpening < nextParaClosing) { const innerText = markdownChunks.extractText(nextParaOpening, nextParaClosing, rewriteRules); @@ -82,11 +70,11 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re if (previousParaOpening > 0) { const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); if (innerText.length === 0) { - addComment(chunk, 'innerText.length === 0'); + addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); continue; } if (innerText.endsWith(' %}}')) { - addComment(chunk, 'innerText.endsWith(\' %}}\')'); + addComment(chunk, 'mergeParagraphs.ts: innerText.endsWith(\' %}}\')'); continue; } } @@ -94,27 +82,28 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const nextText = findFirstTextAfterPos(position); if (nextText === '* ' || nextText?.trim().length === 0) { markdownChunks.chunks.splice(position, 2, { - isTag: false, - text: '\n', + isTag: true, + tag: 'EOL/', mode: 'md', - comment: 'End of line, but next line is list' + comment: 'mergeParagraphs.ts: End of line, but next line is list', + payload: {} }); position--; previousParaOpening = 0; } else { markdownChunks.chunks.splice(position, 2, { isTag: true, - tag: 'BR/', + tag: 'EOL/', // Or BR/ ? mode: 'md', payload: {}, - comment: 'End of line, two paras merge together' + comment: 'mergeParagraphs.ts: End of line, two paras merge together' }); position--; previousParaOpening = 0; } } else { - addComment(chunk, 'nextChunk is not P'); + addComment(chunk, 'mergeParagraphs.ts: nextChunk is not P'); } } } diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts index 8ffcbbe7..d9891897 100644 --- a/src/odt/postprocess/processListsAndNumbering.ts +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -35,6 +35,15 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { } } + function getTopList() { + for (let i = stack.length - 1; i >=0; i--) { + if (stack[i].tag === 'LI') { + return stack[i]; + } + } + return null; + } + function getTopListStyleName(): string { for (let i = stack.length - 1; i >=0; i--) { const leaf = stack[i]; @@ -61,6 +70,7 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { const margins = {}; + let lastItem = null; for (let position = 0; position < markdownChunks.length; position++) { const chunk = markdownChunks.chunks[position]; if (!chunk.isTag) { @@ -88,6 +98,11 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { const parentLevel = topElement(stack); + if ('IMG/' === tag) { + const level = lastItem?.payload?.listLevel; + chunk.payload.listLevel = level; + } + if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { stack.push({ tag, @@ -97,6 +112,10 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { continue; } + if ('LI' === tag) { + lastItem = chunk; + } + const listLevel = stack.filter(item => item.tag === 'UL').length; const currentElement = topElement(stack); diff --git a/test/odt_md/example-document.md b/test/odt_md/example-document.md index f6d6d473..47947eee 100644 --- a/test/odt_md/example-document.md +++ b/test/odt_md/example-document.md @@ -96,52 +96,32 @@ This is monospaced text. This should line up | Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and *Markdown Here* -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. *Markdown Here* supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the [highlight.js demo page](http://softwaremaniacs.org/media/soft/highlight/test.html). - - ### Typescript / Javascript {{% markdown %}} - ```javascript class MyClass { - public static myValue: string; - constructor(init: string) { - this.myValue = init; - } - } - import fs = require("fs"); - module MyModule { - export interface MyInterface extends Other { - myProperty: any; - } - } - declare magicNumber number; - myArray.forEach(() => { }); // fat arrow syntax - ``` - {{% /markdown %}} - ## Video - From Youtube: [Google Drive, Docs, and Project Management with GSuite](https://www.youtube.com/watch?v=v6QAIWLCz8I&t=1743s) @@ -150,15 +130,12 @@ From Youtube: ## Horizontal Lines - This is some text separated by a horizontal line ___ - - This is after the horizontal line. @@ -180,7 +157,6 @@ This is after the horizontal line. Some **bold** **_boldanditalic_*** italic* text - ## Equations ### Using the actual equation object @@ -188,7 +164,6 @@ Some **bold** **_boldanditalic_*** italic* text - ### Text equivalent *E=mc**2* @@ -197,7 +172,4 @@ Some **bold** **_boldanditalic_*** italic* text 1Footnotes should display as a footnote, and should always display at the very end of the document (page)**?** This is some sample text with a footnote. - - This is some other data. - diff --git a/test/odt_md/issue-434.odt b/test/odt_md/issue-434.odt index 6b791d92605a119e62a474d1f906bacd300f472d..79ce9ad25aff91104172961f1ee721e3f75a415d 100644 GIT binary patch delta 32889 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 Y{r9)MN8o`;1Oag)DIuGK|LO350O0;~X8-^I delta 33209 zcmY&<190at*Y4Kcx?5XYY;9w^wQburf5o=l+P3ZXU)$c=wr$;g-|xFK_hyngnUhSC znVd-Fuw4dq}Y zv4`mYtBfZC`yZ_X7{~v%vi;v)@sQyE?H2CFJ-SvosOf%991ej^T7rNAtcS*{D)Hle+W7kzGq0H19Cp3`EIL+K zc_g;pBpzS($6{|?+j9SnPvaGA;;AcTwFy7KQ}r`&e{$(YgMiLE9jqNbPao@Vh*{z5 z?KZg#lEj$~Sja+}9x;AYZwNz2S)rVhw-Q5gK%^QtIikf`$Q=qm)GZ zSR8)!k_lnVXrx?k^1Hf8M*CZ5akN0vj$6+o*G_avF3kvH$1s+wmwG0il(Xzty9EM3 zU||b>)=DkM0jMv*)m;xg;NHBwms%{4xTf-u-O17DV0;a3W zCM^h1vKlPQ3L0ay?fvU%Qu8x&bpHsAd0%2XT>^h5hU`yh>ODtCEq7dYIRUKBwRe~N zK_kMSY|4V){2Pj)D+a{Izjo5gu*o})Uk3?~l^F)7ml>m`lmUok)X*)Sl`q%FWkyN5 zsqplAw(v?;yOy<_=@a}DlL9~Zek4^@SBGC*oG28|tp*D7=%jizd%L`sRh^D09Tt@}=N+G&w`keb z7WzUMSgLMkq`)B0uMvsui<6U^m!f8gefv=x?A6MGsk&*OgWOD8a&KQ(jgO4TCyuT7 z;|G&udiu%gsC@pEG7X|%&MfBAsTqD6yNrsGU*dy!%F#HZ#H1D)({PSTP+S#=SPYOT z0NmQAgfWx@6kBSmw&)hOznk|vjEPOXjfJ@7TtCv+S^)3%A8s$Y#}$erTcmAyh0d-n zJ`GxU{NG}L2F^PE-c{6#%LufvUP+1-7n8~`&^Lx;sm0_W)Xzd?R$;|cLJpM`Q%Ol1 zkbI~ORjjZhU(IcvuG$HC)9}WeR6LaG@wpX)N|d(G7{bq=`Xttt?OO)N0$+Cc>$N>{ zbmGBAk%4WL15mh%CoOtW;#YV~AgirO`z_^%+qy2r<~g!{r2sxb0oFH});Ith%`+v+ zMvew8pM=kRdHNC}msq_Ts%4rerv|*Ue0s7jJrTm>lJHh>k>H)Aui+@%T__iidOMu= zxNtHcIaD=t&0;q~`hqQ;EBQgFlV0AB;$$Qp2AD5UUI;o&V$H4$D7MQC>4hPaZUcG+mI1lR2Fjv zf?PL;+atkuvhvzij)C3^NzIA(8>77DNSR4uQ!q)El)S@^6pF>cnfbxYpHzs&xpV}1EOvudr z)D3v%#x7s%zQGa>p**bBF!x@8u6*Lru}?D@*{q;WyJBj%Hf32LFJ~;;n8P9_IAe!u(yQdnf!Q`^52so zf;>o2!!E;o@SPDGN~At*+Gm0YNfM?bk)6G?J*aH_BgoMh2Ca`qH1PBTDHUpm5fZ4- zdEx^ka6=c&PrQPkgK%hH6#9*uO%#K`iPj;A-#jIPme+L?q7beb3-)Ro?&Mo^8^LR^JxZlJi&@&m7xx3bacZu_uU zka;RmPN7hChcGF&17Zh^C`2%uVHlS-^F?uvA`w(t5%koO=729XR8dq2*NQOnw5ak< zp*-zu{Fr1{si+2mAr|;}erpJm)bHq4nG5S5_k|W~N{mJW8>*5_^fXv5^gyaE{x?4@ zXWe>;aPYmZv(D{Pu4RnTo8@_v=Ub0pTk_xbi3!Z)oE)+@7A>}h7FI2Y)x;mJyByh9 zRiz1q^tI%(&;9b#<~s(D<{V0|-IU+f&Gl8X%CjLcr0sI1`gSX7-x6o7{Yt zeANrWWpe1^RsYpIxhjp@004d+WvoPl!M<>a3vnD!NiH6@cZ~uEv-Q1;RX0 zP3`n&2-T4;AN4CAQ&Ke6_h5&2>iDl{UNFxZGF>ZGm&mJ~>dldL9UF-zZV`72Fc7v> z8uhAp2>7i0kbYyXIL=W@(r6#wkU(=zp~HdW2*Cbm;6{Td#%%`B;}H zpz};s7_%t_4zM~Fp95~=3q>>|Ki-y(D9y(pDBf01BqpcX!QQ|tYdGFzw9+h8|C*Ay zLpb^#%!9vJiWV2V-RbyPLxCQ36L03{V_^_<0-gtJ-{T;mc(as$`W^Vc@U$M;O9*CV z{itF{)9t-_H%By)DjWNl?3Mq#X5p*ZNJPwyh?c)(b|Ul0p8+AlR9U6mab|xXzVP>lzkg|;ceis`w2Us^Euc2`rrH37pmNY z0p`n>Q>OoW-~KOtQ@8!uD|xB^<;!LKF@iV%4h9AbhYI%%?;9*K8X^`B3IY};77`{g z5&<(RG6@zA6*eI=5(x(`2@Nhe`wwgc3S0~t5lW1|$};Z)$G7333=`o*gT z{9;fM4cQkGKCRFM)e1akucCTur$zeGn8>L)igCRGq!ZGbntYsGO@R__jIsya&mHX@pSR< z_H=RbbaVIiaPji;(zfx}arkX)<8R>#gjo5;xp)UT`NrD#Cwc|^aSBXu3QBeTlk5_j z=@}H~9-QhOn&26h?ircu6It|;e>}vko+cnn5BF5J~#NRu~-zGWOH7nLP zH`1dh*5~){-+@7)A%8-G{)C4G1%&?z3J(ttj!1}(i3*ELO^67NiH=E)4Npo+0{o)$ zgOf@^GwTvE3NvytqH?Pv^FYb@1qrzo8My`N`Q=$9#hK+5NySYiNkIh}3ALGja+>pG%PZ?D>nlp@S{o_~YN~2m>dKm$no=rzGHM5NtGkPu zhDti7Ye3!gZT)qfBds01UES@~K=)Kd&wO+5Xw&dQ=iiOyiS3T*y`IXHf!d7j>fGVx z;=T^hbVtc}cU6CHV}DQkcu&K4U)#iJ$9zxaY=8aCVC(EKXl|r^d9ZeMq+xZuZR2mp z_IUH|bjQ%p(7^cY)Xeb6%<|lL|MJYp;?mO4(!t2a_5AwY%J%x?*7?K^aKF5Bv~+a0 zcJ;nH*SEDWy1z8Iw>)+<-*dV=aI!YKyFP!lF?zZ+vAZ?Dx4m|Fym7WYce=NFezJaf zy>Y!gaeK775JJMQ$8SA)kc|m7A--T27uCGbU1bFMN3&`MuogPIS;6R=_ zZ>cpIBr@1M7Yy>D^d`bfUJvB2vfy81!V>N(*E3^n%Eq#RN;%P9scOIXdtM+it9Wwa zZn!JAxdsti?huuSz8E3Ra*#4ZR$qQS`+>afpjdP}s47(6vVPo|yT#Y*q6dIBgkrt= zIA6{;*aQMxSEjE_PC3^-pJ}f1H(dy%=WHLLkn;P=Xo=KyOOe1$3QwY$%1n!8f4gQGf(UF@lIsQx%>zwWPFGGjO%ThGbb_=vJP2xFqiVn3 zpDmqC^Sr^ufJnW_ZHsuz*nX|`uMo~j4!L$d%rifm;a1rRlK4K>Dli|l+rt43uIg0? zvDHt86c~)d!DDcdURf#X6K15nj>g_Z(WsC2*toUz?FhXf`Vg_6d|=0%!)lk_C7k0K zQiVjsvWmu04jVx9|K#riy=#emQs>oqPka{KFC(qa4$hxXd`(bPIA)O44{tn1sZox< zaLlT#&vl|^$QM}S28POOvYgXeL8WfdN0aT?+hA1CgMn5Qk*`WPi7o{td{pUszjbXT zq9Z;`pGmuPhU8s!Qq;D4828YQQ~6gfescd*x*eXqne=1r+suI&z9K-bL>T4EXwECE zS2n6sEQ-^x5L&FocOI`k!M-Vl-t)cVf+glpB-{qiV92tf2QJg&!ahZY6v&n-=it>0$^EoOsIXd_YrA#o|;GN7>!|W@ORl z#+S#FbezebN6uC91#Ht*yp-wQUZ`a5k7PuMsZ+Y7yrSZzk|+&E?ytJsVxygU@$gyl z`e+LHfHz)7nO*GX&GB7)T+YDD$z4n~Wm zGOQ|s=#Lfw;{Blz1@7^`$}!-Hf#ACz0g|3Rup)R#!;}!DU-*~O##pCZN8U}+*p83} zj0C{ox);ERZy5^0<@B*z{qE9g46h_n>jQtmjh3l-QlVtifeYeS6$TRD>B^cW<~|H- zW9$!+1LpiP$-z`5?vXqB!f~%txs<=O-}h3KKf;ih+k+C1u0rpjV1wnEQn%P#a>F4n z;Uj(K){y5;faQjQpvpiWq++a9s$)^Ps-O3`Qkjbi;e%w_=0|;E_KpP*Y;~wlL=d}U zaUrL2K;d1_Aj2cSI3PpxWQnwt#h*qGaQj^VSnGmHW(NcLo*68yy)*eDpmva|gE?ol zLrd;Yi$YAe90~f;Smz+&0EZu z)nLW9jJcqDw*_{a-Qq4V*dsYgO`x<+yea8$IT1uszUCY=yF@G(vVJREQ zkYXY(z%dvF*Lk064tVt}0cjn05bQ_NwFWzHkz%)pabJHAs71@mcu1^NY)R+o4>uwp zy+gt3tHEOJocpbm)GCVe%4sXeasC2)ZLK#%Y5pB4CnDeIRx~N^EwYrQb&YE;%`*@# zqk~gvRng3?$<}a^orLbOx4}r7pI^e+y7#H_7bD}ES=SzlBN;f4#@}tpQ1j9!0 zIE=zTLmE+F-*00Zp>5stNaJq;CmAfvOe|>GL^{IU#FZ)2@;5scl#K3H&}-7xgIEMc z^0*WIF_^Ks9MSW?6p<6K9h_Gh(gmlX#l%zs1qLMZ_EMTlhlz?TUcbtuwGFG7fNtX_ zzZ#o)*}cE_nB`lav9z84R$K-ix1#o~_}K{!%~0~AhkbfdKK^?4?mw&pb-9nK`RA=F zPHmA+Ll~$-nO>K?eecspX20~Dv6--ubv9EhvkYQvAY-Jn%AW3L=%LlZzM+&tyP_5a zO7Y;m)otyB4A7S?qdoEX<})S{2x5qfqqJ(U4wZJ3&1CMWvxOtAA(N_ozEf5M)kV#R!QY&lB1`l?yOGuGhRfX#N% z)2S3Si5gOp;&92#J&)WqEHo&a?}{TO(x5VY;p;?Jz*aCzGbw){VjM+DSgdw`_0wS+ z9!Y4(aUY9?{3DnQK;5{qRiZ>zK8WjgMA&@`??wjuaNhs&siqiosP2Ca3k4?tQZd1@ z@tQFJPh{BtCZ{}%%#`c6tugt9IdK;hMUK7G>;TY9lZ{EkS=dQz%6Vm59(ls_xWl}& z69$B)#@N(*lP2$XkMm-I+%dbB7=@SLgToKQiOULLz~fwja!hGmlly;Xr#;yXs^=Av8K%O$8>-4j3U^P;(`Gld70jP`jE-2XHh<@eZ{5?MEp*|Lanw42+0Hm$RkqWv!*f6z9?F9E!S{UC$7N}ANi^+^S-=% z#5}uqMhH;}=4Ie3UMdUUQ;2TnQ3y?(asQ}2=gMt3HHFlXRoxdHDJYaSA*xyp{3TuWN`t&ky!G>s#1}vh%lew8{2_%xW2r7$Nk#SvTg^Grp=$fw141t@w_Y3*mv;76Ng04Q z8%glQ)s?|fksSvvP+!Faqo1z2mm;R-QREvp%?)Nj2iJ%mlR2qauNd>3Tk7P$ny^^c zZavjc3Nu*kD9B%n%WMg^qJY+#8@@lRFxuIOI@pUDI9CO@0)3)_B@zs4oTl{`f(MI4Ezt$TQX+7h5veQ(yj}6s^BPJ(??OkOeonSc3YS zm9*COjxRpV%gb}9F}9WLxAyH$P+CZy5My*!88q_vse&J*gw+I1#&=GF+kiHrW3l{M z@s9gq{cs$Huw#>F$udS#BHCK?7|!JzPIpoR*5>PF_*B>3e3q^rI7dOu59^awYK3#R z=2}D!McOXT3L~yC)8f^okhwy?(K|T||DOw7!IMkuoI3e^jM^fo@RZG#)@PBtNkl@2 zIwKIztNLeQ_L1DlEbP=*#K7%^C3iq9l2*dKY(gbS!$0304`-izi+zD;j9_EJE#f&# zNDe~aLlrd$T>+uHW{wX83pa&PYWaD}u*9u00gB{Qa3?0B<1UknGGR8|7I)#MMj5#6 z4a+iHD?f;J`MR_9YzP;T2^;)@E4MAk*nU)yZSz}&NiFpQIsyt+9sv6>I~m!hTxBIf ze7XNH5wkruqg)xuQtcGa*xE~~!qT}3eY)TNyqj@6Usa1j51>XoStNY%9vV}%)!y#^ z`TN1T0p@(=2G`Ra>$etVDb2Bwk=m>P(qN(;Sj1b%NOz!em|UZ^hrJZbBo1MXi*BNB z64i9|;-W!z5O!jxezR(IrpOd55vkyEFll4)=zf zAFKl(qu}jBk((&7DdU`1R9k&fp6Y?EBL0H{3%zr0bWKCD1(~3r?lCzRJPpHaaWzPD zQliSEvfn7dc6q&1b|=Bl;+4!4PYa3BA)huqoF=Pgj6gX!I*Kd%_nae!*w=SO$ za>)ttM%e!AaSw$cm7IgSaC<}&S|0^`Wbp(qQ!d=xyq{}&2O^XrkgLhdxAiX&oCAOR z8i@xOnd4V(KC*5Zpy0ZGZ$N5s%P6Y;!pdRY!FlO@PA)lutQ{jB?EU zy3Xw9?R6kk3RoI^cL}*FJ6h4(!j%E>p+%Q{5-AKoMUmCl=?mU{tt>TvcH8b?cvmfg z(y|RI-Ptsv>ANUBdXWbA>@%3zoYD1o9ZYh~dVT%-nr45DtYSYSH{XD00H}|rhs<@)X zP|Y4@10?14K$jL&mNXO=&&$1>YEz-RNJK54cS;SvQPa3<%dOxtgTiRw8oMx}hAh6W z*v1@1R~3n{SXrs!wbgi)Pjl0dTRh|RQ6(WSIb#o6a~sLZgM?s%wFv?oqdsznQ0*Z4|}0Ug-zrlkYGe|~#Q^d(~b$;C`y{%+mNRQ*tASCZRpk`f|QPLtu*ZG|!WjMO0=nuQ}T8_8f1 znC0p3czXUFs~#U07ifL1j_~aWnv{tVqfL{9DVk)975Di7xzpo&s-obZ?l&00Cyf_U zZv9^PR1t-&jV>w(gH@CkXM_Igw5VFck+c+*AI3{ZLVjL*=h4AA z-gGihQ+z6=q7obv)#w}$oe`OGCv%Kk04Sy1@?)?bRf8L|jd1RblqZXjn*$uvI?R?HJ}Q&W2_N3Hanel+%;k z=Djax_)7Qy_9hQA`(A}Uc@k^aXS5(t-08aaT>W?{a$Fg5#iEd_olYb%?|2Xdm@SNI zddMH-G?e7NIo47O)VGKRN{gYGbMaGm+So_3%cKt$n71(1vOji+J>(L@8N(0_O@PLY zJm0GJ?MG5cg9V>$#-Qe^kQC$(63h!h0f>`{o~g+%rFWUdmQK<^07qyAKH{d71)EZD z*`z_NVwa2%3F6H_B~9BH+c!HP)%2hH2CnFXn?%9ov-|!}5?+XgM5L)~UPTjzJpTZN z24#0zuT*bZ^7m=G;P2N{FbMO}U>~ZVf1y-17VNINlw#v9S2eA@G-HSOjh`Lg?Ec=~ z`@CVUj)^Q1T8c436W&mZZ3Q}+Y$eHny#){&^We?W;aQx|l7&J?k(~B%YGH$53*bK#q|RFE|NRa!bGk>O zb3Vmln7?ZEB=_H2oD&id)!jd_myu~D?hn}MO($O>Z|;T5Q1tSRK5~j#}@z%03Co-yC?x z-AC}+U>ik6eM$;klNh12Ay=}PK)po3;%oN7$f6O+;G}Q;aTgABm0%ah$0>8F3I)@^ zXm|B6GQZh%#Q5tc+(#s8&IVMJ-D*oHmWCmv?AK_cG+_Dn5wKj)--d^$V;n-n(1p zRJ*nEW=53&3@d;eeRrj~lZ_xd8Ls_pJLLX~yGfOZuCQW=D)f+cQJa7!KMjJE_XRFC zt-+h3Y_GA-Bu~+;Xmf#g=(1YKC6rOCet033x^cPzcfGNd<0xtLX;i-=*j8lT#$eSr z=b5%r5V_XiI;A+XDs?V^#Q4^v{PE|m>pR<75R;S|bRz&83{7nRw{YAxSW5Z9)7c}B zmu?ZJs8_Sg-pDyp=ON><8U!|0{7*0-z6 zrJ;T}J8OW=*HkA&i5JG3WKWhSF!hN5qW$?~84`u$zpnw7kZaL})>GA51vjJ3`M3IS zW%R!}rcBLD+z$I9TY{IO5&9{77!EG%L7A4FyA?c#uOpo-DK}cw(?O?EBvpSay{5kF zxW!QjXt`gu)l(YPA*PTeq_;MP0|mE6uh6!Vz}&M~;B`GiQ(EUqBmU9BB9_;} z65DC#f=>Aqtg>S$_1BUb*~_2`)6^z~bNrQkw!bA@jI?=`+qg1E&!j9RJ#U?Y=ZNLy zLg#eOC{vco9E)((uXrFHysXrpOzf7xBhHCoqvaIQH&GEJ=DPc63?^g(5BAID_6V%5 zrXR5W3^-lzU2lrK8Z5WFXJs+tLul@DxzMC48^^y!iQH(Qp$KcET#{d&7h{w~CBv!3^}kAR}ouzh0!)UYSLEyXWtnrGy& zm@aN6Id0?)xvjZ-jD?bk3yvf;Nqpw4H>z*X23WLJZfTL_7#uZmcZVy7!l-rQ$BR6_ zv#;N=XjMEIifl3}x`sQ^-f3q5o|GXDNS7PT`l}h3n!E7?$;^`i-w`k@7eCG~hM=qY z-5pNhqxr6WP8wB%P2$MqM;FS1Lk0#gBNx6fyTW0vd_p`#biHjss5%S>BUtm6Bbc0d zefYzusv(fRN@99{_|Jc~vZQVkdH!tLKNWO860?_{3bGl#SOuN>!w_Nvo~T_iuXrT? zK*QqihXcI?+PzM1CgS&W7{ZtxEEWhT$Is);rFS`B2lvNEBKIvQYkH#d8onzcRD`<` z*w|}YudQl`PZP47KV9K@o@f5w3gjqZ(pqayD^Po4pbG|J2%o}E*ebsKqvJTO2;GT0 z)|5O0F3?qYuezIj|6Hg6+X7uUIon;H#C?>8pNG)~?i>pcsU;3Amw@DF&Vce==zg@o zJ3r}_&;^>uYLb&@X9zRJD30C%@7l6UWDN6trhw z@2)?swtkbj7)zTG8r(?(v-aDz_@>Q~9QQx14S1Tcy)L z)2L%a#ZSTJY-p#Z5=A^WN4{7k$OYj;-_!gn)kTY8{2*PVB*n8;A?!@tN@!%R$!L=( z;~V<|M~AXe;_D_L%xF2jGt%AnK@$`Sb?7+C#sf{2vvc#&3jqcEh0DlH0ZZ{9`i3^n zJN~2(E~mN|)v}&@3Jn?fImN(yCj9-`3j8us`a3qovfyYt5hTU-0GbY?gL8_3S-`kA zN}vAvb?6^Ff~+TERAH!9C}(G;ZQb7<4DkqI{y4#*P|L&s&(v0GoaxoqqgIath_#`# zE2ic^0j?lu9ws6L7F6iWVc~<+RN@@-9TsEW5`{f_~5-25EZAvQAaS$kw1 zGnD4*hi$PyYK~9;B(dv14;WcQOOku9V{*JMQ~G0e`*M|#jL?HFpIgQAhpQ;&s(_*G z1YxIKCi2<`%d;stqyL8h9kv+J5v(_!Hr@Dm$pFvZ?_*2~4ZF4o58M1sa#+eXg=>K~ zL_OsMUaJb1D)Qg1d6vvGQ3MWR`M=y4PcjHkvn(_Kd^{hvuS-63>pOm_n3vcQFo)i| zRQU2o=wh0fWYzk;v1<5?3wRy_AROB2-Y0dGrCPN$`?YUrPU5|KjZc&Ilo_bbJ1GO##o$H*i@--Rs_ePmawq9)-3?9Gi|1~W zk=za-AsYL`c$9~t@mLl;muNok4^Dbe|n#N5m#Y6&dbfT^P-?%rB0aV?j!=GZ>r*!_i}xmmalN3(mnRQ zqYfQ+(Kosp&nd-dUaNjqcH5P?Y3Gn=z^UF-P=!kFm(6iczUw zS>kAS0*Ql?s)F}e#ql{m<4}5*nr*JkhqQ(-m~%}#`J@6vxTi4vd$o&m>kS*@RE>et zF3^3ivIAU44;(Fw^)TGt(A1GC#*8Pg)Rb4t)!LJ0;hgKy#Kfh9+RXCAu8BSE<(~ypQtjwyqD=9jhva z&)Jn`W_La_=OA+S%yO9bBYhUfU@1VB-mV7eva@bPT_6h1E47h=$~CE}2#-hFV-@LLQq1S%a;ZNHEE+g}wosfZL zM-7FmNF9whI6kV%hIttds$r4cdV5iYx|lL4tC@pH=%vvC!5!shob@ecY;^z_`e!ym zM5-^goyls&!$#JTAmfmZDMN}wRrYOn0|OH-QEyj_0o!U{Co?zeiBUT$jJkXW7-~Jl z`+ZG5>TSucqWvVEYgC?w`uS{Ru~ajUF&Y?h6! z86=b&z9`lwtNt5LA`SP`EXqF)A6wX$20M0D>(8wl&*9+Y7T<%=SzudK!TM6F`pw0g z`Bv;IPuRsF`N?zi{2gfB`m9gqA9bpKLHOAb5V_v>Xm0@=xdr8lYXG3nm!h(xL0KhL z%LVz32`Am;Keppgw`((M`XiISTs~Y{EC$p#f4ha=VsT`+n`WBgL$CTA;z-Rxt~0Kg zG|5pq?^l&KAkb~JfHCeoUvb_WjAm}M^h1h-dyOaeBP4Z)k)I5`5!u9w*m2|5z1o#b zJ4ajK4KmGf4PC_;$S8irc&=7i7^sI{*Bmd~8@M9aekui++pB&P4 zyIYBAzN-6;nKcYd-jv?>*q^~{C|Ya$aE!$_*yXy??gKFYAO`<)@#u~?p6#T5a&fWu+)slyfZXGv&s?pits`Z zkWU~(z-X+?MGB!h72HsrOi_RlX2QBbyEUs#ugU zxX)rbAUh9iT}?(12Q4;?G`oA=q8&ug7xWQB&GFz(C1S%UFiVe8UV{k|3AgC;#zB|$ z{Cr8{RtdHK7VS!c2qnRW1XU20j4*7?ObAz;rZ#_Jx(*jf; z|6cx#Gtxzho!<5GFvjo}d67UF#$qcB+eQ23dKvCv#t;;YH;pga9 zW@72@W5lFFwLaAn$Kp8Yp}TBKR8|k_%k%7JruVaf z9b9d0XFL&e-1Fr%rgxL_Z>9E@;@$-jfLmsrdMzk|PdY)NU7X&+JEW=@i5FuVEg7Hj zbi4w|>HP>sYn%dfmuTsX5k1QR-y z>KdTrYiww^>L>h-TQ3tS)u;;sK{YWh55@yn35&6OE}Nj7N#e0-uKH6E)GbxRpEREN zmqOG9nwkUq1h^)}yYl{ZX}+*)8^^fWR0si*U_nl1*3bx%dDb)tWXbkHNbagTZ;!X) zz>gA7crhB``U5_;Yef-)oXBS8U)z<_D-aZuiE8m&h_$&EnKcngs{YBpsUc}~rXvsF z#?KiOXm{mu#m?z#o1YkwpN#8#e$hy-`^QH2I1$vh&M?K+)zbkT#RoxI#dnelBamL^}obnt){IzrGM;fB3(eOvo`}8lZ5-wB^9ogW*%IA|uKzJIw%W8@Gq} zI;6OoE*6A0$5qx2zVFpV@0T`~Mhz-3G&6@*a~m8DbU!j(J&9OnWX+Wy$-)~<+b;Wc zSGZ*Hpm8Z1RMz$788ili2^R9laD+%wCHLF{9XcTw4fZp&peJn_G)yk$-AAW`*LRQE z=jx)gD4vAy$dXWh*pgVEa;5@Sb9y6azd?lGq7T0}7vCn~B0as{3>vlCBTKApa+y(` z>=8JACbrMW&)n3xm9VFJNoW2Pd7`6Pbw8cQxfZsPDep#|6>}(#Z}lq zx46dLI5KxV|LTMDNvzxfi*XUbu)g!>n!b>rZ)l^N#TMNHIH% zUmrzqd(`Q0~qAlq=?C6wV0D=uC5D|Jk;{ND=8)su`?FJ|~4E5xf~BYPL8n zt7seOmyUrXcZL4vZF~ga$k?|{{n)1N#_z{B7ML@$n=KaCcgXP4VSgF+@*$&Lguc4F z8QdB9uK%+__wZwEmkc=WL9DJnj8VR!FX34EE}(hP{A?vt@Yg&`+{Fb%J-vGwWMK;a zpIu!7!?LvVOL0;>N5+9TK_n@Ote7cjEFBi_W!$y54n87+sUPaO zZ?HkhLX7CT-*DkXe+SPOC68W7>3*PQc2|Tdno9gO@`YUfA(xGU8)iMlNqjX7c43`0 zo=EvvOt975(w3C_MbXW{8$BVCp69Jy)UlwQyFQy)4dy!lQ(zZn>Gd(^I4F5M&(K)S z--^5aWV>T!13<|cVmDXAO*t#SQh9gXcgg=yJ)?=m@2e9?tki(yZKAu{jzF3cGuD!E z+OcvC;JkyR+lUSssX~t|AK*@4@l}BQ7mU-ryxYdh}A@KZxA4{(G(U2}v>)>ne&RWiOx z6kf{Fnl*o3cfEh=y@)~X9L<7?(U9+b2kzNux{(X>X?XBd+X!txlTORu_3GJ~-4pV1 z`CSdCrq?Cv@%m68We5x#KApct=;~62h6ldIz;^-RCoPKKZ}D8H%phmbOjjpir{gYZu|^S5EK^CdE-BWW9z80Yye_bn@fqDuQaK`N(`AbVgt1!^r20L7_3x_fhhY z+;*VG@+scT7iqRZbeb~ut&(`m$`8O#vh{!Df!Ak&zPE55O{q!GR7jqJgpIBv zVisNi%Qn{7q&Q=CdZTcByxhi4@gwEZ@~?jQ@4tPK1;uKCkJK&0(O&&cqmEy%}htnXg5^q41? z3(@a-`ftR+dqe|<;K0q0RMD)e%St>=g@Ij%l*Vav1Z-k@gzCu4#h(IYfq-uuM&Eif z#Hs0=O>Amf>lToQ7Zk6JChY$7MZQ9%VPy>yPC@I{2;TrP(d)=UvUwZSwgA4XN4GqW+b z3sZywd0Fd6%iaB3#hzjbZpAoyVrumq8z+|~NqS2rT7cCvV$jaQkQUx8dCzO0!vzBv z=pDAFy7(e?b@uI2*LJ;DsS9%=GP%a>@awE-WEH_6(FV(MsLpAotsOF zRub-m7YqG0DCsYzj0}>%93xd5XPa855`Hmc&(EqIwzF{C6O{fC4)`(^MWlo-QLy`P ztZ4S)vv2y3s)(*ZIrRH%2*Jp6;`<*rf~&b~^I?$zKJLGWZmTc`C*2C+rc5A(nEuq? z+oGi0(TeG+r)QOl`~Yw<@~)~+zwHc>4>=bKz7bNnvInK=++>C=MsTg{AAxw(B?}$8 z>&1}^Zmqsp=&yYu46k#2rI)Hb7%aAK^JjKZjDUsl(u4uWnvB7hN1rPiD_{4c#Q4VU z!gdfn=%9PQa@Q^JYDoNutHR$6RZD}MKD_9t5~#!wCX_+mb_Sps)KHlr{LZcrW8?<$ zWthFv43b9(-JC8pZGNt{!n{utU%57snESzVtLeORP|!h^ z(bkvoYyNF3{V=Hb=VVLD_V2Ox#Tl^jI;T^peBX^o#uKb815;*R@B$c^tJS~QqVx|> zGU9%?Bk39jJpH8DGI8_>5U(q`nwDneQH-{xX}ee<5x{4JtiI`JD{&0O?n7ob(K3Ea zopKA=vC~*yMlEswLp7%>z!=5%CLT7Qm8OW&bZC9v`d~crT4qvODM7ThUs>TT;wj!3 z(B;HVSNZQBSly(0V&bJ{TMu_uVM1C(;puC&ctaeCa=?U1-pw9v;iN!%(S7q>ikl3 zaw+oZg;qkr8v2W!$IrG56g|q5>3Y^q^R4o2W# zumF_o-aoUI-d!&;F}q+?;y1jbtrRorF0QqGtQ2$gNvZ84veD98b(+nS#Jr@hR86Oy zvLou(IqICSZM9&9PD>SmD1(b;IObRn1kM^xF;{V>CM*)qn#tFEoiuihCXz||*dmIYHBL7Me&cA>|MSxKB4HyoUYcZWMji$q*__(R zMHOah^h8|r_7!))tVI%nCh*H*m|r9n;+kxxmLh`5&KTY121b~EoT~j{A!x0ng?tkK$aMas;Chv*k2WfgMv-8oUNeelvx(C(^@wP5Ul=Zz znwYjfUUf2O;#en&25O3ZtaajgtMDFa4~t8)=v=rvnN)lRmA zKnzvzL{y=9TeJX>+WRRW)tKwW9e={tGKO_=E@t*UI_I!#G5lh8iJ9mH|1z_^eQc!D z@&bl;b11Z<=hdI7U1MXpp=~@&8h`kzfrS^Pojmg*)@u(!=5=(nf&FpHIA2NEM_{|0o73hP4%#mjNd^- z=2po;RH6r~Y<)zl-&28cPQ_^q9<{w+$NKTkS+QqH9w3*m?OLrRHwQ)j}AY3Jl73;FJ+JI9{rCd6(REn_XS zV#TM5pQ^6fb=|$Xe{}WURdua?lGSzmn0=I% zAq^4@5=iCRIcU!iH1qn&TDpgG;Hz~+SRvy*9H?RO??(DUZaKA_Sy?C{>Pb-EKP z25b#Uk^A(mN^w`fVrEW$_-dzT7FNo{SAAGSReO-i+Ka5%1Ef7O8fxPuv*84*wa)u` z^rz+tia#b_rKs*K%d0Tv(Ve|yN%|VNJluAyS5fC>#-^KM*|DN?y8LYDo?{nS&-UNI zVt0p@I;0*%>ejJBazIvvnb0*j>;O`dvrKc@*~(=duca_cQ}v9(V#t;W8v8z_eqvt* z&D%l1aW!AFA)pOyAW$-%(zA)?R7@;D9jsGsdpekMInQo-i~(Gj&&A2eU9OHIAcpOz zWls(kZKSd;0&?(Kzzf)arZ7HxjqZwLd*XQNxB&Zf3}TvAeBPXIyX&UqElI!Mf9(Rv8QFG6P75|=N6Nk+*?e?@#c%>X!Gto4sg}ig28i>3no>`};_fOoz!A{zJJ} zlUy(V1o)I{V7KAc_2lRHfxx_5*?dYfT3-7O&R`0q(5zDd1!09*QuzhOrHT6(0a!40 z@JmF}b-;ELXnxBHGL9qZ(O3?=f4P_M3DByk?oHnD;NBi3w3^$s5{pg#8Bf=Ndbf;1 z9(eTaCOI;1q7Ok&iLabcd6bv!n@3*kGL|r8B!Gf3zDcGxEAtw+O>5B4VN193!Pnc| zVCaTxtOyggYb>mTSvLNW0O3ar%wKjdsR*LIT>wIa!<}?pO3Yc51$f%xUAHth1kw|+ zG{Z^~lrZ&fG+9VhTpGT^(AF!&Ie*`u^zp0)jTy6|ko228B_nrycc6xe@wVTBjdb{u z_IsS628K+|xN@|7SL`fszw_q7Cc51D++P=gRWEwvL+s_v0`k2^0)3NQ~+pVvYh?D^dHg4@4~2W}Va z9|g|*L0RW_LbihHbo3AuTzNjB0cdWLGLkF0^tO2ZL5V+fdkEXc-xw3 zufaRXv<3~DZ^eC4x^H0#a_Snj2-B;BqWhVdJmTT3Uy@nQq znDx#V$_cD|>Hv2cg?4Fh2obE1q4Foz^%ldEVOaL1@Fp! zLmS^{*jr~Tqv?{V)t{_I@Z9f;3;@qdXDSibibabNLF8OzMXo17!^Lq9xY6JWdM zW!CS*aQm8!Rac_ngLwHQ+fNe(w!AUdqP1`T}Vs(>v4xsnPGiezM z6dVa8U|2^#5_RJsa!d-Qvpuf*ywi*_UJD@Y6E76Ok?HC^;+a~wXaX?i!CDGF=Wzqq zx|@+5QX;5q9B=8{|3yLq0J0Pmf(OtGrVA3_6JLoS?PG9Zu2*>*<01AFU~6O+Z32eB#_jW3C^* z$SF&pD17z0QUuSm{B}s6O2eGUoIsy*lWUGn#szM1GXwk#mJXs%-V0po&%n;7;?nY5 zg^rlQ@&?w{!ZTzQ7U;JXLlKwcE6_@)bAeXE>(c|XN^s4tSe-_S*dxV5xwC{foLfAD zbl2=@Q>_bU7wNTVUVv{)TUM3P0&Du?e6GJ`jhDW$eu1xnUa+eTibXFT=uBHeALHs_K0wb<;CKjtmf7e5AA#IUEdG*Q&=9{=3psY3;#6DKLxoDQ7Ra z(22~XM81$*N`UYq4hPG0uF@zY|bWvOEpS)DOi} z7tf1U8_n)P07^^gpW!?-{N6}abP$>F-XUH@5dCUS0|25!s1L=2Uu^>t~J^j^RofteQBD?n9@l={tGoP zx8xGuCQpbzSBk6(yCfhyEN0}p2vH-5F#A?Ko^67f?I%O4=6@8sO{}@k$b$YrP)A5v z=9;ttp9K8Pywp9Hh&GUhim7gql-M=Nrj+xZStNRz!gN%aN#|-v$786JYlx^c47+mN zZ0cEQPfE5W!1n@+4N3<08n`o6DN-v%V>!J;p^usPLlSjGar{!K0E^lroCLAdP@L=5Rab%Bw#-9E{iA(Lv(mM(ls2t(9TBv1QuMWMvv{3Wy2 z-8%J7r`vQ;4_Wwu;t7}ExX>ju1ytTSp+@JqXHrt~+PV2`Vnq>_B@oF=3;=jz28VI! z_P2C|xteY_`P8$SQnjm^Y1WmWFMViGRBc$B>nWBmRJtL++dkL%DmM_Q>AIU;rQ+AV z`>IlBJ!kfq&x6Z#?($`LlT7BhVB%J2;Iz({Kfuw+dSPAo#zvv3ZzH=lu;PMB8J(>o z;Lfdn))Z1$n+IdrXi(4_ssU`Cy5+|WkJE4<&EE{(i+{ifCea6~IdY6Ra^(!tPFi^VkvYSWbHRS3-)*`!!2C%N_}Jm@J~q3I+t?rq`=;MI2u0DT+mOR` z8S&UgD~rmth>i6}(+QVmXV&v#Yy)0&t(QDskn6QrDb^-r9`gPZwHe^H+Ss$ATS?)$ zO(=|TuG7%%Uej!oL$eTZZ=h%K(H?1b-*US@q2@?ZqCwzpNuRW%%j?}a{Mc(!dY;$O zcUf68stu+L$!1}kAoJa_UKwHBbU*^A(x$m*=)SCqI%OjYh$??=VT@Z`wp@n_YnY*caF?H%z1zU4PzL@62(L>Th@t;SmcF3P@@^(gNdxk8(`l|44*x%DLqMmmUe5Nvr1 zU~8zLgEAt0dd{BXiZjsEJ6tKUTb{VmyIXatnMnUN;ZTC%*mLlq-7Su5Ci_>Y`yQO= zgbzBp-(jmXjPNY_L6&j6{0`o28b*0`mg^FfLK+aVzA0YKa4Pc|jhpkhbE#r#tqQ$V zqF~n_A_$sdw0e{czq90b1LmirsKfg{0ZYemPndAhf}vea-`iq``gR~2^YhZd`L^M^ z1Yj^V!Q}^6xFLV~L@wV`Di_nK>z>D3^qYSBmyfXmo?ACWK0IW8Up}+$qYA?;LJ%yH z6e2*LlJPRrHP^}ExFfd)K_0SXP*QhCR8uBo5UVIfKyw1@E32VMem2n!u)eb&#dzk^ zy}9$`TX(J0m8C9`-XshO3$o{y4B98XUh!n=8{&6q=`pdc+bhD`^zeNxQ?yCPcep!( z$O~(-JL#Ub%mde*#s#Bw7%ee;>2t^2g$IbtM<^;--<7d=Jp{ZAiiO~r>i`jG1$1{w3{nGG3jn=` zo+pTmS%N*E`a4I<^v&oHkE!E&d;f}w^mWByH);zck`e1{zv`l^ecXcQiT zQWX&Ww2!V#VwXBe!Y|vkz$jdIO3XM{8!2EDAsYp0kGBvd+A`9V@&KqKzyMx;n>d6q zu_;|4o8U`O$GI!!C=A~us)Q3G7t&{H$6doY*9ab?+UflwQCQ5jNg|>y=;pDvg9w6x3gh1lE^Blr-8YAC>Z!LV-zYKJlf#di=xZ(Dv3Q=`J#Pl(R_X1 zy_qi0)#oTW-R}{zM~^duBgUu4JhX0;I7Xmcxzt4u5sJs&l|*H41rpAdcZ)FX zbDYW8n-ej1INcs`0cQ6ceO4kbb~1&yQ;z#PS(_S6soV52sAHzeZ<98ISBa}!#=dX1 zDUU1k>o7#MQm-&kI`xV{;v*<;wa94#HzHaaKOZEE|D@MCG#3vF90C|R46nmcLE+jj z#y%TKN2~8P2qM)7@&SBrlJ6QDYCchv11NfTJ?m^rn_s$mBCV%GSDe>pPW9XoSUe=m zx#7{UUw;Mh$Cq8gKARcH-t8E?kVeMZ*l}_p-TZYIbE_-4?7>!U|3w2L zyJwrqUM$5(zv<%=Qo!ANC-4HTa7>^EIkQnyE}v+1yX`d(ZKfW(Xh$QUVMzEQxf$kN z$RLz(z;<94Lseg?_E=7m{=PSfsCTeEI6`Rx2*+}1aP7{4Axu$6DW&qgvmbHZ94zp1*?e?yAEO_uwIaHQFm?wFTDa-cgYOR@VnmAEGbJ;B*6x2D*u-1qF9NZX38O;hrz91>HK{ep?{hdsHbmwSID>%UpfUSC$^R)o-=gFOdN1KHJ`|7KY(@6r|9Sd7O8ow(SIFVA)%abj0o-Xs5)8Y-XZ zudEjU&JtsOdgEekN$bcyKg6`G4xx)SOTknwuR*t%w%1F}SS% zV%`JP$la}fJj_V!D9zwQ>tsl**xhJs>V(=kKRUdpT+bcTHP`j;cn;zm*a*j<%1gY0 zM3e3Bd|iW6ROb{lNqi6CPC01;EiCFV~+b;WgGjjnc(CAey4W$l>;6CIpAi-ir1 zHupg`Qq|85UViFpx#XDTm^E;};iEzeuSK!hxPK1X;`mS_XS4<>R*c0fwI^B)SiFf8sPcZXwHYXe;V^9tGG*i0o60frg*oHV9l=FeOIuF7b zjZ<373^8GWelfkNKuEXY&6NN;IDLI897$~u$!pZegb}--P|-e(L^1kMF0sr6t+TQp zcXLaWMt&GXBOv=<5=swI$bu1h=-fK3L!mqmviLTF$&!b3l z>W9q{*KKXP>Fj$;5JJN6*-*pB!q(1g>08B6XK7P@r-=j`P_&4*^;7nF%I)MDo3cFa zpfmT3$Bcd)n}a|wn)}(jl(TBCyb%!ElEsDB@Aw`B zesw7*UhNruf4UOdi8hD%xU07e54gbIR2QJg4=U553Dnt{Y5cn#SHsme1tr~9H5A%V z$>N8nt+(=zHCJBG*RFLXuWliq*f9)*?m3N)7=H)&LEJqZV}5GwwRiJp+S*$>4Tyya(`RytJEQr^yOi zIlkh(uCku0qK>I7#KubeD!DsTXES%z%cy|L7KcB1o!SUtEL)@Koo*k~%=MXw%dN@- zeG}#(eEA{RTDE|m=(3gMdp(L~5OFo5=#06H(?ZWur7?xwvETw5O2C@#Pjh)_SdcLS zcXXdIt%Csf_&qZ%Cw}fJp3ik+&)X)>mJYA;+2^5NeS*AC?o=?XcBiS{#>guZd z#b)`ud7r(MFOtvp%UZO|{bw6OGOhzdM|7no(S*?gAuvGQ1Wg_M@9lF_hvE4!*!bAq zE}Ls8Pahhvcn@w&qG+h-3tdEiZy!!N|9!%Y4*7O1AhZDY;CZ^_0~R!Yt6o26ncTW; z0)ovj3clT;FFJ&nU7eA=JU!qYNlq;Wp}+4X_%XR69=9VJC!@V^6RgWyu99ME)JH*s zRW?8B!FB;`k~QVu$6ay1C9*bGQvX71XPK}Rp0^F+ZOYW*qJk4#W4>$V=-+hQ7czA@ zmfv0x4B#34mSL&vh3|?^ER&8vg+NnStVo^c(|XK`Rq!H>g}JR2Gp_|>XeW6z3Fls( zk1C2t*i5L4c*ZH@XTz8N)dVfToh_;4`y6FoE=C170VqA)49x$vd&22*df;>6b3Lxb zXnuFMnrsVNGRXYW+&l+de(EmOf5r5y9!eDL+xsAk@?*-HC<^^F)08)*54RU{RtI}W zwk_>4kl*Iw_??(KbjX5csfphHXf8diiY)Sqx1eK*4oc&CtB>PEw1=SDt-C+$ka%2`YUW9V}KHkM>G>G=;-oP6brt`j)r3x`u4eXs$XK! z_YBXVEIjv0pMLecn!XEpz)J}X08y|wQ$Ydt?hjAI06KonMFmjNwKTn9mG_QD=opuX zNgSDK^1*h;o-_<396b!O0Y&t`5t4$t5o}L(1{II8dzvvPHY_h<9Jjo0_6#!FVJluY z7}A~w3CgSBW#&*?*ckn`^uJcEu6+MG0JycaFzkfD_GujSwde@OiRo>;z6x{f%uN6s z!K&s$j%geA^_ii~NkpIqOh zcHQb^b=!DOdj_)E3aS46rbN)b<+p*fMX^3(M=<65-puxw?OehI?>n^OePT~zP>^c;7dY0q%FsiU+Q@j(+q|fSAZ^rMv`&hJ>_Iif% z`|OTlLHlFbGP+}|6Lh`(ieit-X+s$5 zyT3hqp;;j(UFc)(IC&9_4W5Fe3T3+NiI`_ga*AVlfjgRqMDurM|C+m*D(8{C^c|hX za_A&m$UZwIPKoeZ9}bYS78Lb|Sn*J4^%K_c5nYlOfB9LjXQ`Px=-WJ#2=El1Fd<`S zSFNBGWh>%pZM}%&Y!f$BL4%&+-3=O3Hm#3p?sSKdL`e|mCUl~&9_2{(#I?vxY$ZL* zh{_xH6qzJmPALYPI{pd$A}iF1$=CR3x)$>TEb&C)4VtV@&>A4OqLzZ5iD-iV7l4UW z`BUwZuX1nbqOBI!G^7X|^Nzd1vy^{v@5<>mDyG#=k=s)2{gjpF(Y9$Y1o%bGYkGUmpiu2saet zZmC%LhAZK-N&-Mub7l3y4?S@OqFQuAdmZeyWcu{O1?}WUZaBPKgGpN}Jw$(hSDEr@$k2%|DdkUQ?)4v~e1A(lloVaxyiW&B{-d7vjEoH7mL!_a zbolwHBL9*)71SB$yJGz=H+B+EkBdP2HX8YVadHE!}Gvqek+xmh7j)z^3`+n03nva{0 zzU9RS+34MiJ5GV&A}M;;dgeVt_h1NSScIRr;~MkyCWl*yP=b4~+2W$1AJ!x^o7B;c z*sXI0hZ_Lr?R>`o=s%=i6x>Y6W;Z4XXhYTAj_A$q>_lsy3pE2o{*htY13{&R+|BYz zTPIn#sZc~`qj%bhP{5>gbnoP#XIXpwW9~g+q2od#vG^%4$@+LNuHvdV@fXNa$7D)lII^(lY zfnRwN;@U{E{)2AsB1u;To?2XQ>vH|o84`7NkJqX4pj!Y_&Q&?5Obc>vs zV+UGhaaV!5#!13J%048owK?B7k&gad+Qr!JasIi2ydxZl_R;PhtiiT{eVIM6@=M*6 zr%U0ownIGr-PgbwNbHiY8LJ7CQ$1R;U^52CH4N6KjD{gxn$(L&mnFgHGPp7z1)|=+Xka^jFoR;9s?bZF1(Mnng_!z6PHf1H`HuW3EdM3 zWL-^hC=gAxGw2K%`_x7xYHUnOngJ`?dX7p`+Ze0vEijkh4XCk{#3(|eDY{lfKT|5g zo%!-53~N3-+s|W&cwAaKFo3p?{KEv7_A=dcgPDk7TZ!1{**^^`_~vKrvN|Ng18mi{ z^EBM@oLS^ePyp%770lpD#uai_ZF~Wdj21`$WP*b=Zo;$;Xu=;Gu7pEtd=PQugc)nh zdU)#z;P2O-D5Ox|zvrNXM_Qi@$JT3nxs7NL370ni;t5KCu*CyN#q9k;{-%gw#2w$9 zZzAPe-IVqh@tdG$OaS{;Kg{%pY*keh9W30k`3}QzdF`;Zi>}M~1(EDjWHjbP)y)bV zGg3pOPFy32<L4)* z;Ksj-ra$_)uZGIk0OgGj+aF|_iR8)A$0U&^ZN54fcL*1nJoG}r6eRchS$lQvZET0Y zh{rWBgZ-OSd=ZJg+gPTZHAAe*t&*|&Z@jS3ZOz$gN}VPEpR?Z&Gy}0L;29IQExOme z)MEZpT0y6cS+|j)F|AnP+yNDEp;XiAqneP|3k4HQ?Qm}yn-GgfVF7-)Cx)9KG`gl1}7 z;1!0z^=BOcz(MCf^w)fL8gdfsluyHgiuZKzBbq3qj-^?AP4at&Fn9gp_=npnrAX!* z44}0$B{nKn;UArQ-ATXT-+Y(%Bb?3M1ow~^;vNvY_tfgdLh02_4@pNM$wCA|(2>re z4r;c4L`1w~L+Vq0(CF#x6+mi)^g|Q3+9|ZFdX^^u2!K|atq0#m#3c8>yx--mCA^PM zi6fg)V|Z};v+{Gd=|!S>I9N2LgyBW;VtxiuO3dK_NSOE0XUt>MxvrC9G$B^GSokFUuKq@6X+7H;4Ln+TA z0O_FJ??l31P!Dz8A>uXujGI3=fK!YDi2jh_v#M?%*bY*Qldl86G*PxAX2WU$Qi@G%s?FXC{-rHG zvv8k(BZ7^*r@CRL$*6Zt5b;8>CSQhe7XXJ}i2%vV^cA6BMI&x*+r%q`OLbN;X)+@X zMNCG4YW%C5G~#dl@A?z6i=~bNV@L5`H@^8|BUWcff;Eb9EbQX((zidH7P7BH2z$Nm zg(cHu%KMA>RvXEdK7jBi#$BpW=-*dLCQcgen2-TNu@PG{ZF?-Y;?&^A27y)aRe%ia zsq@cS8#=x}4upmY(pQHAleBg*sbzJ`$pHvYvmq)N0WY&Oz9Bonpm77;P$%B9QB<-C zK1m|#@&|dcXTRAM@*WGd#BtLTL$L)Hy81^P0~pts#)&M*b2rguN7AbGxN3uDYJYOr zl%8))Ar7JoaEU*jOOHJkSs1zq?tD+inaw)36v|=;iCm`MG!v;1balf6>+03} zZuTh~H%2(MMRc8HK0^&}Gwi@!?m>kV0Y%+Mv3gz4{NWMsK1Vjj2_#K|rQ$I@7Chh& zQS4tM5LlCNdT;A_Mg4m$KO;Ok$G;}M)`|JnpJW#d0y1VhF;y@Fxxlg*2@6ow`EF2v zjb+AU5oIkyk+8asbyY}#y3$qoE9Kr0*v6sBfy~4FtRXq`ZFgaxq@jx9$XrhWX?P`_ z)2_d0LJ-9X*FT{yOO*O0eZupG*LuUhyl~D1Mi;4?cUtox{=v#nVL14_=1uDJG}CD9 z3W)D)W7<|(5_S^y&rfk(G=9Jf3NY~5Ewh4Rk^PwDyxS_iGL6Id2sQW2py#xI3O?=0 zjh5AKG0)Yj3{rz|uTxlvdLE5ysbkCT#bJb5rwOHFBozR}FzZJAG32{S zb4X%@Ku5{4v}MZ^NL+iED=F-v8ikF!Wv6U5WgV@Tj>%8Wlx?F-fYE3G9BX6A0A>(@ zMkU-jF>JkIn>n~ve9Cspyz{~Jo^*LF{|dxJKew#?EgAjZvCg2!482o=V8;-?5yO0- z%eZj`Xpg3_jxx8zeF9*=X;WlWQ~TJmYI9*7gK4YIrE@`?HsCzvc~S66E25`aH_k3AOnhED8mE?-*a46%gzNvGXLEBI2t`#~Vyfr`ynpM}qCsh)y}pc_0G8|ddz zsZea;>mMwX!%Bd~z7TEa2k8fiKZfLrzXzJ)qI~sHY#kW=H#HV8`4o}K7;tNBook>k zJe5|oVNhaY-?EeVisC$m;^OG*QLLjyyyFT4)s>9m=99o^lEkPAK|w?DU=8m@Q0C$N zT*SK(4od7s1gtTR(zn1h#!2`Vw8l7d--6p12L%wf5h4BU{1)2Z&TnD;?fe$r`6s#3pjm3^fK#DmI!YjA+`2V`)-9gLpENSyy6Ex z0r6i)`C)n?uN2fmH@KW{uk85))6KU%@vjJqFENWNcWjBfqt(<0)oqoSwRZS_T130g zsSGYWjBj8ZXXXNsx@L<-0hP|x7U|_8ya^RF?>YWIs~B%XQn0uvjOCbZUJ9ZuhYY*m zYnMBS{1&gS&fUTfZx7cEGc{nfSkom1CAtB;P{cWWgMqg&$KwnGPwOjUFgq zd+0$#oQgEIvb$n*pe0+Tc8O7nYs#|<8nSJ5nF1PUCVwwR9U)9q0Ew(tYKzwLsv;V9 zt|k-Tmc&MLwMD~!3}db)bpIG!wyG-L{)wqvO=`qWRH2hBPA_wQO`caFFXh}fS{VDv z@qH!YSvE?Nz?xnyFo2YJ0PW_34nDlnY9v7VeE#4oy+(^KqpN; zi~noOG}S3*mR~C2cLT(FGuHlk@rKaT_akB!>;TN;6WO!;G~X_uX~7Arxxb6%^iRPL@0)yb)!Uy7P}R;4xVgQC6AWR7Yqy%r)Q>C#rhB+g9$x*`BE zp&)i+y-Os2U?p%wCUeD)`N8w#y?s=V6|k}D9_J+>R5P4ONcm-K$Pu1C`<|3KnFmV% zH|7fiR5kmWp=f*WqP}E38wTWePw`|^6jE6YtNyGvWI%HOuU6%mg9kYHb+hyMh z!MDQrt+?Bc_stM0{y3a5`d0i@ejGCCUkz^wlUw){7+<-dU~0slr`_&4&xe4Ybp4eb)1Ihh=olU7yw?DQ$pXVZvhmA5 z2vlY+Gy@GwFJ3QYn996Oh7gG%T!#|Cpkl2k_e(Mt+;lw>T^K(Y;u7p`m(}@R`z?ALW|*rh`z z1S=5>$C^K;;setY$+$0!yeXUP#uSgqw-b%F6L@51+#Id07vCrBUCU?dXG*^-!-DMj zlVE;638m^IBD&;bZMkj+YpJZ&#!3-c`#pJtC`k52dxxzje_`dA(XXsz)OE?=rUh_D z4AjMS0!!rnJ=t)sb%ePnjbz`*gdQ}-C#r10=`sPuCYY&p>?#@Ux5>{nYwoaQe-8hN zK&9)sx_yeIe+4(5>Ai1cuWc6qh&{{)4f_5hBn1cNI zC?Fcgo8u;ea>v;CQI9ygsdP znRoYuEGAbnA6r*bn}4NZw<8=U(8o3ALIYm0ZQgYjo07Q~nDj5TW1&fu^!@B^x!tX@ zN;RlDQJN;UQ>6^F19knVX__C|&euOgsXo-Js7>%dK-78vCT{z`5v9U>6Qw4+dcyz~ zG&daAT9G=hl=SD*WVwvOld~FXvEL$bjvCTx6XpglPUt}C$?p9?5r8CBt8cNhEcEE* zW_A1u#6NEEZGRihkG7syJI$vHVKreM+{f06Sq&S>lDI;#CGh@&P&k%vStncuEXY$(q^x$4e>%X%D!#a0C! zt7TEXoOyk-n89fa)tp~@XQ8KqDq?92=H{69$A*;2du9oGg!pUqR5-Zvlyq%TL^qtL zn>PYXEe~pu4(%R3C2%QO^IYej>Ey`8=u_P{omO$yAx4sid>4mo6IIU}ZgCLhdCFJI zy&o?h)^uHcN?SqYnP-TH+$VCWcLjauyeC~@x-FGiDJ!7TSBa*I4eE5)SARO8)hu1kOEQ7>k9UV1$|4 zKRMW56_18sz(;N5CtmivA8g(Iym@o2g8;2CRM6Vej|q@r4Lh`LvuwzD^pj%E+p$@cSXY3D0`%gN|!HE3!uN#v8AT73-7@mD{-x zGE7z9E03E?@;k}knpX}v{u{3pDae1h*8JzDdX_Vdo+M&|dR50fU`TW{DM*YR@L4cv zwk~agPbGdc&yEM+O-ThUHy|ly#;z9SqnQ;|ID|S?Ui8OiVm+1SMb9$RL(H;+%Q)(y zw9Y2&-vaza6!VGSlkk7R$mY1@6?i+B3kKZ7KV(SKM)t zBkDEs;x6uA1nsP_4TSV*_85WOsJz&#*Qo+_AxZ&+YW#q%h0DqP7KMF!wu|51>KxYZZwIDy;S2io{PXst{n~B|)3>h4H$4KB zWTc(ztnmTEVdW{GHpxLbQp0L&0vQ402xPP9;S3}?oMcY?y^{h&Y!C*V{hy*#?<3gs zSR+FLksC|Rsv#Og*9feRD7rZK)<<3(bTZxv8cOhGIc=Imt@M{ zv&$!$KL+-U%8I=ouNzjXCzGuCociGH_h?WOldmzWc6%gwX z%M&UIY~#=6s&TIDB1Rzx4x3ZOMvZk_tcrtHfiHHMbW3KgNo#s8h&G@?kAylOhrq-ZFpx zxe1>|UF}uyuz$OpSX1jES#&L(ji|PLNQ^bRB3B1G+=*Jv04gPoMm7<=Atg!vSk!vQ z>${{9md6ak!+!+lMgP&HJZTt+6|`R-Q;nOwOVPmUo@W&7`+luQ&1zmt&8g4l`@9v~ zQDh^SkxC5!u3A6e3;9xag$R2>_W1*-oP0)3C@ux$(!M-&z@%zBBi15v|F)rGS^-JS zlZ(R7HJ3HR@u-j@>BdijY4#2aF-rlva#3ZPufuwRz=RCcs98ebHp(3a6=^FY0q%yC zBY)MdJGCCHag4CxyhIi3N;SYLtu3w`ao2tIzID5f&1_Jtpq6-27wDTbgrEQr_VnRu zsUc>AsGBmeyka!R!{VZ4;LxS=ZE{Ao>-H#gUPww{czEnIO0RJQ(&l~jAoRC1@ z4A1|s~CzQG!Sa&cAj{2jc*T zC)@y|Bsc`4{->q;UlyAGuvCWN06+fA(j5H15BmR!qEA2yMM?Pjk3Br0I0?QXwEse= z3=#PciIadBO8qaLCsgcTG9;7@?w@z`pVadSW1&|0w*kO`wu`v?8MXK>K}W6zt-H@dTjwaNd$ z{&#-&-#`3+^1J_9CkbO=*o6OJCrX9D&;bJhk%0mM{qr^a*U}D301Kx89}oV=^uGWc CjyGKZ diff --git a/test/odt_md/list-indent.md b/test/odt_md/list-indent.md index 0c029d57..3d40733c 100644 --- a/test/odt_md/list-indent.md +++ b/test/odt_md/list-indent.md @@ -10,7 +10,7 @@ {{% tip %}} If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. - ![](1000020100000311000001824A182983854F26CB.png) + ![](1000020100000311000001824A182983854F26CB.png) {{% /tip %}} 3. Required for Certification: Select this to indicate the panel action is required for members of the panel. Leave unchecked if the panel action is voluntary. If checked, a panel member failing or becoming overdue for the action will become de-certified from the panel. @@ -22,7 +22,7 @@ 1. Date of Birth: Triggers the panel action on the panel member's date of birth, on a schedule determined by the starting age and frequency. Assumes the panel member's DOB has been captured in the chart demographics. 2. Other Action (Triggered): The Other Action (Triggered) trigger date allows users to trigger a panel action at the same time as another action item, indicated in this panel action. For example, an action to trigger an Audiogram may be for Entry, Routine, or Exit actions; if checked, other actions may use this panel action as a trigger. This option must be selected for the action to display in the Related Action list. The Related Action list displays when then Trigger Date is set to Other Action (Triggered) or Prior Action (Completed). Additionally, action items can be configured to trigger with the Representative Events, as needed, if that programming is utilized. This allows all action items to trigger together for a panel. Triggers with all the same date are usually tied to representative event. - ![](100002010000033B00000036339CF669B6C2B512.png) + ![](100002010000033B00000036339CF669B6C2B512.png) 3. Point in Time: The Point in Time trigger date allows users to trigger an action item on the same day and month, each year (must be MM/DD format). 4. Panel Expiration: Triggers on the expiration date specified in the panel status. Most panels will be configured with a representative event as the @@ -39,4 +39,3 @@ 13. Instructions: Free text instructions for a provider to perform this action item, if necessary. Could be instructions or pass/fail criteria, etc. - diff --git a/test/odt_md/pre-mie.md b/test/odt_md/pre-mie.md index 4d5b728f..66e13ab7 100644 --- a/test/odt_md/pre-mie.md +++ b/test/odt_md/pre-mie.md @@ -7,10 +7,8 @@ https://webchartnow.com/fhirr4sandbox/webchart.cgi/fhir/CarePlan/11 ``` {{% /pre %}} - ## Response - {{% pre language="json" theme="RDark" %}} ``` diff --git a/test/utils.ts b/test/utils.ts index 39c919a0..c1b1ead7 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -8,16 +8,31 @@ export function createTmpDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'wg-')); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function trailSpacesReplacer(x) { x = x.replace(/\n/g, ''); return '\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7'.substring(0, x.length); } +function consoleColorPatch(patch: string) { + for (const line of patch.split('\n')) { + if (line.startsWith('-')) { + console.log(ansi_colors.red(line)); + continue; + } + if (line.startsWith('+')) { + console.log(ansi_colors.green(line)); + continue; + } + console.log(line); + } +} + export function compareTexts(input, output, ignoreWhitespace = true, fileName = 'file.txt') { if (!ignoreWhitespace) { const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: false, newlineIsToken: true }); if (patch.indexOf('@@') > -1) { - console.log(patch); + consoleColorPatch(patch); return false; } return true; @@ -30,31 +45,10 @@ export function compareTexts(input, output, ignoreWhitespace = true, fileName = const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: true, newlineIsToken: true }); if (patch.indexOf('@@') > -1) { - console.log(patch); + consoleColorPatch(patch); return false; } return true; -/* - - let diff = diffLines(input, output, { - ignoreWhitespace, - newlineIsToken: true - }); - if (ignoreWhitespace) { - diff = diff.filter(row => (row.added || row.removed) && row.value.replace(/\n/g, '').length > 0); - } - - for (const part of diff) { - if (part.added) { - continue; - } - if (part.removed) { - continue; - } - } - - return diff.length === 0; -*/ } export function compareTextsWithLines(input, output) { From 374f5a22a3662b079a64be9a36ff650a89ae24b1 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Thu, 21 Mar 2024 19:53:04 +0100 Subject: [PATCH 05/18] More markdown work --- src/odt/MarkdownChunks.ts | 16 +++- src/odt/postprocess/addEmptyLines.ts | 95 +++++++++++++++++---- src/odt/postprocess/hideSuggestedChanges.ts | 6 +- src/odt/postprocess/mergeParagraphs.ts | 47 ++++++++-- src/odt/postprocess/postProcessText.ts | 1 - test/odt_md/example-document.md | 21 +---- test/odt_md/issue-431.md | 1 + test/odt_md/issue-434-2.md | 8 +- 8 files changed, 136 insertions(+), 59 deletions(-) diff --git a/src/odt/MarkdownChunks.ts b/src/odt/MarkdownChunks.ts index 1cd84c96..f8e7faec 100644 --- a/src/odt/MarkdownChunks.ts +++ b/src/odt/MarkdownChunks.ts @@ -5,7 +5,7 @@ import {ansi_colors} from '../utils/logger/colors.js'; export type OutputMode = 'md' | 'html' | 'raw'; -export type TAG = 'HR/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | +export type TAG = 'HR/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | 'BLANK/' | 'BR/' | // BR/ is intentional line break (2 spaces at the end of line) - shift+enter 'EOL/' | // EOL/ is line ending 'EMPTY_LINE/' | // EMPTY_LINE/ is blank line (it can be merged or removed) @@ -160,6 +160,8 @@ function chunkToText(chunk: MarkdownChunk) { return '\n'; case 'EMPTY_LINE/': return '\n'; + case 'BLANK/': + return ''; } break; case 'md': @@ -444,7 +446,7 @@ export class MarkdownChunks { } extractText(start: number, end: number, rules: RewriteRule[] = []) { - const slice = chunksToText(this.chunks.slice(start, end).filter(i => !i.isTag), rules).join(''); + const slice = chunksToText(this.chunks.slice(start, end).filter(i => !i.isTag || ['BR/', 'EOL/', 'EMPTY_LINE/'].includes(i.tag)), rules).join(''); return slice; } @@ -459,8 +461,14 @@ export class MarkdownChunks { } } - removeChunk(start: number, deleteCount = 1) { - this.chunks.splice(start, deleteCount); + removeChunk(start: number, deleteCount = 1, comment = '') { + this.chunks.splice(start, deleteCount, { + mode: 'raw', + isTag: true, + tag: 'BLANK/', + payload: {}, + comment + }); for (let i = start; i < this.chunks.length; i++) { const chunk = this.chunks[i]; if (chunk.isTag) { diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index e3d669f5..2783f913 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -1,11 +1,76 @@ import {MarkdownChunks} from '../MarkdownChunks.ts'; +/* + +EOL/ + is line ending + There should be some text before this tag + +EMPTY_LINE/ + is blank line (it can be merged or removed) + There should not be any text before this tag, only EOL/ or BR/ + +BR/ + is intentional line break (2 spaces at the end of line) - shift+enter + +*/ + +function isPreviousChunkEmptyLine(markdownChunks: MarkdownChunks, position: number) { + const chunk = markdownChunks.chunks[position - 1]; + if (!chunk) { + return false; + } + + if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { + return true; + } + + return false; +} + +function isNextChunkEmptyLine(markdownChunks: MarkdownChunks, position: number) { + const chunk = markdownChunks.chunks[position + 1]; + if (!chunk) { + return false; + } + + if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { + return true; + } + if (chunk.isTag && 'EOL/' === chunk.tag) { + return true; + } + + return false; +} + export function addEmptyLines(markdownChunks: MarkdownChunks) { for (let position = 0; position < markdownChunks.length; position++) { const chunk = markdownChunks.chunks[position]; - if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['/H1', '/H2', '/H3', '/H4', 'IMG/', 'SVG/', '/UL'].indexOf(chunk.tag) > -1) { + if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['IMG/', 'SVG/'].indexOf(chunk.tag) > -1) { + const nextTag = markdownChunks.chunks[position + 1]; + if (nextTag.isTag && nextTag.tag === 'IMG/') { + markdownChunks.chunks.splice(position + 1, 0, { + isTag: true, + mode: 'md', + tag: 'EOL/', + payload: {}, + comment: 'addEmptyLines.ts: EOL/ after IMG/' + }, { + isTag: true, + mode: 'md', + tag: 'EMPTY_LINE/', + payload: {}, + comment: 'addEmptyLines.ts: Between images' + }); + } + // position--; + continue; + } + + if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['/H1', '/H2', '/H3', '/H4', '/UL'].indexOf(chunk.tag) > -1) { const nextTag = markdownChunks.chunks[position + 1]; if (chunk.tag === '/UL' && chunk.payload.listLevel !== 1) { @@ -18,18 +83,7 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // continue; } - // if (nextTag.isTag && nextTag.tag === 'IMG/') { - // markdownChunks.chunks.splice(position + 1, 0, { - // isTag: true, - // mode: 'md', - // tag: 'EMPTY_LINE/', - // payload: {}, - // comment: 'addEmptyLines.ts: Between images' - // }); - // // position+=2; - // continue; - // } - if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { + if (!isNextChunkEmptyLine(markdownChunks, position) && !isPreviousChunkEmptyLine(markdownChunks, position)) { markdownChunks.chunks.splice(position + 1, 0, { isTag: true, mode: 'md', @@ -37,7 +91,11 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { payload: {}, comment: 'addEmptyLines.ts: Add empty line after: ' + chunk.tag }); + position--; + continue; } + // if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { + // } } // listLevel @@ -55,15 +113,19 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // continue; } - if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD') && !(prevTag.isTag && prevTag.tag === 'EMPTY_LINE/')) { + if (!isNextChunkEmptyLine(markdownChunks, position) && !isPreviousChunkEmptyLine(markdownChunks, position)) { markdownChunks.chunks.splice(position, 0, { isTag: true, mode: 'md', tag: 'EMPTY_LINE/', // payload: {}, - comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag, + comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag + JSON.stringify(prevTag), payload: {} }); + position++; + } + + if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD') && !(prevTag.isTag && prevTag.tag === 'EMPTY_LINE/')) { // markdownChunks.chunks.splice(position, 0, { // isTag: false, // mode: 'md', @@ -71,7 +133,6 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // // payload: {}, // comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag // }); - position++; } } } @@ -90,7 +151,7 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { markdownChunks.chunks.splice(position + 1, 1, { isTag: true, mode: 'md', - tag: 'EMPTY_LINE/', + tag: 'EOL/', payload: {}, comment: 'addEmptyLines.ts: Between images /P' }, { diff --git a/src/odt/postprocess/hideSuggestedChanges.ts b/src/odt/postprocess/hideSuggestedChanges.ts index e76c0267..846c5c69 100644 --- a/src/odt/postprocess/hideSuggestedChanges.ts +++ b/src/odt/postprocess/hideSuggestedChanges.ts @@ -7,19 +7,19 @@ export function hideSuggestedChanges(markdownChunks: MarkdownChunks) { if (chunk.isTag && chunk.tag === 'CHANGE') { inChange = true; markdownChunks.removeChunk(position); - position--; + // position--; continue; } if (chunk.isTag && chunk.tag === '/CHANGE') { inChange = false; markdownChunks.removeChunk(position); - position--; + // position--; continue; } if (inChange) { markdownChunks.removeChunk(position); - position--; + // position--; } } diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index a544ff74..604c8a23 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -45,6 +45,7 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re if (macros.length > 0) { addComment(chunk, 'mergeParagraphs.ts: macros.length > 0'); + macros.splice(0, macros.length); continue; } @@ -63,14 +64,22 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re // payload: {} // }); // position--; - continue; + // continue; } } if (previousParaOpening > 0) { const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); if (innerText.length === 0) { - addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); + //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); + markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { + mode: 'md', + isTag: true, + tag: 'EMPTY_LINE/', + payload: {}, + comment: 'mergeParagraphs.ts: convert empty paragraph to EMPTY_LINE/' + }); + position--; continue; } if (innerText.endsWith(' %}}')) { @@ -91,18 +100,38 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re position--; previousParaOpening = 0; } else { - markdownChunks.chunks.splice(position, 2, { - isTag: true, - tag: 'EOL/', // Or BR/ ? - mode: 'md', - payload: {}, - comment: 'mergeParagraphs.ts: End of line, two paras merge together' - }); + const prevTag = markdownChunks.chunks[position - 1]; + if (prevTag.isTag && prevTag.tag === 'EMPTY_LINE/') { + markdownChunks.chunks.splice(position, 2); + } else { + markdownChunks.chunks.splice(position, 2, { + isTag: true, + tag: 'BR/', + mode: 'md', + payload: {}, + comment: 'mergeParagraphs.ts: End of line, two paras merge together' + }); + } position--; previousParaOpening = 0; } } else { + if (previousParaOpening > 0) { + const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); + if (innerText.length === 0) { + //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); + markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { + mode: 'md', + isTag: true, + tag: 'EMPTY_LINE/', + payload: {}, + comment: 'mergeParagraphs.ts: convert empty paragraph to EMPTY_LINE/' + }); + position--; + continue; + } + } addComment(chunk, 'mergeParagraphs.ts: nextChunk is not P'); } } diff --git a/src/odt/postprocess/postProcessText.ts b/src/odt/postprocess/postProcessText.ts index 13440f1a..1fa5e02a 100644 --- a/src/odt/postprocess/postProcessText.ts +++ b/src/odt/postprocess/postProcessText.ts @@ -1,4 +1,3 @@ -import {} from '../StateMachine.ts'; import {isBeginMacro, isEndMacro} from './mergeParagraphs.ts'; export function emptyString(str: string) { diff --git a/test/odt_md/example-document.md b/test/odt_md/example-document.md index 47947eee..93ed0db6 100644 --- a/test/odt_md/example-document.md +++ b/test/odt_md/example-document.md @@ -9,20 +9,13 @@ # Heading 1 - - ## Heading level 2 - - Some normal text with hyperlinks to a [website](https://www.enterprisehealth.com/) and a link to a document on the [shared drive](gdoc:1H6vwfQXIexdg4ldfaoPUjhOZPnSkNn6h29WD6Fi-SBY) with multiple versions of [the link](gdoc:1H6vwfQXIexdg4ldfaoPUjhOZPnSkNn6h29WD6Fi-SBY) because people cut and paste. [Link to test page](gdoc:1iou0QW09pdUhaNtS1RfjJh12lxKAbbq91-SHGihXu_4). Link to [doc in another folder](gdoc:1G4xwfBdH5mvEQyGN16TD2vFUHP8aNgU7wPst-2QTZug). - - ### Heading level 3 - with a table - @@ -54,13 +47,11 @@ After subtable ### Heading 3 - a diagram with links -[Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) +[Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) [Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) ### Heading 3 - with a Table of contents - - * [Heading 1](#heading-1) * [Heading level 2](#heading-level-2) * [Heading level 3 - with a table](#heading-level-3-with-a-table) @@ -70,12 +61,8 @@ After subtable * [Image](#image) * [Preformatted Text](#preformatted-text) - - # Other examples - - ## Image ![](1000000000000200000001804F9AAE46CD6D0DF2.gif) @@ -83,14 +70,12 @@ After subtable ## Preformatted Text - ``` This is monospaced text. This should line up | with this | ``` - ## Code Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and *Markdown Here* -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. *Markdown Here* supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the [highlight.js demo page](http://softwaremaniacs.org/media/soft/highlight/test.html). @@ -121,7 +106,6 @@ myArray.forEach(() => { }); // fat arrow syntax ## Video - From Youtube: [Google Drive, Docs, and Project Management with GSuite](https://www.youtube.com/watch?v=v6QAIWLCz8I&t=1743s) @@ -129,7 +113,6 @@ From Youtube: ## Horizontal Lines - This is some text separated by a horizontal line @@ -162,8 +145,6 @@ Some **bold** **_boldanditalic_*** italic* text ### Using the actual equation object - - ### Text equivalent *E=mc**2* diff --git a/test/odt_md/issue-431.md b/test/odt_md/issue-431.md index 3cf841fc..ee50f616 100644 --- a/test/odt_md/issue-431.md +++ b/test/odt_md/issue-431.md @@ -7,4 +7,5 @@ Indexing can be done immediately following scanning, or saved for a later time. {{% /info %}} **Move Left** or **Move Right**: Users can use the Move Left or Move Right button to rearrange the pages of a batch. After using the Prev and Next buttons to navigate through the pages, users can then use the Move Left button to place the current page being displayed ahead (i.e., to the left) of the next page. Clicking the Move Right button will place the current page after (i.e., to the right) one page. + **Crop**: Users may Crop a document prior to indexing it to a chart. First, navigate to the page needing cropped, using the Prev or Next button, accordingly. To utilize the Crop feature, simply hover the mouse over the scanned image and place the cross cursor (+) at the starting point of the intended crop. Left-click and hold the mouse button, dragging the cursor over the scanned image, highlighting (in black) the area intended to be cropped and kept. Release the mouse. If the highlighted field needs redone, simply left-click the mouse again, and resize the crop field. When ready, click the **Crop** button. The area highlighted in black will be saved. After clicking the Crop button, the image will refresh, showing the cropped document in the upper-left corner. The cropped image will be stored in the chart, once uploaded. If more of the document requires cropping, simply continue by repeating the steps, above. diff --git a/test/odt_md/issue-434-2.md b/test/odt_md/issue-434-2.md index 6958c506..0814979f 100644 --- a/test/odt_md/issue-434-2.md +++ b/test/odt_md/issue-434-2.md @@ -1,4 +1,5 @@ -**This** is a line +**This** is a line + **This** is line two @@ -6,13 +7,12 @@ * Second line +hi -hi  This is a cool new wikiGDrive! With a change. - ![](1000000000000801000006000FC688CE4398B42C.jpg) ![](100000000000080100000600C22E7BC1728488D6.jpg) @@ -20,5 +20,3 @@ This is a cool new wikiGDrive! With a change. ![](10000000000009C4000007537CA7AEBC30C18882.jpg) ![](10000000000009C400000753E2645860B6CEB342.jpg) - - From bfadd11e96c6e0b8e0678925ce0e0c56dfb22ec1 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sat, 30 Mar 2024 19:25:58 +0100 Subject: [PATCH 06/18] Another bunch of md refactor --- package-lock.json | 334 +++++++++-- src/odt/MarkdownChunks.ts | 539 ------------------ src/odt/MarkdownNodes.ts | 180 ++++++ src/odt/OdtToMarkdown.ts | 387 +++++++------ src/odt/StateMachine.ts | 369 +++++------- src/odt/macroUtils.ts | 44 ++ src/odt/markdownNodesUtils.ts | 447 +++++++++++++++ src/odt/postprocess/addEmptyLines.ts | 143 +++-- .../postprocess/addEmptyLinesAfterParas.ts | 40 +- src/odt/postprocess/addIndentsAndBullets.ts | 35 +- src/odt/postprocess/fixBold.ts | 44 -- src/odt/postprocess/fixBoldItalic.ts | 39 ++ src/odt/postprocess/fixListParagraphs.ts | 33 +- .../fixSpacesInsideInlineFormatting.ts | 24 +- src/odt/postprocess/hideSuggestedChanges.ts | 53 +- src/odt/postprocess/mergeParagraphs.ts | 19 +- src/odt/postprocess/mergeTexts.ts | 18 + src/odt/postprocess/postProcess.ts | 48 ++ src/odt/postprocess/postProcessHeaders.ts | 44 +- src/odt/postprocess/postProcessPreMacros.ts | 35 +- src/odt/postprocess/postProcessText.ts | 2 +- .../postprocess/processListsAndNumbering.ts | 58 +- .../removeInsideDoubleCodeBegin.ts | 25 +- src/odt/postprocess/removeMarkdownMacro.ts | 19 + .../removePreWrappingAroundMacros.ts | 44 +- src/odt/postprocess/removeTdParas.ts | 36 ++ src/odt/postprocess/rewriteHeaders.ts | 27 + src/odt/postprocess/trimEndOfParagraphs.ts | 27 +- test/odt_md/td-bullets.md | 3 +- test/utils.ts | 2 +- 30 files changed, 1821 insertions(+), 1297 deletions(-) delete mode 100644 src/odt/MarkdownChunks.ts create mode 100644 src/odt/MarkdownNodes.ts create mode 100644 src/odt/macroUtils.ts create mode 100644 src/odt/markdownNodesUtils.ts delete mode 100644 src/odt/postprocess/fixBold.ts create mode 100644 src/odt/postprocess/fixBoldItalic.ts create mode 100644 src/odt/postprocess/mergeTexts.ts create mode 100644 src/odt/postprocess/postProcess.ts create mode 100644 src/odt/postprocess/removeMarkdownMacro.ts create mode 100644 src/odt/postprocess/removeTdParas.ts create mode 100644 src/odt/postprocess/rewriteHeaders.ts diff --git a/package-lock.json b/package-lock.json index 0234dbf9..c59775a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "dotenv": "^8.6.0", "enquirer": "^2.3.6", "env-paths": "2.2.1", - "express": "^4.19.2", + "express": "4.19.2", "express-jwt": "8.2.1", "express-rate-limit": "^6.6.0", "htmlparser2": "9.0.0", @@ -105,7 +105,6 @@ "apps/ui": { "name": "@mieweb/wikigdrive-ui", "version": "2.0.0-alpha", - "extraneous": true, "license": "ISC", "dependencies": { "vue": "3.2.45", @@ -119,6 +118,19 @@ "vite": "4.5.2" } }, + "apps/ui/node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -132,7 +144,6 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", - "peer": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -633,6 +644,10 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mieweb/wikigdrive-ui": { + "resolved": "apps/ui", + "link": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1628,7 +1643,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", - "peer": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/shared": "3.2.45", @@ -1640,7 +1654,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", - "peer": true, "dependencies": { "@vue/compiler-core": "3.2.45", "@vue/shared": "3.2.45" @@ -1650,7 +1663,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", - "peer": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.45", @@ -1668,17 +1680,20 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.2.45", "@vue/shared": "3.2.45" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + }, "node_modules/@vue/reactivity": { "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", - "peer": true, "dependencies": { "@vue/shared": "3.2.45" } @@ -1687,7 +1702,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", - "peer": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.45", @@ -1700,7 +1714,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", - "peer": true, "dependencies": { "@vue/reactivity": "3.2.45", "@vue/shared": "3.2.45" @@ -1710,7 +1723,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", - "peer": true, "dependencies": { "@vue/runtime-core": "3.2.45", "@vue/shared": "3.2.45", @@ -1721,7 +1733,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", - "peer": true, "dependencies": { "@vue/compiler-ssr": "3.2.45", "@vue/shared": "3.2.45" @@ -1733,8 +1744,7 @@ "node_modules/@vue/shared": { "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", - "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==", - "peer": true + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" }, "node_modules/accepts": { "version": "1.3.8", @@ -2026,6 +2036,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/bootstrap": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", @@ -2536,11 +2552,22 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", - "peer": true + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" }, "node_modules/date-now": { "version": "0.1.4", @@ -3021,6 +3048,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-vue": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz", + "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^8.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -3037,6 +3084,33 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -3142,8 +3216,7 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "peer": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/esutils": { "version": "2.0.3", @@ -4401,7 +4474,6 @@ "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "peer": true, "dependencies": { "sourcemap-codec": "^1.4.8" } @@ -4752,6 +4824,18 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -5030,6 +5114,19 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -5742,7 +5839,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5759,8 +5855,7 @@ "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "peer": true + "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, "node_modules/split-ca": { "version": "1.0.1", @@ -6236,7 +6331,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.2.45", "@vue/compiler-sfc": "3.2.45", @@ -6245,6 +6339,55 @@ "@vue/shared": "3.2.45" } }, + "node_modules/vue-eslint-parser": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", + "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==", + "dev": true, + "dependencies": { + "debug": "^4.3.2", + "eslint-scope": "^7.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.0.0", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-prism-editor": { + "version": "2.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz", + "integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "dependencies": { + "@vue/devtools-api": "^6.4.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6481,8 +6624,7 @@ "@babel/parser": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", - "peer": true + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/runtime": { "version": "7.17.9", @@ -6738,6 +6880,26 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mieweb/wikigdrive-ui": { + "version": "file:apps/ui", + "requires": { + "@vitejs/plugin-vue": "4.0.0", + "eslint-plugin-vue": "8.7.1", + "typescript": "4.9.4", + "vite": "4.5.2", + "vue": "3.2.45", + "vue-prism-editor": "2.0.0-alpha.2", + "vue-router": "4.1.6" + }, + "dependencies": { + "typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7420,7 +7582,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", - "peer": true, "requires": { "@babel/parser": "^7.16.4", "@vue/shared": "3.2.45", @@ -7432,7 +7593,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", - "peer": true, "requires": { "@vue/compiler-core": "3.2.45", "@vue/shared": "3.2.45" @@ -7442,7 +7602,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", - "peer": true, "requires": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.45", @@ -7460,17 +7619,20 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", - "peer": true, "requires": { "@vue/compiler-dom": "3.2.45", "@vue/shared": "3.2.45" } }, + "@vue/devtools-api": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + }, "@vue/reactivity": { "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", - "peer": true, "requires": { "@vue/shared": "3.2.45" } @@ -7479,7 +7641,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", - "peer": true, "requires": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.45", @@ -7492,7 +7653,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", - "peer": true, "requires": { "@vue/reactivity": "3.2.45", "@vue/shared": "3.2.45" @@ -7502,7 +7662,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", - "peer": true, "requires": { "@vue/runtime-core": "3.2.45", "@vue/shared": "3.2.45", @@ -7513,7 +7672,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", - "peer": true, "requires": { "@vue/compiler-ssr": "3.2.45", "@vue/shared": "3.2.45" @@ -7522,8 +7680,7 @@ "@vue/shared": { "version": "3.2.45", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", - "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==", - "peer": true + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" }, "accepts": { "version": "1.3.8", @@ -7756,6 +7913,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "bootstrap": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", @@ -8140,11 +8303,16 @@ "which": "^2.0.1" } }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, "csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", - "peer": true + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" }, "date-now": { "version": "0.1.4", @@ -8532,6 +8700,20 @@ } } }, + "eslint-plugin-vue": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz", + "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", + "dev": true, + "requires": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^8.0.1" + } + }, "eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -8542,6 +8724,23 @@ "estraverse": "^5.2.0" } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -8586,8 +8785,7 @@ "estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "peer": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "esutils": { "version": "2.0.3", @@ -9557,7 +9755,6 @@ "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "peer": true, "requires": { "sourcemap-codec": "^1.4.8" } @@ -9821,6 +10018,15 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, "object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -10021,6 +10227,16 @@ } } }, + "postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -10528,8 +10744,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-js": { "version": "1.0.2", @@ -10539,8 +10754,7 @@ "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "peer": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "split-ca": { "version": "1.0.1", @@ -10866,7 +11080,6 @@ "version": "3.2.45", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", - "peer": true, "requires": { "@vue/compiler-dom": "3.2.45", "@vue/compiler-sfc": "3.2.45", @@ -10875,6 +11088,35 @@ "@vue/shared": "3.2.45" } }, + "vue-eslint-parser": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", + "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==", + "dev": true, + "requires": { + "debug": "^4.3.2", + "eslint-scope": "^7.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.0.0", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.5" + } + }, + "vue-prism-editor": { + "version": "2.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz", + "integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==", + "requires": {} + }, + "vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "requires": { + "@vue/devtools-api": "^6.4.5" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/src/odt/MarkdownChunks.ts b/src/odt/MarkdownChunks.ts deleted file mode 100644 index f8e7faec..00000000 --- a/src/odt/MarkdownChunks.ts +++ /dev/null @@ -1,539 +0,0 @@ -import {ListStyle, Style, TextProperty} from './LibreOffice.ts'; -import {inchesToPixels} from './utils.ts'; -import {applyRewriteRule, RewriteRule} from './applyRewriteRule.ts'; -import {ansi_colors} from '../utils/logger/colors.js'; - -export type OutputMode = 'md' | 'html' | 'raw'; - -export type TAG = 'HR/' | 'B' | '/B' | 'I' | '/I' | 'BI' | '/BI' | 'BLANK/' | - 'BR/' | // BR/ is intentional line break (2 spaces at the end of line) - shift+enter - 'EOL/' | // EOL/ is line ending - 'EMPTY_LINE/' | // EMPTY_LINE/ is blank line (it can be merged or removed) - 'H1' | 'H2' | 'H3' | 'H4' | '/H1' | '/H2' | '/H3' | '/H4' | - 'P' | '/P' | 'CODE' | '/CODE' | 'PRE' | '/PRE' | - 'UL' | '/UL' | 'LI' | '/LI' | 'A' | '/A' | - 'TABLE' | '/TABLE' | 'TR' | '/TR' | 'TD' | '/TD' | - 'TOC' | '/TOC' | 'SVG/' | 'IMG/' | - 'EMB_SVG' | '/EMB_SVG' | 'EMB_SVG_G' | '/EMB_SVG_G' | 'EMB_SVG_P/' | 'EMB_SVG_TEXT' | '/EMB_SVG_TEXT' | - 'EMB_SVG_TSPAN' | '/EMB_SVG_TSPAN' | - 'CHANGE' | '/CHANGE' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; - -export const isOpening = (tag: TAG) => !tag.startsWith('/') && !tag.endsWith('/'); -export const isClosing = (tag: TAG) => tag.startsWith('/'); - -export interface TagPayload { - lang?: string; - position?: number; - id?: string; - listId?: string; - continueList?: string; - href?: string; - alt?: string; - marginLeft?: number; - bullet?: boolean; - number?: number; - style?: Style; - styleTxt?: string; - listStyle?: ListStyle; - continueNumbering?: boolean; - listLevel?: number; - bookmarkName?: string; - pathD?: string; - - x?: number; - y?: number; - width?: number; - height?: number; - transform?: string; -} - -export interface MarkdownTextChunk { - isTag: false; - mode: OutputMode; - text: string; - comment?: string; -} - -export interface MarkdownTagChunk { - isTag: true; - mode: OutputMode; - tag?: TAG; - payload: TagPayload; - comment?: string; -} - -type MarkdownChunk = MarkdownTextChunk | MarkdownTagChunk; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function debugChunkToText(chunk: MarkdownChunk) { - if (chunk.isTag === false) { - return chunk.text; - } - - return chunk.tag; -} - -export function addComment(chunk: MarkdownTagChunk, comment: string) { - if (chunk.comment) { - chunk.comment += ' ' + comment; - } else { - chunk.comment = comment; - } -} - -export function textStyleToString(textProperty: TextProperty) { - if (!textProperty) { - return ''; - } - let styleTxt = ''; - - if (textProperty.fontColor) { - styleTxt += ` fill: ${textProperty.fontColor};`; - } - if (textProperty.fontSize) { - // styleTxt += ` font-size: ${inchesToMm(textProperty.fontSize)}mm;`; - } - - return styleTxt; -} - -function styleToString(style: Style) { - let styleTxt = ''; - if (style?.graphicProperties) { - const graphicProperties = style?.graphicProperties; - // if (graphicProperties.stroke) { - // styleTxt += ` stroke: ${graphicProperties.stroke};`; - // } - if (graphicProperties.strokeWidth) { - styleTxt += ` stroke-width: ${graphicProperties.strokeWidth};`; - } - if (graphicProperties.strokeColor) { - styleTxt += ` stroke: ${graphicProperties.strokeColor};`; - } - if (graphicProperties.strokeLinejoin) { - styleTxt += ` stroke-line-join: ${graphicProperties.strokeLinejoin};`; - } - // if (graphicProperties.fill) { - // styleTxt += ` fill: ${graphicProperties.fill};`; - // } - if (graphicProperties.fillColor) { - styleTxt += ` fill: ${graphicProperties.fillColor};`; - } - } - - if (!styleTxt) { - return 'fill: transparent;'; - } - - return styleTxt; -} - - -function buildSvgStart(payload: TagPayload) { - const width = payload.width; - const height = payload.height; - - - let retVal = `\n`; - const styleTxt = styleToString(payload?.style); - if (styleTxt) { - retVal += `\n`; - } - return retVal; -} - -function chunkToText(chunk: MarkdownChunk) { - if (chunk.isTag === false) { - return chunk.text; - } - - switch (chunk.mode) { - case 'raw': - switch (chunk.tag) { - case '/P': - return '\n'; - case '/PRE': - return '\n'; - case 'BR/': - return '\n'; - case 'EOL/': - return '\n'; - case 'EMPTY_LINE/': - return '\n'; - case 'BLANK/': - return ''; - } - break; - case 'md': - switch (chunk.tag) { - case 'P': - break; - case '/P': - return ''; - case 'BR/': - return ' \n'; - case 'EOL/': - return '\n'; - case 'EMPTY_LINE/': - return '\n'; - case 'PRE': - return '\n```'+ (chunk.payload?.lang || '') +'\n'; - case '/PRE': - return '\n```\n'; - case 'CODE': - return '`'; - case '/CODE': - return '`'; - case 'I': - return '*'; - case '/I': - return '*'; - case 'BI': - return '**_'; - case '/BI': - return '_**'; - case 'B': - return '**'; - case '/B': - return '**'; - case 'H1': - return '# '; - case 'H2': - return '## '; - case 'H3': - return '### '; - case 'H4': - return '#### '; - case '/H1': - return '\n'; - case '/H2': - return '\n'; - case '/H3': - return '\n'; - case '/H4': - return '\n'; - case 'HR/': - return '\n___\n '; - case 'A': - return '['; - case '/A': - return `](${chunk.payload.href})`; - case 'SVG/': - return `![](${chunk.payload.href})`; - case 'IMG/': - return `![](${chunk.payload.href})`; - case 'EMB_SVG': - return buildSvgStart(chunk.payload); - case 'HTML_MODE/': - return '\n'; - } - break; - case 'html': - switch (chunk.tag) { - case 'BR/': - return '\n'; - case 'EOL/': - return '\n'; - case 'EMPTY_LINE/': - return '
'; - case 'HR/': - return '
'; - case 'B': - return ''; - case '/B': - return ''; - case 'I': - return ''; - case '/I': - return ''; - case 'BI': - return ''; - case '/BI': - return ''; - case 'H1': - return '

'; - case 'H2': - return '

'; - case 'H3': - return '

'; - case 'H4': - return '

'; - case '/H1': - return '

'; - case '/H2': - return ''; - case '/H3': - return ''; - case '/H4': - return ''; - case 'P': - return '

'; - case '/P': - return '

'; - case 'CODE': - return ''; - case '/CODE': - return ''; - case 'PRE': - return '
';
-        case '/PRE':
-          return '
'; - case 'UL': - if (chunk.payload.number > 0) { - return '
    '; - } else { - return '
      '; - } - case '/UL': - if (chunk.payload.number > 0) { - return '
'; - } else { - return ''; - } - case 'LI': - return '
  • '; - case '/LI': - return '
  • '; - case 'A': - return ``; - case '/A': - return ''; - case 'TABLE': - return '\n
    Heading 1
    \n'; - case '/TABLE': - return '\n
    \n'; - case 'TR': - return '\n'; - case '/TR': - return '\n'; - case 'TD': - return ''; - case '/TD': - return '\n'; - case 'TOC': - break; - case '/TOC': - break; - case 'SVG/': - return ``; - case 'IMG/': - return ``; - case 'EMB_SVG': - return buildSvgStart(chunk.payload); - case '/EMB_SVG': - return '\n'; - case 'EMB_SVG_G': - { - if (chunk.payload.x || chunk.payload.y) { - const transformStr = `transform="translate(${chunk.payload.x || 0}, ${chunk.payload.y || 0})"`; - return `\n`; - } - return '\n'; - } - case '/EMB_SVG_G': - return '\n'; - case 'EMB_SVG_P/': - return `\n`; - case 'EMB_SVG_TEXT': - return ``; - case '/EMB_SVG_TEXT': - return '\n'; - case 'EMB_SVG_TSPAN': - { - const fontSize = inchesToPixels(chunk.payload.style?.textProperties.fontSize); - return ``; - } - case '/EMB_SVG_TSPAN': - return '\n'; - } - break; - } - - return ''; -} - - -function chunksToText(chunks: MarkdownChunk[], rules: RewriteRule[]) { - const retVal = []; - - for (let chunkNo = 0; chunkNo < chunks.length; chunkNo++) { - const chunk = chunks[chunkNo]; - - if ('tag' in chunk && ['SVG/', 'IMG/'].includes(chunk.tag)) { - let broke = false; - for (const rule of rules) { - const { shouldBreak, text } = applyRewriteRule(rule, { - ...chunk, - href: 'payload' in chunk ? chunk.payload?.href : undefined, - alt: 'payload' in chunk ? chunk.payload?.alt : undefined - }); - - if (shouldBreak) { - retVal.push(text); - broke = true; - break; - } - } - - if (broke) { - continue; - } - } - - if ('tag' in chunk && 'A' === chunk.tag) { - let matchingNo = -1; - - for (let idx = chunkNo + 1; idx < chunks.length; idx++) { - const chunkEnd = chunks[idx]; - if ('tag' in chunkEnd && chunkEnd.tag === '/A') { - matchingNo = idx; - break; - } - } - - if (matchingNo !== -1) { - const alt = chunksToText(chunks.slice(chunkNo + 1, matchingNo).filter(i => !i.isTag), rules).join(''); - let broke = false; - for (const rule of rules) { - const { shouldBreak, text } = applyRewriteRule(rule, { - ...chunk, - href: 'payload' in chunk ? chunk.payload?.href : undefined, - alt - }); - - if (shouldBreak) { - retVal.push(text); - broke = true; - break; - } - } - - if (broke) { - chunks.splice(chunkNo, matchingNo - chunkNo); - continue; - } - } - } - - retVal.push(chunkToText(chunk)); - } - - // chunks.map(c => chunkToText(c)); -/* -*/ - - return retVal; -} - - -export class MarkdownChunks { - chunks: MarkdownChunk[] = []; - - get length() { - return this.chunks.length; - } - - push(s: MarkdownChunk) { - this.chunks.push(s); - } - - toString(rules: RewriteRule[] = []) { - // console.log(this.chunks.map(c => debugChunkToText(c)).join('\n')); - return chunksToText(this.chunks, rules).join('') - .split('\n') - .map(line => line.trim().length > 0 ? line : '') - .join('\n'); - } - - extractText(start: number, end: number, rules: RewriteRule[] = []) { - const slice = chunksToText(this.chunks.slice(start, end).filter(i => !i.isTag || ['BR/', 'EOL/', 'EMPTY_LINE/'].includes(i.tag)), rules).join(''); - return slice; - } - - replace(start: number, end: number, chunk: MarkdownChunk) { - const deleteCount = end - start + 1; - this.chunks.splice(start, deleteCount, chunk); - for (let i = start; i < this.chunks.length; i++) { - const chunk = this.chunks[i]; - if (chunk.isTag) { - chunk.payload.position -= deleteCount; - } - } - } - - removeChunk(start: number, deleteCount = 1, comment = '') { - this.chunks.splice(start, deleteCount, { - mode: 'raw', - isTag: true, - tag: 'BLANK/', - payload: {}, - comment - }); - for (let i = start; i < this.chunks.length; i++) { - const chunk = this.chunks[i]; - if (chunk.isTag) { - chunk.payload.position -= deleteCount; - } - } - } - - dump(logger = console) { - for (let position = 0; position < this.chunks.length; position++) { - const chunk = this.chunks[position]; - let line = position + '\t'; - - switch (chunk.mode) { - case 'md': - line += 'M '; - break; - case 'html': - line += 'H '; - break; - case 'raw': - line += 'R '; - break; - } - - if (chunk.isTag === true) { - line += chunk.tag; - - if (chunk.tag === 'UL') { - line += ` (Level: ${chunk.payload.listLevel})`; - } - } - if (chunk.isTag === false) { - line += chunk.text - .replace(/\n/g, '\\n') - .replace(/\t/g, '[TAB]'); - } - - if (chunk.comment) { - line += '\t// ' + chunk.comment; - } - - if (logger === console) { - if (line.indexOf('StateMachine.ts:') > -1) { - console.log(ansi_colors.gray(line)); - continue; - } - console.log(line); - continue; - } - - logger.log(line); - - } - } - - findNext(tag: TAG, start: number) { - let nextTagPosition = -1; - for (let idx = start + 1; idx < this.chunks.length; idx++) { - const chunk = this.chunks[idx]; - if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { - nextTagPosition = idx; - break; - } - } - return nextTagPosition; - } -} diff --git a/src/odt/MarkdownNodes.ts b/src/odt/MarkdownNodes.ts new file mode 100644 index 00000000..bf2ed0dd --- /dev/null +++ b/src/odt/MarkdownNodes.ts @@ -0,0 +1,180 @@ +import {ListStyle, Style} from './LibreOffice.ts'; +import {fixCharacters} from './utils.ts'; +import {RewriteRule} from './applyRewriteRule.ts'; +import {chunksToText} from './markdownNodesUtils.ts'; + +export type OutputMode = 'md' | 'html' | 'raw'; + +export type TAG = 'BODY' | 'HR/' | 'B' | 'I' | 'BI' | 'BLANK/' | // | '/B' | '/I' | '/BI' + 'BR/' | // BR/ is intentional line break (2 spaces at the end of line) - shift+enter + 'EOL/' | // EOL/ is line ending + 'EMPTY_LINE/' | // EMPTY_LINE/ is blank line (it can be merged or removed) + 'H1' | 'H2' | 'H3' | 'H4' | //'/H1' | '/H2' | '/H3' | '/H4' | + 'P' | 'CODE' | 'PRE' | // '/P' | '/CODE' | '/PRE' | + 'UL' | 'LI' | 'A' | // | '/UL' | '/LI' | '/A' + 'TABLE' | 'TR' | 'TD' | // | '/TABLE' | '/TR' | '/TD' + 'TOC' | 'SVG/' | 'IMG/' | // | '/TOC' + 'EMB_SVG' | 'EMB_SVG_G' | 'EMB_SVG_P/' | 'EMB_SVG_TEXT' | // | '/EMB_SVG' | '/EMB_SVG_G' | '/EMB_SVG_TEXT' + 'EMB_SVG_TSPAN' | // | '/EMB_SVG_TSPAN' + 'CHANGE_START' | 'CHANGE_END' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; + +export const isSelfClosing = (tag: TAG) => tag.endsWith('/'); +// export const isOpening = (tag: TAG) => !tag.startsWith('/') && !tag.endsWith('/'); +// export const isClosing = (tag: TAG) => tag.startsWith('/'); + +export interface TagPayload { + lang?: string; + position?: number; + id?: string; + listId?: string; + continueList?: string; + href?: string; + alt?: string; + marginLeft?: number; + bullet?: boolean; + number?: number; + style?: Style; + styleTxt?: string; + listStyle?: ListStyle; + continueNumbering?: boolean; + listLevel?: number; + bookmarkName?: string; + pathD?: string; + + x?: number; + y?: number; + width?: number; + height?: number; + transform?: string; +} + +export interface MarkdownTextNode { + isTag: false; + mode: OutputMode; + text: string; + comment?: string; + parent?: MarkdownTagNode; +} + +export interface MarkdownTagNode { + isTag: true; + mode: OutputMode; + tag: TAG; + payload: TagPayload; + comment?: string; + children: MarkdownNode[]; + parent?: MarkdownTagNode; +} + +export type MarkdownNode = MarkdownTextNode | MarkdownTagNode; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars + + +export class MarkdownNodes { + // chunks: MarkdownNode[] = []; + + public readonly body: MarkdownTagNode; + + constructor() { + this.body = this.createNode('BODY', {}); + } + + createNode(tag: TAG, payload: TagPayload = {}, mode: OutputMode = 'md'): MarkdownTagNode { + const node: MarkdownTagNode = { + isTag: true, + tag, + mode, + payload, + children: [] + }; + + const oldSplice = node.children.splice; + node.children.splice = function (start, deleteCount, ...items) { + const retVal = oldSplice.apply(node.children, [start, deleteCount, ...items]) + // const retVal = oldSplice(start, deleteCount, ...items); + + for (let idx = 0; idx < items.length; idx++) { + items[idx].parent = node; + } + + return retVal; + }; + + return node; + } + + // get length() { + // return this.chunks.length; + // } + + // push(s: MarkdownChunk) { + // this.chunks.push(s); + // } + + toString(rules: RewriteRule[] = []) { + // console.log(this.chunks.map(c => debugChunkToText(c)).join('\n')); + return chunksToText(this.body.children, { rules, mode: 'md' }) + .split('\n') + .map(line => line.trim().length > 0 ? line : '') + .join('\n'); + } + + replace(start: number, end: number, chunk: MarkdownNode) { + const deleteCount = end - start + 1; + this.body.children.splice(start, deleteCount, chunk); + for (let i = start; i < this.body.children.length; i++) { + const chunk = this.body.children[i]; + if (chunk.isTag) { + chunk.payload.position -= deleteCount; + } + } + } + + removeChunk(start: number, deleteCount = 1, comment = '') { + throw new Error('TODO remove'); + this.body.children.splice(start, deleteCount); + /*, { + mode: 'raw', + isTag: true, + tag: 'BLANK/', + payload: {}, + children: [] + comment + } + */ + for (let i = start; i < this.body.children.length; i++) { + const chunk = this.body.children[i]; + if (chunk.isTag) { + chunk.payload.position -= deleteCount; + } + } + } + + // findNext(tag: TAG, start: number) { + // let nextTagPosition = -1; + // for (let idx = start + 1; idx < this.chunks.length; idx++) { + // const chunk = this.chunks[idx]; + // if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { + // nextTagPosition = idx; + // break; + // } + // } + // return nextTagPosition; + // } + append(parent: MarkdownTagNode, child: MarkdownTagNode) { + parent.children.push(child); + child.parent = parent; + } + + appendText(parent: MarkdownTagNode, txt: string) { + txt = fixCharacters(txt); + parent.children.push({ + isTag: false, + mode: 'md', // this.currentMode, + text: txt, + parent, + comment: 'MarkdownNodes.ts: appendText' + }); + } +} diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index c239e983..e1405abc 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -17,13 +17,14 @@ import { TextSpan } from './LibreOffice.ts'; import {urlToFolderId} from '../utils/idParsers.ts'; -import {MarkdownChunks} from './MarkdownChunks.ts'; -import {isMarkdownMacro, StateMachine} from './StateMachine.ts'; +import {MarkdownNodes, MarkdownTagNode} from './MarkdownNodes.ts'; import {inchesToPixels, inchesToSpaces, spaces} from './utils.ts'; import {extractPath} from './extractPath.ts'; import {mergeDeep} from './mergeDeep.ts'; import {RewriteRule} from './applyRewriteRule.ts'; import {postProcessText} from './postprocess/postProcessText.ts'; +import {isMarkdownMacro} from './macroUtils.ts'; +import {postProcess} from './postprocess/postProcess.ts'; function getBaseFileName(fileName) { return fileName.replace(/.*\//, ''); @@ -59,15 +60,17 @@ function getInnerText(span: TextSpan) { export class OdtToMarkdown { - private readonly stateMachine: StateMachine; + public errors: string[] = []; + // private readonly stateMachine: StateMachine; private readonly styles: { [p: string]: Style } = {}; public readonly links: Set = new Set(); - private readonly chunks: MarkdownChunks = new MarkdownChunks(); + private readonly chunks: MarkdownNodes = new MarkdownNodes(); private picturesDir = ''; private rewriteRules: RewriteRule[] = []; constructor(private document: DocumentContent, private documentStyles: DocumentStyles, private fileNameMap: FileNameMap = {}) { - this.stateMachine = new StateMachine(this.chunks); + + // this.stateMachine = new StateMachine(this.chunks); } getStyle(styleName: string): Style { @@ -119,24 +122,23 @@ export class OdtToMarkdown { const listLevels = Object.keys(listLevelsObj); listLevels.sort((a, b) => inchesToPixels(a) - inchesToPixels(b)); - this.stateMachine.setListLevels(listLevels); + // this.stateMachine.setListLevels(listLevels); // TODO for (const tableOfContent of this.document.body.text.list) { if (tableOfContent.type === 'toc') { - await this.tocToText(tableOfContent); + await this.tocToText(this.chunks.body, tableOfContent); } } - await this.officeTextToText(this.document.body.text); + await this.officeTextToText(this.chunks.body, this.document.body.text); // text = this.processMacros(text); // text = this.fixBlockMacros(text); - this.stateMachine.postProcess(); + await postProcess(this.chunks); const markdown = this.chunks.toString(this.rewriteRules); const trimmed = this.trimBreaks(markdown); - const rewrittenHeaders = await this.rewriteHeaders(trimmed); - return postProcessText(rewrittenHeaders); + return postProcessText(trimmed); } trimBreaks(markdown: string) { @@ -168,66 +170,76 @@ export class OdtToMarkdown { } getErrors() { - return this.stateMachine.errors; + return this.errors; } - async tocToText(tableOfContent: TableOfContent): Promise { - this.stateMachine.pushTag('TOC'); + async tocToText(currentTagNode: MarkdownTagNode, tableOfContent: TableOfContent): Promise { + const tocNode = this.chunks.createNode('TOC', {}); + this.chunks.append(currentTagNode, tocNode); + + // this.stateMachine.pushTag('TOC'); for (const paragraph of tableOfContent.indexBody.list) { - await this.paragraphToText(paragraph); + await this.paragraphToText(tocNode, paragraph); } - this.stateMachine.pushTag('/TOC'); + // this.stateMachine.pushTag('/TOC'); } - async spanToText(span: TextSpan): Promise { + async spanToText(currentTagNode: MarkdownTagNode, span: TextSpan): Promise { const style = this.getStyle(span.styleName); if (COURIER_FONTS.indexOf(style.textProperties.fontName) > -1) { - this.stateMachine.pushTag('CODE'); + const block = this.chunks.createNode('CODE'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } - if (style.textProperties?.fontStyle === 'italic' && style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('BI'); + const block = this.chunks.createNode('BI'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } else if (style.textProperties?.fontStyle === 'italic') { - this.stateMachine.pushTag('I'); + const block = this.chunks.createNode('I'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } else if (style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('B'); + const block = this.chunks.createNode('B'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } for (const child of span.list) { if (typeof child === 'string') { - this.stateMachine.pushText(child); + this.chunks.appendText(currentTagNode, child); continue; } switch (child.type) { case 'line_break': - this.stateMachine.pushTag('BR/'); + this.chunks.append(currentTagNode, this.chunks.createNode('BR/')); break; case 'tab': - this.stateMachine.pushText('\t'); + this.chunks.appendText(currentTagNode, '\t'); break; case 'space': - this.stateMachine.pushText(spaces((child).chars || 1)); + this.chunks.appendText(currentTagNode, spaces((child).chars || 1)); break; } } - if (style.textProperties?.fontStyle === 'italic' && style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('/BI'); - } else - if (style.textProperties?.fontStyle === 'italic') { - this.stateMachine.pushTag('/I'); - } else - if (style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('/B'); - } - - if (COURIER_FONTS.indexOf(style.textProperties.fontName) > -1) { - this.stateMachine.pushTag('/CODE'); - } + // if (style.textProperties?.fontStyle === 'italic' && style.textProperties?.fontWeight === 'bold') { + // this.stateMachine.pushTag('/BI'); + // } else + // if (style.textProperties?.fontStyle === 'italic') { + // this.stateMachine.pushTag('/I'); + // } else + // if (style.textProperties?.fontWeight === 'bold') { + // this.stateMachine.pushTag('/B'); + // } + // + // if (COURIER_FONTS.indexOf(style.textProperties.fontName) > -1) { + // this.stateMachine.pushTag('/CODE'); + // } } addLink(href: string) { @@ -236,7 +248,7 @@ export class OdtToMarkdown { } } - async linkToText(link: TextLink): Promise { + async linkToText(currentTagNode: MarkdownTagNode, link: TextLink): Promise { let href = link.href; const id = urlToFolderId(href); if (id) { @@ -245,26 +257,29 @@ export class OdtToMarkdown { this.addLink(href); - this.stateMachine.pushTag('A', { href: href }); + // this.stateMachine.pushTag('A', { href: href }); + const block = this.chunks.createNode('A', { href: href }); + this.chunks.append(currentTagNode, block); + currentTagNode = block; for (const child of link.list) { if (typeof child === 'string') { - this.stateMachine.pushText(child); + this.chunks.appendText(currentTagNode, child); continue; } switch (child.type) { case 'span': { - await this.spanToText(child); + await this.spanToText(currentTagNode, child); } break; } } - this.stateMachine.pushTag('/A', { href: href }); + // this.stateMachine.pushTag('/A', { href: href }); } - async drawCustomShape(drawCustomShape: DrawCustomShape) { + async drawCustomShape(currentTagNode: MarkdownTagNode, drawCustomShape: DrawCustomShape) { // https://documentation.libreoffice.org/assets/Uploads/Documentation/en/Tutorials/CustomShapes7/Custom-Shape-Tutorial.odt // https://code.woboq.org/libreoffice/libreoffice/svx/source/customshapes/EnhancedCustomShape2d.cxx.html#1808 // https://code.woboq.org/libreoffice/libreoffice/xmloff/source/draw/ximpcustomshape.cxx.html @@ -273,19 +288,21 @@ export class OdtToMarkdown { const logwidth = inchesToPixels(drawCustomShape.width); const logheight = inchesToPixels(drawCustomShape.height); - this.stateMachine.pushTag('EMB_SVG', { + const blockSvg = this.chunks.createNode('EMB_SVG', { width: logwidth, height: logheight }); + this.chunks.append(currentTagNode, blockSvg); for (const item of drawCustomShape.list) { if (item.type === 'draw_enhanced_geometry') { const enhancedGeometry = item; - this.stateMachine.pushTag('EMB_SVG_P/', { + const blockSvgP = this.chunks.createNode('EMB_SVG_P/', { pathD: extractPath(enhancedGeometry, logwidth, logheight), style }); + this.chunks.append(blockSvg, blockSvgP); } } for (const item of drawCustomShape.list) { @@ -296,10 +313,13 @@ export class OdtToMarkdown { continue; } - this.stateMachine.pushTag('EMB_SVG_TEXT'); + const blockSvgText = this.chunks.createNode('EMB_SVG_TEXT'); + this.chunks.append(blockSvg, blockSvgText); + + // this.stateMachine.pushTag('EMB_SVG_TEXT'); for (const child of paragraph.list) { if (typeof child === 'string') { - this.stateMachine.pushText(child); + this.chunks.appendText(currentTagNode, child); continue; } switch (child.type) { @@ -308,42 +328,45 @@ export class OdtToMarkdown { const span = child; const style = this.getStyle(span.styleName); - this.stateMachine.pushTag('EMB_SVG_TSPAN', { + + const blockSvgTextSpan = this.chunks.createNode('EMB_SVG_TSPAN', { style }); + this.chunks.append(blockSvgText, blockSvgTextSpan); for (const child of span.list) { if (typeof child === 'string') { - this.stateMachine.pushText(child); + this.chunks.appendText(blockSvgTextSpan, child); continue; } switch (child.type) { case 'line_break': - this.stateMachine.pushTag('BR/'); + this.chunks.append(blockSvgTextSpan, this.chunks.createNode('BR/')); break; case 'tab': - this.stateMachine.pushText('\t'); + this.chunks.appendText(blockSvgTextSpan, '\t'); break; case 'space': - this.stateMachine.pushText(spaces((child).chars || 1)); + this.chunks.appendText(blockSvgTextSpan, spaces((child).chars || 1)); break; } } } - this.stateMachine.pushTag('/EMB_SVG_TSPAN'); + // this.stateMachine.pushTag('/EMB_SVG_TSPAN'); break; } } - this.stateMachine.pushTag('/EMB_SVG_TEXT'); + // this.stateMachine.pushTag('/EMB_SVG_TEXT'); } } - this.stateMachine.pushTag('/EMB_SVG'); + // this.stateMachine.pushTag('/EMB_SVG'); } - async drawGToText(drawG: DrawG) { - this.stateMachine.pushTag('HTML_MODE/'); + async drawGToText(currentTagNode: MarkdownTagNode, drawG: DrawG) { + const blockHtml = this.chunks.createNode('HTML_MODE/'); + this.chunks.append(currentTagNode, blockHtml); this.getStyle(drawG.styleName); @@ -360,32 +383,33 @@ export class OdtToMarkdown { } } - this.stateMachine.pushTag('EMB_SVG', { + const blockSvg = this.chunks.createNode('EMB_SVG', { width: maxx, height: maxy, styleTxt: `width: ${maxx / 100}mm; height: ${maxy / 100}mm;` }); + this.chunks.append(blockHtml, blockSvg); + // currentTagNode = blockSvg; for (const drawCustomShape of drawG.list) { - this.stateMachine.pushTag('EMB_SVG_G', { + const blockSvgGroup = this.chunks.createNode('EMB_SVG_G', { x: inchesToPixels(drawCustomShape.x), y: inchesToPixels(drawCustomShape.y) }); - await this.drawCustomShape(drawCustomShape); - this.stateMachine.pushTag('/EMB_SVG_G'); + this.chunks.append(blockSvg, blockSvgGroup); + await this.drawCustomShape(blockSvgGroup, drawCustomShape); } - this.stateMachine.pushTag('/EMB_SVG'); - this.stateMachine.pushTag('MD_MODE/'); + const emptyLine = this.chunks.createNode('EMPTY_LINE/'); + this.chunks.append(currentTagNode, emptyLine); + + const blockWarning = this.chunks.createNode('B'); + this.chunks.append(currentTagNode, blockWarning); - this.stateMachine.pushTag('EMPTY_LINE/'); - this.stateMachine.pushTag('B'); - this.stateMachine.pushText('INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); - this.stateMachine.pushError('INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); - this.stateMachine.pushTag('/B'); - this.stateMachine.pushTag('BR/'); + this.chunks.appendText(blockWarning, 'INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); + this.pushError('INSTEAD OF EMBEDDED DIAGRAM ABOVE USE EMBEDDED DIAGRAM FROM DRIVE AND PUT LINK TO IT IN THE DESCRIPTION. See: https://github.com/mieweb/wikiGDrive/issues/353'); } - async drawFrameToText(drawFrame: DrawFrame) { + async drawFrameToText(currentTagNode: MarkdownTagNode, drawFrame: DrawFrame) { if (drawFrame.object) { // TODO: MathML return; } @@ -397,13 +421,16 @@ export class OdtToMarkdown { const svgId = urlToFolderId(altText); if (svgId) { - this.stateMachine.pushTag('SVG/', { href: 'gdoc:' + svgId }); + const node = this.chunks.createNode('SVG/', { href: 'gdoc:' + svgId }); + this.chunks.append(currentTagNode, node); } else if (imageLink.endsWith('.svg')) { - this.stateMachine.pushTag('SVG/', { href: imageLink, alt: altText }); + const node = this.chunks.createNode('SVG/', { href: imageLink, alt: altText }); + this.chunks.append(currentTagNode, node); // this.stateMachine.pushTag(``); } else { - this.stateMachine.pushTag('IMG/', { href: imageLink, alt: altText }); + const node = this.chunks.createNode('IMG/', { href: imageLink, alt: altText }); + this.chunks.append(currentTagNode, node); // this.stateMachine.pushTag(`![${altText}](${imageLink})`); } } @@ -449,27 +476,39 @@ export class OdtToMarkdown { } - async paragraphToText(paragraph: TextParagraph): Promise { + async paragraphToText(currentTagNode: MarkdownTagNode, paragraph: TextParagraph): Promise { const style = this.getStyle(paragraph.styleName); const listStyle = this.getListStyle(style.listStyleName); const bookmarkName = paragraph.bookmark?.name || null; if (this.hasStyle(paragraph, 'Heading_20_1')) { - this.stateMachine.pushTag('H1', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const header = this.chunks.createNode('H1', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, header); + currentTagNode = header; } else if (this.hasStyle(paragraph, 'Heading_20_2')) { - this.stateMachine.pushTag('H2', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const header = this.chunks.createNode('H2', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, header); + currentTagNode = header; } else if (this.hasStyle(paragraph, 'Heading_20_3')) { - this.stateMachine.pushTag('H3', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const header = this.chunks.createNode('H3', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, header); + currentTagNode = header; } else if (this.hasStyle(paragraph, 'Heading_20_4')) { - this.stateMachine.pushTag('H4', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const header = this.chunks.createNode('H4', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, header); + currentTagNode = header; } else if (this.isCourier(paragraph.styleName)) { - this.stateMachine.pushTag('PRE', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const block = this.chunks.createNode('PRE', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } else { - this.stateMachine.pushTag('P', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + const block = this.chunks.createNode('P', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName }); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } let codeElementsCount = 0; @@ -495,7 +534,7 @@ export class OdtToMarkdown { const onlyCodeChildren = codeElementsCount > 0 && codeElementsCount + textElementsCount === paragraph.list.length; if (onlyCodeChildren) { - this.stateMachine.pushTag('PRE'); + currentTagNode.tag = 'PRE'; } /* switch (this.top.mode) { @@ -513,24 +552,28 @@ export class OdtToMarkdown { if (!this.isCourier(paragraph.styleName)) { if (style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('B'); + // this.stateMachine.pushTag('B'); + const block = this.chunks.createNode('B', {}); + this.chunks.append(currentTagNode, block); + currentTagNode = block; } } for (const child of paragraph.list) { if (typeof child === 'string') { - this.stateMachine.pushText(child); + this.chunks.appendText(currentTagNode, child); continue; } switch (child.type) { case 'line_break': - this.stateMachine.pushTag('BR/'); + this.chunks.append(currentTagNode, this.chunks.createNode('BR/', {})); + // this.stateMachine.pushTag('BR/'); break; case 'tab': - this.stateMachine.pushText('\t'); + this.chunks.appendText(currentTagNode, '\t'); break; case 'space': - this.stateMachine.pushText(spaces((child).chars || 1)); + this.chunks.appendText(currentTagNode, spaces((child).chars || 1)); break; case 'span': { @@ -539,169 +582,185 @@ export class OdtToMarkdown { if (COURIER_FONTS.indexOf(spanStyle.textProperties.fontName) > -1 && onlyCodeChildren) { const span2 = Object.assign({}, span); span2.styleName = ''; - await this.spanToText(span2); + await this.spanToText(currentTagNode, span2); } else if (COURIER_FONTS.indexOf(spanStyle.textProperties.fontName) > -1) { - this.stateMachine.pushTag('CODE'); + const codeBlock = this.chunks.createNode('CODE'); + this.chunks.append(currentTagNode, codeBlock); const span2 = Object.assign({}, span); span2.styleName = ''; - await this.spanToText(span2); - this.stateMachine.pushTag('/CODE'); + await this.spanToText(codeBlock, span2); + // this.stateMachine.pushTag('/CODE'); } else { - await this.spanToText(span); + await this.spanToText(currentTagNode, span); } } break; case 'link': { const link = child; - await this.linkToText(link); + await this.linkToText(currentTagNode, link); } break; case 'rect': { const rect = child; if (rect.width === '100%') { - this.stateMachine.pushTag('HR/'); + const node = this.chunks.createNode('HR/'); + this.chunks.append(currentTagNode, node); } } break; case 'draw_frame': - await this.drawFrameToText(child); + await this.drawFrameToText(currentTagNode, child); break; case 'draw_custom_shape': - this.stateMachine.pushTag('HTML_MODE/'); - await this.drawCustomShape(child); - this.stateMachine.pushTag('MD_MODE/'); + { + const htmlBlock = this.chunks.createNode('HTML_MODE/'); + this.chunks.append(currentTagNode, htmlBlock); + await this.drawCustomShape(htmlBlock, child); + } break; case 'draw_g': - await this.drawGToText(child); + await this.drawGToText(currentTagNode, child); break; case 'change_start': - this.stateMachine.pushTag('CHANGE'); + this.chunks.append(currentTagNode, this.chunks.createNode('CHANGE_START')); break; case 'change_end': - this.stateMachine.pushTag('/CHANGE'); + this.chunks.append(currentTagNode, this.chunks.createNode('CHANGE_END')); break; } } - if (!this.isCourier(paragraph.styleName)) { - if (style.textProperties?.fontWeight === 'bold') { - this.stateMachine.pushTag('/B'); - } - } - - if (onlyCodeChildren) { - this.stateMachine.pushTag('/PRE'); - } - - if (this.hasStyle(paragraph, 'Heading_20_1')) { - this.stateMachine.pushTag('/H1'); - } else - if (this.hasStyle(paragraph, 'Heading_20_2')) { - this.stateMachine.pushTag('/H2'); - } else - if (this.hasStyle(paragraph, 'Heading_20_3')) { - this.stateMachine.pushTag('/H3'); - } else - if (this.hasStyle(paragraph, 'Heading_20_4')) { - this.stateMachine.pushTag('/H4'); - } else - if (this.isCourier(paragraph.styleName)) { - this.stateMachine.pushTag('/PRE'); - } else { - this.stateMachine.pushTag('/P'); - } + // if (!this.isCourier(paragraph.styleName)) { + // if (style.textProperties?.fontWeight === 'bold') { + // this.stateMachine.pushTag('/B'); + // } + // } + // + // if (onlyCodeChildren) { + // this.stateMachine.pushTag('/PRE'); + // } + // + // if (this.hasStyle(paragraph, 'Heading_20_1')) { + // this.stateMachine.pushTag('/H1'); + // } else + // if (this.hasStyle(paragraph, 'Heading_20_2')) { + // this.stateMachine.pushTag('/H2'); + // } else + // if (this.hasStyle(paragraph, 'Heading_20_3')) { + // this.stateMachine.pushTag('/H3'); + // } else + // if (this.hasStyle(paragraph, 'Heading_20_4')) { + // this.stateMachine.pushTag('/H4'); + // } else + // if (this.isCourier(paragraph.styleName)) { + // this.stateMachine.pushTag('/PRE'); + // } else { + // this.stateMachine.pushTag('/P'); + // } } - async tableCellToText(tableCell: TableCell): Promise { - this.stateMachine.pushTag('TD'); // colspan + async tableCellToText(currentTagNode: MarkdownTagNode, tableCell: TableCell): Promise { + // this.stateMachine.pushTag('TD'); // colspan + const block = this.chunks.createNode('TD'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; + for (const child of tableCell.list) { switch (child.type) { case 'paragraph': - await this.paragraphToText(child); + await this.paragraphToText(currentTagNode, child); break; case 'list': - await this.listToText(child); + await this.listToText(currentTagNode, child); break; case 'table': - await this.tableToText(child); + await this.tableToText(currentTagNode, child); break; } } - this.stateMachine.pushTag('/TD'); + // this.stateMachine.pushTag('/TD'); } - async tableRowToText(tableRow: TableRow): Promise { - this.stateMachine.pushTag('TR'); + async tableRowToText(currentTagNode: MarkdownTagNode, tableRow: TableRow): Promise { + // this.stateMachine.pushTag('TR'); + const block = this.chunks.createNode('TR'); + this.chunks.append(currentTagNode, block); + currentTagNode = block; for (const tableCell of tableRow.cells) { - await this.tableCellToText(tableCell); + await this.tableCellToText(currentTagNode, tableCell); } - this.stateMachine.pushTag('/TR'); + // this.stateMachine.pushTag('/TR'); } - async tableToText(table: TableTable): Promise { - this.stateMachine.pushTag('TABLE'); + async tableToText(currentTagNode: MarkdownTagNode, table: TableTable): Promise { + const blockHtml = this.chunks.createNode('HTML_MODE/'); + this.chunks.append(currentTagNode, blockHtml); + + // this.stateMachine.pushTag('TABLE'); + const block = this.chunks.createNode('TABLE'); + this.chunks.append(blockHtml, block); + currentTagNode = block; for (const tableRow of table.rows) { - await this.tableRowToText(tableRow); + await this.tableRowToText(currentTagNode, tableRow); } - this.stateMachine.pushTag('/TABLE'); + // this.stateMachine.pushTag('/TABLE'); } - async listToText(list: TextList): Promise { + async listToText(currentTagNode: MarkdownTagNode, list: TextList): Promise { const listStyle = this.getListStyle(list.styleName); const continueNumbering = list.continueNumbering === 'true'; - this.stateMachine.pushTag('UL', { listId: list.id, continueList: list.continueList, listStyle, continueNumbering }); + const ulBlock = this.chunks.createNode('UL', { listId: list.id, continueList: list.continueList, listStyle, continueNumbering }); + this.chunks.append(currentTagNode, ulBlock); + for (const listItem of list.list) { - this.stateMachine.pushTag('LI', { listId: list.id }); + const liBlock = this.chunks.createNode('LI', { listId: list.id }); + this.chunks.append(ulBlock, liBlock); + for (const item of listItem.list) { if (item.type === 'paragraph') { - await this.paragraphToText(item); + await this.paragraphToText(liBlock, item); } if (item.type === 'list') { - await this.listToText(item); + await this.listToText(liBlock, item); } } - this.stateMachine.pushTag('/LI'); } - this.stateMachine.pushTag('/UL', { listId: list.id, listStyle }); } - async officeTextToText(content: OfficeText): Promise { + async officeTextToText(currentTagNode: MarkdownTagNode, content: OfficeText): Promise { for (const child of content.list) { switch (child.type) { case 'paragraph': - await this.paragraphToText(child); + await this.paragraphToText(currentTagNode, child); break; case 'table': - await this.tableToText(child); + await this.tableToText(currentTagNode, child); break; case 'list': - await this.listToText(child); + await this.listToText(currentTagNode, child); break; case 'toc': - await this.tocToText(child); + await this.tocToText(currentTagNode, child); break; } } } - private async rewriteHeaders(txt: string) { - for (const id in this.stateMachine.headersMap) { - const slug = this.stateMachine.headersMap[id]; - txt = txt.replace(new RegExp(id, 'g'), slug); - } - return txt; - } - setPicturesDir(picturesDir: string) { this.picturesDir = picturesDir; } setRewriteRules(rewriteRules: RewriteRule[]) { this.rewriteRules = rewriteRules; - this.stateMachine.setRewriteRules(rewriteRules); + // this.stateMachine.setRewriteRules(rewriteRules); } + + pushError(error: string) { + this.errors.push(error); + } + } diff --git a/src/odt/StateMachine.ts b/src/odt/StateMachine.ts index 3fb1efc3..e91b10d7 100644 --- a/src/odt/StateMachine.ts +++ b/src/odt/StateMachine.ts @@ -1,22 +1,7 @@ -import slugify from 'slugify'; - -import {isClosing, isOpening, MarkdownChunks, OutputMode, TAG, TagPayload} from './MarkdownChunks.ts'; -import {fixCharacters} from './utils.ts'; +import {MarkdownNodes, OutputMode, TAG, TagPayload} from './MarkdownNodes.ts'; import {RewriteRule} from './applyRewriteRule.ts'; -import {postProcessHeaders} from './postprocess/postProcessHeaders.ts'; -import {postProcessPreMacros} from './postprocess/postProcessPreMacros.ts'; -import {addIndentsAndBullets} from './postprocess/addIndentsAndBullets.ts'; -import {fixBold} from './postprocess/fixBold.ts'; -import {hideSuggestedChanges} from './postprocess/hideSuggestedChanges.ts'; -import {addEmptyLines} from './postprocess/addEmptyLines.ts'; -import {mergeParagraphs} from './postprocess/mergeParagraphs.ts'; -import {removePreWrappingAroundMacros} from './postprocess/removePreWrappingAroundMacros.ts'; -import {fixListParagraphs} from './postprocess/fixListParagraphs.ts'; -import {fixSpacesInsideInlineFormatting} from './postprocess/fixSpacesInsideInlineFormatting.ts'; -import {removeInsideDoubleCodeBegin} from './postprocess/removeInsideDoubleCodeBegin.ts'; -import {trimEndOfParagraphs} from './postprocess/trimEndOfParagraphs.ts'; -import {processListsAndNumbering} from './postprocess/processListsAndNumbering.ts'; -import {addEmptyLinesAfterParas} from './postprocess/addEmptyLinesAfterParas.js'; +import {isMarkdownBeginMacro, isMarkdownEndMacro} from './macroUtils.ts'; +import {extractText} from './markdownNodesUtils.js'; interface TagLeaf { mode: OutputMode; @@ -25,53 +10,13 @@ interface TagLeaf { payload: TagPayload; } -export function isMarkdownBeginMacro(innerTxt: string) { - if ('{{markdown}}' === innerTxt) return true; - if ('{{% markdown %}}' === innerTxt) return true; - - if (innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}')) { - // return true; - } - - return false; -} - -export function isMarkdownEndMacro(innerTxt: string) { - if ('{{/markdown}}' === innerTxt) return true; - if ('{{% /markdown %}}' === innerTxt) return true; - - if (innerTxt.startsWith('{{% /pre ') && innerTxt.endsWith(' %}}')) { - // return true; - } - - return false; -} - -export function isMarkdownMacro(innerTxt) { - const prefix = innerTxt.substring(0, innerTxt.indexOf('}}') + '}}'.length); - const suffix = innerTxt.substring(innerTxt.lastIndexOf('{{')); - return isMarkdownBeginMacro(prefix) && isMarkdownEndMacro(suffix); -} - -export function stripMarkdownMacro(innerTxt) { - const prefix = innerTxt.substring(0, innerTxt.indexOf('}}') + '}}'.length); - const suffix = innerTxt.substring(innerTxt.lastIndexOf('{{')); - if (isMarkdownBeginMacro(prefix) && isMarkdownEndMacro(suffix)) { - return innerTxt.substring(prefix.length, innerTxt.length - suffix.length); - } - return innerTxt; -} - export class StateMachine { - public errors: string[] = []; private readonly tagsTree: TagLeaf[] = []; private rewriteRules: RewriteRule[] = []; currentMode: OutputMode = 'md'; - headersMap: { [id: string]: string } = {}; - private listLevels: string[] = []; - constructor(public markdownChunks: MarkdownChunks) { + constructor(public markdownChunks: MarkdownNodes) { } get parentLevel() { @@ -86,31 +31,31 @@ export class StateMachine { } } - pushTag(tag: TAG, payload: TagPayload = {}) { + async pushTag(tag: TAG, payload: TagPayload = {}) { payload.position = this.markdownChunks.length; // PRE-PUSH-PRE-TREEPUSH - if (isOpening(tag)) { - this.tagsTree.push({ - mode: this.currentMode, - tag, - payload, - level: this.tagsTree.length - }); - } - - // PRE-PUSH-AFTER-TREEPUSH - - // PUSH - - this.markdownChunks.push({ - isTag: true, - mode: this.currentMode, - tag: tag, - payload, - comment: 'StateMachine.ts: pushTag' - }); + // if (isOpening(tag)) { + // this.tagsTree.push({ + // mode: this.currentMode, + // tag, + // payload, + // level: this.tagsTree.length + // }); + // } + // + // // PRE-PUSH-AFTER-TREEPUSH + // + // // PUSH + // + // this.markdownChunks.push({ + // isTag: true, + // mode: this.currentMode, + // tag: tag, + // payload, + // comment: 'StateMachine.ts: pushTag' + // }); // POST-PUSH-BEFORE-TREEPOP @@ -136,102 +81,95 @@ export class StateMachine { // } // } - // Inside list item tags like needs to be html tags - if (this.currentMode === 'md' && tag === '/P' && this.parentLevel?.tag === 'LI') { - for (let pos = this.currentLevel.payload.position + 1; pos < payload.position; pos++) { - const chunk = this.markdownChunks.chunks[pos]; - if (chunk.isTag && chunk.tag === 'A') continue; - if (chunk.isTag && chunk.tag === '/A') continue; - if (chunk.isTag && chunk.tag === 'IMG/') continue; - if (chunk.isTag && chunk.tag === 'SVG/') continue; - - chunk.mode = 'html'; - } - } - - if (this.currentMode === 'md' && tag === '/TABLE') { - for (let pos = this.currentLevel.payload.position; pos < payload.position + 1; pos++) { - const chunk = this.markdownChunks.chunks[pos]; - chunk.mode = 'html'; - } - } - - if (tag === 'PRE') { - const prevTag = this.markdownChunks.chunks[payload.position - 1]; - if (prevTag.isTag && prevTag.tag === '/PRE') { - this.markdownChunks.removeChunk(payload.position - 1); - this.markdownChunks.chunks[payload.position] = { - isTag: true, - mode: this.currentMode, - tag: 'BR/', - payload: {}, - comment: 'StateMachine.ts: Merging PRE tags' - }; - } - } - - if (tag === 'B' && ['H1', 'H2', 'H3', 'H4', 'BI'].indexOf(this.parentLevel?.tag) > -1) { - this.markdownChunks.removeChunk(payload.position); - } - if (tag === '/B' && ['H1', 'H2', 'H3', 'H4', '/BI'].indexOf(this.parentLevel?.tag) > -1) { - this.markdownChunks.removeChunk(payload.position); - } - - if (tag === 'P' && this.parentLevel?.tag === 'TD') { - this.markdownChunks.removeChunk(payload.position); - } - if (tag === '/P' && this.parentLevel?.tag === 'TD') { - this.markdownChunks.chunks[payload.position] = { - isTag: true, - mode: this.currentMode, - tag: 'BR/', - payload: {} - }; - } - if (tag === '/TD') { - const prevChunk = this.markdownChunks.chunks[payload.position - 1]; - if (prevChunk.isTag && prevChunk.tag === 'BR/') { - this.markdownChunks.removeChunk(payload.position - 1); - } - } - - if (tag === '/I') { - const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); - if (innerTxt.startsWith('{{%') && innerTxt.endsWith('%}}')) { - this.markdownChunks.removeChunk(payload.position); - this.markdownChunks.removeChunk(this.currentLevel.payload.position); - } - } - - if (tag === 'HTML_MODE/') { - this.currentMode = 'html'; - } - if (tag === 'MD_MODE/') { - this.currentMode = 'md'; - } - - if (['/H1', '/H2', '/H3', '/H4'].includes(tag) && 'md' === this.currentMode) { - if (this.currentLevel.payload.bookmarkName) { - const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); - const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); - if (slug) { - this.headersMap[this.currentLevel.payload.bookmarkName] = slug; - } - } - } + // REFACT + // if (this.currentMode === 'md' && tag === '/TABLE') { + // for (let pos = this.currentLevel.payload.position; pos < payload.position + 1; pos++) { + // const chunk = this.markdownChunks.chunks[pos]; + // chunk.mode = 'html'; + // } + // } + // /REFACT + + // REFACT + // if (tag === 'PRE') { + // const prevTag = this.markdownChunks.chunks[payload.position - 1]; + // if (prevTag.isTag && prevTag.tag === '/PRE') { + // this.markdownChunks.removeChunk(payload.position - 1); + // this.markdownChunks.chunks[payload.position] = { + // isTag: true, + // mode: this.currentMode, + // tag: 'BR/', + // payload: {}, + // comment: 'StateMachine.ts: Merging PRE tags' + // }; + // } + // } + // /REFACT + + // REFACT + // if (tag === 'B' && ['H1', 'H2', 'H3', 'H4', 'BI'].indexOf(this.parentLevel?.tag) > -1) { + // this.markdownChunks.removeChunk(payload.position); + // } + // if (tag === '/B' && ['H1', 'H2', 'H3', 'H4', '/BI'].indexOf(this.parentLevel?.tag) > -1) { + // this.markdownChunks.removeChunk(payload.position); + // } + // /REFACT + + // REFACT + // if (tag === 'P' && this.parentLevel?.tag === 'TD') { + // this.markdownChunks.removeChunk(payload.position); + // } + // if (tag === '/P' && this.parentLevel?.tag === 'TD') { + // this.markdownChunks.chunks[payload.position] = { + // isTag: true, + // mode: this.currentMode, + // tag: 'BR/', + // payload: {} + // }; + // } + // if (tag === '/TD') { + // const prevChunk = this.markdownChunks.chunks[payload.position - 1]; + // if (prevChunk.isTag && prevChunk.tag === 'BR/') { + // this.markdownChunks.removeChunk(payload.position - 1); + // } + // } + // /REFACT + + // REFACT + // if (tag === '/I') { + // const innerTxt = extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); + // if (innerTxt.startsWith('{{%') && innerTxt.endsWith('%}}')) { + // this.markdownChunks.removeChunk(payload.position); + // this.markdownChunks.removeChunk(this.currentLevel.payload.position); + // } + // } + // /REFACT + + // if (tag === 'HTML_MODE/') { + // this.currentMode = 'html'; + // } + // if (tag === 'MD_MODE/') { + // this.currentMode = 'md'; + // } + + // REFACT + // if (['/H1', '/H2', '/H3', '/H4'].includes(tag) && 'md' === this.currentMode) { + // if (this.currentLevel.payload.bookmarkName) { + // const innerTxt = extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); + // const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); + // if (slug) { + // this.headersMap[this.currentLevel.payload.bookmarkName] = slug; + // } + // } + // } + // /REFACT if (tag === '/P' || tag === '/PRE') { - const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); + const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); switch (this.currentMode) { case 'raw': { - switch (innerTxt) { - case '{{/rawhtml}}': - // this.markdownChunks[payload.position].comment = 'Switching to md - {{/rawhtml}}'; - this.currentMode = 'md'; - break; - } - if (isMarkdownEndMacro(innerTxt)) { + if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { // this.markdownChunks[payload.position].comment = 'Switching to md - isMarkdownEndMacro'; this.currentMode = 'md'; } @@ -239,13 +177,7 @@ export class StateMachine { break; case 'md': { - switch (innerTxt) { - case '{{rawhtml}}': - // this.markdownChunks[payload.position].comment = 'Switching to raw - {{rawhtml}}'; - this.currentMode = 'raw'; - break; - } - if (isMarkdownBeginMacro(innerTxt)) { + if (innerTxt === '{{rawhtml}}' || isMarkdownBeginMacro(innerTxt)) { // this.markdownChunks[payload.position].comment = 'Switching to raw - isMarkdownBeginMacro'; this.currentMode = 'raw'; } @@ -255,68 +187,27 @@ export class StateMachine { } } - if (tag === '/CODE') { - const innerTxt = this.markdownChunks.extractText(this.currentLevel.payload.position, payload.position, this.rewriteRules); - switch (this.currentMode) { - case 'md': - if (isMarkdownMacro(innerTxt)) { - this.markdownChunks.replace(this.currentLevel.payload.position, payload.position, { - isTag: false, - mode: this.currentMode, - text: stripMarkdownMacro(innerTxt), - comment: 'StateMachine.ts: replace code part with stripped macro' - }); - } - } - } - - if (isClosing(tag)) { - this.tagsTree.pop(); - } + // REFACT + // if (tag === '/CODE') { + // const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); + // switch (this.currentMode) { + // case 'md': + // if (isMarkdownMacro(innerTxt)) { + // this.markdownChunks.replace(this.currentLevel.payload.position, payload.position, { + // isTag: false, + // mode: this.currentMode, + // text: stripMarkdownMacro(innerTxt), + // comment: 'StateMachine.ts: replace code part with stripped macro' + // }); + // } + // } + // } + // /REFACT + + // if (isClosing(tag)) { + // this.tagsTree.pop(); + // } // POST-PUSH-AFTER-TREEPOP } - - pushText(txt: string) { - txt = fixCharacters(txt); - this.markdownChunks.push({ - isTag: false, - mode: this.currentMode, - text: txt, - comment: 'StateMachine.ts: pushText' - }); - } - - postProcess() { - processListsAndNumbering(this.markdownChunks); - postProcessHeaders(this.markdownChunks); - removePreWrappingAroundMacros(this.markdownChunks); - removeInsideDoubleCodeBegin(this.markdownChunks); - fixSpacesInsideInlineFormatting(this.markdownChunks); - fixBold(this.markdownChunks); - fixListParagraphs(this.markdownChunks); - hideSuggestedChanges(this.markdownChunks); - trimEndOfParagraphs(this.markdownChunks); - addEmptyLinesAfterParas(this.markdownChunks); - addEmptyLines(this.markdownChunks); - addIndentsAndBullets(this.markdownChunks); - postProcessPreMacros(this.markdownChunks); - mergeParagraphs(this.markdownChunks, this.rewriteRules); - - if (process.env.DEBUG_COLORS) { - this.markdownChunks.dump(); - } - } - - pushError(error: string) { - this.errors.push(error); - } - - setRewriteRules(rewriteRules: RewriteRule[]) { - this.rewriteRules = rewriteRules; - } - - setListLevels(listLevels: string[]) { - this.listLevels = listLevels; - } } diff --git a/src/odt/macroUtils.ts b/src/odt/macroUtils.ts new file mode 100644 index 00000000..6a5362a0 --- /dev/null +++ b/src/odt/macroUtils.ts @@ -0,0 +1,44 @@ +export function isMarkdownBeginMacro(innerTxt: string) { + if ('{{markdown}}' === innerTxt) return true; + if ('{{% markdown %}}' === innerTxt) return true; + + if (innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}')) { + // return true; + } + + return false; +} + +export function isMarkdownEndMacro(innerTxt: string) { + if ('{{/markdown}}' === innerTxt) return true; + if ('{{% /markdown %}}' === innerTxt) return true; + + if (innerTxt.startsWith('{{% /pre ') && innerTxt.endsWith(' %}}')) { + // return true; + } + + return false; +} + +export function isMarkdownMacro(innerTxt: string) { + const prefix = innerTxt.substring(0, innerTxt.indexOf('}}') + '}}'.length); + const suffix = innerTxt.substring(innerTxt.lastIndexOf('{{')); + return isMarkdownBeginMacro(prefix) && isMarkdownEndMacro(suffix); +} + +export function stripMarkdownMacro(innerTxt: string) { + const prefix = innerTxt.substring(0, innerTxt.indexOf('}}') + '}}'.length); + const suffix = innerTxt.substring(innerTxt.lastIndexOf('{{')); + if (isMarkdownBeginMacro(prefix) && isMarkdownEndMacro(suffix)) { + return innerTxt.substring(prefix.length, innerTxt.length - suffix.length); + } + return innerTxt; +} + +export function isBeginMacro(innerTxt: string) { + return innerTxt.startsWith('{{% ') && !innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); +} + +export function isEndMacro(innerTxt: string) { + return innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); +} diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts new file mode 100644 index 00000000..b2742d61 --- /dev/null +++ b/src/odt/markdownNodesUtils.ts @@ -0,0 +1,447 @@ +import {Style, TextProperty} from './LibreOffice.ts'; +import {inchesToPixels, spaces} from './utils.ts'; +import {applyRewriteRule, RewriteRule} from './applyRewriteRule.ts'; +import {type MarkdownNode, MarkdownTagNode, OutputMode, TagPayload} from './MarkdownNodes.ts'; + +export function debugChunkToText(chunk: MarkdownNode) { + if (chunk.isTag === false) { + return chunk.text; + } + + return chunk.tag; +} + +export function addComment(chunk: MarkdownTagNode, comment: string) { + if (chunk.comment) { + chunk.comment += ' ' + comment; + } else { + chunk.comment = comment; + } +} + +export function textStyleToString(textProperty: TextProperty) { + if (!textProperty) { + return ''; + } + let styleTxt = ''; + + if (textProperty.fontColor) { + styleTxt += ` fill: ${textProperty.fontColor};`; + } + if (textProperty.fontSize) { + // styleTxt += ` font-size: ${inchesToMm(textProperty.fontSize)}mm;`; + } + + return styleTxt; +} + +function styleToString(style: Style) { + let styleTxt = ''; + if (style?.graphicProperties) { + const graphicProperties = style?.graphicProperties; + // if (graphicProperties.stroke) { + // styleTxt += ` stroke: ${graphicProperties.stroke};`; + // } + if (graphicProperties.strokeWidth) { + styleTxt += ` stroke-width: ${graphicProperties.strokeWidth};`; + } + if (graphicProperties.strokeColor) { + styleTxt += ` stroke: ${graphicProperties.strokeColor};`; + } + if (graphicProperties.strokeLinejoin) { + styleTxt += ` stroke-line-join: ${graphicProperties.strokeLinejoin};`; + } + // if (graphicProperties.fill) { + // styleTxt += ` fill: ${graphicProperties.fill};`; + // } + if (graphicProperties.fillColor) { + styleTxt += ` fill: ${graphicProperties.fillColor};`; + } + } + + if (!styleTxt) { + return 'fill: transparent;'; + } + + return styleTxt; +} + + +function buildSvgStart(payload: TagPayload) { + const width = payload.width; + const height = payload.height; + + + let retVal = `\n`; + const styleTxt = styleToString(payload?.style); + if (styleTxt) { + retVal += `\n`; + } + return retVal; +} + +interface ToTextContext { + mode: OutputMode; + rules: RewriteRule[]; + onlyNotTag?: boolean; + inListItem?: boolean; +} + +function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { + ctx = Object.assign({ rules: [], mode: 'md' }, ctx); + + if (chunk.isTag === false) { + return chunk.text; + } + + if (ctx.inListItem) { + switch (chunk.tag) { + case 'B': + return '' + chunksToText(chunk.children, ctx) + ''; + case 'I': + return '' + chunksToText(chunk.children, ctx) + ''; + case 'BI': + return '' + chunksToText(chunk.children, ctx) + '\''; + } + } + + switch (ctx.mode) { + case 'raw': + switch (chunk.tag) { + case 'BODY': + return chunksToText(chunk.children, ctx); + case 'P': + return chunksToText(chunk.children, ctx) + '\n'; + case 'PRE': + return chunksToText(chunk.children, ctx) + '\n'; + case 'BR/': + return '\n'; + case 'EOL/': + return '\n'; + case 'EMPTY_LINE/': + return '\n'; + case 'BLANK/': + return ''; + default: + return chunksToText(chunk.children, ctx); + } + break; + case 'md': + switch (chunk.tag) { + case 'BODY': + return chunksToText(chunk.children, ctx); + case 'P': + return chunksToText(chunk.children, ctx); + case 'BR/': + return ' \n'; + case 'EOL/': + return '\n'; + case 'EMPTY_LINE/': + return '\n'; + case 'PRE': + return '\n```'+ (chunk.payload?.lang || '') +'\n' + chunksToText(chunk.children, ctx) + '\n```\n'; + case 'CODE': + return '`' + chunksToText(chunk.children, ctx) + '`'; + case 'I': + return '*' + chunksToText(chunk.children, ctx) + '*'; + case 'BI': + return '**_' + chunksToText(chunk.children, ctx) + '_**'; + case 'B': + return '**' + chunksToText(chunk.children, ctx) + '**'; + case 'H1': + return '# ' + chunksToText(chunk.children, ctx) + '\n'; + case 'H2': + return '## ' + chunksToText(chunk.children, ctx) + '\n'; + case 'H3': + return '### ' + chunksToText(chunk.children, ctx) + '\n'; + case 'H4': + return '#### ' + chunksToText(chunk.children, ctx) + '\n'; + case 'HR/': + return '\n___\n'; + case 'A': + return '[' + chunksToText(chunk.children, ctx) + `](${chunk.payload.href})`; + case 'SVG/': + return `![](${chunk.payload.href})`; + case 'IMG/': + return `![](${chunk.payload.href})`; + case 'EMB_SVG': + return buildSvgStart(chunk.payload); + case 'HTML_MODE/': // TODO + return chunksToText(chunk.children, { ...ctx, mode: 'html' }) + '\n'; + case 'LI': // TODO + return chunksToText(chunk.children, { ...ctx, inListItem: true }); + default: + return chunksToText(chunk.children, ctx); + } + break; + case 'html': + switch (chunk.tag) { + case 'BODY': + return chunksToText(chunk.children, ctx); + case 'BR/': + return '\n'; + case 'EOL/': + return '\n'; + case 'EMPTY_LINE/': + return '
    '; + case 'HR/': + return '
    '; + case 'B': + return '' + chunksToText(chunk.children, ctx) + ''; + case 'I': + return '' + chunksToText(chunk.children, ctx) + ''; + case 'BI': + return '' + chunksToText(chunk.children, ctx) + '\''; + case 'H1': + return '

    ' + chunksToText(chunk.children, ctx) + '

    '; + case 'H2': + return '

    ' + chunksToText(chunk.children, ctx) + '

    '; + case 'H3': + return '

    ' + chunksToText(chunk.children, ctx) + '

    '; + case 'H4': + return '

    ' + chunksToText(chunk.children, ctx) + '

    '; + case 'P': + return '

    ' + chunksToText(chunk.children, ctx) + '

    '; + case 'CODE': + return '' + chunksToText(chunk.children, ctx) + ''; + case 'PRE': + return '
    ' + chunksToText(chunk.children, ctx) + '
    '; + case 'UL': + if (chunk.payload.number > 0) { + return '
      ' + chunksToText(chunk.children, ctx) + '
    '; + } else { + return '
      ' + chunksToText(chunk.children, ctx) + '
    '; + } + case 'LI': + return '
  • ' + chunksToText(chunk.children, ctx) + '
  • '; + case 'A': + return `` + chunksToText(chunk.children, ctx) + ''; + case 'TABLE': + return '\n\n' + chunksToText(chunk.children, ctx) + '\n
    \n'; + case 'TR': + return '\n' + chunksToText(chunk.children, ctx) + '\n'; + case 'TD': + return '' + chunksToText(chunk.children, ctx) + '\n'; + case 'TOC': + return chunksToText(chunk.children, ctx); + case 'SVG/': + return ``; + case 'IMG/': + return ``; + case 'EMB_SVG': + return buildSvgStart(chunk.payload) + chunksToText(chunk.children, ctx) + '\n'; + case 'EMB_SVG_G': + { + if (chunk.payload.x || chunk.payload.y) { + const transformStr = `transform="translate(${chunk.payload.x || 0}, ${chunk.payload.y || 0})"`; + return `\n` + chunksToText(chunk.children, ctx) + '\n'; + } + return '\n' + chunksToText(chunk.children, ctx) + '\n'; + } + case 'EMB_SVG_P/': + return `\n`; + case 'EMB_SVG_TEXT': + return `` + chunksToText(chunk.children, ctx) + '\n'; + case 'EMB_SVG_TSPAN': + { + const fontSize = inchesToPixels(chunk.payload.style?.textProperties.fontSize); + return `` + chunksToText(chunk.children, ctx) + '\n'; + } + default: + return chunksToText(chunk.children, ctx); + } + break; + } + + return ''; +} + +export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string { + const retVal = []; + + ctx = Object.assign({ rules: [], mode: 'md' }, ctx); + + for (let chunkNo = 0; chunkNo < chunks.length; chunkNo++) { + const chunk = chunks[chunkNo]; + + if ('tag' in chunk && ['SVG/', 'IMG/'].includes(chunk.tag)) { + let broke = false; + for (const rule of ctx.rules) { + const { shouldBreak, text } = applyRewriteRule(rule, { + ...chunk, + href: 'payload' in chunk ? chunk.payload?.href : undefined, + alt: 'payload' in chunk ? chunk.payload?.alt : undefined + }); + + if (shouldBreak) { + retVal.push(text); + broke = true; + break; + } + } + + if (broke) { + return retVal.join('');; + } + } + + if ('tag' in chunk && 'A' === chunk.tag) { + // let matchingNo = -1; + // + // for (let idx = chunkNo + 1; idx < chunks.length; idx++) { + // const chunkEnd = chunks[idx]; + // if ('tag' in chunkEnd && chunkEnd.tag === '/A') { + // matchingNo = idx; + // break; + // } + // } + + // if (matchingNo !== -1) { + const alt = chunksToText(chunk.children, { ...ctx, onlyNotTag: true }); // .filter(i => !i.isTag) + let broke = false; + for (const rule of ctx.rules) { + const { shouldBreak, text } = applyRewriteRule(rule, { + ...chunk, + href: 'payload' in chunk ? chunk.payload?.href : undefined, + alt + }); + + if (shouldBreak) { + retVal.push(text); + broke = true; + break; + } + } + + // if (broke) { + // chunks.splice(chunkNo, matchingNo - chunkNo); + // return retVal; + // } + // } + } + + retVal.push(chunkToText(chunk, ctx)); + } + + // chunks.map(c => chunkToText(c)); + /* + */ + + return retVal.join(''); +} + +export async function walkRecursiveAsync(node: MarkdownNode, callback: (node: MarkdownNode, ctx?: object) => Promise, ctx?: object) { + if (node.isTag) { + const subCtx = await callback(node, ctx) || Object.assign({}, ctx); + for (let nodeIdx = 0; nodeIdx < node.children.length; nodeIdx++) { + const child = node.children[nodeIdx]; + const retVal = await walkRecursiveAsync(child, callback, { ...subCtx, nodeIdx }); + if (retVal && 'nodeIdx' in retVal && typeof retVal.nodeIdx === 'number') { + nodeIdx = retVal.nodeIdx; + } + } + return subCtx; + } else { + return await callback(node, ctx); + } +} + +export function walkRecursiveSync(node: MarkdownNode, callback: (node: MarkdownNode, ctx?: object) => object | void, ctx?: object, callbackEnd?: (node: MarkdownNode, ctx?: object) => object | void): void | object { + if (node.isTag) { + const subCtx = callback(node, ctx) || Object.assign({}, ctx); + // let nodeIdx = 0; + for (let nodeIdx = 0; nodeIdx < node.children.length; nodeIdx++) { + const child = node.children[nodeIdx]; + const retVal = walkRecursiveSync(child, callback, { ...subCtx, nodeIdx }, callbackEnd); + if (retVal && 'nodeIdx' in retVal && typeof retVal.nodeIdx === 'number') { + nodeIdx = retVal.nodeIdx; + } + } + if (callbackEnd) { + callbackEnd(node, ctx); + } + return subCtx; + } else { + return callback(node, ctx); + } +} + +export async function extractText(node: MarkdownNode) { + let retVal = ''; + await walkRecursiveAsync(node, async (child) => { + if (child.isTag === false) { + retVal += child.text; + return; + } + if (child.isTag === true) { + if (['BR/', 'EOL/', 'EMPTY_LINE/'].includes(child.tag)) { + + } + return; + } + + }); + return retVal; +} + +/* +export function extractText(node: MarkdownTagNode, start: number, end: number, rules: RewriteRule[] = []) { + const slice = chunksToText(node.children.slice(start, end).filter(i => !i.isTag || ['BR/', 'EOL/', 'EMPTY_LINE/'].includes(i.tag)), rules); + return slice; +} +*/ + + +export function dump(body: MarkdownTagNode, logger = console) { + let position = 0; + + walkRecursiveSync(body, (chunk, ctx: { level: number }) => { + let line = position + '\t'; + + switch (chunk.mode) { + case 'md': + line += 'M '; + break; + case 'html': + line += 'H '; + break; + case 'raw': + line += 'R '; + break; + } + + line += spaces(ctx.level); + + if (chunk.isTag === true) { + line += chunk.tag; + + if (chunk.tag === 'UL') { + line += ` (Level: ${chunk.payload.listLevel})`; + } + } + if (chunk.isTag === false) { + line += chunk.text + .replace(/\n/g, '\\n') + .replace(/\t/g, '[TAB]'); + } + + if (chunk.comment) { + line += '\t// ' + chunk.comment; + } + + if (logger === console) { + // if (line.indexOf('StateMachine.ts:') > -1) { + // console.log(ansi_colors.gray(line)); + // continue; + // } + console.log(line); + // continue; + } else { + logger.log(line); + } + + position++; + + return { ...ctx, level: ctx.level + 1 }; + }, { level: 0 }); +} diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index 2783f913..3ed629f3 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -1,4 +1,5 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNode, MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; /* @@ -15,7 +16,7 @@ BR/ */ -function isPreviousChunkEmptyLine(markdownChunks: MarkdownChunks, position: number) { +function isPreviousChunkEmptyLine(markdownChunks: MarkdownNodes, position: number) { const chunk = markdownChunks.chunks[position - 1]; if (!chunk) { return false; @@ -28,7 +29,7 @@ function isPreviousChunkEmptyLine(markdownChunks: MarkdownChunks, position: numb return false; } -function isNextChunkEmptyLine(markdownChunks: MarkdownChunks, position: number) { +function isNextChunkEmptyLine(markdownChunks: MarkdownNodes, position: number) { const chunk = markdownChunks.chunks[position + 1]; if (!chunk) { return false; @@ -44,85 +45,85 @@ function isNextChunkEmptyLine(markdownChunks: MarkdownChunks, position: number) return false; } -export function addEmptyLines(markdownChunks: MarkdownChunks) { +function isChunkEmptyLine(chunk: MarkdownNode) { + if (!chunk) { + return false; + } + + if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { + return true; + } + if (chunk.isTag && 'EOL/' === chunk.tag) { + return true; + } - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; + return false; +} - if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['IMG/', 'SVG/'].indexOf(chunk.tag) > -1) { - const nextTag = markdownChunks.chunks[position + 1]; +export function addEmptyLines(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (!chunk.parent) { + return; + } + + const prevTag = chunk.parent?.children[ctx.nodeIdx - 1] || null; + const nextTag = chunk.parent?.children[ctx.nodeIdx + 1] || null; + if (chunk.isTag && ['IMG/', 'SVG/'].indexOf(chunk.tag) > -1 && nextTag) { if (nextTag.isTag && nextTag.tag === 'IMG/') { - markdownChunks.chunks.splice(position + 1, 0, { - isTag: true, - mode: 'md', - tag: 'EOL/', - payload: {}, + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { + ...markdownChunks.createNode('EOL/'), comment: 'addEmptyLines.ts: EOL/ after IMG/' }, { - isTag: true, - mode: 'md', - tag: 'EMPTY_LINE/', - payload: {}, + ...markdownChunks.createNode('EMPTY_LINE/'), comment: 'addEmptyLines.ts: Between images' }); } - // position--; - continue; + return; } - if (position + 1 < markdownChunks.chunks.length && chunk.isTag && ['/H1', '/H2', '/H3', '/H4', '/UL'].indexOf(chunk.tag) > -1) { - const nextTag = markdownChunks.chunks[position + 1]; - - if (chunk.tag === '/UL' && chunk.payload.listLevel !== 1) { - continue; + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'UL'].indexOf(chunk.tag) > -1 && nextTag) { + if (chunk.tag === 'UL' && chunk.payload.listLevel !== 1) { + return; } - if (chunk.tag === '/UL' && nextTag.isTag && nextTag.tag === 'UL') { - continue; + if (chunk.tag === 'UL' && nextTag.isTag && nextTag.tag === 'UL') { + return; } - if (chunk.tag === '/UL' && nextTag.isTag && nextTag.tag === 'P') { - // continue; + if (chunk.tag === 'UL' && nextTag.isTag && nextTag.tag === 'P') { + // return; } - - if (!isNextChunkEmptyLine(markdownChunks, position) && !isPreviousChunkEmptyLine(markdownChunks, position)) { - markdownChunks.chunks.splice(position + 1, 0, { - isTag: true, - mode: 'md', - tag: 'EMPTY_LINE/', - payload: {}, - comment: 'addEmptyLines.ts: Add empty line after: ' + chunk.tag - }); - position--; - continue; - } - // if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { - // } } + // if (!isChunkEmptyLine(nextTag) && !isChunkEmptyLine(prevTag)) { + // chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { + // ...markdownChunks.createNode('EMPTY_LINE/'), + // comment: 'addEmptyLines.ts: Add empty line after: ' + (chunk.tag || chunk.text) + // }); + // return; + // } + // if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { + // } + // listLevel - if (position > 1 && chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'IMG/', 'SVG/', 'UL'].indexOf(chunk.tag) > -1) { - const prevTag = markdownChunks.chunks[position - 1]; + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'IMG/', 'SVG/', 'UL'].indexOf(chunk.tag) > -1 && nextTag) { + const prevTag = chunk.parent.children[ctx.nodeIdx - 1]; if (chunk.tag === 'UL' && chunk.payload.listLevel !== 1) { - continue; + return; } - if (chunk.tag === 'UL' && prevTag.isTag && prevTag.tag === '/UL') { - continue; + if (chunk.tag === 'UL' && prevTag && prevTag.isTag && prevTag.tag === 'UL') { + return; } - if (chunk.tag === 'UL' && prevTag.isTag && prevTag.tag === '/P') { - // continue; + if (chunk.tag === 'UL' && prevTag && prevTag.isTag && prevTag.tag === 'P') { + // return; } - if (!isNextChunkEmptyLine(markdownChunks, position) && !isPreviousChunkEmptyLine(markdownChunks, position)) { - markdownChunks.chunks.splice(position, 0, { - isTag: true, - mode: 'md', - tag: 'EMPTY_LINE/', - // payload: {}, - comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag + JSON.stringify(prevTag), - payload: {} + if (!isChunkEmptyLine(nextTag) && !isChunkEmptyLine(prevTag)) { + chunk.parent.children.splice(ctx.nodeIdx, 0, { + ...markdownChunks.createNode('EMPTY_LINE/'), + comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag + JSON.stringify({ ...prevTag, children: undefined, parent: undefined }) }); - position++; + return; } if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD') && !(prevTag.isTag && prevTag.tag === 'EMPTY_LINE/')) { @@ -135,32 +136,26 @@ export function addEmptyLines(markdownChunks: MarkdownChunks) { // }); } } - } + }); - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; + + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (!(chunk.isTag && chunk.tag === 'IMG/' && chunk.mode === 'md')) { - continue; + return; } - const nextTag = markdownChunks.chunks[position + 1]; + const nextTag = chunk.parent.children[ctx.nodeIdx + 1] || null; if (!(nextTag.isTag && nextTag.tag === 'BR/' && nextTag.mode === 'md')) { - continue; + return; } - markdownChunks.chunks.splice(position + 1, 1, { - isTag: true, - mode: 'md', - tag: 'EOL/', - payload: {}, + chunk.parent.children.splice(ctx.nodeIdx + 1, 1, { + ...markdownChunks.createNode('EOL/'), comment: 'addEmptyLines.ts: Between images /P' }, { - isTag: true, - mode: 'md', - tag: 'EMPTY_LINE/', - payload: {}, + ...markdownChunks.createNode('EMPTY_LINE/'), comment: 'addEmptyLines.ts: Between images /P' }); - } + }); } diff --git a/src/odt/postprocess/addEmptyLinesAfterParas.ts b/src/odt/postprocess/addEmptyLinesAfterParas.ts index bf6e1138..d9962f8c 100644 --- a/src/odt/postprocess/addEmptyLinesAfterParas.ts +++ b/src/odt/postprocess/addEmptyLinesAfterParas.ts @@ -1,38 +1,36 @@ -import {MarkdownChunks} from '../MarkdownChunks.js'; - -export function addEmptyLinesAfterParas(markdownChunks: MarkdownChunks) { - - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - const nextChunk = markdownChunks.chunks[position + 1] || null; - const prevChunk = markdownChunks.chunks[position - 1] || null; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; +export function addEmptyLinesAfterParas(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.mode !== 'md') { - continue; + return; } if (!chunk.isTag) { - continue; + return; } - if ('/P' === chunk.tag) { + if (chunk.isTag === true && ['P', 'PRE'].includes(chunk.tag)) { + const prevChunk = chunk.children[chunk.children.length - 1] || null; + const nextChunk = chunk.parent.children[ctx.nodeIdx + 1] || null; + + // if (chunk.children.length > 0) { + // + // } + if (nextChunk && nextChunk.isTag && nextChunk.tag === 'EMPTY_LINE/') { // continue; } if (prevChunk && prevChunk.isTag && prevChunk.tag === 'EMPTY_LINE/') { - continue; + return; } - markdownChunks.chunks.splice(position + 1, 0, { - mode: 'md', - isTag: true, - tag: 'EOL/', - comment: 'addEmptyLinesAfterParas.ts: break after para', - payload: {} + chunk.children.splice(chunk.children.length, 0, { + ...markdownChunks.createNode('EOL/'), + comment: 'addEmptyLinesAfterParas.ts: break after ' + chunk.tag, }); - position++; } - } - + }); } diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts index 3454d2b7..f810e887 100644 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ b/src/odt/postprocess/addIndentsAndBullets.ts @@ -1,19 +1,18 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; import {spaces} from '../utils.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - +export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.isTag === true && chunk.tag === 'P' && chunk.mode === 'md') { const level = (chunk.payload.listLevel || 1) - 1; if (!chunk.payload.listLevel) { - continue; + return; } if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { - continue; + return; } // let indent = spaces(level * 4); GDocs not fully compatible @@ -26,8 +25,8 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { const otherStr = indent + spaces(listStr.length); let prevEmptyLine = 1; - for (let position2 = position + 1; position2 < markdownChunks.length; position2++) { - const chunk2 = markdownChunks.chunks[position2]; + for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { + const chunk2 = chunk.parent.children[position2]; if (chunk2.isTag === true && chunk2.tag === '/P' && chunk.mode === 'md') { position += position2 - position - 1; break; @@ -46,7 +45,7 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { } if (prevEmptyLine > 0) { - markdownChunks.chunks.splice(position2, 0, { + chunk.parent.children.splice(position2, 0, { mode: 'md', isTag: false, text: prevEmptyLine === 1 ? firstStr : otherStr, @@ -54,15 +53,14 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { }); prevEmptyLine = 0; position2++; + return; } } } - } + }); let lastItem = null; - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.isTag === true && chunk.tag === 'LI' && chunk.mode === 'md') { lastItem = chunk; } @@ -80,16 +78,15 @@ export function addIndentsAndBullets(markdownChunks: MarkdownChunks) { indent += ' '; } - markdownChunks.chunks.splice(position, 0, { + chunk.parent.children.splice(ctx.nodeIdx, 0, { mode: 'md', isTag: false, text: indent, - comment: 'ddIndentsAndBullets.ts: Indent image, level: ' + level + comment: 'addIndentsAndBullets.ts: Indent image, level: ' + level }); } - position++; - continue; + return; } - } + }); } diff --git a/src/odt/postprocess/fixBold.ts b/src/odt/postprocess/fixBold.ts deleted file mode 100644 index b097a166..00000000 --- a/src/odt/postprocess/fixBold.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; - -export function fixBold(markdownChunks: MarkdownChunks) { - - const matching = { - '/B': 'B', - '/I': 'I' - }; - - const double = ['B', 'I', '/B', '/I']; - - for (let position = 1; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag && Object.keys(matching).indexOf(chunk.tag) > -1) { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag && prevChunk.tag === matching[chunk.tag]) { - markdownChunks.removeChunk(position); - markdownChunks.removeChunk(position - 1); - position-=2; - continue; - } - } - - if (chunk.isTag && ['PRE'].indexOf(chunk.tag) > -1) { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag && prevChunk.tag === '/PRE') { - prevChunk.tag = 'BR/'; - markdownChunks.removeChunk(position); - position--; - continue; - } - } - - if (chunk.isTag && double.indexOf(chunk.tag) > -1) { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag && prevChunk.tag == chunk.tag) { - markdownChunks.removeChunk(position); - position--; - continue; - } - } - } - -} diff --git a/src/odt/postprocess/fixBoldItalic.ts b/src/odt/postprocess/fixBoldItalic.ts new file mode 100644 index 00000000..00ae3ae5 --- /dev/null +++ b/src/odt/postprocess/fixBoldItalic.ts @@ -0,0 +1,39 @@ +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.js'; + +export async function fixBoldItalic(markdownChunks: MarkdownNodes) { + // Remove empty Bold and empty Italic + await walkRecursiveAsync(markdownChunks.body, async (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag === true && ['B', 'I'].includes(chunk.tag)) { + if (chunk.children.length === 0) { + chunk.parent.children.splice(ctx.nodeIdx, 1); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + + if (chunk.children.length === 1) { + const insideChunk = chunk.children[0]; + if (chunk.isTag === true && insideChunk.isTag && chunk.tag === insideChunk.tag) { + chunk.children.splice(0, 1, insideChunk); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + } + + if (chunk.isTag === true && ['B'].includes(chunk.tag)) { + if (chunk.parent.isTag && ['H1', 'H2', 'H3', 'H4', 'BI'].includes(chunk.parent.tag)) { + chunk.parent.children.splice(ctx.nodeIdx, 1, ...chunk.children); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + + if (chunk.isTag === true && ['I'].includes(chunk.tag)) { + const innerTxt = await extractText(chunk); + if (innerTxt.startsWith('{{%') && innerTxt.endsWith('%}}')) { + chunk.parent.children.splice(ctx.nodeIdx, 1, ...chunk.children); + return { nodeIdx: ctx.nodeIdx - 1 }; + // this.markdownChunks.removeChunk(payload.position); + // this.markdownChunks.removeChunk(this.currentLevel.payload.position); + } + } + }); +} diff --git a/src/odt/postprocess/fixListParagraphs.ts b/src/odt/postprocess/fixListParagraphs.ts index 02df4f07..564d48e8 100644 --- a/src/odt/postprocess/fixListParagraphs.ts +++ b/src/odt/postprocess/fixListParagraphs.ts @@ -1,6 +1,34 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.js'; -export function fixListParagraphs(markdownChunks: MarkdownChunks) { +export function fixListParagraphs(markdownChunks: MarkdownNodes) { + + // Inside list item tags like needs to be html tags + let inHtml = false; + + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { level: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag && ['LI'].includes(chunk.tag)) { + if (chunk.children.length === 0) { + return; + } + } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + }); + + /* TODO: WTF? let nextPara = null; for (let position = markdownChunks.length - 1; position >= 0; position--) { const chunk = markdownChunks.chunks[position]; @@ -16,4 +44,5 @@ export function fixListParagraphs(markdownChunks: MarkdownChunks) { nextPara = chunk; } } + */ } diff --git a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts index e13ef2be..ff332961 100644 --- a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts +++ b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts @@ -1,25 +1,29 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownChunks) { - for (let position = 1; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag && ['/B', '/I'].indexOf(chunk.tag) > -1) { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag === false && prevChunk.mode === 'md') { +export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (!chunk.parent) { + return; + } + + if (chunk.isTag && ['B', 'I'].indexOf(chunk.tag) > -1) { + const prevChunk = chunk.children[chunk.children.length - 1]; + if (prevChunk && prevChunk.isTag === false && prevChunk.mode === 'md') { const text = prevChunk.text; const removedTrailingSpaces = text.replace(/[\s]+$/, ''); const spaces = text.substring(removedTrailingSpaces.length); if (spaces.length > 0) { prevChunk.text = removedTrailingSpaces; - markdownChunks.chunks.splice(position + 1, 0, { + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { isTag: false, mode: 'md', text: spaces, comment: 'fixSpacesInsideInlineFormatting.ts: spaces.length > 0' }); - position++; + // position++; } } } - } + }); } diff --git a/src/odt/postprocess/hideSuggestedChanges.ts b/src/odt/postprocess/hideSuggestedChanges.ts index 846c5c69..307b6974 100644 --- a/src/odt/postprocess/hideSuggestedChanges.ts +++ b/src/odt/postprocess/hideSuggestedChanges.ts @@ -1,26 +1,45 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; -export function hideSuggestedChanges(markdownChunks: MarkdownChunks) { - let inChange = false; - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag && chunk.tag === 'CHANGE') { - inChange = true; - markdownChunks.removeChunk(position); - // position--; +export function hideSuggestedChanges(markdownChunks: MarkdownNodes) { + const body = markdownChunks.body; + for (let pos1 = 0; pos1 < body.children.length; pos1++) { + const para1 = body.children[pos1]; + if (!para1.isTag || para1.tag !== 'P') { continue; } - if (chunk.isTag && chunk.tag === '/CHANGE') { - inChange = false; - markdownChunks.removeChunk(position); - // position--; + + let changeStart = -1; + for (let i = 0; i < para1.children.length; i++) { + const child = para1.children[i]; + if (child.isTag && child.tag === 'CHANGE_START') { + changeStart = i; + } + } + + if (!changeStart) { continue; } - if (inChange) { - markdownChunks.removeChunk(position); - // position--; + for (let pos2 = pos1 + 1; pos2 < body.children.length; pos2++) { + const para2 = body.children[pos2]; + if (!para2.isTag || para2.tag !== 'P') { + continue; + } + + let changeEnd = -1; + for (let i = 0; i < para2.children.length; i++) { + const child = para2.children[i]; + if (child.isTag && child.tag === 'CHANGE_END') { + changeEnd = i; + } + } + + if (changeStart > -1 && changeEnd > -1) { + para1.children.splice(changeStart, para1.children.length - changeStart); + para2.children.splice(0, changeEnd + 1); + body.children.splice(pos1 + 1, pos2 - pos1 - 1); + break; + } } } - } diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index 604c8a23..3ec1c5ed 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,15 +1,10 @@ -import {addComment, MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; import {RewriteRule} from '../applyRewriteRule.ts'; +import {isBeginMacro, isEndMacro} from '../macroUtils.ts'; +import {addComment, extractText} from '../markdownNodesUtils.js'; -export function isBeginMacro(innerTxt: string) { - return innerTxt.startsWith('{{% ') && !innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); -} - -export function isEndMacro(innerTxt: string) { - return innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); -} -export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: RewriteRule[]) { +export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules: RewriteRule[]) { let previousParaOpening = 0; const macros = []; @@ -54,7 +49,7 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re const nextParaClosing = markdownChunks.findNext('/P', position); if (nextParaOpening > 0 && nextParaOpening < nextParaClosing) { - const innerText = markdownChunks.extractText(nextParaOpening, nextParaClosing, rewriteRules); + const innerText = extractText(markdownChunks.body, nextParaOpening, nextParaClosing, rewriteRules); if (innerText.length === 0) { // markdownChunks.chunks.splice(nextParaOpening, nextParaClosing - nextParaOpening + 1, { // isTag: true, @@ -69,7 +64,7 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re } if (previousParaOpening > 0) { - const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); + const innerText = extractText(markdownChunks.body, previousParaOpening, position, rewriteRules); if (innerText.length === 0) { //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { @@ -118,7 +113,7 @@ export function mergeParagraphs(markdownChunks: MarkdownChunks, rewriteRules: Re } else { if (previousParaOpening > 0) { - const innerText = markdownChunks.extractText(previousParaOpening, position, rewriteRules); + const innerText = extractText(markdownChunks.body, previousParaOpening, position, rewriteRules); if (innerText.length === 0) { //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { diff --git a/src/odt/postprocess/mergeTexts.ts b/src/odt/postprocess/mergeTexts.ts new file mode 100644 index 00000000..04fb3539 --- /dev/null +++ b/src/odt/postprocess/mergeTexts.ts @@ -0,0 +1,18 @@ +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; + +export function mergeTexts(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.parent && chunk.isTag === false && chunk.mode === 'md') { + const nextChunk = chunk.parent.children[ctx.nodeIdx + 1]; + // console.log('HIT', ctx.nodeIdx); + if (nextChunk?.isTag === false && nextChunk?.mode === 'md') { + chunk.text = chunk.text + nextChunk.text; + chunk.parent.children.splice(ctx.nodeIdx + 1, 1); + + + return {nodeIdx: ctx.nodeIdx - 1}; + } + } + }); +} diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts new file mode 100644 index 00000000..66b68093 --- /dev/null +++ b/src/odt/postprocess/postProcess.ts @@ -0,0 +1,48 @@ +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {dump} from '../markdownNodesUtils.ts'; + +import {processListsAndNumbering} from './processListsAndNumbering.ts'; +import {postProcessHeaders} from './postProcessHeaders.ts'; +import {removePreWrappingAroundMacros} from './removePreWrappingAroundMacros.ts'; +import {removeInsideDoubleCodeBegin} from './removeInsideDoubleCodeBegin.ts'; +import {fixSpacesInsideInlineFormatting} from './fixSpacesInsideInlineFormatting.ts'; +import {fixBoldItalic} from './fixBoldItalic.ts'; +import {fixListParagraphs} from './fixListParagraphs.ts'; +import {hideSuggestedChanges} from './hideSuggestedChanges.ts'; +import {trimEndOfParagraphs} from './trimEndOfParagraphs.ts'; +import {addEmptyLinesAfterParas} from './addEmptyLinesAfterParas.ts'; +import {addEmptyLines} from './addEmptyLines.ts'; +import {addIndentsAndBullets} from './addIndentsAndBullets.ts'; +import {postProcessPreMacros} from './postProcessPreMacros.ts'; +import {mergeParagraphs} from './mergeParagraphs.ts'; +import {removeTdParas} from './removeTdParas.js'; +import {mergeTexts} from './mergeTexts.ts'; +import {rewriteHeaders} from './rewriteHeaders.js'; +import {removeMarkdownMacro} from './removeMarkdownMacro.js'; + +export async function postProcess(chunks: MarkdownNodes) { + removeTdParas(chunks); + processListsAndNumbering(chunks); + postProcessHeaders(chunks); + removePreWrappingAroundMacros(chunks); + removeInsideDoubleCodeBegin(chunks); + fixSpacesInsideInlineFormatting(chunks); + await fixBoldItalic(chunks); + fixListParagraphs(chunks); + hideSuggestedChanges(chunks); + + trimEndOfParagraphs(chunks); + // mergeParagraphs(chunks, this.rewriteRules); + + addEmptyLinesAfterParas(chunks); + addEmptyLines(chunks); + // addIndentsAndBullets(chunks); + mergeTexts(chunks); + // postProcessPreMacros(chunks); + await rewriteHeaders(chunks); + await removeMarkdownMacro(chunks); + + if (process.env.DEBUG_COLORS) { + dump(chunks.body); + } +} diff --git a/src/odt/postprocess/postProcessHeaders.ts b/src/odt/postprocess/postProcessHeaders.ts index f30d0630..f6a98ced 100644 --- a/src/odt/postprocess/postProcessHeaders.ts +++ b/src/odt/postprocess/postProcessHeaders.ts @@ -1,37 +1,27 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function postProcessHeaders(markdownChunks: MarkdownChunks) { - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; +export function postProcessHeaders(markdownChunks: MarkdownNodes) { - if (chunk.isTag && ['/H1', '/H2', '/H3', '/H4'].indexOf(chunk.tag) > -1) { - const prevChunk = markdownChunks.chunks[position - 1]; - const tagOpening = chunk.tag.substring(1); - if (prevChunk.isTag && prevChunk.tag === tagOpening) { - markdownChunks.removeChunk(position); - markdownChunks.removeChunk(position - 1); - position -= 2; - continue; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4'].indexOf(chunk.tag) > -1) { + if (chunk.children.length === 0) { + chunk.parent.children.splice(ctx.nodeIdx, 1); } + return; } + if (chunk.isTag && chunk.tag === 'P') { + if (chunk.children.length === 1) { + const preChunk = chunk.children[0]; - if (chunk.isTag && chunk.tag === 'PRE') { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag && prevChunk.tag === 'P') { - markdownChunks.removeChunk(position - 1); - position--; - continue; + if (preChunk.isTag && preChunk.tag === 'PRE') { + preChunk.parent = chunk.parent; + chunk.parent.children.splice(ctx.nodeIdx, 1, preChunk); + } } + return; } - if (chunk.isTag && chunk.tag === '/PRE') { - const prevChunk = markdownChunks.chunks[position + 1]; - if (prevChunk?.isTag && prevChunk.tag === '/P') { - markdownChunks.removeChunk(position + 1); - position--; - continue; - } - } - } + }); } diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index a1f69efe..5325311d 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -1,4 +1,5 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; function isPreBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}'); @@ -8,20 +9,25 @@ function isPreEndMacro(innerTxt: string) { return innerTxt.startsWith('{{% /pre ') && innerTxt.endsWith(' %}}'); } -export function postProcessPreMacros(markdownChunks: MarkdownChunks) { - for (let position = 1; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; +export function postProcessPreMacros(markdownChunks: MarkdownNodes) { - if (chunk.isTag === false && chunk.mode === 'md') { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag === false && prevChunk.mode === 'md') { - prevChunk.text = prevChunk.text + chunk.text; - markdownChunks.removeChunk(position); - position-=2; - continue; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk && chunk.isTag && chunk.tag === 'PRE') { + const firstChild = chunk.children[0]; + const lastChild = chunk.children[chunk.children.length - 1]; + + if (firstChild.isTag === false && isPreBeginMacro(firstChild.text) && + lastChild.isTag === false && isPreEndMacro(lastChild.text)) { + + chunk.children.splice(0, 1); + chunk.children.splice(chunk.children.length - 1, 1); + + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, lastChild); + chunk.parent.children.splice(ctx.nodeIdx, 0, firstChild); } } +/* if (chunk.isTag === false && isPreBeginMacro(chunk.text)) { const prevChunk = markdownChunks.chunks[position - 1]; if (prevChunk && prevChunk.isTag && prevChunk.tag === 'PRE') { @@ -36,6 +42,10 @@ export function postProcessPreMacros(markdownChunks: MarkdownChunks) { continue; } } + }); + + for (let position = 1; position < markdownChunks.length; position++) { + const chunk = markdownChunks.chunks[position]; if (chunk.isTag === false && isPreEndMacro(chunk.text)) { const postChunk = markdownChunks.chunks[position + 1]; @@ -49,5 +59,6 @@ export function postProcessPreMacros(markdownChunks: MarkdownChunks) { }); } } - } + */ + }); } diff --git a/src/odt/postprocess/postProcessText.ts b/src/odt/postprocess/postProcessText.ts index 1fa5e02a..b14f8027 100644 --- a/src/odt/postprocess/postProcessText.ts +++ b/src/odt/postprocess/postProcessText.ts @@ -1,4 +1,4 @@ -import {isBeginMacro, isEndMacro} from './mergeParagraphs.ts'; +import {isBeginMacro, isEndMacro} from '../macroUtils.ts'; export function emptyString(str: string) { return str.trim().length === 0; diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts index d9891897..ae970fa8 100644 --- a/src/odt/postprocess/processListsAndNumbering.ts +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -1,6 +1,9 @@ -import {MarkdownChunks, TagPayload} from '../MarkdownChunks.ts'; +import {MarkdownNodes, TagPayload} from '../MarkdownNodes.ts'; +import {spaces} from '../utils.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function processListsAndNumbering(markdownChunks: MarkdownChunks) { +export function processListsAndNumbering(markdownChunks: MarkdownNodes) { + const body = markdownChunks.body; const counters: { [id: string]: number } = {}; let preserveMinLevel = 999; @@ -71,31 +74,17 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { const margins = {}; let lastItem = null; - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; + walkRecursiveSync(body, (chunk, ctx: { level: number }) => { if (!chunk.isTag) { - continue; + return; } if (chunk.mode !== 'md') { - continue; + return ; } const tag = chunk.tag; - if (['/TOC', '/UL', '/LI', '/P'].includes(tag)) { - const currentLevel = topElement(stack); - chunk.payload.listLevel = currentLevel.payload.listLevel; - stack.pop(); - - if (tag === '/UL') { - // this.listLevel--; - preserveMinLevel = 999; - } - - continue; - } - const parentLevel = topElement(stack); if ('IMG/' === tag) { @@ -109,7 +98,7 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { payload: chunk.payload }); } else { - continue; + return ; } if ('LI' === tag) { @@ -161,7 +150,6 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { level = margins[currentElement.payload.marginLeft]; } - const listStyle = parentLevel.payload.listStyle || currentElement.payload.listStyle; const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); @@ -176,6 +164,32 @@ export function processListsAndNumbering(markdownChunks: MarkdownChunks) { break; } } - } + + return { ...ctx, level: ctx.level + 1 }; + }, { level: 0 }, (chunk, ctx: { level: number }) => { + if (!chunk.isTag) { + return; + } + + if (chunk.mode !== 'md') { + return ; + } + + const tag = chunk.tag; + + if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { + const currentLevel = topElement(stack); + chunk.payload.listLevel = currentLevel.payload.listLevel; + stack.pop(); + + if (tag === 'UL') { + // this.listLevel--; + preserveMinLevel = 999; + } + + return ; + } + }); + } diff --git a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts index 89f95897..029a3d1c 100644 --- a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts +++ b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts @@ -1,16 +1,17 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function removeInsideDoubleCodeBegin(markdownChunks: MarkdownChunks) { - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag === false && chunk.text.startsWith('```') && chunk.text.length > 3) { - const preChunk = markdownChunks.chunks[position - 2]; - if (preChunk.isTag && preChunk.tag === 'PRE') { - preChunk.payload.lang = chunk.text.substring(3); - markdownChunks.removeChunk(position); - position--; - continue; +export function removeInsideDoubleCodeBegin(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (preChunk) => { + if (preChunk.isTag === true && preChunk.tag === 'PRE') { + if (preChunk.children.length > 0) { + + const firstChild = preChunk.children[0]; + if (firstChild.isTag === false && firstChild.text.startsWith('```') && firstChild.text.length > 3) { + preChunk.payload.lang = firstChild.text.substring(3); + preChunk.children.splice(0, 1); + } } } - } + }); } diff --git a/src/odt/postprocess/removeMarkdownMacro.ts b/src/odt/postprocess/removeMarkdownMacro.ts new file mode 100644 index 00000000..2e80d07e --- /dev/null +++ b/src/odt/postprocess/removeMarkdownMacro.ts @@ -0,0 +1,19 @@ +import {MarkdownNodes} from '../MarkdownNodes.js'; +import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.js'; +import {isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.js'; + +export async function removeMarkdownMacro(markdownChunks: MarkdownNodes) { + await walkRecursiveAsync(markdownChunks.body, async (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'CODE' && chunk.mode === 'md') { + const innerTxt = await extractText(chunk); + if (isMarkdownMacro(innerTxt)) { + chunk.parent.children.splice(ctx.nodeIdx, 1, { + isTag: false, + mode: chunk.mode, + text: stripMarkdownMacro(innerTxt), + comment: 'stripMarkdownMacro.ts: replace code part with stripped macro' + }); + } + } + }); +} diff --git a/src/odt/postprocess/removePreWrappingAroundMacros.ts b/src/odt/postprocess/removePreWrappingAroundMacros.ts index 822ffd31..591e4cf5 100644 --- a/src/odt/postprocess/removePreWrappingAroundMacros.ts +++ b/src/odt/postprocess/removePreWrappingAroundMacros.ts @@ -1,29 +1,27 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; -import {isMarkdownBeginMacro, isMarkdownEndMacro} from '../StateMachine.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {isMarkdownBeginMacro, isMarkdownEndMacro} from '../macroUtils.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function removePreWrappingAroundMacros(markdownChunks: MarkdownChunks) { - for (let position = 0; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag === false && isMarkdownBeginMacro(chunk.text)) { - const prevChunk = markdownChunks.chunks[position - 1]; - const postChunk = markdownChunks.chunks[position + 1]; - if (prevChunk.isTag && prevChunk.tag === 'PRE' && postChunk.isTag && postChunk.tag === '/PRE') { - markdownChunks.removeChunk(position - 1); - postChunk.tag = 'PRE'; - position--; - continue; +export function removePreWrappingAroundMacros(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (preChunk, ctx: { nodeIdx: number }) => { + if (preChunk.isTag === true && preChunk.tag === 'PRE') { + if (preChunk.children.length > 0) { + const lastChild = preChunk.children[preChunk.children.length - 1]; + if (lastChild.isTag === false && isMarkdownEndMacro(lastChild.text)) { + lastChild.parent = preChunk.parent; + preChunk.children.splice(preChunk.children.length - 1, 1); + preChunk.parent.children.splice(ctx.nodeIdx + 1, 0, lastChild); + } } - } - if (chunk.isTag === false && isMarkdownEndMacro(chunk.text)) { - const preChunk = markdownChunks.chunks[position - 1]; - const postChunk = markdownChunks.chunks[position + 1]; - if (preChunk.isTag && preChunk.tag === 'PRE' && postChunk.isTag && postChunk.tag === '/PRE') { - preChunk.tag = '/PRE'; - markdownChunks.removeChunk(position + 1); - position--; - continue; + if (preChunk.children.length > 0) { + const firstChild = preChunk.children[0]; + if (firstChild.isTag === false && isMarkdownBeginMacro(firstChild.text)) { + firstChild.parent = preChunk.parent; + preChunk.children.splice(0, 1); + preChunk.parent.children.splice(ctx.nodeIdx, 0, firstChild); + } } } - } + }); } diff --git a/src/odt/postprocess/removeTdParas.ts b/src/odt/postprocess/removeTdParas.ts new file mode 100644 index 00000000..5daba357 --- /dev/null +++ b/src/odt/postprocess/removeTdParas.ts @@ -0,0 +1,36 @@ +import {MarkdownNodes} from '../MarkdownNodes.js'; +import {walkRecursiveSync} from '../markdownNodesUtils.js'; + +export function removeTdParas(markdownChunks: MarkdownNodes) { + let inHtml = false; + + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { level: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml && chunk.isTag && ['TD', 'LI'].includes(chunk.tag)) { + for (let pos = 0; pos < chunk.children.length; pos++) { + const child = chunk.children[pos]; + if (child.isTag && child.tag === 'P') { + chunk.children.splice(pos, 1, ...child.children); + } + } + + while (chunk.children.length > 0) { + const lastChild = chunk.children[chunk.children.length - 1]; + if (lastChild.isTag && lastChild.tag === 'EOL/') { + chunk.children.splice(chunk.children.length - 1, 1); + continue; + } + break; + } + } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + }); +} diff --git a/src/odt/postprocess/rewriteHeaders.ts b/src/odt/postprocess/rewriteHeaders.ts new file mode 100644 index 00000000..ec094619 --- /dev/null +++ b/src/odt/postprocess/rewriteHeaders.ts @@ -0,0 +1,27 @@ +import slugify from 'slugify'; +import {extractText, walkRecursiveAsync, walkRecursiveSync} from '../markdownNodesUtils.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; + +export async function rewriteHeaders(markdownChunks: MarkdownNodes) { + const headersMap = {}; + + await walkRecursiveAsync(markdownChunks.body, async (chunk) => { + if (chunk.isTag === true && ['H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) { // && 'md' === this.currentMode) { + if (chunk.payload.bookmarkName) { + const innerTxt = await extractText(chunk); + const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); + if (slug) { + headersMap['#' + chunk.payload.bookmarkName] = '#' + slug; + } + } + } + }); + + walkRecursiveSync(markdownChunks.body, (chunk) => { + if (chunk.isTag === true && chunk.payload?.href) { + if (headersMap[chunk.payload.href]) { + chunk.payload.href = headersMap[chunk.payload.href]; + } + } + }); +} diff --git a/src/odt/postprocess/trimEndOfParagraphs.ts b/src/odt/postprocess/trimEndOfParagraphs.ts index 8ff5f87d..d77ab711 100644 --- a/src/odt/postprocess/trimEndOfParagraphs.ts +++ b/src/odt/postprocess/trimEndOfParagraphs.ts @@ -1,14 +1,21 @@ -import {MarkdownChunks} from '../MarkdownChunks.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -export function trimEndOfParagraphs(markdownChunks: MarkdownChunks) { - for (let position = 1; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - - if (chunk.isTag === true && chunk.tag === '/P') { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk.isTag === false) { - prevChunk.text = prevChunk.text.replace(/ +$/, ''); +export function trimEndOfParagraphs(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk) => { + if (chunk.isTag === true && chunk.tag === 'P') { + if (chunk.children.length > 0) { + const lastChunk = chunk.children[chunk.children.length - 1]; + if (lastChunk.isTag === false) { + lastChunk.text = lastChunk.text.replace(/ +$/, ''); + } } } - } + // if (chunk.isTag === true && chunk.tag === '/P') { + // const prevChunk = markdownChunks.chunks[position - 1]; + // if (prevChunk.isTag === false) { + // prevChunk.text = prevChunk.text.replace(/ +$/, ''); + // } + // } + }); } diff --git a/test/odt_md/td-bullets.md b/test/odt_md/td-bullets.md index 2766bbad..d00cadd9 100644 --- a/test/odt_md/td-bullets.md +++ b/test/odt_md/td-bullets.md @@ -1,8 +1,7 @@ - + diff --git a/test/utils.ts b/test/utils.ts index c1b1ead7..f94fcb7b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -43,7 +43,7 @@ export function compareTexts(input, output, ignoreWhitespace = true, fileName = output = output.split('\n').map(line => line.replace(/[\s]+$/, '')).join('\n'); } - const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: true, newlineIsToken: true }); + const patch = createPatch(fileName, input, output, 'oldHeader', 'newHeader', { ignoreWhitespace: true, newlineIsToken: false }); if (patch.indexOf('@@') > -1) { consoleColorPatch(patch); return false; From 1304c53fe633f400fc7717b3eb51d3308a476e24 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sun, 31 Mar 2024 21:41:51 +0200 Subject: [PATCH 07/18] Improve list items --- src/odt/MarkdownNodes.ts | 10 +- src/odt/markdownNodesUtils.ts | 95 ++++++++++++++++++- .../postprocess/addEmptyLinesAfterParas.ts | 6 +- src/odt/postprocess/addIndentsAndBullets.ts | 8 +- .../fixSpacesInsideInlineFormatting.ts | 3 +- src/odt/postprocess/postProcess.ts | 2 +- .../postprocess/processListsAndNumbering.ts | 47 ++++----- src/odt/postprocess/trimEndOfParagraphs.ts | 6 -- 8 files changed, 132 insertions(+), 45 deletions(-) diff --git a/src/odt/MarkdownNodes.ts b/src/odt/MarkdownNodes.ts index bf2ed0dd..501f966a 100644 --- a/src/odt/MarkdownNodes.ts +++ b/src/odt/MarkdownNodes.ts @@ -50,7 +50,6 @@ export interface TagPayload { export interface MarkdownTextNode { isTag: false; - mode: OutputMode; text: string; comment?: string; parent?: MarkdownTagNode; @@ -58,7 +57,6 @@ export interface MarkdownTextNode { export interface MarkdownTagNode { isTag: true; - mode: OutputMode; tag: TAG; payload: TagPayload; comment?: string; @@ -80,18 +78,17 @@ export class MarkdownNodes { this.body = this.createNode('BODY', {}); } - createNode(tag: TAG, payload: TagPayload = {}, mode: OutputMode = 'md'): MarkdownTagNode { + createNode(tag: TAG, payload: TagPayload = {}): MarkdownTagNode { const node: MarkdownTagNode = { isTag: true, tag, - mode, payload, children: [] }; const oldSplice = node.children.splice; node.children.splice = function (start, deleteCount, ...items) { - const retVal = oldSplice.apply(node.children, [start, deleteCount, ...items]) + const retVal = oldSplice.apply(node.children, [start, deleteCount, ...items]); // const retVal = oldSplice(start, deleteCount, ...items); for (let idx = 0; idx < items.length; idx++) { @@ -114,7 +111,7 @@ export class MarkdownNodes { toString(rules: RewriteRule[] = []) { // console.log(this.chunks.map(c => debugChunkToText(c)).join('\n')); - return chunksToText(this.body.children, { rules, mode: 'md' }) + return chunksToText(this.body.children, { rules, mode: 'md', addLiIndents: true }) .split('\n') .map(line => line.trim().length > 0 ? line : '') .join('\n'); @@ -171,7 +168,6 @@ export class MarkdownNodes { txt = fixCharacters(txt); parent.children.push({ isTag: false, - mode: 'md', // this.currentMode, text: txt, parent, comment: 'MarkdownNodes.ts: appendText' diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts index b2742d61..ad2f02a2 100644 --- a/src/odt/markdownNodesUtils.ts +++ b/src/odt/markdownNodesUtils.ts @@ -87,6 +87,93 @@ interface ToTextContext { inListItem?: boolean; } +function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean}, innerText: string) { + if (!ctx.addLiIndents) { + return innerText; + } + if (!chunk.isTag) { + return innerText; + } + if (chunk.tag !== 'LI') { + return innerText; + } + + let noPara = false; + if (chunk.children.length > 0 && chunk.children[0].isTag && chunk.children[0].tag === 'UL') { // No para, no symbol + noPara = true; + // return innerText; + } + + if (!chunk.payload.listLevel) { + // return innerText; + } + + // const level = (chunk.payload.listLevel || 1) - 1; + const level = 0; + + if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { + // return innerText; + } + + // let indent = spaces(level * 4); GDocs not fully compatible + // if (chunk.payload.style?.paragraphProperties?.marginLeft) { + // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); + // } + // const indent = spaces(level * 3); + const listStr = chunk.payload.bullet ? '* ' : chunk.payload.number > 0 ? `${chunk.payload.number}. ` : ''; + // const firstStr = indent + listStr; + // const otherStr = indent + spaces(listStr.length); + + const firstStr = listStr; + const otherStr = spaces(listStr.length || 3); + + return innerText + .split('\n') + .map((line, idx) => { + if (noPara) { + return otherStr + '' + line; + } + if (idx === 0) { + return firstStr + '' + line; + } + return otherStr + '' + line; + }) + .join('\n') + '\n'; + +/* let prevEmptyLine = 1; + for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { + const chunk2 = chunk.parent.children[position2]; + if (chunk2.isTag === true && chunk2.tag === '/P' && chunk.mode === 'md') { + position += position2 - position - 1; + break; + } + + if (chunk2.isTag === true && ['BR/'].indexOf(chunk2.tag) > -1) { + prevEmptyLine = 2; + continue; + } + + if (chunk2.isTag === false && chunk2.text.startsWith('{{% ') && chunk2.text.endsWith(' %}}')) { + const innerText = chunk2.text.substring(3, chunk2.text.length - 3); + if (innerText.indexOf(' %}}') === -1) { + continue; + } + } + + if (prevEmptyLine > 0) { + chunk.parent.children.splice(position2, 0, { + isTag: false, + text: prevEmptyLine === 1 ? firstStr : otherStr, + comment: `addIndentsAndBullets.ts: Indent (${chunk.payload.bullet ? 'bullet' : 'number ' + chunk.payload.number}), level: ` + level + ', prevEmptyLine: ' + (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) + }); + prevEmptyLine = 0; + position2++; + return innerText; + } + } + */ +} + function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { ctx = Object.assign({ rules: [], mode: 'md' }, ctx); @@ -169,7 +256,7 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'HTML_MODE/': // TODO return chunksToText(chunk.children, { ...ctx, mode: 'html' }) + '\n'; case 'LI': // TODO - return chunksToText(chunk.children, { ...ctx, inListItem: true }); + return addLiNumbers(chunk, ctx, chunksToText(chunk.children, { ...ctx, inListItem: true })); default: return chunksToText(chunk.children, ctx); } @@ -269,6 +356,7 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string for (const rule of ctx.rules) { const { shouldBreak, text } = applyRewriteRule(rule, { ...chunk, + mode: 'TODO', // TODO href: 'payload' in chunk ? chunk.payload?.href : undefined, alt: 'payload' in chunk ? chunk.payload?.alt : undefined }); @@ -302,6 +390,7 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string for (const rule of ctx.rules) { const { shouldBreak, text } = applyRewriteRule(rule, { ...chunk, + mode: 'TODO', // TODO href: 'payload' in chunk ? chunk.payload?.href : undefined, alt }); @@ -418,6 +507,10 @@ export function dump(body: MarkdownTagNode, logger = console) { if (chunk.tag === 'UL') { line += ` (Level: ${chunk.payload.listLevel})`; } + if (chunk.tag === 'LI') { + line += ` (${chunk.payload?.bullet || chunk.payload?.number}, Level: ${chunk.payload.listLevel})`; + } + } if (chunk.isTag === false) { line += chunk.text diff --git a/src/odt/postprocess/addEmptyLinesAfterParas.ts b/src/odt/postprocess/addEmptyLinesAfterParas.ts index d9962f8c..33795985 100644 --- a/src/odt/postprocess/addEmptyLinesAfterParas.ts +++ b/src/odt/postprocess/addEmptyLinesAfterParas.ts @@ -3,9 +3,9 @@ import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function addEmptyLinesAfterParas(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (chunk.mode !== 'md') { - return; - } + // if (chunk.mode !== 'md') { + // return; + // } if (!chunk.isTag) { return; diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts index f810e887..536ab73a 100644 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ b/src/odt/postprocess/addIndentsAndBullets.ts @@ -5,12 +5,13 @@ import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.isTag === true && chunk.tag === 'P' && chunk.mode === 'md') { - const level = (chunk.payload.listLevel || 1) - 1; - if (!chunk.payload.listLevel) { return; } + // const level = (chunk.payload.listLevel || 1) - 1; + const level = 0; + if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { return; } @@ -46,7 +47,6 @@ export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { if (prevEmptyLine > 0) { chunk.parent.children.splice(position2, 0, { - mode: 'md', isTag: false, text: prevEmptyLine === 1 ? firstStr : otherStr, comment: `addIndentsAndBullets.ts: Indent (${chunk.payload.bullet ? 'bullet' : 'number ' + chunk.payload.number}), level: ` + level + ', prevEmptyLine: ' + (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) @@ -59,6 +59,7 @@ export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { } }); +/* let lastItem = null; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.isTag === true && chunk.tag === 'LI' && chunk.mode === 'md') { @@ -88,5 +89,6 @@ export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { return; } }); +*/ } diff --git a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts index ff332961..bde579a1 100644 --- a/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts +++ b/src/odt/postprocess/fixSpacesInsideInlineFormatting.ts @@ -9,7 +9,7 @@ export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownNodes) { if (chunk.isTag && ['B', 'I'].indexOf(chunk.tag) > -1) { const prevChunk = chunk.children[chunk.children.length - 1]; - if (prevChunk && prevChunk.isTag === false && prevChunk.mode === 'md') { + if (prevChunk && prevChunk.isTag === false) { const text = prevChunk.text; const removedTrailingSpaces = text.replace(/[\s]+$/, ''); const spaces = text.substring(removedTrailingSpaces.length); @@ -17,7 +17,6 @@ export function fixSpacesInsideInlineFormatting(markdownChunks: MarkdownNodes) { prevChunk.text = removedTrailingSpaces; chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { isTag: false, - mode: 'md', text: spaces, comment: 'fixSpacesInsideInlineFormatting.ts: spaces.length > 0' }); diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index 66b68093..7bcc18b4 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -36,7 +36,7 @@ export async function postProcess(chunks: MarkdownNodes) { addEmptyLinesAfterParas(chunks); addEmptyLines(chunks); - // addIndentsAndBullets(chunks); + addIndentsAndBullets(chunks); mergeTexts(chunks); // postProcessPreMacros(chunks); await rewriteHeaders(chunks); diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts index ae970fa8..a2f8f33b 100644 --- a/src/odt/postprocess/processListsAndNumbering.ts +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -73,24 +73,26 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { const margins = {}; - let lastItem = null; + // let lastItem = null; walkRecursiveSync(body, (chunk, ctx: { level: number }) => { if (!chunk.isTag) { return; } - if (chunk.mode !== 'md') { - return ; - } + // refact + // if (chunk.mode !== 'md') { + // return ; + // } const tag = chunk.tag; const parentLevel = topElement(stack); - if ('IMG/' === tag) { - const level = lastItem?.payload?.listLevel; - chunk.payload.listLevel = level; - } + // TODO: Why? + // if ('IMG/' === tag) { + // const level = lastItem?.payload?.listLevel; + // chunk.payload.listLevel = level; + // } if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { stack.push({ @@ -101,15 +103,15 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { return ; } - if ('LI' === tag) { - lastItem = chunk; - } + // if ('LI' === tag) { + // lastItem = chunk; + // } const listLevel = stack.filter(item => item.tag === 'UL').length; const currentElement = topElement(stack); - if (currentElement.tag === 'UL') { + if ('UL' === currentElement.tag) { currentElement.payload.listLevel = listLevel; if (currentElement.payload.continueNumbering || currentElement.payload.continueList) { @@ -121,7 +123,7 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { } } - if (tag === 'LI') { + if ('LI' === currentElement.tag) { if (parentLevel?.tag === 'UL') { currentElement.payload.listLevel = parentLevel.payload.listLevel; const listStyleName = (currentElement.payload.listStyle?.name || getTopListStyleName()) + '_' + currentElement.payload.listLevel; @@ -131,10 +133,10 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { } } - if (tag === 'P') { - const currentMode = chunk.mode; - switch (currentMode) { - case 'md': + if ('P' === currentElement.tag) { + // const currentMode = chunk.mode; + // switch (currentMode) { + // case 'md': if (parentLevel?.tag === 'TOC') { currentElement.payload.bullet = true; } @@ -159,10 +161,11 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { currentElement.payload.number = parentLevel.payload.number; } else { currentElement.payload.bullet = true; + parentLevel.payload.bullet = true; } } - break; - } + // break; + // } } return { ...ctx, level: ctx.level + 1 }; @@ -171,9 +174,9 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { return; } - if (chunk.mode !== 'md') { - return ; - } + // if (chunk.mode !== 'md') { + // return ; + // } const tag = chunk.tag; diff --git a/src/odt/postprocess/trimEndOfParagraphs.ts b/src/odt/postprocess/trimEndOfParagraphs.ts index d77ab711..45529707 100644 --- a/src/odt/postprocess/trimEndOfParagraphs.ts +++ b/src/odt/postprocess/trimEndOfParagraphs.ts @@ -11,11 +11,5 @@ export function trimEndOfParagraphs(markdownChunks: MarkdownNodes) { } } } - // if (chunk.isTag === true && chunk.tag === '/P') { - // const prevChunk = markdownChunks.chunks[position - 1]; - // if (prevChunk.isTag === false) { - // prevChunk.text = prevChunk.text.replace(/ +$/, ''); - // } - // } }); } From 95c30715299088b615dc9053ee0005d9c20bb73f Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 12:14:18 +0200 Subject: [PATCH 08/18] Improve markdown processing --- src/odt/MarkdownNodes.ts | 2 +- src/odt/markdownNodesUtils.ts | 56 +- src/odt/postprocess/addEmptyLines.ts | 95 +- .../postprocess/addEmptyLinesAfterParas.ts | 20 +- src/odt/postprocess/addIndentsAndBullets.ts | 19 +- src/odt/postprocess/convertToc.ts | 23 + src/odt/postprocess/mergeParagraphs.ts | 43 +- src/odt/postprocess/mergeTexts.ts | 4 +- src/odt/postprocess/postProcess.ts | 32 +- src/odt/postprocess/postProcessPreMacros.ts | 61 +- .../postprocess/processListsAndNumbering.ts | 35 +- src/odt/postprocess/removeEmptyTags.ts | 27 + src/odt/postprocess/removeExcessiveLines.ts | 130 + src/odt/postprocess/removeMarkdownMacro.ts | 18 +- src/odt/postprocess/removeTdParas.ts | 5 +- src/odt/postprocess/trimEndOfParagraphs.ts | 15 - src/odt/postprocess/trimParagraphs.ts | 46 + test/odt_md/MarkDownTransform.test.ts | 70 +- test/odt_md/block-macro.md | 1 - test/odt_md/bullets.md | 1 + test/odt_md/code-links.md | 6 +- test/odt_md/confluence.md | 22 - test/odt_md/embedded-diagram-example.md | 2 +- test/odt_md/example-document.md | 22 +- test/odt_md/example-document.md.json | 4584 ---------- test/odt_md/intro-to-the-system.md | 15 +- test/odt_md/intro-to-the-system.md.json | 7683 ---------------- test/odt_md/intro-to-the-system.odt | Bin 27107 -> 54411 bytes test/odt_md/issue-434-2.md | 2 - test/odt_md/issue-435-436.md | 6 +- test/odt_md/issue-443.md | 18 +- test/odt_md/line-breaks.md | 14 +- test/odt_md/list-indent.md | 51 +- test/odt_md/list-test.md | 52 +- test/odt_md/nested-ordered-list.md | 1 - test/odt_md/project-overview.md | 116 +- test/odt_md/project-overview.md.json | 7767 ----------------- test/odt_md/quotes.md | 3 + test/odt_md/strong-headers.md | 5 + test/odt_md/suggest.md | 50 +- test/odt_md/td-bullets.md | 5 +- 41 files changed, 706 insertions(+), 20421 deletions(-) create mode 100644 src/odt/postprocess/convertToc.ts create mode 100644 src/odt/postprocess/removeEmptyTags.ts create mode 100644 src/odt/postprocess/removeExcessiveLines.ts delete mode 100644 src/odt/postprocess/trimEndOfParagraphs.ts create mode 100644 src/odt/postprocess/trimParagraphs.ts delete mode 100644 test/odt_md/example-document.md.json delete mode 100644 test/odt_md/intro-to-the-system.md.json delete mode 100644 test/odt_md/project-overview.md.json diff --git a/src/odt/MarkdownNodes.ts b/src/odt/MarkdownNodes.ts index 501f966a..b9c2c351 100644 --- a/src/odt/MarkdownNodes.ts +++ b/src/odt/MarkdownNodes.ts @@ -16,7 +16,7 @@ export type TAG = 'BODY' | 'HR/' | 'B' | 'I' | 'BI' | 'BLANK/' | // | '/B' | '/I 'TOC' | 'SVG/' | 'IMG/' | // | '/TOC' 'EMB_SVG' | 'EMB_SVG_G' | 'EMB_SVG_P/' | 'EMB_SVG_TEXT' | // | '/EMB_SVG' | '/EMB_SVG_G' | '/EMB_SVG_TEXT' 'EMB_SVG_TSPAN' | // | '/EMB_SVG_TSPAN' - 'CHANGE_START' | 'CHANGE_END' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; + 'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; export const isSelfClosing = (tag: TAG) => tag.endsWith('/'); // export const isOpening = (tag: TAG) => !tag.startsWith('/') && !tag.endsWith('/'); diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts index ad2f02a2..eb9a1f5d 100644 --- a/src/odt/markdownNodesUtils.ts +++ b/src/odt/markdownNodesUtils.ts @@ -85,9 +85,11 @@ interface ToTextContext { rules: RewriteRule[]; onlyNotTag?: boolean; inListItem?: boolean; + addLiIndents?: boolean; + parentLevel?: number; } -function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean}, innerText: string) { +function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean, parentLevel?: number}, innerText: string) { if (!ctx.addLiIndents) { return innerText; } @@ -108,8 +110,8 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean}, inn // return innerText; } - // const level = (chunk.payload.listLevel || 1) - 1; - const level = 0; + const level = !ctx.parentLevel ? (chunk.payload.listLevel || 1) - 1 : 0; + // const level = 0; if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { // return innerText; @@ -119,17 +121,20 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean}, inn // if (chunk.payload.style?.paragraphProperties?.marginLeft) { // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); // } - // const indent = spaces(level * 3); + const indent = spaces(level * 4); const listStr = chunk.payload.bullet ? '* ' : chunk.payload.number > 0 ? `${chunk.payload.number}. ` : ''; - // const firstStr = indent + listStr; - // const otherStr = indent + spaces(listStr.length); + const firstStr = indent + listStr; + const otherStr = indent + spaces(4); - const firstStr = listStr; - const otherStr = spaces(listStr.length || 3); + // const firstStr = listStr; + // const otherStr = spaces(listStr.length < 3 ? listStr.length : 3 || 3); return innerText .split('\n') - .map((line, idx) => { + .map((line, idx, lines) => { + if (idx === lines.length - 1 && line === '') { // Last line should be EOL, don't put spaces after it + return line; + } if (noPara) { return otherStr + '' + line; } @@ -138,7 +143,7 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean}, inn } return otherStr + '' + line; }) - .join('\n') + '\n'; + .join('\n'); /* let prevEmptyLine = 1; for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { @@ -198,9 +203,9 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'BODY': return chunksToText(chunk.children, ctx); case 'P': - return chunksToText(chunk.children, ctx) + '\n'; + return chunksToText(chunk.children, ctx); case 'PRE': - return chunksToText(chunk.children, ctx) + '\n'; + return chunksToText(chunk.children, ctx); case 'BR/': return '\n'; case 'EOL/': @@ -226,7 +231,7 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'EMPTY_LINE/': return '\n'; case 'PRE': - return '\n```'+ (chunk.payload?.lang || '') +'\n' + chunksToText(chunk.children, ctx) + '\n```\n'; + return '```'+ (chunk.payload?.lang || '') +'\n' + chunksToText(chunk.children, ctx) + '```\n'; case 'CODE': return '`' + chunksToText(chunk.children, ctx) + '`'; case 'I': @@ -238,13 +243,16 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'H1': return '# ' + chunksToText(chunk.children, ctx) + '\n'; case 'H2': + // if (chunk.children.length === 0) { // TODO + // return '\n'; + // } return '## ' + chunksToText(chunk.children, ctx) + '\n'; case 'H3': return '### ' + chunksToText(chunk.children, ctx) + '\n'; case 'H4': return '#### ' + chunksToText(chunk.children, ctx) + '\n'; case 'HR/': - return '\n___\n'; + return '___'; case 'A': return '[' + chunksToText(chunk.children, ctx) + `](${chunk.payload.href})`; case 'SVG/': @@ -254,9 +262,13 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'EMB_SVG': return buildSvgStart(chunk.payload); case 'HTML_MODE/': // TODO - return chunksToText(chunk.children, { ...ctx, mode: 'html' }) + '\n'; + return chunksToText(chunk.children, { ...ctx, mode: 'html' }); + case 'RAW_MODE/': + return chunksToText(chunk.children, { ...ctx, mode: 'raw' }); case 'LI': // TODO - return addLiNumbers(chunk, ctx, chunksToText(chunk.children, { ...ctx, inListItem: true })); + return addLiNumbers(chunk, ctx, chunksToText(chunk.children, { ...ctx, inListItem: true, parentLevel: chunk.payload.listLevel })); + case 'TOC': + return chunksToText(chunk.children, ctx); // TODO default: return chunksToText(chunk.children, ctx); } @@ -266,7 +278,7 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'BODY': return chunksToText(chunk.children, ctx); case 'BR/': - return '\n'; + return '
    \n'; case 'EOL/': return '\n'; case 'EMPTY_LINE/': @@ -304,7 +316,7 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'A': return `` + chunksToText(chunk.children, ctx) + ''; case 'TABLE': - return '\n
    statusCurrent status of the Immunization event -
    • Completed

    • entered-in-error

    • not-done

    Current status of the Immunization event
    • Completed
    • entered-in-error
    • not-done
    Code Yes Yes
    \n' + chunksToText(chunk.children, ctx) + '\n
    \n'; + return '\n' + chunksToText(chunk.children, ctx) + '
    \n'; case 'TR': return '\n' + chunksToText(chunk.children, ctx) + '\n'; case 'TD': @@ -419,7 +431,7 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string return retVal.join(''); } -export async function walkRecursiveAsync(node: MarkdownNode, callback: (node: MarkdownNode, ctx?: object) => Promise, ctx?: object) { +export async function walkRecursiveAsync(node: MarkdownNode, callback: (node: MarkdownNode, ctx?: object) => Promise, ctx?: object, callbackEnd?: (node: MarkdownNode, ctx?: object) => object | void) { if (node.isTag) { const subCtx = await callback(node, ctx) || Object.assign({}, ctx); for (let nodeIdx = 0; nodeIdx < node.children.length; nodeIdx++) { @@ -429,6 +441,9 @@ export async function walkRecursiveAsync(node: MarkdownNode, callback: (node: Ma nodeIdx = retVal.nodeIdx; } } + if (callbackEnd) { + callbackEnd(node, ctx); + } return subCtx; } else { return await callback(node, ctx); @@ -480,7 +495,6 @@ export function extractText(node: MarkdownTagNode, start: number, end: number, r } */ - export function dump(body: MarkdownTagNode, logger = console) { let position = 0; @@ -505,7 +519,7 @@ export function dump(body: MarkdownTagNode, logger = console) { line += chunk.tag; if (chunk.tag === 'UL') { - line += ` (Level: ${chunk.payload.listLevel})`; + line += ` (Level: ${chunk.payload.listLevel}, #${chunk.payload?.number || ''})`; } if (chunk.tag === 'LI') { line += ` (${chunk.payload?.bullet || chunk.payload?.number}, Level: ${chunk.payload.listLevel})`; diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index 3ed629f3..c2cab916 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -53,14 +53,107 @@ function isChunkEmptyLine(chunk: MarkdownNode) { if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { return true; } - if (chunk.isTag && 'EOL/' === chunk.tag) { + if (chunk.isTag && 'BR/' === chunk.tag) { return true; } + if (chunk.isTag && 'EOL/' === chunk.tag) { + // return true; + } return false; } + + export function addEmptyLines(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.parent && chunk.parent.tag !== 'BODY') { + return; + } + + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'P', 'UL', 'IMG/', 'SVG/'].includes(chunk.tag)) { + const prevChunk = chunk.parent.children[ctx.nodeIdx - 1]; + + if (chunk.tag === 'UL') { + if ((chunk.payload.continueNumbering || chunk.payload.number > 1)) { + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'UL') { + return; + } + } else { + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'UL' && !prevChunk.payload.number) { + return; + } + } + } + + if (!isChunkEmptyLine(prevChunk)) { + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'addEmptyLines.ts: before ' + chunk.tag; + chunk.parent.children.splice(ctx.nodeIdx, 0, emptyLine); + return { nodeIdx: ctx.nodeIdx + 1 }; + } + + // chunk.parent.children.splice(ctx.nodeIdx, 1); + // return { nodeIdx: ctx.nodeIdx - 1 }; + } + + }); + + let inHtml = false; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag && chunk.parent?.tag === 'P') { + + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'P', 'UL', 'IMG/', 'SVG/'].includes(chunk.tag)) { + const prevChunk = chunk.parent.children[ctx.nodeIdx - 1]; + + if (chunk.tag === 'UL' && (chunk.payload.continueNumbering || chunk.payload.number > 1)) { + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'UL') { + return; + } else { + if (prevChunk && prevChunk.isTag && prevChunk.tag === 'UL' && !prevChunk.payload.number) { + return; + } + } + } + + if (chunk.isTag && ['IMG/', 'SVG/'].includes(chunk.tag)) { + const nextChunk = chunk.parent.children[ctx.nodeIdx + 1]; + if (!(nextChunk.isTag && nextChunk.tag === 'EOL/')) { + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, markdownChunks.createNode('EOL/')); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + + if (!isChunkEmptyLine(prevChunk)) { + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'addEmptyLines.ts: inside P, before ' + chunk.tag; + chunk.parent.children.splice(ctx.nodeIdx, 0, emptyLine); + return { nodeIdx: ctx.nodeIdx + 1 }; + } + + // chunk.parent.children.splice(ctx.nodeIdx, 1); + // return { nodeIdx: ctx.nodeIdx - 1 }; + } + + } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + }); + + return; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (!chunk.parent) { return; diff --git a/src/odt/postprocess/addEmptyLinesAfterParas.ts b/src/odt/postprocess/addEmptyLinesAfterParas.ts index 33795985..0231d231 100644 --- a/src/odt/postprocess/addEmptyLinesAfterParas.ts +++ b/src/odt/postprocess/addEmptyLinesAfterParas.ts @@ -2,10 +2,17 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function addEmptyLinesAfterParas(markdownChunks: MarkdownNodes) { + + let inHtml = false; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - // if (chunk.mode !== 'md') { - // return; - // } + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } if (!chunk.isTag) { return; @@ -24,7 +31,7 @@ export function addEmptyLinesAfterParas(markdownChunks: MarkdownNodes) { } if (prevChunk && prevChunk.isTag && prevChunk.tag === 'EMPTY_LINE/') { - return; + // return; } chunk.children.splice(chunk.children.length, 0, { @@ -32,5 +39,10 @@ export function addEmptyLinesAfterParas(markdownChunks: MarkdownNodes) { comment: 'addEmptyLinesAfterParas.ts: break after ' + chunk.tag, }); } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } }); } diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts index 536ab73a..76605795 100644 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ b/src/odt/postprocess/addIndentsAndBullets.ts @@ -3,8 +3,18 @@ import {spaces} from '../utils.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { + let inHtml = false; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (chunk.isTag === true && chunk.tag === 'P' && chunk.mode === 'md') { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag === true && chunk.tag === 'P') { if (!chunk.payload.listLevel) { return; } @@ -28,7 +38,7 @@ export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { let prevEmptyLine = 1; for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { const chunk2 = chunk.parent.children[position2]; - if (chunk2.isTag === true && chunk2.tag === '/P' && chunk.mode === 'md') { + if (chunk2.isTag === true && chunk2.tag === '/P') { position += position2 - position - 1; break; } @@ -57,6 +67,11 @@ export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { } } } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } }); /* diff --git a/src/odt/postprocess/convertToc.ts b/src/odt/postprocess/convertToc.ts new file mode 100644 index 00000000..34ef3d21 --- /dev/null +++ b/src/odt/postprocess/convertToc.ts @@ -0,0 +1,23 @@ +import {walkRecursiveSync} from '../markdownNodesUtils.js'; +import {MarkdownNodes} from '../MarkdownNodes.js'; + +export function convertToc(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (!(chunk.isTag && chunk.tag === 'TOC')) { + return; + } + + for (let idx = 0; idx < chunk.children.length; idx++) { + const child = chunk.children[idx]; + if (child.isTag && child.tag === 'P') { + const liElement = markdownChunks.createNode('LI'); + const children = chunk.children.splice(idx, 1, liElement); + liElement.children.splice(0, 0, ...children); + } + } + + const ulElement = markdownChunks.createNode('UL'); + const children = chunk.children.splice(0, chunk.children.length, ulElement); + ulElement.children.splice(0, 0, ...children); + }); +} diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index 3ec1c5ed..698a29a5 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,10 +1,46 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; import {RewriteRule} from '../applyRewriteRule.ts'; -import {isBeginMacro, isEndMacro} from '../macroUtils.ts'; -import {addComment, extractText} from '../markdownNodesUtils.js'; +// import {isBeginMacro, isEndMacro, isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.js'; -export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules: RewriteRule[]) { +export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules?: RewriteRule[]) { + + let inHtml = false; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag && ['P', 'PRE'].includes(chunk.tag)) { + const nextChunk = chunk.parent.children[ctx.nodeIdx + 1]; + if (nextChunk?.isTag && nextChunk.tag === chunk.tag) { + const children = nextChunk.children.splice(0, nextChunk.children.length); + + if (chunk.tag === 'P') { + children.unshift(markdownChunks.createNode('EMPTY_LINE/')); + } + + chunk.children.splice(chunk.children.length, 0, ...children); + + chunk.parent.children.splice(ctx.nodeIdx + 1, 1); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + }); + + +/* let previousParaOpening = 0; const macros = []; @@ -131,4 +167,5 @@ export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules: Rew } } } + */ } diff --git a/src/odt/postprocess/mergeTexts.ts b/src/odt/postprocess/mergeTexts.ts index 04fb3539..89af7eff 100644 --- a/src/odt/postprocess/mergeTexts.ts +++ b/src/odt/postprocess/mergeTexts.ts @@ -3,10 +3,10 @@ import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function mergeTexts(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (chunk.parent && chunk.isTag === false && chunk.mode === 'md') { + if (chunk.parent && chunk.isTag === false) { const nextChunk = chunk.parent.children[ctx.nodeIdx + 1]; // console.log('HIT', ctx.nodeIdx); - if (nextChunk?.isTag === false && nextChunk?.mode === 'md') { + if (nextChunk?.isTag === false) { chunk.text = chunk.text + nextChunk.text; chunk.parent.children.splice(ctx.nodeIdx + 1, 1); diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index 7bcc18b4..7094feb9 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -9,19 +9,22 @@ import {fixSpacesInsideInlineFormatting} from './fixSpacesInsideInlineFormatting import {fixBoldItalic} from './fixBoldItalic.ts'; import {fixListParagraphs} from './fixListParagraphs.ts'; import {hideSuggestedChanges} from './hideSuggestedChanges.ts'; -import {trimEndOfParagraphs} from './trimEndOfParagraphs.ts'; +import {trimParagraphs} from './trimParagraphs.ts'; import {addEmptyLinesAfterParas} from './addEmptyLinesAfterParas.ts'; import {addEmptyLines} from './addEmptyLines.ts'; -import {addIndentsAndBullets} from './addIndentsAndBullets.ts'; -import {postProcessPreMacros} from './postProcessPreMacros.ts'; -import {mergeParagraphs} from './mergeParagraphs.ts'; import {removeTdParas} from './removeTdParas.js'; import {mergeTexts} from './mergeTexts.ts'; import {rewriteHeaders} from './rewriteHeaders.js'; import {removeMarkdownMacro} from './removeMarkdownMacro.js'; +import {postProcessPreMacros} from './postProcessPreMacros.ts'; +import {mergeParagraphs} from './mergeParagraphs.ts'; +import {convertToc} from './convertToc.js'; +import {removeEmptyTags} from './removeEmptyTags.js'; +import {removeExcessiveLines} from './removeExcessiveLines.js'; + export async function postProcess(chunks: MarkdownNodes) { - removeTdParas(chunks); + convertToc(chunks); processListsAndNumbering(chunks); postProcessHeaders(chunks); removePreWrappingAroundMacros(chunks); @@ -31,17 +34,24 @@ export async function postProcess(chunks: MarkdownNodes) { fixListParagraphs(chunks); hideSuggestedChanges(chunks); - trimEndOfParagraphs(chunks); - // mergeParagraphs(chunks, this.rewriteRules); - + trimParagraphs(chunks); addEmptyLinesAfterParas(chunks); - addEmptyLines(chunks); - addIndentsAndBullets(chunks); + removeTdParas(chunks); // Requires: addEmptyLinesAfterParas + + // addIndentsAndBullets(chunks); mergeTexts(chunks); - // postProcessPreMacros(chunks); await rewriteHeaders(chunks); await removeMarkdownMacro(chunks); + // TODO macros + mergeParagraphs(chunks); + postProcessPreMacros(chunks); + + removeEmptyTags(chunks); + addEmptyLines(chunks); + + removeExcessiveLines(chunks); + if (process.env.DEBUG_COLORS) { dump(chunks.body); } diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index 5325311d..4e19d248 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -11,13 +11,65 @@ function isPreEndMacro(innerTxt: string) { export function postProcessPreMacros(markdownChunks: MarkdownNodes) { + let inHtml = false; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk && chunk.isTag && ['P'].includes(chunk.tag)) { + let firstChildIdx = -1; + for (let idx = 0; idx < chunk.children.length; idx++) { + const child = chunk.children[idx]; + if (firstChildIdx === -1) { + // if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { + if (child.isTag === false && child.text === '{{rawhtml}}') { + firstChildIdx = idx; + } + continue; + } + + if (child.isTag && chunk.tag === 'HTML_MODE/') { + firstChildIdx = -1; + continue; + } + + if (firstChildIdx > -1 && child.isTag === false && child.text === '{{/rawhtml}}') { + const afterFirst = chunk.children[firstChildIdx + 1]; + if (afterFirst.isTag && afterFirst.tag === 'EOL/') { + firstChildIdx++; + } + + const lastChildIdx = idx; + + const rawMode = markdownChunks.createNode('RAW_MODE/'); + const children = chunk.children.splice(firstChildIdx + 1, lastChildIdx - firstChildIdx - 1, rawMode); + + rawMode.children.splice(0, 0, ...children); + + idx -= children.length; + + firstChildIdx = -1; + continue; + } + + + + } + } + + if (chunk && chunk.isTag && chunk.tag === 'PRE') { const firstChild = chunk.children[0]; const lastChild = chunk.children[chunk.children.length - 1]; - if (firstChild.isTag === false && isPreBeginMacro(firstChild.text) && - lastChild.isTag === false && isPreEndMacro(lastChild.text)) { + if (firstChild && firstChild.isTag === false && isPreBeginMacro(firstChild.text) && + lastChild && lastChild.isTag === false && isPreEndMacro(lastChild.text)) { chunk.children.splice(0, 1); chunk.children.splice(chunk.children.length - 1, 1); @@ -60,5 +112,10 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { } } */ + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } }); } diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts index a2f8f33b..672cb22c 100644 --- a/src/odt/postprocess/processListsAndNumbering.ts +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -118,8 +118,20 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { preserveMinLevel = listLevel; } + const listStyleName = (currentElement.payload.listStyle?.name || getTopListStyleName()) + '_' + listLevel; if (!(preserveMinLevel <= listLevel)) { clearListsNo((currentElement.payload.listStyle?.name || getTopListStyleName()) + '_', listLevel); + } else { + currentElement.payload.number = fetchListNo(listStyleName) + 1; + currentElement.payload.continueNumbering = true; + } + + const firstChild = chunk.children[0]; + if (firstChild && firstChild.isTag && firstChild.tag === 'LI') { + const firstGrandChild = firstChild.children[0]; + if (firstGrandChild && firstGrandChild.isTag && firstGrandChild.tag !== 'P') { + currentElement.payload.continueNumbering = true; + } } } @@ -127,9 +139,12 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { if (parentLevel?.tag === 'UL') { currentElement.payload.listLevel = parentLevel.payload.listLevel; const listStyleName = (currentElement.payload.listStyle?.name || getTopListStyleName()) + '_' + currentElement.payload.listLevel; - currentElement.payload.number = currentElement.payload.number || fetchListNo(listStyleName); - currentElement.payload.number++; - storeListNo(listStyleName, currentElement.payload.number); + + if (!(chunk.children.length > 0 && chunk.children[0].isTag && chunk.children[0].tag === 'UL')) { // Has para, increase number + currentElement.payload.number = currentElement.payload.number || fetchListNo(listStyleName); + currentElement.payload.number++; + storeListNo(listStyleName, currentElement.payload.number); + } } } @@ -137,9 +152,9 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { // const currentMode = chunk.mode; // switch (currentMode) { // case 'md': - if (parentLevel?.tag === 'TOC') { - currentElement.payload.bullet = true; - } + // if (parentLevel?.tag === 'TOC') { + // currentElement.payload.bullet = true; + // } if (parentLevel?.tag === 'LI') { @@ -156,6 +171,7 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); currentElement.payload.listLevel = level; + parentLevel.payload.listLevel = level; if (isNumeric) { currentElement.payload.number = parentLevel.payload.number; @@ -180,6 +196,13 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { const tag = chunk.tag; + if ('LI' === tag) { + const parentElement = chunk.parent; + if (parentElement?.tag === 'UL') { + // parentElement.payload.listLevel = chunk.payload.listLevel; + } + } + if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { const currentLevel = topElement(stack); chunk.payload.listLevel = currentLevel.payload.listLevel; diff --git a/src/odt/postprocess/removeEmptyTags.ts b/src/odt/postprocess/removeEmptyTags.ts new file mode 100644 index 00000000..a76e216f --- /dev/null +++ b/src/odt/postprocess/removeEmptyTags.ts @@ -0,0 +1,27 @@ +import {MarkdownNodes} from '../MarkdownNodes.js'; +import {walkRecursiveSync} from '../markdownNodesUtils.js'; + +export function removeEmptyTags(markdownChunks: MarkdownNodes) { + let inHtml = false; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'P'].includes(chunk.tag) && chunk.children.length === 0) { + chunk.parent.children.splice(ctx.nodeIdx, 1); + return {nodeIdx: ctx.nodeIdx - 1}; + } + + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + }); +} diff --git a/src/odt/postprocess/removeExcessiveLines.ts b/src/odt/postprocess/removeExcessiveLines.ts new file mode 100644 index 00000000..ff4df9ec --- /dev/null +++ b/src/odt/postprocess/removeExcessiveLines.ts @@ -0,0 +1,130 @@ +import {MarkdownNodes} from '../MarkdownNodes.js'; +import {walkRecursiveSync} from '../markdownNodesUtils.js'; + +export function removeExcessiveLines(markdownChunks: MarkdownNodes) { + + let inHtml = false; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.parent && chunk.parent.tag !== 'BODY') { + return; + } + + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + + }, {}, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } + + if (chunk.isTag && ['P', 'BODY'].includes(chunk.tag)) { + for (let idx = chunk.children.length - 1; idx > 0; idx--) { + const child = chunk.children[idx]; + const prevChild = chunk.children[idx - 1]; + + if (child.isTag && child.tag === 'EOL/') { + if (prevChild.isTag && ['EOL/', 'EMPTY_LINE/'].includes(prevChild.tag)) { + child.tag = 'EMPTY_LINE/'; + child.comment = 'removeExcessiveLines.ts: converted EOL/ to EMPTY_LINE/'; + continue; + } + } + } + } + + if (chunk.isTag && ['P', 'BODY', 'H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) { + if (chunk.children.length > 0) { + const child = chunk.children[0]; + if (child.isTag && child.tag === 'EOL/') { + child.tag = 'EMPTY_LINE/'; + child.comment = 'removeExcessiveLines.ts: converted first EOL/ to EMPTY_LINE/'; + } + } + } + + if (chunk.isTag && ['P'].includes(chunk.tag)) { + for (let idx = chunk.children.length - 1; idx > 0; idx--) { + const child = chunk.children[idx]; + const prevChild = chunk.children[idx - 1]; + + if (child.isTag && child.tag === 'EMPTY_LINE/') { + if (prevChild.isTag && prevChild.tag === 'EMPTY_LINE/') { + chunk.children.splice(idx, 1); + continue; + } + } + } + + for (let idx = chunk.children.length - 1; idx >= 0; idx--) { + const child = chunk.children[idx]; + + if (child.isTag && child.tag === 'EMPTY_LINE/') { + chunk.children.splice(idx, 1); + child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; + + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, child); + } else { + break; + } + } + + if (chunk.children.length > 0) { + const child = chunk.children[0]; + + if (child.isTag && child.tag === 'EMPTY_LINE/') { + chunk.children.splice(0, 1); + child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; + + chunk.parent.children.splice(ctx.nodeIdx, 0, child); + } + } + } + + if (chunk.isTag && ['P', 'BODY', 'H1', 'H2', 'H3', 'H4', 'UL'].includes(chunk.tag)) { + if (chunk.children.length === 0) { + chunk.parent.children.splice(ctx.nodeIdx, 1); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + + }); + + for (let idx = markdownChunks.body.children.length - 1; idx > 0; idx--) { + const child = markdownChunks.body.children[idx]; + const prevChild = markdownChunks.body.children[idx - 1]; + + if (child.isTag && child.tag === 'EMPTY_LINE/') { + if (prevChild.isTag && prevChild.tag === 'EMPTY_LINE/') { + markdownChunks.body.children.splice(idx, 1); + continue; + } + } + } + + while (markdownChunks.body.children.length > 0) { + const firstChild = markdownChunks.body.children[0] + if (firstChild.isTag && firstChild.tag === 'EMPTY_LINE/') { + markdownChunks.body.children.splice(0, 1); + continue; + } + break; + } + + while (markdownChunks.body.children.length > 0) { + const firstChild = markdownChunks.body.children[markdownChunks.body.children.length - 1] + if (firstChild.isTag && firstChild.tag === 'EMPTY_LINE/') { + markdownChunks.body.children.splice(markdownChunks.body.children.length - 1, 1); + continue; + } + break; + } + +} diff --git a/src/odt/postprocess/removeMarkdownMacro.ts b/src/odt/postprocess/removeMarkdownMacro.ts index 2e80d07e..49f3c755 100644 --- a/src/odt/postprocess/removeMarkdownMacro.ts +++ b/src/odt/postprocess/removeMarkdownMacro.ts @@ -3,17 +3,31 @@ import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.js'; import {isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.js'; export async function removeMarkdownMacro(markdownChunks: MarkdownNodes) { + let inHtml = false; await walkRecursiveAsync(markdownChunks.body, async (chunk, ctx: { nodeIdx: number }) => { - if (chunk.isTag && chunk.tag === 'CODE' && chunk.mode === 'md') { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = true; + return; + } + + if (inHtml) { + return; + } + + if (chunk.isTag && chunk.tag === 'CODE') { const innerTxt = await extractText(chunk); if (isMarkdownMacro(innerTxt)) { chunk.parent.children.splice(ctx.nodeIdx, 1, { isTag: false, - mode: chunk.mode, text: stripMarkdownMacro(innerTxt), comment: 'stripMarkdownMacro.ts: replace code part with stripped macro' }); } } + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml = false; + return; + } }); } diff --git a/src/odt/postprocess/removeTdParas.ts b/src/odt/postprocess/removeTdParas.ts index 5daba357..ae6b58b0 100644 --- a/src/odt/postprocess/removeTdParas.ts +++ b/src/odt/postprocess/removeTdParas.ts @@ -2,6 +2,7 @@ import {MarkdownNodes} from '../MarkdownNodes.js'; import {walkRecursiveSync} from '../markdownNodesUtils.js'; export function removeTdParas(markdownChunks: MarkdownNodes) { + // Run after addEmptyLinesAfterParas let inHtml = false; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { level: number }) => { @@ -14,13 +15,13 @@ export function removeTdParas(markdownChunks: MarkdownNodes) { for (let pos = 0; pos < chunk.children.length; pos++) { const child = chunk.children[pos]; if (child.isTag && child.tag === 'P') { - chunk.children.splice(pos, 1, ...child.children); + chunk.children.splice(pos, 1, ...child.children, markdownChunks.createNode('BR/')); } } while (chunk.children.length > 0) { const lastChild = chunk.children[chunk.children.length - 1]; - if (lastChild.isTag && lastChild.tag === 'EOL/') { + if (lastChild.isTag && ['EOL/', 'BR/'].includes(lastChild.tag)) { chunk.children.splice(chunk.children.length - 1, 1); continue; } diff --git a/src/odt/postprocess/trimEndOfParagraphs.ts b/src/odt/postprocess/trimEndOfParagraphs.ts deleted file mode 100644 index 45529707..00000000 --- a/src/odt/postprocess/trimEndOfParagraphs.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {walkRecursiveSync} from '../markdownNodesUtils.ts'; - -export function trimEndOfParagraphs(markdownChunks: MarkdownNodes) { - walkRecursiveSync(markdownChunks.body, (chunk) => { - if (chunk.isTag === true && chunk.tag === 'P') { - if (chunk.children.length > 0) { - const lastChunk = chunk.children[chunk.children.length - 1]; - if (lastChunk.isTag === false) { - lastChunk.text = lastChunk.text.replace(/ +$/, ''); - } - } - } - }); -} diff --git a/src/odt/postprocess/trimParagraphs.ts b/src/odt/postprocess/trimParagraphs.ts new file mode 100644 index 00000000..31621e04 --- /dev/null +++ b/src/odt/postprocess/trimParagraphs.ts @@ -0,0 +1,46 @@ +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; + +export function trimParagraphs(markdownChunks: MarkdownNodes) { + walkRecursiveSync(markdownChunks.body, (chunk) => { + if (chunk.isTag === true && ['P', 'H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) { + while (chunk.children.length > 0) { + const lastChunk = chunk.children[chunk.children.length - 1]; + if (lastChunk.isTag === false) { + let origText = lastChunk.text; + lastChunk.text = lastChunk.text.replace(/\s+$/, ''); + + if (lastChunk.text === '') { + chunk.children.splice(chunk.children.length - 1, 1); + continue; + } + + if (origText === lastChunk.text) { + break; + } + continue; + } + break; + } + + while (chunk.children.length > 0) { + const firstChunk = chunk.children[0]; + if (firstChunk.isTag === false) { + let origText = firstChunk.text; + firstChunk.text = firstChunk.text.replace(/^\s+/, ''); + + if (firstChunk.text === '') { + chunk.children.splice(0, 1); + continue; + } + + if (origText === firstChunk.text) { + break; + } + continue; + } + break; + } + } + }); +} diff --git a/test/odt_md/MarkDownTransform.test.ts b/test/odt_md/MarkDownTransform.test.ts index 4a852dc9..a7b0c35b 100644 --- a/test/odt_md/MarkDownTransform.test.ts +++ b/test/odt_md/MarkDownTransform.test.ts @@ -16,116 +16,116 @@ const __dirname = path.dirname(__filename); describe('MarkDownTransformTest', () => { - it('test ./nested-ordered-list.md.markdown', async () => { + it('test ./nested-ordered-list.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/nested-ordered-list.md').toString(); const markdown = await transformOdt('nested-ordered-list'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./raw-html.md.markdown', async () => { - const testMarkdown = fs.readFileSync(__dirname + '/raw-html.md').toString(); - const markdown = await transformOdt('raw-html'); - assert.ok(compareTexts(testMarkdown, markdown)); - }); - - it('test ./bullets.md.markdown', async () => { + it('test ./bullets.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/bullets.md').toString(); const markdown = await transformOdt('bullets'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./quotes.md.markdown', async () => { + it('test ./quotes.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/quotes.md').toString(); const markdown = await transformOdt('quotes'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./curly-braces.md.markdown', async () => { + it('test ./curly-braces.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/curly-braces.md').toString(); const markdown = await transformOdt('curly-braces'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./confluence.md.markdown', async () => { + it('test ./confluence.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/confluence.md').toString(); const markdown = await transformOdt('confluence'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./block-macro.md.markdown', async () => { - const testMarkdown = fs.readFileSync(__dirname + '/block-macro.md').toString(); - const markdown = await transformOdt('block-macro'); - // console.log(markdown); - assert.ok(compareTexts(testMarkdown, markdown)); - }); - - it('test ./project-overview.md.markdown', async () => { + it('test ./project-overview.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/project-overview.md').toString(); const markdown = await transformOdt('project-overview'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./example-document.md.markdown', async () => { - const testMarkdown = fs.readFileSync(__dirname + '/example-document.md').toString(); - const markdown = await transformOdt('example-document'); - assert.ok(compareTexts(testMarkdown, markdown, false)); - }); - - it('test ./intro-to-the-system.md.markdown', async () => { + it('test ./intro-to-the-system.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/intro-to-the-system.md').toString(); const markdown = await transformOdt('intro-to-the-system'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./list-test.md.markdown', async () => { + it('test ./list-test.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/list-test.md').toString(); const markdown = await transformOdt('list-test'); assert.ok(compareTexts(testMarkdown, markdown, false)); }); - it('test ./list-indent.md.markdown', async () => { + it('test ./list-indent.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/list-indent.md').toString(); const markdown = await transformOdt('list-indent'); assert.ok(compareTexts(testMarkdown, markdown, false)); }); - it('test ./strong-headers.md.markdown', async () => { + it('test ./strong-headers.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/strong-headers.md').toString(); const markdown = await transformOdt('strong-headers'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./embedded-diagram-example', async () => { + it('test ./embedded-diagram-example.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/embedded-diagram-example.md').toString(); const markdown = await transformOdt('embedded-diagram-example'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./suggest', async () => { + it('test ./suggest.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/suggest.md').toString(); const markdown = await transformOdt('suggest'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./pre-mie', async () => { + it('test ./raw-html.md', async () => { + const testMarkdown = fs.readFileSync(__dirname + '/raw-html.md').toString(); + const markdown = await transformOdt('raw-html'); + assert.ok(compareTexts(testMarkdown, markdown)); + }); + + it('test ./pre-mie.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/pre-mie.md').toString(); const markdown = await transformOdt('pre-mie'); assert.ok(compareTexts(testMarkdown, markdown, false)); }); - it('test ./td-bullets', async () => { + it('test ./block-macro.md', async () => { + const testMarkdown = fs.readFileSync(__dirname + '/block-macro.md').toString(); + const markdown = await transformOdt('block-macro'); + // console.log(markdown); + assert.ok(compareTexts(testMarkdown, markdown)); + }); + + it('test ./example-document.md', async () => { + const testMarkdown = fs.readFileSync(__dirname + '/example-document.md').toString(); + const markdown = await transformOdt('example-document'); + assert.ok(compareTexts(testMarkdown, markdown, false)); + }); + + it('test ./td-bullets.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/td-bullets.md').toString(); const markdown = await transformOdt('td-bullets'); assert.ok(compareTexts(testMarkdown, markdown)); }); - it('test ./line-breaks', async () => { + it('test ./line-breaks.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/line-breaks.md').toString(); const markdown = await transformOdt('line-breaks'); assert.ok(compareTexts(testMarkdown, markdown, false)); }); - it('test ./code-links', async () => { + it('test ./code-links.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/code-links.md').toString(); const markdown = await transformOdt('code-links'); assert.ok(compareTexts(testMarkdown, markdown, false)); diff --git a/test/odt_md/block-macro.md b/test/odt_md/block-macro.md index 0bc0e934..9c942030 100644 --- a/test/odt_md/block-macro.md +++ b/test/odt_md/block-macro.md @@ -34,4 +34,3 @@ For more information on filtering tables and using the DataVis grids, see the [U {{% info %}} The logged-in user will now be able to track the enrolled measures from the Quality Reporting portlet on the Quick View. {{% /info %}} - diff --git a/test/odt_md/bullets.md b/test/odt_md/bullets.md index 5e626cfc..fa4a618a 100644 --- a/test/odt_md/bullets.md +++ b/test/odt_md/bullets.md @@ -30,4 +30,5 @@ Item before list * Some item level3 * Some item level3 * Some item level3 + Item after list diff --git a/test/odt_md/code-links.md b/test/odt_md/code-links.md index f96787b9..3807990f 100644 --- a/test/odt_md/code-links.md +++ b/test/odt_md/code-links.md @@ -1,3 +1,5 @@ -Inline markdown test -[![Develop Server Deploy](https://github.com/mieweb/wikiGDrive/actions/workflows/DevelopServerDeploy.yml/badge.svg?branch=develop&event=push)](https://github.com/mieweb/wikiGDrive/actions/workflows/DevelopServerDeploy.yml) [![Prod Server Deploy](https://github.com/mieweb/wikiGDrive/actions/workflows/ProdServerDeploy.yml/badge.svg?branch=master&event=push)](https://github.com/mieweb/wikiGDrive/actions/workflows/ProdServerDeploy.yml) +Inline markdown test + +[![Develop Server Deploy](https://github.com/mieweb/wikiGDrive/actions/workflows/DevelopServerDeploy.yml/badge.svg?branch=develop&event=push)](https://github.com/mieweb/wikiGDrive/actions/workflows/DevelopServerDeploy.yml) [![Prod Server Deploy](https://github.com/mieweb/wikiGDrive/actions/workflows/ProdServerDeploy.yml/badge.svg?branch=master&event=push)](https://github.com/mieweb/wikiGDrive/actions/workflows/ProdServerDeploy.yml) + End diff --git a/test/odt_md/confluence.md b/test/odt_md/confluence.md index dfec21fb..4b961fb1 100644 --- a/test/odt_md/confluence.md +++ b/test/odt_md/confluence.md @@ -4,8 +4,6 @@ Convert Confluence Documents in to Google Documents for the purpose of using WikiGDrive to publish them. - - ## Delivery A new github repo with a node.js script specific to this conversion. @@ -13,50 +11,31 @@ A new github repo with a node.js script specific to this conversion. ## High level Process * Scan all of the documents in a Confluence Space - * Make google documents in a shared drive (two passes will be required so links between documents can be known as content is added). - * Parent/Child relationship must be intact. - * Import Confluence "Attachments" to Google Drive so they can be referenced. - * For each document - * Import content from Confluence Page into Google Documents - * Heading should be headings - * Paragraphs should be paragraphs - * Images (which are attachments in Confluence) should be embedded in google docs - * Macros can be wrapped in {{% curlies %}} and dumped as text - * Example Block Macro: {{% macroname propertyname='value' propertyname='value' %}} macro body, if exists {{% /macroname %}} - * Example Inline Macro: {{% macroname propertyname='value' propertyname='value' /%}} - * Tables should be kept tables - * Embedded Video should be converted to an image with a hyperlink - * Formatting is not required to be converted. - - ## Proposed Instructions - - ``` confluence2google ``` - ## Links and Possible Approaches 1. Use REST API @@ -80,4 +59,3 @@ Simple - [https://confluence.example.com/display/DOCS/Sample](https://confluence Complex - [https://confluence.example.com/pages/viewpage.action?pageId=789](https://confluence.example.com/pages/viewpage.action?pageId=789) API Call: [https://confluence.example.com/rest/api/content/789](https://confluence.example.com/rest/api/content/789) - diff --git a/test/odt_md/embedded-diagram-example.md b/test/odt_md/embedded-diagram-example.md index a8abfa94..3b5b8bc9 100644 --- a/test/odt_md/embedded-diagram-example.md +++ b/test/odt_md/embedded-diagram-example.md @@ -50,8 +50,8 @@ Image from file: done - Inserted as unlinked: + ![](100002010000025C000001488DD4BFD479C0CF61.png) done diff --git a/test/odt_md/example-document.md b/test/odt_md/example-document.md index 93ed0db6..c051270a 100644 --- a/test/odt_md/example-document.md +++ b/test/odt_md/example-document.md @@ -44,10 +44,10 @@ After subtable - ### Heading 3 - a diagram with links -[Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) +[Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) + [Diagram](gdoc:1Du-DYDST4liLykJl0fHSCvuQYIYhtOfwco-ntn38Dy8) ### Heading 3 - with a Table of contents @@ -69,7 +69,6 @@ After subtable ## Preformatted Text - ``` This is monospaced text. This should line up | with this | @@ -110,28 +109,24 @@ From Youtube: [Google Drive, Docs, and Project Management with GSuite](https://www.youtube.com/watch?v=v6QAIWLCz8I&t=1743s) - ## Horizontal Lines This is some text separated by a horizontal line - ___ - This is after the horizontal line. - ## Lists * Bullet 1 * Bullet 2 - * SubBullet 1 - * SubBullet 2 + * SubBullet 1 + * SubBullet 2 * Bullet 3 - 1. SubNumeric 1 - 2. SubNumeric 2 - 3. SubNumeric 3 + 1. SubNumeric 1 + 2. SubNumeric 2 + 3. SubNumeric 3 1. Alpha 1 2. Alpha 2 3. Alpha 3 @@ -142,8 +137,7 @@ Some **bold** **_boldanditalic_*** italic* text ## Equations -### Using the actual equation object - +### Using the actual equation object ### Text equivalent diff --git a/test/odt_md/example-document.md.json b/test/odt_md/example-document.md.json deleted file mode 100644 index b669166f..00000000 --- a/test/odt_md/example-document.md.json +++ /dev/null @@ -1,4584 +0,0 @@ -{ - "title": "Example Document", - "body": { - "content": [{ - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1, - "endIndex": 11, - "paragraph": { - "elements": [{ - "startIndex": 1, - "endIndex": 11, - "textRun": { - "content": "Heading 1\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.pur85qa8iw5l", - "namedStyleType": "HEADING_1", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 11, - "endIndex": 12, - "paragraph": { - "elements": [{ - "startIndex": 11, - "endIndex": 12, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 12, - "endIndex": 28, - "paragraph": { - "elements": [{ - "startIndex": 12, - "endIndex": 28, - "textRun": { - "content": "Heading level 2\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.rwkjzl1scjzh", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 28, - "endIndex": 29, - "paragraph": { - "elements": [{ - "startIndex": 28, - "endIndex": 29, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 29, - "endIndex": 235, - "paragraph": { - "elements": [{ - "startIndex": 29, - "endIndex": 67, - "textRun": { - "content": "Some normal text with hyperlinks to a ", - "textStyle": {} - } - }, - { - "startIndex": 67, - "endIndex": 74, - "textRun": { - "content": "website", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://www.enterprisehealth.com" - } - } - } - }, - { - "startIndex": 74, - "endIndex": 107, - "textRun": { - "content": " and a link to a document on the ", - "textStyle": {} - } - }, - { - "startIndex": 107, - "endIndex": 119, - "textRun": { - "content": "shared drive", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://drive.google.com/a/example.com/open?id=abc" - } - } - } - }, - { - "startIndex": 119, - "endIndex": 146, - "textRun": { - "content": " with multiple versions of ", - "textStyle": {} - } - }, - { - "startIndex": 146, - "endIndex": 154, - "textRun": { - "content": "the link", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://docs.google.com/document/d/abc/edit#" - } - } - } - }, - { - "startIndex": 154, - "endIndex": 185, - "textRun": { - "content": " because people cut and paste. ", - "textStyle": {} - } - }, - { - "startIndex": 185, - "endIndex": 202, - "textRun": { - "content": "Link to test page", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://docs.google.com/document/d/abc" - } - } - } - }, - { - "startIndex": 202, - "endIndex": 212, - "textRun": { - "content": ". Link to ", - "textStyle": {} - } - }, - { - "startIndex": 212, - "endIndex": 233, - "textRun": { - "content": "doc in another folder", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://docs.google.com/document/d/abc/" - } - } - } - }, - { - "startIndex": 233, - "endIndex": 235, - "textRun": { - "content": ".\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 235, - "endIndex": 236, - "paragraph": { - "elements": [{ - "startIndex": 235, - "endIndex": 236, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 236, - "endIndex": 267, - "paragraph": { - "elements": [{ - "startIndex": 236, - "endIndex": 267, - "textRun": { - "content": "Heading level 3 - with a table\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.t3tjnjbci85", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 267, - "endIndex": 268, - "paragraph": { - "elements": [{ - "startIndex": 267, - "endIndex": 268, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 268, - "endIndex": 367, - "table": { - "rows": 2, - "columns": 5, - "tableRows": [{ - "startIndex": 269, - "endIndex": 325, - "tableCells": [{ - "startIndex": 270, - "endIndex": 281, - "content": [{ - "startIndex": 271, - "endIndex": 281, - "paragraph": { - "elements": [{ - "startIndex": 271, - "endIndex": 281, - "textRun": { - "content": "Heading 1\n", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "CENTER", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 281, - "endIndex": 292, - "content": [{ - "startIndex": 282, - "endIndex": 292, - "paragraph": { - "elements": [{ - "startIndex": 282, - "endIndex": 292, - "textRun": { - "content": "Heading 2\n", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "CENTER", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 292, - "endIndex": 303, - "content": [{ - "startIndex": 293, - "endIndex": 303, - "paragraph": { - "elements": [{ - "startIndex": 293, - "endIndex": 303, - "textRun": { - "content": "Heading 3\n", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "CENTER", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 303, - "endIndex": 314, - "content": [{ - "startIndex": 304, - "endIndex": 314, - "paragraph": { - "elements": [{ - "startIndex": 304, - "endIndex": 314, - "textRun": { - "content": "Heading 4\n", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "CENTER", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 314, - "endIndex": 325, - "content": [{ - "startIndex": 315, - "endIndex": 325, - "paragraph": { - "elements": [{ - "startIndex": 315, - "endIndex": 325, - "textRun": { - "content": "Heading 5\n", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "CENTER", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - }, - { - "startIndex": 325, - "endIndex": 366, - "tableCells": [{ - "startIndex": 326, - "endIndex": 334, - "content": [{ - "startIndex": 327, - "endIndex": 334, - "paragraph": { - "elements": [{ - "startIndex": 327, - "endIndex": 334, - "textRun": { - "content": "Cell 1\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": {}, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 334, - "endIndex": 342, - "content": [{ - "startIndex": 335, - "endIndex": 342, - "paragraph": { - "elements": [{ - "startIndex": 335, - "endIndex": 342, - "textRun": { - "content": "Cell 2\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": {}, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 342, - "endIndex": 350, - "content": [{ - "startIndex": 343, - "endIndex": 350, - "paragraph": { - "elements": [{ - "startIndex": 343, - "endIndex": 350, - "textRun": { - "content": "Cell 3\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": {}, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 350, - "endIndex": 358, - "content": [{ - "startIndex": 351, - "endIndex": 358, - "paragraph": { - "elements": [{ - "startIndex": 351, - "endIndex": 358, - "textRun": { - "content": "Cell 4\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": {}, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - }, - { - "startIndex": 358, - "endIndex": 366, - "content": [{ - "startIndex": 359, - "endIndex": 366, - "paragraph": { - "elements": [{ - "startIndex": 359, - "endIndex": 366, - "textRun": { - "content": "Cell 5\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - } - }], - "tableCellStyle": { - "rowSpan": 1, - "columnSpan": 1, - "backgroundColor": {}, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "contentAlignment": "TOP" - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - } - ], - "tableStyle": { - "tableColumnProperties": [{ - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - } - ] - } - } - }, - { - "startIndex": 367, - "endIndex": 368, - "paragraph": { - "elements": [{ - "startIndex": 367, - "endIndex": 368, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 368, - "endIndex": 369, - "paragraph": { - "elements": [{ - "startIndex": 368, - "endIndex": 369, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 369, - "endIndex": 402, - "paragraph": { - "elements": [{ - "startIndex": 369, - "endIndex": 402, - "textRun": { - "content": "Heading 3 - a diagram with links\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.ambls07qke35", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 402, - "endIndex": 403, - "paragraph": { - "elements": [{ - "startIndex": 402, - "endIndex": 403, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 403, - "endIndex": 405, - "paragraph": { - "elements": [{ - "startIndex": 403, - "endIndex": 404, - "inlineObjectElement": { - "inlineObjectId": "kix.45vzc2wq9yr8", - "textStyle": {} - } - }, - { - "startIndex": 404, - "endIndex": 405, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 405, - "endIndex": 406, - "paragraph": { - "elements": [{ - "startIndex": 405, - "endIndex": 406, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 406, - "endIndex": 443, - "paragraph": { - "elements": [{ - "startIndex": 406, - "endIndex": 443, - "textRun": { - "content": "Heading 3 - with a Table of contents\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.f49uy1gok3t5", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 443, - "endIndex": 444, - "paragraph": { - "elements": [{ - "startIndex": 443, - "endIndex": 444, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 444, - "endIndex": 612, - "tableOfContents": { - "content": [{ - "startIndex": 445, - "endIndex": 455, - "paragraph": { - "elements": [{ - "startIndex": 445, - "endIndex": 454, - "textRun": { - "content": "Heading 1", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.pur85qa8iw5l" - } - } - } - }, - { - "startIndex": 454, - "endIndex": 455, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 4, - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - } - } - } - }, - { - "startIndex": 455, - "endIndex": 471, - "paragraph": { - "elements": [{ - "startIndex": 455, - "endIndex": 470, - "textRun": { - "content": "Heading level 2", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.rwkjzl1scjzh" - } - } - } - }, - { - "startIndex": 470, - "endIndex": 471, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 471, - "endIndex": 502, - "paragraph": { - "elements": [{ - "startIndex": 471, - "endIndex": 501, - "textRun": { - "content": "Heading level 3 - with a table", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.t3tjnjbci85" - } - } - } - }, - { - "startIndex": 501, - "endIndex": 502, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 502, - "endIndex": 535, - "paragraph": { - "elements": [{ - "startIndex": 502, - "endIndex": 534, - "textRun": { - "content": "Heading 3 - a diagram with links", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.ambls07qke35" - } - } - } - }, - { - "startIndex": 534, - "endIndex": 535, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 535, - "endIndex": 572, - "paragraph": { - "elements": [{ - "startIndex": 535, - "endIndex": 571, - "textRun": { - "content": "Heading 3 - with a Table of contents", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.f49uy1gok3t5" - } - } - } - }, - { - "startIndex": 571, - "endIndex": 572, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 572, - "endIndex": 587, - "paragraph": { - "elements": [{ - "startIndex": 572, - "endIndex": 586, - "textRun": { - "content": "Other examples", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.p5x030kzej77" - } - } - } - }, - { - "startIndex": 586, - "endIndex": 587, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - } - } - } - }, - { - "startIndex": 587, - "endIndex": 593, - "paragraph": { - "elements": [{ - "startIndex": 587, - "endIndex": 592, - "textRun": { - "content": "Image", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.p56cvcv8bx70" - } - } - } - }, - { - "startIndex": 592, - "endIndex": 593, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 593, - "endIndex": 611, - "paragraph": { - "elements": [{ - "startIndex": 593, - "endIndex": 610, - "textRun": { - "content": "Preformatted Text", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.74or9yzabmh6" - } - } - } - }, - { - "startIndex": 610, - "endIndex": 611, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - } - ] - } - }, - { - "startIndex": 612, - "endIndex": 613, - "paragraph": { - "elements": [{ - "startIndex": 612, - "endIndex": 613, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 613, - "endIndex": 628, - "paragraph": { - "elements": [{ - "startIndex": 613, - "endIndex": 628, - "textRun": { - "content": "Other examples\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.p5x030kzej77", - "namedStyleType": "HEADING_1", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 628, - "endIndex": 629, - "paragraph": { - "elements": [{ - "startIndex": 628, - "endIndex": 629, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 629, - "endIndex": 635, - "paragraph": { - "elements": [{ - "startIndex": 629, - "endIndex": 635, - "textRun": { - "content": "Image\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.p56cvcv8bx70", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 635, - "endIndex": 637, - "paragraph": { - "elements": [{ - "startIndex": 635, - "endIndex": 636, - "inlineObjectElement": { - "inlineObjectId": "kix.yxw8u6creutz", - "textStyle": {} - } - }, - { - "startIndex": 636, - "endIndex": 637, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 637, - "endIndex": 639, - "paragraph": { - "elements": [{ - "startIndex": 637, - "endIndex": 638, - "inlineObjectElement": { - "inlineObjectId": "kix.6vqlx5cgywvz", - "textStyle": {} - } - }, - { - "startIndex": 638, - "endIndex": 639, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 639, - "endIndex": 657, - "paragraph": { - "elements": [{ - "startIndex": 639, - "endIndex": 657, - "textRun": { - "content": "Preformatted Text\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.74or9yzabmh6", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 657, - "endIndex": 658, - "paragraph": { - "elements": [{ - "startIndex": 657, - "endIndex": 658, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 658, - "endIndex": 706, - "paragraph": { - "elements": [{ - "startIndex": 658, - "endIndex": 706, - "textRun": { - "content": "This is monospaced text. This should line up |\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.u4r0fzitq9go", - "namedStyleType": "SUBTITLE", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 706, - "endIndex": 754, - "paragraph": { - "elements": [{ - "startIndex": 706, - "endIndex": 754, - "textRun": { - "content": " with this |\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.xfm80x6au75v", - "namedStyleType": "SUBTITLE", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 754, - "endIndex": 757, - "paragraph": { - "elements": [{ - "startIndex": 754, - "endIndex": 757, - "textRun": { - "content": " \n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.kp9rgbt3gma0", - "namedStyleType": "SUBTITLE", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 757, - "endIndex": 758, - "paragraph": { - "elements": [{ - "startIndex": 757, - "endIndex": 758, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 758, - "endIndex": 763, - "paragraph": { - "elements": [{ - "startIndex": 758, - "endIndex": 763, - "textRun": { - "content": "Code\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.niow4ogfp967", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 763, - "endIndex": 1254, - "paragraph": { - "elements": [{ - "startIndex": 763, - "endIndex": 882, - "textRun": { - "content": "Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and ", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }, - { - "startIndex": 882, - "endIndex": 895, - "textRun": { - "content": "Markdown Here", - "textStyle": { - "italic": true, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }, - { - "startIndex": 895, - "endIndex": 1042, - "textRun": { - "content": " -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. ", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1042, - "endIndex": 1055, - "textRun": { - "content": "Markdown Here", - "textStyle": { - "italic": true, - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1055, - "endIndex": 1230, - "textRun": { - "content": " supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the ", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1230, - "endIndex": 1252, - "textRun": { - "content": "highlight.js demo page", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.011764706, - "green": 0.4, - "blue": 0.8392157 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "link": { - "url": "http://softwaremaniacs.org/media/soft/highlight/test.html" - } - } - } - }, - { - "startIndex": 1252, - "endIndex": 1254, - "textRun": { - "content": ".\n", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1254, - "endIndex": 1255, - "paragraph": { - "elements": [{ - "startIndex": 1254, - "endIndex": 1255, - "textRun": { - "content": "\n", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "red": 1, - "green": 1, - "blue": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.14117648, - "green": 0.16078432, - "blue": 0.18039216 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1255, - "endIndex": 1279, - "paragraph": { - "elements": [{ - "startIndex": 1255, - "endIndex": 1279, - "textRun": { - "content": "Typescript / Javascript\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.jt47qp4o5ir1", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1279, - "endIndex": 1292, - "paragraph": { - "elements": [{ - "startIndex": 1279, - "endIndex": 1292, - "textRun": { - "content": "{{% markdown %}}\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1292, - "endIndex": 1306, - "paragraph": { - "elements": [{ - "startIndex": 1292, - "endIndex": 1306, - "textRun": { - "content": "```javascript\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1306, - "endIndex": 1322, - "paragraph": { - "elements": [{ - "startIndex": 1306, - "endIndex": 1322, - "textRun": { - "content": "class MyClass {\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1322, - "endIndex": 1355, - "paragraph": { - "elements": [{ - "startIndex": 1322, - "endIndex": 1355, - "textRun": { - "content": " public static myValue: string;\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1355, - "endIndex": 1385, - "paragraph": { - "elements": [{ - "startIndex": 1355, - "endIndex": 1385, - "textRun": { - "content": " constructor(init: string) {\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1385, - "endIndex": 1410, - "paragraph": { - "elements": [{ - "startIndex": 1385, - "endIndex": 1410, - "textRun": { - "content": " this.myValue = init;\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1410, - "endIndex": 1414, - "paragraph": { - "elements": [{ - "startIndex": 1410, - "endIndex": 1414, - "textRun": { - "content": " }\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1414, - "endIndex": 1416, - "paragraph": { - "elements": [{ - "startIndex": 1414, - "endIndex": 1416, - "textRun": { - "content": "}\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1416, - "endIndex": 1443, - "paragraph": { - "elements": [{ - "startIndex": 1416, - "endIndex": 1443, - "textRun": { - "content": "import fs = require(\"fs\");\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1443, - "endIndex": 1461, - "paragraph": { - "elements": [{ - "startIndex": 1443, - "endIndex": 1461, - "textRun": { - "content": "module MyModule {\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1461, - "endIndex": 1508, - "paragraph": { - "elements": [{ - "startIndex": 1461, - "endIndex": 1508, - "textRun": { - "content": " export interface MyInterface extends Other {\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1508, - "endIndex": 1529, - "paragraph": { - "elements": [{ - "startIndex": 1508, - "endIndex": 1529, - "textRun": { - "content": " myProperty: any;\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1529, - "endIndex": 1533, - "paragraph": { - "elements": [{ - "startIndex": 1529, - "endIndex": 1533, - "textRun": { - "content": " }\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1533, - "endIndex": 1535, - "paragraph": { - "elements": [{ - "startIndex": 1533, - "endIndex": 1535, - "textRun": { - "content": "}\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1535, - "endIndex": 1563, - "paragraph": { - "elements": [{ - "startIndex": 1535, - "endIndex": 1563, - "textRun": { - "content": "declare magicNumber number;\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1563, - "endIndex": 1611, - "paragraph": { - "elements": [{ - "startIndex": 1563, - "endIndex": 1611, - "textRun": { - "content": "myArray.forEach(() => { }); // fat arrow syntax\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1611, - "endIndex": 1615, - "paragraph": { - "elements": [{ - "startIndex": 1611, - "endIndex": 1615, - "textRun": { - "content": "```\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1615, - "endIndex": 1629, - "paragraph": { - "elements": [{ - "startIndex": 1615, - "endIndex": 1629, - "textRun": { - "content": "{{% /markdown %}}\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1629, - "endIndex": 1630, - "paragraph": { - "elements": [{ - "startIndex": 1629, - "endIndex": 1630, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1630, - "endIndex": 1636, - "paragraph": { - "elements": [{ - "startIndex": 1630, - "endIndex": 1636, - "textRun": { - "content": "Video\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.9b72fldy8rju", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1636, - "endIndex": 1637, - "paragraph": { - "elements": [{ - "startIndex": 1636, - "endIndex": 1637, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1637, - "endIndex": 1651, - "paragraph": { - "elements": [{ - "startIndex": 1637, - "endIndex": 1651, - "textRun": { - "content": "From Youtube:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1651, - "endIndex": 1653, - "paragraph": { - "elements": [{ - "startIndex": 1651, - "endIndex": 1652, - "inlineObjectElement": { - "inlineObjectId": "kix.riy9ecw8monw", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://www.youtube.com/watch?v=v6QAIWLCz8I&t=1743s" - } - } - } - }, - { - "startIndex": 1652, - "endIndex": 1653, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1653, - "endIndex": 1708, - "paragraph": { - "elements": [{ - "startIndex": 1653, - "endIndex": 1707, - "textRun": { - "content": "Google Drive, Docs, and Project Management with GSuite", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://www.youtube.com/watch?v=v6QAIWLCz8I&t=1743s" - } - } - } - }, - { - "startIndex": 1707, - "endIndex": 1708, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1708, - "endIndex": 1709, - "paragraph": { - "elements": [{ - "startIndex": 1708, - "endIndex": 1709, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1709, - "endIndex": 1726, - "paragraph": { - "elements": [{ - "startIndex": 1709, - "endIndex": 1726, - "textRun": { - "content": "Horizontal Lines\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.q7zgn9e3oi6b", - "namedStyleType": "HEADING_2", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1726, - "endIndex": 1727, - "paragraph": { - "elements": [{ - "startIndex": 1726, - "endIndex": 1727, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1727, - "endIndex": 1776, - "paragraph": { - "elements": [{ - "startIndex": 1727, - "endIndex": 1776, - "textRun": { - "content": "This is some text separated by a horizontal line\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1776, - "endIndex": 1777, - "paragraph": { - "elements": [{ - "startIndex": 1776, - "endIndex": 1777, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1777, - "endIndex": 1779, - "paragraph": { - "elements": [{ - "startIndex": 1777, - "endIndex": 1778, - "horizontalRule": { - "textStyle": {} - } - }, - { - "startIndex": 1778, - "endIndex": 1779, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1779, - "endIndex": 1780, - "paragraph": { - "elements": [{ - "startIndex": 1779, - "endIndex": 1780, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1780, - "endIndex": 1815, - "paragraph": { - "elements": [{ - "startIndex": 1780, - "endIndex": 1815, - "textRun": { - "content": "This is after the horizontal line.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1815, - "endIndex": 1816, - "paragraph": { - "elements": [{ - "startIndex": 1815, - "endIndex": 1816, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1816, - "endIndex": 1826, - "paragraph": { - "elements": [{ - "startIndex": 1816, - "endIndex": 1826, - "textRun": { - "content": "Equations\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.guzoh1oxt0s4", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1826, - "endIndex": 1860, - "paragraph": { - "elements": [{ - "startIndex": 1826, - "endIndex": 1860, - "textRun": { - "content": "\tUsing the actual equation object\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.xupy2b5teiu", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1860, - "endIndex": 1871, - "paragraph": { - "elements": [{ - "startIndex": 1860, - "endIndex": 1870, - "equation": {} - }, - { - "startIndex": 1870, - "endIndex": 1871, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1871, - "endIndex": 1872, - "paragraph": { - "elements": [{ - "startIndex": 1871, - "endIndex": 1872, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1872, - "endIndex": 1888, - "paragraph": { - "elements": [{ - "startIndex": 1872, - "endIndex": 1888, - "textRun": { - "content": "Text equivalent\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.decz1axq5tzn", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1888, - "endIndex": 1894, - "paragraph": { - "elements": [{ - "startIndex": 1888, - "endIndex": 1892, - "textRun": { - "content": "E=mc", - "textStyle": { - "italic": true - } - } - }, - { - "startIndex": 1892, - "endIndex": 1894, - "textRun": { - "content": "2\n", - "textStyle": { - "italic": true, - "baselineOffset": "SUPERSCRIPT" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 1894, - "endIndex": 1904, - "paragraph": { - "elements": [{ - "startIndex": 1894, - "endIndex": 1904, - "textRun": { - "content": "Footnotes\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "headingId": "h.44175oezvk2", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1904, - "endIndex": 1948, - "paragraph": { - "elements": [{ - "startIndex": 1904, - "endIndex": 1905, - "footnoteReference": { - "footnoteId": "kix.6ef0di9qd512", - "footnoteNumber": "1", - "textStyle": {} - } - }, - { - "startIndex": 1905, - "endIndex": 1948, - "textRun": { - "content": " This is some sample text with a footnote.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1948, - "endIndex": 1949, - "paragraph": { - "elements": [{ - "startIndex": 1948, - "endIndex": 1949, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1949, - "endIndex": 1974, - "paragraph": { - "elements": [{ - "startIndex": 1949, - "endIndex": 1974, - "textRun": { - "content": "This is some other data.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - } - ] - }, - "footnotes": { - "kix.6ef0di9qd512": { - "footnoteId": "kix.6ef0di9qd512", - "content": [{ - "endIndex": 107, - "paragraph": { - "elements": [{ - "endIndex": 105, - "textRun": { - "content": " Footnotes should display as a footnote, and should always display at the very end of the document (page)", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - }, - { - "startIndex": 105, - "endIndex": 107, - "textRun": { - "content": "?\n", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 107, - "endIndex": 108, - "paragraph": { - "elements": [{ - "startIndex": 107, - "endIndex": 108, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 108, - "endIndex": 109, - "paragraph": { - "elements": [{ - "startIndex": 108, - "endIndex": 109, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 109, - "endIndex": 110, - "paragraph": { - "elements": [{ - "startIndex": 109, - "endIndex": 110, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 110, - "endIndex": 111, - "paragraph": { - "elements": [{ - "startIndex": 110, - "endIndex": 111, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - } - ] - } - }, - "documentStyle": { - "background": { - "color": {} - }, - "pageNumberStart": 1, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginRight": { - "magnitude": 72, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 72, - "unit": "PT" - }, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - } - }, - "namedStyles": { - "styles": [{ - "namedStyleType": "NORMAL_TEXT", - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 115, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": true, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_1", - "textStyle": { - "fontSize": { - "magnitude": 16, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.mkxk62eqq59a", - "namedStyleType": "HEADING_1", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_2", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.5nzlqsjr37x", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_3", - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_4", - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.ioi9fbjg71c1", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_5", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_6", - "textStyle": { - "italic": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "TITLE", - "textStyle": { - "fontSize": { - "magnitude": 21, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "SUBTITLE", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Courier New", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true - } - } - ] - }, - "suggestionsViewMode": "SUGGESTIONS_INLINE", - "inlineObjects": { - "kix.6vqlx5cgywvz": { - "objectId": "kix.6vqlx5cgywvz", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "contentUri": "https://lh6.googleusercontent.com/opCTglJjqHduJb0wdxNn3KLSeCIZwImqdKjDb22pg3wv3ZsmIW8rNPGrbq3kFLeaN3U-WKkvQbV-n93-7bQXJVhKdpltQ60P6GEgtjbWbFLvPPJpOeIdaf6M9xUL-_QJGwV5e0b0BNy0TAYLIA", - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "magnitude": 445.2515337423313, - "unit": "PT" - }, - "width": { - "magnitude": 445.2515337423313, - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - } - } - } - }, - "kix.yxw8u6creutz": { - "objectId": "kix.yxw8u6creutz", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "contentUri": "https://lh3.googleusercontent.com/abc", - "sourceUri": "https://lh3.google.com/u/0/d/abc", - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "magnitude": 94.00240384615385, - "unit": "PT" - }, - "width": { - "magnitude": 325.875, - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - } - } - } - }, - "kix.45vzc2wq9yr8": { - "objectId": "kix.45vzc2wq9yr8", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "contentUri": "https://lh3.googleusercontent.com/l_QXaLeI_2t1wBtL6JUOB5RkEsflE9af7vQpxU2wJ_fL6clcIzocGBuJD_Z1lTPoFlGpoC0MVRNqLL-hVzCaRSkfU-XR2V2AMoL0VDBcDD-EacZzAscT_J7qb9L3O0pFIUGU9stD6coseNurSA", - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "magnitude": 219, - "unit": "PT" - }, - "width": { - "magnitude": 468, - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "linkedContentReference": {} - } - } - }, - "kix.riy9ecw8monw": { - "objectId": "kix.riy9ecw8monw", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "contentUri": "https://lh4.googleusercontent.com/Ygzc-fl5xauu67WhE295Prbom-UQaiJpNSM8GxfWUvXHm5-lnPqv5oqPj40yJXWAdy6ld9cv5OUkfejjA6M0eqFrqfJkf1Sz5zBClqWM7IjtyMAKzH_CHtU937l8872AmOKZFEOWKk9tmoIj0A", - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "magnitude": 263, - "unit": "PT" - }, - "width": { - "magnitude": 468, - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - } - } - } - } - }, - "documentId": "1R1Cjfd5YErh118hue8Rki1z0fFPvO8rB2F5Ri8UFRJ0" -} diff --git a/test/odt_md/intro-to-the-system.md b/test/odt_md/intro-to-the-system.md index f059cd00..cf7539fb 100644 --- a/test/odt_md/intro-to-the-system.md +++ b/test/odt_md/intro-to-the-system.md @@ -1,7 +1,5 @@ The following page addresses the basic navigation and functionality of {{% system-name %}} . If you're a current user, you'll notice some things might look a little different. We've improved the user experience — making {{% system-name %}} even more intuitive and efficient. - - ## User Interface The linked text on the left side of the system features a number of tabs that link to different areas of the system. This menu can be hidden to provide more real estate, or pinned open with the pin icon. The first tab on the left side menu is the to the Quick View. @@ -18,8 +16,6 @@ To the right: * The link icon indicates whether or not the signed in user is connected to the database. This area also displays messages to notify a user if another user is working in a document that is currently open. * The question mark icon links to available help documentation — if help is available the icon is orange. - - ### Quick View Most users see this page when they sign in to the system. It is a customizable dashboard featuring information from throughout the system pertinent to the user's daily job functions. This information is organized into areas called Portlets. Portlets can be collapsed and expanded — or moved by dragging and dropping, so what's important to a specific user can be placed at the top. You can also remove Portlets entirely. @@ -30,16 +26,9 @@ Searching for a patient's chart still begins at the E-Chart tab. The overall loo E-chart navigation includes a number of toolbars, including: -* The Tab toolbar, which navigates through the chart. The active tab is a solid color — some tabs have drop down navigation indicated by a mark in the lower - -right corner. - +* The Tab toolbar, which navigates through the chart. The active tab is a solid color — some tabs have drop down navigation indicated by a mark in the lower right corner. * The Patient Info toolbar displays the patient's name and medical record number. - - * Also available in the Patient Info toolbar is the side chart icon, which displays patient historical information such as medical records, past - -encounters, notes and comments. - + * Also available in the Patient Info toolbar is the side chart icon, which displays patient historical information such as medical records, past encounters, notes and comments. * The Patient Extended Toolbar displays demographic, encounter, and other related information. * The Alerts toolbar displays available alert information — such as allergies or do not release requests. * The Encounter toolbar displays information and links to the open patient encounter. diff --git a/test/odt_md/intro-to-the-system.md.json b/test/odt_md/intro-to-the-system.md.json deleted file mode 100644 index ef455e96..00000000 --- a/test/odt_md/intro-to-the-system.md.json +++ /dev/null @@ -1,7683 +0,0 @@ -{ - "title": "Intro to the System", - "body": { - "content": [{ - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1, - "endIndex": 310, - "paragraph": { - "elements": [{ - "startIndex": 1, - "endIndex": 310, - "textRun": { - "content": "The following page addresses the basic navigation and functionality of {{% system-name %}} . If you're a current user, you'll notice some things might look a little different. We've improved the user experience — making {{% system-name %}} even more intuitive and efficient.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 310, - "endIndex": 311, - "paragraph": { - "elements": [{ - "startIndex": 310, - "endIndex": 311, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 311, - "endIndex": 326, - "paragraph": { - "elements": [{ - "startIndex": 311, - "endIndex": 326, - "textRun": { - "content": "User Interface\n", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 18, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 326, - "endIndex": 592, - "paragraph": { - "elements": [{ - "startIndex": 326, - "endIndex": 592, - "textRun": { - "content": "The linked text on the left side of the system features a number of tabs that link to different areas of the system. This menu can be hidden to provide more real estate, or pinned open with the pin icon. The first tab on the left side menu is the to the Quick View.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 592, - "endIndex": 614, - "paragraph": { - "elements": [{ - "startIndex": 592, - "endIndex": 614, - "textRun": { - "content": "Starting on the left:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 614, - "endIndex": 668, - "paragraph": { - "elements": [{ - "startIndex": 614, - "endIndex": 668, - "textRun": { - "content": "The Menu button displays or hides the left side menu.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - } - } - }, - { - "startIndex": 668, - "endIndex": 845, - "paragraph": { - "elements": [{ - "startIndex": 668, - "endIndex": 845, - "textRun": { - "content": "The Home button brings you back to this page from any page in {{% system-name %}} while the default is the Quick View page, a user can specify their home page.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - } - } - }, - { - "startIndex": 845, - "endIndex": 969, - "paragraph": { - "elements": [{ - "startIndex": 845, - "endIndex": 969, - "textRun": { - "content": "The Doc Queue indicates open designated lists for tasks, documents, and other information assigned to a user or department.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - } - } - }, - { - "startIndex": 969, - "endIndex": 983, - "paragraph": { - "elements": [{ - "startIndex": 969, - "endIndex": 983, - "textRun": { - "content": "To the right:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 983, - "endIndex": 1031, - "paragraph": { - "elements": [{ - "startIndex": 983, - "endIndex": 1031, - "textRun": { - "content": "The signed-in user and their role is displayed.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.2", - "textStyle": {} - } - } - }, - { - "startIndex": 1031, - "endIndex": 1234, - "paragraph": { - "elements": [{ - "startIndex": 1031, - "endIndex": 1234, - "textRun": { - "content": "The link icon indicates whether or not the signed in user is connected to the database. This area also displays messages to notify a user if another user is working in a document that is currently open.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.2", - "textStyle": {} - } - } - }, - { - "startIndex": 1234, - "endIndex": 1338, - "paragraph": { - "elements": [{ - "startIndex": 1234, - "endIndex": 1338, - "textRun": { - "content": "The question mark icon links to available help documentation — if help is available the icon is orange.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.2", - "textStyle": {} - } - } - }, - { - "startIndex": 1338, - "endIndex": 1339, - "paragraph": { - "elements": [{ - "startIndex": 1338, - "endIndex": 1339, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1339, - "endIndex": 1350, - "paragraph": { - "elements": [{ - "startIndex": 1339, - "endIndex": 1350, - "textRun": { - "content": "Quick View\n", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1350, - "endIndex": 1771, - "paragraph": { - "elements": [{ - "startIndex": 1350, - "endIndex": 1771, - "textRun": { - "content": "Most users see this page when they sign in to the system. It is a customizable dashboard featuring information from throughout the system pertinent to the user’s daily job functions. This information is organized into areas called Portlets. Portlets can be collapsed and expanded — or moved by dragging and dropping, so what's important to a specific user can be placed at the top. You can also remove Portlets entirely.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1771, - "endIndex": 1773, - "paragraph": { - "elements": [{ - "startIndex": 1771, - "endIndex": 1772, - "inlineObjectElement": { - "inlineObjectId": "i.0", - "textStyle": {} - } - }, - { - "startIndex": 1772, - "endIndex": 1773, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1773, - "endIndex": 1774, - "paragraph": { - "elements": [{ - "startIndex": 1773, - "endIndex": 1774, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1774, - "endIndex": 1782, - "paragraph": { - "elements": [{ - "startIndex": 1774, - "endIndex": 1782, - "textRun": { - "content": "E-Chart\n", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 1782, - "endIndex": 2099, - "paragraph": { - "elements": [{ - "startIndex": 1782, - "endIndex": 2099, - "textRun": { - "content": "Searching for a patient's chart still begins at the E-Chart tab. The overall look and feel of the patient search matches the new {{% system-name %}} styling, including the tabs. The E-Chart defaults to the Patient Summary page. The Patient Summary displays data in portlets much like the Quick View.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2099, - "endIndex": 2160, - "paragraph": { - "elements": [{ - "startIndex": 2099, - "endIndex": 2160, - "textRun": { - "content": "E-chart navigation includes a number of toolbars, including:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2160, - "endIndex": 2315, - "paragraph": { - "elements": [{ - "startIndex": 2160, - "endIndex": 2315, - "textRun": { - "content": "The Tab toolbar, which navigates through the chart. The active tab is a solid color — some tabs have drop down navigation indicated by a mark in the lower\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.3", - "textStyle": {} - } - } - }, - { - "startIndex": 2315, - "endIndex": 2329, - "paragraph": { - "elements": [{ - "startIndex": 2315, - "endIndex": 2329, - "textRun": { - "content": "right corner.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2329, - "endIndex": 2409, - "paragraph": { - "elements": [{ - "startIndex": 2329, - "endIndex": 2409, - "textRun": { - "content": "The Patient Info toolbar displays the patient’s name and medical record number.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.4", - "textStyle": {} - } - } - }, - { - "startIndex": 2409, - "endIndex": 2552, - "paragraph": { - "elements": [{ - "startIndex": 2409, - "endIndex": 2552, - "textRun": { - "content": "Also available in the Patient Info toolbar is the side chart icon, which displays patient historical information such as medical records, past\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 42, - "unit": "PT" - }, - "indentStart": { - "magnitude": 60, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.5", - "nestingLevel": 1, - "textStyle": {} - } - } - }, - { - "startIndex": 2552, - "endIndex": 2584, - "paragraph": { - "elements": [{ - "startIndex": 2552, - "endIndex": 2584, - "textRun": { - "content": "encounters, notes and comments.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2584, - "endIndex": 2677, - "paragraph": { - "elements": [{ - "startIndex": 2584, - "endIndex": 2677, - "textRun": { - "content": "The Patient Extended Toolbar displays demographic, encounter, and other related information.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.6", - "textStyle": {} - } - } - }, - { - "startIndex": 2677, - "endIndex": 2781, - "paragraph": { - "elements": [{ - "startIndex": 2677, - "endIndex": 2781, - "textRun": { - "content": "The Alerts toolbar displays available alert information — such as allergies or do not release requests.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.6", - "textStyle": {} - } - } - }, - { - "startIndex": 2781, - "endIndex": 2865, - "paragraph": { - "elements": [{ - "startIndex": 2781, - "endIndex": 2865, - "textRun": { - "content": "The Encounter toolbar displays information and links to the open patient encounter.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.6", - "textStyle": {} - } - } - }, - { - "startIndex": 2865, - "endIndex": 2867, - "paragraph": { - "elements": [{ - "startIndex": 2865, - "endIndex": 2866, - "inlineObjectElement": { - "inlineObjectId": "i.1", - "textStyle": {} - } - }, - { - "startIndex": 2866, - "endIndex": 2867, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2867, - "endIndex": 2868, - "paragraph": { - "elements": [{ - "startIndex": 2867, - "endIndex": 2868, - "textRun": { - "content": "\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2868, - "endIndex": 2886, - "paragraph": { - "elements": [{ - "startIndex": 2868, - "endIndex": 2886, - "textRun": { - "content": "Dynamic Encounter\n", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 2886, - "endIndex": 3261, - "paragraph": { - "elements": [{ - "startIndex": 2886, - "endIndex": 3261, - "textRun": { - "content": "As its name suggests, the dynamic encounter can be customized on the fly. The Show hidden section list button displays any available section options that do not already appear on the page. The dynamic encounter features the same patient-related toolbars as the E-Chart. The Show exam sections pop-up window allows the user click on a section name to add it to the encounter.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 3261, - "endIndex": 3464, - "paragraph": { - "elements": [{ - "startIndex": 3261, - "endIndex": 3464, - "textRun": { - "content": "Note: Multiple users can open a dynamic encounter at the same time. A warning message displays in red at the top of the dynamic encounter to notify the logged in user if another user owns the encounter.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 3464, - "endIndex": 3606, - "paragraph": { - "elements": [{ - "startIndex": 3464, - "endIndex": 3606, - "textRun": { - "content": "The dynamic encounter is divided into a number of sections. Tabs that link to each section appear in the tab toolbar. These sections include:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 3606, - "endIndex": 3617, - "paragraph": { - "elements": [{ - "startIndex": 3606, - "endIndex": 3617, - "textRun": { - "content": "Subjective\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.7", - "textStyle": {} - } - } - }, - { - "startIndex": 3617, - "endIndex": 3627, - "paragraph": { - "elements": [{ - "startIndex": 3617, - "endIndex": 3627, - "textRun": { - "content": "Objective\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.7", - "textStyle": {} - } - } - }, - { - "startIndex": 3627, - "endIndex": 3638, - "paragraph": { - "elements": [{ - "startIndex": 3627, - "endIndex": 3638, - "textRun": { - "content": "Assessment\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.7", - "textStyle": {} - } - } - }, - { - "startIndex": 3638, - "endIndex": 3643, - "paragraph": { - "elements": [{ - "startIndex": 3638, - "endIndex": 3643, - "textRun": { - "content": "Plan\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.7", - "textStyle": {} - } - } - }, - { - "startIndex": 3643, - "endIndex": 3651, - "paragraph": { - "elements": [{ - "startIndex": 3643, - "endIndex": 3651, - "textRun": { - "content": "Summary\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.7", - "textStyle": {} - } - } - }, - { - "startIndex": 3651, - "endIndex": 3776, - "paragraph": { - "elements": [{ - "startIndex": 3651, - "endIndex": 3776, - "textRun": { - "content": "Click the section heading to open a section for editing. When the section is open for editing, the following buttons appear:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 3776, - "endIndex": 3855, - "paragraph": { - "elements": [{ - "startIndex": 3776, - "endIndex": 3855, - "textRun": { - "content": "Next - Saves data entered into the current section and opens the next section.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.8", - "textStyle": {} - } - } - }, - { - "startIndex": 3855, - "endIndex": 3915, - "paragraph": { - "elements": [{ - "startIndex": 3855, - "endIndex": 3915, - "textRun": { - "content": "Cancel - Closes the section. Any data entered is not saved.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.8", - "textStyle": {} - } - } - }, - { - "startIndex": 3915, - "endIndex": 3970, - "paragraph": { - "elements": [{ - "startIndex": 3915, - "endIndex": 3970, - "textRun": { - "content": "Hide section - Removes the section from the encounter.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.8", - "textStyle": {} - } - } - }, - { - "startIndex": 3970, - "endIndex": 4186, - "paragraph": { - "elements": [{ - "startIndex": 3970, - "endIndex": 4186, - "textRun": { - "content": "When a section is open for editing, a Next button appears at the bottom. Click the next button to open the next available section for editing. Some sections have display options. The following options are available:\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 4186, - "endIndex": 4235, - "paragraph": { - "elements": [{ - "startIndex": 4186, - "endIndex": 4235, - "textRun": { - "content": "Summary view - Displays data in a bulleted list.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.9", - "textStyle": {} - } - } - }, - { - "startIndex": 4235, - "endIndex": 4275, - "paragraph": { - "elements": [{ - "startIndex": 4235, - "endIndex": 4275, - "textRun": { - "content": "Detail view - Displays data in a table.\n", - "textStyle": {} - } - }], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.9", - "textStyle": {} - } - } - }, - { - "startIndex": 4275, - "endIndex": 4277, - "paragraph": { - "elements": [{ - "startIndex": 4275, - "endIndex": 4276, - "inlineObjectElement": { - "inlineObjectId": "i.2", - "textStyle": {} - } - }, - { - "startIndex": 4276, - "endIndex": 4277, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceBelow": { - "magnitude": 11.25, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 4277, - "endIndex": 4291, - "paragraph": { - "elements": [{ - "startIndex": 4277, - "endIndex": 4291, - "textRun": { - "content": "Related Pages\n", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 18, - "unit": "PT" - } - } - } - }], - "paragraphStyle": { - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - } - }, - { - "startIndex": 4291, - "endIndex": 4306, - "paragraph": { - "elements": [{ - "startIndex": 4291, - "endIndex": 4305, - "textRun": { - "content": "System Anatomy", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://docs.google.com/document/d/18GsYTHwhKmZ1xxRbrRVf7RPrHHXjGrwWHTgd0s5s3_A" - } - } - } - }, - { - "startIndex": 4305, - "endIndex": 4306, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.93333334 - } - } - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "magnitude": 12, - "unit": "PT" - }, - "indentStart": { - "magnitude": 30, - "unit": "PT" - }, - "shading": { - "backgroundColor": {} - } - }, - "bullet": { - "listId": "kix.list.a", - "textStyle": {} - } - } - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "pageNumberStart": 1, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginRight": { - "magnitude": 72, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 72, - "unit": "PT" - }, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - } - }, - "namedStyles": { - "styles": [{ - "namedStyleType": "NORMAL_TEXT", - "textStyle": {}, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": false, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_1", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 24, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_2", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 18, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 11.25, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 11.25, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_3", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_4", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 12, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 12.75, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12.75, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_5", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 9, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 12.75, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12.75, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_6", - "textStyle": { - "bold": true, - "italic": false, - "fontSize": { - "magnitude": 8, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 18, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 18, - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "TITLE", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 36, - "unit": "PT" - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 6, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "SUBTITLE", - "textStyle": { - "italic": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 24, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Georgia", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 18, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - } - ] - }, - "lists": { - "kix.list.4": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.1": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - }, - "kix.list.a": { - "listProperties": { - "nestingLevels": [{ - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "startNumber": 1 - } - ] - } - } - }, - "revisionId": "AOV_f48hgkCF2XGw87un6GhV_AHqPqyBJIvOM_iw6BRHvEAVvBSLFEcFqTwGgnOaF6oisbu-reHu9pHicUFPnw", - "suggestionsViewMode": "SUGGESTIONS_INLINE", - "inlineObjects": { - "i.0": { - "objectId": "i.0", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginRight": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 1.5, - "unit": "PT" - } - } - } - }, - "i.2": { - "objectId": "i.2", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginRight": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 1.5, - "unit": "PT" - } - } - } - }, - "i.1": { - "objectId": "i.1", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginRight": { - "magnitude": 1.5, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 1.5, - "unit": "PT" - } - } - } - } - }, - "documentId": "1f159KylSfviuqHk2je4ROsaXjgK7iuuflvI3ihruGuQ" -} diff --git a/test/odt_md/intro-to-the-system.odt b/test/odt_md/intro-to-the-system.odt index 1b207a08b0a764f054312bb2a8a4e70abcc7e985..7b838a0390a283501cbfd2ca442d3f65d2c2dea4 100644 GIT binary patch literal 54411 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 P3c!O3OmHANgj;_A5+Jk0 literal 27107 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 NlEJxqI)l5T{{gl^BR>ED diff --git a/test/odt_md/issue-434-2.md b/test/odt_md/issue-434-2.md index 0814979f..726eb744 100644 --- a/test/odt_md/issue-434-2.md +++ b/test/odt_md/issue-434-2.md @@ -2,11 +2,9 @@ **This** is line two - * This is a bulleted test * Second line - hi  diff --git a/test/odt_md/issue-435-436.md b/test/odt_md/issue-435-436.md index 9cd8dcd7..2488b631 100644 --- a/test/odt_md/issue-435-436.md +++ b/test/odt_md/issue-435-436.md @@ -10,6 +10,6 @@ Similar to adding a new question, users must have the proper permission to modif ![](10000001000001AE0000013A3DD8D81D5AC3DDAE.png) 5. From here, you may update the Description, the process order, the question code, or the response. - 1. You may not change this from one "type" of response (like Text) to another type (like Yes/No). For this type of change, create a new question. - 2. Be sure to click the Submit button at the bottom to save your changes. - ![](10000001000003C90000010BE8F0122D00285531.png) + 1. You may not change this from one "type" of response (like Text) to another type (like Yes/No). For this type of change, create a new question. + 2. Be sure to click the Submit button at the bottom to save your changes. + ![](10000001000003C90000010BE8F0122D00285531.png) diff --git a/test/odt_md/issue-443.md b/test/odt_md/issue-443.md index 53af8499..aa4ae84d 100644 --- a/test/odt_md/issue-443.md +++ b/test/odt_md/issue-443.md @@ -9,16 +9,16 @@ Review the standard  {{% system-name %}} workflows and gather information neede ### Agenda - 2 Hours * Demonstrate Faxing - * System Configuration - * Load Fax Coversheet(s) - * Inbound Fax Queue - * Outbound Faxing - * Configure Fax Queue Permissions, User Preferences, and Security Settings + * System Configuration + * Load Fax Coversheet(s) + * Inbound Fax Queue + * Outbound Faxing + * Configure Fax Queue Permissions, User Preferences, and Security Settings * Demonstrate Reports - * System Configuration - * Configure Reports - * Pivot Report Access - * Report Restrictions + * System Configuration + * Configure Reports + * Pivot Report Access + * Report Restrictions ## Afterward diff --git a/test/odt_md/line-breaks.md b/test/odt_md/line-breaks.md index 69b41c44..b1f54b2e 100644 --- a/test/odt_md/line-breaks.md +++ b/test/odt_md/line-breaks.md @@ -1,4 +1,5 @@ -Some text with line break +Some text with line break + Another line lb Some text without line break Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. @@ -7,24 +8,19 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod lb tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint lb occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - 1. First item -2. Second item with - Line break +2. Second item with + Line break 3. Third line - - - -
    1cell2cell lb +2cell lb
    Another line
    3rd cell 4th cell
    - diff --git a/test/odt_md/list-indent.md b/test/odt_md/list-indent.md index 3d40733c..313ccba6 100644 --- a/test/odt_md/list-indent.md +++ b/test/odt_md/list-indent.md @@ -3,39 +3,38 @@ ![](10000201000004BE0000011D30E30AE192655040.png) - 3. When adding action items to panels, the [Representative Event panel action](#tyjcwt) is usually added to the panel first. Fill out all of the necessary fields according to the information acquired in the Health Surveillance matrix, and click Submit to save the panel action to the panel. - 1. Action Name: Required field. The Action Name is usually the name of a test/procedure that is the component/action of the panel. The name will be displayed listings and dialogues throughout the system. - 2. Lead Time: The Lead Time translates to the number of days prior to the Trigger Date the panel action becomes visible and is created within the system. This defines how many days before the Trigger Date that the panel/orders will populate on the Due List. Keep Lead Times consistent when setting multiple action items in a panel; otherwise, each component of the panel will have different Due Dates if there are different Lead Times on each. Emails can be configured to send email notifications, as needed, with a list of associated charts/employees that will be due. The recipient has the time between receiving the email and the panel action Trigger Date to notify Health Services of any issues or mistakes with the list. Emails to the member/chart will not be sent until the actual Trigger Date. (Email reminders are separately configured on a per client basis. Email notification may not apply to all clients). + 1. Action Name: Required field. The Action Name is usually the name of a test/procedure that is the component/action of the panel. The name will be displayed listings and dialogues throughout the system. + 2. Lead Time: The Lead Time translates to the number of days prior to the Trigger Date the panel action becomes visible and is created within the system. This defines how many days before the Trigger Date that the panel/orders will populate on the Due List. Keep Lead Times consistent when setting multiple action items in a panel; otherwise, each component of the panel will have different Due Dates if there are different Lead Times on each. Emails can be configured to send email notifications, as needed, with a list of associated charts/employees that will be due. The recipient has the time between receiving the email and the panel action Trigger Date to notify Health Services of any issues or mistakes with the list. Emails to the member/chart will not be sent until the actual Trigger Date. (Email reminders are separately configured on a per client basis. Email notification may not apply to all clients). {{% tip %}} - If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. - ![](1000020100000311000001824A182983854F26CB.png) +If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. +![](1000020100000311000001824A182983854F26CB.png) {{% /tip %}} - 3. Required for Certification: Select this to indicate the panel action is required for members of the panel. Leave unchecked if the panel action is voluntary. If checked, a panel member failing or becoming overdue for the action will become de-certified from the panel. - 4. Indication Rule: Users can select any action rule found in the Action Rules editor, using the drop-down. For more information on the Action Rules, see the [Health Surveillance Action Rules](gdoc:10wTqIF8gtUDBbJmbk_LjlUeNmtU_vvbVFoVWTZnuMqc) documentation. The action rule must evaluate to True in order for this panel action to trigger for a panel member. [Action Rules](#1fob9te) are usually configured by an MIE Developer after an MIE Implementer has collected all of the necessary details for the configuration. - 1. Indication Rules can be used to only trigger the panel action for a member of the panel, if they are part of a specific department, for example. Or another more complex example would be a panel action configured to trigger a Hep3rd injection, only if the member of the panel had the second Hepatitis injection given within the last 8 weeks. - 5. Contraindication Rule: Users can select any action rule found in the Action Rules editor, using the drop-down. The action rule must evaluate to False in order for this panel action to trigger for a panel member. For more information on the Action Rules, see the [Health Surveillance Action Rules](gdoc:10wTqIF8gtUDBbJmbk_LjlUeNmtU_vvbVFoVWTZnuMqc) documentation. - 6. Trigger Type: Entry, Routine, Exit. Select the type of trigger, to define at what point in the panel member's current role/job status, the regulating agency or company requires the panel action to be completed. Entry will trigger when a panel member is first put in the panel. The Panel Evaluator scheduled job will run every day, triggering panels as appropriate, based on the the configured panel actions and the trigger type selected. - 7. Trigger Date: On what date should the panel action trigger? Use the drop-down to select one of the following Trigger Dates: - 1. Date of Birth: Triggers the panel action on the panel member's date of birth, on a schedule determined by the starting age and frequency. Assumes the panel member's DOB has been captured in the chart demographics. - 2. Other Action (Triggered): The Other Action (Triggered) trigger date allows users to trigger a panel action at the same time as another action item, indicated in this panel action. For example, an action to trigger an Audiogram may be for Entry, Routine, or Exit actions; if checked, other actions may use this panel action as a trigger. This option must be selected for the action to display in the Related Action list. The Related Action list displays when then Trigger Date is set to Other Action (Triggered) or Prior Action (Completed). Additionally, action items can be configured to trigger with the Representative Events, as needed, if that programming is utilized. This allows all action items to trigger together for a panel. Triggers with all the same date are usually tied to representative event. - - ![](100002010000033B00000036339CF669B6C2B512.png) - - 3. Point in Time: The Point in Time trigger date allows users to trigger an action item on the same day and month, each year (must be MM/DD format). - 4. Panel Expiration: Triggers on the expiration date specified in the panel status. Most panels will be configured with a representative event as the - 9. Trigger Others: If checked, other panel actions may use this action item as a trigger. This must be set for the action to display in the Related Action list. - 1. Auto-Waive (this action item) if none (no other actions) Triggered: In instances where a Representative Event may be added after the completion of all other panel actions - 10. Frequency: Day, Weeks, Months, Years. Use the drop-down to define the time period of how often the panel action should be triggered. Actions with zero (0) frequency values will trigger whenever the parent action is set to trigger. - 11. Valid For: Day, Weeks, Months, Years. Use the drop-down to define the acceptable time period for which the panel action may be performed, prior to the action Due Date, and still count as acceptable by the regulating body or company. - 1. Current Panel Only: This is a checkbox that is associated with the Valid For field. If checked, this panel action will be triggered, regardless of whether the same encounter or procedure was completed for a different panel. For example, if a panel member is included in both the Asbestos panel and Benzene panel, and both require a Chest Xray, then {{% system-name %}} would (by default) only populate Chest Xray once on the Due List. With the Current Panel Only option selected, in this example, the Chest Xray will display twice, once for each panel. - 12. Grace Period: Day, Weeks, Months, Years. Use the drop-down to define how much time the panel member is allotted to complete the panel action, from the time it is visible till the time it is considered overdue. Periodic email notifications can be set up with scheduled jobs, if preferred. The Grace Period is before the Due Date, meaning the Grace Period is the amount of time before the Due Date that the invitations, emails, and questionnaire become available. The panel member gets notified at the point of the Grace Period plus Lead Time. + 3. Required for Certification: Select this to indicate the panel action is required for members of the panel. Leave unchecked if the panel action is voluntary. If checked, a panel member failing or becoming overdue for the action will become de-certified from the panel. + 4. Indication Rule: Users can select any action rule found in the Action Rules editor, using the drop-down. For more information on the Action Rules, see the [Health Surveillance Action Rules](gdoc:10wTqIF8gtUDBbJmbk_LjlUeNmtU_vvbVFoVWTZnuMqc) documentation. The action rule must evaluate to True in order for this panel action to trigger for a panel member. [Action Rules](#1fob9te) are usually configured by an MIE Developer after an MIE Implementer has collected all of the necessary details for the configuration. + 1. Indication Rules can be used to only trigger the panel action for a member of the panel, if they are part of a specific department, for example. Or another more complex example would be a panel action configured to trigger a Hep3rd injection, only if the member of the panel had the second Hepatitis injection given within the last 8 weeks. + 5. Contraindication Rule: Users can select any action rule found in the Action Rules editor, using the drop-down. The action rule must evaluate to False in order for this panel action to trigger for a panel member. For more information on the Action Rules, see the [Health Surveillance Action Rules](gdoc:10wTqIF8gtUDBbJmbk_LjlUeNmtU_vvbVFoVWTZnuMqc) documentation. + 6. Trigger Type: Entry, Routine, Exit. Select the type of trigger, to define at what point in the panel member's current role/job status, the regulating agency or company requires the panel action to be completed. Entry will trigger when a panel member is first put in the panel. The Panel Evaluator scheduled job will run every day, triggering panels as appropriate, based on the the configured panel actions and the trigger type selected. + 7. Trigger Date: On what date should the panel action trigger? Use the drop-down to select one of the following Trigger Dates: + 1. Date of Birth: Triggers the panel action on the panel member's date of birth, on a schedule determined by the starting age and frequency. Assumes the panel member's DOB has been captured in the chart demographics. + 2. Other Action (Triggered): The Other Action (Triggered) trigger date allows users to trigger a panel action at the same time as another action item, indicated in this panel action. For example, an action to trigger an Audiogram may be for Entry, Routine, or Exit actions; if checked, other actions may use this panel action as a trigger. This option must be selected for the action to display in the Related Action list. The Related Action list displays when then Trigger Date is set to Other Action (Triggered) or Prior Action (Completed). Additionally, action items can be configured to trigger with the Representative Events, as needed, if that programming is utilized. This allows all action items to trigger together for a panel. Triggers with all the same date are usually tied to representative event. + +![](100002010000033B00000036339CF669B6C2B512.png) + + 3. Point in Time: The Point in Time trigger date allows users to trigger an action item on the same day and month, each year (must be MM/DD format). + 4. Panel Expiration: Triggers on the expiration date specified in the panel status. Most panels will be configured with a representative event as the + 8. Trigger Others: If checked, other panel actions may use this action item as a trigger. This must be set for the action to display in the Related Action list. + 1. Auto-Waive (this action item) if none (no other actions) Triggered: In instances where a Representative Event may be added after the completion of all other panel actions + 9. Frequency: Day, Weeks, Months, Years. Use the drop-down to define the time period of how often the panel action should be triggered. Actions with zero (0) frequency values will trigger whenever the parent action is set to trigger. + 10. Valid For: Day, Weeks, Months, Years. Use the drop-down to define the acceptable time period for which the panel action may be performed, prior to the action Due Date, and still count as acceptable by the regulating body or company. + 1. Current Panel Only: This is a checkbox that is associated with the Valid For field. If checked, this panel action will be triggered, regardless of whether the same encounter or procedure was completed for a different panel. For example, if a panel member is included in both the Asbestos panel and Benzene panel, and both require a Chest Xray, then {{% system-name %}} would (by default) only populate Chest Xray once on the Due List. With the Current Panel Only option selected, in this example, the Chest Xray will display twice, once for each panel. + 11. Grace Period: Day, Weeks, Months, Years. Use the drop-down to define how much time the panel member is allotted to complete the panel action, from the time it is visible till the time it is considered overdue. Periodic email notifications can be set up with scheduled jobs, if preferred. The Grace Period is before the Due Date, meaning the Grace Period is the amount of time before the Due Date that the invitations, emails, and questionnaire become available. The panel member gets notified at the point of the Grace Period plus Lead Time. {{% note %}} - Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. +Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. {{% /note %}} - 13. Instructions: Free text instructions for a provider to perform this action item, if necessary. Could be instructions or pass/fail criteria, etc. + 12. Instructions: Free text instructions for a provider to perform this action item, if necessary. Could be instructions or pass/fail criteria, etc. diff --git a/test/odt_md/list-test.md b/test/odt_md/list-test.md index b8636295..4f26f8f2 100644 --- a/test/odt_md/list-test.md +++ b/test/odt_md/list-test.md @@ -8,37 +8,37 @@ Action items that are configured with a Trigger Date of **Prior Action (Complet 3. level1_3 4. level1_4 - 1. level2_1 - 2. level2_2 - 3. level2_3 - 4. level2_4 + 1. level2_1 + 2. level2_2 + 3. level2_3 + 4. level2_4 {{% tip %}} - If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. +If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. {{% /tip %}} - 5. level2_5 - 6. level2_6 - 1. level3_1 - 7. level2_7 - 8. level2_8 - 9. level2_9 - 1. llevel3_1 - 2. llevel3_2 - 3. llevel3_3 - 4. llevel3_4 - 10. level2_10 - 1. lllevel3_1 - 11. level2_11 - 12. level2_12 - 1. llllevel3_1 - 13. level2_13 - 14. level2_14 + 5. level2_5 + 6. level2_6 + 1. level3_1 + 7. level2_7 + 8. level2_8 + 9. level2_9 + 1. llevel3_1 + 2. llevel3_2 + 3. llevel3_3 + 4. llevel3_4 + 10. level2_10 + 1. lllevel3_1 + 11. level2_11 + 12. level2_12 + 1. llllevel3_1 + 13. level2_13 + 14. level2_14 {{% note %}} - Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. +Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. {{% /note %}} - 15. level2_15 - 16. level2_16 - 17. level2_17 + 15. level2_15 + 16. level2_16 + 17. level2_17 diff --git a/test/odt_md/nested-ordered-list.md b/test/odt_md/nested-ordered-list.md index 2e87424c..f7fb3512 100644 --- a/test/odt_md/nested-ordered-list.md +++ b/test/odt_md/nested-ordered-list.md @@ -11,4 +11,3 @@ * Another Test Indent 6. Check if there are any conflicts (same desireLocalPath) 7. Check if any conflicts can be removed - diff --git a/test/odt_md/project-overview.md b/test/odt_md/project-overview.md index 9895be89..6b5f4155 100644 --- a/test/odt_md/project-overview.md +++ b/test/odt_md/project-overview.md @@ -12,11 +12,8 @@ * [Images](#images) * [FAQ](#faq) - # Wiki G Drive Project Overview - - * [Wiki G Drive Project Overview](#wiki-g-drive-project-overview) * [Overview](#overview) * [Requirements](#requirements) @@ -38,86 +35,63 @@ WikiGDrive is a node app that uses the [Google Drive API](https://developers.goo With a "Shared Drive" as the key, WikiGDrive: * Reads all the files from a Google "Shared Drive" - * Builds a map of the driveId (URL) to the pathname in the "Shared Drive" - * For each Google Document: - * Converts to a Markdown file with the path (instead of the driveId for the file) - * Changes driveId to the path (eg: 12lvdxKgGsD.../edit would be changed to /filename - * Support diagrams as SVG (and map the URLs in the diagram) WikiGDrive scans for changes in the drive and then refresh the local converted files. - ![](10000201000002BA000001464F317568B8F12696.png) The WikiGDrive refreshes the "Local Filesystem" with changes from the Google Shared Drive overwriting or deleting any content to bring it into sync. The Local Filesystem is not preserved (since we will be committing the markdown in github anyway). +WikiGDrive Add-On + +* Validates page +* Shows hyperlinks (what links here) + +WikiGDrive GitHub + +* Allows for updates in markdown to update wikigdrive for simple changes. If complex at least a warning is added to the document that it's been modified and should not be updated. +* [](https://github.com/koppen/redmine_github_hook) +* [](https://github.com/moneypark/redmine_github_pull_requests_tool) + + + ## Requirements The app must: 1. be able to be run once or run as a daemon (looking for changes in near real-time) - 2. Take changes from gdrive and propagate them down to the local file system (likely a git repo) - 3. Detect file [moves and renames](#renames-and-redirecting) from prior runs and leave redirects in the local file system to the new file or directory. - 4. Convert google docs to markdown while preserving as much of the meaning of the document. (Headings, images, drawings, tables, etc). - 1. Each generated file should have parsable comments embedded in the original source google doc is known. - 2. Embedded images (not originally stored on the shared folder will have to be extracted to the filesystem with a hashing system to prevent duplicate copies files in cases where images are pasted into multiple documents. - 5. Convert google drawings to svg and fix up urls as well. - 6. Download images and place them in the proper folder. Embed metadata in the image pointing to the source on the google drive. It could be a .md file with the same name as the image. - 7. Translate hyperlinks to the filesystem relative paths if they exist in the shared drive (both within Docs and Drawings). Must support both document urls and heading URLs. - 8. Construct a [table of contents and an index](#table-of-contents-and-index) from all of the documents in the shared drive. - 1. It should be parsable so Javascript on the client could search and build navigation - 2. There should be generated markdown file ([toc.md](#table-of-contents) and [index.md](#_25nwvh7c83vs)) - - - - Later phase: * Confluence -> markdown export - * Scientific notation for headers (as an option) - * Google sheets to CSV with MIE's datavis - * Markdown -> Google Docs converter - - - - ## Instructions (proposed) - - npm install wikigdrive - - node_modules/bin/wikigdrive -h - - wikigdrive [shared drive url] - - Options: —config (.wikigdrive) @@ -130,81 +104,47 @@ Options: —watch (keep scanning for changes, ie: daemon) - - wikigdrive keeps a local JSON config file in the dest directory with state from prior runs. The config contains a map of URL driveIds to the local filenames along with metadata about each file. - - ## Renames and Redirecting When a Document is renamed or moved in the shared drive the driveId says the same, but its place in the filesystem changes. For example a document named "Carbon" would be created as Carbon.md. Sometime later its renamed to "Carbon Fiber" then a new file "Carbon Fiber.md" would be made with the content and the old "Carbon.md" is changed to: - - ``` Renamed to [Carbon Fiber](Carbon-Fiber.md) ``` - By leaving the old markdown files no one gets lost or gets broken links if the names change. - - If a document is moved from one folder to another or a directory is renamed, all files in the path will get renamed too and the original paths will get a redirect file in their old location. For example - - * Folder - * Example-1.md - * Example-2.md - - If Folder is renamed to Container, the new layout would be: * Container - * Example-1.md - * Example-2.md - * Folder - * Example-1.md -> /Container/Example-1.md - * Example-2.md -> /Container/Example-2.md - - Then sometime later, "Example 1" is renamed to "Sample 1" the folder layout should be: * Container - * Sample-1.md - * Example-1.md -> Sample-1.md - * Example-2.md - * Folder - * Example-1.md -> /Container/Sample-1.md - * Example-2.md -> /Container/Example-2.md - - - - ## Collisions with Filenames Google Drive allows filenames with the same name to be created on shared drives. When transforming them into the local filesystem, each file will be renamed to a new file and a disambiguation page will be placed in their place. Eg: - - Google Drive Folder Carbon Carbon-1.md @@ -213,20 +153,14 @@ Google Drive Folder Carbon.md - - The contents of Carbon.md would show each of the conflicting references: - - There were two documents with the same name in the same folder. * [Carbon](Carbon-1.md) * [Carbon](Carbon-2.md) - - ## Table of Contents and Index In the root of the local filesystem two files will be created: the toc.md and index.md @@ -239,18 +173,17 @@ The table of contents is a layout of the documents and their position in the dri The index is a listing of all of the defined terms and their references in the documents. The processing may be passed to another tool to construct the index. Examples: [kramdown](https://meta.stackexchange.com/questions/72395/is-it-possible-to-have-definition-lists-in-markdown), [Asciidoctor](https://asciidoctor.org/docs/user-manual/) - ## Markdown Cleanup * Bold headings: ([issue](https://github.com/mieweb/wikiGDrive/issues/17)) Remove the ** bold markdown from all headings. -![](10000201000001A5000000492C856905A808045C.png) + ![](10000201000001A5000000492C856905A808045C.png) * End of line bold text: ([issue](https://github.com/mieweb/wikiGDrive/issues/15)) The closing ** for bold text at the end of a line is being placed on a newline and not being parsed. -![](10000201000005480000004BB83F3F8B5F0C77BD.png) + ![](10000201000005480000004BB83F3F8B5F0C77BD.png) * Italics/bold in an unordered list: ([issue](https://github.com/mieweb/wikiGDrive/issues/16)) Italics are not being rendered if in a list item. We may need to find these and replace the */** with em/strong tags. Example is rendered in browser next to [Google Doc](gdoc:108WScoxxGKKKOsGWF7UNZ4rLRanGXu6BPdJ-axjVn5s). -![](1000020100000243000000F28AB7617254FDBB3A.png) - + ![](1000020100000243000000F28AB7617254FDBB3A.png) ## Images + Two kinds of images exist within Google Docs: 1) Embedded images stored within the document and 2) images that are referenced to another "Drawing" on the google drive. WikiGDrive processes images by placing them in a folder named with a similar name to the page. (eg: index.md would result in a folder index.images with each embedded image in that folder). If you make a drawing somewhere in the google drive folder and link it in the google document (WITH A HYPERLINK b/c Google does not expose the internal link via the api) then WikiGDrive will process the drawing as a SVG and place a proper reference to the SVG in the markdown. @@ -258,32 +191,17 @@ If you make a drawing somewhere in the google drive folder and link it in the go ## FAQ * What is the purpose of this tool? - * To enable collaborative editing of documentation and the ability to publish that documentation as well as linking it to revision control system branches (like in git) - * Why use google at all. Why not use markdown and GitHub? - * No collaboration in real-time. Also, markdown requires skill when managing screenshots and diagrams that are not easily accomplished in markdown. - * Why not just use google docs? - * Would love it if it were possible, but the drive does not offer the ability to publish pages in a clean way. The URLs are not SEO friendly. Would love it if there was a driveId map where every document could be given a friendly name (aka its title on the drive). Then (like Wikipedia has [disambiguation](https://en.wikipedia.org/wiki/Wikipedia:Disambiguation) pages), a reader could be redirected to the proper content. Google doesn't, so this project is an attempt to fill that gap. - * Also, Google does not have a good blame system for contributions to a document. Hopefully, this is fixed someday but in the meantime, GitHub on markdown can *help* fill the void. - * Why markdown? - * All ears for a different preferred format. It's easy to read when editing directly and when doing a diff for changes it's clean - * What about mismatches in Docs vs Markdown - * There are features of Google Docs that are not going to be supported. Like coloring text, page breaks, headers, comments, etc. These features are not core to our goals for clean [WYSIYYM](https://en.wikipedia.org/wiki/WYSIWYM). - * Keeping a WYSIWYM style insures a good mobile experience to view and edit. - * Why not make a website front end to a Google shared drive? - * Our goals are to be able to take versions of the content and commit them along with a version of the code at a point in time. By just making a website, it would allow for real time viewing of the content but no way to go to a specific version of the documentation at a given time. - * A website front end is a goal for real-time testing of the viewing experience but initially, we want to make markdown that can be committed. - diff --git a/test/odt_md/project-overview.md.json b/test/odt_md/project-overview.md.json deleted file mode 100644 index 0b58408f..00000000 --- a/test/odt_md/project-overview.md.json +++ /dev/null @@ -1,7767 +0,0 @@ -{ - "title": "Wiki G Drive Project Overview", - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1, - "endIndex": 31, - "paragraph": { - "elements": [ - { - "startIndex": 1, - "endIndex": 31, - "textRun": { - "content": "Wiki G Drive Project Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.rv5b8ogzvg6h", - "namedStyleType": "HEADING_1", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 31, - "endIndex": 32, - "paragraph": { - "elements": [ - { - "startIndex": 31, - "endIndex": 32, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 32, - "endIndex": 231, - "tableOfContents": { - "content": [ - { - "startIndex": 33, - "endIndex": 63, - "paragraph": { - "elements": [ - { - "startIndex": 33, - "endIndex": 62, - "textRun": { - "content": "Wiki G Drive Project Overview", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.rv5b8ogzvg6h" - } - } - } - }, - { - "startIndex": 62, - "endIndex": 63, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 4, - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - } - } - } - }, - { - "startIndex": 63, - "endIndex": 72, - "paragraph": { - "elements": [ - { - "startIndex": 63, - "endIndex": 71, - "textRun": { - "content": "Overview", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.d92q25gd8wdx" - } - } - } - }, - { - "startIndex": 71, - "endIndex": 72, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 72, - "endIndex": 78, - "paragraph": { - "elements": [ - { - "startIndex": 72, - "endIndex": 77, - "textRun": { - "content": "Links", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.xqs1pat65cgx" - } - } - } - }, - { - "startIndex": 77, - "endIndex": 78, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 78, - "endIndex": 87, - "paragraph": { - "elements": [ - { - "startIndex": 78, - "endIndex": 86, - "textRun": { - "content": "Contacts", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.17le3t7nckjw" - } - } - } - }, - { - "startIndex": 86, - "endIndex": 87, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 87, - "endIndex": 100, - "paragraph": { - "elements": [ - { - "startIndex": 87, - "endIndex": 99, - "textRun": { - "content": "Requirements", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.vttky12ic725" - } - } - } - }, - { - "startIndex": 99, - "endIndex": 100, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 100, - "endIndex": 124, - "paragraph": { - "elements": [ - { - "startIndex": 100, - "endIndex": 123, - "textRun": { - "content": "Instructions (proposed)", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.xcz3eudi65fe" - } - } - } - }, - { - "startIndex": 123, - "endIndex": 124, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 124, - "endIndex": 148, - "paragraph": { - "elements": [ - { - "startIndex": 124, - "endIndex": 147, - "textRun": { - "content": "Renames and Redirecting", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.mqk2e0362t9z" - } - } - } - }, - { - "startIndex": 147, - "endIndex": 148, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 148, - "endIndex": 174, - "paragraph": { - "elements": [ - { - "startIndex": 148, - "endIndex": 173, - "textRun": { - "content": "Collisions with Filenames", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.qyy6tn72wtk7" - } - } - } - }, - { - "startIndex": 173, - "endIndex": 174, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 174, - "endIndex": 202, - "paragraph": { - "elements": [ - { - "startIndex": 174, - "endIndex": 201, - "textRun": { - "content": "Table of Contents and Index", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.iqx1hufojtom" - } - } - } - }, - { - "startIndex": 201, - "endIndex": 202, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - }, - { - "startIndex": 202, - "endIndex": 220, - "paragraph": { - "elements": [ - { - "startIndex": 202, - "endIndex": 219, - "textRun": { - "content": "Table of Contents", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.h3vpqh726qwp" - } - } - } - }, - { - "startIndex": 219, - "endIndex": 220, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 220, - "endIndex": 226, - "paragraph": { - "elements": [ - { - "startIndex": 220, - "endIndex": 225, - "textRun": { - "content": "Index", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.25nwvh7c83vs" - } - } - } - }, - { - "startIndex": 225, - "endIndex": 226, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 226, - "endIndex": 230, - "paragraph": { - "elements": [ - { - "startIndex": 226, - "endIndex": 229, - "textRun": { - "content": "FAQ", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE", - "link": { - "headingId": "h.dqy5042w3u8q" - } - } - } - }, - { - "startIndex": 229, - "endIndex": 230, - "textRun": { - "content": "\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "lineSpacing": 100, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "magnitude": 3, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - }, - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - } - } - } - } - ] - } - }, - { - "startIndex": 231, - "endIndex": 232, - "paragraph": { - "elements": [ - { - "startIndex": 231, - "endIndex": 232, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 232, - "endIndex": 241, - "paragraph": { - "elements": [ - { - "startIndex": 232, - "endIndex": 241, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.d92q25gd8wdx", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 241, - "endIndex": 366, - "paragraph": { - "elements": [ - { - "startIndex": 241, - "endIndex": 303, - "textRun": { - "content": "Develop a node.js script that will use the Google Drive API (", - "textStyle": {} - } - }, - { - "startIndex": 303, - "endIndex": 363, - "textRun": { - "content": "https://developers.google.com/drive/api/v3/quickstart/nodejs", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://developers.google.com/drive/api/v3/quickstart/nodejs" - } - } - } - }, - { - "startIndex": 363, - "endIndex": 366, - "textRun": { - "content": ") \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 366, - "endIndex": 367, - "paragraph": { - "elements": [ - { - "startIndex": 366, - "endIndex": 367, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 367, - "endIndex": 401, - "paragraph": { - "elements": [ - { - "startIndex": 367, - "endIndex": 401, - "textRun": { - "content": "With a \"Shared Drive\" as the key:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 401, - "endIndex": 449, - "paragraph": { - "elements": [ - { - "startIndex": 401, - "endIndex": 449, - "textRun": { - "content": "Read all the files from a Google \"Shared Drive\"\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "textStyle": {} - } - } - }, - { - "startIndex": 449, - "endIndex": 520, - "paragraph": { - "elements": [ - { - "startIndex": 449, - "endIndex": 520, - "textRun": { - "content": "Build a map of the driveId (URL) to the pathname in the \"Shared Drive\"\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "textStyle": {} - } - } - }, - { - "startIndex": 520, - "endIndex": 546, - "paragraph": { - "elements": [ - { - "startIndex": 520, - "endIndex": 546, - "textRun": { - "content": "For each Google Document:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "textStyle": {} - } - } - }, - { - "startIndex": 546, - "endIndex": 625, - "paragraph": { - "elements": [ - { - "startIndex": 546, - "endIndex": 625, - "textRun": { - "content": "Convert to a Markdown file with the path (instead of the driveId for the file)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "nestingLevel": 1, - "textStyle": {} - } - } - }, - { - "startIndex": 625, - "endIndex": 709, - "paragraph": { - "elements": [ - { - "startIndex": 625, - "endIndex": 709, - "textRun": { - "content": "Change driveId to the path (eg: 12lvdxKgGsD.../edit would be changed to /filename\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "nestingLevel": 1, - "textStyle": {} - } - } - }, - { - "startIndex": 709, - "endIndex": 767, - "paragraph": { - "elements": [ - { - "startIndex": 709, - "endIndex": 767, - "textRun": { - "content": "Support diagrams as SVG (and map the URLs in the diagram)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.n2aubyz5a4e3", - "nestingLevel": 1, - "textStyle": {} - } - } - }, - { - "startIndex": 767, - "endIndex": 768, - "paragraph": { - "elements": [ - { - "startIndex": 767, - "endIndex": 768, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 768, - "endIndex": 860, - "paragraph": { - "elements": [ - { - "startIndex": 768, - "endIndex": 860, - "textRun": { - "content": "The script should scan for changes in the drive and then refresh the local converted files.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 860, - "endIndex": 861, - "paragraph": { - "elements": [ - { - "startIndex": 860, - "endIndex": 861, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 861, - "endIndex": 863, - "paragraph": { - "elements": [ - { - "startIndex": 861, - "endIndex": 862, - "inlineObjectElement": { - "inlineObjectId": "kix.3vos7cbo46z2", - "textStyle": {} - } - }, - { - "startIndex": 862, - "endIndex": 863, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 863, - "endIndex": 864, - "paragraph": { - "elements": [ - { - "startIndex": 863, - "endIndex": 864, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 864, - "endIndex": 1122, - "paragraph": { - "elements": [ - { - "startIndex": 864, - "endIndex": 1122, - "textRun": { - "content": "The node.js script should refresh the \"Local Filesystem\" with changes from the Google Shared Drive overwriting or deleting any content to bring it into sync. The Local Filesystem is not preserved (since we will be committing the markdown in github anyway).\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1122, - "endIndex": 1123, - "paragraph": { - "elements": [ - { - "startIndex": 1122, - "endIndex": 1123, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1123, - "endIndex": 1129, - "paragraph": { - "elements": [ - { - "startIndex": 1123, - "endIndex": 1129, - "textRun": { - "content": "Links\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.xqs1pat65cgx", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1129, - "endIndex": 1175, - "paragraph": { - "elements": [ - { - "startIndex": 1129, - "endIndex": 1137, - "textRun": { - "content": "Project ", - "textStyle": {} - } - }, - { - "startIndex": 1137, - "endIndex": 1173, - "textRun": { - "content": "https://github.com/mieweb/wikiGDrive", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://github.com/mieweb/wikiGDrive" - } - } - } - }, - { - "startIndex": 1173, - "endIndex": 1175, - "textRun": { - "content": " \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.v3vkf3q3sa7h", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1175, - "endIndex": 1203, - "paragraph": { - "elements": [ - { - "startIndex": 1175, - "endIndex": 1182, - "textRun": { - "content": "Google ", - "textStyle": {} - } - }, - { - "startIndex": 1182, - "endIndex": 1202, - "textRun": { - "content": "Shared Drive Example", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://drive.google.com/drive/u/0/folders/0AB1bEyFsoJ9pUk9PVA" - } - } - } - }, - { - "startIndex": 1202, - "endIndex": 1203, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.v3vkf3q3sa7h", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1203, - "endIndex": 1219, - "paragraph": { - "elements": [ - { - "startIndex": 1203, - "endIndex": 1210, - "textRun": { - "content": "Upwork ", - "textStyle": {} - } - }, - { - "startIndex": 1210, - "endIndex": 1217, - "textRun": { - "content": "posting", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://www.upwork.com/jobs/~01f8c39128e36f1bf0" - } - } - } - }, - { - "startIndex": 1217, - "endIndex": 1219, - "textRun": { - "content": " \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.v3vkf3q3sa7h", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1219, - "endIndex": 1273, - "paragraph": { - "elements": [ - { - "startIndex": 1219, - "endIndex": 1235, - "textRun": { - "content": "Example system: ", - "textStyle": {} - } - }, - { - "startIndex": 1235, - "endIndex": 1271, - "textRun": { - "content": "http://wikigrive.gitgis.com/toc.html", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "http://wikigrive.gitgis.com/toc.html" - } - } - } - }, - { - "startIndex": 1271, - "endIndex": 1273, - "textRun": { - "content": " \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.v3vkf3q3sa7h", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1273, - "endIndex": 1274, - "paragraph": { - "elements": [ - { - "startIndex": 1273, - "endIndex": 1274, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - } - } - } - }, - { - "startIndex": 1274, - "endIndex": 1283, - "paragraph": { - "elements": [ - { - "startIndex": 1274, - "endIndex": 1283, - "textRun": { - "content": "Contacts\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.17le3t7nckjw", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1283, - "endIndex": 1339, - "paragraph": { - "elements": [ - { - "startIndex": 1283, - "endIndex": 1296, - "textRun": { - "content": "Doug Horner (", - "textStyle": {} - } - }, - { - "startIndex": 1296, - "endIndex": 1313, - "textRun": { - "content": "example@example.com", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "mailto:example@example.com" - } - } - } - }, - { - "startIndex": 1313, - "endIndex": 1339, - "textRun": { - "content": ") phone: 123-123-123\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.7nyr4kj8ib2f", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1339, - "endIndex": 1340, - "paragraph": { - "elements": [ - { - "startIndex": 1339, - "endIndex": 1340, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1340, - "endIndex": 1353, - "paragraph": { - "elements": [ - { - "startIndex": 1340, - "endIndex": 1353, - "textRun": { - "content": "Requirements\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.vttky12ic725", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1353, - "endIndex": 1354, - "paragraph": { - "elements": [ - { - "startIndex": 1353, - "endIndex": 1354, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1354, - "endIndex": 1368, - "paragraph": { - "elements": [ - { - "startIndex": 1354, - "endIndex": 1368, - "textRun": { - "content": "The app must:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 1368, - "endIndex": 1450, - "paragraph": { - "elements": [ - { - "startIndex": 1368, - "endIndex": 1450, - "textRun": { - "content": "be able to be run once or run as a daemon (looking for changes in near real-time)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1450, - "endIndex": 1544, - "paragraph": { - "elements": [ - { - "startIndex": 1450, - "endIndex": 1544, - "textRun": { - "content": "Take changes from gdrive and propagate them down to the local file system (likely a git repo)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1544, - "endIndex": 1666, - "paragraph": { - "elements": [ - { - "startIndex": 1544, - "endIndex": 1556, - "textRun": { - "content": "Detect file ", - "textStyle": {} - } - }, - { - "startIndex": 1556, - "endIndex": 1573, - "textRun": { - "content": "moves and renames", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.mqk2e0362t9z" - } - } - } - }, - { - "startIndex": 1573, - "endIndex": 1666, - "textRun": { - "content": " from prior runs and leave redirects in the local file system to the new file or directory. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1666, - "endIndex": 1799, - "paragraph": { - "elements": [ - { - "startIndex": 1666, - "endIndex": 1799, - "textRun": { - "content": "Convert google docs to markdown while preserving as much of the meaning of the document. (Headings, images, drawings, tables, etc). \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1799, - "endIndex": 1903, - "paragraph": { - "elements": [ - { - "startIndex": 1799, - "endIndex": 1903, - "textRun": { - "content": "Each generated file should have parsable comments embedded in the original source google doc is known. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 1903, - "endIndex": 2121, - "paragraph": { - "elements": [ - { - "startIndex": 1903, - "endIndex": 2121, - "textRun": { - "content": "Embedded images (not originally stored on the shared folder will have to be extracted to the filesystem with a hashing system to prevent duplicate copies files in cases where images are pasted into multiple documents.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2121, - "endIndex": 2178, - "paragraph": { - "elements": [ - { - "startIndex": 2121, - "endIndex": 2178, - "textRun": { - "content": "Convert google drawings to svg and fix up urls as well. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2178, - "endIndex": 2360, - "paragraph": { - "elements": [ - { - "startIndex": 2178, - "endIndex": 2360, - "textRun": { - "content": "Download images and place them in the proper folder. Embed metadata in the image pointing to the source on the google drive. It could be a .md file with the same name as the image. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2360, - "endIndex": 2532, - "paragraph": { - "elements": [ - { - "startIndex": 2360, - "endIndex": 2532, - "textRun": { - "content": "Translate hyperlinks to the filesystem relative paths if they exist in the shared drive (both within Docs and Drawings). Must support both document urls and heading URLs. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2532, - "endIndex": 2622, - "paragraph": { - "elements": [ - { - "startIndex": 2532, - "endIndex": 2544, - "textRun": { - "content": "Construct a ", - "textStyle": {} - } - }, - { - "startIndex": 2544, - "endIndex": 2574, - "textRun": { - "content": "table of contents and an index", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.iqx1hufojtom" - } - } - } - }, - { - "startIndex": 2574, - "endIndex": 2622, - "textRun": { - "content": " from all of the documents in the shared drive.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2622, - "endIndex": 2707, - "paragraph": { - "elements": [ - { - "startIndex": 2622, - "endIndex": 2707, - "textRun": { - "content": "It should be parsable so Javascript on the client could search and build navigation \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2707, - "endIndex": 2769, - "paragraph": { - "elements": [ - { - "startIndex": 2707, - "endIndex": 2748, - "textRun": { - "content": "There should be generated markdown file (", - "textStyle": {} - } - }, - { - "startIndex": 2748, - "endIndex": 2754, - "textRun": { - "content": "toc.md", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.h3vpqh726qwp" - } - } - } - }, - { - "startIndex": 2754, - "endIndex": 2759, - "textRun": { - "content": " and ", - "textStyle": {} - } - }, - { - "startIndex": 2759, - "endIndex": 2767, - "textRun": { - "content": "index.md", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "headingId": "h.25nwvh7c83vs" - } - } - } - }, - { - "startIndex": 2767, - "endIndex": 2769, - "textRun": { - "content": ")\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.nakfi9umjv9d", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2769, - "endIndex": 2770, - "paragraph": { - "elements": [ - { - "startIndex": 2769, - "endIndex": 2770, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2770, - "endIndex": 2771, - "paragraph": { - "elements": [ - { - "startIndex": 2770, - "endIndex": 2771, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2771, - "endIndex": 2784, - "paragraph": { - "elements": [ - { - "startIndex": 2771, - "endIndex": 2784, - "textRun": { - "content": "Later phase:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2784, - "endIndex": 2814, - "paragraph": { - "elements": [ - { - "startIndex": 2784, - "endIndex": 2814, - "textRun": { - "content": "Confluence -> markdown export\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.feo7r77m8hen", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2814, - "endIndex": 2861, - "paragraph": { - "elements": [ - { - "startIndex": 2814, - "endIndex": 2861, - "textRun": { - "content": "Scientific notation for headers (as an option)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.feo7r77m8hen", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2861, - "endIndex": 2901, - "paragraph": { - "elements": [ - { - "startIndex": 2861, - "endIndex": 2901, - "textRun": { - "content": "Google sheets to CSV with MIE’s datavis\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.feo7r77m8hen", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2901, - "endIndex": 2935, - "paragraph": { - "elements": [ - { - "startIndex": 2901, - "endIndex": 2935, - "textRun": { - "content": "Markdown -> Google Docs converter\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.feo7r77m8hen", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 2935, - "endIndex": 2936, - "paragraph": { - "elements": [ - { - "startIndex": 2935, - "endIndex": 2936, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2936, - "endIndex": 2937, - "paragraph": { - "elements": [ - { - "startIndex": 2936, - "endIndex": 2937, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2937, - "endIndex": 2961, - "paragraph": { - "elements": [ - { - "startIndex": 2937, - "endIndex": 2961, - "textRun": { - "content": "Instructions (proposed)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.xcz3eudi65fe", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2961, - "endIndex": 2962, - "paragraph": { - "elements": [ - { - "startIndex": 2961, - "endIndex": 2962, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2962, - "endIndex": 2985, - "paragraph": { - "elements": [ - { - "startIndex": 2962, - "endIndex": 2985, - "textRun": { - "content": "npm install wikigdrive\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2985, - "endIndex": 2986, - "paragraph": { - "elements": [ - { - "startIndex": 2985, - "endIndex": 2986, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 2986, - "endIndex": 3017, - "paragraph": { - "elements": [ - { - "startIndex": 2986, - "endIndex": 3017, - "textRun": { - "content": "node_modules/bin/wikigdrive -h\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3017, - "endIndex": 3018, - "paragraph": { - "elements": [ - { - "startIndex": 3017, - "endIndex": 3018, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3018, - "endIndex": 3048, - "paragraph": { - "elements": [ - { - "startIndex": 3018, - "endIndex": 3048, - "textRun": { - "content": "wikigdrive [shared drive url]\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3048, - "endIndex": 3049, - "paragraph": { - "elements": [ - { - "startIndex": 3048, - "endIndex": 3049, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3049, - "endIndex": 3058, - "paragraph": { - "elements": [ - { - "startIndex": 3049, - "endIndex": 3058, - "textRun": { - "content": "Options:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3058, - "endIndex": 3080, - "paragraph": { - "elements": [ - { - "startIndex": 3058, - "endIndex": 3080, - "textRun": { - "content": "—config (.wikigdrive)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3080, - "endIndex": 3086, - "paragraph": { - "elements": [ - { - "startIndex": 3080, - "endIndex": 3086, - "textRun": { - "content": "—user\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3086, - "endIndex": 3092, - "paragraph": { - "elements": [ - { - "startIndex": 3086, - "endIndex": 3092, - "textRun": { - "content": "—pass\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3092, - "endIndex": 3123, - "paragraph": { - "elements": [ - { - "startIndex": 3092, - "endIndex": 3123, - "textRun": { - "content": "—dest (current working folder)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3123, - "endIndex": 3170, - "paragraph": { - "elements": [ - { - "startIndex": 3123, - "endIndex": 3170, - "textRun": { - "content": "—watch (keep scanning for changes, ie: daemon)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 3170, - "endIndex": 3171, - "paragraph": { - "elements": [ - { - "startIndex": 3170, - "endIndex": 3171, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3171, - "endIndex": 3366, - "paragraph": { - "elements": [ - { - "startIndex": 3171, - "endIndex": 3366, - "textRun": { - "content": "wikigdrive keeps a local JSON config file in the dest directory with state from prior runs. The config contains a map of URL driveIds to the local filenames along with metadata about each file. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3366, - "endIndex": 3367, - "paragraph": { - "elements": [ - { - "startIndex": 3366, - "endIndex": 3367, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3367, - "endIndex": 3392, - "paragraph": { - "elements": [ - { - "startIndex": 3367, - "endIndex": 3392, - "textRun": { - "content": "Renames and Redirecting \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.mqk2e0362t9z", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3392, - "endIndex": 3734, - "paragraph": { - "elements": [ - { - "startIndex": 3392, - "endIndex": 3734, - "textRun": { - "content": "When a Document is renamed or moved in the shared drive the driveId says the same, but its place in the filesystem changes. For example a document named “Carbon” would be created as Carbon.md. Sometime later its renamed to “Carbon Fiber” then a new file “Carbon Fiber.md” would be made with the content and the old “Carbon.md” is changed to:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3734, - "endIndex": 3735, - "paragraph": { - "elements": [ - { - "startIndex": 3734, - "endIndex": 3735, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3735, - "endIndex": 3778, - "paragraph": { - "elements": [ - { - "startIndex": 3735, - "endIndex": 3778, - "textRun": { - "content": "Renamed to [Carbon Fiber](Carbon-Fiber.md)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.ermo2u5ejnrl", - "namedStyleType": "SUBTITLE", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3778, - "endIndex": 3779, - "paragraph": { - "elements": [ - { - "startIndex": 3778, - "endIndex": 3779, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3779, - "endIndex": 3873, - "paragraph": { - "elements": [ - { - "startIndex": 3779, - "endIndex": 3873, - "textRun": { - "content": "By leaving the old markdown files no one gets lost or gets broken links if the names change. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3873, - "endIndex": 3874, - "paragraph": { - "elements": [ - { - "startIndex": 3873, - "endIndex": 3874, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 3874, - "endIndex": 4078, - "paragraph": { - "elements": [ - { - "startIndex": 3874, - "endIndex": 4078, - "textRun": { - "content": "If a document is moved from one folder to another or a directory is renamed, all files in the path will get renamed too and the original paths will get a redirect file in their old location. For example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4078, - "endIndex": 4079, - "paragraph": { - "elements": [ - { - "startIndex": 4078, - "endIndex": 4079, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4079, - "endIndex": 4086, - "paragraph": { - "elements": [ - { - "startIndex": 4079, - "endIndex": 4086, - "textRun": { - "content": "Folder\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4086, - "endIndex": 4099, - "paragraph": { - "elements": [ - { - "startIndex": 4086, - "endIndex": 4099, - "textRun": { - "content": "Example-1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4099, - "endIndex": 4112, - "paragraph": { - "elements": [ - { - "startIndex": 4099, - "endIndex": 4112, - "textRun": { - "content": "Example-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4112, - "endIndex": 4113, - "paragraph": { - "elements": [ - { - "startIndex": 4112, - "endIndex": 4113, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4113, - "endIndex": 4173, - "paragraph": { - "elements": [ - { - "startIndex": 4113, - "endIndex": 4173, - "textRun": { - "content": "If Folder is renamed to Container, the new layout would be:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4173, - "endIndex": 4183, - "paragraph": { - "elements": [ - { - "startIndex": 4173, - "endIndex": 4183, - "textRun": { - "content": "Container\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4183, - "endIndex": 4196, - "paragraph": { - "elements": [ - { - "startIndex": 4183, - "endIndex": 4196, - "textRun": { - "content": "Example-1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4196, - "endIndex": 4209, - "paragraph": { - "elements": [ - { - "startIndex": 4196, - "endIndex": 4209, - "textRun": { - "content": "Example-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4209, - "endIndex": 4216, - "paragraph": { - "elements": [ - { - "startIndex": 4209, - "endIndex": 4216, - "textRun": { - "content": "Folder\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4216, - "endIndex": 4256, - "paragraph": { - "elements": [ - { - "startIndex": 4216, - "endIndex": 4256, - "textRun": { - "content": "Example-1.md -> /Container/Example-1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4256, - "endIndex": 4296, - "paragraph": { - "elements": [ - { - "startIndex": 4256, - "endIndex": 4296, - "textRun": { - "content": "Example-2.md -> /Container/Example-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4296, - "endIndex": 4297, - "paragraph": { - "elements": [ - { - "startIndex": 4296, - "endIndex": 4297, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4297, - "endIndex": 4384, - "paragraph": { - "elements": [ - { - "startIndex": 4297, - "endIndex": 4384, - "textRun": { - "content": "Then sometime later, “Example 1” is renamed to “Sample 1” the folder layout should be:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4384, - "endIndex": 4394, - "paragraph": { - "elements": [ - { - "startIndex": 4384, - "endIndex": 4394, - "textRun": { - "content": "Container\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4394, - "endIndex": 4406, - "paragraph": { - "elements": [ - { - "startIndex": 4394, - "endIndex": 4400, - "textRun": { - "content": "Sample", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4400, - "endIndex": 4401, - "textRun": { - "content": "-", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4401, - "endIndex": 4406, - "textRun": { - "content": "1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - } - }, - { - "startIndex": 4406, - "endIndex": 4434, - "paragraph": { - "elements": [ - { - "startIndex": 4406, - "endIndex": 4422, - "textRun": { - "content": "Example-1.md -> ", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4422, - "endIndex": 4428, - "textRun": { - "content": "Sample", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4428, - "endIndex": 4429, - "textRun": { - "content": "-", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4429, - "endIndex": 4434, - "textRun": { - "content": "1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - } - }, - { - "startIndex": 4434, - "endIndex": 4447, - "paragraph": { - "elements": [ - { - "startIndex": 4434, - "endIndex": 4447, - "textRun": { - "content": "Example-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4447, - "endIndex": 4454, - "paragraph": { - "elements": [ - { - "startIndex": 4447, - "endIndex": 4454, - "textRun": { - "content": "Folder\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4454, - "endIndex": 4493, - "paragraph": { - "elements": [ - { - "startIndex": 4454, - "endIndex": 4481, - "textRun": { - "content": "Example-1.md -> /Container/", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4481, - "endIndex": 4487, - "textRun": { - "content": "Sample", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4487, - "endIndex": 4488, - "textRun": { - "content": "-", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - }, - { - "startIndex": 4488, - "endIndex": 4493, - "textRun": { - "content": "1.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - } - } - } - } - }, - { - "startIndex": 4493, - "endIndex": 4533, - "paragraph": { - "elements": [ - { - "startIndex": 4493, - "endIndex": 4533, - "textRun": { - "content": "Example-2.md -> /Container/Example-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.yz0ks5o8ctn9", - "nestingLevel": 1, - "textStyle": { - "fontSize": { - "magnitude": 10, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - }, - { - "startIndex": 4533, - "endIndex": 4534, - "paragraph": { - "elements": [ - { - "startIndex": 4533, - "endIndex": 4534, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4534, - "endIndex": 4535, - "paragraph": { - "elements": [ - { - "startIndex": 4534, - "endIndex": 4535, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4535, - "endIndex": 4561, - "paragraph": { - "elements": [ - { - "startIndex": 4535, - "endIndex": 4561, - "textRun": { - "content": "Collisions with Filenames\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.qyy6tn72wtk7", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4561, - "endIndex": 4795, - "paragraph": { - "elements": [ - { - "startIndex": 4561, - "endIndex": 4795, - "textRun": { - "content": "Google Drive allows filenames with the same name to be created on shared drives. When transforming them into the local filesystem, each file will be renamed to a new file and a disambiguation page will be placed in their place. Eg:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4795, - "endIndex": 4796, - "paragraph": { - "elements": [ - { - "startIndex": 4795, - "endIndex": 4796, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4796, - "endIndex": 4829, - "paragraph": { - "elements": [ - { - "startIndex": 4796, - "endIndex": 4829, - "textRun": { - "content": "Google Drive Folder\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4829, - "endIndex": 4871, - "paragraph": { - "elements": [ - { - "startIndex": 4829, - "endIndex": 4871, - "textRun": { - "content": " Carbon Carbon-1.md\t\t\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4871, - "endIndex": 4911, - "paragraph": { - "elements": [ - { - "startIndex": 4871, - "endIndex": 4911, - "textRun": { - "content": " Carbon Carbon-2.md\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4911, - "endIndex": 4950, - "paragraph": { - "elements": [ - { - "startIndex": 4911, - "endIndex": 4950, - "textRun": { - "content": " Carbon.md \n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 4950, - "endIndex": 4951, - "paragraph": { - "elements": [ - { - "startIndex": 4950, - "endIndex": 4951, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 4951, - "endIndex": 5024, - "paragraph": { - "elements": [ - { - "startIndex": 4951, - "endIndex": 4956, - "textRun": { - "content": "The c", - "textStyle": {} - } - }, - { - "startIndex": 4956, - "endIndex": 5024, - "textRun": { - "content": "ontents of Carbon.md would show each of the conflicting references:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5024, - "endIndex": 5025, - "paragraph": { - "elements": [ - { - "startIndex": 5024, - "endIndex": 5025, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5025, - "endIndex": 5093, - "paragraph": { - "elements": [ - { - "startIndex": 5025, - "endIndex": 5093, - "textRun": { - "content": " There were two documents with the same name in the same folder.\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5093, - "endIndex": 5122, - "paragraph": { - "elements": [ - { - "startIndex": 5093, - "endIndex": 5122, - "textRun": { - "content": " * [Carbon](Carbon-1.md) \n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - } - } - } - }, - { - "startIndex": 5122, - "endIndex": 5151, - "paragraph": { - "elements": [ - { - "startIndex": 5122, - "endIndex": 5151, - "textRun": { - "content": " * [Carbon](Carbon-2.md) \n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5151, - "endIndex": 5153, - "paragraph": { - "elements": [ - { - "startIndex": 5151, - "endIndex": 5153, - "textRun": { - "content": " \n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Consolas", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5153, - "endIndex": 5181, - "paragraph": { - "elements": [ - { - "startIndex": 5153, - "endIndex": 5181, - "textRun": { - "content": "Table of Contents and Index\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.iqx1hufojtom", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5181, - "endIndex": 5268, - "paragraph": { - "elements": [ - { - "startIndex": 5181, - "endIndex": 5268, - "textRun": { - "content": "In the root of the local filesystem two files will be created: the toc.md and index.md\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5268, - "endIndex": 5286, - "paragraph": { - "elements": [ - { - "startIndex": 5268, - "endIndex": 5286, - "textRun": { - "content": "Table of Contents\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.h3vpqh726qwp", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 5286, - "endIndex": 5445, - "paragraph": { - "elements": [ - { - "startIndex": 5286, - "endIndex": 5445, - "textRun": { - "content": "The table of contents is a layout of the documents and their position in the drive as an unordered list. It should not contain redirected files, images, etc. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 5445, - "endIndex": 5451, - "paragraph": { - "elements": [ - { - "startIndex": 5445, - "endIndex": 5451, - "textRun": { - "content": "Index\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.25nwvh7c83vs", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 5451, - "endIndex": 5643, - "paragraph": { - "elements": [ - { - "startIndex": 5451, - "endIndex": 5621, - "textRun": { - "content": "The index is a listing of all of the defined terms and their references in the documents. The processing may be passed to another tool to construct the index. Examples: ", - "textStyle": {} - } - }, - { - "startIndex": 5621, - "endIndex": 5629, - "textRun": { - "content": "kramdown", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://meta.stackexchange.com/questions/72395/is-it-possible-to-have-definition-lists-in-markdown" - } - } - } - }, - { - "startIndex": 5629, - "endIndex": 5631, - "textRun": { - "content": ", ", - "textStyle": {} - } - }, - { - "startIndex": 5631, - "endIndex": 5642, - "textRun": { - "content": "Asciidoctor", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://asciidoctor.org/docs/user-manual/" - } - } - } - }, - { - "startIndex": 5642, - "endIndex": 5643, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - } - } - }, - { - "startIndex": 5643, - "endIndex": 5647, - "paragraph": { - "elements": [ - { - "startIndex": 5643, - "endIndex": 5647, - "textRun": { - "content": "FAQ\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "headingId": "h.dqy5042w3u8q", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT" - } - } - }, - { - "startIndex": 5647, - "endIndex": 5681, - "paragraph": { - "elements": [ - { - "startIndex": 5647, - "endIndex": 5681, - "textRun": { - "content": "What is the purpose of this tool?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 5681, - "endIndex": 5848, - "paragraph": { - "elements": [ - { - "startIndex": 5681, - "endIndex": 5848, - "textRun": { - "content": "To enable collaborative editing of documentation and the ability to publish that documentation as well as linking it to revision control system branches (like in git)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 5848, - "endIndex": 5904, - "paragraph": { - "elements": [ - { - "startIndex": 5848, - "endIndex": 5904, - "textRun": { - "content": "Why use google at all. Why not use markdown and GitHub?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 5904, - "endIndex": 6051, - "paragraph": { - "elements": [ - { - "startIndex": 5904, - "endIndex": 6051, - "textRun": { - "content": "No collaboration in real-time. Also, markdown requires skill when managing screenshots and diagrams that are not easily accomplished in markdown. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6051, - "endIndex": 6081, - "paragraph": { - "elements": [ - { - "startIndex": 6051, - "endIndex": 6081, - "textRun": { - "content": "Why not just use google docs?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6081, - "endIndex": 6509, - "paragraph": { - "elements": [ - { - "startIndex": 6081, - "endIndex": 6369, - "textRun": { - "content": "Would love it if it were possible, but the drive does not offer the ability to publish pages in a clean way. The URLs are not SEO friendly. Would love it if there was a driveId map where every document could be given a friendly name (aka its title on the drive). Then (like Wikipedia has ", - "textStyle": {} - } - }, - { - "startIndex": 6369, - "endIndex": 6383, - "textRun": { - "content": "disambiguation", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://en.wikipedia.org/wiki/Wikipedia:Disambiguation" - } - } - } - }, - { - "startIndex": 6383, - "endIndex": 6509, - "textRun": { - "content": " pages), a reader could be redirected to the proper content. Google doesn’t, so this project is an attempt to fill that gap. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6509, - "endIndex": 6689, - "paragraph": { - "elements": [ - { - "startIndex": 6509, - "endIndex": 6689, - "textRun": { - "content": "Also, Google does not have a good blame system for contributions to a document. Hopefully, this is fixed someday but in the meantime, GitHub on markdown can *help* fill the void. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6689, - "endIndex": 6703, - "paragraph": { - "elements": [ - { - "startIndex": 6689, - "endIndex": 6703, - "textRun": { - "content": "Why markdown?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6703, - "endIndex": 6831, - "paragraph": { - "elements": [ - { - "startIndex": 6703, - "endIndex": 6831, - "textRun": { - "content": "All ears for a different preferred format. It’s easy to read when editing directly and when doing a diff for changes it’s clean\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6831, - "endIndex": 6873, - "paragraph": { - "elements": [ - { - "startIndex": 6831, - "endIndex": 6873, - "textRun": { - "content": "What about mismatches in Docs vs Markdown\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 6873, - "endIndex": 7061, - "paragraph": { - "elements": [ - { - "startIndex": 6873, - "endIndex": 7051, - "textRun": { - "content": "There are features of Google Docs that are not going to be supported. Like coloring text, page breaks, headers, comments, etc. These features are not core to our goals for clean ", - "textStyle": {} - } - }, - { - "startIndex": 7051, - "endIndex": 7058, - "textRun": { - "content": "WYSIYYM", - "textStyle": { - "underline": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.06666667, - "green": 0.33333334, - "blue": 0.8 - } - } - }, - "link": { - "url": "https://en.wikipedia.org/wiki/WYSIWYM" - } - } - } - }, - { - "startIndex": 7058, - "endIndex": 7061, - "textRun": { - "content": ". \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 7061, - "endIndex": 7137, - "paragraph": { - "elements": [ - { - "startIndex": 7061, - "endIndex": 7137, - "textRun": { - "content": "Keeping a WYSIWYM style insures a good mobile experience to view and edit. \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 7137, - "endIndex": 7196, - "paragraph": { - "elements": [ - { - "startIndex": 7137, - "endIndex": 7196, - "textRun": { - "content": "Why not make a website front end to a Google shared drive?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 7196, - "endIndex": 7477, - "paragraph": { - "elements": [ - { - "startIndex": 7196, - "endIndex": 7477, - "textRun": { - "content": "Our goals are to be able to take versions of the content and commit them along with a version of the code at a point in time. By just making a website, it would allow for real time viewing of the content but no way to go to a specific version of the documentation at a given time.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - }, - { - "startIndex": 7477, - "endIndex": 7619, - "paragraph": { - "elements": [ - { - "startIndex": 7477, - "endIndex": 7618, - "textRun": { - "content": "A website front end is a goal for real-time testing of the viewing experience but initially, we want to make markdown that can be committed. ", - "textStyle": {} - } - }, - { - "startIndex": 7618, - "endIndex": 7619, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spacingMode": "COLLAPSE_LISTS", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - } - }, - "bullet": { - "listId": "kix.ba54h86yyzh1", - "nestingLevel": 1, - "textStyle": { - "underline": false - } - } - } - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "pageNumberStart": 1, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginRight": { - "magnitude": 72, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 72, - "unit": "PT" - }, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "textStyle": { - "bold": false, - "italic": false, - "underline": false, - "strikethrough": false, - "smallCaps": false, - "backgroundColor": {}, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - }, - "baselineOffset": "NONE" - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "alignment": "START", - "lineSpacing": 115, - "direction": "LEFT_TO_RIGHT", - "spacingMode": "NEVER_COLLAPSE", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "borderBetween": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderTop": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderBottom": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderLeft": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "borderRight": { - "color": {}, - "width": { - "unit": "PT" - }, - "padding": { - "unit": "PT" - }, - "dashStyle": "SOLID" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "indentEnd": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "avoidWidowAndOrphan": true, - "shading": { - "backgroundColor": {} - } - } - }, - { - "namedStyleType": "HEADING_1", - "textStyle": { - "fontSize": { - "magnitude": 16, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.mkxk62eqq59a", - "namedStyleType": "HEADING_1", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_2", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.5nzlqsjr37x", - "namedStyleType": "HEADING_2", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_3", - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_4", - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "headingId": "h.ioi9fbjg71c1", - "namedStyleType": "HEADING_3", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_5", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "HEADING_6", - "textStyle": { - "italic": true, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 0.4, - "green": 0.4, - "blue": 0.4 - } - } - }, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "magnitude": 8, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "TITLE", - "textStyle": { - "fontSize": { - "magnitude": 21, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Trebuchet MS", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "keepLinesTogether": true, - "keepWithNext": true - } - }, - { - "namedStyleType": "SUBTITLE", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Courier New", - "weight": 400 - } - }, - "paragraphStyle": { - "namedStyleType": "NORMAL_TEXT", - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true - } - } - ] - }, - "lists": { - "kix.6hybdcb2jcjq": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "-", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 342, - "unit": "PT" - }, - "indentStart": { - "magnitude": 360, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.7nyr4kj8ib2f": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.n2aubyz5a4e3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.nakfi9umjv9d": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "DECIMAL", - "glyphFormat": "%0.", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "ALPHA", - "glyphFormat": "%1.", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "END", - "glyphType": "ROMAN", - "glyphFormat": "%2.", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "DECIMAL", - "glyphFormat": "%3.", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "ALPHA", - "glyphFormat": "%4.", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "END", - "glyphType": "ROMAN", - "glyphFormat": "%5.", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "DECIMAL", - "glyphFormat": "%6.", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphType": "ALPHA", - "glyphFormat": "%7.", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "END", - "glyphType": "ROMAN", - "glyphFormat": "%8.", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.ba54h86yyzh1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.feo7r77m8hen": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.qb390vqo88i0": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.v3vkf3q3sa7h": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - }, - "kix.yz0ks5o8ctn9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%0", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%1", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%2", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%3", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%4", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%5", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "●", - "glyphFormat": "%6", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "○", - "glyphFormat": "%7", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - }, - { - "bulletAlignment": "START", - "glyphSymbol": "■", - "glyphFormat": "%8", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "textStyle": { - "underline": false - }, - "startNumber": 1 - } - ] - } - } - }, - "suggestionsViewMode": "SUGGESTIONS_INLINE", - "inlineObjects": { - "kix.3vos7cbo46z2": { - "objectId": "kix.3vos7cbo46z2", - "inlineObjectProperties": { - "embeddedObject": { - "imageProperties": { - "contentUri": "https://lh5.googleusercontent.com/LCU8gGjdjtQy_Z63Zn1GDRjNnKYygrrGMpXokYelyqYlvub6jYTeCwAh6A_G4WOpUu9TbSzzaH_XGJe-09IA3lDelvY4cSuOLN_AdpNOjauCiFXC-yFQ9bTzk9NlnS9khcFW1FtklezWVJrowQ", - "cropProperties": {} - }, - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "width": { - "unit": "PT" - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED" - }, - "size": { - "height": { - "magnitude": 177, - "unit": "PT" - }, - "width": { - "magnitude": 379.624703087886, - "unit": "PT" - } - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "linkedContentReference": {} - } - } - } - }, - "documentId": "1H6vwfQXIexdg4ldfaoPUjhOZPnSkNn6h29WD6Fi-SBY" -} diff --git a/test/odt_md/quotes.md b/test/odt_md/quotes.md index b49b21f5..f8377c24 100644 --- a/test/odt_md/quotes.md +++ b/test/odt_md/quotes.md @@ -1,4 +1,7 @@ {{% only sys="sysname" %}} + {{% only sys='sysname' %}} + {{% only sys="sysname" %}} + {{% only sys='sysname' %}} diff --git a/test/odt_md/strong-headers.md b/test/odt_md/strong-headers.md index 9b1b56c7..8a6f3db3 100644 --- a/test/odt_md/strong-headers.md +++ b/test/odt_md/strong-headers.md @@ -1,8 +1,11 @@ ## Data Migration Workflow Considerations + As we may recall, the Health Surveillance module allows users to easily track and manage overall health for risk groups and patient populations. Following that is information on how to utilize the import/export tools available with every {{% system-name %}} system. ### Strategies for Migrating Legacy Health Surveillance Data + Health Surveillance (HS) Data is typically broken down into 4 parts during the data migration process: + 1. Active HS Memberships and Next Due Dates 2. Historical HS Memberships @@ -22,7 +25,9 @@ Some clients require the migration of historical, or non-active HS memberships. In some cases, open or pending orders are required for migration for employees with overdue but active panel memberships. This use case is discussed further in the section on [How to determine the Next Due Date on a HS Panel](#3rdcrjn). #### Historical Orders + In some cases, migration of completed orders are required to show that tests or tasks were completed on a particular date. Like any other data migration, discrete migration of historical orders involves more mapping and more effort than creating a summary document of historical orders. The following use cases are most common where the migration of historical orders are needed: ### UL/OHM-Specific Overview for Migrating Legacy Health Surveillance Data + Over the years {{% system-name %}} has completed many data migrations from OHM. Through this process our team has built a series of tools to allow us to convert this data quickly and easily. The following outlines the types of data, modules, and storage we consider for the migration. Consider the storage type needed for the data being migrated, as well as the mapping that may be required when pursuing efforts of discrete data, specifically. Note the table names in parentheses utilized for each module. diff --git a/test/odt_md/suggest.md b/test/odt_md/suggest.md index 3e50ac03..90f6c682 100644 --- a/test/odt_md/suggest.md +++ b/test/odt_md/suggest.md @@ -1,12 +1,7 @@ - - - [https://github.com/mieweb/WikiGDriveTest](https://github.com/mieweb/WikiGDriveTest) - This is a readme - ``` Code block @@ -14,65 +9,22 @@ Code block testing ``` - This is a change - - This is page one - This is link to the [Embedded Diagram Example](gdoc:1O9wKbDsMhFCUG207rHq_PvdmflsuYQMzcWbnuq4Sq_U) document. - - This is a link to internal [documentation](gdoc:11FpspHmC7WbPP_oO5q3VbvwbNEv0Yx41LndH95ECF-w). This is a change. - - - - Another change Alice test + Alice test 2 Testing by Bridget - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this is page two in pageless mode diff --git a/test/odt_md/td-bullets.md b/test/odt_md/td-bullets.md index d00cadd9..17bf2aa7 100644 --- a/test/odt_md/td-bullets.md +++ b/test/odt_md/td-bullets.md @@ -1,11 +1,10 @@ - + -
    statusCurrent status of the Immunization event
    • Completed
    • entered-in-error
    • not-done
    Current status of the Immunization event
    +
    • Completed
    • entered-in-error
    • not-done
    Code Yes Yes
    - From dba66b1e189d1b1b4c295a0209b06b1ba89f4fba Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 15:11:50 +0200 Subject: [PATCH 09/18] Improve markdown processing --- src/odt/OdtToMarkdown.ts | 2 +- src/odt/StateMachine.ts | 46 +++++----- src/odt/macroUtils.ts | 9 ++ src/odt/markdownNodesUtils.ts | 3 + src/odt/postprocess/addEmptyLines.ts | 15 +++- src/odt/postprocess/mergeParagraphs.ts | 11 ++- src/odt/postprocess/postProcess.ts | 6 +- src/odt/postprocess/postProcessPreMacros.ts | 87 +++++++++++++++---- src/odt/postprocess/removeExcessiveLines.ts | 40 ++++++++- .../removeInsideDoubleCodeBegin.ts | 37 +++++++- .../removePreWrappingAroundMacros.ts | 33 +++++-- src/odt/postprocess/removeTdParas.ts | 14 +-- test/odt_md/block-macro.md | 1 - test/odt_md/example-document.md | 5 +- test/odt_md/list-indent.md | 9 +- test/odt_md/list-test.md | 8 +- test/odt_md/pre-mie.md | 2 + 17 files changed, 247 insertions(+), 81 deletions(-) diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index e1405abc..a593d12d 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -400,6 +400,7 @@ export class OdtToMarkdown { } const emptyLine = this.chunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'drawGToText: warning'; this.chunks.append(currentTagNode, emptyLine); const blockWarning = this.chunks.createNode('B'); @@ -567,7 +568,6 @@ export class OdtToMarkdown { switch (child.type) { case 'line_break': this.chunks.append(currentTagNode, this.chunks.createNode('BR/', {})); - // this.stateMachine.pushTag('BR/'); break; case 'tab': this.chunks.appendText(currentTagNode, '\t'); diff --git a/src/odt/StateMachine.ts b/src/odt/StateMachine.ts index e91b10d7..5d36c0b0 100644 --- a/src/odt/StateMachine.ts +++ b/src/odt/StateMachine.ts @@ -1,7 +1,5 @@ import {MarkdownNodes, OutputMode, TAG, TagPayload} from './MarkdownNodes.ts'; import {RewriteRule} from './applyRewriteRule.ts'; -import {isMarkdownBeginMacro, isMarkdownEndMacro} from './macroUtils.ts'; -import {extractText} from './markdownNodesUtils.js'; interface TagLeaf { mode: OutputMode; @@ -164,28 +162,28 @@ export class StateMachine { // } // /REFACT - if (tag === '/P' || tag === '/PRE') { - const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); - switch (this.currentMode) { - case 'raw': - { - if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { - // this.markdownChunks[payload.position].comment = 'Switching to md - isMarkdownEndMacro'; - this.currentMode = 'md'; - } - } - break; - case 'md': - { - if (innerTxt === '{{rawhtml}}' || isMarkdownBeginMacro(innerTxt)) { - // this.markdownChunks[payload.position].comment = 'Switching to raw - isMarkdownBeginMacro'; - this.currentMode = 'raw'; - } - - } - break; - } - } + // if (tag === '/P' || tag === '/PRE') { + // const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); + // switch (this.currentMode) { + // case 'raw': + // { + // if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { + // // this.markdownChunks[payload.position].comment = 'Switching to md - isMarkdownEndMacro'; + // this.currentMode = 'md'; + // } + // } + // break; + // case 'md': + // { + // if (innerTxt === '{{rawhtml}}' || isMarkdownBeginMacro(innerTxt)) { + // // this.markdownChunks[payload.position].comment = 'Switching to raw - isMarkdownBeginMacro'; + // this.currentMode = 'raw'; + // } + // + // } + // break; + // } + // } // REFACT // if (tag === '/CODE') { diff --git a/src/odt/macroUtils.ts b/src/odt/macroUtils.ts index 6a5362a0..3ef10889 100644 --- a/src/odt/macroUtils.ts +++ b/src/odt/macroUtils.ts @@ -9,6 +9,15 @@ export function isMarkdownBeginMacro(innerTxt: string) { return false; } +export function getMarkdownEndMacro(innerTxt: string) { + if ('{{markdown}}' === innerTxt) return '{{/markdown}}'; + if ('{{% markdown %}}' === innerTxt) return '{{% /markdown %}}'; + + if (innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}')) { + // return innerTxt.replace('{{% pre ', '{{% /pre '); + } +} + export function isMarkdownEndMacro(innerTxt: string) { if ('{{/markdown}}' === innerTxt) return true; if ('{{% /markdown %}}' === innerTxt) return true; diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts index eb9a1f5d..179b5b2c 100644 --- a/src/odt/markdownNodesUtils.ts +++ b/src/odt/markdownNodesUtils.ts @@ -518,6 +518,9 @@ export function dump(body: MarkdownTagNode, logger = console) { if (chunk.isTag === true) { line += chunk.tag; + if (chunk.tag === 'PRE') { + line += ` (Lang: ${chunk.payload.lang || ''})`; + } if (chunk.tag === 'UL') { line += ` (Level: ${chunk.payload.listLevel}, #${chunk.payload?.number || ''})`; } diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index c2cab916..666be27f 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -71,6 +71,17 @@ export function addEmptyLines(markdownChunks: MarkdownNodes) { return; } + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) { + const nextChunk = chunk.parent.children[ctx.nodeIdx + 1]; + + if (!isChunkEmptyLine(nextChunk)) { + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'addEmptyLines.ts: after ' + chunk.tag; + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, emptyLine); + return { nodeIdx: ctx.nodeIdx - 1 }; + } + } + if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'P', 'UL', 'IMG/', 'SVG/'].includes(chunk.tag)) { const prevChunk = chunk.parent.children[ctx.nodeIdx - 1]; @@ -92,11 +103,7 @@ export function addEmptyLines(markdownChunks: MarkdownNodes) { chunk.parent.children.splice(ctx.nodeIdx, 0, emptyLine); return { nodeIdx: ctx.nodeIdx + 1 }; } - - // chunk.parent.children.splice(ctx.nodeIdx, 1); - // return { nodeIdx: ctx.nodeIdx - 1 }; } - }); let inHtml = false; diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index 698a29a5..ea5f52b9 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,10 +1,9 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {RewriteRule} from '../applyRewriteRule.ts'; // import {isBeginMacro, isEndMacro, isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.js'; -export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules?: RewriteRule[]) { +export function mergeParagraphs(markdownChunks: MarkdownNodes) { let inHtml = false; walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { @@ -23,7 +22,13 @@ export function mergeParagraphs(markdownChunks: MarkdownNodes, rewriteRules?: Re const children = nextChunk.children.splice(0, nextChunk.children.length); if (chunk.tag === 'P') { - children.unshift(markdownChunks.createNode('EMPTY_LINE/')); + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'mergeParagraphs.ts: empty line between two of: ' + chunk.tag; + children.unshift(emptyLine); + } + + if (chunk.tag === 'PRE' && nextChunk.payload?.lang) { + chunk.payload.lang = nextChunk.payload?.lang; } chunk.children.splice(chunk.children.length, 0, ...children); diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index 7094feb9..cda2c5c7 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -27,8 +27,6 @@ export async function postProcess(chunks: MarkdownNodes) { convertToc(chunks); processListsAndNumbering(chunks); postProcessHeaders(chunks); - removePreWrappingAroundMacros(chunks); - removeInsideDoubleCodeBegin(chunks); fixSpacesInsideInlineFormatting(chunks); await fixBoldItalic(chunks); fixListParagraphs(chunks); @@ -41,11 +39,13 @@ export async function postProcess(chunks: MarkdownNodes) { // addIndentsAndBullets(chunks); mergeTexts(chunks); await rewriteHeaders(chunks); - await removeMarkdownMacro(chunks); // TODO macros mergeParagraphs(chunks); + removePreWrappingAroundMacros(chunks); + await removeMarkdownMacro(chunks); postProcessPreMacros(chunks); + removeInsideDoubleCodeBegin(chunks); removeEmptyTags(chunks); addEmptyLines(chunks); diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index 4e19d248..9468dcde 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -1,5 +1,6 @@ -import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {MarkdownNodes, MarkdownTagNode} from '../MarkdownNodes.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; +import {getMarkdownEndMacro, isMarkdownBeginMacro} from '../macroUtils.js'; function isPreBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}'); @@ -9,6 +10,32 @@ function isPreEndMacro(innerTxt: string) { return innerTxt.startsWith('{{% /pre ') && innerTxt.endsWith(' %}}'); } +function getMatching(startText: string) { + if (startText === '{{rawhtml}}') { + return '{{/rawhtml}}'; + } + + if (isMarkdownBeginMacro(startText)) { + return getMarkdownEndMacro(startText); + } + + return ''; +} + +function cleanupLines(chunk: MarkdownTagNode) { + while (chunk.children.length > 0) { + const child = chunk.children[0]; + + if (child.isTag && child.tag === 'EMPTY_LINE/') { + chunk.children.splice(0, 1); + // child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; + // chunk.parent.children.splice(ctx.nodeIdx, 0, child); + } + + break; + } +} + export function postProcessPreMacros(markdownChunks: MarkdownNodes) { let inHtml = false; @@ -24,11 +51,12 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { if (chunk && chunk.isTag && ['P'].includes(chunk.tag)) { let firstChildIdx = -1; + let startText = ''; for (let idx = 0; idx < chunk.children.length; idx++) { const child = chunk.children[idx]; if (firstChildIdx === -1) { - // if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { - if (child.isTag === false && child.text === '{{rawhtml}}') { + if (child.isTag === false && (child.text === '{{rawhtml}}' || isMarkdownBeginMacro(child.text))) { + startText = child.text; firstChildIdx = idx; } continue; @@ -39,7 +67,7 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { continue; } - if (firstChildIdx > -1 && child.isTag === false && child.text === '{{/rawhtml}}') { + if (firstChildIdx > -1 && child.isTag === false && child.text === getMatching(startText)) { const afterFirst = chunk.children[firstChildIdx + 1]; if (afterFirst.isTag && afterFirst.tag === 'EOL/') { firstChildIdx++; @@ -48,34 +76,63 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { const lastChildIdx = idx; const rawMode = markdownChunks.createNode('RAW_MODE/'); - const children = chunk.children.splice(firstChildIdx + 1, lastChildIdx - firstChildIdx - 1, rawMode); + rawMode.comment = 'postProcessPreMacros.ts: enter raw mode after: ' + startText; + const children = chunk.children.splice(firstChildIdx + 2, lastChildIdx - firstChildIdx - 2, rawMode); rawMode.children.splice(0, 0, ...children); idx -= children.length; firstChildIdx = -1; + + cleanupLines(rawMode); + continue; } + } + } + if (chunk && chunk.isTag && chunk.tag === 'PRE') { + let firstChildIdx = -1; + for (let idx = 0; idx < chunk.children.length; idx++) { + const child = chunk.children[idx]; + if (child.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(child.tag)) { + continue; + } + firstChildIdx = idx; + break; + } + let lastChildIdx = -1; + for (let idx = chunk.children.length - 1; idx >= 0; idx--) { + const child = chunk.children[idx]; + if (child.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(child.tag)) { + continue; + } + lastChildIdx = idx; + break; + } + if (firstChildIdx === -1 || lastChildIdx === -1) { + return; } - } + const firstChild = chunk.children[firstChildIdx]; + const lastChild = chunk.children[lastChildIdx]; - if (chunk && chunk.isTag && chunk.tag === 'PRE') { - const firstChild = chunk.children[0]; - const lastChild = chunk.children[chunk.children.length - 1]; + if (firstChild.isTag === false && isPreBeginMacro(firstChild.text) && + lastChild.isTag === false && isPreEndMacro(lastChild.text)) { - if (firstChild && firstChild.isTag === false && isPreBeginMacro(firstChild.text) && - lastChild && lastChild.isTag === false && isPreEndMacro(lastChild.text)) { + const afterFirst = chunk.children[firstChildIdx + 1]; + if (afterFirst.isTag && afterFirst.tag === 'EOL/') { + firstChildIdx++; + } - chunk.children.splice(0, 1); - chunk.children.splice(chunk.children.length - 1, 1); + const after = chunk.children.splice(lastChildIdx, chunk.children.length - lastChildIdx); + const before = chunk.children.splice(0, firstChildIdx + 1); - chunk.parent.children.splice(ctx.nodeIdx + 1, 0, lastChild); - chunk.parent.children.splice(ctx.nodeIdx, 0, firstChild); + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, ...after); + chunk.parent.children.splice(ctx.nodeIdx, 0, ...before); } } diff --git a/src/odt/postprocess/removeExcessiveLines.ts b/src/odt/postprocess/removeExcessiveLines.ts index ff4df9ec..898f7dcd 100644 --- a/src/odt/postprocess/removeExcessiveLines.ts +++ b/src/odt/postprocess/removeExcessiveLines.ts @@ -29,11 +29,49 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { for (let idx = chunk.children.length - 1; idx > 0; idx--) { const child = chunk.children[idx]; const prevChild = chunk.children[idx - 1]; + const nextChild = chunk.children[idx + 1]; + + if (prevChild && !(prevChild.isTag && prevChild.tag === 'BR/')) { + continue; + } + + if (child.isTag && child.tag === 'BR/') { + if ((nextChild && nextChild.isTag && nextChild.tag === 'IMG/')) { + const eol = markdownChunks.createNode('EOL/'); + eol.comment = 'removeExcessiveLines.ts: Converted BR/ to EOL/'; + chunk.children.splice(idx, 1, eol); + continue; + } + + const eol = markdownChunks.createNode('EOL/'); + eol.comment = 'removeExcessiveLines.ts: Converted BR/ to EOL/ + EMPTY_LINE/'; + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'removeExcessiveLines.ts: Converted BR/ to EOL/ + EMPTY_LINE/'; + chunk.children.splice(idx, 1, eol, emptyLine); + continue; + } + } + + for (let idx = chunk.children.length - 1; idx > 0; idx--) { + const child = chunk.children[idx]; + const prevChild = chunk.children[idx - 1]; + +/* + if (child.isTag && child.tag === 'BR/') { + if (prevChild.isTag && ['BR/', 'EOL/', 'EMPTY_LINE/'].includes(prevChild.tag)) { + child.tag = 'EMPTY_LINE/'; + child.comment = 'removeExcessiveLines.ts: converted BR/ to EMPTY_LINE/'; + idx += 2; + continue; + } + } +*/ if (child.isTag && child.tag === 'EOL/') { if (prevChild.isTag && ['EOL/', 'EMPTY_LINE/'].includes(prevChild.tag)) { child.tag = 'EMPTY_LINE/'; child.comment = 'removeExcessiveLines.ts: converted EOL/ to EMPTY_LINE/'; + idx++; continue; } } @@ -50,7 +88,7 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { } } - if (chunk.isTag && ['P'].includes(chunk.tag)) { + if (chunk.isTag && ['P', 'LI', 'UL'].includes(chunk.tag)) { for (let idx = chunk.children.length - 1; idx > 0; idx--) { const child = chunk.children[idx]; const prevChild = chunk.children[idx - 1]; diff --git a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts index 029a3d1c..65137eec 100644 --- a/src/odt/postprocess/removeInsideDoubleCodeBegin.ts +++ b/src/odt/postprocess/removeInsideDoubleCodeBegin.ts @@ -5,11 +5,40 @@ export function removeInsideDoubleCodeBegin(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (preChunk) => { if (preChunk.isTag === true && preChunk.tag === 'PRE') { if (preChunk.children.length > 0) { + let firstChildIdx = -1; + for (let idx = 0; idx < preChunk.children.length; idx++) { + const child = preChunk.children[idx]; + if (child.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(child.tag)) { + continue; + } + firstChildIdx = idx; + break; + } + + let lastChildIdx = -1; + for (let idx = preChunk.children.length - 1; idx >= 0; idx--) { + const child = preChunk.children[idx]; + if (child.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(child.tag)) { + continue; + } + lastChildIdx = idx; + break; + } + + if (firstChildIdx === -1 || lastChildIdx === -1) { + return; + } + + const firstChild = preChunk.children[firstChildIdx]; + const lastChild = preChunk.children[lastChildIdx]; + + if (lastChild.isTag === false && lastChild.text === '```') { + preChunk.children.splice(lastChildIdx, preChunk.children.length - lastChildIdx); + } - const firstChild = preChunk.children[0]; - if (firstChild.isTag === false && firstChild.text.startsWith('```') && firstChild.text.length > 3) { - preChunk.payload.lang = firstChild.text.substring(3); - preChunk.children.splice(0, 1); + if (firstChild.isTag === false && firstChild.text.startsWith('```') && firstChild.text.length >= 3) { + preChunk.payload.lang = firstChild.text.substring(3); + preChunk.children.splice(0, firstChildIdx + 1); } } } diff --git a/src/odt/postprocess/removePreWrappingAroundMacros.ts b/src/odt/postprocess/removePreWrappingAroundMacros.ts index 591e4cf5..87fc8bef 100644 --- a/src/odt/postprocess/removePreWrappingAroundMacros.ts +++ b/src/odt/postprocess/removePreWrappingAroundMacros.ts @@ -6,20 +6,39 @@ export function removePreWrappingAroundMacros(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (preChunk, ctx: { nodeIdx: number }) => { if (preChunk.isTag === true && preChunk.tag === 'PRE') { if (preChunk.children.length > 0) { - const lastChild = preChunk.children[preChunk.children.length - 1]; + let lastChildIdx = -1; + for (let idx = preChunk.children.length - 1; idx >= 0; idx--) { + const child = preChunk.children[idx]; + if (child.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(child.tag)) { + continue; + } + lastChildIdx = idx; + break; + } + + const lastChild = preChunk.children[lastChildIdx]; if (lastChild.isTag === false && isMarkdownEndMacro(lastChild.text)) { lastChild.parent = preChunk.parent; - preChunk.children.splice(preChunk.children.length - 1, 1); - preChunk.parent.children.splice(ctx.nodeIdx + 1, 0, lastChild); + const after = preChunk.children.splice(lastChildIdx, preChunk.children.length - lastChildIdx); + preChunk.parent.children.splice(ctx.nodeIdx + 1, 0, ...after); } } if (preChunk.children.length > 0) { - const firstChild = preChunk.children[0]; + let firstChildIdx = 0; + const firstChild = preChunk.children[firstChildIdx]; if (firstChild.isTag === false && isMarkdownBeginMacro(firstChild.text)) { - firstChild.parent = preChunk.parent; - preChunk.children.splice(0, 1); - preChunk.parent.children.splice(ctx.nodeIdx, 0, firstChild); + const afterFirst = preChunk.children[firstChildIdx + 1]; + if (afterFirst && afterFirst.isTag && afterFirst.tag === 'EOL/') { + firstChildIdx++; + } + + // console.log('CCCC', preChunk.children.map(c => ({...c, parent: undefined, children: undefined}))); + + // firstChild.parent = preChunk.parent; + const before = preChunk.children.splice(0, firstChildIdx + 1); + // console.log('BBB', before); + preChunk.parent.children.splice(ctx.nodeIdx, 0, ...before); } } } diff --git a/src/odt/postprocess/removeTdParas.ts b/src/odt/postprocess/removeTdParas.ts index ae6b58b0..b6803092 100644 --- a/src/odt/postprocess/removeTdParas.ts +++ b/src/odt/postprocess/removeTdParas.ts @@ -3,11 +3,11 @@ import {walkRecursiveSync} from '../markdownNodesUtils.js'; export function removeTdParas(markdownChunks: MarkdownNodes) { // Run after addEmptyLinesAfterParas - let inHtml = false; + let inHtml = 0; - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { level: number }) => { + walkRecursiveSync(markdownChunks.body, (chunk) => { if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = true; + inHtml++; return; } @@ -15,7 +15,11 @@ export function removeTdParas(markdownChunks: MarkdownNodes) { for (let pos = 0; pos < chunk.children.length; pos++) { const child = chunk.children[pos]; if (child.isTag && child.tag === 'P') { - chunk.children.splice(pos, 1, ...child.children, markdownChunks.createNode('BR/')); + const br = markdownChunks.createNode('BR/'); + br.comment = 'removeTdParas.ts: Break after removed td paragraph'; + chunk.children.splice(pos, 1, ...child.children, br); + pos--; + continue; } } @@ -30,7 +34,7 @@ export function removeTdParas(markdownChunks: MarkdownNodes) { } }, {}, (chunk) => { if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = false; + inHtml--; return; } }); diff --git a/test/odt_md/block-macro.md b/test/odt_md/block-macro.md index 9c942030..c2df6703 100644 --- a/test/odt_md/block-macro.md +++ b/test/odt_md/block-macro.md @@ -17,7 +17,6 @@ Ensure all participating provider profiles are edited with the correct National {{% /warning %}} 3. After the page loads, use the Opt In section to begin enrolling users, accordingly. Begin typing into the User autocomplete each name of the user(s)/provider(s) being enrolled. Multiple users/providers may be enrolled at one time. - 4. If enrolling in a specific program, or one of the sponsored incentive programs, specify the Start/End Dates of the intended performance period. Otherwise, leave blank. 5. Next, from the Available Measures table, select all measures needing to be tracked, specific to the relevant program(s) opted into by the enrolled provider(s). Filter the columns, as needed. diff --git a/test/odt_md/example-document.md b/test/odt_md/example-document.md index c051270a..07b1c4fd 100644 --- a/test/odt_md/example-document.md +++ b/test/odt_md/example-document.md @@ -15,7 +15,6 @@ Some normal text with hyperlinks to a [website](https://www.enterprisehealth.com ### Heading level 3 - with a table - @@ -28,7 +27,7 @@ Some normal text with hyperlinks to a [website](https://www.enterprisehealth.com -
    Heading 1Cell 1 Cell 2 Cell 3Cell 4 +Cell 4
    @@ -79,7 +78,6 @@ This is monospaced text. This should line up | Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and *Markdown Here* -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. *Markdown Here* supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the [highlight.js demo page](http://softwaremaniacs.org/media/soft/highlight/test.html). - ### Typescript / Javascript {{% markdown %}} @@ -102,7 +100,6 @@ myArray.forEach(() => { }); // fat arrow syntax ``` {{% /markdown %}} - ## Video From Youtube: diff --git a/test/odt_md/list-indent.md b/test/odt_md/list-indent.md index 313ccba6..22c15989 100644 --- a/test/odt_md/list-indent.md +++ b/test/odt_md/list-indent.md @@ -7,8 +7,8 @@ 1. Action Name: Required field. The Action Name is usually the name of a test/procedure that is the component/action of the panel. The name will be displayed listings and dialogues throughout the system. 2. Lead Time: The Lead Time translates to the number of days prior to the Trigger Date the panel action becomes visible and is created within the system. This defines how many days before the Trigger Date that the panel/orders will populate on the Due List. Keep Lead Times consistent when setting multiple action items in a panel; otherwise, each component of the panel will have different Due Dates if there are different Lead Times on each. Emails can be configured to send email notifications, as needed, with a list of associated charts/employees that will be due. The recipient has the time between receiving the email and the panel action Trigger Date to notify Health Services of any issues or mistakes with the list. Emails to the member/chart will not be sent until the actual Trigger Date. (Email reminders are separately configured on a per client basis. Email notification may not apply to all clients). -{{% tip %}} -If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. +{{% tip %}} +If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. ![](1000020100000311000001824A182983854F26CB.png) {{% /tip %}} @@ -32,9 +32,8 @@ If the panel action is for a type of exposure, users will not want to set any Le 1. Current Panel Only: This is a checkbox that is associated with the Valid For field. If checked, this panel action will be triggered, regardless of whether the same encounter or procedure was completed for a different panel. For example, if a panel member is included in both the Asbestos panel and Benzene panel, and both require a Chest Xray, then {{% system-name %}} would (by default) only populate Chest Xray once on the Due List. With the Current Panel Only option selected, in this example, the Chest Xray will display twice, once for each panel. 11. Grace Period: Day, Weeks, Months, Years. Use the drop-down to define how much time the panel member is allotted to complete the panel action, from the time it is visible till the time it is considered overdue. Periodic email notifications can be set up with scheduled jobs, if preferred. The Grace Period is before the Due Date, meaning the Grace Period is the amount of time before the Due Date that the invitations, emails, and questionnaire become available. The panel member gets notified at the point of the Grace Period plus Lead Time. -{{% note %}} -Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. +{{% note %}} +Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. {{% /note %}} 12. Instructions: Free text instructions for a provider to perform this action item, if necessary. Could be instructions or pass/fail criteria, etc. - diff --git a/test/odt_md/list-test.md b/test/odt_md/list-test.md index 4f26f8f2..4a9d1437 100644 --- a/test/odt_md/list-test.md +++ b/test/odt_md/list-test.md @@ -13,8 +13,8 @@ Action items that are configured with a Trigger Date of **Prior Action (Complet 3. level2_3 4. level2_4 -{{% tip %}} -If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. +{{% tip %}} +If the panel action is for a type of exposure, users will not want to set any Lead Time days. Lead Time is not needed for an exposure type panel action. {{% /tip %}} 5. level2_5 @@ -35,8 +35,8 @@ If the panel action is for a type of exposure, users will not want to set any Le 13. level2_13 14. level2_14 -{{% note %}} -Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. +{{% note %}} +Health Questionnaires (if being done electronically and via portal) would be an Encounter event type and the specific electronic encounter order item would need selected (the order item that points to the electronic health questionnaire layout). For every questionnaire that users want documented electronically, via an encounter, two (2) order items and panel actions are needed; that's one (1) for the Health Questionnaire electronic encounter and the other (1) for the Due List item, in order to mark Complete. {{% /note %}} 15. level2_15 diff --git a/test/odt_md/pre-mie.md b/test/odt_md/pre-mie.md index 66e13ab7..5b72a4e0 100644 --- a/test/odt_md/pre-mie.md +++ b/test/odt_md/pre-mie.md @@ -2,6 +2,7 @@ {{% pre language="html" theme="RDark" %}} ``` + https://webchartnow.com/fhirr4sandbox/webchart.cgi/fhir/CarePlan/11 ``` @@ -38,5 +39,6 @@ https://webchartnow.com/fhirr4sandbox/webchart.cgi/fhir/CarePlan/11 }, "status": "completed" } + ``` {{% /pre %}} From cda9b63afc52ba7745678d4ed49f614fbfa8c87c Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 15:49:26 +0200 Subject: [PATCH 10/18] Improve markdown processing --- src/odt/MarkdownNodes.ts | 49 +---- src/odt/OdtToMarkdown.ts | 86 +------- src/odt/StateMachine.ts | 211 -------------------- src/odt/markdownNodesUtils.ts | 58 +++--- src/odt/postprocess/applyRewriteRules.ts | 83 ++++++++ src/odt/postprocess/fixBoldItalic.ts | 4 +- src/odt/postprocess/mergeParagraphs.ts | 145 +------------- src/odt/postprocess/postProcess.ts | 6 +- src/odt/postprocess/postProcessPreMacros.ts | 36 +--- src/odt/postprocess/removeMarkdownMacro.ts | 2 +- src/odt/postprocess/rewriteHeaders.ts | 2 +- test/odt_md/rewrite-rules.md | 1 - 12 files changed, 137 insertions(+), 546 deletions(-) delete mode 100644 src/odt/StateMachine.ts create mode 100644 src/odt/postprocess/applyRewriteRules.ts diff --git a/src/odt/MarkdownNodes.ts b/src/odt/MarkdownNodes.ts index b9c2c351..91ba3fe7 100644 --- a/src/odt/MarkdownNodes.ts +++ b/src/odt/MarkdownNodes.ts @@ -1,6 +1,5 @@ import {ListStyle, Style} from './LibreOffice.ts'; import {fixCharacters} from './utils.ts'; -import {RewriteRule} from './applyRewriteRule.ts'; import {chunksToText} from './markdownNodesUtils.ts'; export type OutputMode = 'md' | 'html' | 'raw'; @@ -18,10 +17,6 @@ export type TAG = 'BODY' | 'HR/' | 'B' | 'I' | 'BI' | 'BLANK/' | // | '/B' | '/I 'EMB_SVG_TSPAN' | // | '/EMB_SVG_TSPAN' 'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; -export const isSelfClosing = (tag: TAG) => tag.endsWith('/'); -// export const isOpening = (tag: TAG) => !tag.startsWith('/') && !tag.endsWith('/'); -// export const isClosing = (tag: TAG) => tag.startsWith('/'); - export interface TagPayload { lang?: string; position?: number; @@ -101,17 +96,8 @@ export class MarkdownNodes { return node; } - // get length() { - // return this.chunks.length; - // } - - // push(s: MarkdownChunk) { - // this.chunks.push(s); - // } - - toString(rules: RewriteRule[] = []) { - // console.log(this.chunks.map(c => debugChunkToText(c)).join('\n')); - return chunksToText(this.body.children, { rules, mode: 'md', addLiIndents: true }) + toString() { + return chunksToText(this.body.children, { mode: 'md', addLiIndents: true }) .split('\n') .map(line => line.trim().length > 0 ? line : '') .join('\n'); @@ -128,37 +114,6 @@ export class MarkdownNodes { } } - removeChunk(start: number, deleteCount = 1, comment = '') { - throw new Error('TODO remove'); - this.body.children.splice(start, deleteCount); - /*, { - mode: 'raw', - isTag: true, - tag: 'BLANK/', - payload: {}, - children: [] - comment - } - */ - for (let i = start; i < this.body.children.length; i++) { - const chunk = this.body.children[i]; - if (chunk.isTag) { - chunk.payload.position -= deleteCount; - } - } - } - - // findNext(tag: TAG, start: number) { - // let nextTagPosition = -1; - // for (let idx = start + 1; idx < this.chunks.length; idx++) { - // const chunk = this.chunks[idx]; - // if (chunk.isTag && chunk.mode === 'md' && chunk.tag === tag) { - // nextTagPosition = idx; - // break; - // } - // } - // return nextTagPosition; - // } append(parent: MarkdownTagNode, child: MarkdownTagNode) { parent.children.push(child); child.parent = parent; diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index a593d12d..066af47b 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -61,7 +61,6 @@ function getInnerText(span: TextSpan) { export class OdtToMarkdown { public errors: string[] = []; - // private readonly stateMachine: StateMachine; private readonly styles: { [p: string]: Style } = {}; public readonly links: Set = new Set(); private readonly chunks: MarkdownNodes = new MarkdownNodes(); @@ -69,8 +68,6 @@ export class OdtToMarkdown { private rewriteRules: RewriteRule[] = []; constructor(private document: DocumentContent, private documentStyles: DocumentStyles, private fileNameMap: FileNameMap = {}) { - - // this.stateMachine = new StateMachine(this.chunks); } getStyle(styleName: string): Style { @@ -122,8 +119,6 @@ export class OdtToMarkdown { const listLevels = Object.keys(listLevelsObj); listLevels.sort((a, b) => inchesToPixels(a) - inchesToPixels(b)); - // this.stateMachine.setListLevels(listLevels); // TODO - for (const tableOfContent of this.document.body.text.list) { if (tableOfContent.type === 'toc') { await this.tocToText(this.chunks.body, tableOfContent); @@ -134,9 +129,9 @@ export class OdtToMarkdown { // text = this.processMacros(text); // text = this.fixBlockMacros(text); - await postProcess(this.chunks); + await postProcess(this.chunks, this.rewriteRules); - const markdown = this.chunks.toString(this.rewriteRules); + const markdown = this.chunks.toString(); const trimmed = this.trimBreaks(markdown); return postProcessText(trimmed); } @@ -177,11 +172,9 @@ export class OdtToMarkdown { const tocNode = this.chunks.createNode('TOC', {}); this.chunks.append(currentTagNode, tocNode); - // this.stateMachine.pushTag('TOC'); for (const paragraph of tableOfContent.indexBody.list) { await this.paragraphToText(tocNode, paragraph); } - // this.stateMachine.pushTag('/TOC'); } async spanToText(currentTagNode: MarkdownTagNode, span: TextSpan): Promise { @@ -226,20 +219,6 @@ export class OdtToMarkdown { break; } } - - // if (style.textProperties?.fontStyle === 'italic' && style.textProperties?.fontWeight === 'bold') { - // this.stateMachine.pushTag('/BI'); - // } else - // if (style.textProperties?.fontStyle === 'italic') { - // this.stateMachine.pushTag('/I'); - // } else - // if (style.textProperties?.fontWeight === 'bold') { - // this.stateMachine.pushTag('/B'); - // } - // - // if (COURIER_FONTS.indexOf(style.textProperties.fontName) > -1) { - // this.stateMachine.pushTag('/CODE'); - // } } addLink(href: string) { @@ -257,7 +236,6 @@ export class OdtToMarkdown { this.addLink(href); - // this.stateMachine.pushTag('A', { href: href }); const block = this.chunks.createNode('A', { href: href }); this.chunks.append(currentTagNode, block); currentTagNode = block; @@ -275,8 +253,6 @@ export class OdtToMarkdown { break; } } - - // this.stateMachine.pushTag('/A', { href: href }); } async drawCustomShape(currentTagNode: MarkdownTagNode, drawCustomShape: DrawCustomShape) { @@ -316,7 +292,6 @@ export class OdtToMarkdown { const blockSvgText = this.chunks.createNode('EMB_SVG_TEXT'); this.chunks.append(blockSvg, blockSvgText); - // this.stateMachine.pushTag('EMB_SVG_TEXT'); for (const child of paragraph.list) { if (typeof child === 'string') { this.chunks.appendText(currentTagNode, child); @@ -352,16 +327,11 @@ export class OdtToMarkdown { } } } - - // this.stateMachine.pushTag('/EMB_SVG_TSPAN'); break; } } - // this.stateMachine.pushTag('/EMB_SVG_TEXT'); } } - - // this.stateMachine.pushTag('/EMB_SVG'); } async drawGToText(currentTagNode: MarkdownTagNode, drawG: DrawG) { @@ -428,11 +398,9 @@ export class OdtToMarkdown { if (imageLink.endsWith('.svg')) { const node = this.chunks.createNode('SVG/', { href: imageLink, alt: altText }); this.chunks.append(currentTagNode, node); - // this.stateMachine.pushTag(``); } else { const node = this.chunks.createNode('IMG/', { href: imageLink, alt: altText }); this.chunks.append(currentTagNode, node); - // this.stateMachine.pushTag(`![${altText}](${imageLink})`); } } } @@ -538,22 +506,8 @@ export class OdtToMarkdown { currentTagNode.tag = 'PRE'; } -/* switch (this.top.mode) { - case 'html': - this.stateMachine.pushTag('P'); - break; - case 'md': - if (paragraph.styleName) { - const spaces = inchesToSpaces(style.paragraphProperties?.marginLeft); - if (spaces) { - this.stateMachine.pushTag(spaces); - } - } - }*/ - if (!this.isCourier(paragraph.styleName)) { if (style.textProperties?.fontWeight === 'bold') { - // this.stateMachine.pushTag('B'); const block = this.chunks.createNode('B', {}); this.chunks.append(currentTagNode, block); currentTagNode = block; @@ -589,7 +543,6 @@ export class OdtToMarkdown { const span2 = Object.assign({}, span); span2.styleName = ''; await this.spanToText(codeBlock, span2); - // this.stateMachine.pushTag('/CODE'); } else { await this.spanToText(currentTagNode, span); } @@ -631,38 +584,9 @@ export class OdtToMarkdown { break; } } - - // if (!this.isCourier(paragraph.styleName)) { - // if (style.textProperties?.fontWeight === 'bold') { - // this.stateMachine.pushTag('/B'); - // } - // } - // - // if (onlyCodeChildren) { - // this.stateMachine.pushTag('/PRE'); - // } - // - // if (this.hasStyle(paragraph, 'Heading_20_1')) { - // this.stateMachine.pushTag('/H1'); - // } else - // if (this.hasStyle(paragraph, 'Heading_20_2')) { - // this.stateMachine.pushTag('/H2'); - // } else - // if (this.hasStyle(paragraph, 'Heading_20_3')) { - // this.stateMachine.pushTag('/H3'); - // } else - // if (this.hasStyle(paragraph, 'Heading_20_4')) { - // this.stateMachine.pushTag('/H4'); - // } else - // if (this.isCourier(paragraph.styleName)) { - // this.stateMachine.pushTag('/PRE'); - // } else { - // this.stateMachine.pushTag('/P'); - // } } async tableCellToText(currentTagNode: MarkdownTagNode, tableCell: TableCell): Promise { - // this.stateMachine.pushTag('TD'); // colspan const block = this.chunks.createNode('TD'); this.chunks.append(currentTagNode, block); currentTagNode = block; @@ -680,32 +604,27 @@ export class OdtToMarkdown { break; } } - // this.stateMachine.pushTag('/TD'); } async tableRowToText(currentTagNode: MarkdownTagNode, tableRow: TableRow): Promise { - // this.stateMachine.pushTag('TR'); const block = this.chunks.createNode('TR'); this.chunks.append(currentTagNode, block); currentTagNode = block; for (const tableCell of tableRow.cells) { await this.tableCellToText(currentTagNode, tableCell); } - // this.stateMachine.pushTag('/TR'); } async tableToText(currentTagNode: MarkdownTagNode, table: TableTable): Promise { const blockHtml = this.chunks.createNode('HTML_MODE/'); this.chunks.append(currentTagNode, blockHtml); - // this.stateMachine.pushTag('TABLE'); const block = this.chunks.createNode('TABLE'); this.chunks.append(blockHtml, block); currentTagNode = block; for (const tableRow of table.rows) { await this.tableRowToText(currentTagNode, tableRow); } - // this.stateMachine.pushTag('/TABLE'); } async listToText(currentTagNode: MarkdownTagNode, list: TextList): Promise { @@ -756,7 +675,6 @@ export class OdtToMarkdown { setRewriteRules(rewriteRules: RewriteRule[]) { this.rewriteRules = rewriteRules; - // this.stateMachine.setRewriteRules(rewriteRules); } pushError(error: string) { diff --git a/src/odt/StateMachine.ts b/src/odt/StateMachine.ts deleted file mode 100644 index 5d36c0b0..00000000 --- a/src/odt/StateMachine.ts +++ /dev/null @@ -1,211 +0,0 @@ -import {MarkdownNodes, OutputMode, TAG, TagPayload} from './MarkdownNodes.ts'; -import {RewriteRule} from './applyRewriteRule.ts'; - -interface TagLeaf { - mode: OutputMode; - level: number; - tag: TAG; - payload: TagPayload; -} - -export class StateMachine { - private readonly tagsTree: TagLeaf[] = []; - private rewriteRules: RewriteRule[] = []; - - currentMode: OutputMode = 'md'; - - constructor(public markdownChunks: MarkdownNodes) { - } - - get parentLevel() { - if (this.tagsTree.length > 1) { - return this.tagsTree[this.tagsTree.length - 2]; - } - } - - get currentLevel() { - if (this.tagsTree.length > 0) { - return this.tagsTree[this.tagsTree.length - 1]; - } - } - - async pushTag(tag: TAG, payload: TagPayload = {}) { - payload.position = this.markdownChunks.length; - - // PRE-PUSH-PRE-TREEPUSH - - // if (isOpening(tag)) { - // this.tagsTree.push({ - // mode: this.currentMode, - // tag, - // payload, - // level: this.tagsTree.length - // }); - // } - // - // // PRE-PUSH-AFTER-TREEPUSH - // - // // PUSH - // - // this.markdownChunks.push({ - // isTag: true, - // mode: this.currentMode, - // tag: tag, - // payload, - // comment: 'StateMachine.ts: pushTag' - // }); - - // POST-PUSH-BEFORE-TREEPOP - -// Quarantine. -// TODO: Remove after 2024.10.03 -// Related: code-links.md -// if (tag === '/CODE') { -// switch (this.currentMode) { -// case 'md': -// if (this.parentLevel?.tag === 'P' || !this.parentLevel) { -// const matchingPos = this.currentLevel.payload.position; // this.parentLevel.payload.position + 1 -// const afterPara = this.markdownChunks.chunks[matchingPos]; -// if (afterPara.isTag === true && afterPara.tag === 'CODE') { -// afterPara.tag = 'PRE'; -// this.markdownChunks.chunks[this.markdownChunks.length - 1] = { -// isTag: true, -// mode: this.currentMode, -// tag: '/PRE', -// payload -// }; -// } -// } -// } -// } - - // REFACT - // if (this.currentMode === 'md' && tag === '/TABLE') { - // for (let pos = this.currentLevel.payload.position; pos < payload.position + 1; pos++) { - // const chunk = this.markdownChunks.chunks[pos]; - // chunk.mode = 'html'; - // } - // } - // /REFACT - - // REFACT - // if (tag === 'PRE') { - // const prevTag = this.markdownChunks.chunks[payload.position - 1]; - // if (prevTag.isTag && prevTag.tag === '/PRE') { - // this.markdownChunks.removeChunk(payload.position - 1); - // this.markdownChunks.chunks[payload.position] = { - // isTag: true, - // mode: this.currentMode, - // tag: 'BR/', - // payload: {}, - // comment: 'StateMachine.ts: Merging PRE tags' - // }; - // } - // } - // /REFACT - - // REFACT - // if (tag === 'B' && ['H1', 'H2', 'H3', 'H4', 'BI'].indexOf(this.parentLevel?.tag) > -1) { - // this.markdownChunks.removeChunk(payload.position); - // } - // if (tag === '/B' && ['H1', 'H2', 'H3', 'H4', '/BI'].indexOf(this.parentLevel?.tag) > -1) { - // this.markdownChunks.removeChunk(payload.position); - // } - // /REFACT - - // REFACT - // if (tag === 'P' && this.parentLevel?.tag === 'TD') { - // this.markdownChunks.removeChunk(payload.position); - // } - // if (tag === '/P' && this.parentLevel?.tag === 'TD') { - // this.markdownChunks.chunks[payload.position] = { - // isTag: true, - // mode: this.currentMode, - // tag: 'BR/', - // payload: {} - // }; - // } - // if (tag === '/TD') { - // const prevChunk = this.markdownChunks.chunks[payload.position - 1]; - // if (prevChunk.isTag && prevChunk.tag === 'BR/') { - // this.markdownChunks.removeChunk(payload.position - 1); - // } - // } - // /REFACT - - // REFACT - // if (tag === '/I') { - // const innerTxt = extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); - // if (innerTxt.startsWith('{{%') && innerTxt.endsWith('%}}')) { - // this.markdownChunks.removeChunk(payload.position); - // this.markdownChunks.removeChunk(this.currentLevel.payload.position); - // } - // } - // /REFACT - - // if (tag === 'HTML_MODE/') { - // this.currentMode = 'html'; - // } - // if (tag === 'MD_MODE/') { - // this.currentMode = 'md'; - // } - - // REFACT - // if (['/H1', '/H2', '/H3', '/H4'].includes(tag) && 'md' === this.currentMode) { - // if (this.currentLevel.payload.bookmarkName) { - // const innerTxt = extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); - // const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); - // if (slug) { - // this.headersMap[this.currentLevel.payload.bookmarkName] = slug; - // } - // } - // } - // /REFACT - - // if (tag === '/P' || tag === '/PRE') { - // const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); - // switch (this.currentMode) { - // case 'raw': - // { - // if (innerTxt === '{{/rawhtml}}' || isMarkdownEndMacro(innerTxt)) { - // // this.markdownChunks[payload.position].comment = 'Switching to md - isMarkdownEndMacro'; - // this.currentMode = 'md'; - // } - // } - // break; - // case 'md': - // { - // if (innerTxt === '{{rawhtml}}' || isMarkdownBeginMacro(innerTxt)) { - // // this.markdownChunks[payload.position].comment = 'Switching to raw - isMarkdownBeginMacro'; - // this.currentMode = 'raw'; - // } - // - // } - // break; - // } - // } - - // REFACT - // if (tag === '/CODE') { - // const innerTxt = await extractText(this.markdownChunks.body, this.currentLevel.payload.position, payload.position, this.rewriteRules); - // switch (this.currentMode) { - // case 'md': - // if (isMarkdownMacro(innerTxt)) { - // this.markdownChunks.replace(this.currentLevel.payload.position, payload.position, { - // isTag: false, - // mode: this.currentMode, - // text: stripMarkdownMacro(innerTxt), - // comment: 'StateMachine.ts: replace code part with stripped macro' - // }); - // } - // } - // } - // /REFACT - - // if (isClosing(tag)) { - // this.tagsTree.pop(); - // } - - // POST-PUSH-AFTER-TREEPOP - } -} diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts index 179b5b2c..dc77d176 100644 --- a/src/odt/markdownNodesUtils.ts +++ b/src/odt/markdownNodesUtils.ts @@ -1,6 +1,5 @@ import {Style, TextProperty} from './LibreOffice.ts'; import {inchesToPixels, spaces} from './utils.ts'; -import {applyRewriteRule, RewriteRule} from './applyRewriteRule.ts'; import {type MarkdownNode, MarkdownTagNode, OutputMode, TagPayload} from './MarkdownNodes.ts'; export function debugChunkToText(chunk: MarkdownNode) { @@ -82,7 +81,6 @@ function buildSvgStart(payload: TagPayload) { interface ToTextContext { mode: OutputMode; - rules: RewriteRule[]; onlyNotTag?: boolean; inListItem?: boolean; addLiIndents?: boolean; @@ -180,7 +178,7 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean, pare } function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { - ctx = Object.assign({ rules: [], mode: 'md' }, ctx); + ctx = Object.assign({ mode: 'md' }, ctx); if (chunk.isTag === false) { return chunk.text; @@ -358,14 +356,15 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string { const retVal = []; - ctx = Object.assign({ rules: [], mode: 'md' }, ctx); + ctx = Object.assign({ rewriteRule: [], mode: 'md' }, ctx); for (let chunkNo = 0; chunkNo < chunks.length; chunkNo++) { const chunk = chunks[chunkNo]; + /* if ('tag' in chunk && ['SVG/', 'IMG/'].includes(chunk.tag)) { let broke = false; - for (const rule of ctx.rules) { + for (const rule of ctx.rewriteRule) { const { shouldBreak, text } = applyRewriteRule(rule, { ...chunk, mode: 'TODO', // TODO @@ -381,7 +380,7 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string } if (broke) { - return retVal.join('');; + return retVal.join(''); } } @@ -399,7 +398,7 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string // if (matchingNo !== -1) { const alt = chunksToText(chunk.children, { ...ctx, onlyNotTag: true }); // .filter(i => !i.isTag) let broke = false; - for (const rule of ctx.rules) { + for (const rule of ctx.rewriteRule) { const { shouldBreak, text } = applyRewriteRule(rule, { ...chunk, mode: 'TODO', // TODO @@ -414,12 +413,15 @@ export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string } } - // if (broke) { - // chunks.splice(chunkNo, matchingNo - chunkNo); + // console.log('XXXXXXXXXXXXXXXXx', alt, broke, retVal); + if (broke) { + return retVal.join(''); + // retVal.splice(chunkNo, matchingNo - chunkNo); // return retVal; - // } + } // } } +*/ retVal.push(chunkToText(chunk, ctx)); } @@ -470,45 +472,45 @@ export function walkRecursiveSync(node: MarkdownNode, callback: (node: MarkdownN } } -export async function extractText(node: MarkdownNode) { +export function extractText(node: MarkdownNode) { let retVal = ''; - await walkRecursiveAsync(node, async (child) => { + walkRecursiveSync(node, (child) => { if (child.isTag === false) { retVal += child.text; return; } if (child.isTag === true) { if (['BR/', 'EOL/', 'EMPTY_LINE/'].includes(child.tag)) { - + retVal += '\n'; } return; } - }); return retVal; } -/* -export function extractText(node: MarkdownTagNode, start: number, end: number, rules: RewriteRule[] = []) { - const slice = chunksToText(node.children.slice(start, end).filter(i => !i.isTag || ['BR/', 'EOL/', 'EMPTY_LINE/'].includes(i.tag)), rules); - return slice; -} -*/ - export function dump(body: MarkdownTagNode, logger = console) { let position = 0; + const stack = []; + walkRecursiveSync(body, (chunk, ctx: { level: number }) => { let line = position + '\t'; - switch (chunk.mode) { - case 'md': + if (chunk.isTag && ['HTML_MODE/', 'RAW_MODE/'].includes(chunk.tag)) { + stack.push(chunk.tag); + } + + const mode = stack[stack.length - 1] || 'MARK_DOWN/'; + + switch (mode) { + case 'MARK_DOWN/': line += 'M '; break; - case 'html': + case 'HTML_MODE/': line += 'H '; break; - case 'raw': + case 'RAW_MODE/': line += 'R '; break; } @@ -553,5 +555,9 @@ export function dump(body: MarkdownTagNode, logger = console) { position++; return { ...ctx, level: ctx.level + 1 }; - }, { level: 0 }); + }, { level: 0 }, (chunk) => { + if (chunk.isTag && ['HTML_MODE/', 'RAW_MODE/'].includes(chunk.tag)) { + stack.pop(); + } + }); } diff --git a/src/odt/postprocess/applyRewriteRules.ts b/src/odt/postprocess/applyRewriteRules.ts new file mode 100644 index 00000000..2bba7080 --- /dev/null +++ b/src/odt/postprocess/applyRewriteRules.ts @@ -0,0 +1,83 @@ +import {MarkdownNodes, MarkdownTextNode} from '../MarkdownNodes.js'; +import {chunksToText, walkRecursiveSync} from '../markdownNodesUtils.js'; +import {applyRewriteRule, RewriteRule} from '../applyRewriteRule.js'; + +export function applyRewriteRules(markdownChunks: MarkdownNodes, rewriteRule: RewriteRule[] = []) { + let inHtml = 0; + walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml++; + return; + } + + if ('tag' in chunk && ['SVG/', 'IMG/'].includes(chunk.tag)) { + let broke = false; + for (const rule of rewriteRule) { + const { shouldBreak, text } = applyRewriteRule(rule, { + ...chunk, + mode: inHtml ? 'html' : 'md', + href: 'payload' in chunk ? chunk.payload?.href : undefined, + alt: 'payload' in chunk ? chunk.payload?.alt : undefined + }); + + const textNode: MarkdownTextNode = { + isTag: false, + text, + parent: undefined, + comment: 'MarkdownNodes.ts: appendText' + }; + chunk.parent.children.splice(ctx.nodeIdx, 1, textNode); + + if (shouldBreak) { + // retVal.push(text); + broke = true; + break; + } + } + + if (broke) { + // return retVal.join(''); + } + } + + if ('tag' in chunk && 'A' === chunk.tag) { + const alt = chunksToText(chunk.children, { ...ctx, mode: 'md', onlyNotTag: true }); + let broke = false; + for (const rule of rewriteRule) { + const { shouldBreak, text } = applyRewriteRule(rule, { + ...chunk, + mode: inHtml ? 'html' : 'md', + href: 'payload' in chunk ? chunk.payload?.href : undefined, + alt + }); + + const textNode: MarkdownTextNode = { + isTag: false, + text, + parent: undefined, + comment: 'MarkdownNodes.ts: appendText' + }; + chunk.parent.children.splice(ctx.nodeIdx, 1, textNode); + + if (shouldBreak) { + // retVal.push(text); + broke = true; + break; + } + } + + if (broke) { + // return retVal.join(''); + // retVal.splice(chunkNo, matchingNo - chunkNo); + // return retVal; + } + } + + }, {}, (chunk) => { + if (chunk.isTag && chunk.tag === 'HTML_MODE/') { + inHtml--; + return; + } + }); + +} diff --git a/src/odt/postprocess/fixBoldItalic.ts b/src/odt/postprocess/fixBoldItalic.ts index 00ae3ae5..d44ccd1f 100644 --- a/src/odt/postprocess/fixBoldItalic.ts +++ b/src/odt/postprocess/fixBoldItalic.ts @@ -27,12 +27,10 @@ export async function fixBoldItalic(markdownChunks: MarkdownNodes) { } if (chunk.isTag === true && ['I'].includes(chunk.tag)) { - const innerTxt = await extractText(chunk); + const innerTxt = extractText(chunk); if (innerTxt.startsWith('{{%') && innerTxt.endsWith('%}}')) { chunk.parent.children.splice(ctx.nodeIdx, 1, ...chunk.children); return { nodeIdx: ctx.nodeIdx - 1 }; - // this.markdownChunks.removeChunk(payload.position); - // this.markdownChunks.removeChunk(this.currentLevel.payload.position); } } }); diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index ea5f52b9..a2c415d9 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,7 +1,5 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; -// import {isBeginMacro, isEndMacro, isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.ts'; -import {walkRecursiveSync} from '../markdownNodesUtils.js'; - +import {extractText, walkRecursiveSync} from '../markdownNodesUtils.js'; export function mergeParagraphs(markdownChunks: MarkdownNodes) { @@ -22,9 +20,14 @@ export function mergeParagraphs(markdownChunks: MarkdownNodes) { const children = nextChunk.children.splice(0, nextChunk.children.length); if (chunk.tag === 'P') { - const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); - emptyLine.comment = 'mergeParagraphs.ts: empty line between two of: ' + chunk.tag; - children.unshift(emptyLine); + const temp = markdownChunks.createNode('P'); + temp.children.splice(0, 0, ...children); + const innerTxt = extractText(temp); + if (!innerTxt.startsWith('{{/rawhtml}}')) { + const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); + emptyLine.comment = 'mergeParagraphs.ts: empty line between two of: ' + chunk.tag; + children.unshift(emptyLine); + } } if (chunk.tag === 'PRE' && nextChunk.payload?.lang) { @@ -43,134 +46,4 @@ export function mergeParagraphs(markdownChunks: MarkdownNodes) { return; } }); - - -/* - let previousParaOpening = 0; - const macros = []; - - function findFirstTextAfterPos(start: number): string | null { - for (let pos = start + 1; pos < markdownChunks.chunks.length; pos++) { - const currentChunk = markdownChunks.chunks[pos]; - if ('text' in currentChunk && currentChunk.text.trim() !== '') { - return currentChunk.text; - } - } - return null; - } - - for (let position = 0; position < markdownChunks.length - 1; position++) { - const chunk = markdownChunks.chunks[position]; - - if (chunk.isTag && chunk.mode === 'md' && chunk.tag === 'P') { - previousParaOpening = position; - continue; - } - - if (chunk.isTag === false && chunk.mode === 'md' && isBeginMacro(chunk.text)) { - macros.push(chunk.text); - continue; - } - - if (chunk.isTag === false && chunk.mode === 'md' && isEndMacro(chunk.text)) { - continue; - } - - if (chunk.isTag && chunk.mode === 'md' && chunk.tag === '/P') { - const nextChunk = markdownChunks.chunks[position + 1]; - - if (macros.length > 0) { - addComment(chunk, 'mergeParagraphs.ts: macros.length > 0'); - macros.splice(0, macros.length); - continue; - } - - if (nextChunk.isTag && nextChunk.mode === 'md' && nextChunk.tag === 'P') { - const nextParaOpening = markdownChunks.findNext('P', position); - const nextParaClosing = markdownChunks.findNext('/P', position); - - if (nextParaOpening > 0 && nextParaOpening < nextParaClosing) { - const innerText = extractText(markdownChunks.body, nextParaOpening, nextParaClosing, rewriteRules); - if (innerText.length === 0) { - // markdownChunks.chunks.splice(nextParaOpening, nextParaClosing - nextParaOpening + 1, { - // isTag: true, - // tag: 'BR/', - // mode: 'md', - // comment: 'Converted empty paragraph to BR/', - // payload: {} - // }); - // position--; - // continue; - } - } - - if (previousParaOpening > 0) { - const innerText = extractText(markdownChunks.body, previousParaOpening, position, rewriteRules); - if (innerText.length === 0) { - //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); - markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { - mode: 'md', - isTag: true, - tag: 'EMPTY_LINE/', - payload: {}, - comment: 'mergeParagraphs.ts: convert empty paragraph to EMPTY_LINE/' - }); - position--; - continue; - } - if (innerText.endsWith(' %}}')) { - addComment(chunk, 'mergeParagraphs.ts: innerText.endsWith(\' %}}\')'); - continue; - } - } - - const nextText = findFirstTextAfterPos(position); - if (nextText === '* ' || nextText?.trim().length === 0) { - markdownChunks.chunks.splice(position, 2, { - isTag: true, - tag: 'EOL/', - mode: 'md', - comment: 'mergeParagraphs.ts: End of line, but next line is list', - payload: {} - }); - position--; - previousParaOpening = 0; - } else { - const prevTag = markdownChunks.chunks[position - 1]; - if (prevTag.isTag && prevTag.tag === 'EMPTY_LINE/') { - markdownChunks.chunks.splice(position, 2); - } else { - markdownChunks.chunks.splice(position, 2, { - isTag: true, - tag: 'BR/', - mode: 'md', - payload: {}, - comment: 'mergeParagraphs.ts: End of line, two paras merge together' - }); - } - position--; - previousParaOpening = 0; - } - - } else { - if (previousParaOpening > 0) { - const innerText = extractText(markdownChunks.body, previousParaOpening, position, rewriteRules); - if (innerText.length === 0) { - //addComment(chunk, 'mergeParagraphs.ts: innerText.length === 0'); - markdownChunks.chunks.splice(previousParaOpening, position - previousParaOpening + 1, { - mode: 'md', - isTag: true, - tag: 'EMPTY_LINE/', - payload: {}, - comment: 'mergeParagraphs.ts: convert empty paragraph to EMPTY_LINE/' - }); - position--; - continue; - } - } - addComment(chunk, 'mergeParagraphs.ts: nextChunk is not P'); - } - } - } - */ } diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index cda2c5c7..be6356a5 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -22,8 +22,10 @@ import {mergeParagraphs} from './mergeParagraphs.ts'; import {convertToc} from './convertToc.js'; import {removeEmptyTags} from './removeEmptyTags.js'; import {removeExcessiveLines} from './removeExcessiveLines.js'; +import {applyRewriteRules} from './applyRewriteRules.js'; +import {RewriteRule} from '../applyRewriteRule.js'; -export async function postProcess(chunks: MarkdownNodes) { +export async function postProcess(chunks: MarkdownNodes, rewriteRules: RewriteRule[]) { convertToc(chunks); processListsAndNumbering(chunks); postProcessHeaders(chunks); @@ -52,6 +54,8 @@ export async function postProcess(chunks: MarkdownNodes) { removeExcessiveLines(chunks); + applyRewriteRules(chunks, rewriteRules); + if (process.env.DEBUG_COLORS) { dump(chunks.body); } diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index 9468dcde..d379ff05 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -77,7 +77,7 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { const rawMode = markdownChunks.createNode('RAW_MODE/'); rawMode.comment = 'postProcessPreMacros.ts: enter raw mode after: ' + startText; - const children = chunk.children.splice(firstChildIdx + 2, lastChildIdx - firstChildIdx - 2, rawMode); + const children = chunk.children.splice(firstChildIdx + 1, lastChildIdx - firstChildIdx - 1, rawMode); rawMode.children.splice(0, 0, ...children); @@ -135,40 +135,6 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { chunk.parent.children.splice(ctx.nodeIdx, 0, ...before); } } - -/* - if (chunk.isTag === false && isPreBeginMacro(chunk.text)) { - const prevChunk = markdownChunks.chunks[position - 1]; - if (prevChunk && prevChunk.isTag && prevChunk.tag === 'PRE') { - markdownChunks.chunks.splice(position + 1, 0, { - isTag: true, - tag: 'PRE', - mode: 'md', - payload: {} - }); - markdownChunks.removeChunk(position - 1); - position--; - continue; - } - } - }); - - for (let position = 1; position < markdownChunks.length; position++) { - const chunk = markdownChunks.chunks[position]; - - if (chunk.isTag === false && isPreEndMacro(chunk.text)) { - const postChunk = markdownChunks.chunks[position + 1]; - if (postChunk && postChunk.isTag && postChunk.tag === '/PRE') { - markdownChunks.removeChunk(position + 1); - markdownChunks.chunks.splice(position, 0, { - isTag: true, - tag: '/PRE', - mode: 'md', - payload: {} - }); - } - } - */ }, {}, (chunk) => { if (chunk.isTag && chunk.tag === 'HTML_MODE/') { inHtml = false; diff --git a/src/odt/postprocess/removeMarkdownMacro.ts b/src/odt/postprocess/removeMarkdownMacro.ts index 49f3c755..509c7179 100644 --- a/src/odt/postprocess/removeMarkdownMacro.ts +++ b/src/odt/postprocess/removeMarkdownMacro.ts @@ -15,7 +15,7 @@ export async function removeMarkdownMacro(markdownChunks: MarkdownNodes) { } if (chunk.isTag && chunk.tag === 'CODE') { - const innerTxt = await extractText(chunk); + const innerTxt = extractText(chunk); if (isMarkdownMacro(innerTxt)) { chunk.parent.children.splice(ctx.nodeIdx, 1, { isTag: false, diff --git a/src/odt/postprocess/rewriteHeaders.ts b/src/odt/postprocess/rewriteHeaders.ts index ec094619..0a5ba35d 100644 --- a/src/odt/postprocess/rewriteHeaders.ts +++ b/src/odt/postprocess/rewriteHeaders.ts @@ -8,7 +8,7 @@ export async function rewriteHeaders(markdownChunks: MarkdownNodes) { await walkRecursiveAsync(markdownChunks.body, async (chunk) => { if (chunk.isTag === true && ['H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) { // && 'md' === this.currentMode) { if (chunk.payload.bookmarkName) { - const innerTxt = await extractText(chunk); + const innerTxt = extractText(chunk); const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g }); if (slug) { headersMap['#' + chunk.payload.bookmarkName] = '#' + slug; diff --git a/test/odt_md/rewrite-rules.md b/test/odt_md/rewrite-rules.md index c5b52b8f..23bf2a3f 100644 --- a/test/odt_md/rewrite-rules.md +++ b/test/odt_md/rewrite-rules.md @@ -4,7 +4,6 @@ How to show a player: - You From 104cc6415ea4407c8c5c9ecaae0f54f244ed8e1a Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 15:53:24 +0200 Subject: [PATCH 11/18] Improve markdown processing --- src/odt/postprocess/addIndentsAndBullets.ts | 109 ------------------ src/odt/postprocess/fixListParagraphs.ts | 48 -------- src/odt/postprocess/postProcess.ts | 4 - .../postprocess/processListsAndNumbering.ts | 87 ++++---------- .../removePreWrappingAroundMacros.ts | 4 - test/odt_md/MarkDownTransform.test.ts | 1 - 6 files changed, 24 insertions(+), 229 deletions(-) delete mode 100644 src/odt/postprocess/addIndentsAndBullets.ts delete mode 100644 src/odt/postprocess/fixListParagraphs.ts diff --git a/src/odt/postprocess/addIndentsAndBullets.ts b/src/odt/postprocess/addIndentsAndBullets.ts deleted file mode 100644 index 76605795..00000000 --- a/src/odt/postprocess/addIndentsAndBullets.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {spaces} from '../utils.ts'; -import {walkRecursiveSync} from '../markdownNodesUtils.ts'; - -export function addIndentsAndBullets(markdownChunks: MarkdownNodes) { - let inHtml = false; - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = true; - return; - } - - if (inHtml) { - return; - } - - if (chunk.isTag === true && chunk.tag === 'P') { - if (!chunk.payload.listLevel) { - return; - } - - // const level = (chunk.payload.listLevel || 1) - 1; - const level = 0; - - if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { - return; - } - - // let indent = spaces(level * 4); GDocs not fully compatible - // if (chunk.payload.style?.paragraphProperties?.marginLeft) { - // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); - // } - const indent = spaces(level * 3); - const listStr = chunk.payload.bullet ? '* ' : chunk.payload.number > 0 ? `${chunk.payload.number}. ` : ''; - const firstStr = indent + listStr; - const otherStr = indent + spaces(listStr.length); - - let prevEmptyLine = 1; - for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { - const chunk2 = chunk.parent.children[position2]; - if (chunk2.isTag === true && chunk2.tag === '/P') { - position += position2 - position - 1; - break; - } - - if (chunk2.isTag === true && ['BR/'].indexOf(chunk2.tag) > -1) { - prevEmptyLine = 2; - continue; - } - - if (chunk2.isTag === false && chunk2.text.startsWith('{{% ') && chunk2.text.endsWith(' %}}')) { - const innerText = chunk2.text.substring(3, chunk2.text.length - 3); - if (innerText.indexOf(' %}}') === -1) { - continue; - } - } - - if (prevEmptyLine > 0) { - chunk.parent.children.splice(position2, 0, { - isTag: false, - text: prevEmptyLine === 1 ? firstStr : otherStr, - comment: `addIndentsAndBullets.ts: Indent (${chunk.payload.bullet ? 'bullet' : 'number ' + chunk.payload.number}), level: ` + level + ', prevEmptyLine: ' + (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) - }); - prevEmptyLine = 0; - position2++; - return; - } - } - } - }, {}, (chunk) => { - if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = false; - return; - } - }); - -/* - let lastItem = null; - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (chunk.isTag === true && chunk.tag === 'LI' && chunk.mode === 'md') { - lastItem = chunk; - } - - if (chunk.isTag === true && chunk.tag === 'IMG/' && chunk.mode === 'md') { - const level = (chunk.payload.listLevel || 1) - 1; - - if (level > 0) { - let indent = spaces(level * 3); - - if (lastItem.payload.bullet) { - indent += ' '; - } else - if (lastItem.payload.number > 0) { - indent += ' '; - } - - chunk.parent.children.splice(ctx.nodeIdx, 0, { - mode: 'md', - isTag: false, - text: indent, - comment: 'addIndentsAndBullets.ts: Indent image, level: ' + level - }); - } - return; - } - }); -*/ - -} diff --git a/src/odt/postprocess/fixListParagraphs.ts b/src/odt/postprocess/fixListParagraphs.ts deleted file mode 100644 index 564d48e8..00000000 --- a/src/odt/postprocess/fixListParagraphs.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {walkRecursiveSync} from '../markdownNodesUtils.js'; - -export function fixListParagraphs(markdownChunks: MarkdownNodes) { - - // Inside list item tags like needs to be html tags - let inHtml = false; - - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { level: number }) => { - if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = true; - return; - } - - if (inHtml) { - return; - } - - if (chunk.isTag && ['LI'].includes(chunk.tag)) { - if (chunk.children.length === 0) { - return; - } - } - }, {}, (chunk) => { - if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = false; - return; - } - }); - - /* TODO: WTF? - let nextPara = null; - for (let position = markdownChunks.length - 1; position >= 0; position--) { - const chunk = markdownChunks.chunks[position]; - if (chunk.isTag && chunk.tag === 'P') { - if (nextPara) { - if (nextPara.payload?.listLevel && !chunk.payload?.listLevel) { - chunk.payload.listLevel = nextPara?.payload?.listLevel; - } - if (!chunk.payload?.bullet && nextPara.payload?.number === chunk.payload?.number && nextPara.payload?.listLevel === chunk.payload?.listLevel) { - delete nextPara.payload.number; - } - } - nextPara = chunk; - } - } - */ -} diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index be6356a5..5fd980b4 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -7,7 +7,6 @@ import {removePreWrappingAroundMacros} from './removePreWrappingAroundMacros.ts' import {removeInsideDoubleCodeBegin} from './removeInsideDoubleCodeBegin.ts'; import {fixSpacesInsideInlineFormatting} from './fixSpacesInsideInlineFormatting.ts'; import {fixBoldItalic} from './fixBoldItalic.ts'; -import {fixListParagraphs} from './fixListParagraphs.ts'; import {hideSuggestedChanges} from './hideSuggestedChanges.ts'; import {trimParagraphs} from './trimParagraphs.ts'; import {addEmptyLinesAfterParas} from './addEmptyLinesAfterParas.ts'; @@ -31,18 +30,15 @@ export async function postProcess(chunks: MarkdownNodes, rewriteRules: RewriteRu postProcessHeaders(chunks); fixSpacesInsideInlineFormatting(chunks); await fixBoldItalic(chunks); - fixListParagraphs(chunks); hideSuggestedChanges(chunks); trimParagraphs(chunks); addEmptyLinesAfterParas(chunks); removeTdParas(chunks); // Requires: addEmptyLinesAfterParas - // addIndentsAndBullets(chunks); mergeTexts(chunks); await rewriteHeaders(chunks); - // TODO macros mergeParagraphs(chunks); removePreWrappingAroundMacros(chunks); await removeMarkdownMacro(chunks); diff --git a/src/odt/postprocess/processListsAndNumbering.ts b/src/odt/postprocess/processListsAndNumbering.ts index 672cb22c..51a85be5 100644 --- a/src/odt/postprocess/processListsAndNumbering.ts +++ b/src/odt/postprocess/processListsAndNumbering.ts @@ -1,5 +1,4 @@ import {MarkdownNodes, TagPayload} from '../MarkdownNodes.ts'; -import {spaces} from '../utils.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function processListsAndNumbering(markdownChunks: MarkdownNodes) { @@ -38,15 +37,6 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { } } - function getTopList() { - for (let i = stack.length - 1; i >=0; i--) { - if (stack[i].tag === 'LI') { - return stack[i]; - } - } - return null; - } - function getTopListStyleName(): string { for (let i = stack.length - 1; i >=0; i--) { const leaf = stack[i]; @@ -79,21 +69,10 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { return; } - // refact - // if (chunk.mode !== 'md') { - // return ; - // } - const tag = chunk.tag; const parentLevel = topElement(stack); - // TODO: Why? - // if ('IMG/' === tag) { - // const level = lastItem?.payload?.listLevel; - // chunk.payload.listLevel = level; - // } - if (['TOC', 'UL', 'LI', 'P'].includes(tag)) { stack.push({ tag, @@ -103,10 +82,6 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { return ; } - // if ('LI' === tag) { - // lastItem = chunk; - // } - const listLevel = stack.filter(item => item.tag === 'UL').length; const currentElement = topElement(stack); @@ -149,39 +124,30 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { } if ('P' === currentElement.tag) { - // const currentMode = chunk.mode; - // switch (currentMode) { - // case 'md': - // if (parentLevel?.tag === 'TOC') { - // currentElement.payload.bullet = true; - // } - - if (parentLevel?.tag === 'LI') { - - if (!margins[currentElement.payload.marginLeft]) { - margins[currentElement.payload.marginLeft] = listLevel; - } - - let level = parentLevel.payload.listLevel; - if (margins[currentElement.payload.marginLeft]) { - level = margins[currentElement.payload.marginLeft]; - } - - const listStyle = parentLevel.payload.listStyle || currentElement.payload.listStyle; - const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); - - currentElement.payload.listLevel = level; - parentLevel.payload.listLevel = level; - - if (isNumeric) { - currentElement.payload.number = parentLevel.payload.number; - } else { - currentElement.payload.bullet = true; - parentLevel.payload.bullet = true; - } - } - // break; - // } + if (parentLevel?.tag === 'LI') { + + if (!margins[currentElement.payload.marginLeft]) { + margins[currentElement.payload.marginLeft] = listLevel; + } + + let level = parentLevel.payload.listLevel; + if (margins[currentElement.payload.marginLeft]) { + level = margins[currentElement.payload.marginLeft]; + } + + const listStyle = parentLevel.payload.listStyle || currentElement.payload.listStyle; + const isNumeric = !!(listStyle?.listLevelStyleNumber && listStyle.listLevelStyleNumber.find(i => i.level == level)); + + currentElement.payload.listLevel = level; + parentLevel.payload.listLevel = level; + + if (isNumeric) { + currentElement.payload.number = parentLevel.payload.number; + } else { + currentElement.payload.bullet = true; + parentLevel.payload.bullet = true; + } + } } return { ...ctx, level: ctx.level + 1 }; @@ -190,10 +156,6 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { return; } - // if (chunk.mode !== 'md') { - // return ; - // } - const tag = chunk.tag; if ('LI' === tag) { @@ -217,5 +179,4 @@ export function processListsAndNumbering(markdownChunks: MarkdownNodes) { } }); - } diff --git a/src/odt/postprocess/removePreWrappingAroundMacros.ts b/src/odt/postprocess/removePreWrappingAroundMacros.ts index 87fc8bef..c48242ae 100644 --- a/src/odt/postprocess/removePreWrappingAroundMacros.ts +++ b/src/odt/postprocess/removePreWrappingAroundMacros.ts @@ -33,11 +33,7 @@ export function removePreWrappingAroundMacros(markdownChunks: MarkdownNodes) { firstChildIdx++; } - // console.log('CCCC', preChunk.children.map(c => ({...c, parent: undefined, children: undefined}))); - - // firstChild.parent = preChunk.parent; const before = preChunk.children.splice(0, firstChildIdx + 1); - // console.log('BBB', before); preChunk.parent.children.splice(ctx.nodeIdx, 0, ...before); } } diff --git a/test/odt_md/MarkDownTransform.test.ts b/test/odt_md/MarkDownTransform.test.ts index a7b0c35b..4cf18ac1 100644 --- a/test/odt_md/MarkDownTransform.test.ts +++ b/test/odt_md/MarkDownTransform.test.ts @@ -103,7 +103,6 @@ describe('MarkDownTransformTest', () => { it('test ./block-macro.md', async () => { const testMarkdown = fs.readFileSync(__dirname + '/block-macro.md').toString(); const markdown = await transformOdt('block-macro'); - // console.log(markdown); assert.ok(compareTexts(testMarkdown, markdown)); }); From 543d66db62518b10c4fbbdc778458994c4dba77b Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:06:32 +0200 Subject: [PATCH 12/18] Improve markdown processing --- src/odt/postprocess/postProcessPreMacros.ts | 7 +--- src/odt/postprocess/removeExcessiveLines.ts | 40 ++++++++++++++------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index d379ff05..93f191eb 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -25,13 +25,10 @@ function getMatching(startText: string) { function cleanupLines(chunk: MarkdownTagNode) { while (chunk.children.length > 0) { const child = chunk.children[0]; - if (child.isTag && child.tag === 'EMPTY_LINE/') { chunk.children.splice(0, 1); - // child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; - // chunk.parent.children.splice(ctx.nodeIdx, 0, child); + continue; } - break; } } @@ -86,8 +83,6 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { firstChildIdx = -1; cleanupLines(rawMode); - - continue; } } } diff --git a/src/odt/postprocess/removeExcessiveLines.ts b/src/odt/postprocess/removeExcessiveLines.ts index 898f7dcd..bf3bc71b 100644 --- a/src/odt/postprocess/removeExcessiveLines.ts +++ b/src/odt/postprocess/removeExcessiveLines.ts @@ -3,14 +3,14 @@ import {walkRecursiveSync} from '../markdownNodesUtils.js'; export function removeExcessiveLines(markdownChunks: MarkdownNodes) { - let inHtml = false; - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { + let inHtml = 0; + walkRecursiveSync(markdownChunks.body, (chunk) => { if (chunk.parent && chunk.parent.tag !== 'BODY') { return; } if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = true; + inHtml++; return; } @@ -21,11 +21,29 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { }, {}, (chunk, ctx: { nodeIdx: number }) => { if (chunk.isTag && chunk.tag === 'HTML_MODE/') { - inHtml = false; + inHtml--; return; } if (chunk.isTag && ['P', 'BODY'].includes(chunk.tag)) { + + for (let idx = 0; idx < chunk.children.length; idx++) { + const child = chunk.children[idx]; + if (child.isTag && child.tag === 'BR/') { + const prevChild = chunk.children[idx - 1]; + const nextChild = chunk.children[idx + 1]; + + if (nextChild && nextChild.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(nextChild.tag)) { + child.comment = 'removeExcessiveLines.ts: converted BR/ to EOL/'; + child.tag = 'EOL/'; + } else + if (prevChild && prevChild.isTag && ['EOL/', 'BR/', 'EMPTY_LINE/'].includes(prevChild.tag)) { + child.comment = 'removeExcessiveLines.ts: converted BR/ to EOL/'; + child.tag = 'EOL/'; + } + } + } + for (let idx = chunk.children.length - 1; idx > 0; idx--) { const child = chunk.children[idx]; const prevChild = chunk.children[idx - 1]; @@ -48,7 +66,6 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { const emptyLine = markdownChunks.createNode('EMPTY_LINE/'); emptyLine.comment = 'removeExcessiveLines.ts: Converted BR/ to EOL/ + EMPTY_LINE/'; chunk.children.splice(idx, 1, eol, emptyLine); - continue; } } @@ -72,7 +89,6 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { child.tag = 'EMPTY_LINE/'; child.comment = 'removeExcessiveLines.ts: converted EOL/ to EMPTY_LINE/'; idx++; - continue; } } } @@ -96,7 +112,6 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { if (child.isTag && child.tag === 'EMPTY_LINE/') { if (prevChild.isTag && prevChild.tag === 'EMPTY_LINE/') { chunk.children.splice(idx, 1); - continue; } } } @@ -106,9 +121,11 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { if (child.isTag && child.tag === 'EMPTY_LINE/') { chunk.children.splice(idx, 1); - child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; - chunk.parent.children.splice(ctx.nodeIdx + 1, 0, child); + if (chunk.tag === 'P') { + child.comment = 'removeExcessiveLines.ts: moved EMPTY_LINE/ to parent'; + chunk.parent.children.splice(ctx.nodeIdx + 1, 0, child); + } } else { break; } @@ -142,13 +159,12 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { if (child.isTag && child.tag === 'EMPTY_LINE/') { if (prevChild.isTag && prevChild.tag === 'EMPTY_LINE/') { markdownChunks.body.children.splice(idx, 1); - continue; } } } while (markdownChunks.body.children.length > 0) { - const firstChild = markdownChunks.body.children[0] + const firstChild = markdownChunks.body.children[0]; if (firstChild.isTag && firstChild.tag === 'EMPTY_LINE/') { markdownChunks.body.children.splice(0, 1); continue; @@ -157,7 +173,7 @@ export function removeExcessiveLines(markdownChunks: MarkdownNodes) { } while (markdownChunks.body.children.length > 0) { - const firstChild = markdownChunks.body.children[markdownChunks.body.children.length - 1] + const firstChild = markdownChunks.body.children[markdownChunks.body.children.length - 1]; if (firstChild.isTag && firstChild.tag === 'EMPTY_LINE/') { markdownChunks.body.children.splice(markdownChunks.body.children.length - 1, 1); continue; From 703cf457b0ba8376be45cf2e019b75d4a8bccbfd Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:35:23 +0200 Subject: [PATCH 13/18] Improve markdown processing --- src/google/markdownToHtml.ts | 4 +- src/odt/MarkdownNodes.ts | 2 +- src/odt/OdtToMarkdown.ts | 5 +- src/odt/macroUtils.ts | 8 ++ src/odt/markdownNodesUtils.ts | 142 ++------------------ src/odt/postprocess/postProcessPreMacros.ts | 79 ++++++++++- src/odt/postprocess/postProcessText.ts | 69 ---------- test/md_html/paras.html | 2 +- test/odt_md/list-test.md | 1 - 9 files changed, 102 insertions(+), 210 deletions(-) delete mode 100644 src/odt/postprocess/postProcessText.ts diff --git a/src/google/markdownToHtml.ts b/src/google/markdownToHtml.ts index 89803447..8bd43bbe 100644 --- a/src/google/markdownToHtml.ts +++ b/src/google/markdownToHtml.ts @@ -31,8 +31,8 @@ export async function markdownToHtml(buffer: Buffer): Promise { const html = marked.parse(md, { pedantic: false, hooks: { preprocess: (markdown: string) => markdown, postprocess(html: string) { - const style = '\n'; - return `\n\n\n${style}\n\n${html}\n\n\n`; + const style = '\n'; + return `\n\n\n${style}\n\n${html.trim()}\n\n\n`; } } }); diff --git a/src/odt/MarkdownNodes.ts b/src/odt/MarkdownNodes.ts index 91ba3fe7..736195b5 100644 --- a/src/odt/MarkdownNodes.ts +++ b/src/odt/MarkdownNodes.ts @@ -15,7 +15,7 @@ export type TAG = 'BODY' | 'HR/' | 'B' | 'I' | 'BI' | 'BLANK/' | // | '/B' | '/I 'TOC' | 'SVG/' | 'IMG/' | // | '/TOC' 'EMB_SVG' | 'EMB_SVG_G' | 'EMB_SVG_P/' | 'EMB_SVG_TEXT' | // | '/EMB_SVG' | '/EMB_SVG_G' | '/EMB_SVG_TEXT' 'EMB_SVG_TSPAN' | // | '/EMB_SVG_TSPAN' - 'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'COMMENT'; + 'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'MACRO_MODE/' | 'COMMENT'; export interface TagPayload { lang?: string; diff --git a/src/odt/OdtToMarkdown.ts b/src/odt/OdtToMarkdown.ts index 066af47b..ba886876 100644 --- a/src/odt/OdtToMarkdown.ts +++ b/src/odt/OdtToMarkdown.ts @@ -22,7 +22,6 @@ import {inchesToPixels, inchesToSpaces, spaces} from './utils.ts'; import {extractPath} from './extractPath.ts'; import {mergeDeep} from './mergeDeep.ts'; import {RewriteRule} from './applyRewriteRule.ts'; -import {postProcessText} from './postprocess/postProcessText.ts'; import {isMarkdownMacro} from './macroUtils.ts'; import {postProcess} from './postprocess/postProcess.ts'; @@ -132,8 +131,7 @@ export class OdtToMarkdown { await postProcess(this.chunks, this.rewriteRules); const markdown = this.chunks.toString(); - const trimmed = this.trimBreaks(markdown); - return postProcessText(trimmed); + return this.trimBreaks(markdown); } trimBreaks(markdown: string) { @@ -157,7 +155,6 @@ export class OdtToMarkdown { if (rows[i] === ' ') { rows[i] = ''; - continue; } } diff --git a/src/odt/macroUtils.ts b/src/odt/macroUtils.ts index 3ef10889..2c0cf92a 100644 --- a/src/odt/macroUtils.ts +++ b/src/odt/macroUtils.ts @@ -48,6 +48,14 @@ export function isBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% ') && !innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); } +export function getEndMacro(innerTxt: string) { + const txt= innerTxt.replace('{{% ', '').replace(' %}}', '') + .split(' ') + .shift(); + + return '{{% /' + txt + ' %}}'; +} + export function isEndMacro(innerTxt: string) { return innerTxt.startsWith('{{% /') && innerTxt.endsWith(' %}}'); } diff --git a/src/odt/markdownNodesUtils.ts b/src/odt/markdownNodesUtils.ts index dc77d176..1fc3c8da 100644 --- a/src/odt/markdownNodesUtils.ts +++ b/src/odt/markdownNodesUtils.ts @@ -84,6 +84,7 @@ interface ToTextContext { onlyNotTag?: boolean; inListItem?: boolean; addLiIndents?: boolean; + isMacro?: boolean; parentLevel?: number; } @@ -101,32 +102,15 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean, pare let noPara = false; if (chunk.children.length > 0 && chunk.children[0].isTag && chunk.children[0].tag === 'UL') { // No para, no symbol noPara = true; - // return innerText; - } - - if (!chunk.payload.listLevel) { - // return innerText; } const level = !ctx.parentLevel ? (chunk.payload.listLevel || 1) - 1 : 0; - // const level = 0; - if (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) { - // return innerText; - } - - // let indent = spaces(level * 4); GDocs not fully compatible - // if (chunk.payload.style?.paragraphProperties?.marginLeft) { - // indent = spaces(inchesToSpaces(chunk.payload.style?.paragraphProperties?.marginLeft) - 4); - // } const indent = spaces(level * 4); const listStr = chunk.payload.bullet ? '* ' : chunk.payload.number > 0 ? `${chunk.payload.number}. ` : ''; const firstStr = indent + listStr; const otherStr = indent + spaces(4); - // const firstStr = listStr; - // const otherStr = spaces(listStr.length < 3 ? listStr.length : 3 || 3); - return innerText .split('\n') .map((line, idx, lines) => { @@ -142,39 +126,6 @@ function addLiNumbers(chunk: MarkdownTagNode, ctx: {addLiIndents?: boolean, pare return otherStr + '' + line; }) .join('\n'); - -/* let prevEmptyLine = 1; - for (let position2 = ctx.nodeIdx + 1; position2 < chunk.parent.children.length; position2++) { - const chunk2 = chunk.parent.children[position2]; - if (chunk2.isTag === true && chunk2.tag === '/P' && chunk.mode === 'md') { - position += position2 - position - 1; - break; - } - - if (chunk2.isTag === true && ['BR/'].indexOf(chunk2.tag) > -1) { - prevEmptyLine = 2; - continue; - } - - if (chunk2.isTag === false && chunk2.text.startsWith('{{% ') && chunk2.text.endsWith(' %}}')) { - const innerText = chunk2.text.substring(3, chunk2.text.length - 3); - if (innerText.indexOf(' %}}') === -1) { - continue; - } - } - - if (prevEmptyLine > 0) { - chunk.parent.children.splice(position2, 0, { - isTag: false, - text: prevEmptyLine === 1 ? firstStr : otherStr, - comment: `addIndentsAndBullets.ts: Indent (${chunk.payload.bullet ? 'bullet' : 'number ' + chunk.payload.number}), level: ` + level + ', prevEmptyLine: ' + (!chunk.payload.bullet && !(chunk.payload.number > 0) && level === 0) - }); - prevEmptyLine = 0; - position2++; - return innerText; - } - } - */ } function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { @@ -212,10 +163,8 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { return '\n'; case 'BLANK/': return ''; - default: - return chunksToText(chunk.children, ctx); } - break; + return chunksToText(chunk.children, ctx); case 'md': switch (chunk.tag) { case 'BODY': @@ -223,6 +172,9 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { case 'P': return chunksToText(chunk.children, ctx); case 'BR/': + if (ctx.isMacro) { + return '\n'; + } return ' \n'; case 'EOL/': return '\n'; @@ -263,14 +215,14 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { return chunksToText(chunk.children, { ...ctx, mode: 'html' }); case 'RAW_MODE/': return chunksToText(chunk.children, { ...ctx, mode: 'raw' }); + case 'MACRO_MODE/': + return chunksToText(chunk.children, { ...ctx, mode: 'md', isMacro: true }); case 'LI': // TODO return addLiNumbers(chunk, ctx, chunksToText(chunk.children, { ...ctx, inListItem: true, parentLevel: chunk.payload.listLevel })); case 'TOC': return chunksToText(chunk.children, ctx); // TODO - default: - return chunksToText(chunk.children, ctx); } - break; + return chunksToText(chunk.children, ctx); case 'html': switch (chunk.tag) { case 'BODY': @@ -344,92 +296,22 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) { const fontSize = inchesToPixels(chunk.payload.style?.textProperties.fontSize); return `` + chunksToText(chunk.children, ctx) + '\n'; } - default: - return chunksToText(chunk.children, ctx); } - break; + return chunksToText(chunk.children, ctx); + default: + return ''; } - - return ''; } export function chunksToText(chunks: MarkdownNode[], ctx: ToTextContext): string { const retVal = []; - - ctx = Object.assign({ rewriteRule: [], mode: 'md' }, ctx); + ctx = Object.assign({ mode: 'md' }, ctx); for (let chunkNo = 0; chunkNo < chunks.length; chunkNo++) { const chunk = chunks[chunkNo]; - - /* - if ('tag' in chunk && ['SVG/', 'IMG/'].includes(chunk.tag)) { - let broke = false; - for (const rule of ctx.rewriteRule) { - const { shouldBreak, text } = applyRewriteRule(rule, { - ...chunk, - mode: 'TODO', // TODO - href: 'payload' in chunk ? chunk.payload?.href : undefined, - alt: 'payload' in chunk ? chunk.payload?.alt : undefined - }); - - if (shouldBreak) { - retVal.push(text); - broke = true; - break; - } - } - - if (broke) { - return retVal.join(''); - } - } - - if ('tag' in chunk && 'A' === chunk.tag) { - // let matchingNo = -1; - // - // for (let idx = chunkNo + 1; idx < chunks.length; idx++) { - // const chunkEnd = chunks[idx]; - // if ('tag' in chunkEnd && chunkEnd.tag === '/A') { - // matchingNo = idx; - // break; - // } - // } - - // if (matchingNo !== -1) { - const alt = chunksToText(chunk.children, { ...ctx, onlyNotTag: true }); // .filter(i => !i.isTag) - let broke = false; - for (const rule of ctx.rewriteRule) { - const { shouldBreak, text } = applyRewriteRule(rule, { - ...chunk, - mode: 'TODO', // TODO - href: 'payload' in chunk ? chunk.payload?.href : undefined, - alt - }); - - if (shouldBreak) { - retVal.push(text); - broke = true; - break; - } - } - - // console.log('XXXXXXXXXXXXXXXXx', alt, broke, retVal); - if (broke) { - return retVal.join(''); - // retVal.splice(chunkNo, matchingNo - chunkNo); - // return retVal; - } - // } - } -*/ - retVal.push(chunkToText(chunk, ctx)); } - // chunks.map(c => chunkToText(c)); - /* - */ - return retVal.join(''); } diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index 93f191eb..a9fc45e7 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -1,6 +1,6 @@ import {MarkdownNodes, MarkdownTagNode} from '../MarkdownNodes.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -import {getMarkdownEndMacro, isMarkdownBeginMacro} from '../macroUtils.js'; +import {getEndMacro, getMarkdownEndMacro, isBeginMacro, isMarkdownBeginMacro} from '../macroUtils.js'; function isPreBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}'); @@ -19,18 +19,44 @@ function getMatching(startText: string) { return getMarkdownEndMacro(startText); } + if (isBeginMacro(startText)) { + return getEndMacro(startText); + } + return ''; } function cleanupLines(chunk: MarkdownTagNode) { while (chunk.children.length > 0) { const child = chunk.children[0]; - if (child.isTag && child.tag === 'EMPTY_LINE/') { + if (child.isTag && ['EMPTY_LINE/', 'EOL/'].includes(child.tag)) { chunk.children.splice(0, 1); continue; } break; } + + for (let idx = chunk.children.length - 1; idx >= 1; idx--) { + const child = chunk.children[idx]; + const prevChild = chunk.children[idx - 1]; + + if (child.isTag && child.tag === 'EOL/') { + if (prevChild.isTag && prevChild.tag === 'EOL/') { + child.tag = 'EMPTY_LINE/'; + continue; + } + } + break; + } + + while (chunk.children.length > 0) { + const child = chunk.children[chunk.children.length - 1]; + if (child.isTag && ['EMPTY_LINE/'].includes(child.tag)) { + chunk.children.splice(chunk.children.length - 1, 1); + continue; + } + break; + } } export function postProcessPreMacros(markdownChunks: MarkdownNodes) { @@ -87,6 +113,55 @@ export function postProcessPreMacros(markdownChunks: MarkdownNodes) { } } + if (chunk && chunk.isTag && ['P'].includes(chunk.tag)) { + let firstChildIdx = -1; + let startText = ''; + for (let idx = 0; idx < chunk.children.length; idx++) { + const child = chunk.children[idx]; + if (firstChildIdx === -1) { + if (child.isTag === false && isBeginMacro(child.text)) { + startText = child.text; + firstChildIdx = idx; + } + continue; + } + + if (child.isTag && chunk.tag === 'HTML_MODE/') { + firstChildIdx = -1; + continue; + } + + if (firstChildIdx > -1 && child.isTag === false && child.text === getMatching(startText)) { + const afterFirst = chunk.children[firstChildIdx + 1]; + if (afterFirst.isTag && ['EOL/', 'BR/'].includes(afterFirst.tag)) { + afterFirst.tag = 'EOL/'; + firstChildIdx++; + } + + const lastChildIdx = idx; + + const macroMode = markdownChunks.createNode('MACRO_MODE/'); + macroMode.comment = 'postProcessPreMacros.ts: enter macro mode after: ' + startText; + const children = chunk.children.splice(firstChildIdx + 1, lastChildIdx - firstChildIdx - 1, macroMode); + + macroMode.children.splice(0, 0, ...children); + + idx -= children.length; + + firstChildIdx = -1; + + walkRecursiveSync(macroMode, chunk => { + if (chunk.isTag && chunk.tag === 'BR/') { + chunk.tag = 'EOL/'; + chunk.comment = 'postProcessPreMacros.ts: Converted BR/ to EOL/ inside macro'; + } + }); + + cleanupLines(macroMode); + } + } + } + if (chunk && chunk.isTag && chunk.tag === 'PRE') { let firstChildIdx = -1; for (let idx = 0; idx < chunk.children.length; idx++) { diff --git a/src/odt/postprocess/postProcessText.ts b/src/odt/postprocess/postProcessText.ts deleted file mode 100644 index b14f8027..00000000 --- a/src/odt/postprocess/postProcessText.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {isBeginMacro, isEndMacro} from '../macroUtils.ts'; - -export function emptyString(str: string) { - return str.trim().length === 0; -} - -export function postProcessText(markdown: string): string { - const lines = markdown.split('\n'); - - function addEmptyLineBefore(lineNo: number) { - lines.splice(lineNo, 0, ''); - if (lineNo > 0 && lines[lineNo - 1].endsWith(' ')) { - lines[lineNo - 1] = lines[lineNo - 1].replace(/ +$/, ''); - } - } - - function hasClosingMacroAfter(lineNo: number, currentLine: string) { - const closingText = currentLine.replace('{{% ', '{{% /'); - for (let idx = lineNo + 1; idx < lines.length; idx++) { - if (lines[idx] === closingText) { - return true; - } - } - return false; - } - - for (let lineNo = 1; lineNo < lines.length; lineNo++) { - const prevLine = lines[lineNo - 1]; - const currentLine = lines[lineNo]; - if (isBeginMacro(currentLine) && !emptyString(prevLine) && hasClosingMacroAfter(lineNo, currentLine)) { - addEmptyLineBefore(lineNo); - lineNo--; - continue; - } - } - - for (let lineNo = 0; lineNo < lines.length - 1; lineNo++) { - const currentLine = lines[lineNo]; - const nextLine = lines[lineNo + 1]; - if (isBeginMacro(currentLine) && emptyString(nextLine) && hasClosingMacroAfter(lineNo, currentLine)) { - lines.splice(lineNo + 1, 1); - lineNo--; - continue; - } - } - - for (let lineNo = 0; lineNo < lines.length - 1; lineNo++) { - const currentLine = lines[lineNo]; - const nextLine = lines[lineNo + 1]; - if (isEndMacro(currentLine) && !emptyString(nextLine)) { - addEmptyLineBefore(lineNo + 1); - lineNo--; - continue; - } - } - - for (let lineNo = 1; lineNo < lines.length; lineNo++) { - const prevLine = lines[lineNo - 1]; - const currentLine = lines[lineNo]; - if (isEndMacro(currentLine) && emptyString(prevLine)) { - lines.splice(lineNo - 1, 1); - lineNo--; - continue; - } - } - - - return lines.join('\n'); -} diff --git a/test/md_html/paras.html b/test/md_html/paras.html index f96aa0ee..01ac273f 100644 --- a/test/md_html/paras.html +++ b/test/md_html/paras.html @@ -2,7 +2,7 @@ diff --git a/test/odt_md/list-test.md b/test/odt_md/list-test.md index 4a9d1437..28255b15 100644 --- a/test/odt_md/list-test.md +++ b/test/odt_md/list-test.md @@ -1,5 +1,4 @@ {{% warning title="Remember" %}} - Action items that are configured with a Trigger Date of **Prior Action (Completed)** will require an order item be specified, in order for there to be an available record in the chart history with a Last Completed Date. This is necessary, so that any additional triggered actions dependent upon the Last Completed Date of the specified order item will be triggered. {{% /warning %}} From 889f6770101076b84647aac2dea54373cc216aa4 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:36:38 +0200 Subject: [PATCH 14/18] Fix extensions --- src/odt/postprocess/applyRewriteRules.ts | 6 +++--- src/odt/postprocess/convertToc.ts | 4 ++-- src/odt/postprocess/fixBoldItalic.ts | 2 +- src/odt/postprocess/mergeParagraphs.ts | 2 +- src/odt/postprocess/postProcess.ts | 16 ++++++++-------- src/odt/postprocess/postProcessPreMacros.ts | 2 +- src/odt/postprocess/removeEmptyTags.ts | 4 ++-- src/odt/postprocess/removeExcessiveLines.ts | 4 ++-- src/odt/postprocess/removeMarkdownMacro.ts | 6 +++--- src/odt/postprocess/removeTdParas.ts | 4 ++-- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/odt/postprocess/applyRewriteRules.ts b/src/odt/postprocess/applyRewriteRules.ts index 2bba7080..c70d8c4d 100644 --- a/src/odt/postprocess/applyRewriteRules.ts +++ b/src/odt/postprocess/applyRewriteRules.ts @@ -1,6 +1,6 @@ -import {MarkdownNodes, MarkdownTextNode} from '../MarkdownNodes.js'; -import {chunksToText, walkRecursiveSync} from '../markdownNodesUtils.js'; -import {applyRewriteRule, RewriteRule} from '../applyRewriteRule.js'; +import {MarkdownNodes, MarkdownTextNode} from '../MarkdownNodes.ts'; +import {chunksToText, walkRecursiveSync} from '../markdownNodesUtils.ts'; +import {applyRewriteRule, RewriteRule} from '../applyRewriteRule.ts'; export function applyRewriteRules(markdownChunks: MarkdownNodes, rewriteRule: RewriteRule[] = []) { let inHtml = 0; diff --git a/src/odt/postprocess/convertToc.ts b/src/odt/postprocess/convertToc.ts index 34ef3d21..b026126a 100644 --- a/src/odt/postprocess/convertToc.ts +++ b/src/odt/postprocess/convertToc.ts @@ -1,5 +1,5 @@ -import {walkRecursiveSync} from '../markdownNodesUtils.js'; -import {MarkdownNodes} from '../MarkdownNodes.js'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; export function convertToc(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { diff --git a/src/odt/postprocess/fixBoldItalic.ts b/src/odt/postprocess/fixBoldItalic.ts index d44ccd1f..8df5490f 100644 --- a/src/odt/postprocess/fixBoldItalic.ts +++ b/src/odt/postprocess/fixBoldItalic.ts @@ -1,5 +1,5 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.js'; +import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.ts'; export async function fixBoldItalic(markdownChunks: MarkdownNodes) { // Remove empty Bold and empty Italic diff --git a/src/odt/postprocess/mergeParagraphs.ts b/src/odt/postprocess/mergeParagraphs.ts index a2c415d9..b2966779 100644 --- a/src/odt/postprocess/mergeParagraphs.ts +++ b/src/odt/postprocess/mergeParagraphs.ts @@ -1,5 +1,5 @@ import {MarkdownNodes} from '../MarkdownNodes.ts'; -import {extractText, walkRecursiveSync} from '../markdownNodesUtils.js'; +import {extractText, walkRecursiveSync} from '../markdownNodesUtils.ts'; export function mergeParagraphs(markdownChunks: MarkdownNodes) { diff --git a/src/odt/postprocess/postProcess.ts b/src/odt/postprocess/postProcess.ts index 5fd980b4..439756b9 100644 --- a/src/odt/postprocess/postProcess.ts +++ b/src/odt/postprocess/postProcess.ts @@ -11,18 +11,18 @@ import {hideSuggestedChanges} from './hideSuggestedChanges.ts'; import {trimParagraphs} from './trimParagraphs.ts'; import {addEmptyLinesAfterParas} from './addEmptyLinesAfterParas.ts'; import {addEmptyLines} from './addEmptyLines.ts'; -import {removeTdParas} from './removeTdParas.js'; +import {removeTdParas} from './removeTdParas.ts'; import {mergeTexts} from './mergeTexts.ts'; -import {rewriteHeaders} from './rewriteHeaders.js'; -import {removeMarkdownMacro} from './removeMarkdownMacro.js'; +import {rewriteHeaders} from './rewriteHeaders.ts'; +import {removeMarkdownMacro} from './removeMarkdownMacro.ts'; import {postProcessPreMacros} from './postProcessPreMacros.ts'; import {mergeParagraphs} from './mergeParagraphs.ts'; -import {convertToc} from './convertToc.js'; -import {removeEmptyTags} from './removeEmptyTags.js'; -import {removeExcessiveLines} from './removeExcessiveLines.js'; -import {applyRewriteRules} from './applyRewriteRules.js'; -import {RewriteRule} from '../applyRewriteRule.js'; +import {convertToc} from './convertToc.ts'; +import {removeEmptyTags} from './removeEmptyTags.ts'; +import {removeExcessiveLines} from './removeExcessiveLines.ts'; +import {applyRewriteRules} from './applyRewriteRules.ts'; +import {RewriteRule} from '../applyRewriteRule.ts'; export async function postProcess(chunks: MarkdownNodes, rewriteRules: RewriteRule[]) { convertToc(chunks); diff --git a/src/odt/postprocess/postProcessPreMacros.ts b/src/odt/postprocess/postProcessPreMacros.ts index a9fc45e7..257218f1 100644 --- a/src/odt/postprocess/postProcessPreMacros.ts +++ b/src/odt/postprocess/postProcessPreMacros.ts @@ -1,6 +1,6 @@ import {MarkdownNodes, MarkdownTagNode} from '../MarkdownNodes.ts'; import {walkRecursiveSync} from '../markdownNodesUtils.ts'; -import {getEndMacro, getMarkdownEndMacro, isBeginMacro, isMarkdownBeginMacro} from '../macroUtils.js'; +import {getEndMacro, getMarkdownEndMacro, isBeginMacro, isMarkdownBeginMacro} from '../macroUtils.ts'; function isPreBeginMacro(innerTxt: string) { return innerTxt.startsWith('{{% pre ') && innerTxt.endsWith(' %}}'); diff --git a/src/odt/postprocess/removeEmptyTags.ts b/src/odt/postprocess/removeEmptyTags.ts index a76e216f..4db06281 100644 --- a/src/odt/postprocess/removeEmptyTags.ts +++ b/src/odt/postprocess/removeEmptyTags.ts @@ -1,5 +1,5 @@ -import {MarkdownNodes} from '../MarkdownNodes.js'; -import {walkRecursiveSync} from '../markdownNodesUtils.js'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function removeEmptyTags(markdownChunks: MarkdownNodes) { let inHtml = false; diff --git a/src/odt/postprocess/removeExcessiveLines.ts b/src/odt/postprocess/removeExcessiveLines.ts index bf3bc71b..6e97d390 100644 --- a/src/odt/postprocess/removeExcessiveLines.ts +++ b/src/odt/postprocess/removeExcessiveLines.ts @@ -1,5 +1,5 @@ -import {MarkdownNodes} from '../MarkdownNodes.js'; -import {walkRecursiveSync} from '../markdownNodesUtils.js'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function removeExcessiveLines(markdownChunks: MarkdownNodes) { diff --git a/src/odt/postprocess/removeMarkdownMacro.ts b/src/odt/postprocess/removeMarkdownMacro.ts index 509c7179..7c5a3f0e 100644 --- a/src/odt/postprocess/removeMarkdownMacro.ts +++ b/src/odt/postprocess/removeMarkdownMacro.ts @@ -1,6 +1,6 @@ -import {MarkdownNodes} from '../MarkdownNodes.js'; -import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.js'; -import {isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.js'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {extractText, walkRecursiveAsync} from '../markdownNodesUtils.ts'; +import {isMarkdownMacro, stripMarkdownMacro} from '../macroUtils.ts'; export async function removeMarkdownMacro(markdownChunks: MarkdownNodes) { let inHtml = false; diff --git a/src/odt/postprocess/removeTdParas.ts b/src/odt/postprocess/removeTdParas.ts index b6803092..4f73ab4c 100644 --- a/src/odt/postprocess/removeTdParas.ts +++ b/src/odt/postprocess/removeTdParas.ts @@ -1,5 +1,5 @@ -import {MarkdownNodes} from '../MarkdownNodes.js'; -import {walkRecursiveSync} from '../markdownNodesUtils.js'; +import {MarkdownNodes} from '../MarkdownNodes.ts'; +import {walkRecursiveSync} from '../markdownNodesUtils.ts'; export function removeTdParas(markdownChunks: MarkdownNodes) { // Run after addEmptyLinesAfterParas From 885ecd991db98fa41fb306f950ddea86629f5900 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:38:55 +0200 Subject: [PATCH 15/18] Improve test --- test/md_html/markdownToHtmlTest.ts | 6 +++--- test/utils.ts | 14 -------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/test/md_html/markdownToHtmlTest.ts b/test/md_html/markdownToHtmlTest.ts index bf49c52b..1d7b2935 100644 --- a/test/md_html/markdownToHtmlTest.ts +++ b/test/md_html/markdownToHtmlTest.ts @@ -4,10 +4,10 @@ import * as domutils from 'domutils'; import render from 'dom-serializer'; import {Element} from 'domhandler'; -import {markdownToHtml} from '../../src/google/markdownToHtml'; -import {convertToAbsolutePath} from '../../src/LinkTranslator'; +import {markdownToHtml} from '../../src/google/markdownToHtml.ts'; +import {convertToAbsolutePath} from '../../src/LinkTranslator.ts'; import fs from 'fs'; -import {compareTexts} from '../utils'; +import {compareTexts} from '../utils.ts'; import path from 'path'; import { fileURLToPath } from 'url'; diff --git a/test/utils.ts b/test/utils.ts index f94fcb7b..b653fd0f 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -51,20 +51,6 @@ export function compareTexts(input, output, ignoreWhitespace = true, fileName = return true; } -export function compareTextsWithLines(input, output) { - const diff = diffLines(input, output, { - ignoreWhitespace: true - }).filter(row => (row.added || row.removed)); - - diff.forEach(function(part) { - // process.stdout.write(Math.floor(idx / 2 + 2) + ':\t'); - const color = part.added ? 'green' : part.removed ? 'red' : 'grey'; - process.stdout.write(part.value[color]); - }); - - return diff.length === 0; -} - export function compareObjects(obj1, obj2, prefix = '') { const set = new Set(); Object.keys(obj1).forEach(k => set.add(k)); From 815745536a54706b3bce7bf4bc1b9753eb3bd17d Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:42:35 +0200 Subject: [PATCH 16/18] Cleanups --- src/odt/postprocess/addEmptyLines.ts | 111 --------------------------- 1 file changed, 111 deletions(-) diff --git a/src/odt/postprocess/addEmptyLines.ts b/src/odt/postprocess/addEmptyLines.ts index 666be27f..bcabe617 100644 --- a/src/odt/postprocess/addEmptyLines.ts +++ b/src/odt/postprocess/addEmptyLines.ts @@ -16,35 +16,6 @@ BR/ */ -function isPreviousChunkEmptyLine(markdownChunks: MarkdownNodes, position: number) { - const chunk = markdownChunks.chunks[position - 1]; - if (!chunk) { - return false; - } - - if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { - return true; - } - - return false; -} - -function isNextChunkEmptyLine(markdownChunks: MarkdownNodes, position: number) { - const chunk = markdownChunks.chunks[position + 1]; - if (!chunk) { - return false; - } - - if (chunk.isTag && 'EMPTY_LINE/' === chunk.tag) { - return true; - } - if (chunk.isTag && 'EOL/' === chunk.tag) { - return true; - } - - return false; -} - function isChunkEmptyLine(chunk: MarkdownNode) { if (!chunk) { return false; @@ -63,8 +34,6 @@ function isChunkEmptyLine(chunk: MarkdownNode) { return false; } - - export function addEmptyLines(markdownChunks: MarkdownNodes) { walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (chunk.parent && chunk.parent.tag !== 'BODY') { @@ -159,86 +128,6 @@ export function addEmptyLines(markdownChunks: MarkdownNodes) { } }); - return; - - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { - if (!chunk.parent) { - return; - } - - const prevTag = chunk.parent?.children[ctx.nodeIdx - 1] || null; - const nextTag = chunk.parent?.children[ctx.nodeIdx + 1] || null; - if (chunk.isTag && ['IMG/', 'SVG/'].indexOf(chunk.tag) > -1 && nextTag) { - if (nextTag.isTag && nextTag.tag === 'IMG/') { - chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { - ...markdownChunks.createNode('EOL/'), - comment: 'addEmptyLines.ts: EOL/ after IMG/' - }, { - ...markdownChunks.createNode('EMPTY_LINE/'), - comment: 'addEmptyLines.ts: Between images' - }); - } - return; - } - - if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'UL'].indexOf(chunk.tag) > -1 && nextTag) { - if (chunk.tag === 'UL' && chunk.payload.listLevel !== 1) { - return; - } - if (chunk.tag === 'UL' && nextTag.isTag && nextTag.tag === 'UL') { - return; - } - if (chunk.tag === 'UL' && nextTag.isTag && nextTag.tag === 'P') { - // return; - } - } - - // if (!isChunkEmptyLine(nextTag) && !isChunkEmptyLine(prevTag)) { - // chunk.parent.children.splice(ctx.nodeIdx + 1, 0, { - // ...markdownChunks.createNode('EMPTY_LINE/'), - // comment: 'addEmptyLines.ts: Add empty line after: ' + (chunk.tag || chunk.text) - // }); - // return; - // } - // if (!(nextTag.isTag && nextTag.tag === 'BR/') && !(nextTag.isTag && nextTag.tag === '/TD') && !(nextTag.isTag && nextTag.tag === 'EMPTY_LINE/')) { - // } - - // listLevel - - if (chunk.isTag && ['H1', 'H2', 'H3', 'H4', 'IMG/', 'SVG/', 'UL'].indexOf(chunk.tag) > -1 && nextTag) { - const prevTag = chunk.parent.children[ctx.nodeIdx - 1]; - - if (chunk.tag === 'UL' && chunk.payload.listLevel !== 1) { - return; - } - if (chunk.tag === 'UL' && prevTag && prevTag.isTag && prevTag.tag === 'UL') { - return; - } - if (chunk.tag === 'UL' && prevTag && prevTag.isTag && prevTag.tag === 'P') { - // return; - } - - if (!isChunkEmptyLine(nextTag) && !isChunkEmptyLine(prevTag)) { - chunk.parent.children.splice(ctx.nodeIdx, 0, { - ...markdownChunks.createNode('EMPTY_LINE/'), - comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag + JSON.stringify({ ...prevTag, children: undefined, parent: undefined }) - }); - return; - } - - if (!(prevTag.isTag && prevTag.tag === 'BR/') && !(prevTag.isTag && prevTag.tag === 'TD') && !(prevTag.isTag && prevTag.tag === 'EMPTY_LINE/')) { - // markdownChunks.chunks.splice(position, 0, { - // isTag: false, - // mode: 'md', - // text: '\n', - // // payload: {}, - // comment: 'addEmptyLines.ts: Add empty line before: ' + chunk.tag - // }); - } - } - }); - - walkRecursiveSync(markdownChunks.body, (chunk, ctx: { nodeIdx: number }) => { if (!(chunk.isTag && chunk.tag === 'IMG/' && chunk.mode === 'md')) { return; From 7c6aad262be6d830b88cf576570a2d04340cc403 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:43:34 +0200 Subject: [PATCH 17/18] Cleanups --- test/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils.ts b/test/utils.ts index b653fd0f..f0c4ae48 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import { diffLines, createPatch } from 'diff'; +import {createPatch} from 'diff'; import {ansi_colors} from '../src/utils/logger/colors.ts'; export function createTmpDir() { From 77c47f01572ae4bd97051c3c30991470274f53d1 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Fri, 5 Apr 2024 16:54:34 +0200 Subject: [PATCH 18/18] Cleanups --- test/utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/utils.ts b/test/utils.ts index f0c4ae48..8e1145cc 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -36,9 +36,7 @@ export function compareTexts(input, output, ignoreWhitespace = true, fileName = return false; } return true; - } - - if (ignoreWhitespace) { + } else { input = input.split('\n').map(line => line.replace(/[\s]+$/, '')).join('\n'); output = output.split('\n').map(line => line.replace(/[\s]+$/, '')).join('\n'); }
    C1