Add graph possibilities
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Add dot language, which renders graphs - Show search results when no results - Add graph page
This commit is contained in:
parent
a2cca78018
commit
fd755bfb61
73
editor/package-lock.json
generated
73
editor/package-lock.json
generated
|
@ -7,6 +7,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz",
|
||||
"integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg=="
|
||||
},
|
||||
"@egjs/hammerjs": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
||||
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
|
||||
"requires": {
|
||||
"@types/hammerjs": "^2.0.36"
|
||||
}
|
||||
},
|
||||
"@types/anymatch": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
|
||||
|
@ -30,6 +38,11 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/hammerjs": {
|
||||
"version": "2.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
||||
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
|
@ -4532,6 +4545,11 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"keycharm": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.3.1.tgz",
|
||||
"integrity": "sha512-zn47Ti4FJT9zdF+YBBLWJsfKF/fYQHkrYlBeB5Ez5e2PjW7SoIxr43yehAne2HruulIoid4NKZZxO0dHBygCtQ=="
|
||||
},
|
||||
"killable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||
|
@ -6981,6 +6999,13 @@
|
|||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
|
@ -7532,6 +7557,14 @@
|
|||
"faye-websocket": "^0.10.0",
|
||||
"uuid": "^3.4.0",
|
||||
"websocket-driver": "0.6.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sockjs-client": {
|
||||
|
@ -8266,6 +8299,11 @@
|
|||
"setimmediate": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"timsort": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
|
@ -8636,9 +8674,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.0.3",
|
||||
|
@ -8671,6 +8709,21 @@
|
|||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"vis-data": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-6.6.1.tgz",
|
||||
"integrity": "sha512-xmujDB2Dzf8T04rGFJ9OP4OA6zRVrz8R9hb0CVKryBrZRCljCga9JjSfgctA8S7wdZu7otDtUIwX4ZOgfV/57w=="
|
||||
},
|
||||
"vis-network": {
|
||||
"version": "7.6.10",
|
||||
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-7.6.10.tgz",
|
||||
"integrity": "sha512-wL1dHBWWpzxvUaM0miccDuSLQ2tkw93jCA3j4Zizh4ruph+UXnjkouayaOyJIx43wULUSoKGWkhE6na1q208TA=="
|
||||
},
|
||||
"vis-util": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-4.3.2.tgz",
|
||||
"integrity": "sha512-FIS75hhrzbX1qJwFVwVVm1q2/TEktJWjgWsV0T3E9AYC4PWyQCBKk2LgsSLi+O8NBi7gTe9D4K75MqdPTHrRnA=="
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
|
@ -8995,6 +9048,14 @@
|
|||
"requires": {
|
||||
"ansi-colors": "^3.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-sources": {
|
||||
|
@ -9076,9 +9137,9 @@
|
|||
}
|
||||
},
|
||||
"wiki-list-editor": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/wiki-list-editor/-/wiki-list-editor-0.7.8.tgz",
|
||||
"integrity": "sha512-t9O3Guey/EI6KdWLn2YVN43I70pBRpOov8iq0kPa9ZukE+eE55iNUWBQW7D61QypUwBegqQWEm4zAQyoo9f8FA==",
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/wiki-list-editor/-/wiki-list-editor-0.8.0.tgz",
|
||||
"integrity": "sha512-mA1JyClhwrvf+sZgZZm7H4TwLQ+xcciJThoZz+Ya7JVrffmWCex8HRByXfxpOA4C0XDNC/Wx1RpCYjf/zBHH3Q==",
|
||||
"requires": {
|
||||
"dragula": "^3.7.2",
|
||||
"he": "^1.2.0",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"axios": "^0.19.0",
|
||||
"bulma": "^0.7.5",
|
||||
"clipboard": "^2.0.6",
|
||||
|
@ -18,6 +19,7 @@
|
|||
"file-loader": "^6.0.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"jquery-contextmenu": "^2.9.2",
|
||||
"keycharm": "^0.3.1",
|
||||
"lunr": "^2.3.8",
|
||||
"markdown-it": "^11.0.0",
|
||||
"markdown-it-mark": "^3.0.0",
|
||||
|
@ -29,7 +31,12 @@
|
|||
"prismjs": "^1.20.0",
|
||||
"sass-loader": "^7.3.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"wiki-list-editor": "^0.7.8"
|
||||
"timsort": "^0.3.0",
|
||||
"uuid": "^8.1.0",
|
||||
"vis-data": "^6.6.1",
|
||||
"vis-network": "^7.6.10",
|
||||
"vis-util": "^4.3.2",
|
||||
"wiki-list-editor": "^0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -r esm",
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'jquery-contextmenu';
|
|||
import getCaretCoordinates from './caret-position'
|
||||
import moment from 'moment'
|
||||
import mermaid from 'mermaid'
|
||||
import { parseDOTNetwork, Network } from "vis-network/peer";
|
||||
import PrismJS from 'prismjs'
|
||||
import 'prismjs/plugins/filter-highlight-all/prism-filter-highlight-all'
|
||||
import 'prismjs/components/prism-php'
|
||||
|
@ -21,12 +22,17 @@ import 'prismjs/components/prism-perl'
|
|||
import 'prismjs/components/prism-css'
|
||||
import 'prismjs/components/prism-markup-templating'
|
||||
import 'prismjs/components/prism-jq'
|
||||
import './styles.scss';
|
||||
import '../node_modules/jquery-contextmenu/dist/jquery.contextMenu.css';
|
||||
|
||||
import './styles.scss'
|
||||
import '../node_modules/jquery-contextmenu/dist/jquery.contextMenu.css'
|
||||
import 'vis-network/styles/vis-network.css'
|
||||
|
||||
moment.locale('nl')
|
||||
mermaid.initialize({startOnLoad: true})
|
||||
|
||||
PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-mermaid')
|
||||
PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-dot')
|
||||
|
||||
function isMultiline(input) {
|
||||
return input.value.startsWith("```", 0)
|
||||
}
|
||||
|
@ -83,20 +89,20 @@ function addIndicator(editor, indicator) {
|
|||
let holder = document.getElementById('editor');
|
||||
|
||||
function showSearchResults(searchTool, query, input, value, resultType) {
|
||||
showSearchResultsExtended('#link-complete', 'link-template', searchTool, query, input, value, resultType, {belowCursor: true})
|
||||
showSearchResultsExtended('#link-complete', 'link-template', searchTool, query, input, value, resultType, {showOnlyResults:true, belowCursor: true})
|
||||
}
|
||||
|
||||
function showSearchResultsExtended(element, template, searchTool, query, input, value, resultType, options) {
|
||||
const $lc = $(element)
|
||||
let results = searchTool(query)
|
||||
|
||||
if (query.length === 0 || !results.length) {
|
||||
let opt = options || {};
|
||||
|
||||
if (opt.showOnlyResults && (query.length === 0 || !results.length)) {
|
||||
$lc.fadeOut()
|
||||
return
|
||||
}
|
||||
|
||||
let opt = options || {};
|
||||
|
||||
$lc.data('result-type', resultType)
|
||||
|
||||
if (opt.belowCursor) {
|
||||
|
@ -180,6 +186,24 @@ $(document).on('popup:selected', '#autocomplete', function (event, linkName, res
|
|||
}
|
||||
})
|
||||
|
||||
function renderGraphs() {
|
||||
$('code.language-dot').each(function (i, code) {
|
||||
if (!code.innerText) {
|
||||
return true
|
||||
}
|
||||
|
||||
let data = parseDOTNetwork(code.innerText)
|
||||
let network = new Network(code, data, {
|
||||
layout: {
|
||||
randomSeed: 1239043
|
||||
}
|
||||
});
|
||||
$(code).on('click', function () {
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (holder) {
|
||||
const MD = new MarkdownIt({
|
||||
linkify: true,
|
||||
|
@ -200,9 +224,9 @@ if (holder) {
|
|||
})).use(MarkdownItMark)
|
||||
|
||||
const options = {
|
||||
transform(text, callback) {
|
||||
transform(text, element) {
|
||||
let converted = (text.startsWith("```", 0)) ? MD.render(text) : MD.renderInline(text)
|
||||
return callback(converted)
|
||||
element.html(converted)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +245,12 @@ if (holder) {
|
|||
).save()
|
||||
})
|
||||
|
||||
editor.on('rendered', function () {
|
||||
PrismJS.highlightAll()
|
||||
mermaid.init()
|
||||
renderGraphs();
|
||||
})
|
||||
|
||||
createPageSearch().then(function ({titleSearch, commandSearch, commands}) {
|
||||
editor.on('start-editing', function (input) {
|
||||
const $lc = $('#link-complete');
|
||||
|
@ -370,9 +400,9 @@ if (holder) {
|
|||
editor.on('stop-editing', function (input) {
|
||||
$(input).parents('.list-item').removeClass('active');
|
||||
$('#link-complete').off()
|
||||
PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-mermaid')
|
||||
PrismJS.highlightAll()
|
||||
mermaid.init()
|
||||
renderGraphs();
|
||||
})
|
||||
})
|
||||
$.contextMenu({
|
||||
|
|
138
main.go
138
main.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -10,6 +11,8 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -94,6 +97,26 @@ type indexPage struct {
|
|||
Backrefs map[string][]Backref
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Id int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
type Edge struct {
|
||||
From int `json:"from"`
|
||||
To int `json:"to"`
|
||||
}
|
||||
|
||||
type graphPage struct {
|
||||
pageBaseInfo
|
||||
Session *Session
|
||||
Title string
|
||||
Name string
|
||||
References Refs
|
||||
Nodes template.JS
|
||||
Edges template.JS
|
||||
}
|
||||
|
||||
type editPage struct {
|
||||
pageBaseInfo
|
||||
Session *Session
|
||||
|
@ -121,6 +144,7 @@ type recentPage struct {
|
|||
}
|
||||
|
||||
type indexHandler struct{}
|
||||
type graphHandler struct{}
|
||||
type saveHandler struct{}
|
||||
type editHandler struct{}
|
||||
type historyHandler struct{}
|
||||
|
@ -428,6 +452,119 @@ func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *graphHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
sess, err := NewSession(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := sess.Flush(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
refs := make(Refs)
|
||||
|
||||
f, err := os.Open(filepath.Join(mp.(*FilePages).dirname, "backrefs.json"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
err = json.NewDecoder(f).Decode(&refs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
var nodesBuf bytes.Buffer
|
||||
var edgesBuf bytes.Buffer
|
||||
|
||||
var nodes []Node
|
||||
var edges []Edge
|
||||
|
||||
nodeCount := 1
|
||||
nodeMap := make(map[string]int)
|
||||
|
||||
for key, references := range refs {
|
||||
if _, e := nodeMap[key]; !e {
|
||||
nodeMap[key] = nodeCount
|
||||
nodeCount += 1
|
||||
}
|
||||
for _, item := range references {
|
||||
if _, e := nodeMap[item.Name]; !e {
|
||||
nodeMap[item.Name] = nodeCount
|
||||
nodeCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name, id := range nodeMap {
|
||||
nodes = append(nodes, Node{
|
||||
Id: id,
|
||||
Label: name,
|
||||
})
|
||||
}
|
||||
|
||||
edgeSet := make(map[Edge]bool)
|
||||
for key, references := range refs {
|
||||
if toID, e := nodeMap[key]; e {
|
||||
for _, item := range references {
|
||||
if fromID, e := nodeMap[item.Name]; e {
|
||||
edge := Edge{
|
||||
From: fromID,
|
||||
To: toID,
|
||||
}
|
||||
|
||||
if _, e := edgeSet[edge]; !e {
|
||||
edgeSet[edge] = true
|
||||
edges = append(edges, edge)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = json.NewEncoder(&nodesBuf).Encode(&nodes)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(&edgesBuf).Encode(&edges)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
pageBase := getPageBase()
|
||||
data := graphPage{
|
||||
pageBaseInfo: pageBase,
|
||||
Session: sess,
|
||||
Title: "Graph",
|
||||
Name: "Graph",
|
||||
References: refs,
|
||||
Nodes: template.JS(nodesBuf.String()),
|
||||
Edges: template.JS(edgesBuf.String()),
|
||||
}
|
||||
|
||||
t, err := template.ParseFiles("templates/layout.html", "templates/graph.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
|
@ -749,6 +886,7 @@ func main() {
|
|||
http.Handle("/edit/", &editHandler{})
|
||||
http.Handle("/history/", &historyHandler{})
|
||||
http.Handle("/recent/", &recentHandler{})
|
||||
http.Handle("/graph/", &graphHandler{})
|
||||
http.Handle("/", &indexHandler{})
|
||||
|
||||
fmt.Printf("Running on port %d\n", *port)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<a href="/edit/{{ .Name }}" class="navbar-item">Edit</a>
|
||||
<a href="/history/{{ .Name }}" class="navbar-item">History</a>
|
||||
<a href="/recent/" class="navbar-item">Recent Changes</a>
|
||||
<a href="/graph/" class="navbar-item">Graph</a>
|
||||
<a href="/auth/logout" class="navbar-item">Logout</a>
|
||||
<span class="navbar-item"><b>{{ $.Session.Me }}</b></span>
|
||||
{{ else }}
|
||||
|
|
Loading…
Reference in New Issue
Block a user