mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-09 09:52:54 +01:00
feature (wip): wip for tags
This commit is contained in:
parent
c4e5da9169
commit
dbf0878335
10 changed files with 297 additions and 71 deletions
4
client/assets/icons/tag.svg
Normal file
4
client/assets/icons/tag.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" style="fill:#6f6f6f;stroke:#6f6f6f;">
|
||||||
|
<path d="m 51.741297,117.22165 c -2.466177,0 -4.788482,-0.96594 -6.535367,-2.69223 L 13.885528,83.208986 c -1.726339,-1.746857 -2.692249,-4.069156 -2.692249,-6.535348 0,-2.466185 0.96591,-4.788492 2.692249,-6.535368 L 66.764417,17.259402 c 3.000527,-3.000523 8.446629,-5.261167 12.700782,-5.261167 h 27.703341 c 5.09681,0 9.24816,4.151389 9.24816,9.227587 v 27.703353 c 0,4.233618 -2.26065,9.700278 -5.26116,12.700812 L 58.276658,114.50887 c -1.746869,1.74684 -4.04863,2.71278 -6.535361,2.71278 z M 79.465199,18.163668 c -2.548374,0 -6.535359,1.644105 -8.32334,3.452635 L 18.262982,74.495181 c -0.575448,0.575452 -0.883718,1.356397 -0.883718,2.178457 0,0.8426 0.328824,1.603006 0.883718,2.178453 l 31.320416,31.320439 c 0.575435,0.5754 1.356403,0.88368 2.178452,0.88368 0.842611,0 1.603015,-0.32882 2.178449,-0.88368 L 106.8192,57.293642 c 1.80851,-1.808537 3.45264,-5.774997 3.45264,-8.343889 V 21.246394 c 0,-1.705772 -1.37695,-3.082726 -3.08273,-3.082726 z" style="stroke-width:2.2;" />
|
||||||
|
<circle cx="142.1252" cy="101.61908" r="28.324646" style="fill-opacity:0;stroke-width:20;" transform="matrix(0.38692388,0,0,0.38692388,30.309631,2.968202)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -65,6 +65,8 @@ export const Icon = (props) => {
|
||||||
img = "/assets/icons/delete.svg";
|
img = "/assets/icons/delete.svg";
|
||||||
} else if (props.name === "share") {
|
} else if (props.name === "share") {
|
||||||
img = "/assets/icons/share.svg";
|
img = "/assets/icons/share.svg";
|
||||||
|
} else if (props.name === "tag") {
|
||||||
|
img = "/assets/icons/tag.svg";
|
||||||
} else if (props.name === "bucket") {
|
} else if (props.name === "bucket") {
|
||||||
img = img_bucket;
|
img = img_bucket;
|
||||||
} else if (props.name === "download_white") {
|
} else if (props.name === "download_white") {
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,61 @@
|
||||||
import { cache, currentShare, currentBackend } from "../helpers/";
|
import { cache, currentShare, currentBackend } from "../helpers/";
|
||||||
|
|
||||||
class TagManager {
|
class TagManager {
|
||||||
all(tagPath = "/", maxSize = -1) {
|
all(tagPath = "/") {
|
||||||
return cache.get(cache.FILE_TAG, [currentBackend(), currentShare()]).then((DB) => {
|
return cache.get(cache.FILE_TAG, [currentBackend(), currentShare()]).then((DB) => {
|
||||||
if (DB === null) {
|
if (DB === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
const tags = this._tagPathStringToArray(tagPath);
|
||||||
if (tagPath == "/") {
|
if (tags.length === 0) {
|
||||||
const scoreFn = (acc, el) => (acc + el.replace(/[^\/]/g, "").length);
|
return Object.keys(DB.tags);
|
||||||
const tags = Object.keys(DB.tags).sort((a, b) => {
|
|
||||||
if (DB.tags[a].length === DB.tags[b].length) {
|
|
||||||
return DB.tags[a].reduce(scoreFn, 0) - DB.tags[b].reduce(scoreFn, 0);
|
|
||||||
}
|
|
||||||
return DB.tags[a].length < DB.tags[b].length ? 1 : -1;
|
|
||||||
});
|
|
||||||
if(tags.length === 0) {
|
|
||||||
return ["Bookmark"];
|
|
||||||
} else if(tags.length >= 5) {
|
|
||||||
return ["All"].concat(tags.slice(0, 5));
|
|
||||||
}
|
|
||||||
return tags;
|
|
||||||
}
|
}
|
||||||
return [
|
|
||||||
// "Bookmark", "wiki", "B", "C", "D", "E", "F"
|
// STEP1: build the graph of selected tags
|
||||||
];
|
|
||||||
|
// STEP2: build the node that connects to the initial graph
|
||||||
|
return Object.keys(DB.tags)
|
||||||
|
.map((tag) => {
|
||||||
|
if (tags.indexOf(tag) !== -1) { // ignore tag that are already selected
|
||||||
|
return { tag, scrore: 0 };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tag,
|
||||||
|
score: DB.tags[tag].reduce((path, acc) => {
|
||||||
|
// TODO
|
||||||
|
return acc;
|
||||||
|
}, 0),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((t) => t && t.score > 0)
|
||||||
|
.sort((a, b) => a.score > b.score)
|
||||||
|
.map((d) => d.tag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
files(tagPath) {
|
files(tagPath) {
|
||||||
const tags = this._tagPathStringToArray(tagPath, false);
|
let tags = this._tagPathStringToArray(tagPath);
|
||||||
if (tags.length === 0) return Promise.resolve([]);
|
|
||||||
else if (tags.length > 1) return Promise.resolve([]); // TODO
|
|
||||||
|
|
||||||
return cache.get(cache.FILE_TAG, [currentBackend(), currentShare()]).then((DB) => {
|
return cache.get(cache.FILE_TAG, [currentBackend(), currentShare()]).then((DB) => {
|
||||||
if(!DB) return [];
|
if (!DB) return [];
|
||||||
switch(tags[0]) {
|
else if (!DB.tags) return [];
|
||||||
case "All":
|
else if (tags.length === 0) tags = Object.keys(DB.tags);
|
||||||
return this.all()
|
|
||||||
.then((tags) => (tags.reduce((acc, el) => {
|
// push all the candidates in an array
|
||||||
return DB.tags[el] ? acc.concat(DB.tags[el]) : acc;
|
let paths = (DB.tags[tags[0]] || []).map((t) => ({path: t, tag: tags[0]}));
|
||||||
}, [])));
|
for (let i=1; i<tags.length; i++) {
|
||||||
default:
|
const tp = DB.tags[tags[i]];
|
||||||
return Promise.resolve(DB.tags[tags[0]] || []);
|
if (!tp) continue;
|
||||||
|
paths = paths.concat(tp.map((t) => ({path: t, tag: tags[i]})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark element of the array that shouldn't be here
|
||||||
|
return paths;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_tagPathStringToArray(tagPathString, removeFirst = true) {
|
_tagPathStringToArray(tagPathString) {
|
||||||
return tagPathString
|
return tagPathString.split("/").filter((r) => r !== "");
|
||||||
.split("/")
|
|
||||||
.filter((r) => r !== "" && (removeFirst ? r !== "All" : true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addTagToFile(tag, path) {
|
addTagToFile(tag, path) {
|
||||||
|
|
@ -72,13 +77,15 @@ class TagManager {
|
||||||
DB.tags[tag].splice(idx, 1);
|
DB.tags[tag].splice(idx, 1);
|
||||||
if (DB.tags[tag].length === 0) {
|
if (DB.tags[tag].length === 0) {
|
||||||
delete DB.tags[tag];
|
delete DB.tags[tag];
|
||||||
delete DB.weight[tag];
|
|
||||||
}
|
}
|
||||||
return DB;
|
return DB;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
import(DB) {
|
import(DB) {
|
||||||
|
if(JSON.stringify(Object.keys(DB)) !== JSON.stringify(["tags", "share", "backend"])) {
|
||||||
|
return Promise.reject(new Error("Not Valid"));
|
||||||
|
}
|
||||||
return cache.upsert(cache.FILE_TAG, [currentBackend(), currentShare()], () => {
|
return cache.upsert(cache.FILE_TAG, [currentBackend(), currentShare()], () => {
|
||||||
return DB;
|
return DB;
|
||||||
});
|
});
|
||||||
|
|
@ -89,7 +96,7 @@ class TagManager {
|
||||||
return cache.get(cache.FILE_TAG, key)
|
return cache.get(cache.FILE_TAG, key)
|
||||||
.then((a) => {
|
.then((a) => {
|
||||||
if (a === null) {
|
if (a === null) {
|
||||||
return {tags: {}, weight: {}, share: key[1], backend: key[0]}
|
return {tags: {}, share: key[1], backend: key[0]}
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ export function FrequentlyAccess({ files, tags }) {
|
||||||
<NgIf cond={!!tags && tags.length > 0}>
|
<NgIf cond={!!tags && tags.length > 0}>
|
||||||
<span className="caption">{t("Tag")}</span>
|
<span className="caption">{t("Tag")}</span>
|
||||||
<div className="frequent_wrapper">
|
<div className="frequent_wrapper">
|
||||||
|
<Link to={"/tags/"}>
|
||||||
|
<Icon name={"directory"} />
|
||||||
|
<div>All</div>
|
||||||
|
</Link>
|
||||||
{
|
{
|
||||||
tags && tags.map((tag, index) => {
|
tags && tags.map((tag, index) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
139
client/pages/filespage/tag.js
Normal file
139
client/pages/filespage/tag.js
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Icon, Input,
|
||||||
|
} from "../../components/";
|
||||||
|
import { Tags } from "../../model/";
|
||||||
|
import { t } from "../../locales/";
|
||||||
|
import "./tag.scss";
|
||||||
|
|
||||||
|
export function TagComponent({ path }) {
|
||||||
|
const [DB, setDB] = useState(null);
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Tags.export().then((db) => {
|
||||||
|
setDB(db);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onFormSubmit = (e) => {
|
||||||
|
if (!DB) return;
|
||||||
|
else if(!DB.tags) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
const it = input.trim().toLowerCase();
|
||||||
|
if (it === "") return;
|
||||||
|
|
||||||
|
const newDB = {...DB};
|
||||||
|
newDB.tags = {};
|
||||||
|
newDB.tags[it] = [path];
|
||||||
|
Object.keys(DB.tags).forEach((tag) => {
|
||||||
|
newDB.tags[tag] = DB.tags[tag];
|
||||||
|
});
|
||||||
|
setDB(newDB);
|
||||||
|
setInput("");
|
||||||
|
Tags.import(newDB);
|
||||||
|
};
|
||||||
|
const onClickTag = (tagName) => {
|
||||||
|
if (!DB) return;
|
||||||
|
else if(!DB.tags) return;
|
||||||
|
console.log("CLICK ON ", tagName, "idx", DB.tags[tagName].indexOf(path));
|
||||||
|
|
||||||
|
const newDB = {...DB};
|
||||||
|
if (isTagActive(tagName)) {
|
||||||
|
newDB.tags[tagName].splice(DB.tags[tagName].indexOf(path), 1);
|
||||||
|
} else {
|
||||||
|
newDB.tags[tagName].push(path);
|
||||||
|
}
|
||||||
|
setDB(newDB);
|
||||||
|
Tags.import(newDB);
|
||||||
|
};
|
||||||
|
const onClickMoveUp = (tagName) => {
|
||||||
|
if (!DB) return;
|
||||||
|
else if(!DB.tags) return;
|
||||||
|
|
||||||
|
const newDB = {...DB};
|
||||||
|
const keys = Object.keys(DB.tags) || [];
|
||||||
|
const n = keys.indexOf(tagName);
|
||||||
|
if (n === 0) return;
|
||||||
|
|
||||||
|
newDB.tags = {};
|
||||||
|
for (let i=0; i<keys.length; i++) {
|
||||||
|
if (i === n-1) {
|
||||||
|
newDB.tags[keys[i+1]] = DB.tags[keys[i+1]];
|
||||||
|
} else if (i === n) {
|
||||||
|
newDB.tags[keys[i-1]] = DB.tags[keys[i-1]];
|
||||||
|
} else {
|
||||||
|
newDB.tags[keys[i]] = DB.tags[keys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDB(newDB);
|
||||||
|
Tags.import(newDB);
|
||||||
|
};
|
||||||
|
const onClickMoveDown = (tagName) => {
|
||||||
|
if (!DB) return;
|
||||||
|
else if(!DB.tags) return;
|
||||||
|
|
||||||
|
const newDB = {...DB};
|
||||||
|
const keys = Object.keys(DB.tags) || [];
|
||||||
|
const n = keys.indexOf(tagName);
|
||||||
|
if (n === keys.length - 1) return;
|
||||||
|
|
||||||
|
newDB.tags = {};
|
||||||
|
for (let i=0; i<keys.length; i++) {
|
||||||
|
if (i === n) {
|
||||||
|
newDB.tags[keys[i+1]] = DB.tags[keys[i+1]];
|
||||||
|
} else if (i === n+1) {
|
||||||
|
newDB.tags[keys[i-1]] = DB.tags[keys[i-1]];
|
||||||
|
} else {
|
||||||
|
newDB.tags[keys[i]] = DB.tags[keys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDB(newDB);
|
||||||
|
Tags.import(newDB);
|
||||||
|
};
|
||||||
|
const onClickRemove = (tagName) => {
|
||||||
|
const newDB = {...DB};
|
||||||
|
delete newDB.tags[tagName];
|
||||||
|
Tags.import(newDB);
|
||||||
|
setDB(newDB);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTagActive = (tagName) => {
|
||||||
|
if (!DB) return false;
|
||||||
|
else if(!DB.tags) return false;
|
||||||
|
return DB.tags[tagName].indexOf(path) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAGS = DB && Object.keys(DB.tags);
|
||||||
|
return (
|
||||||
|
<div className="component_tag">
|
||||||
|
<form onSubmit={(e) => onFormSubmit(e)}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
placeholder={t("Create a Tag")}
|
||||||
|
autoFocus />
|
||||||
|
</form>
|
||||||
|
<div className="scroll-y">
|
||||||
|
{
|
||||||
|
TAGS && TAGS.length > 0 ?
|
||||||
|
Object.keys(DB.tags).map((tag) => (
|
||||||
|
<div key={tag} className={"box no-select" + (isTagActive(tag) ? " active" : "")}>
|
||||||
|
<div onClick={() => onClickTag(tag)}>{ tag } <span className="count">{ (DB.tags[tag] || []).length }</span></div>
|
||||||
|
<Icon name="arrow_top" onClick={() => onClickMoveUp(tag)} />
|
||||||
|
<Icon name="arrow_bottom" onClick={() => onClickMoveDown(tag)} />
|
||||||
|
<Icon name="close" onClick={() => onClickRemove(tag)} />
|
||||||
|
</div>
|
||||||
|
)) : (
|
||||||
|
<div className={"box no-select"}>
|
||||||
|
<div onClick={() => onClickTag(t("Bookmark"))}>{ t("Bookmark") }</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
client/pages/filespage/tag.scss
Normal file
43
client/pages/filespage/tag.scss
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
.component_tag {
|
||||||
|
input {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
input::placeholder {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
background: var(--bg-color);
|
||||||
|
transition: background 0.1s ease;
|
||||||
|
&.active{ background: var(--primary); color: var(--color); }
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
background: rgba(0,0,0,0.05);
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 5px;
|
||||||
|
&[alt="close"] {
|
||||||
|
width: 14px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
&:before { content: "["; }
|
||||||
|
&:after { content: "]"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scroll-y {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { Card, NgIf, Icon, EventEmitter, img_placeholder, Input } from "../../co
|
||||||
import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from "../../helpers/";
|
import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from "../../helpers/";
|
||||||
import { Files } from "../../model/";
|
import { Files } from "../../model/";
|
||||||
import { ShareComponent } from "./share";
|
import { ShareComponent } from "./share";
|
||||||
|
import { TagComponent } from "./tag";
|
||||||
import { t } from "../../locales/";
|
import { t } from "../../locales/";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -227,6 +228,13 @@ class ExistingThingComponent extends React.Component {
|
||||||
this.setState({ delete_request: false });
|
this.setState({ delete_request: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTagRequest() {
|
||||||
|
alert.now(
|
||||||
|
<TagComponent path={this.props.file.path} type={this.props.file.type} />,
|
||||||
|
() => {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onShareRequest(filename) {
|
onShareRequest(filename) {
|
||||||
alert.now(
|
alert.now(
|
||||||
<ShareComponent path={this.props.file.path} type={this.props.file.type} />,
|
<ShareComponent path={this.props.file.path} type={this.props.file.type} />,
|
||||||
|
|
@ -321,6 +329,7 @@ class ExistingThingComponent extends React.Component {
|
||||||
onClickRename={this.onRenameRequest.bind(this)}
|
onClickRename={this.onRenameRequest.bind(this)}
|
||||||
onClickDelete={this.onDeleteRequest.bind(this)}
|
onClickDelete={this.onDeleteRequest.bind(this)}
|
||||||
onClickShare={this.onShareRequest.bind(this)}
|
onClickShare={this.onShareRequest.bind(this)}
|
||||||
|
onClickTag={this.onTagRequest.bind(this)}
|
||||||
is_renaming={this.state.is_renaming}
|
is_renaming={this.state.is_renaming}
|
||||||
can_rename={this.props.metadata.can_rename !== false}
|
can_rename={this.props.metadata.can_rename !== false}
|
||||||
can_delete={this.props.metadata.can_delete !== false}
|
can_delete={this.props.metadata.can_delete !== false}
|
||||||
|
|
@ -371,6 +380,7 @@ class Filename extends React.Component {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onRename(this.state.filename);
|
this.props.onRename(this.state.filename);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel() {
|
onCancel() {
|
||||||
|
|
@ -380,6 +390,7 @@ class Filename extends React.Component {
|
||||||
|
|
||||||
preventSelect(e) {
|
preventSelect(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -412,7 +423,7 @@ class Filename extends React.Component {
|
||||||
<NgIf cond={this.props.is_renaming === true} type="inline">
|
<NgIf cond={this.props.is_renaming === true} type="inline">
|
||||||
<form
|
<form
|
||||||
onClick={this.preventSelect}
|
onClick={this.preventSelect}
|
||||||
onSubmit={this.onRename.bind(this)}>
|
onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); this.onRename(e) }}>
|
||||||
<input
|
<input
|
||||||
value={this.state.filename}
|
value={this.state.filename}
|
||||||
onChange={(e) => this.setState({ filename: e.target.value })}
|
onChange={(e) => this.setState({ filename: e.target.value })}
|
||||||
|
|
@ -443,6 +454,11 @@ const ActionButton = (props) => {
|
||||||
props.onClickShare();
|
props.onClickShare();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTag = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
props.onClickTag();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="component_action">
|
<div className="component_action">
|
||||||
<NgIf
|
<NgIf
|
||||||
|
|
@ -453,14 +469,25 @@ const ActionButton = (props) => {
|
||||||
onClick={onRename}
|
onClick={onRename}
|
||||||
className="component_updater--icon" />
|
className="component_updater--icon" />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf
|
{
|
||||||
type="inline"
|
/canary/.test(location.search) ? (
|
||||||
cond={props.can_delete !== false}>
|
<span type="inline">
|
||||||
<Icon
|
<Icon
|
||||||
name="delete"
|
name="tag"
|
||||||
onClick={onDelete}
|
onClick={onTag}
|
||||||
className="component_updater--icon" />
|
className="component_updater--icon" />
|
||||||
</NgIf>
|
</span>
|
||||||
|
) : (
|
||||||
|
<NgIf
|
||||||
|
type="inline"
|
||||||
|
cond={props.can_delete !== false}>
|
||||||
|
<Icon
|
||||||
|
name="delete"
|
||||||
|
onClick={onDelete}
|
||||||
|
className="component_updater--icon" />
|
||||||
|
</NgIf>
|
||||||
|
)
|
||||||
|
}
|
||||||
<NgIf
|
<NgIf
|
||||||
type="inline"
|
type="inline"
|
||||||
cond={props.can_share !== false}>
|
cond={props.can_share !== false}>
|
||||||
|
|
|
||||||
|
|
@ -39,21 +39,11 @@ export function TagsPageComponent({ match }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClickRemoveFile = (file) => {
|
const onClickRemoveFile = (file) => {
|
||||||
prompt.now(
|
Tags.removeTagFromFile(
|
||||||
t("Confirm by typing") + ": remove",
|
file.tag,
|
||||||
(answer) => {
|
file.path,
|
||||||
if (answer !== "remove") {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
Tags.removeTagFromFile(
|
|
||||||
path.split("/").filter((r) => !!r).slice(-1)[0],
|
|
||||||
file,
|
|
||||||
);
|
|
||||||
setRefresh(refresh + 1);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
() => {},
|
|
||||||
);
|
);
|
||||||
|
setRefresh(refresh + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClickMoreDropdown = (what) => {
|
const onClickMoreDropdown = (what) => {
|
||||||
|
|
@ -74,15 +64,12 @@ export function TagsPageComponent({ match }) {
|
||||||
notify.send(t("Not Valid"), "error");
|
notify.send(t("Not Valid"), "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(JSON.stringify(Object.keys(jsonObject)) !== JSON.stringify(["tags", "weight", "share", "backend"])) {
|
|
||||||
notify.send(t("Not Valid"), "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Tags.import(jsonObject).then(() => {
|
Tags.import(jsonObject).then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setRefresh(refresh + 1);
|
setRefresh(refresh + 1);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
notify.send(t(err && err.message), "error");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -111,7 +98,11 @@ export function TagsPageComponent({ match }) {
|
||||||
<div className="component_container">
|
<div className="component_container">
|
||||||
<h1>
|
<h1>
|
||||||
{
|
{
|
||||||
path.split("/").filter((r) => r).map((tag, idx) => (
|
path === "/" ? (
|
||||||
|
<Link to="/">
|
||||||
|
<Icon name="arrow_left" />home
|
||||||
|
</Link>
|
||||||
|
) : path.split("/").filter((r) => r).map((tag, idx) => (
|
||||||
<React.Fragment key={idx}>#{tag} </React.Fragment>
|
<React.Fragment key={idx}>#{tag} </React.Fragment>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -173,15 +164,16 @@ export function TagsPageComponent({ match }) {
|
||||||
{
|
{
|
||||||
files && files.map((file, idx) => (
|
files && files.map((file, idx) => (
|
||||||
<div className="component_thing view-list" key={idx}>
|
<div className="component_thing view-list" key={idx}>
|
||||||
<Link to={(isAFolder(file) ? URL_FILES : URL_VIEWER) + file}>
|
<Link to={(isAFolder(file.path) ? URL_FILES : URL_VIEWER) + file.path}>
|
||||||
<Card>
|
<Card>
|
||||||
<span className="component_action" style={{float: "right"}} onClick={(e) => { e.preventDefault(); onClickRemoveFile(file)}}>
|
<span className="component_action" style={{float: "right"}} onClick={(e) => { e.preventDefault(); onClickRemoveFile(file)}}>
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</span>
|
</span>
|
||||||
<span><Icon name={filetype(file)} /></span>
|
<span><Icon name={filetype(file.path)} /></span>
|
||||||
<span className="component_filename">
|
<span className="component_filename">
|
||||||
<span className="file-details">
|
<span className="file-details">
|
||||||
{basename(file)} <br/><i>{file}</i>
|
{basename(file.path)}<br/>
|
||||||
|
{path === "/" && (<i>#{file.tag} </i>)}<i>{file.path}</i>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,15 @@
|
||||||
h1 {
|
h1 {
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: -7px 0 0 0;
|
margin: 0 0 0 0;
|
||||||
float: left;
|
float: left;
|
||||||
line-height: 30px;
|
font-size: 1.7rem;
|
||||||
|
line-height: 1.7rem;
|
||||||
|
a .component_icon {
|
||||||
|
float: left;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > .component_container {
|
& > .component_container {
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,10 @@ func CanManageShare(fn func(*App, http.ResponseWriter, *http.Request)) func(ctx
|
||||||
fn(ctx, res, req)
|
fn(ctx, res, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Log.Debug("middleware::session::share 'permission denied - s.CanShare[%+v] s.Backend[%s]'", s.CanShare, s.Backend)
|
||||||
|
} else {
|
||||||
|
Log.Debug("middleware::session::share 'permission denied - s.CanShare[%+v] s.Backend[%s] GenerateID[%s]'", s.CanShare, s.Backend, id)
|
||||||
}
|
}
|
||||||
Log.Debug("middleware::session::share 'permission denied - s.CanShare[%+v] s.Backend[%s] GenerateID[%s]'", s.CanShare, s.Backend, id)
|
|
||||||
SendErrorResult(res, ErrPermissionDenied)
|
SendErrorResult(res, ErrPermissionDenied)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue