Skip to content

Commit

Permalink
Fix anchor links to sections of documents
Browse files Browse the repository at this point in the history
Resolves: #472
  • Loading branch information
ggodlewski committed Jun 23, 2024
1 parent 0d13ac8 commit cb08db6
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/containers/transform/LocalLinks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {FileContentService} from '../../utils/FileContentService.ts';
import {FileId} from '../../model/model.js';
import {FileId} from '../../model/model.ts';

interface Link {
fileId: string;
Expand Down Expand Up @@ -78,7 +78,7 @@ export class LocalLinks {
if (link.fileId === fileId) {
const links = link.links
.filter(link => link.startsWith('gdoc:'))
.map(link => link.substring('gdoc:'.length));
.map(link => link.substring('gdoc:'.length).replace(/#.*/, ''));

return links.map(fileId => ({
fileId,
Expand Down
10 changes: 6 additions & 4 deletions src/containers/transform/TransformContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {MarkdownTreeProcessor} from './MarkdownTreeProcessor.ts';
import {LunrIndexer} from '../search/LunrIndexer.ts';
import {JobManagerContainer} from '../job/JobManagerContainer.ts';
import {UserConfigService} from '../google_folder/UserConfigService.ts';
import {getUrlHash} from '../../utils/idParsers.ts';

const __filename = fileURLToPath(import.meta.url);

Expand Down Expand Up @@ -342,10 +343,10 @@ export class TransformContainer extends Container {
processed.add(fileId);
const backLinks = this.localLinks.getBackLinks(fileId);
for (const backLink of backLinks) {
if (processed.has(backLink)) {
if (processed.has(backLink.fileId)) {
continue;
}
filterFilesIds.add(backLink);
filterFilesIds.add(backLink.fileId);
}
}
if (filterFilesIds.size > 0) {
Expand Down Expand Up @@ -426,7 +427,8 @@ export class TransformContainer extends Container {
if (fileName.endsWith('.md') || fileName.endsWith('.svg')) {
const content = await destinationDirectory.readFile(fileName);
const newContent = content.replace(/(gdoc:[A-Z0-9_-]+)/ig, (str: string) => {
const fileId = str.substring('gdoc:'.length);
const fileId = str.substring('gdoc:'.length).replace(/#.*/, '');
const hash = getUrlHash(str);
const lastLog = this.localLog.findLastFile(fileId);
if (lastLog && lastLog.event !== 'removed') {
if (fileName.endsWith('.svg')) {
Expand All @@ -435,7 +437,7 @@ export class TransformContainer extends Container {
return convertToRelativeMarkDownPath(lastLog.filePath, destinationDirectory.getVirtualPath() + fileName);
}
} else {
return 'https://drive.google.com/open?id=' + fileId;
return 'https://drive.google.com/open?id=' + fileId + hash.replace('#_', '#heading=h.');
}
});
if (content !== newContent) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import yaml from 'js-yaml';

import {MdFile} from '../../../model/LocalFile';
import {FRONTMATTER_DUMP_OPTS} from './frontmatter';
import {MdFile} from '../../../model/LocalFile.ts';
import {FRONTMATTER_DUMP_OPTS} from './frontmatter.ts';

export function generateDocumentFrontMatter(localFile: MdFile, links: string[],
fm_without_version = false) {
Expand Down
8 changes: 4 additions & 4 deletions src/odt/LibreOffice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ export class TextLink implements ParagraphSection {

@XmlElement()
@XmlAttribute('text:name', 'name')
export class TextBookmark {
export class TextBookmark implements ParagraphSection {
type = 'bookmark';
name: string;
}

Expand Down Expand Up @@ -200,7 +201,7 @@ export class TextChangeEnd {
@XmlElement()
@XmlText('list', {isArray: true})
@XmlAttribute('text:style-name', 'styleName')
@XmlElementChild('text:bookmark', 'bookmark', 'TextBookmark')
@XmlElementChild('text:bookmark', 'list', 'TextBookmark', {isArray: true})
@XmlElementChild('text:a', 'list', 'TextLink', {isArray: true})
@XmlElementChild('text:span', 'list', 'TextSpan', {isArray: true})
@XmlElementChild('draw:rect', 'list', 'DrawRect', {isArray: true})
Expand All @@ -215,8 +216,7 @@ export class TextChangeEnd {
@XmlElementChild('text:change-end', 'list', 'TextChangeEnd', {isArray: true})
export class TextParagraph implements TextSection {
type = 'paragraph';
bookmark: TextBookmark;
list: Array<string | TextLink | TextSpan | DrawRect | DrawFrame | TextTab | TextLineBreak | TextSpace | DrawG | TextChangeStart | TextChangeEnd | DrawCustomShape> = [];
list: Array<string | TextLink | TextSpan | DrawRect | DrawFrame | TextTab | TextLineBreak | TextSpace | DrawG | TextChangeStart | TextChangeEnd | DrawCustomShape | TextBookmark> = [];
annotations: OfficeAnnotation[] = [];
styleName: string;
}
Expand Down
3 changes: 1 addition & 2 deletions src/odt/MarkdownNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type TAG = 'BODY' | 'HR/' | 'B' | 'I' | 'BI' | 'BLANK/' | // | '/B' | '/I
'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'
'MATHML' |
'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'MACRO_MODE/' | 'COMMENT';
'CHANGE_START' | 'CHANGE_END' | 'RAW_MODE/' | 'HTML_MODE/' | 'MD_MODE/' | 'MACRO_MODE/' | 'COMMENT' | 'BOOKMARK/';

export interface TagPayload {
lang?: string;
Expand All @@ -34,7 +34,6 @@ export interface TagPayload {
listStyle?: ListStyle;
continueNumbering?: boolean;
listLevel?: number;
bookmarkName?: string;
pathD?: string;

x?: number;
Expand Down
29 changes: 17 additions & 12 deletions src/odt/OdtToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
TableCell,
TableOfContent,
TableRow,
TableTable,
TableTable, TextBookmark,
TextLink,
TextList,
TextParagraph,
TextProperty,
TextSpace,
TextSpan
} from './LibreOffice.ts';
import {urlToFolderId} from '../utils/idParsers.ts';
import {getUrlHash, urlToFolderId} from '../utils/idParsers.ts';
import {MarkdownNodes, MarkdownTagNode} from './MarkdownNodes.ts';
import {inchesToPixels, inchesToSpaces, spaces} from './utils.ts';
import {extractPath} from './extractPath.ts';
Expand Down Expand Up @@ -230,13 +230,14 @@ export class OdtToMarkdown {
async linkToText(currentTagNode: MarkdownTagNode, link: TextLink): Promise<void> {
let href = link.href;
const id = urlToFolderId(href);
const hash = getUrlHash(link.href);
if (id) {
href = 'gdoc:' + id;
}

this.addLink(href);
this.addLink(href + hash);

const block = this.chunks.createNode('A', { href: href });
const block = this.chunks.createNode('A', { href: href + hash });
this.chunks.append(currentTagNode, block);
currentTagNode = block;

Expand Down Expand Up @@ -458,38 +459,36 @@ export class OdtToMarkdown {
return false;
}


async paragraphToText(currentTagNode: MarkdownTagNode, paragraph: TextParagraph): Promise<void> {
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')) {
const header = this.chunks.createNode('H1', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const header = this.chunks.createNode('H1', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, header);
currentTagNode = header;
} else
if (this.hasStyle(paragraph, 'Heading_20_2')) {
const header = this.chunks.createNode('H2', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const header = this.chunks.createNode('H2', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, header);
currentTagNode = header;
} else
if (this.hasStyle(paragraph, 'Heading_20_3')) {
const header = this.chunks.createNode('H3', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const header = this.chunks.createNode('H3', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, header);
currentTagNode = header;
} else
if (this.hasStyle(paragraph, 'Heading_20_4')) {
const header = this.chunks.createNode('H4', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const header = this.chunks.createNode('H4', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, header);
currentTagNode = header;
} else
if (this.isCourier(paragraph.styleName)) {
const block = this.chunks.createNode('PRE', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const block = this.chunks.createNode('PRE', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, block);
currentTagNode = block;
} else {
const block = this.chunks.createNode('P', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle, bookmarkName });
const block = this.chunks.createNode('P', { marginLeft: inchesToSpaces(style.paragraphProperties?.marginLeft), style, listStyle });
this.chunks.append(currentTagNode, block);
currentTagNode = block;
}
Expand Down Expand Up @@ -596,6 +595,12 @@ export class OdtToMarkdown {
case 'change_end':
this.chunks.append(currentTagNode, this.chunks.createNode('CHANGE_END'));
break;
case 'bookmark':
{
const bookmark = <TextBookmark>child;
this.chunks.append(currentTagNode, this.chunks.createNode('BOOKMARK/', { id: bookmark.name }));
}
break;
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/odt/markdownNodesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) {
return addLiNumbers(chunk, ctx, chunksToText(chunk.children, { ...ctx, inListItem: true, parentLevel: chunk.payload.listLevel }));
case 'TOC':
return chunksToText(chunk.children, ctx); // TODO
case 'BOOKMARK/':
return `<a id="${chunk.payload.id}"></a>`;
}
return chunksToText(chunk.children, ctx);
case 'html':
Expand Down Expand Up @@ -296,6 +298,8 @@ function chunkToText(chunk: MarkdownNode, ctx: ToTextContext) {
const fontSize = inchesToPixels(chunk.payload.style?.textProperties.fontSize);
return `<tspan style="${textStyleToString(chunk.payload.style?.textProperties)}" font-size="${fontSize}">` + chunksToText(chunk.children, ctx) + '</tspan>\n';
}
case 'BOOKMARK/':
return `<a id="${chunk.payload.id}"></a>`;
}
return chunksToText(chunk.children, ctx);
default:
Expand Down
25 changes: 7 additions & 18 deletions src/odt/postprocess/rewriteHeaders.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import slugify from 'slugify';
import {extractText, walkRecursiveAsync, walkRecursiveSync} from '../markdownNodesUtils.ts';
import {walkRecursiveAsync} 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 = extractText(chunk);
const slug = slugify(innerTxt.trim(), { replacement: '-', lower: true, remove: /[#*+~.()'"!:@]/g });
if (slug) {
headersMap['#' + chunk.payload.bookmarkName] = '#' + slug;
if (chunk.isTag && ['H1', 'H2', 'H3', 'H4'].includes(chunk.tag)) {
if (chunk.children.length > 1) {
const first = chunk.children[0];
if (first.isTag && first.tag === 'BOOKMARK/') {
const toMove = chunk.children.splice(0, 1);
chunk.children.splice(chunk.children.length, 0, ...toMove);
}
}
}
});

walkRecursiveSync(markdownChunks.body, (chunk) => {
if (chunk.isTag === true && chunk.payload?.href) {
if (headersMap[chunk.payload.href]) {
chunk.payload.href = headersMap[chunk.payload.href];
}
}
});
}
8 changes: 8 additions & 0 deletions src/utils/idParsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ export function urlToFolderId(url: string): string | null {

return null;
}

export function getUrlHash(url: string): string {
const idx = url.indexOf('#');
if (idx >= 0) {
return url.substring(idx).replace('#heading=h.', '#_');
}
return '';
}

0 comments on commit cb08db6

Please sign in to comment.