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

yaml-front-matter 提供自定义渲染功能 #1751

Open
2061360308 opened this issue Jan 11, 2025 · 1 comment
Open

yaml-front-matter 提供自定义渲染功能 #1751

2061360308 opened this issue Jan 11, 2025 · 1 comment

Comments

@2061360308
Copy link

你在什么场景下需要该功能?

我在做 Hugo 的博客文章编辑器,这个场景下大部分需要的 Front Matter 都是可以预测,且需要 Date 这样的数据手动输入太麻烦。我的目的是能够给编辑器提供更友好且智能的FrontMatter编辑方案。

描述最优的解决方案

最好是能够暴露一个像已有的自定义渲染器那样的接口(已有的测试貌似只能对代码块起作用)。

描述候选解决方案

如果您实在是没有时间支持这种小众功能,我希望能够从您那儿了解到codeRender这类模块是如何工作的。

我在源码中已经翻找到了相关的代码(src\ts\wysiwyg\input.ts),但是我不明白即时渲染模式下用户看到的和实际内在输入是怎么区分的,我分析了yaml-front-matter块和代码块的区别,如下图,怀疑是vditor-wysiwyg__blockvditor-wysiwyg__previewyaml-front-matter块没有vditor-wysiwyg__preview。但在这又是lute模块的问题了,继续深入后我发现只提供了lute.min.js没有相关的未压缩版本。
图片

其他信息

下面给出一张最终希望功能的大致动态图(简单更改了src\ts\wysiwyg\input.ts,具体代码在文末)

umf5f-841l5

我预计特殊处理的 Hugo Front Matter 字段有

  • 标题:预计展示位普通输入框
  • 是否为草稿:预计显示为开关
  • 创建和修改日期:预计添加日期时间选择器,自动补充
  • 分类和标签以及关键词:预计显示为标签输入框,并且给统计文章词频一键添加标签和关键词
  • 文章描述:预计显示为文本框,添加自动补充摘要功能

以上是我的所有想法,感谢帮助!

vditor.wysiwyg.element.querySelectorAll(".vditor-wysiwyg__block[data-type='yaml-front-matter']")
            .forEach((item: HTMLElement) => {
                item.querySelectorAll("code").forEach((codeElement: HTMLElement) => {
                    codeElement.className = "language-yaml";
                    codeElement.innerHTML = `<input type="text" placeholder="title" style="width:100%;height: 14px;" />`;
                    console.log("这里是 input 的 yaml-front-matter")
                });
            });
@2061360308
Copy link
Author

2061360308 commented Jan 12, 2025

今天又研究了一下,发现直接在渲染的时候这么搞还是有点难度的,我参考了CodeBlock的实现过程,也动手在浏览器中手动动态更改代码尝试了一下,发现了一些问题。

首先就是现在CodeBlock的模式是点击就会把原始代码显示出来,这种放到复杂情况又是文本输入框、标签选择框又是开关,时间拾取器这些,就有些不合适

此外就算暴露接口,我也已经意识到了渲染器也会很难写。(直接用Vue在编辑器外面会简单很多)

所以最后我还是先采用笨办法实现了,经过阅读源码我已经知道不能随意改动Vditor内部的元素,所以这次就直接放在外面,至于编辑器是内部自滚动,现在我加上yaml-front-matter这个块后就得重新设置滚动区域。我直接暴力使用important覆盖掉了样式(虽然不雅观吧,但是非常有效,我自己随手写的也不要求太严格了)

c9whq-hjkdb
sis80-yppdn

可以看出最终效果还是不错的,能够可视化几种常见的基本类型数据,然后编辑器也能够正常滚动,工具栏吸顶也没问题。最重要的是其实也看不出是拼接的感觉,可以说完美符合我的预期

下面放我主要的代码,希望能帮助到和我有一样需求的人。

.vditor {
  border: none !important;
  height: 100% !important;
}

.vditor-toolbar {
  padding: 0 !important;
}

.vditor-reset {
  overflow: visible !important;
  padding: 0 40px !important;
}

.vditor-outline {
  display: none !important;
}

打开文件以及获取文件内容的时候分别要进行front matter与主内容的分离和合并操作

const getContent = () => {
  /**
   * 获取编辑器内容
   * 会将 frontMatter 和 content 合并
   */
  let yamlFrontMatter = stringifyFrontMatter(frontMatterObject);
  let content = (vditorInstance as any).getValue();
  return yamlFrontMatter + content;
};

const openFile = async (path: string) => {
  /**
   * 读取文件内容并设置到编辑器中
   * 会将 frontMatter 的内容分离出来,并设置到 frontMatter 中
   * 由 createVditorInstance 创建完Vditor实例后调用
   *  */
  console.log("openFile", path);
  fs.get(path).then((content) => {
    console.log("openFile content", content);
    // 分离 frontMatter 和 content
    let result: { frontMatter: Record<string, any>; content: string } =
      splitFrontMatter(content);
    frontMatterObject = result.frontMatter;
    for (const key in frontMatterObject) {
      let value = frontMatterObject[key];
      let type = typeof value;

      let item_type: fronMatterValueType = fronMatterValueType.string;

      if (type === "string") {
        item_type = fronMatterValueType.string;
      } else if (type === "number") {
        item_type = fronMatterValueType.number;
      } else if (type === "boolean") {
        item_type = fronMatterValueType.boolean;
      } else if (type === "object") {
        if (Array.isArray(value)) {
          item_type = fronMatterValueType.array;
        } else if (value instanceof Date) {
          item_type = fronMatterValueType.date;
        }
      } else {
        continue; // 其他类型不处理, 暂时不支持
      }

      frontMatter.value.push({
        name: key,
        type: item_type,
        value: value,
      });
    }

    frontMatterBack = frontMatter.value.map((item) => item.name);

    // 设置清理后的内容到编辑器中
    if (vditorInstance) {
      vditorInstance.setValue(result.content, true);
    }
  });
};

// yaml的处理工具函数

export const splitFrontMatter = (content: string) => {
  const yalmPattern = /^---[\s\S]*?---/;
  const yamlMatch = content.match(yalmPattern);
  const yamlContent = yamlMatch ? yamlMatch[0] : "";
  const cleanedContent = content.replace(yamlContent, "");

  let frontMatter = {};

  if (yamlContent) {
    frontMatter = parseFrontMatter(yamlContent);
  }

  return {
    frontMatter,
    content: cleanedContent,
  };
};

export const parseFrontMatter = (content: string) => {
  let parsedYaml = <Record<string, any>>{};
  if (content) {
    try {
      parsedYaml = yaml.load(content.replace(/^---|---$/g, "")) as Record<
        string,
        any
      >;
    } catch (e) {
      console.error("Error parsing YAML:", e);
    }
  }

  return parsedYaml;
};

export const formatDate = (date: Date, formatString: string, offset:string) => {
  return format(date, formatString) + offset;
};

export const stringifyFrontMatter = (frontMatter: Record<string, any>) => {
  console.log("frontMatterObject", frontMatter);
  for (const key in frontMatter) {
    console.log("key", key);
    let value = frontMatter[key];
    if (value instanceof Date) {
      console.log("formatDate", formatDate(value, "yyyy-MM-dd'T'HH:mm:ss", "+08:00"));
      frontMatter[key] = formatDate(value, "yyyy-MM-dd'T'HH:mm:ss", "+08:00");
    }
  }
  return `---\n${yaml.dump(frontMatter)}---\n`;
};

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

1 participant