Reorganize assets folder
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
function encodePickedPath(p) {
|
||||
if (p === '/' || p === '') return '/';
|
||||
return '/' + p.replace(/^\/+/, '').split('/').map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
function promptPageName(title, initial, confirmLabel, onName) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'modal-input';
|
||||
input.placeholder = 'Page name';
|
||||
if (initial) input.value = initial;
|
||||
openModal({
|
||||
title: title,
|
||||
body: input,
|
||||
confirm: {
|
||||
label: confirmLabel,
|
||||
onConfirm: function () {
|
||||
var name = input.value.trim();
|
||||
if (!name) return;
|
||||
onName(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function newPage() {
|
||||
var current = decodeURIComponent(window.location.pathname).replace(/\/+$/, '') || '/';
|
||||
openTreePicker({
|
||||
title: 'New page — where?',
|
||||
mode: 'folder',
|
||||
initialPath: current,
|
||||
preselect: current,
|
||||
hideFiles: true,
|
||||
confirmLabel: 'NEXT',
|
||||
onSelect: function (parentPath) {
|
||||
promptPageName('New page — name?', '', 'CREATE', function (name) {
|
||||
var base = parentPath === '/' ? '/' : encodePickedPath(parentPath) + '/';
|
||||
window.location.href = base + encodeURIComponent(name) + '/?edit';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function movePage() {
|
||||
var current = decodeURIComponent(window.location.pathname).replace(/\/+$/, '');
|
||||
if (!current) return;
|
||||
var segs = current.split('/').filter(Boolean);
|
||||
var currentName = segs[segs.length - 1] || '';
|
||||
var parent = '/' + segs.slice(0, -1).join('/');
|
||||
if (parent === '/') parent = '/';
|
||||
|
||||
openTreePicker({
|
||||
title: 'Move — new parent?',
|
||||
mode: 'folder',
|
||||
initialPath: parent,
|
||||
preselect: parent,
|
||||
hideFiles: true,
|
||||
confirmLabel: 'NEXT',
|
||||
onSelect: function (newParent) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'modal-input';
|
||||
input.placeholder = 'Page name';
|
||||
input.value = currentName;
|
||||
|
||||
var linksCheckbox = document.createElement('input');
|
||||
linksCheckbox.type = 'checkbox';
|
||||
linksCheckbox.id = 'move-update-links';
|
||||
|
||||
var linksLabel = document.createElement('label');
|
||||
linksLabel.htmlFor = linksCheckbox.id;
|
||||
linksLabel.className = 'modal-checkbox';
|
||||
linksLabel.appendChild(linksCheckbox);
|
||||
linksLabel.appendChild(document.createTextNode('Update links'));
|
||||
|
||||
var body = document.createDocumentFragment();
|
||||
body.appendChild(input);
|
||||
body.appendChild(linksLabel);
|
||||
|
||||
openModal({
|
||||
title: 'Move — new name?',
|
||||
body: body,
|
||||
confirm: {
|
||||
label: 'MOVE',
|
||||
onConfirm: function () {
|
||||
var name = input.value.trim();
|
||||
if (!name) return;
|
||||
var dest = (newParent === '/' ? '' : newParent) + '/' + name;
|
||||
var action = window.location.pathname + '?move=' +
|
||||
encodeURIComponent(dest);
|
||||
if (linksCheckbox.checked) action += '&links=1';
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = action;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deletePage() {
|
||||
var decodedPath = decodeURIComponent(window.location.pathname);
|
||||
|
||||
openModal({
|
||||
title: 'Delete page',
|
||||
body: 'Delete ' + decodedPath + ' and everything inside it?',
|
||||
confirm: {
|
||||
label: 'DELETE',
|
||||
danger: true,
|
||||
enterConfirms: false,
|
||||
onConfirm: function () {
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = window.location.pathname + '?delete=1';
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
},
|
||||
cancel: { autofocus: true },
|
||||
swapButtons: true
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
wireDropdown(document.querySelector('[data-action="actions-drop"]'));
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
(function () {
|
||||
var content = document.querySelector("main");
|
||||
if (!content) return;
|
||||
|
||||
var headings = content.querySelectorAll("h2, h3, h4");
|
||||
if (!headings) return
|
||||
|
||||
headings.forEach(function (h) {
|
||||
if (!h.id) return;
|
||||
var a = document.createElement('a');
|
||||
a.href = '#' + h.id;
|
||||
a.className = 'heading-anchor';
|
||||
a.setAttribute('aria-label', 'Link to this section');
|
||||
a.textContent = '#';
|
||||
h.insertBefore(a, h.firstChild);
|
||||
});
|
||||
}());
|
||||
@@ -0,0 +1,12 @@
|
||||
(function () {
|
||||
document.querySelectorAll('.content a[href^="http"]').forEach(function (a) {
|
||||
var hostname = new URL(a.href).hostname;
|
||||
var img = document.createElement('img');
|
||||
img.src = 'https://icons.duckduckgo.com/ip3/' + hostname + '.ico';
|
||||
img.width = 16;
|
||||
img.height = 16;
|
||||
img.style.verticalAlign = 'middle';
|
||||
img.style.marginRight = '3px';
|
||||
a.prepend(img);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,52 @@
|
||||
{{define "headScripts"}}<script src="/_/page/actions.js"></script>{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
{{if .Content}}
|
||||
<div class="content">{{.Content}}</div>
|
||||
{{end}}
|
||||
{{if .SpecialContent}}
|
||||
<div class="content">{{.SpecialContent}}</div>
|
||||
{{end}}
|
||||
{{if .Entries}}
|
||||
<h2 id="files">Files</h2>
|
||||
<div class="listing">
|
||||
{{range .Entries}}
|
||||
<div class="listing-item">
|
||||
<span class="icon">{{.Icon}}</span>
|
||||
<a href="{{.URL}}">{{.Name}}</a>
|
||||
<span class="meta">{{.Meta}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if not .Content}}
|
||||
{{if not .SpecialContent}}
|
||||
<p class="empty">Empty folder — <a href="?edit">[CREATE]</a></p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if or .Content .SpecialContent}}
|
||||
<script src="/_/page/content.js"></script>
|
||||
<script src="/_/page/anchors.js"></script>
|
||||
<script src="/_/page/toc.js"></script>
|
||||
<script src="/_/page/tasks.js"></script>
|
||||
{{end}}
|
||||
{{if .Content}}
|
||||
<script src="/_/page/sections.js"></script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "extras"}}
|
||||
{{if .SidebarWidget}}{{.SidebarWidget}}{{end}}
|
||||
{{if .CanEdit}}
|
||||
<div class="fab dropdown">
|
||||
<button class="btn btn-fab" data-action="actions-drop" title="Actions" aria-label="Actions">≡</button>
|
||||
<div class="dropdown-menu align-right open-up">
|
||||
<button class="btn dropdown-item" onclick="newPage()" title="New page (N)">NEW</button>
|
||||
<a class="btn dropdown-item" href="?edit" title="Edit page (E)">EDIT</a>
|
||||
{{if not .IsRoot}}
|
||||
<button class="btn dropdown-item" onclick="movePage()" title="Move page (M)">MOVE</button>
|
||||
<button class="btn dropdown-item danger" onclick="deletePage()" title="Delete page">DELETE</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,17 @@
|
||||
(function () {
|
||||
var content = document.querySelector('.content');
|
||||
if (!content) return;
|
||||
var headings = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||
if (!headings.length) return;
|
||||
|
||||
// Section 0 is pre-heading content, editable via full-page edit.
|
||||
// Sections 1..N each start at a heading; that is the index sent to the server.
|
||||
headings.forEach(function (h, i) {
|
||||
var a = document.createElement('a');
|
||||
a.href = '?edit§ion=' + (i + 1);
|
||||
a.className = 'btn btn-small';
|
||||
a.textContent = 'edit';
|
||||
h.appendChild(document.createTextNode(' '))
|
||||
h.appendChild(a);
|
||||
});
|
||||
}());
|
||||
@@ -0,0 +1,24 @@
|
||||
(function () {
|
||||
document.querySelectorAll('input.task-checkbox[data-task-index]').forEach(function (cb) {
|
||||
cb.addEventListener('change', function () {
|
||||
var idx = cb.dataset.taskIndex;
|
||||
var checked = cb.checked;
|
||||
cb.disabled = true;
|
||||
fetch(window.location.pathname + '?toggle=' + idx, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'checked=' + checked
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
cb.checked = !checked;
|
||||
alert('Failed to save task state (' + res.status + ')');
|
||||
}
|
||||
}).catch(function () {
|
||||
cb.checked = !checked;
|
||||
alert('Failed to save task state');
|
||||
}).finally(function () {
|
||||
cb.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,59 @@
|
||||
(function () {
|
||||
var content = document.querySelector("main");
|
||||
if (!content) return;
|
||||
|
||||
var headings = content.querySelectorAll("h2, h3, h4");
|
||||
if (headings.length < 2) return;
|
||||
|
||||
var nav = document.createElement("nav");
|
||||
nav.className = "toc";
|
||||
|
||||
var header = document.createElement("div");
|
||||
header.className = "panel-header";
|
||||
header.textContent = "Contents";
|
||||
nav.appendChild(header);
|
||||
|
||||
var list = document.createElement("ul");
|
||||
headings.forEach(function (h) {
|
||||
if (!h.id) return;
|
||||
var li = document.createElement("li");
|
||||
li.className = "toc-" + h.tagName.toLowerCase();
|
||||
var a = document.createElement("a");
|
||||
a.href = "#" + h.id;
|
||||
var clone = h.cloneNode(true);
|
||||
clone.querySelectorAll(".btn, .muted, .heading-anchor").forEach(function (el) { el.remove(); });
|
||||
a.textContent = clone.textContent.trim();
|
||||
li.appendChild(a);
|
||||
list.appendChild(li);
|
||||
});
|
||||
nav.appendChild(list);
|
||||
|
||||
var toggle = document.createElement("button");
|
||||
toggle.type = "button";
|
||||
toggle.className = "panel-toggle";
|
||||
toggle.textContent = "Contents";
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
toggle.addEventListener("click", function () {
|
||||
var open = nav.classList.toggle("is-open");
|
||||
toggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
});
|
||||
|
||||
var main = document.querySelector("main");
|
||||
if (main) {
|
||||
main.parentNode.insertBefore(toggle, main);
|
||||
main.parentNode.insertBefore(nav, main);
|
||||
} else {
|
||||
document.body.appendChild(toggle);
|
||||
document.body.appendChild(nav);
|
||||
}
|
||||
|
||||
var pageHeader = document.querySelector("header");
|
||||
function updateTop() {
|
||||
if (!pageHeader || getComputedStyle(nav).position !== "fixed") return;
|
||||
var rect = pageHeader.getBoundingClientRect();
|
||||
nav.style.top = Math.max(8, rect.bottom + 8) + "px";
|
||||
}
|
||||
window.addEventListener("scroll", updateTop, { passive: true });
|
||||
window.addEventListener("resize", updateTop);
|
||||
updateTop();
|
||||
})();
|
||||
Reference in New Issue
Block a user