mirror of
https://github.com/ada-dmitry/ada-dmitry.github.io.git
synced 2026-09-24 01:10:35 +00:00
Обновить визуализацию графа заметок: заменить библиотеку на ForceGraph и улучшить обработку данных. Восстановить генерацию graph.json с узлами и рёбрами между заметками.
This commit is contained in:
+22
-9
@@ -1,16 +1,29 @@
|
|||||||
---
|
---
|
||||||
|
layout: single
|
||||||
title: "Граф заметок"
|
title: "Граф заметок"
|
||||||
permalink: /graph/
|
permalink: /graph/
|
||||||
layout: single
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div id="mynetwork" style="width:100%;height:70vh"></div>
|
<div id="graph" style="height:70vh"></div>
|
||||||
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
||||||
|
<script src="https://unpkg.com/force-graph"></script>
|
||||||
<script>
|
<script>
|
||||||
fetch("{{ '/graph.json' | relative_url }}").then(r=>r.json()).then(data=>{
|
fetch('{{ "/graph.json" | relative_url }}')
|
||||||
const nodes = data.nodes.map(n=>({id:n.id,label:n.title,url:n.url}));
|
.then(r => r.json())
|
||||||
const edges = data.edges.map(e=>({from:e.source,to:e.target}));
|
.then(data => {
|
||||||
const net = new vis.Network(document.getElementById('mynetwork'),{nodes,edges},{});
|
const el = document.getElementById('graph');
|
||||||
net.on("click", p => { if(p.nodes.length){ const n = nodes.find(x=>x.id===p.nodes[0]); if(n?.url) location.href=n.url; }});
|
const Graph = ForceGraph()(el)
|
||||||
});
|
.graphData(data)
|
||||||
|
.nodeId('id')
|
||||||
|
.nodeLabel(n => n.title)
|
||||||
|
.nodeAutoColorBy('group')
|
||||||
|
.backgroundColor(getComputedStyle(document.body).backgroundColor || '#111')
|
||||||
|
.linkColor(() => 'rgba(173,216,230,0.6)')
|
||||||
|
.linkDirectionalParticles(2)
|
||||||
|
.linkDirectionalParticleSpeed(0.004)
|
||||||
|
.onNodeClick(n => window.location = n.url);
|
||||||
|
|
||||||
|
// чуть уменьшить размеры узлов/шрифтов для тёмной темы
|
||||||
|
Graph.nodeRelSize(6);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user