Missing changes
This commit is contained in:
+3
-1
@@ -123,7 +123,9 @@
|
|||||||
// folder anchors there end in "/" and fall through to navigation.
|
// folder anchors there end in "/" and fall through to navigation.
|
||||||
// Search file-result anchors join in too: page results end in "/"
|
// Search file-result anchors join in too: page results end in "/"
|
||||||
// and fall through to navigation, file results don't and open locally.
|
// and fall through to navigation, file results don't and open locally.
|
||||||
var anchor = e.target.closest('.list-item a, a.thumb-tile, .photo-grid a, aside.tree-sidebar a.tree-file, .search-card a');
|
// Embedded images (a.embed-link inside rendered content) wrap the
|
||||||
|
// raw file href, so they open the full-size original locally too.
|
||||||
|
var anchor = e.target.closest('.list-item a, a.thumb-tile, .photo-grid a, aside.tree-sidebar a.tree-file, .search-card a, .content a.embed-link');
|
||||||
if (!anchor) return;
|
if (!anchor) return;
|
||||||
var item = anchor.closest('.list-item');
|
var item = anchor.closest('.list-item');
|
||||||
// Only intercept the primary file link, and only for files (not folders).
|
// Only intercept the primary file link, and only for files (not folders).
|
||||||
|
|||||||
@@ -308,6 +308,42 @@ main > h2 {
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Embedded images ===
|
||||||
|
Wiki-style figure box: a bounded, framed thumbnail with an optional caption.
|
||||||
|
The fixed width matches the 300px thumbnail request so the box never scales
|
||||||
|
the image up. left/right float so body text wraps; center is a plain block. */
|
||||||
|
.embed {
|
||||||
|
margin: var(--space-2) 0;
|
||||||
|
width: 300px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: var(--space-2);
|
||||||
|
background: var(--bg-panel);
|
||||||
|
border: var(--border);
|
||||||
|
}
|
||||||
|
.embed-left {
|
||||||
|
float: left;
|
||||||
|
margin-right: var(--space-4);
|
||||||
|
}
|
||||||
|
.embed-right {
|
||||||
|
float: right;
|
||||||
|
margin-left: var(--space-4);
|
||||||
|
}
|
||||||
|
.embed-center {
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
.embed .embed-link { display: block; }
|
||||||
|
.embed img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.embed figcaption {
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
a.heading-anchor {
|
a.heading-anchor {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
margin-right: 0.4em;
|
margin-right: 0.4em;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ var md goldmark.Markdown
|
|||||||
// targets against the filesystem.
|
// targets against the filesystem.
|
||||||
func initMarkdown(root string) {
|
func initMarkdown(root string) {
|
||||||
md = goldmark.New(
|
md = goldmark.New(
|
||||||
goldmark.WithExtensions(extension.GFM, extension.Table, newWikiLinkExt(root), newExtLinksExt()),
|
goldmark.WithExtensions(extension.GFM, extension.Table, newWikiLinkExt(root), newWikiEmbedExt(root), newExtLinksExt()),
|
||||||
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
|
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
|
||||||
goldmark.WithRendererOptions(html.WithUnsafe(), html.WithHardWraps()),
|
goldmark.WithRendererOptions(html.WithUnsafe(), html.WithHardWraps()),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ import (
|
|||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
thumbnailers = append(thumbnailers, &imageThumbnailer{})
|
thumbnailers = append(thumbnailers, &imageThumbnailer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isImageFile reports whether name is a raster image we can thumbnail and thus
|
||||||
|
// embed on a page. Video files also carry thumbnails but are not embeddable, so
|
||||||
|
// this keys on the image thumbnailer's own extension set rather than
|
||||||
|
// hasThumbnail.
|
||||||
|
func isImageFile(name string) bool {
|
||||||
|
return (&imageThumbnailer{}).CanHandle(strings.ToLower(filepath.Ext(name)))
|
||||||
|
}
|
||||||
|
|
||||||
type imageThumbnailer struct{}
|
type imageThumbnailer struct{}
|
||||||
|
|
||||||
func (it *imageThumbnailer) CanHandle(ext string) bool {
|
func (it *imageThumbnailer) CanHandle(ext string) bool {
|
||||||
|
|||||||
+27
-4
@@ -115,6 +115,23 @@ func wikiTargetHref(target string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wikiFileHref converts a wiki target to a URL href for the raw file: each
|
||||||
|
// segment is percent-encoded and, unlike wikiTargetHref, no trailing slash is
|
||||||
|
// appended. Used for image-embed click-through so the href points straight at
|
||||||
|
// the file (companion click-interception skips anything ending in "/").
|
||||||
|
func wikiFileHref(target string) string {
|
||||||
|
target = normalizeWikiTarget(target)
|
||||||
|
if target == "/" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
for _, seg := range strings.Split(strings.TrimPrefix(target, "/"), "/") {
|
||||||
|
b.WriteByte('/')
|
||||||
|
b.WriteString(url.PathEscape(seg))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
// wikiTargetExists reports whether the on-disk path backing the target exists
|
// wikiTargetExists reports whether the on-disk path backing the target exists
|
||||||
// under root. Any existing path — file or folder — counts as resolved; only a
|
// under root. Any existing path — file or folder — counts as resolved; only a
|
||||||
// missing path is treated as broken.
|
// missing path is treated as broken.
|
||||||
@@ -148,13 +165,20 @@ func (r *wikiLinkRenderer) render(w util.BufWriter, source []byte, node ast.Node
|
|||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
n := node.(*wikiLinkNode)
|
n := node.(*wikiLinkNode)
|
||||||
target := string(n.Target)
|
writeWikiLink(w, r.root, string(n.Target), string(n.Display))
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWikiLink renders a wiki-link anchor for target with an optional display
|
||||||
|
// override. An empty display falls back to the target's last segment; a target
|
||||||
|
// that does not resolve on disk gets the `broken` class. Shared by the wiki-link
|
||||||
|
// renderer and the embed renderer's link fallback.
|
||||||
|
func writeWikiLink(w util.BufWriter, root, target, display string) {
|
||||||
href := wikiTargetHref(target)
|
href := wikiTargetHref(target)
|
||||||
display := string(n.Display)
|
|
||||||
if display == "" {
|
if display == "" {
|
||||||
display = wikiDefaultDisplay(target)
|
display = wikiDefaultDisplay(target)
|
||||||
}
|
}
|
||||||
broken := !wikiTargetExists(r.root, target)
|
broken := !wikiTargetExists(root, target)
|
||||||
|
|
||||||
w.WriteString(`<a href="`)
|
w.WriteString(`<a href="`)
|
||||||
w.WriteString(href)
|
w.WriteString(href)
|
||||||
@@ -165,7 +189,6 @@ func (r *wikiLinkRenderer) render(w util.BufWriter, source []byte, node ast.Node
|
|||||||
w.WriteString(`>`)
|
w.WriteString(`>`)
|
||||||
w.Write(util.EscapeHTML([]byte(display)))
|
w.Write(util.EscapeHTML([]byte(display)))
|
||||||
w.WriteString(`</a>`)
|
w.WriteString(`</a>`)
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type wikiLinkExt struct{ root string }
|
type wikiLinkExt struct{ root string }
|
||||||
|
|||||||
Reference in New Issue
Block a user