思源笔记折腾记录 - 快速输入

本贴最后更新于 470 天前,其中的信息可能已经时移俗易

一、代码实现

啊 这次这个可能有一定的风险.

siyuan-noob 升级了一下之后,现在可以注册新的菜单类型了.

import { 注册自定义菜单类型 } from "siyuan-noob/customMenu/index.js";
import { getSelectionPosition } from "siyuan-noob/utilFront/range.js";
import {
  hasClosestBlock,
  hasClosestByTag,
} from "siyuan-noob/utilFront/hasClosest.js";
import { 注册表 } from "siyuan-noob/commonStruct";
import 核心api from "siyuan-noob/utilKernel/kernelApi";

这里注册一个新的菜单类型,就叫行内唤起菜单吧.@style{}

let 旧目标节点
let 行内唤起菜单;
行内唤起菜单 = 注册自定义菜单类型(
  //决定了菜单的标题
  "快速插入",
  //决定了菜单监听的事件类型有哪些
  ["input","beforeinput", "keydown" ,"keypress"],
  //判定函数,决定了菜单是否显示
  (event) => {  
    if (getSelection().rangeCount>0&&getSelection().getRangeAt(0).commonAncestorContainer.textContent) {
      行内唤起菜单.菜单状态.当前块元素 = hasClosestBlock(
        getSelection().getRangeAt(0).commonAncestorContainer
      );
      if (行内唤起菜单.菜单状态.当前块元素) {
        行内唤起菜单.菜单状态.当前块id =
          行内唤起菜单.菜单状态.当前块元素.getAttribute("data-node-id");
        行内唤起菜单.菜单状态.当前节点 =
          getSelection().getRangeAt(0).commonAncestorContainer;
        行内唤起菜单.菜单状态.rangePosition = getSelectionPosition(event.target);
        行内唤起菜单.菜单状态.range = getSelection().getRangeAt(0).cloneRange();
        行内唤起菜单.菜单状态.当前行内元素 = hasClosestByTag(
          getSelection().getRangeAt(0).commonAncestorContainer,
          "SPAN"
        );
      }
      if(event.code&&(event.code==="ArrowLeft"||event.code==="ArrowRight")
	&&旧目标节点 === 行内唤起菜单.菜单状态.当前节点
	&&!window.siyuan.menus.menu.element.querySelector('.b3-menu__submenu')
	){
	window.siyuan.menus.menu.remove()
	return
      }
      旧目标节点 = 行内唤起菜单.菜单状态.当前节点
      return (
        行内唤起菜单.菜单状态.当前块元素 &&
        行内唤起菜单.待渲染菜单项目数组 &&
        行内唤起菜单.待渲染菜单项目数组[0]
      );
    } else {
      return false;
    }
  },
  //坐标计算函数,决定了菜单浮现的位置
  (event) => {
    return {
      x: 行内唤起菜单.菜单状态.rangePosition.left + 18,
      y: 行内唤起菜单.菜单状态.rangePosition.top + 18,
    };
  },
  //监听对象,决定了对哪个元素进行监听
  document,
  //事件配置,决定了事件监听器的options选项
  true
);
//筛选函数,这个函数决定了单个选项会不会渲染
行内唤起菜单.筛选函数 = (item) => {
  return item.行内关键词判断函数(行内唤起菜单.菜单状态);
};
//这里使得这个菜单的项目可以在外部注册
let 行内唤起菜单注册表 = new 注册表("自定义关键词菜单");
行内唤起菜单.菜单注册表 = 行内唤起菜单注册表.items;

//这里说是其实是通过render函数批量注册了一堆菜单项目

先来一个插入笔记内关键词的看看

let 词表 = [];
//这样在其他地方也可以通过同一个名字的注册表来增加关键词
let 关键词注册表 = new 注册表("关键词注册表");
行内唤起菜单注册表.注册({
  id: "快捷关键词输入",
  点击回调函数: async (e) => {
    let 当前块id = 行内唤起菜单.菜单状态.当前块id;
  },
  行内关键词判断函数: (菜单状态) => {
    if (
      行内唤起菜单.菜单状态.当前节点 
      //下面这两行代码的意思是只有输入@@的时候才会显示出那个输入框,我这里先注释掉了,需要的可以加上
      //&&
      //行内唤起菜单.菜单状态.当前节点.textContent.indexOf("@@") >= 0
    ) {
      let textContent = 行内唤起菜单.菜单状态.当前节点.textContent;
      if (!textContent.trim()) {
        return;
      }
      词表 = [];
      //这个函数会返回关键词注册表里面的所有项目
      let 已注册关键词表 = 关键词注册表.list();
      已注册关键词表.forEach((注册项) => {
        let flag;
        if (注册项.唤起词列表) {
	  //如果唤起词列表里面有的话
          注册项.唤起词列表.forEach((唤起词) => {
            if (唤起词 === textContent.trim()) {
              flag = true;
            }
	  
            if (唤起词.indexOf(textContent.split("@@").pop()) > -1) {
              flag = true;
            }
          });
        }
	//如果id包含的话
        if (注册项.id.indexOf(textContent.trim()) > -1) {
          flag = true;
        }
        if (注册项.id.indexOf(textContent.split("@@").pop()) > -1) {
          flag = true;
        }
	//如果内容命中了的话
        if (注册项.内容.indexOf(textContent.trim()) > -1) {
          flag = true;
        }
        if (注册项.内容.indexOf(textContent.split("@@").pop()) > -1) {
          flag = true;
        }
        flag ? 词表.push(注册项.内容) : null;
      });
      //默认把当前窗口所有行内元素全都加上去
      let list = document.querySelectorAll(
        ".protyle-wysiwyg div[data-node-id] span:not(.hljs span)"
      );
      list.forEach((el) => {
        let text = el.innerText.trim();
        if (
          textContent.trim() !== "" &&
          text &&
          text.indexOf(textContent.split("@@").pop()) > -1 &&
          el.className !== "hljs-string" &&
          text !== textContent.split("@@").pop()
        ) {
          词表.push(text);
        }
      });
      词表 = Array.from(new Set(词表));
      return textContent.trim().length >= 2 && 词表.length;
    }
  },
  render: () => {
    let html = "";
    let div = document.createElement("div");

    词表.forEach((spanText) => {
      html += `
    <button style="width: calc(100% - 16px)" 
    class="b3-menu__item">
      <span class="b3-menu__label">${Lute.EscapeHTMLStr(spanText.trim())}</span>
    </button>  
    `;
    });
    词表 = [];
    div.innerHTML = html;
    div.querySelectorAll("button").forEach((button) => {
      button.addEventListener("click", (e) => {
        let value = hasClosestByTag(e.target, "BUTTON").querySelector(
          "span"
        ).innerText;
        if (行内唤起菜单.菜单状态.当前节点.textContent.indexOf("@@") > -1) {
          行内唤起菜单.菜单状态.当前节点.textContent =
            行内唤起菜单.菜单状态.当前节点.textContent.slice(
              0,
              行内唤起菜单.菜单状态.当前节点.textContent.lastIndexOf("@@")
            ) + value;
        } else {
          行内唤起菜单.菜单状态.当前节点.textContent = value;
        }
        var range = document.createRange();
        range.selectNodeContents(行内唤起菜单.菜单状态.当前节点);
        range.collapse(false);
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
        window.siyuan.menus.menu.remove();
      });
    });
    setTimeout(() => {
      div.querySelectorAll("button").forEach((button) => {
        window.siyuan.menus.menu.append(button);
        if (
          window.siyuan.menus.menu.element.getBoundingClientRect().bottom >=
          window.innerHeight
        ) {
          window.siyuan.menus.menu.element.style.top =
            window.siyuan.menus.menu.element.getBoundingClientRect().top -
            window.siyuan.menus.menu.element.getBoundingClientRect().bottom +
            window.innerHeight +
            "px";
        }
      });
      div.remove();
    });
    return div;
  },
});

然后我们来试试增加一些关键词,先把所有文档以及带有命名和别名的块加进去,限制 1000 个,反正你自己改吧.

//这个你其实可以自己改掉
关键词注册表.注册({
  id: "思源笔记",
  唤起词: ["siyuan", "sy"],
  内容: "思源笔记还可以啊",
});

//这里是通过sql将所有带有命名和别名的块以及文档块都弄进去了
核心api.sql(
  {
    stmt: `select * from blocks where name or alias  or type = 'd' limit 1000`,
  },
  "",
  (data) => {
    data.forEach((block) => {
      let 唤起词 = [];
      if (block.alias) {
        唤起词 = 唤起词.concat(block.alias.split(","));
      }
      if (block.name) {
        唤起词.push(block.name);
      }
      if (block.title) {
        唤起词.push(block.title);
      }
      关键词注册表.注册({
        id: block.id,
        唤起词: 唤起词,
        内容: block.content,
      });
    });
  }
);

为了测试它的表现,我们再弄一个新的菜单项,这个菜单项的作用就是在你输入 @style 之后,如果再输入一段内容就可以设置当前块的样式

//注册块样式
let styleData;
行内唤起菜单注册表.注册({
  id: "快捷块样式",
  行内关键词判断函数: (菜单状态) => {
    if (
      菜单状态.当前节点 &&
      (菜单状态.当前节点.wholeText||菜单状态.当前节点.textContent).indexOf("@style") >-1
    ) {
      let wholeText = 菜单状态.当前节点.wholeText;
      try {
         styleData = new Function(`"use strict";return (${wholeText.split("@style").pop()})`)();
        return true;
      } catch (e) {
        return false;
      }
    }
  },
  文字: "设置为块样式",
  图标: "#iconFormat",
  点击回调函数: () => {
    let element =行内唤起菜单.菜单状态.当前节点.parentElement
    Object.getOwnPropertyNames(styleData).forEach((ruler) => {
      行内唤起菜单.菜单状态.当前块元素.style[ruler] = styleData[ruler];
      行内唤起菜单.菜单状态.当前节点.parentElement.innerText=行内唤起菜单.菜单状态.当前节点.wholeText.slice(
        0,
        行内唤起菜单.菜单状态.当前节点.wholeText.lastIndexOf("@style")
      );
      element.insertAdjacentHTML("beforeend", "<wbr>");
    });
    var range = document.createRange();
    let wbr=element.querySelector('wbr')
    range.selectNodeContents(element.querySelector('wbr'));
    range.collapse(false);
    const selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
    wbr.remove()
    window.siyuan.menus.menu.remove();
  },
});

比如说 @style{color:'red'}就可以当前块的样式设置成红色字体.

或者这种形式也可以 @style(()={return {color:'red'}})()


安装方式

这还是一个可运行代码片段,可以使用这个代码片段来进行安装,话说有老哥应该已经弄了一个运行代码的插件来着

思源笔记折腾记录 - 运行你的笔记 - 链滴 (ld246.com)

直接剪藏这篇笔记之后,用运行到代码片段功能安装到代码片段就可以了。

使用效果

来我来打个样

快速输入内容


如果这玩意对你有用可以去爱发电给我买杯咖啡

leolee9086 正在创作一些简单的技术教程和小工具,以及设计方面内容 | 爱发电 (afdian.net)

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    20156 引用 • 77717 回帖
2 操作
leolee 在 2023-04-10 01:44:54 更新了该帖
leolee 在 2023-04-10 00:17:47 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...