• Home
  • File
  • Cell
  • Section
  • Help
  • 🔬 Testing Fuzzy text search

    Table of Contents

    • Related§

    • Fuse.js§

      • Reference§

      • Implementations§

        • Viewer§
        • Repl§
    • FuzzySet§

    Related

    • testing/index
    • testing/compact_manifest

    Fuse.js

    Reference

    https://fusejs.io/↗

    https://dev.to/noclat/using-fuse-js-with-react-to-build-an-advanced-search-with-highlighting-4b93↗

    https://fusejs.io/examples.html#extended-search↗

    jscollapsereference
    return (async (fn) => {
      const { default: Fuse } = await import(
        "https://cdn.jsdelivr.net/npm/fuse.js@6.4.6/dist/fuse.esm.js"
      );
      const manifest = await fetch("/manifest.json").then((res) => res.json());
      const fuse = new Fuse(manifest.nodes, {
        includeScore: true,
        keys: ["title", "id"],
      });
    
      // 3. Now search!
      return fuse.search("../../..", { limit: 5 });
    })();
    
    

    Implementations

    Viewer

    js../plugins/viewers/search.jsplugincollapsetype=Viewerof=search2
    export const viewer = ({ node, React }) => {
      const { useState } = React;
      const meta = node?.properties?.meta || {};
    
      const [results, setResults] = useState(null);
      // Recursively builds JSX output adding `<mark>` tags around matches
      const highlight = (value, indices = [], i = 1) => {
        try {
          const pair = indices[indices.length - i];
          return !pair ? (
            value
          ) : (
            <>
              {highlight(value.substring(0, pair[0]), indices, i + 1)}
              <mark>{value.substring(pair[0], pair[1] + 1)}</mark>
              {value.substring(pair[1] + 1)}
            </>
          );
        } catch (err) {
          return err.message;
        }
      };
      const search = async (src) => {
        const t = Date.now();
    
        const { default: Fuse } = await import(
          "https://cdn.jsdelivr.net/npm/fuse.js@6.4.6/dist/fuse.esm.js"
        );
        const manifest = meta.manifest
          ? await fetch("/manifest.json").then((res) => res.json())
          : {};
    
        const fullLocal = await (async (fn) => {
          const path = lit.utils.path;
          const all = [];
          const visit = async (root) => {
            try {
              const list = await lit.fs.readdir(root);
              return Promise.all(
                list.map(async (key) => {
                  const pathname = path.join(root, key);
                  const stat = await lit.fs.stat(pathname).catch((e) => {});
                  let contents;
                  if (
                    !stat ||
                    key === ".git" ||
                    !key ||
                    pathname.endsWith(lit.location.src)
                  ) {
                  } else if (stat.type === "dir") await visit(pathname);
                  else if (pathname.endsWith(".lit")) {
                    contents = await lit.fs.readFile(pathname, {
                      encoding: "utf8",
                      localOnly: true,
                    }); //.slice(0,10);
                    contents.split("\n").map((line, index) =>
                      all.push({
                        pathname,
                        type: "line",
                        contents: line,
                        lineNo: index,
                      })
                    );
                  }
                  const item = { pathname, type: stat?.type, contents: pathname };
                  all.push(item);
                  return item;
                })
              );
            } catch (err) {
              alert(err.message);
            }
          };
    
          await visit(meta.ns || "/");
          return all;
        })();
    
        // return fullLocal
    
        const fuse = new Fuse(fullLocal, {
          ignoreLocation: true,
          includeScore: true,
          includeMatches: true,
          ignoreFieldNorm: true,
          minMatchCharLength: 3,
          useExtendedSearch: true,
          threshol: 0.3,
          keys: ["contents"],
        });
    
        // 3. Now search!
        const query = src.trim();
        const msg = (
          <div>
            Results for <span>{query}</span>. In{" "}
            <span>{(Date.now() - t) / 1000}</span> seconds.
          </div>
        );
        const res = fuse
          .search(query, { limit: 10 })
          //.map(x=>x.matches.map(x=>x.indices))
          .map((x) => [
            x.score,
            x.item.pathname,
            x.refIndex,
            x.matches,
            x.item.type,
            x.item.lineNo,
            x.item.contents,
          ]);
        console.log(msg, res);
        setResults({ msg, results: res });
      };
    
      const inputStyle = {
        display: "block",
        width: "100%",
        fontSize: "1em",
      };
    
      return (
        <div>
          <input
            style={inputStyle}
            placeholder={`Search (local ${meta.ns || "/"}) file system`}
            onChange={(ev) => search(ev.target.value, meta)}
          />
          <div>{results && results.msg}</div>
          <div>
            {results &&
              results.results.map((res) => {
                const [score, pathname, index, matches, type, lineNo, val] = res;
                const href = pathname.replace(
                  /.(lit|md)$/,
                  ".html?file=" + pathname
                );
                return (
                  <div style={{ marginBottom: "0.6em" }}>
                    <div>
                      <a href={href}>
                        <strong>{pathname}</strong>
                      </a>{" "}
                      type: <span>{type}</span> score:{" "}
                      <span>{(1 - score) * 100}</span>
                    </div>
                    <blockquote>{highlight(val, matches[0].indices)}</blockquote>
                  </div>
                );
              })}
          </div>
        </div>
      );
    };
    
    
    search2ns=/

    Repl

    js../plugins/repls/search.jsplugincollapsetype=replof=search
    export const repl = async (src, meta) => {
      const t = Date.now();
    
      const { default: Fuse } = await import(
        "https://cdn.jsdelivr.net/npm/fuse.js@6.4.6/dist/fuse.esm.js"
      );
      const manifest = meta.manifest
        ? await fetch("/manifest.json").then((res) => res.json())
        : {};
      // Recursively builds JSX output adding `<mark>` tags around matches
      const highlight = (value, indices = [], i = 1) => {
        const pair = indices[indices.length - i];
        return !pair
          ? value
          : `${highlight(
              value.substring(0, pair[0]),
              indices,
              i + 1
            )}***${value.substring(pair[0], pair[1] + 1)}***${value.substring(
              pair[1] + 1
            )}`;
      };
      const fullLocal = await (async (fn) => {
        const path = lit.utils.path;
        const all = [];
        const visit = async (root) => {
          try {
            const list = await lit.fs.readdir(root);
            return Promise.all(
              list.map(async (key) => {
                const pathname = path.join(root, key);
                const stat = await lit.fs.stat(pathname).catch((e) => {});
                let contents;
                if (key === ".git" || !key || pathname.endsWith(lit.location.src)) {
                } else if (stat.type === "dir") await visit(pathname);
                else if (pathname.endsWith(".lit")) {
                  contents = await lit.fs.readFile(pathname, {
                    encoding: "utf8",
                    localOnly: true,
                  }); //.slice(0,10);
                  contents.split("\n").map((line, index) =>
                    all.push({
                      pathname,
                      type: "line",
                      contents: line,
                      lineNo: index,
                    })
                  );
                }
                const item = { pathname, type: stat.type, contents: pathname };
                all.push(item);
                return item;
              })
            );
          } catch (err) {
            alert(err.message);
          }
        };
    
        await visit(meta.ns || "/");
        return all;
      })();
    
      // return fullLocal
    
      const fuse = new Fuse(fullLocal, {
        ignoreLocation: true,
        includeScore: true,
        includeMatches: true,
        ignoreFieldNorm: true,
        minMatchCharLength: 4,
        useExtendedSearch: true,
        keys: ["contents"],
      });
    
      // 3. Now search!
      const query = src.trim();
      const msg = `Results for search "**${query}**". In **${
        (Date.now() - t) / 1000
      }** seconds.\n\n`;
    
      return (
        msg +
        fuse
          .search(query, { limit: 10 })
          //.map(x=>x.matches.map(x=>x.indices))
          .map((x) => [
            x.score,
            x.item.pathname,
            x.refIndex,
            x.matches,
            x.item.type,
            x.item.lineNo,
            x.item.contents,
          ])
          .map(
            ([score, pathname, index, matches, type, lineNo, val]) =>
              `1. **[${pathname}](${pathname})** *${(1 - score).toFixed(2) * 100}*
    
          ${type} ${lineNo} ${val}`
          )
          .join("\n")
      );
    };
    
    
    textsearch> md
    type=prox
    
    mdUpdated 85w ago

    Results for search "type=prox". In 0.043 seconds.

    1. /index.lit 100

      line 30 ```js !plugin type=proxy id=corsProxy !collapse < ./plugins/other/cors-proxy.js

    2. /config.lit 100

      line 370 ```js !xplugin type=proxy id=corsProxy !collapse < ./plugins/other/cors-proxy.js

    3. /testing/isomorphic_git.lit 100

      line 337 ```js !plugin type=proxy id=corsProxy !collapse < ../plugins/other/cors-proxy.js

    4. /index.lit 67

      line 54 .litDigital GardensTools for ThoughtLearning in PublicThinking in PublicKnowledge GraphLiterate ProgrammingRunbooksInteractive NotebooksGuided LearningShow don't TellNo CodeLow CodeMore CodeRapid PrototypingRead Eval Print Loop<!--MD5=[779870beb8e5aaaf03ae1f70915266b5]

    5. /config.lit 67

      line 115 ```js !plugin type=onload id=sw

    6. /config.lit 67

      line 135 ```js !xplugin type=transformer of=js !collapse < ../plugins/transformers/prettier.js

    7. /config.lit 67

      line 432 ```js !xplugin of=uml !collapse #viewer type=repl

    8. /config.lit 67

      line 524 root nodesome first level nodesecond level nodeanother second level nodeanother first level node<!--MD5=[8c1f1039f1f6300557f480a2340cac76]

    9. /testing/isomorphic_git.lit 67

      line 444 type="text"

    10. /config.lit 56.00000000000001

      line 20 ```js !plugin type=theme id=test

    FuzzySet

    https://github.com/Glench/fuzzyset.js↗

    js
    return import('https://cdn.skypack.dev/fuzzyset').then( FuzzySet => {
    
      const f = new FuzzySet.default()
      f.add("the text of mine")
      f.add("the text of someone else")
      f.add("other texts")
      return f.get("text of")
    })
    
    
    txtUpdated 139.9w ago
    [ [ 0.4375, 'the text of mine' ] ]
    

    Backlinks (2)

    1. 2021-05-23
    2. 2021-07-23