Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tiddlywiki 5.2.7版本中Markdown toc按钮失效 #9

Open
straywriter opened this issue Jun 9, 2023 · 4 comments
Open

Tiddlywiki 5.2.7版本中Markdown toc按钮失效 #9

straywriter opened this issue Jun 9, 2023 · 4 comments

Comments

@straywriter
Copy link

查到 const root = $tw.wiki.parseTiddler(this.tocTitle).tree;这行代码不能返回markdown正文里面的标题导致,相关接口我不太熟悉,不知道怎么修,可以修一下吗

@straywriter
Copy link
Author

straywriter commented Jun 12, 2023

https://github.com/Gk0Wk/TiddlySeq/blob/master/src/page-toc/PageTOCWidget.ts
L%O9PX@I9(~$ZRQDWWH2J3X

这里根据tiddler标题获取那个tree,但是对于markdown来说,html下还有一层

导致tree返回的列表失败,我就卡在这里不知到怎么改了

@oeyoews
Copy link
Contributor

oeyoews commented Jun 12, 2023

由于ts插件修改调试不是很方便(懒), 所以这里仅给出Chatgpt参考代码, 可用性未知

如果当前标题下多嵌套了一层 <div>,可以在 getTOCInfo 方法中进行相应的调整。以下是可能的修改方案:

  1. 找到以下代码块:

    $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 || '',
      });
    });
  2. 将该代码块替换为以下代码块:

    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> 内的文本内容,生成正确的目录列表。

@straywriter
Copy link
Author

发现你改的代码还是有问题,修改了下,目前在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;

Gk0Wk added a commit that referenced this issue Oct 13, 2023
@Gk0Wk
Copy link
Owner

Gk0Wk commented Oct 13, 2023

@straywriter 你的这份代码有个问题就是只能识别纯文本的标题,如果其中包含了变量微件等就看不到了。

因此最后的方式是先渲染,渲染之后再找,这样反倒是很简单,写个dfs就可以了。

于是现在tos已经可以支持任意位置的标题了,除了写在外面的、还有引用的等等,和看到的保持一致。

请升级最新版本

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants