Canvas support V1

This commit is contained in:
2026-10-04 15:28:06 +02:00
parent 7749d7c237
commit 2903986f44
20 changed files with 3223 additions and 482 deletions
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
{{/* The canvas view (canvas.go). It fills the whole shell: no folder tree,
no sidebar, no footer. Links are not boosted, so leaving the canvas is a full
navigation and canvas.js can save on pagehide. */}}
{{define "boost"}} hx-boost="false"{{end}}
{{define "page-actions"}}<div id="page-actions" class="page-actions">{{template "headerActions" .}}</div>{{end}}
{{define "headerActions"}}
<a class="btn btn-block" href="{{.PageURL}}" title="Back to the page"><span class="btn-icon">{{icon "up"}}</span><span class="btn-label">PAGE</span></a>
<a class="btn btn-block" href="{{.RawURL}}" download="{{.Name}}" title="Download the canvas file"><span class="btn-icon">{{icon "doc"}}</span><span class="btn-label">DOWNLOAD</span></a>
{{end}}
{{define "shell"}}
<div class="canvas-app" data-version="{{.Version}}"{{if .Error}} data-readonly{{end}}>
<div class="canvas-bar">
<span class="canvas-title truncate"><a href="{{.PageURL}}">{{.PageURL}}</a>{{.Name}}</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}}
<div class="canvas-stage">
<div class="canvas-node-toolbar" hidden>{{template "editor-toolbar"}}</div>
<div class="canvas-viewport" tabindex="0">
<div class="canvas-world">
<svg class="canvas-edges" aria-hidden="true"></svg>
<div class="canvas-edge-labels"></div>
<div class="canvas-nodes">{{range .Nodes}}{{template "canvas-node" .}}{{end}}</div>
</div>
<p class="canvas-hint"{{if .Nodes}} hidden{{end}}>Double-click to add a note</p>
</div>
</div>
<script type="application/json" id="canvas-data">{{.Doc}}</script>
</div>
{{if not .Error}}
<script src="{{asset "editor/vendor/codemirror.bundle.js"}}"></script>
<script src="{{asset "editor/tables.js"}}"></script>
<script src="{{asset "editor/dates.js"}}"></script>
<script src="{{asset "editor/movie.js"}}"></script>
<script src="{{asset "editor/wikicomplete.js"}}"></script>
<script src="{{asset "editor/core.js"}}"></script>
{{end}}
<script src="{{asset "canvas/canvas.js"}}"></script>
{{end}}
+20
View File
@@ -0,0 +1,20 @@
{{/* One canvas node element (canvas.go canvasNodeView). Rendered for the
whole canvas on load and for a single node by POST ?render, so the editor
(canvas/canvas.js) never builds node markup itself. Geometry is in canvas
units; canvas.js keeps style and data-color in sync while editing. */}}
{{define "canvas-node"}}<div class="canvas-node canvas-node-{{.Type}}" data-id="{{.ID}}"{{with .Preset}} data-color="{{.}}"{{end}} style="left: {{.X}}px; top: {{.Y}}px; width: {{.W}}px; height: {{.H}}px;{{with .Hex}} --node-color: {{.}};{{end}}">
{{- if .Label}}<div class="canvas-node-label truncate">{{if .LabelURL}}<a href="{{.LabelURL}}">{{.Label}}</a>{{else}}{{.Label}}{{end}}</div>{{end -}}
<div class="canvas-node-body{{if .Markdown}} content{{end}}">{{.Body}}</div></div>{{end}}
{{define "canvas-media"}}{{if eq .Kind "image"}}<a class="canvas-media" href="{{.URL}}"><img src="{{.Thumb}}" alt="" loading="lazy" draggable="false"></a>
{{- else if eq .Kind "video"}}<video class="canvas-media" src="{{.URL}}" controls preload="metadata"></video>
{{- else}}<audio class="canvas-media" src="{{.URL}}" controls preload="metadata"></audio>{{end}}{{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-missing"}}<p class="canvas-missing">Missing: {{.}}</p>{{end}}
{{define "canvas-group-bg"}}<div class="canvas-group-bg canvas-group-bg-{{.Style}}" style="background-image: url('{{.URL}}')"></div>{{end}}