sort works by currently-reading
This commit is contained in:
parent
06d782e77a
commit
d689733bbb
|
@ -2,6 +2,7 @@
|
|||
|
||||
import argparse
|
||||
import asyncio
|
||||
from configparser import ConfigParser
|
||||
import importlib_resources as resources
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
@ -1389,6 +1390,48 @@ def generate(args):
|
|||
debug('main database closed')
|
||||
|
||||
|
||||
def update_reading(args):
|
||||
debug('updating list of currently-reading works')
|
||||
|
||||
firefox_data_dir = Path.home() / '.mozilla' / 'firefox'
|
||||
debug('finding firefox profiles')
|
||||
firefox_profiles_parser = ConfigParser()
|
||||
firefox_profiles_parser.read(firefox_data_dir / 'profiles.ini')
|
||||
default_profile = next(
|
||||
section['Path']
|
||||
for section in firefox_profiles_parser.values()
|
||||
if section.get('Default') == '1'
|
||||
)
|
||||
debug(f'selecting default profile {default_profile}')
|
||||
storage_path = firefox_data_dir / default_profile / 'storage' / 'default'
|
||||
dlibrary_site_works_path = args.destdir / 'site' / 'works'
|
||||
origin_storage_glob = (
|
||||
'file+++' +
|
||||
str(dlibrary_site_works_path.absolute()).replace('/', '+') +
|
||||
'*+view+index.html/ls/data.sqlite'
|
||||
)
|
||||
debug(f'searching for local storage matching glob {origin_storage_glob}')
|
||||
|
||||
reading_works = []
|
||||
for local_storage_db in storage_path.glob(origin_storage_glob):
|
||||
debug(f'reading db {local_storage_db}')
|
||||
try:
|
||||
with sqlite3.connect(local_storage_db, timeout=0.1) as con:
|
||||
cur = con.cursor()
|
||||
for (key,) in cur.execute("SELECT key FROM data WHERE key LIKE '%-currentPage' AND VALUE != CAST('0' AS BLOB)"):
|
||||
work_id = key[:-len('-currentPage')]
|
||||
reading_works.append(work_id)
|
||||
except sqlite3.OperationalError:
|
||||
debug(f'database {local_storage_db} locked, skipping')
|
||||
|
||||
debug(f'reading works: {reading_works}')
|
||||
output_file = args.destdir / 'site' / 'reading.js'
|
||||
with open(output_file, 'w') as f:
|
||||
f.write('const READING_WORKS = [\n')
|
||||
for work_id in reading_works:
|
||||
f.write(f' "{work_id}",\n')
|
||||
f.write('];')
|
||||
|
||||
argparser = argparse.ArgumentParser(
|
||||
prog='dlibrary',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
|
@ -1578,6 +1621,22 @@ parser_generate = subparsers.add_parser(
|
|||
)
|
||||
parser_generate.set_defaults(func=generate)
|
||||
|
||||
parser_update_reading = subparsers.add_parser(
|
||||
'update-reading',
|
||||
help='update list of currently-reading works (firefox-only)',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=textwrap.dedent("""\
|
||||
If accessing dlibrary via file:// URLs, every distinct filepath is
|
||||
its own opaque origin, so individual works can't share
|
||||
localStorage with the main overview page, making it impossible to
|
||||
sort works by currently-reading status. This subcommand can be run
|
||||
periodically to work around the same-origin limitations by
|
||||
directly accessing data from firefox's localStorage and putting a
|
||||
list of currently-reading works in a JS file that the main page can read.
|
||||
"""),
|
||||
)
|
||||
parser_update_reading.set_defaults(func=update_reading)
|
||||
|
||||
def main():
|
||||
args = argparser.parse_args()
|
||||
|
||||
|
|
|
@ -98,6 +98,10 @@ body {
|
|||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.card.reading {
|
||||
background: #522;
|
||||
}
|
||||
|
||||
.card img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
|
3
dlibrary/static/icons/bookmark.svg
Normal file
3
dlibrary/static/icons/bookmark.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='180' height='180' viewBox='-20 -20 180 180'>
|
||||
<path d='M 30 0 L 30 140 L 70 100 L 110 140 L 110 0 Z' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/>
|
||||
</svg>
|
After Width: | Height: | Size: 332 B |
|
@ -28,9 +28,14 @@ function newSeed() {
|
|||
return Math.floor(Math.random() * LCG_M);
|
||||
}
|
||||
|
||||
function isReading(work) {
|
||||
return READING_WORKS.indexOf(work.id) !== -1 || !!localStorage.getItem(`${work.id}-currentPage`);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const shuffleButton = document.getElementById('shuffle');
|
||||
const sortButton = document.getElementById('sort');
|
||||
const readingButton = document.getElementById('reading');
|
||||
const searchBox = document.getElementById('search');
|
||||
const listContainer = document.getElementById('main-listing');
|
||||
|
||||
|
@ -45,6 +50,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
if (isReading(work)) {
|
||||
card.classList.add('reading');
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `${ROOT}/works/${work.id}/${INDEX}`;
|
||||
|
@ -87,6 +95,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
case 'dateAsc':
|
||||
orderedWorks = WORKS.toReversed();
|
||||
break;
|
||||
case 'reading':
|
||||
orderedWorks = WORKS.toSorted((a, b) => {
|
||||
const aReading = isReading(a);
|
||||
const bReading = isReading(b);
|
||||
|
||||
if (aReading && !bReading) {
|
||||
return -1;
|
||||
}
|
||||
if (bReading && !aReading) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
break;
|
||||
default:
|
||||
orderedWorks = [...WORKS];
|
||||
break;
|
||||
|
@ -120,23 +142,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
window.addEventListener('scroll', scrollHandler);
|
||||
|
||||
function updateOrdering(o) {
|
||||
ordering = o;
|
||||
localStorage.setItem('indexOrdering', ordering);
|
||||
applyView();
|
||||
}
|
||||
|
||||
shuffleButton.onclick = () => {
|
||||
shuffleSeed = newSeed();
|
||||
localStorage.setItem('shuffleSeed', shuffleSeed);
|
||||
ordering = 'shuffle';
|
||||
localStorage.setItem('indexOrdering', ordering);
|
||||
|
||||
applyView();
|
||||
updateOrdering('shuffle');
|
||||
};
|
||||
sortButton.onclick = () => {
|
||||
if (ordering === 'dateDesc') {
|
||||
ordering = 'dateAsc';
|
||||
updateOrdering('dateAsc');
|
||||
} else {
|
||||
ordering = 'dateDesc';
|
||||
updateOrdering('dateDesc');
|
||||
}
|
||||
localStorage.setItem('indexOrdering', ordering);
|
||||
|
||||
applyView();
|
||||
};
|
||||
readingButton.onclick = () => {
|
||||
updateOrdering('reading');
|
||||
};
|
||||
|
||||
searchBox.oninput = applyView;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
const INDEX = "{{ index() }}";
|
||||
const WORKS = {{ works | tojson }};
|
||||
</script>
|
||||
<script src="{{ root() }}/reading.js"></script>
|
||||
<script src="{{ root() }}/static/index.js"></script>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
@ -18,6 +19,7 @@
|
|||
<div id="controls">
|
||||
<button id="shuffle" name="Shuffle"><img src="{{ root() }}/static/icons/shuffle.svg"/></button>
|
||||
<button id="sort" name="Sort"><img src="{{ root() }}/static/icons/sort.svg"/></button>
|
||||
<button id="reading" name="Reading"><img src="{{ root() }}/static/icons/bookmark.svg"/></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="search-container">
|
||||
|
|
Loading…
Reference in a new issue