diff --git a/app/__init__.py b/app/__init__.py index 1c9d6de..f9dbf1b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ -from flask import Flask, session +from flask import Flask, session, request from dotenv import load_dotenv from .models import Avatars, Users, PostHistory, Posts from .auth import digest @@ -131,6 +131,7 @@ def create_app(): from app.routes.mod import bp as mod_bp from app.routes.api import bp as api_bp from app.routes.posts import bp as posts_bp + from app.routes.hyperapi import bp as hyperapi_bp app.register_blueprint(app_bp) app.register_blueprint(topics_bp) app.register_blueprint(threads_bp) @@ -138,6 +139,7 @@ def create_app(): app.register_blueprint(mod_bp) app.register_blueprint(api_bp) app.register_blueprint(posts_bp) + app.register_blueprint(hyperapi_bp) app.config['SESSION_COOKIE_SECURE'] = True @@ -200,6 +202,15 @@ def create_app(): for id_, text in matches ] + @app.errorhandler(404) + def _handle_404(e): + if request.path.startswith('/hyperapi/'): + return '

not found

', e.code + elif request.path.startswith('/api/'): + return {'error': 'not found'}, e.code + else: + return e + # this only happens at build time but # build time is when updates are done anyway # sooo... /shrug diff --git a/app/models.py b/app/models.py index ed40939..500b086 100644 --- a/app/models.py +++ b/app/models.py @@ -371,6 +371,16 @@ class BookmarkCollections(Model): res = db.fetch_one(q, self.id) return int(res['pc']) + def has_thread(self, thread_id): + q = 'SELECT EXISTS(SELECT 1 FROM bookmarked_threads WHERE collection_id = ? AND thread_id = ?) as e' + res = db.fetch_one(q, self.id, int(thread_id))['e'] + return int(res) == 1 + + def has_post(self, post_id): + q = 'SELECT EXISTS(SELECT 1 FROM bookmarked_posts WHERE collection_id = ? AND post_id = ?) as e' + res = db.fetch_one(q, self.id, int(post_id))['e'] + return int(res) == 1 + class BookmarkedPosts(Model): table = 'bookmarked_posts' diff --git a/app/routes/api.py b/app/routes/api.py index 94191a6..a59b769 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -2,7 +2,7 @@ from flask import Blueprint, request, url_for from ..lib.babycode import babycode_to_html from ..constants import REACTION_EMOJI from .users import is_logged_in, get_active_user -from ..models import APIRateLimits, Threads, Reactions, Users, BookmarkCollections +from ..models import APIRateLimits, Threads, Reactions, Users, BookmarkCollections, BookmarkedThreads, BookmarkedPosts from ..db import db bp = Blueprint("api", __name__, url_prefix="/api/") @@ -139,3 +139,76 @@ def manage_bookmark_collections(user_id): return {'status': 'ok'}, 200 + + +@bp.post('/bookmark-post/') +def bookmark_post(post_id): + if not is_logged_in(): + return {'error': 'not authorized', 'error_code': 401}, 401 + + operation = request.json.get('operation') + if operation == 'remove' and request.json.get('collection_id', '') == '': + return {'status': 'not modified'}, 304 + collection_id = int(request.json.get('collection_id')) + post_id = int(post_id) + memo = request.json.get('memo', '') + + if operation == 'move': + bm = BookmarkedPosts.find({'post_id': post_id}) + if not bm: + BookmarkedPosts.create({ + 'post_id': post_id, + 'collection_id': collection_id, + 'note': memo, + }) + else: + bm.update({ + 'collection_id': collection_id, + 'note': memo, + }) + elif operation == 'remove': + bm = BookmarkedPosts.find({'post_id': post_id}) + if bm: + bm.delete() + else: + return {'error': 'bad request'}, 400 + + return {'status': 'ok'}, 200 + + +@bp.post('/bookmark-thread/') +def bookmark_thread(thread_id): + if not is_logged_in(): + return {'error': 'not authorized', 'error_code': 401}, 401 + + operation = request.json.get('operation') + if operation == 'remove' and request.json.get('collection_id', '') == '': + return {'status': 'not modified'}, 304 + collection_id = int(request.json.get('collection_id')) + thread_id = int(thread_id) + memo = request.json.get('memo', '') + + if operation == 'move': + bm = BookmarkedThreads.find({'thread_id': thread_id}) + if not bm: + BookmarkedThreads.create({ + 'thread_id': thread_id, + 'collection_id': collection_id, + 'note': memo, + }) + else: + bm.update({ + 'collection_id': collection_id, + 'note': memo, + }) + elif operation == 'remove': + bm = BookmarkedThreads.find({ + 'thread_id': thread_id, + 'note': memo, + }) + if bm: + bm.delete() + else: + return {'error': 'bad request'}, 400 + + return {'status': 'ok'}, 200 diff --git a/app/routes/hyperapi.py b/app/routes/hyperapi.py new file mode 100644 index 0000000..f622cca --- /dev/null +++ b/app/routes/hyperapi.py @@ -0,0 +1,53 @@ +from flask import Blueprint, render_template, abort, request +from .users import get_active_user, is_logged_in +from ..models import BookmarkCollections, BookmarkedPosts, BookmarkedThreads +from functools import wraps + +bp = Blueprint('hyperapi', __name__, url_prefix='/hyperapi/') + +def login_required(view_func): + @wraps(view_func) + def dec(*args, **kwargs): + if not is_logged_in(): + abort(403) + return view_func(*args, **kwargs) + return dec + +def account_required(view_func): + @wraps(view_func) + def dec(*args, **kwargs): + if get_active_user().is_guest(): + abort(403) + return view_func(*args, **kwargs) + return dec + +@bp.errorhandler(403) +def handle_403(e): + return "

forbidden

", 403 + + +@bp.get('bookmarks-dropdown/') +@login_required +@account_required +def bookmarks_dropdown(bookmark_type): + collections = BookmarkCollections.findall({'user_id': get_active_user().id}) + concept_id = request.args.get('id') + require_reload = bool(int(request.args.get('require_reload', default=0))) + if bookmark_type.lower() == 'thread': + selected = next(filter(lambda bc: bc.has_thread(concept_id), collections), None) + elif bookmark_type.lower() == 'post': + selected = next(filter(lambda bc: bc.has_post(concept_id), collections), None) + else: + abort(400) + return + + if selected: + if bookmark_type.lower() == 'thread': + memo = BookmarkedThreads.find({'collection_id': selected.id, 'thread_id': int(concept_id)}).note + else: + memo = BookmarkedPosts.find({'collection_id': selected.id, 'post_id': int(concept_id)}).note + else: + memo = '' + + + return render_template('components/bookmarks_dropdown.html', collections=collections, id=concept_id, selected=selected, type=bookmark_type, memo=memo, require_reload=require_reload) diff --git a/app/templates/base.html b/app/templates/base.html index d8e1bb3..57b5099 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,20 +10,23 @@ {% endif %} + - {% include 'common/topnav.html' %} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - {{ infobox(message, category) }} - {% endfor %} - {% endif %} - {% endwith %} - {% block content %}{% endblock %} - + + {% include 'common/topnav.html' %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + {{ infobox(message, category) }} + {% endfor %} + {% endif %} + {% endwith %} + {% block content %}{% endblock %} + + diff --git a/app/templates/common/macros.html b/app/templates/common/macros.html index 7b41902..4771958 100644 --- a/app/templates/common/macros.html +++ b/app/templates/common/macros.html @@ -28,6 +28,14 @@ {% endmacro %} +{% macro bookmark_button(type, id, message = "Bookmark…", require_reload=false) %} +{% set bid = type[0] + id | string %} +
+ +
+
+{% endmacro %} + {% macro infobox(message, kind=InfoboxKind.INFO) %}
@@ -104,7 +112,8 @@ 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…" + show_bookmark = false, memo = None, bookmark_message = "Bookmark…", + reload_after_bookmark = false ) %} {% set postclass = "post" %} {% if editing %} @@ -182,7 +191,7 @@ {% endif %} {% if show_bookmark %} - + {{ bookmark_button(type="post", id=post.id, message=bookmark_message, require_reload=reload_after_bookmark)}} {% endif %}
diff --git a/app/templates/components/bookmarks_dropdown.html b/app/templates/components/bookmarks_dropdown.html new file mode 100644 index 0000000..385bb86 --- /dev/null +++ b/app/templates/components/bookmarks_dropdown.html @@ -0,0 +1,23 @@ +{% set bookmark_url = None %} +{% if type == 'post' %} + {% set bookmark_url = url_for('api.bookmark_post', post_id=id) %} +{% else %} + {% set bookmark_url = url_for('api.bookmark_thread', thread_id=id) %} +{% endif %} +
+
+ Bookmark collections + {% if not require_reload %} + View bookmarks + {% endif %} +
+
+ {% for collection in collections %} +
{{collection.name}} ({{ collection.get_posts_count() }}p, {{ collection.get_threads_count() }}t)
+ {% endfor %} +
+ + + + +
diff --git a/app/templates/threads/thread.html b/app/templates/threads/thread.html index f3f96c4..7b154e9 100644 --- a/app/templates/threads/thread.html +++ b/app/templates/threads/thread.html @@ -1,4 +1,4 @@ -{% from 'common/macros.html' import pager, babycode_editor_form, full_post %} +{% from 'common/macros.html' import pager, babycode_editor_form, full_post, bookmark_button %} {% from 'common/icons.html' import icn_bookmark %} {% extends "base.html" %} {% block title %}{{ thread.title }}{% endblock %} @@ -30,7 +30,7 @@ {% endif %} {% if can_bookmark %} - + {{ bookmark_button(type="thread", id=thread.id) }} {% endif %} {% if can_lock %}
diff --git a/app/templates/topics/topic.html b/app/templates/topics/topic.html index 1e55615..e1b791a 100644 --- a/app/templates/topics/topic.html +++ b/app/templates/topics/topic.html @@ -1,4 +1,4 @@ -{% from 'common/macros.html' import pager, timestamp %} +{% from 'common/macros.html' import pager, timestamp, bookmark_button %} {% from 'common/icons.html' import icn_bookmark, icn_lock, icn_sticky %} {% extends "base.html" %} {% block title %}browsing topic {{ topic['name'] }}{% endblock %} @@ -53,7 +53,7 @@ {% if active_user and not active_user.is_guest() -%} - + {{ bookmark_button(type="thread", id=thread.id) }} {%- endif %} diff --git a/app/templates/users/bookmarks.html b/app/templates/users/bookmarks.html index 9dd643e..507b9ff 100644 --- a/app/templates/users/bookmarks.html +++ b/app/templates/users/bookmarks.html @@ -1,9 +1,10 @@ -{% from "common/macros.html" import accordion, full_post %} +{% from "common/macros.html" import accordion, full_post, bookmark_button %} {% from "common/icons.html" import icn_bookmark %} {% extends "base.html" %} {% block title %}bookmarks{% endblock %} {% block content %}
+ Manage collections {% for collection in collections | sort(attribute='sort_order') %} {% call(section) accordion(disabled=collection.is_empty()) %} {% if section == 'header' %} @@ -28,7 +29,7 @@ {{ thread.note }} - + {{ bookmark_button(type='thread', id=thread.thread_id, message='Manage…', require_reload=true) }} {% endfor %} @@ -40,7 +41,7 @@ Posts{{" (no bookmarks)" if not collection.has_posts() else ""}} {% else %} {% for post in collection.get_posts() %} - {{ 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…") }} + {{ 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…", reload_after_bookmark=true) }} {% endfor %} {% endif %} {% endcall %} diff --git a/data/static/css/style.css b/data/static/css/style.css index 876cc81..459716c 100644 --- a/data/static/css/style.css +++ b/data/static/css/style.css @@ -1005,7 +1005,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus border-collapse: collapse; width: 100%; margin: 10px 0; - overflow: hidden; } .colorful-table tr th { @@ -1183,7 +1182,6 @@ ul, ol { box-sizing: border-box; border: 1px solid black; margin: 10px 5px; - overflow: hidden; } .accordion.hidden { @@ -1303,3 +1301,58 @@ footer { .babycode-guide-list { border-bottom: 1px dashed; } + +.bookmark-dropdown-inner { + position: relative; +} + +.bookmarks-dropdown { + background-color: #c1ceb1; + border: 1px solid black; + border-radius: 4px; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.25); + position: absolute; + right: 0; + min-width: 400px; + padding: 10px; + z-index: 100; +} + +.bookmark-dropdown-item { + display: flex; + padding: 10px 0; + margin: 10px 0; + cursor: pointer; + border: 1px solid black; + border-radius: 4px; + color: black; + background-color: rgb(177, 206, 204.5); +} +.bookmark-dropdown-item:hover { + background-color: rgb(192.6, 215.8, 214.6); +} +.bookmark-dropdown-item::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); + width: 24px; + height: 24px; + padding: 0 10px; +} +.bookmark-dropdown-item.selected { + background-color: #beb1ce; +} +.bookmark-dropdown-item.selected:hover { + background-color: rgb(203, 192.6, 215.8); +} +.bookmark-dropdown-item.selected::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E"); +} + +.bookmarks-dropdown-header { + display: flex; + justify-content: space-between; +} + +.bookmark-dropdown-items-container { + max-height: 300px; + overflow: scroll; +} diff --git a/data/static/css/theme-otomotone.css b/data/static/css/theme-otomotone.css index b8e2fc8..11e3571 100644 --- a/data/static/css/theme-otomotone.css +++ b/data/static/css/theme-otomotone.css @@ -1005,7 +1005,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus border-collapse: collapse; width: 100%; margin: 10px 0; - overflow: hidden; } .colorful-table tr th { @@ -1183,7 +1182,6 @@ ul, ol { box-sizing: border-box; border: 1px solid black; margin: 10px 5px; - overflow: hidden; } .accordion.hidden { @@ -1304,6 +1302,61 @@ footer { border-bottom: 1px dashed; } +.bookmark-dropdown-inner { + position: relative; +} + +.bookmarks-dropdown { + background-color: #9b649b; + border: 1px solid black; + border-radius: 4px; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.25); + position: absolute; + right: 0; + min-width: 400px; + padding: 10px; + z-index: 100; +} + +.bookmark-dropdown-item { + display: flex; + padding: 10px 0; + margin: 10px 0; + cursor: pointer; + border: 1px solid black; + border-radius: 4px; + color: #e6e6e6; + background-color: #3c283c; +} +.bookmark-dropdown-item:hover { + background-color: rgb(109.2, 72.8, 109.2); +} +.bookmark-dropdown-item::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); + width: 24px; + height: 24px; + padding: 0 10px; +} +.bookmark-dropdown-item.selected { + background-color: #8a5584; +} +.bookmark-dropdown-item.selected:hover { + background-color: rgb(167.4843049327, 112.9156950673, 161.3067264574); +} +.bookmark-dropdown-item.selected::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E"); +} + +.bookmarks-dropdown-header { + display: flex; + justify-content: space-between; +} + +.bookmark-dropdown-items-container { + max-height: 300px; + overflow: scroll; +} + #topnav { margin-bottom: 10px; border: 10px solid rgb(40, 40, 40); diff --git a/data/static/css/theme-peachy.css b/data/static/css/theme-peachy.css index 89da7f8..93136eb 100644 --- a/data/static/css/theme-peachy.css +++ b/data/static/css/theme-peachy.css @@ -1005,7 +1005,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus border-collapse: collapse; width: 100%; margin: 6px 0; - overflow: hidden; } .colorful-table tr th { @@ -1183,7 +1182,6 @@ ul, ol { box-sizing: border-box; border: 1px solid black; margin: 6px 3px; - overflow: hidden; } .accordion.hidden { @@ -1304,6 +1302,61 @@ footer { border-bottom: 1px dashed; } +.bookmark-dropdown-inner { + position: relative; +} + +.bookmarks-dropdown { + background-color: #f27a5a; + border: 1px solid black; + border-radius: 16px; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.25); + position: absolute; + right: 0; + min-width: 400px; + padding: 6px; + z-index: 100; +} + +.bookmark-dropdown-item { + display: flex; + padding: 6px 0; + margin: 6px 0; + cursor: pointer; + border: 1px solid black; + border-radius: 16px; + color: black; + background-color: #f27a5a; +} +.bookmark-dropdown-item:hover { + background-color: rgb(244.6, 148.6, 123); +} +.bookmark-dropdown-item::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); + width: 24px; + height: 24px; + padding: 0 6px; +} +.bookmark-dropdown-item.selected { + background-color: #b54444; +} +.bookmark-dropdown-item.selected:hover { + background-color: rgb(197.978313253, 103.221686747, 103.221686747); +} +.bookmark-dropdown-item.selected::before { + content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E"); +} + +.bookmarks-dropdown-header { + display: flex; + justify-content: space-between; +} + +.bookmark-dropdown-items-container { + max-height: 300px; + overflow: scroll; +} + #topnav { border-top-left-radius: 16px; border-top-right-radius: 16px; diff --git a/data/static/js/bitties/pyrom-bitty.js b/data/static/js/bitties/pyrom-bitty.js new file mode 100644 index 0000000..6514c85 --- /dev/null +++ b/data/static/js/bitties/pyrom-bitty.js @@ -0,0 +1,64 @@ +const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown' + +export default class { + async showBookmarkMenu(ev, el) { + if ((ev.target.dataset.bookmarkId === el.dataset.bookmarkId) && el.childElementCount === 0) { + const bookmarkMenuHref = `${bookmarkMenuHrefTemplate}/${ev.target.dataset.bookmarkType}?id=${ev.target.dataset.conceptId}&require_reload=${el.dataset.requireReload}`; + const res = await this.api.getHTML(bookmarkMenuHref); + if (res.error) { + return; + } + const frag = res.value; + el.appendChild(frag); + const menu = el.childNodes[0]; + const bRect = el.getBoundingClientRect() + if (bRect.left < window.innerWidth - bRect.right) { + menu.style.right = 'unset'; + } + } else if (el.childElementCount > 0) { + el.removeChild(el.childNodes[0]); + } + } + + selectBookmarkCollection(ev, el) { + const clicked = ev.target; + + if (clicked === el) { + if (clicked.classList.contains('selected')) { + clicked.classList.remove('selected'); + } else { + clicked.classList.add('selected'); + } + } else { + el.classList.remove('selected'); + } + } + + async saveBookmarks(ev, el) { + const bookmarkHref = el.dataset.bookmarkEndpoint; + const collection = el.querySelector('.bookmark-dropdown-item.selected'); + let data = {}; + if (collection) { + data['operation'] = 'move'; + data['collection_id'] = collection.dataset.collectionId; + data['memo'] = el.querySelector('.bookmark-memo-input').value; + } else { + data['operation'] = 'remove'; + data['collection_id'] = el.dataset.originallyContainedIn; + } + + const options = { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + }, + } + const requireReload = parseInt(el.dataset.requireReload) !== 0; + el.remove(); + await fetch(bookmarkHref, options); + if (requireReload) { + window.location.reload(); + } + } +} diff --git a/sass/_default.scss b/sass/_default.scss index 4d52b9e..5069734 100644 --- a/sass/_default.scss +++ b/sass/_default.scss @@ -18,6 +18,14 @@ $MAIN_BG: color.scale($ACCENT_COLOR, $lightness: -10%, $saturation: -40%) !defau $BUTTON_COLOR: color.adjust($ACCENT_COLOR, $hue: 90) !default; $BUTTON_COLOR_2: color.adjust($ACCENT_COLOR, $hue: 180) !default; +$BUTTON_COLOR_HOVER: color.scale($BUTTON_COLOR, $lightness: 20%) !default; +$BUTTON_COLOR_ACTIVE: color.scale($BUTTON_COLOR, $lightness: -10%, $saturation: -70%) !default; +$BUTTON_COLOR_DISABLED: color.scale($BUTTON_COLOR, $lightness: 30%, $saturation: -90%) !default; + +$BUTTON_COLOR_2_HOVER: color.scale($BUTTON_COLOR_2, $lightness: 20%) !default; +$BUTTON_COLOR_2_ACTIVE: color.scale($BUTTON_COLOR_2, $lightness: -10%, $saturation: -70%) !default; +$BUTTON_COLOR_2_DISABLED: color.scale($BUTTON_COLOR_2, $lightness: 30%, $saturation: -90%) !default; + $ACCORDION_COLOR: color.adjust($ACCENT_COLOR, $hue: 140, $lightness: -10%, $saturation: -15%) !default; $DEFAULT_FONT_COLOR: black !default; @@ -885,7 +893,7 @@ $colorful_table_margin: $MEDIUM_PADDING $ZERO_PADDING !default; border-collapse: collapse; width: 100%; margin: $colorful_table_margin; - overflow: hidden; + // overflow: hidden; } $colorful_table_th_color: $BUTTON_COLOR_2 !default; @@ -1095,7 +1103,7 @@ $accordion_margin: $MEDIUM_PADDING $SMALL_PADDING !default; box-sizing: border-box; border: $accordion_border; margin: $accordion_margin; - overflow: hidden; + // overflow: hidden; } .accordion.hidden { @@ -1221,3 +1229,79 @@ $babycode_guide_list_border: 1px dashed !default; .babycode-guide-list { border-bottom: $babycode_guide_list_border; } + +.bookmark-dropdown-inner { + position: relative; +} + +$bookmarks_dropdown_background_color: $ACCENT_COLOR !default; +$bookmarks_dropdown_border_radius: $DEFAULT_BORDER_RADIUS !default; +$bookmarks_dropdown_border: $button_border !default; +$bookmarks_dropdown_shadow: 0 0 30px rgba(0, 0, 0, 0.25) !default; +$bookmarks_dropdown_min_width: 400px !default; +$bookmarks_dropdown_padding: $MEDIUM_PADDING !default; +.bookmarks-dropdown { + background-color: $bookmarks_dropdown_background_color; + border: $bookmarks_dropdown_border; + border-radius: $bookmarks_dropdown_border_radius; + box-shadow: $bookmarks_dropdown_shadow; + position: absolute; + right: 0; + min-width: $bookmarks_dropdown_min_width; + padding: $bookmarks_dropdown_padding; + z-index: 100; +} + +$bookmark_dropdown_item_padding: $MEDIUM_PADDING 0 !default; +$bookmark_dropdown_item_margin: $MEDIUM_PADDING 0 !default; +$bookmark_dropdown_item_font_color: $BUTTON_FONT_COLOR !default; +$bookmark_dropdown_item_background: $BUTTON_COLOR !default; +$bookmark_dropdown_item_background_hover: $BUTTON_COLOR_HOVER !default; +$bookmark_dropdown_item_background_selected: $BUTTON_COLOR_2 !default; +$bookmark_dropdown_item_background_selected_hover: $BUTTON_COLOR_2_HOVER !default; +$bookmark_dropdown_item_icon_size: 24px !default; +$bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default; +.bookmark-dropdown-item { + display: flex; + padding: $bookmark_dropdown_item_padding; + margin: $bookmark_dropdown_item_margin; + cursor: pointer; + border: $button_border; + border-radius: $button_border_radius; + color: $bookmark_dropdown_item_font_color; + + background-color: $bookmark_dropdown_item_background; + &:hover { + background-color: $bookmark_dropdown_item_background_hover; + } + + &::before { + // TODO: un-inline this once the bitty bug is fixed + content: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E'); + width: $bookmark_dropdown_item_icon_size; + height: $bookmark_dropdown_item_icon_size; + padding: $bookmark_dropdown_item_icon_padding; + } + + &.selected { + background-color: $bookmark_dropdown_item_background_selected; + &:hover { + background-color: $bookmark_dropdown_item_background_selected_hover; + } + + &::before{ + content: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E'); + } + } +} + +.bookmarks-dropdown-header { + display: flex; + justify-content: space-between; +} + +$bookmark_dropdown_items_container_max_height: 300px !default; +.bookmark-dropdown-items-container { + max-height: $bookmark_dropdown_items_container_max_height; + overflow: scroll; +}