Tiddlywiki 5.2.7版本中Markdown toc按钮失效
查到 const root = $tw.wiki.parseTiddler(this.tocTitle).tree;这行代码不能返回markdown正文里面的标题导致,相关接口我不太熟悉,不知道怎么修,可以修一下吗
https://github.com/Gk0Wk/TiddlySeq/blob/master/src/page-toc/PageTOCWidget.ts
这里根据tiddler标题获取那个tree,但是对于markdown来说,html下还有一层
由于ts插件修改调试不是很方便(懒), 所以这里仅给出Chatgpt参考代码, 可用性未知
如果当前标题下多嵌套了一层 <div>,可以在 getTOCInfo 方法中进行相应的调整。以下是可能的修改方案:
-
找到以下代码块:
$tw.utils.each([...contents], node => { if (node.type !== 'element') { return; } if (this.includeHeaderMap[(node as any).tag as HeaderTag] !== true) { return; } // Render contents of header contents.length = 1; contents[0] = node; const container = $tw.fakeDocument.createElement('div'); $tw.wiki.makeWidget({ tree: root }).render(container, null); headers.push({ tag: (node as any).tag, count: headersCount[(node as any).tag as HeaderTag]++, text: container.textContent || '', }); }); -
将该代码块替换为以下代码块:
const processNode = (node: IWikiASTNode) => { if (node.type !== 'element') { return; } if (this.includeHeaderMap[(node as any).tag as HeaderTag] !== true) { return; } // Find text within the header element let text = ''; for (const childNode of node.children || []) { if (childNode.type === 'text') { text += childNode.text; } } // Recursive processing for nested div elements for (const childNode of node.children || []) { if (childNode.type === 'element' && childNode.tag === 'div') { processNode(childNode); } } headers.push({ tag: (node as any).tag, count: headersCount[(node as any).tag as HeaderTag]++, text: text.trim(), }); }; $tw.utils.each([...contents], processNode);这个修改会递归地处理标题节点下的子节点,将嵌套在
<div>中的文本内容添加到标题文本中。
通过以上修改,即可在处理标题时,包括嵌套在一层 <div> 内的文本内容,生成正确的目录列表。
发现你改的代码还是有问题,修改了下,目前在markdown中没问题,在tw文档中点击事件注入没做适配( 因为我自己也不使用)
import {
HTMLTags,
IParseTreeNode,
IChangedTiddlers,
IWidgetInitialiseOptions,
IWikiASTNode,
} from 'tiddlywiki';
import { widget as Widget } from '$:/core/modules/widgets/widget.js';
type HeaderTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
class PageTOCWidget extends Widget {
private tocNodeTag: HTMLTags = 'div';
private tocHeaderNodeTag: HTMLTags = 'p';
private tocNodeClass: string = 'gk0wk-tiddlertoc-container';
private tocHeaderNodeClassPrefix: string = 'gk0wk-tiddlertoc-';
private tocTitle: string = '';
private emptyMessage: string = '';
private scrollMode: 'start' | 'center' | 'end' | 'nearest' = 'center';
private includeHeaderMap: Record<HeaderTag, boolean> = {
h1: true,
h2: true,
h3: true,
h4: true,
h5: true,
h6: true,
};
initialise(parseTreeNode: IParseTreeNode, options: IWidgetInitialiseOptions) {
super.initialise(parseTreeNode, options);
this.computeAttributes();
}
execute() {
this.tocTitle = this.getAttribute(
'tiddler',
this.getVariable('currentTiddler'),
);
this.tocNodeTag = this.getAttribute('tag', 'div') as any;
if (($tw.config.htmlUnsafeElements as any).includes(this.tocNodeTag)) {
this.tocNodeTag = 'div';
}
this.tocHeaderNodeTag = this.getAttribute('headerTag', 'p') as any;
if (
($tw.config.htmlUnsafeElements as any).includes(this.tocHeaderNodeTag)
) {
this.tocHeaderNodeTag = 'p';
}
this.tocNodeClass = this.getAttribute(
'class',
'gk0wk-tiddlertoc-container',
);
this.tocHeaderNodeClassPrefix = this.getAttribute(
'headerClassPrefix',
'gk0wk-tiddlertoc-',
);
this.emptyMessage = this.getAttribute('emptyMessage', '');
this.includeHeaderMap.h1 = this.getAttribute('h1', 'yes') === 'yes';
this.includeHeaderMap.h2 = this.getAttribute('h2', 'yes') === 'yes';
this.includeHeaderMap.h3 = this.getAttribute('h3', 'yes') === 'yes';
this.includeHeaderMap.h4 = this.getAttribute('h4', 'yes') === 'yes';
this.includeHeaderMap.h5 = this.getAttribute('h5', 'yes') === 'yes';
this.includeHeaderMap.h6 = this.getAttribute('h6', 'yes') === 'yes';
this.scrollMode = this.getAttribute('scrollMode', 'center') as any;
if (!['start', 'center', 'end', 'nearest'].includes(this.scrollMode)) {
this.scrollMode = 'center';
}
}
render(parent: Node, nextSibling: Node | null) {
this.parentDomNode = parent;
this.execute();
// 递归检测
if (this.parentWidget!.hasVariable('page-toc-recursion-detection', 'yes')) {
this.domNodes.push(
parent.appendChild(this.document.createTextNode('[Page TOC]')),
);
return;
}
this.setVariable('page-toc-recursion-detection', 'yes');
// 渲染目录
const tocNode = $tw.utils.domMaker(this.tocNodeTag, {
class: this.tocNodeClass,
});
this.domNodes.push(tocNode);
try {
const toc = this.getTOCInfo();
if (!toc || toc.headers.length === 0) {
tocNode.insertBefore(
$tw.utils.domMaker(this.tocHeaderNodeTag, {
class: `${this.tocHeaderNodeClassPrefix}empty`,
text: this.emptyMessage,
}),
nextSibling,
);
} else {
for (let i = 0, len = toc.headers.length; i < len; i++) {
const { tag, text, count } = toc.headers[i];
const headerNode = $tw.utils.domMaker(this.tocHeaderNodeTag, {
class: `${this.tocHeaderNodeClassPrefix}${tag}`,
text,
});
if ($tw.browser) {
// eslint-disable-next-line @typescript-eslint/no-loop-func
headerNode.addEventListener('click', () => {
const target = document
.querySelector(
`.tc-tiddler-frame[data-tiddler-title="${toc.title.replace(
'"',
'\\"',
)}"]`,
)
?.querySelectorAll?.(`.tc-tiddler-body > .markdown > ${tag}`)?.[count];
if (!target) {
return;
}
switch (this.scrollMode) {
case 'center':
case 'nearest': {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
target.scrollIntoView({
behavior: 'smooth',
block: this.scrollMode,
});
break;
}
default: {
target.scrollIntoView({
behavior: 'auto',
block: this.scrollMode,
});
if (this.scrollMode === 'end') {
document.body.scrollTop += 100;
if (document.scrollingElement) {
document.scrollingElement.scrollTop += 100;
}
} else {
document.body.scrollTop -= 100;
if (document.scrollingElement) {
document.scrollingElement.scrollTop -= 100;
}
}
}
}
});
}
tocNode.appendChild(headerNode);
}
}
} catch (e) {
console.error(e);
tocNode.textContent = String(e);
}
parent.insertBefore(tocNode, nextSibling);
}
refresh(changedTiddlers: IChangedTiddlers) {
const changedAttributes = this.computeAttributes();
if (
$tw.utils.count(changedAttributes) > 0 ||
Object.hasOwnProperty.call(changedAttributes, this.tocTitle)
) {
this.refreshSelf();
this.refreshChildren(changedTiddlers);
return true;
}
return this.refreshChildren(changedTiddlers);
}
getTOCInfo() {
// Check empty
if (this.tocTitle === '') {
return undefined;
}
const currentTiddler = $tw.wiki.getTiddler(this.tocTitle);
if (!currentTiddler) {
return undefined;
}
const type = currentTiddler.fields.type || 'text/vnd.tiddlywiki';
if (type !== 'text/vnd.tiddlywiki' && type !== 'text/x-markdown' && type !== 'text/markdown') {
return undefined;
}
const headers: { tag: HeaderTag; count: number; text: string }[] = [];
const headersCount: Record<HeaderTag, number> = {
h1: 0,
h2: 0,
h3: 0,
h4: 0,
h5: 0,
h6: 0,
};
const root = $tw.wiki.parseTiddler(this.tocTitle).tree;
if (root.length === 0) {
return undefined;
}
let contents = root;
// Parse params
while (['set', 'importvariables'].includes(contents[0]?.type)) {
contents = (contents[0] as IWikiASTNode).children ?? [];
}
const processNode = (node: IWikiASTNode) => {
if (node.type !== 'element') {
return;
}
// Find text within the header element
let text = '';
for (const childNode of node.children || []) {
if (childNode.type === 'text') {
text += childNode.text;
}
}
// Recursive processing for nested div elements
for (const childNode of node.children || []) {
if (childNode.type === 'element' && childNode.tag.match(/^h[1-6]$/)) {
processNode(childNode);
}
}
if (node.tag.match(/^h[1-6]$/)){
headers.push({
tag: (node as any).tag,
count: headersCount[(node as any).tag as HeaderTag]++,
text: text.trim(),
});
}
};
$tw.utils.each([...contents], processNode);
return {
title: this.tocTitle,
headers,
};
}
}
exports['page-toc'] = PageTOCWidget;
@straywriter 你的这份代码有个问题就是只能识别纯文本的标题,如果其中包含了变量微件等就看不到了。
因此最后的方式是先渲染,渲染之后再找,这样反倒是很简单,写个dfs就可以了。
于是现在tos已经可以支持任意位置的标题了,除了写在外面的、还有引用的等等,和看到的保持一致。
请升级最新版本