diff --git a/backref.go b/backref.go index 3a13e1c..59bc401 100644 --- a/backref.go +++ b/backref.go @@ -120,8 +120,8 @@ func getBackrefs(fp *FilePages, p string) (map[string][]Backref, error) { } line = metaKV.ReplaceAllString(line, "**[[$1]]**: $2") - links := renderLinks(line) - pageText := renderMarkdown2(links) + pageText := renderMarkdown2(renderLinks(line, false)) + editPageText := renderMarkdown2(renderLinks(line, true)) removeBrackets := func(r rune) rune { if r == '[' || r == ']' || r == '*' { @@ -134,6 +134,7 @@ func getBackrefs(fp *FilePages, p string) (map[string][]Backref, error) { Name: ref.Name, Title: title, LineHTML: template.HTML(pageText), + LineEditHTML: template.HTML(editPageText), Line: strings.Map(removeBrackets, ref.Link.Line), }) } diff --git a/editor/src/graph.js b/editor/src/graph.js new file mode 100644 index 0000000..11157b8 --- /dev/null +++ b/editor/src/graph.js @@ -0,0 +1,59 @@ +import {DataSet} from "vis-data/peer"; +import {Network} from "vis-network/peer"; +import $ from 'jquery'; + +function wikiGraph(selector, options) { + $(selector).each(function (i, el) { + let $el = $(el) + var nodeName = $el.data('name') + fetch('/api/graph?name=' + nodeName) + .then(res => res.json()) + .then(graph => { + var nodes = new DataSet(graph.nodes) + var edges = new DataSet(graph.edges) + + var data = { + nodes: nodes, + edges: edges + }; + + var options = { + edges: { + arrows: 'to', + color: { + highlight: 'green' + }, + chosen: { + edge: function (values, id, selected, hovering) { + if (this.from === 1) { + values.color = 'blue'; + } + } + } + }, + + nodes: { + shape: 'dot', + size: 15, + font: { + background: 'white' + } + }, + layout: { + improvedLayout: false + } + }; + + var network = new Network(el, data, options); + network.on('doubleClick', function (props) { + if (props.nodes.length) { + let nodeId = props.nodes[0] + let node = nodes.get(nodeId) + window.location.href = '/edit/' + node.label + } + }) + }) + }) +} + +export default wikiGraph diff --git a/editor/src/index.js b/editor/src/index.js index b66dc8e..4ea6d81 100644 --- a/editor/src/index.js +++ b/editor/src/index.js @@ -23,6 +23,7 @@ import 'prismjs/components/prism-markup-templating' import 'prismjs/components/prism-jq' import menu from './menu.js' import './styles.scss' +import wikiGraph from './graph' moment.locale('nl') mermaid.initialize({startOnLoad: true}) @@ -30,6 +31,10 @@ mermaid.initialize({startOnLoad: true}) PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-mermaid') PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-dot') +$(function () { + wikiGraph('.graph-network') +}) + function isMultiline(input) { return input.value.startsWith("```", 0) } diff --git a/file.go b/file.go index fd2ccf9..8209bd6 100644 --- a/file.go +++ b/file.go @@ -8,6 +8,7 @@ import ( "html" "html/template" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -40,7 +41,10 @@ func NewFilePages(dirname string, index bleve.Index) PagesRepository { fp := &FilePages{dirname, make(chan saveMessage), index} go func() { for msg := range fp.saveC { - fp.save(msg) + err := fp.save(msg) + if err != nil { + log.Println(err) + } } }() return fp diff --git a/graph.go b/graph.go index a19250c..3fc4121 100644 --- a/graph.go +++ b/graph.go @@ -8,10 +8,11 @@ import ( ) type graphBuilder struct { - refs Refs - nodeMap NodeMap - nodes []Node - edges []Edge + refs Refs + nodeMap NodeMap + nodeCount int + nodes []Node + edges []Edge } type NodeMap map[string]int @@ -31,17 +32,23 @@ func NewGraphBuilder(mp PagesRepository) (*graphBuilder, error) { return nil, err } - nodeMap := prepareNodeMap(refs) return &graphBuilder{ refs: refs, - nodeMap: nodeMap, + nodeMap: make(NodeMap), nodes: nil, edges: nil, }, nil } func (gb *graphBuilder) prepareGraph() error { - gb.nodes = prepareNodes(gb.nodeMap) + gb.nodes = prepareNodes(gb.nodeMap, func(node Node) Node { + if node.Id == 0 { + var green string + green = "green" + node.Color = &green + } + return node + }) gb.edges = prepareEdges(gb.refs, gb.nodeMap) return nil } @@ -58,6 +65,26 @@ func (gb *graphBuilder) RemoveNodeWithSuffix(suffix string) { } } +func (gb *graphBuilder) buildFromCenter(name string) error { + _ = gb.addNode(name) + + if ref, e := gb.refs[name]; e { + for _, item := range ref { + gb.addNode(item.Name) + } + } + + for key, references := range gb.refs { + for _, item := range references { + if name == item.Name { + gb.addNode(key) + } + } + } + + return nil +} + func prepareEdges(refs Refs, nodeMap NodeMap) []Edge { var edges []Edge edgeSet := make(map[Edge]bool) @@ -80,33 +107,32 @@ func prepareEdges(refs Refs, nodeMap NodeMap) []Edge { return edges } -func prepareNodes(nodeMap NodeMap) []Node { +func prepareNodes(nodeMap NodeMap, apply func(node Node) Node) []Node { var nodes []Node for name, id := range nodeMap { - nodes = append(nodes, Node{ + nodes = append(nodes, apply(Node{ Id: id, Label: name, - }) + Color: nil, + })) } return nodes } -func prepareNodeMap(refs Refs) NodeMap { - nodeCount := 1 - nodeMap := make(NodeMap) - - for key, references := range refs { - if _, e := nodeMap[key]; !e { - nodeMap[key] = nodeCount - nodeCount += 1 - } +func (gb *graphBuilder) prepareNodeMap() { + for key, references := range gb.refs { + gb.addNode(key) for _, item := range references { - if _, e := nodeMap[item.Name]; !e { - nodeMap[item.Name] = nodeCount - nodeCount += 1 - } + gb.addNode(item.Name) } } - - return nodeMap +} + +func (gb *graphBuilder) addNode(key string) int { + if _, e := gb.nodeMap[key]; !e { + gb.nodeMap[key] = gb.nodeCount + gb.nodeCount += 1 + } + + return gb.nodeMap[key] } diff --git a/main.go b/main.go index 40dd5b4..c0ebb24 100644 --- a/main.go +++ b/main.go @@ -35,10 +35,11 @@ var ( ) type Backref struct { - Name string - Title string - LineHTML template.HTML - Line string + Name string + Title string + LineHTML template.HTML + LineEditHTML template.HTML + Line string } // Page @@ -100,8 +101,9 @@ type indexPage struct { } type Node struct { - Id int `json:"id"` - Label string `json:"label"` + Id int `json:"id"` + Label string `json:"label"` + Color *string `json:"color"` } type Edge struct { @@ -111,11 +113,11 @@ type Edge struct { type graphPage struct { pageBaseInfo - Session *Session - Title string - Name string - Nodes template.JS - Edges template.JS + Session *Session + Title string + Name string + Nodes template.JS + Edges template.JS } type editPage struct { @@ -483,6 +485,8 @@ func (h *graphHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + gb.prepareNodeMap() + gb.RemoveNode("DONE") gb.RemoveNode("TODO") gb.RemoveNode("Home") @@ -631,7 +635,7 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } pageText = metaKV.ReplaceAllString(pageText, "**[[$1]]**: $2") - pageText = renderLinks(pageText) + pageText = renderLinks(pageText, false) pageText = renderMarkdown2(pageText) pageBase := getPageBase() @@ -663,7 +667,7 @@ func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -func renderLinks(pageText string) string { +func renderLinks(pageText string, edit bool) string { hrefRE, err := regexp.Compile(`#?\[\[\s*([^\]]+)\s*\]\]`) if err != nil { log.Fatal(err) @@ -682,7 +686,12 @@ func renderLinks(pageText string) string { if tag { return fmt.Sprintf(`%s`, cleanNameURL(s), s) } - return fmt.Sprintf("[%s](/%s)", s, cleanNameURL(s)) + editPart := "" + if edit { + editPart = "edit/" + } + + return fmt.Sprintf("[%s](/%s%s)", s, editPart, cleanNameURL(s)) }) return pageText } @@ -791,6 +800,48 @@ func main() { w.Header().Set("Content-Type", "application/json") http.ServeFile(w, r, filepath.Join(dataDir, LinksFile)) }) + http.HandleFunc("/api/graph", func(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + + gb, err := NewGraphBuilder(mp) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + err = gb.buildFromCenter(name) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + // Keep a copy of the nodes, buildFromCenter appends to the nodeMap + var nodes []string + for k, _ := range gb.nodeMap { + nodes = append(nodes, k) + } + + for _, node := range nodes { + err = gb.buildFromCenter(node) + } + + err = gb.prepareGraph() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + type data struct { + Nodes []Node `json:"nodes"` + Edges []Edge `json:"edges"` + } + + err = json.NewEncoder(w).Encode(&data{gb.nodes, gb.edges}) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + }) http.Handle("/search/", sh) http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("./dist")))) http.Handle("/save/", &saveHandler{}) diff --git a/templates/edit.html b/templates/edit.html index 3592d3b..8aae32f 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -1,31 +1,39 @@ {{ define "navbar" }} - Back + Back {{ end }} {{define "content"}} -

{{ .Title }}

-
- - {{ .Editor }} -
+
+
+

{{ .Title }}

+
+ + {{ .Editor }} +
-{{ if .Backrefs }} -
-

Linked references

-
    - {{ range $name, $refs := .Backrefs }} -
  • {{ (index $refs 0).Title }} + {{ if .Backrefs }} +
    +

    Linked references

      - {{ range $ref := $refs }} -
    • {{ $ref.LineHTML }}
    • + {{ range $name, $refs := .Backrefs }} +
    • {{ (index $refs 0).Title }} +
        + {{ range $ref := $refs }} +
      • {{ $ref.LineEditHTML }}
      • + {{ end }} +
      +
    • {{ end }}
    -
  • +
{{ end }} - +
+ +
+
+
{{ end }} -{{ end }} {{ define "content_head" }} {{ end }} diff --git a/templates/layout.html b/templates/layout.html index 8b10291..ef987b2 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -226,7 +226,7 @@ -
+