bookmark collections
This commit is contained in:
@@ -352,19 +352,35 @@ class BookmarkCollections(Model):
|
|||||||
return not (self.has_posts() or self.has_threads())
|
return not (self.has_posts() or self.has_threads())
|
||||||
|
|
||||||
def get_threads(self):
|
def get_threads(self):
|
||||||
q = 'SELECT thread_id FROM bookmarked_threads WHERE collection_id = ?'
|
q = 'SELECT id FROM bookmarked_threads WHERE collection_id = ?'
|
||||||
res = db.query(q, self.id)
|
res = db.query(q, self.id)
|
||||||
return [Threads.find({'id': bt['thread_id']}) for bt in res]
|
return [BookmarkedThreads.find({'id': bt['id']}) for bt in res]
|
||||||
|
|
||||||
def get_posts(self):
|
def get_posts(self):
|
||||||
q = 'SELECT post_id FROM bookmarked_posts WHERE collection_id = ?'
|
q = 'SELECT id FROM bookmarked_posts WHERE collection_id = ?'
|
||||||
res = db.query(q, self.id)
|
res = db.query(q, self.id)
|
||||||
return [Posts.find({'id': bt['post_id']}) for bt in res]
|
return [BookmarkedPosts.find({'id': bt['id']}) for bt in res]
|
||||||
|
|
||||||
|
def get_threads_count(self):
|
||||||
|
q = 'SELECT COUNT(*) as tc FROM bookmarked_threads WHERE collection_id = ?'
|
||||||
|
res = db.fetch_one(q, self.id)
|
||||||
|
return int(res['tc'])
|
||||||
|
|
||||||
|
def get_posts_count(self):
|
||||||
|
q = 'SELECT COUNT(*) as pc FROM bookmarked_posts WHERE collection_id = ?'
|
||||||
|
res = db.fetch_one(q, self.id)
|
||||||
|
return int(res['pc'])
|
||||||
|
|
||||||
|
|
||||||
class BookmarkedPosts(Model):
|
class BookmarkedPosts(Model):
|
||||||
table = 'bookmarked_posts'
|
table = 'bookmarked_posts'
|
||||||
|
|
||||||
|
def get_post(self):
|
||||||
|
return Posts.find({'id': self.post_id})
|
||||||
|
|
||||||
|
|
||||||
class BookmarkedThreads(Model):
|
class BookmarkedThreads(Model):
|
||||||
table = 'bookmarked_threads'
|
table = 'bookmarked_threads'
|
||||||
|
|
||||||
|
def get_thread(self):
|
||||||
|
return Threads.find({'id': self.thread_id})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from flask import Blueprint, request, url_for
|
|||||||
from ..lib.babycode import babycode_to_html
|
from ..lib.babycode import babycode_to_html
|
||||||
from ..constants import REACTION_EMOJI
|
from ..constants import REACTION_EMOJI
|
||||||
from .users import is_logged_in, get_active_user
|
from .users import is_logged_in, get_active_user
|
||||||
from ..models import APIRateLimits, Threads, Reactions
|
from ..models import APIRateLimits, Threads, Reactions, Users, BookmarkCollections
|
||||||
from ..db import db
|
from ..db import db
|
||||||
|
|
||||||
bp = Blueprint("api", __name__, url_prefix="/api/")
|
bp = Blueprint("api", __name__, url_prefix="/api/")
|
||||||
@@ -96,3 +96,46 @@ def remove_reaction(post_id):
|
|||||||
reaction.delete()
|
reaction.delete()
|
||||||
|
|
||||||
return {'status': 'removed'}
|
return {'status': 'removed'}
|
||||||
|
|
||||||
|
@bp.post('/manage-bookmark-collections/<user_id>')
|
||||||
|
def manage_bookmark_collections(user_id):
|
||||||
|
if not is_logged_in():
|
||||||
|
return {'error': 'not authorized', 'error_code': 401}, 401
|
||||||
|
|
||||||
|
target_user = Users.find({'id': user_id})
|
||||||
|
if target_user.id != get_active_user().id:
|
||||||
|
return {'error': 'forbidden', 'error_code': 403}, 403
|
||||||
|
|
||||||
|
if target_user.is_guest():
|
||||||
|
return {'error': 'forbidden', 'error_code': 403}, 403
|
||||||
|
|
||||||
|
collections_data = request.json
|
||||||
|
for idx, coll_data in enumerate(collections_data.get('collections')):
|
||||||
|
if coll_data['is_new']:
|
||||||
|
collection = BookmarkCollections.create({
|
||||||
|
'name': coll_data['name'],
|
||||||
|
'user_id': target_user.id,
|
||||||
|
'sort_order': idx,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
collection = BookmarkCollections.find({'id': coll_data['id']})
|
||||||
|
if not collection:
|
||||||
|
continue
|
||||||
|
|
||||||
|
update = {'name': coll_data['name']}
|
||||||
|
if not collection.is_default:
|
||||||
|
update['sort_order'] = idx
|
||||||
|
collection.update(update)
|
||||||
|
|
||||||
|
for removed_id in collections_data.get('removed_collections'):
|
||||||
|
collection = BookmarkCollections.find({'id': removed_id})
|
||||||
|
if not collection:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if collection.is_default:
|
||||||
|
continue
|
||||||
|
|
||||||
|
collection.delete()
|
||||||
|
|
||||||
|
|
||||||
|
return {'status': 'ok'}, 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from flask import (
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from ..db import db
|
from ..db import db
|
||||||
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
|
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
|
||||||
from ..models import Users, Sessions, Subscriptions, Avatars, PasswordResetLinks, InviteKeys, BookmarkCollections
|
from ..models import Users, Sessions, Subscriptions, Avatars, PasswordResetLinks, InviteKeys, BookmarkCollections, BookmarkedThreads
|
||||||
from ..constants import InfoboxKind, PermissionLevel
|
from ..constants import InfoboxKind, PermissionLevel
|
||||||
from ..auth import digest, verify
|
from ..auth import digest, verify
|
||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
@@ -700,4 +700,16 @@ def bookmarks(username):
|
|||||||
return redirect(url_for('.bookmarks', username=get_active_user().username))
|
return redirect(url_for('.bookmarks', username=get_active_user().username))
|
||||||
|
|
||||||
collections = target_user.get_bookmark_collections()
|
collections = target_user.get_bookmark_collections()
|
||||||
|
|
||||||
return render_template('users/bookmarks.html', collections=collections)
|
return render_template('users/bookmarks.html', collections=collections)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/<username>/bookmarks/collections')
|
||||||
|
@login_required
|
||||||
|
def bookmark_collections(username):
|
||||||
|
target_user = Users.find({'username': username})
|
||||||
|
if not target_user or target_user.username != get_active_user().username:
|
||||||
|
return redirect(url_for('.bookmark_collections', username=get_active_user().username))
|
||||||
|
|
||||||
|
collections = target_user.get_bookmark_collections()
|
||||||
|
return render_template('users/bookmark_collections.html', collections=collections)
|
||||||
|
|||||||
@@ -100,7 +100,12 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None, no_reply = false, Reactions = none, show_thread_title = false, show_bookmark = false) %}
|
{% macro full_post(
|
||||||
|
post, render_sig = True, is_latest = False,
|
||||||
|
editing = False, active_user = None, no_reply = false,
|
||||||
|
Reactions = none, show_thread_title = false,
|
||||||
|
show_bookmark = false, memo = None, bookmark_message = "Bookmark…"
|
||||||
|
) %}
|
||||||
{% set postclass = "post" %}
|
{% set postclass = "post" %}
|
||||||
{% if editing %}
|
{% if editing %}
|
||||||
{% set postclass = postclass + " editing" %}
|
{% set postclass = postclass + " editing" %}
|
||||||
@@ -122,6 +127,9 @@
|
|||||||
<div class="post-content-container" {{ "id=latest-post" if is_latest else "" }}>
|
<div class="post-content-container" {{ "id=latest-post" if is_latest else "" }}>
|
||||||
<div class="post-info">
|
<div class="post-info">
|
||||||
<span>
|
<span>
|
||||||
|
{% if memo -%}
|
||||||
|
Memo: <i>{{ memo }}</i> •
|
||||||
|
{%- endif %}
|
||||||
{% if show_thread_title %}
|
{% if show_thread_title %}
|
||||||
<a href="{{ url_for('threads.thread', slug=post.thread_slug) }}">Thread: {{ post.thread_title }}</a>
|
<a href="{{ url_for('threads.thread', slug=post.thread_slug) }}">Thread: {{ post.thread_title }}</a>
|
||||||
•
|
•
|
||||||
@@ -174,7 +182,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if show_bookmark %}
|
{% if show_bookmark %}
|
||||||
<button type="button" class="contain-svg inline icon">{{ icn_bookmark(20) }}Bookmark</button>
|
<button type="button" class="contain-svg inline icon">{{ icn_bookmark(20) }}{{ bookmark_message | safe }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_bookmark %}
|
{% if can_bookmark %}
|
||||||
<button type="button" class="contain-svg inline icon">{{ icn_bookmark(20) }}Bookmark</button>
|
<button type="button" class="contain-svg inline icon">{{ icn_bookmark(20) }}Bookmark…</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_lock %}
|
{% if can_lock %}
|
||||||
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
|
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{% if active_user and not active_user.is_guest() -%}
|
{% if active_user and not active_user.is_guest() -%}
|
||||||
<button class="thread-info-bookmark-button contain-svg icon" type="button">{{ icn_bookmark(20) }}Bookmark</button>
|
<button class="thread-info-bookmark-button contain-svg icon" type="button">{{ icn_bookmark(20) }}Bookmark…</button>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
35
app/templates/users/bookmark_collections.html
Normal file
35
app/templates/users/bookmark_collections.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}managing bookmark collections{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="darkbg settings-container">
|
||||||
|
<h1>Manage bookmark collections</h1>
|
||||||
|
<p>Drag collections to reoder them. You cannot move or remove the default collection, but you can rename it.</p>
|
||||||
|
<div>
|
||||||
|
<button type="button" id="add-collection-button">Add new collection</button>
|
||||||
|
<div id="collections-container">
|
||||||
|
{% for collection in collections | sort(attribute='sort_order') %}
|
||||||
|
<div class="draggable-collection {{ "default" if collection.is_default else ""}}"
|
||||||
|
{% if not collection.is_default %}
|
||||||
|
draggable="true"
|
||||||
|
ondragover="dragOver(event)"
|
||||||
|
ondragstart="dragStart(event)"
|
||||||
|
ondragend="dragEnd()"
|
||||||
|
{% else %}
|
||||||
|
id="default-collection"
|
||||||
|
{% endif %}
|
||||||
|
data-collection-id="{{ collection.id }}">
|
||||||
|
<input type="text" class="collection-name" value="{{ collection.name }}" placeholder="Collection name" required autocomplete="off" maxlength="60"><br>
|
||||||
|
<div>{{ collection.get_threads_count() }} {{ "thread" | pluralize(num=collection.get_threads_count()) }}, {{ collection.get_posts_count() }} {{ "post" | pluralize(num=collection.get_posts_count()) }}</div>
|
||||||
|
{% if collection.is_default %}
|
||||||
|
<i>Default collection</i>
|
||||||
|
{% else %}
|
||||||
|
<button type="button" class="delete-button critical">Delete</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<button type="button" id="save-button" data-submit-href="{{ url_for('api.manage_bookmark_collections', user_id=active_user.id) }}">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{{ "/static/js/manage-bookmark-collections.js" | cachebust }}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
{% from "common/macros.html" import accordion, full_post %}
|
{% from "common/macros.html" import accordion, full_post %}
|
||||||
|
{% from "common/icons.html" import icn_bookmark %}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}bookmarks{% endblock %}
|
{% block title %}bookmarks{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="darkbg inbox-container">
|
<div class="darkbg inbox-container">
|
||||||
{% for collection in collections %}
|
{% for collection in collections | sort(attribute='sort_order') %}
|
||||||
{% call(section) accordion(disabled=collection.is_empty()) %}
|
{% call(section) accordion(disabled=collection.is_empty()) %}
|
||||||
{% if section == 'header' %}
|
{% if section == 'header' %}
|
||||||
<h1 class="thread-title">{{ collection.name }}</h1>{{" (no bookmarks)" if collection.is_empty() else ""}}
|
<h1 class="thread-title">{{ collection.name }}</h1>{{" (no bookmarks)" if collection.is_empty() else ""}}
|
||||||
@@ -12,19 +13,34 @@
|
|||||||
{% if inner_section == 'header' %}
|
{% if inner_section == 'header' %}
|
||||||
Threads{{" (no bookmarks)" if not collection.has_threads() else ""}}
|
Threads{{" (no bookmarks)" if not collection.has_threads() else ""}}
|
||||||
{% else %}
|
{% else %}
|
||||||
<ul>
|
<table class="colorful-table">
|
||||||
{% for thread in collection.get_threads()|sort(attribute='created_at', reverse=true) %}
|
<thead>
|
||||||
<li><a href="{{ url_for('threads.thread', slug=thread.slug) }}">{{ thread.title }}</a></li>
|
<th>Title</th>
|
||||||
{% endfor %}
|
<th>Memo</th>
|
||||||
</ul>
|
<th class="small">Manage</th>
|
||||||
|
</thead>
|
||||||
|
{% for thread in collection.get_threads() %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('threads.thread', slug=thread.get_thread().slug) }}">{{ thread.get_thread().title }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<i>{{ thread.note }}</i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="contain-svg inline icon">{{ icn_bookmark(20) }}Manage…</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% call(inner_section) accordion(disabled=not collection.has_posts()) %}
|
{% call(inner_section) accordion(disabled=not collection.has_posts()) %}
|
||||||
{% if inner_section == 'header' %}
|
{% if inner_section == 'header' %}
|
||||||
Posts{{" (no bookmarks)" if not collection.has_posts() else ""}}
|
Posts{{" (no bookmarks)" if not collection.has_posts() else ""}}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for post in collection.get_posts()|sort(attribute='created_at', reverse=true) %}
|
{% for post in collection.get_posts() %}
|
||||||
{{ full_post(post.get_full_post_view(), no_reply=false, render_sig=false, show_thread_title=true) }}
|
{{ full_post(post.get_post().get_full_post_view(), no_reply=false, render_sig=false, show_thread_title=true, show_bookmark=true, memo=post.note, bookmark_message="Manage…") }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|||||||
@@ -1060,6 +1060,22 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
background-color: rgb(177, 206, 204.5);
|
background-color: rgb(177, 206, 204.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.draggable-collection {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
background-color: #c1ceb1;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border-top: 5px outset rgb(217.26, 220.38, 213.42);
|
||||||
|
border-bottom: 5px outset rgb(135.1928346457, 145.0974015748, 123.0025984252);
|
||||||
|
}
|
||||||
|
.draggable-collection.dragged {
|
||||||
|
background-color: rgb(177, 206, 204.5);
|
||||||
|
}
|
||||||
|
.draggable-collection.default {
|
||||||
|
background-color: #beb1ce;
|
||||||
|
}
|
||||||
|
|
||||||
.editing {
|
.editing {
|
||||||
background-color: rgb(217.26, 220.38, 213.42);
|
background-color: rgb(217.26, 220.38, 213.42);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1060,6 +1060,22 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
background-color: #3c283c;
|
background-color: #3c283c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.draggable-collection {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
background-color: #9b649b;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border-top: 5px outset #503250;
|
||||||
|
border-bottom: 5px outset rgb(96.95, 81.55, 96.95);
|
||||||
|
}
|
||||||
|
.draggable-collection.dragged {
|
||||||
|
background-color: #3c283c;
|
||||||
|
}
|
||||||
|
.draggable-collection.default {
|
||||||
|
background-color: #8a5584;
|
||||||
|
}
|
||||||
|
|
||||||
.editing {
|
.editing {
|
||||||
background-color: #503250;
|
background-color: #503250;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1060,6 +1060,22 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
background-color: #f27a5a;
|
background-color: #f27a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.draggable-collection {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
background-color: #f27a5a;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-top: 5px outset rgb(219.84, 191.04, 183.36);
|
||||||
|
border-bottom: 5px outset rgb(155.8907865169, 93.2211235955, 76.5092134831);
|
||||||
|
}
|
||||||
|
.draggable-collection.dragged {
|
||||||
|
background-color: #f27a5a;
|
||||||
|
}
|
||||||
|
.draggable-collection.default {
|
||||||
|
background-color: #b54444;
|
||||||
|
}
|
||||||
|
|
||||||
.editing {
|
.editing {
|
||||||
background-color: rgb(219.84, 191.04, 183.36);
|
background-color: rgb(219.84, 191.04, 183.36);
|
||||||
}
|
}
|
||||||
|
|||||||
128
data/static/js/manage-bookmark-collections.js
Normal file
128
data/static/js/manage-bookmark-collections.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
let removedCollections = [];
|
||||||
|
|
||||||
|
document.getElementById("add-collection-button").addEventListener("click", () => {
|
||||||
|
const container = document.getElementById("collections-container");
|
||||||
|
const currentCount = container.querySelectorAll(".draggable-collection").length;
|
||||||
|
|
||||||
|
const newId = `new-${Date.now()}`
|
||||||
|
const collectionHtml = `
|
||||||
|
<div class="draggable-collection"
|
||||||
|
data-collection-id="${newId}"
|
||||||
|
draggable="true"
|
||||||
|
ondragover="dragOver(event)"
|
||||||
|
ondragstart="dragStart(event)"
|
||||||
|
ondragend="dragEnd()">
|
||||||
|
<input type="text" class="collection-name" value="" required placeholder="Enter collection name" autocomplete="off" maxlength="60"><br>
|
||||||
|
<div>0 threads, 0 posts</div>
|
||||||
|
<button type="button" class="delete-button critical">Delete</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
container.insertAdjacentHTML('beforeend', collectionHtml);
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener("click", e => {
|
||||||
|
if (!e.target.classList.contains("delete-button")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const collectionDiv = e.target.closest(".draggable-collection");
|
||||||
|
const collectionId = collectionDiv.dataset.collectionId;
|
||||||
|
|
||||||
|
if (!collectionId.startsWith("new-")) {
|
||||||
|
removedCollections.push(collectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionDiv.remove();
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById("save-button").addEventListener("click", async () => {
|
||||||
|
const collections = [];
|
||||||
|
const collectionDivs = document.querySelectorAll(".draggable-collection");
|
||||||
|
let isValid = true;
|
||||||
|
collectionDivs.forEach((collection, index) => {
|
||||||
|
const collectionId = collection.dataset.collectionId;
|
||||||
|
const nameInput = collection.querySelector(".collection-name");
|
||||||
|
|
||||||
|
if (!nameInput.reportValidity()) {
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collections.push({
|
||||||
|
id: collectionId,
|
||||||
|
name: nameInput.value,
|
||||||
|
is_new: collectionId.startsWith("new-"),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
collections: collections,
|
||||||
|
removed_collections: removedCollections,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const saveHref = document.getElementById('save-button').dataset.submitHref;
|
||||||
|
const response = await fetch(saveHref, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.error("Error saving collections");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving collections: ", error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// drag logic
|
||||||
|
// https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap
|
||||||
|
|
||||||
|
let selected = null;
|
||||||
|
const container = document.getElementById("collections-container");
|
||||||
|
function isBefore(el1, el2) {
|
||||||
|
let cur;
|
||||||
|
if (el2.parentNode === el1.parentNode) {
|
||||||
|
for (cur = el1.previousSibling; cur; cur = cur.previousSibling) {
|
||||||
|
if (cur === el2) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragOver(e) {
|
||||||
|
let target = e.target.closest(".draggable-collection")
|
||||||
|
|
||||||
|
if (!target || target === selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBefore(selected, target)) {
|
||||||
|
container.insertBefore(selected, target)
|
||||||
|
} else {
|
||||||
|
container.insertBefore(selected, target.nextSibling)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnd() {
|
||||||
|
if (!selected) return;
|
||||||
|
|
||||||
|
selected.classList.remove("dragged")
|
||||||
|
selected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragStart(e) {
|
||||||
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
|
e.dataTransfer.setData('text/plain', "")
|
||||||
|
selected = e.target
|
||||||
|
selected.classList.add("dragged")
|
||||||
|
}
|
||||||
@@ -960,6 +960,33 @@ $draggable_topic_border_bottom: $draggable_topic_border $DARK_2 !default;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$draggable_collection_background: $ACCENT_COLOR !default;
|
||||||
|
$draggable_collection_dragged_color: $BUTTON_COLOR !default;
|
||||||
|
$draggable_collection_default_color: $BUTTON_COLOR_2 !default;
|
||||||
|
$draggable_collection_padding: $BIG_PADDING !default;
|
||||||
|
$draggable_collection_margin: $MEDIUM_BIG_PADDING 0 !default;
|
||||||
|
$draggable_collection_border: 5px outset !default;
|
||||||
|
$draggable_collection_border_top: $draggable_collection_border $LIGHT !default;
|
||||||
|
$draggable_collection_border_bottom: $draggable_collection_border $DARK_2 !default;
|
||||||
|
.draggable-collection {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
background-color: $draggable_collection_background;
|
||||||
|
padding: $draggable_collection_padding;
|
||||||
|
margin: $draggable_collection_margin;
|
||||||
|
border-top: $draggable_collection_border_top;
|
||||||
|
border-bottom: $draggable_collection_border_bottom;
|
||||||
|
|
||||||
|
&.dragged {
|
||||||
|
background-color: $draggable_collection_dragged_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.default {
|
||||||
|
background-color: $draggable_collection_default_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$post_editing_header_color: $LIGHT !default;
|
$post_editing_header_color: $LIGHT !default;
|
||||||
.editing {
|
.editing {
|
||||||
background-color: $post_editing_header_color;
|
background-color: $post_editing_header_color;
|
||||||
|
|||||||
Reference in New Issue
Block a user