From 3cfaf6276c6ba8dd4450167b5f42f6845895860c Mon Sep 17 00:00:00 2001 From: ada Date: Fri, 22 Aug 2025 16:21:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=B3=D1=80=D0=B0=D1=84=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA:=20=D0=B7=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D0=BE=D1=82=D0=B5=D0=BA=D1=83=20=D0=BD=D0=B0=20ForceGraph=20?= =?UTF-8?q?=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D1=83=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85.=20=D0=92=D0=BE=D1=81=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20=D0=B3=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20graph.json=20?= =?UTF-8?q?=D1=81=20=D1=83=D0=B7=D0=BB=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D1=80?= =?UTF-8?q?=D1=91=D0=B1=D1=80=D0=B0=D0=BC=D0=B8=20=D0=BC=D0=B5=D0=B6=D0=B4?= =?UTF-8?q?=D1=83=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B0=D0=BC=D0=B8?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _pages/graph.md | 31 ++++++++---- _plugins/graph_json.rb | 22 --------- _plugins/note_graph.rb | 108 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 31 deletions(-) delete mode 100644 _plugins/graph_json.rb create mode 100644 _plugins/note_graph.rb diff --git a/_pages/graph.md b/_pages/graph.md index 1071af2..65ff2bb 100644 --- a/_pages/graph.md +++ b/_pages/graph.md @@ -1,16 +1,29 @@ --- +layout: single title: "Граф заметок" permalink: /graph/ -layout: single --- -
- +
+ + diff --git a/_plugins/graph_json.rb b/_plugins/graph_json.rb deleted file mode 100644 index a71584b..0000000 --- a/_plugins/graph_json.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'json' - -Jekyll::Hooks.register :site, :post_write do |site| - notes = site.collections["notes"].docs - nodes = [] - edges = [] - - notes.each do |note| - id = note.url - nodes << { id: id, title: note.data["title"] || note.basename_without_ext, url: note.url } - - # ищем [[ссылки]] - note.content.scan(/\[\[(.*?)\]\]/).each do |m| - target = m[0].downcase.strip.gsub(" ", "-") - target_doc = notes.find { |n| n.basename_without_ext.downcase == target } - edges << { source: id, target: target_doc.url } if target_doc - end - end - - graph = { nodes: nodes, edges: edges } - File.write(File.join(site.dest, "graph.json"), JSON.pretty_generate(graph)) -end diff --git a/_plugins/note_graph.rb b/_plugins/note_graph.rb new file mode 100644 index 0000000..4ef7243 --- /dev/null +++ b/_plugins/note_graph.rb @@ -0,0 +1,108 @@ +# _plugins/note_graph.rb +# Генерирует /graph.json с узлами и рёбрами между заметками из коллекции :notes. +# Понимает [[target]], [[target|alias]], вложенные папки, alias'ы во front matter: +# aliases: ["Короткое имя", "Другое имя"] + +require "json" + +module NoteGraph + class Generator < Jekyll::Generator + safe true + priority :low + + WIKI_RX = /\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]/.freeze + + def generate(site) + notes = (site.collections["notes"]&.docs || []) + return if notes.empty? + + # Карта идентификаторов заметок -> объект + # id = относительный путь внутри _notes без расширения, в нижнем регистре + index = {} + aliases = {} + + notes.each do |doc| + id = norm_id(doc.relative_path) # напр. "it-school/04-auto" + index[id] = { + "id" => id, + "title" => (doc.data["title"] || File.basename(id)), + "url" => doc.url + } + + # поддержка front matter aliases + Array(doc.data["aliases"]).each do |al| + aliases[norm_key(al)] = id + end + + # ещё можно сопоставлять обычное название файла без папок + base = File.basename(id) + aliases[norm_key(base)] = id + end + + # Узлы + nodes = index.values + + # Рёбра + links = [] + + notes.each do |doc| + src = norm_id(doc.relative_path) + content = doc.content.to_s + + content.scan(WIKI_RX).each do |m| + raw = m.first # то, что внутри [[ ... ]] + # цель без якоря/алиаса и расширения + target = raw.strip + target = target.sub(/\|.*/, "") # отрезать |alias + target = target.sub(/#.*/, "") # отрезать #heading + target = target.sub(/\.md$/i, "") # убрать .md + + # 1) прямое совпадение по относительному пути + dst = index[norm_id("_notes/#{target}.md")]&.dig("id") + # 2) совпадение по alias/названию файла + dst ||= aliases[norm_key(target)] + next unless dst && index[dst] + + links << { "source" => src, "target" => dst } + end + end + + data = { "nodes" => nodes, "links" => links } + + site.static_files << GeneratedJson.new(site, data) + end + + # нормализуем «ключ» + def norm_key(s) + s.to_s.downcase.strip.gsub("\\", "/") + end + + # делаем id из относительного пути документа + # "_notes/IT-School/04-auto.md" -> "it-school/04-auto" + def norm_id(relative_path) + p = relative_path.to_s + p = p.sub(%r!^_notes/!i, "") + p = p.sub(/\.md$/i, "") + p.downcase + end + + # Объект статического файла, который кладём в _site/graph.json + class GeneratedJson < Jekyll::StaticFile + def initialize(site, data) + @site = site + @base = site.source + @dir = "/" + @name = "graph.json" + @data = data + super(site, @base, @dir, @name, nil) + end + + def write(dest) + dest_path = Jekyll.sanitized_path(dest, @dir, @name) + FileUtils.mkdir_p(File.dirname(dest_path)) + File.write(dest_path, JSON.pretty_generate(@data)) + true + end + end + end +end