diff --git a/.gitignore b/.gitignore index 9e7126e..89fee28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /_config.py /data/ /node_modules/ -/.mypy_cache/ +.mypy_cache/ *.pyc \ No newline at end of file diff --git a/app.py b/app.py index 0820f4d..5688892 100644 --- a/app.py +++ b/app.py @@ -91,7 +91,11 @@ def noindex(view_func): @app.route("/") def index(): - return render_template("index.html", items=walk_items()) + limit = 50 + page = int(request.args.get("page", 1)) + next_count = (limit * page) + items = walk_items() + return render_template("index.html", items=items[(limit * (page - 1)):next_count], next_page=(page + 1 if len(items) > next_count else None)) @app.route("/manifest.json") @noindex @@ -102,7 +106,12 @@ def serve_manifest(): @app.route("/static/module//") @noindex -def serve_module(module:str, filename:str): +def serve_module_main(module:str, filename:str): + return send_from_directory(os.path.join("node_modules", module), filename) + +@app.route("/static/module/dist//") +@noindex +def serve_module_dist(module:str, filename:str): return send_from_directory(os.path.join("node_modules", module, "dist"), filename) @app.route("/media/") @@ -127,15 +136,17 @@ def view_user(username:str): def search(): query = request.args.get("query", "").lower() found = False - results = {} + results = [] # {} - for folder, items in walk_items().items(): - results[folder] = [] + for item in walk_items(): + # for folder, items in walk_items().items(): + # results[folder] = [] - for item in items: - if any([query in text.lower() for text in item.values()]): - results[folder].append(item) - found = True + #for item in items: + if any([query in text.lower() for text in item.values()]): + # results[folder].append(item) + results.append(item) + found = True return render_template("search.html", items=(results if found else None), query=query) @@ -282,7 +293,7 @@ def walk_items(): data = load_item(iid) results[rel_path].append(data) - return results + return [value for values in results.values() for value in values] # results def walk_collections(username:str): results: dict[str, list[str]] = {"": []} @@ -330,9 +341,10 @@ def load_item(iid:str): for file in files: if file.lower().endswith(ITEMS_EXT): data = data | read_metadata(read_textual(file)) - elif file.lower().endswith(tuple([f".{ext}" for ext in EXTENSIONS["image"]])): data["image"] = file.replace(os.sep, "/").removeprefix(f"{ITEMS_ROOT}/") + elif file.lower().endswith(tuple([f".{ext}" for ext in EXTENSIONS["video"]])): + data["video"] = file.replace(os.sep, "/").removeprefix(f"{ITEMS_ROOT}/") return data @@ -343,6 +355,7 @@ def store_item(iid:str, data:dict, files:dict|None=None): filepath = os.path.join(ITEMS_ROOT, *filename) mkdirs(os.path.join(ITEMS_ROOT, filename[0])) image = False + extra = {key: data[key] for key in ["provenance"] if key in data} data = {key: data[key] for key in ["link", "title", "description", "image", "text"] if key in data} if files and len(files): @@ -351,7 +364,7 @@ def store_item(iid:str, data:dict, files:dict|None=None): file.seek(0, os.SEEK_SET) mime = file.content_type.split("/") ext = mime[1] - if mime[0] == "image" and ext in EXTENSIONS["image"]: + if mime[0] == "image" and ext in EXTENSIONS["image"] or mime[0] == "video" and ext in EXTENSIONS["video"]: file.save(f"{filepath}.{ext}") image = True if not image and "image" in data and data["image"]: @@ -373,8 +386,8 @@ def store_item(iid:str, data:dict, files:dict|None=None): return False if existing: - if "creator" in existing: - data["creator"] = existing["creator"] + if (creator := safe_str_get(existing, "creator")): + data["creator"] = creator else: data["creator"] = current_user.username items = current_user.data["items"] if "items" in current_user.data else [] @@ -382,6 +395,8 @@ def store_item(iid:str, data:dict, files:dict|None=None): current_user.data["items"] = items write_textual(current_user.filepath, write_metadata(current_user.data)) + if (provenance := safe_str_get(extra, "provenance")): + data["systags"] = provenance write_textual(filepath + ITEMS_EXT, write_metadata(data)) return True @@ -404,12 +419,15 @@ def read_metadata(text:str) -> dict: def write_metadata(data:dict) -> str: output = StringIO() config = ConfigParser(interpolation=None) - for key in ("image", "datetime"): + for key in ("image", "video", "datetime"): if key in data: del data[key] - for key in data: - if type(data[key]) == list: - data[key] = list_to_wsv(data[key]) + for key in list(data.keys()): + if (value := data[key]): + if type(value) == list: + data[key] = list_to_wsv(value) + else: + del data[key] config["DEFAULT"] = data config.write(output) return "\n".join(output.getvalue().splitlines()[1:]) # remove section header @@ -457,6 +475,9 @@ def list_to_wsv(data:list, sep="\n") -> str: def wsv_to_list(data:str) -> list: return data.strip().replace(" ", "\n").replace("\t", "\n").splitlines() +def safe_str_get(dikt:dict[str, str|None], key:str) -> str: + return dikt.get(key) or "" + def mkdirs(*paths:str): for path in paths: Path(path).mkdir(parents=True, exist_ok=True) diff --git a/package-lock.json b/package-lock.json index 4552f38..9e212cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,8 @@ "packages": { "": { "dependencies": { - "uikit": "^3.23.11" + "uikit": "^3.23.11", + "unpoly": "^3.11.0" } }, "node_modules/uikit": { @@ -13,6 +14,12 @@ "resolved": "https://registry.npmjs.org/uikit/-/uikit-3.23.11.tgz", "integrity": "sha512-srUFBf5DfUxVpodcygibMQt1vgQjR9wlhIQo4GeWVpugk5+mKLPASJITDoY8wcwXQIHm7koELiPJ+FgNbzLv0A==", "license": "MIT" + }, + "node_modules/unpoly": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/unpoly/-/unpoly-3.11.0.tgz", + "integrity": "sha512-9ozLaNvg17XAUd9c+gMCfDqZePDeb1fGpqQHAuQDMRF/Vf8X94rdrgo6A5oqt4PlxcywoJ8QGH4yS6J0vFZxhg==", + "license": "MIT" } } } diff --git a/package.json b/package.json index f32a865..856de2f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { - "uikit": "^3.23.11" + "uikit": "^3.23.11", + "unpoly": "^3.11.0" } } diff --git a/static/add.js b/static/add.js index 2d5f5d9..910b76c 100644 --- a/static/add.js +++ b/static/add.js @@ -1,57 +1,69 @@ -var link = document.querySelector('form input[name="link"]'); -var check = document.querySelector('form input[type="checkbox"]'); -var image = document.querySelector('form img.image'); -var upload = document.querySelector('form input[name="file"]'); - -upload.addEventListener('change', function(ev) { - const file = ev.target.files[0]; - if (!file) return; - const reader = new FileReader(); - reader.onload = function(e) { - image.src = e.target.result; - image.parentElement.hidden = false; - }; - reader.readAsDataURL(file); -}); - -document.addEventListener('paste', function(ev) { - const items = (ev.clipboardData || ev.originalEvent.clipboardData).items; - for (let item of items) { - if (item.type.indexOf('image') !== -1) { - const file = item.getAsFile(); - const reader = new FileReader(); - reader.onload = function(e) { - image.src = e.target.result; - image.parentElement.hidden = false; - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(file); - upload.files = dataTransfer.files; - }; - reader.readAsDataURL(file); - break; - } +if ('up' in window) { + up.compiler('form.add', addHandler); +} else { + var form = document.querySelector('form.add'); + if (form) { + addHandler(form); } -}); +} -['change', 'input', 'paste'].forEach(handler => { - link.addEventListener(handler, () => { - var url = link.value.trim(); - if (check.checked && url) { - fetch('../api/preview?url=' + encodeURIComponent(url)) - .then(res => res.json()) - .then(data => { - for (var key in data) { - var field = document.querySelector(`form [name="${key}"]`); - if (field) { - field.value = data[key]; - } - var el = document.querySelector(`form [class="${key}"]`); - if (el) { - el.src = data[key]; - el.parentElement.hidden = false; - } - } - }) +function addHandler(form) { + var link = form.querySelector('input[name="link"]'); + var checkLink = form.querySelector('input.from-link'); + // var checkProxatore = form.querySelector('input.with-proxatore'); + var image = form.querySelector('img.image'); + var upload = form.querySelector('input[name="file"]'); + + upload.addEventListener('change', function(ev) { + const file = ev.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = function(e) { + image.src = e.target.result; + image.parentElement.hidden = false; + }; + reader.readAsDataURL(file); + }); + + document.addEventListener('paste', function(ev) { + const items = (ev.clipboardData || ev.originalEvent.clipboardData).items; + for (let item of items) { + if (item.type.indexOf('image') !== -1) { + const file = item.getAsFile(); + const reader = new FileReader(); + reader.onload = function(e) { + image.src = e.target.result; + image.parentElement.hidden = false; + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + upload.files = dataTransfer.files; + }; + reader.readAsDataURL(file); + break; + } } - }) -}); \ No newline at end of file + }); + + ['change', 'input', 'paste'].forEach(handler => { + link.addEventListener(handler, () => { + var url = link.value.trim(); + if (checkLink.checked && url) { + fetch('../api/preview?url=' + encodeURIComponent(url)) + .then(res => res.json()) + .then(data => { + for (var key in data) { + var field = form.querySelector(`[name="${key}"]`); + if (field) { + field.value = data[key]; + } + var el = form.querySelector(`[class="${key}"]`); + if (el) { + el.src = data[key]; + el.parentElement.hidden = false; + } + } + }) + } + }) + }); +} \ No newline at end of file diff --git a/templates/add.html b/templates/add.html index c6b760f..023bb14 100644 --- a/templates/add.html +++ b/templates/add.html @@ -11,23 +11,34 @@
b
--> -
+ -
+
+ +
- +
@@ -50,5 +61,4 @@
- {% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index cd7d4c5..5decf9d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,9 +6,16 @@ {% if title %}{{ title }} | {% endif %}{{ config.APP_ICON }} {{ config.APP_NAME }} - - - + + + + + + {% if canonical %} @@ -43,8 +50,8 @@ -
-
+
+
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} diff --git a/templates/item-content.html b/templates/item-content.html index d93d0e8..f37d27c 100644 --- a/templates/item-content.html +++ b/templates/item-content.html @@ -4,4 +4,6 @@
{% elif item.image %} {{ item.description }} +{% elif item.video %} + {% endif %} \ No newline at end of file diff --git a/templates/item.html b/templates/item.html index d2e7a01..281c65c 100644 --- a/templates/item.html +++ b/templates/item.html @@ -11,12 +11,16 @@ + {% elif item.video %} + {% endif %} {% endblock %} {% block content %}
- {% include "item-content.html" %} + {% with full=true %} + {% include "item-content.html" %} + {% endwith %}