diff --git a/public/assets/components/decorator_shell_filemanager.js b/public/assets/components/decorator_shell_filemanager.js
index f0762acf..4a9611e9 100644
--- a/public/assets/components/decorator_shell_filemanager.js
+++ b/public/assets/components/decorator_shell_filemanager.js
@@ -32,7 +32,7 @@ export default function(ctrl) {
const $main = qs($page, `[data-bind="filemanager-children"]`);
$main.classList.remove("hidden");
ctrl(createRender($main));
- ctrlSidebar(createRender(qs($page, `[data-bind="sidebar"]`)));
+ ctrlSidebar(createRender(qs($page, `[data-bind="sidebar"]`)), {});
onDestroy(async() => {
if ((history.state.previous || "").startsWith("/view/") && location.pathname.startsWith("/files/")) {
await animate($main, { time: 100, keyframes: slideYOut(20), fill: "none" });
diff --git a/public/assets/components/sidebar.css b/public/assets/components/sidebar.css
index b033457e..6884e948 100644
--- a/public/assets/components/sidebar.css
+++ b/public/assets/components/sidebar.css
@@ -17,6 +17,21 @@
direction: rtl;
box-sizing: border-box;
}
+.component_filemanager_shell .component_sidebar .component_skeleton {
+ margin-bottom: 5px;
+ width: calc(100% - 5px);
+ margin-left: 5px;
+ opacity: 0;
+ animation-duration: 0.5s;
+ animation-delay: 1s;
+ animation-name: skeleton-appear;
+ animation-fill-mode: forwards;
+ animation-iteration-count: 1;
+}
+@keyframes skeleton-appear {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
.component_filemanager_shell .component_sidebar > div { direction: ltr; }
.component_filemanager_shell .component_sidebar h3 {
display: flex;
@@ -79,7 +94,8 @@ body.touch-no .component_filemanager_shell .component_sidebar h3 img:hover {
width: 100%;
box-sizing: border-box;
}
-.component_filemanager_shell .component_sidebar a:hover, .component_filemanager_shell .component_sidebar a[aria-selected="true"] {
+.component_filemanager_shell .component_sidebar [data-bind="your-files"] a:hover,
+.component_filemanager_shell .component_sidebar a[aria-selected="true"] {
background: var(--border);
border-radius: 3px;
}
@@ -107,3 +123,26 @@ body.touch-no .component_filemanager_shell .component_sidebar h3 img:hover {
.component_filemanager_shell li.pointer {
cursor: pointer;
}
+
+.component_filemanager_shell .component_sidebar [data-bind="taglist"] a {
+ margin-bottom: 2px;
+ justify-content: space-between;
+ font-size: 0.95rem;
+}
+.component_filemanager_shell .component_sidebar [data-bind="taglist"] a[aria-selected="true"] {
+ background: var(--light);
+ color: #f2f2f2;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+}
+.component_filemanager_shell .component_sidebar [data-bind="taglist"] a[aria-selected="true"] svg {
+ display: block;
+}
+.component_filemanager_shell .component_sidebar [data-bind="taglist"] a svg {
+ display: none;
+ background: rgba(255, 255, 255, 0.03);
+ align-self: center;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ padding: 4px;
+}
diff --git a/public/assets/components/sidebar.js b/public/assets/components/sidebar.js
index 52d3994a..3117c700 100644
--- a/public/assets/components/sidebar.js
+++ b/public/assets/components/sidebar.js
@@ -1,16 +1,20 @@
import { createElement, createRender, onDestroy } from "../lib/skeleton/index.js";
import rxjs, { effect, onClick } from "../lib/rx.js";
+import ajax from "../lib/ajax.js";
import assert from "../lib/assert.js";
import { fromHref, toHref } from "../lib/skeleton/router.js";
import { qs, qsa, safe } from "../lib/dom.js";
import { forwardURLParams } from "../lib/path.js";
import { settingsGet, settingsSave } from "../lib/store.js";
+import { get as getConfig } from "../model/config.js";
import { loadCSS } from "../helpers/loader.js";
import t from "../locales/index.js";
import cache from "../pages/filespage/cache.js";
import { hooks, mv as mv$ } from "../pages/filespage/model_files.js";
import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js";
import { mv as mvVL, withVirtualLayer } from "../pages/filespage/model_virtual_layer.js";
+import { getCurrentPath } from "../pages/viewerpage/common.js";
+import { generateSkeleton } from "./skeleton.js";
const state = { scrollTop: 0, $cache: null };
const mv = (from, to) => withVirtualLayer(
@@ -18,7 +22,7 @@ const mv = (from, to) => withVirtualLayer(
mvVL(from, to),
);
-export default async function ctrlSidebar(render, nRestart = 0) {
+export default async function ctrlSidebar(render, { nRestart = 0 }) {
if (!shouldDisplay()) return;
const $sidebar = render(createElement(`
@@ -28,11 +32,6 @@ export default async function ctrlSidebar(render, nRestart = 0) {
-
-
-
- ${t("Tags")}
-
`));
@@ -76,7 +75,12 @@ export default async function ctrlSidebar(render, nRestart = 0) {
ctrlNavigationPane(render, { $sidebar, nRestart });
// feature: tag viewer
- ctrlTagPane(createRender(qs($sidebar, `[data-bind="your-tags"]`)));
+ effect(rxjs.merge(
+ rxjs.of(null),
+ rxjs.fromEvent(window, "filestash::tag"),
+ ).pipe(rxjs.tap(() => {
+ ctrlTagPane(createRender(qs($sidebar, `[data-bind="your-tags"]`)));
+ })));
}
const withResize = (function() {
@@ -254,37 +258,63 @@ async function _createListOfFiles(path, currentName, dirpath) {
}
async function ctrlTagPane(render) {
- const $page = createElement(`
-
- `);
- render($page);
+ if (!getConfig("enable_tags", false)) return;
+ render(createElement(`${generateSkeleton(2)}
`));
- // only enable this pane in canary mode until it's actually ready
- if (new URLSearchParams(location.search).get("canary") !== "true") {
- $page.classList.add("hidden");
- const orFail = (something) => assert.type(something, HTMLElement);
- orFail(orFail($page.parentElement).previousElementSibling).classList.add("hidden");
+ const $page = createElement(`
+
+
+
+ ${t("Tags")}
+
+
+
+ `);
+ const tags = await ajax({
+ url: forwardURLParams(`api/metadata/search`, ["share"]),
+ method: "POST",
+ responseType: "json",
+ body: JSON.stringify({
+ "tags": [],
+ "path": getCurrentPath("(/view/|/files/)"),
+ }),
+ }).pipe(
+ rxjs.map(({ responseJSON }) =>
+ responseJSON.results
+ .filter(({type}) => type === "folder")
+ .map(({ name }) => name)
+ .sort()
+ ),
+ rxjs.catchError(() => rxjs.of([])),
+ ).toPromise();
+ if (tags.length === 0) {
+ render(createElement(""));
return;
}
+ render($page);
- const tags = [
- { name: t("Bookmark"), color: "green" },
- { name: "important", color: "red" },
- { name: "foobar", color: "saddlebrown" },
- ];
- const $tmpl = (name, color) => createElement(`
-
-
- ${name}
-
- `);
const $fragment = document.createDocumentFragment();
- tags.forEach(({ name, color }) => {
- $fragment.appendChild($tmpl(name, color));
+ tags.forEach((name) => {
+ const $tag = createElement(`
+
+ ${name}
+
+
+ `);
+ const url = new URL(location.href);
+ if (url.searchParams.getAll("tag").indexOf(name) === -1) {
+ $tag.setAttribute("href", forwardURLParams(getCurrentPath() + "?tag=" + name, ["share", "tag"]));
+ } else {
+ url.searchParams.delete("tag", name);
+ $tag.setAttribute("href", url.toString());
+ $tag.setAttribute("aria-selected", "true");
+ }
+ $fragment.appendChild($tag);
});
qs($page, `[data-bind="taglist"]`).appendChild($fragment);
}
diff --git a/public/assets/lib/path.js b/public/assets/lib/path.js
index 930cb3d7..403fbed3 100644
--- a/public/assets/lib/path.js
+++ b/public/assets/lib/path.js
@@ -12,10 +12,11 @@ export function join(baseURL, segment) {
}
export function forwardURLParams(url, allowed = []) {
- const _url = new URL(window.location.origin + "/" + url);
+ const link = new URL(window.location.origin + "/" + url);
for (const [key, value] of new URLSearchParams(location.search)) {
if (allowed.indexOf(key) < 0) continue;
- _url.searchParams.set(key, value);
+ else if (link.searchParams.getAll(key).indexOf(value) !== -1) continue;
+ link.searchParams.append(key, value);
}
- return _url.pathname.substring(1) + _url.search;
+ return link.pathname.substring(1) + link.search;
}
diff --git a/public/assets/pages/filespage/ctrl_submenu.js b/public/assets/pages/filespage/ctrl_submenu.js
index 34da7f24..151fba2d 100644
--- a/public/assets/pages/filespage/ctrl_submenu.js
+++ b/public/assets/pages/filespage/ctrl_submenu.js
@@ -119,7 +119,7 @@ function componentLeft(render, { $scroll, getSelectionLength$ }) {
-