@mieweb/wikigdrive
Version:
Google Drive to MarkDown synchronization
144 lines (122 loc) • 3.65 kB
text/typescript
// Legacy menu generator, use: navigation2menu.mjs
import {urlToFolderId} from '../../utils/idParsers.ts';
import {FileId} from '../../model/model.ts';
import {DocumentContent, TextLink, TextList, TextParagraph, TextSpace, TextSpan, TextTab} from '../../odt/LibreOffice.ts';
export interface NavigationHierarchyNode {
name: string;
weight: number;
identifier: FileId;
parent?: FileId;
pageRef?: string;
}
export interface NavigationHierarchy {
[k: string]: NavigationHierarchyNode;
}
interface Logger {
warn(mxg: string): void;
}
interface NavigationProcessContext {
headerCounter: number;
lastContent: string;
counter: number;
levelParent: {[level: number]: string};
logger: Logger;
result: NavigationHierarchy;
}
function extractText(obj: string | TextLink | TextSpan | TextSpace | TextTab): string {
let retVal = '';
if (typeof obj === 'string') {
retVal += obj;
} else {
if (obj.type === 'space') {
retVal += ' '.substring(0, (<TextSpace>obj).chars);
}
if (obj.type === 'tab') {
retVal += '\t';
}
if (obj.type === 'link') {
for (const item of (<TextLink>obj).list) {
retVal += extractText(item);
}
} else
if (obj.type === 'span') {
for (const item of (<TextSpan>obj).list) {
retVal += extractText(item);
}
}
}
return retVal;
}
function processPara(para: TextParagraph, ctx: NavigationProcessContext, level: number) {
let paraUrl;
let paraContent = '';
para.list = para.list.filter(item => {
if (typeof item !== 'string') {
return true;
}
return !!item.trim();
});
if (para.list.length === 0) {
return;
}
const item = para.list[0];
if (typeof item === 'string') {
paraContent += extractText(item);
} else
if (item.type === 'link') {
const link = <TextLink>item;
paraUrl = link.href;
paraContent += extractText(item);
}
if (paraContent) {
ctx.lastContent = paraContent;
const fileId = paraUrl ? urlToFolderId(paraUrl) : 'header_' + ctx.headerCounter++;
ctx.levelParent[level] = fileId;
const hierarchyFrontMatter: NavigationHierarchyNode = {
identifier: fileId,
name: paraContent.trim(),
weight: ctx.counter
};
if (level > 0) {
hierarchyFrontMatter.parent = ctx.levelParent[level - 1];
}
ctx.result[hierarchyFrontMatter.identifier] = hierarchyFrontMatter;
ctx.counter += 10;
if (!paraUrl) {
ctx.logger.warn(`Warning: .navigation menu has "${paraContent.trim()}" without url near: "${ctx.lastContent.trim()}"`);
}
}
}
function processList(textList: TextList, ctx: NavigationProcessContext, level = 0) {
for (const textListItem of textList.list) {
for (const paraOrList of textListItem.list) {
switch (paraOrList.type) {
case 'list':
processList(<TextList>paraOrList, ctx, level + 1);
break;
case 'paragraph':
processPara(<TextParagraph>paraOrList, ctx, level);
break;
}
}
}
}
// https://developers.google.com/docs/api/concepts/structure
export async function generateNavigationHierarchy(doc: DocumentContent, logger: Logger): Promise<NavigationHierarchy> {
const ctx: NavigationProcessContext = {
headerCounter: 1,
counter: 30,
levelParent: {},
logger: logger,
result: {},
lastContent: 'START'
};
for (const structuralElement of doc.body.text.list) {
if (structuralElement.type !== 'list') {
continue;
}
const textList: TextList = <TextList>structuralElement;
processList(textList, ctx);
}
return ctx.result;
}