Canvas V2
This commit is contained in:
+877
-232
File diff suppressed because it is too large
Load Diff
+3
-46
@@ -13,61 +13,18 @@ navigation and canvas.js can save on pagehide. */}}
|
|||||||
|
|
||||||
{{define "shell"}}
|
{{define "shell"}}
|
||||||
<div class="canvas-app" data-version="{{.Version}}"{{if .Error}} data-readonly{{end}}>
|
<div class="canvas-app" data-version="{{.Version}}"{{if .Error}} data-readonly{{end}}>
|
||||||
<div class="canvas-bar">
|
|
||||||
<span class="canvas-title truncate">{{.Title}}</span>
|
|
||||||
{{if not .Error}}
|
|
||||||
<span class="canvas-tools">
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="text" title="Add a note (or double-click the background)">TEXT</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="file" title="Add a file or page">FILE</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="link" title="Add a web link">LINK</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="group" title="Group the selection">GROUP</button>
|
|
||||||
<span class="toolbar-sep"></span>
|
|
||||||
<span class="dropdown">
|
|
||||||
<button type="button" class="btn btn-tool dropdown-toggle" title="Color of the selection">COLOR▾</button>
|
|
||||||
<div class="dropdown-menu canvas-colors">
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="">None</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="1"><span class="canvas-swatch" data-color="1"></span>Red</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="2"><span class="canvas-swatch" data-color="2"></span>Orange</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="3"><span class="canvas-swatch" data-color="3"></span>Yellow</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="4"><span class="canvas-swatch" data-color="4"></span>Green</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="5"><span class="canvas-swatch" data-color="5"></span>Cyan</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="color" data-value="6"><span class="canvas-swatch" data-color="6"></span>Purple</button>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
<input type="color" class="canvas-color-input" title="Custom color of the selection" value="#c48401" />
|
|
||||||
<span class="dropdown">
|
|
||||||
<button type="button" class="btn btn-tool dropdown-toggle" title="Arrows of the selected connections">ARROWS▾</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="ends" data-value="none arrow">One way →</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="ends" data-value="arrow arrow">Both ways ↔</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="ends" data-value="none none">No arrows —</button>
|
|
||||||
<button type="button" class="btn btn-tool btn-block" data-canvas="ends" data-value="reverse">Reverse ⇄</button>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
<span class="toolbar-sep"></span>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="undo" title="Undo (Ctrl+Z)">↶</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="redo" title="Redo (Ctrl+Y)">↷</button>
|
|
||||||
<button type="button" class="btn btn-tool danger" data-canvas="delete" title="Delete the selection (Del)">DEL</button>
|
|
||||||
<span class="toolbar-sep"></span>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="zoomout" title="Zoom out">−</button>
|
|
||||||
<button type="button" class="btn btn-tool canvas-zoom" data-canvas="zoomreset" title="Zoom to 100%">100%</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="zoomin" title="Zoom in">+</button>
|
|
||||||
<button type="button" class="btn btn-tool" data-canvas="fit" title="Show everything">FIT</button>
|
|
||||||
</span>
|
|
||||||
<span class="canvas-status muted" aria-live="polite"></span>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{if .Error}}<p class="canvas-error">This canvas cannot be edited: {{.Error}}</p>{{end}}
|
{{if .Error}}<p class="canvas-error">This canvas cannot be edited: {{.Error}}</p>{{end}}
|
||||||
<div class="canvas-stage">
|
<div class="canvas-stage">
|
||||||
<div class="canvas-node-toolbar" hidden>{{template "editor-toolbar"}}</div>
|
|
||||||
<div class="canvas-viewport" tabindex="0">
|
<div class="canvas-viewport" tabindex="0">
|
||||||
<div class="canvas-world">
|
<div class="canvas-world">
|
||||||
<svg class="canvas-edges" aria-hidden="true"></svg>
|
<svg class="canvas-edges" aria-hidden="true"></svg>
|
||||||
<div class="canvas-edge-labels"></div>
|
<div class="canvas-edge-labels"></div>
|
||||||
<div class="canvas-nodes">{{range .Nodes}}{{template "canvas-node" .}}{{end}}</div>
|
<div class="canvas-nodes">{{range .Nodes}}{{template "canvas-node" .}}{{end}}</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="canvas-hint"{{if .Nodes}} hidden{{end}}>Double-click to add a note</p>
|
<p class="canvas-hint"{{if .Nodes}} hidden{{end}}>Double-click to add a note. Right-click or long-press for more.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="canvas-statusbar muted"><span class="canvas-status" aria-live="polite"></span><span class="canvas-zoom"></span></div>
|
||||||
|
{{if not .Error}}<div class="canvas-editor-toolbar" hidden>{{template "editor-toolbar"}}</div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<script type="application/json" id="canvas-data">{{.Doc}}</script>
|
<script type="application/json" id="canvas-data">{{.Doc}}</script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ units; canvas.js keeps style and data-color in sync while editing. */}}
|
|||||||
|
|
||||||
{{define "canvas-file"}}<a class="canvas-card" href="{{.URL}}"><span class="canvas-card-icon">{{.Icon}}</span><span class="truncate">{{.Name}}</span></a>{{end}}
|
{{define "canvas-file"}}<a class="canvas-card" href="{{.URL}}"><span class="canvas-card-icon">{{.Icon}}</span><span class="truncate">{{.Name}}</span></a>{{end}}
|
||||||
|
|
||||||
{{define "canvas-link"}}<a class="canvas-card" href="{{.URL}}" target="_blank" rel="noopener noreferrer"><img class="canvas-card-icon" src="https://icons.duckduckgo.com/ip3/{{.Host}}.ico" alt="" width="16" height="16" draggable="false"><span class="canvas-card-text"><span class="truncate">{{.Host}}</span><span class="muted truncate">{{.URL}}</span></span></a>{{end}}
|
{{define "canvas-link"}}<a class="canvas-card" href="{{.URL}}" target="_blank" rel="noopener noreferrer" title="{{.URL}}"><img class="canvas-card-icon" src="https://icons.duckduckgo.com/ip3/{{.Host}}.ico" alt="" width="16" height="16" draggable="false"><span class="canvas-card-text">{{if .Label}}<span class="truncate">{{.Label}}</span><span class="muted truncate">{{.Host}}</span>{{else}}<span class="truncate">{{.Host}}</span><span class="muted truncate">{{.URL}}</span>{{end}}</span></a>{{end}}
|
||||||
|
|
||||||
{{define "canvas-missing"}}<p class="canvas-missing">Missing: {{.}}</p>{{end}}
|
{{define "canvas-missing"}}<p class="canvas-missing">Missing: {{.}}</p>{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
// MarkdownEditor.create(mount, opts) → { view, actions, getValue, focus, destroy }
|
// MarkdownEditor.create(mount, opts) → { view, actions, getValue, focus, destroy }
|
||||||
// opts.doc initial text
|
// opts.doc initial text
|
||||||
// opts.keymap extra CM key bindings, ahead of the defaults
|
// opts.keymap extra CM key bindings, ahead of the defaults
|
||||||
|
// opts.onChange called after every change to the text
|
||||||
// MarkdownEditor.bind(root, getActions)
|
// MarkdownEditor.bind(root, getActions)
|
||||||
// wires root's [data-action] buttons, [data-key] shortcuts and toolbar
|
// wires root's [data-action] buttons, [data-key] shortcuts and toolbar
|
||||||
// dropdowns to whatever getActions() returns (null = no editor open).
|
// dropdowns to whatever getActions() returns (null = no editor open).
|
||||||
@@ -89,6 +90,9 @@ window.MarkdownEditor = (function () {
|
|||||||
CM.closeBrackets(),
|
CM.closeBrackets(),
|
||||||
CM.autocompletion({ override: [WikiComplete.source] }),
|
CM.autocompletion({ override: [WikiComplete.source] }),
|
||||||
CM.theme,
|
CM.theme,
|
||||||
|
opts.onChange ? CM.EditorView.updateListener.of(function (u) {
|
||||||
|
if (u.docChanged) opts.onChange();
|
||||||
|
}) : [],
|
||||||
CM.Prec.highest(CM.keymap.of(tableKeymap)),
|
CM.Prec.highest(CM.keymap.of(tableKeymap)),
|
||||||
CM.keymap.of([].concat(
|
CM.keymap.of([].concat(
|
||||||
CM.closeBracketsKeymap,
|
CM.closeBracketsKeymap,
|
||||||
|
|||||||
+110
-46
@@ -950,51 +950,31 @@ label.tree-name { flex: 1; cursor: pointer; position: relative; }
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.canvas-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-1) var(--space-4);
|
|
||||||
padding: var(--space-2) var(--space-4);
|
|
||||||
border-bottom: var(--border-dashed);
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
}
|
|
||||||
.canvas-title { min-width: 0; max-width: 30rem; }
|
|
||||||
.canvas-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
|
|
||||||
.canvas-status { margin-left: auto; }
|
|
||||||
.canvas-status.is-error { color: var(--danger); }
|
|
||||||
.canvas-zoom { min-width: 4.5em; text-align: center; }
|
|
||||||
.canvas-color-input {
|
|
||||||
width: 1.6rem;
|
|
||||||
height: 1.2rem;
|
|
||||||
padding: 0;
|
|
||||||
border: var(--border);
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.canvas-swatch {
|
|
||||||
display: inline-block;
|
|
||||||
width: 0.8em;
|
|
||||||
height: 0.8em;
|
|
||||||
margin-right: var(--space-2);
|
|
||||||
background: var(--node-color);
|
|
||||||
}
|
|
||||||
.canvas-colors .btn-block { justify-content: flex-start; align-items: center; }
|
|
||||||
.canvas-error { padding: var(--space-2) var(--space-4); color: var(--danger); }
|
.canvas-error { padding: var(--space-2) var(--space-4); color: var(--danger); }
|
||||||
/* The stage holds the viewport and, floating over its top edge while a note
|
/* The stage holds the viewport and what floats over it: the status line,
|
||||||
is open, the page editor's toolbar, so opening it moves nothing. */
|
the context menu and the edit window. */
|
||||||
.canvas-stage { position: relative; flex: 1 1 auto; min-height: 0; }
|
.canvas-stage { position: relative; flex: 1 1 auto; min-height: 0; }
|
||||||
.canvas-node-toolbar { position: absolute; top: 0; left: 0; right: 0; z-index: 10; }
|
.canvas-statusbar {
|
||||||
.canvas-node-toolbar[hidden] { display: none; }
|
position: absolute;
|
||||||
.canvas-node-toolbar .editor-toolbar { position: static; border-left: none; border-right: none; }
|
left: var(--space-2);
|
||||||
|
bottom: var(--space-2);
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: 0 var(--space-2);
|
||||||
|
background: var(--bg);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.canvas-status.is-error { color: var(--danger); pointer-events: auto; }
|
||||||
|
.canvas-editor-toolbar[hidden] { display: none; }
|
||||||
|
|
||||||
.canvas-viewport {
|
.canvas-viewport {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
cursor: grab;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
background-image: radial-gradient(circle, var(--bg-panel-hover) 1px, transparent 1.5px);
|
background-image: radial-gradient(circle, var(--bg-panel-hover) 1px, transparent 1.5px);
|
||||||
}
|
}
|
||||||
.canvas-viewport.is-far { background-image: none; }
|
.canvas-viewport.is-far { background-image: none; }
|
||||||
@@ -1066,7 +1046,7 @@ label.tree-name { flex: 1; cursor: pointer; position: relative; }
|
|||||||
.canvas-node-body {
|
.canvas-node-body {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
padding: var(--space-2) var(--space-3);
|
padding: var(--space-2) var(--space-3);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
@@ -1097,13 +1077,6 @@ audio.canvas-media { height: auto; }
|
|||||||
.canvas-group-bg-ratio { background-size: contain; background-repeat: no-repeat; }
|
.canvas-group-bg-ratio { background-size: contain; background-repeat: no-repeat; }
|
||||||
.canvas-group-bg-repeat { background-repeat: repeat; }
|
.canvas-group-bg-repeat { background-repeat: repeat; }
|
||||||
|
|
||||||
/* The note editor inside a text node (editor/core.js). */
|
|
||||||
.canvas-node.is-editing { z-index: 5; cursor: auto; user-select: text; -webkit-user-select: text; }
|
|
||||||
.canvas-node-editor { flex: 1 1 auto; min-height: 0; display: flex; }
|
|
||||||
.canvas-node-editor .cm-editor { flex: 1 1 auto; min-width: 0; border: none; }
|
|
||||||
.canvas-node-editor .cm-editor .cm-scroller { min-height: 0; }
|
|
||||||
.canvas-node-editor .cm-editor .cm-content { padding: var(--space-2) var(--space-3); }
|
|
||||||
|
|
||||||
/* Handles: connect from a side, resize from the corner. */
|
/* Handles: connect from a side, resize from the corner. */
|
||||||
.canvas-handle, .canvas-resize { position: absolute; display: none; z-index: 1; }
|
.canvas-handle, .canvas-resize { position: absolute; display: none; z-index: 1; }
|
||||||
.canvas-handle {
|
.canvas-handle {
|
||||||
@@ -1132,8 +1105,6 @@ audio.canvas-media { height: auto; }
|
|||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.canvas-node:hover > .canvas-handle { display: block; }
|
.canvas-node:hover > .canvas-handle { display: block; }
|
||||||
}
|
}
|
||||||
.canvas-node.is-editing > .canvas-handle,
|
|
||||||
.canvas-node.is-editing > .canvas-resize { display: none; }
|
|
||||||
|
|
||||||
/* Edges: an SVG sized to their bounding box (canvas.js). The wide invisible
|
/* Edges: an SVG sized to their bounding box (canvas.js). The wide invisible
|
||||||
hit path makes thin lines easy to click. */
|
hit path makes thin lines easy to click. */
|
||||||
@@ -1169,6 +1140,99 @@ audio.canvas-media { height: auto; }
|
|||||||
}
|
}
|
||||||
.canvas-edge-label.is-selected { color: var(--primary-hover); }
|
.canvas-edge-label.is-selected { color: var(--primary-hover); }
|
||||||
|
|
||||||
|
/* Context menu (canvas.js openMenu): fixed at the pointer. */
|
||||||
|
.canvas-menu {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
min-width: 12rem;
|
||||||
|
padding: var(--space-1) 0;
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
}
|
||||||
|
.canvas-menu .btn-block { padding: 0.3rem var(--space-3); }
|
||||||
|
.canvas-menu .btn:disabled { color: var(--text-muted); cursor: default; }
|
||||||
|
.canvas-menu-sep { border-top: var(--border-dashed); margin: var(--space-1) 0; }
|
||||||
|
.canvas-menu-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: 0.3rem var(--space-3);
|
||||||
|
}
|
||||||
|
.canvas-menu-row > .muted { min-width: 3rem; }
|
||||||
|
.canvas-swatch {
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
padding: 0;
|
||||||
|
border: var(--border);
|
||||||
|
background: var(--node-color, transparent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.canvas-swatch:not([data-color]):not(input) {
|
||||||
|
background: linear-gradient(to top right, transparent 45%, var(--text-muted) 45%, var(--text-muted) 55%, transparent 55%);
|
||||||
|
}
|
||||||
|
.canvas-swatch.is-current { outline: 2px solid var(--primary-hover); outline-offset: 1px; }
|
||||||
|
input.canvas-swatch { background: none; }
|
||||||
|
|
||||||
|
/* Edit window (canvas.js openWindow): not modal, placed beside its card;
|
||||||
|
small screens dock it to the bottom. */
|
||||||
|
.canvas-window {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
width: 24rem;
|
||||||
|
max-width: calc(100% - 2rem);
|
||||||
|
max-height: calc(100% - 2rem);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
.canvas-window.is-wide { width: 34rem; height: 24rem; resize: both; overflow: hidden; min-width: 18rem; min-height: 12rem; }
|
||||||
|
.canvas-window .panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-2);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 0.4rem var(--space-4);
|
||||||
|
margin-bottom: 0;
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.canvas-window .panel-body { flex: 1 1 auto; min-height: 0; overflow: auto; }
|
||||||
|
.canvas-window .panel-footer { justify-content: flex-end; }
|
||||||
|
.canvas-window .input { background: var(--bg); }
|
||||||
|
.canvas-field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
|
||||||
|
.canvas-field-label { font-size: var(--font-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
|
||||||
|
.canvas-field-row { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
|
||||||
|
.canvas-field-row > :first-child { flex: 1 1 auto; min-width: 0; }
|
||||||
|
.canvas-field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
|
||||||
|
.canvas-fields { display: flex; flex-direction: column; gap: var(--space-3); }
|
||||||
|
.canvas-window.is-wide .panel-body { padding: 0; }
|
||||||
|
.canvas-window-editor { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
||||||
|
.canvas-window-editor .editor-toolbar { position: static; border-left: none; border-right: none; border-top: none; }
|
||||||
|
.canvas-window-cm { flex: 1 1 auto; min-height: 0; display: flex; }
|
||||||
|
.canvas-window-cm .cm-editor { flex: 1 1 auto; min-width: 0; border: none; }
|
||||||
|
.canvas-window-cm .cm-editor .cm-scroller { min-height: 0; }
|
||||||
|
.canvas-window-cm .cm-editor .cm-content { padding: var(--space-2) var(--space-3); }
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.canvas-window, .canvas-window.is-wide {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: none;
|
||||||
|
height: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
resize: none;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
.canvas-window.is-wide { height: 50vh; }
|
||||||
|
.canvas-window .panel-header { cursor: default; }
|
||||||
|
.canvas-menu .btn-block { min-height: 2.5rem; align-items: center; }
|
||||||
|
.canvas-swatch { width: 1.6rem; height: 1.6rem; }
|
||||||
|
}
|
||||||
|
|
||||||
/* === Responsive === */
|
/* === Responsive === */
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.tree-sidebar, .sidebar { display: none; }
|
.tree-sidebar, .sidebar { display: none; }
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ type canvasCardBody struct {
|
|||||||
Icon template.HTML
|
Icon template.HTML
|
||||||
Name, URL string
|
Name, URL string
|
||||||
Host string
|
Host string
|
||||||
|
Label string // a link's display text, shown instead of its URL
|
||||||
}
|
}
|
||||||
|
|
||||||
type canvasGroupBody struct {
|
type canvasGroupBody struct {
|
||||||
@@ -255,7 +256,7 @@ func (h *handler) renderCanvasNode(n canvasNode) canvasNodeView {
|
|||||||
if u, err := url.Parse(n.URL); err == nil && u.Host != "" {
|
if u, err := url.Parse(n.URL); err == nil && u.Host != "" {
|
||||||
host = u.Host
|
host = u.Host
|
||||||
}
|
}
|
||||||
v.Body = execNodeTemplate("canvas-link", canvasCardBody{Name: n.URL, URL: n.URL, Host: host})
|
v.Body = execNodeTemplate("canvas-link", canvasCardBody{Name: n.URL, URL: n.URL, Host: host, Label: strings.TrimSpace(n.Label)})
|
||||||
case "group":
|
case "group":
|
||||||
v.Label = n.Label
|
v.Label = n.Label
|
||||||
if n.Background != "" {
|
if n.Background != "" {
|
||||||
|
|||||||
@@ -209,6 +209,10 @@ func TestCanvasRenderNodes(t *testing.T) {
|
|||||||
if strings.Contains(link, `href="javascript:`) {
|
if strings.Contains(link, `href="javascript:`) {
|
||||||
t.Errorf("unsafe link rendered:\n%s", link)
|
t.Errorf("unsafe link rendered:\n%s", link)
|
||||||
}
|
}
|
||||||
|
named := render(`{"id":"a","type":"link","url":"https://jsoncanvas.org/spec/1.0/","label":"The spec","x":0,"y":0,"width":1,"height":1}`)
|
||||||
|
if !strings.Contains(named, `<span class="truncate">The spec</span><span class="muted truncate">jsoncanvas.org</span>`) || !strings.Contains(named, `title="https://jsoncanvas.org/spec/1.0/"`) {
|
||||||
|
t.Errorf("link with display text:\n%s", named)
|
||||||
|
}
|
||||||
group := render(`{"id":"a","type":"group","label":"<b>G</b>","color":"#abc","x":0,"y":0,"width":1,"height":1}`)
|
group := render(`{"id":"a","type":"group","label":"<b>G</b>","color":"#abc","x":0,"y":0,"width":1,"height":1}`)
|
||||||
if !strings.Contains(group, "<b>G</b>") || !strings.Contains(group, "--node-color: #abc") {
|
if !strings.Contains(group, "<b>G</b>") || !strings.Contains(group, "--node-color: #abc") {
|
||||||
t.Errorf("group node:\n%s", group)
|
t.Errorf("group node:\n%s", group)
|
||||||
|
|||||||
Reference in New Issue
Block a user