Compare commits
No commits in common. "2a475ffb03888ae294ce6bebbd2c00d3f8d6d64f" and "ce300770136448a435567dc384cc13e452695b53" have entirely different histories.
2a475ffb03
...
ce30077013
28 changed files with 156 additions and 288 deletions
|
|
@ -221,8 +221,6 @@ Here are some thoughts about the kinds of features that need to exist within the
|
||||||
|
|
||||||
## Mirrors
|
## Mirrors
|
||||||
|
|
||||||
https://git.voussoir.net/voussoir/etiquette
|
|
||||||
|
|
||||||
https://github.com/voussoir/etiquette
|
https://github.com/voussoir/etiquette
|
||||||
|
|
||||||
https://gitlab.com/voussoir/etiquette
|
https://gitlab.com/voussoir/etiquette
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ CREATE INDEX IF NOT EXISTS index_tag_group_rel_memberid on tag_group_rel(memberi
|
||||||
CREATE TABLE IF NOT EXISTS tag_synonyms(
|
CREATE TABLE IF NOT EXISTS tag_synonyms(
|
||||||
name TEXT PRIMARY KEY NOT NULL,
|
name TEXT PRIMARY KEY NOT NULL,
|
||||||
mastername TEXT NOT NULL,
|
mastername TEXT NOT NULL,
|
||||||
created INT
|
created INT,
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS index_tag_synonyms_name on tag_synonyms(name);
|
CREATE INDEX IF NOT EXISTS index_tag_synonyms_name on tag_synonyms(name);
|
||||||
CREATE INDEX IF NOT EXISTS index_tag_synonyms_mastername on tag_synonyms(mastername);
|
CREATE INDEX IF NOT EXISTS index_tag_synonyms_mastername on tag_synonyms(mastername);
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ This file provides functions which are used in various places throughout the
|
||||||
codebase but don't deserve to be methods of any class.
|
codebase but don't deserve to be methods of any class.
|
||||||
'''
|
'''
|
||||||
import bs4
|
import bs4
|
||||||
import io
|
|
||||||
import datetime
|
import datetime
|
||||||
import kkroening_ffmpeg
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
|
|
@ -20,9 +18,6 @@ from voussoirkit import imagetools
|
||||||
from voussoirkit import pathclass
|
from voussoirkit import pathclass
|
||||||
from voussoirkit import stringtools
|
from voussoirkit import stringtools
|
||||||
from voussoirkit import timetools
|
from voussoirkit import timetools
|
||||||
from voussoirkit import vlogging
|
|
||||||
|
|
||||||
log = vlogging.get_logger(__name__)
|
|
||||||
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
@ -223,7 +218,6 @@ def _generate_image_thumbnail(filepath, max_width, max_height) -> PIL.Image:
|
||||||
if not os.path.isfile(filepath):
|
if not os.path.isfile(filepath):
|
||||||
raise FileNotFoundError(filepath)
|
raise FileNotFoundError(filepath)
|
||||||
image = PIL.Image.open(filepath)
|
image = PIL.Image.open(filepath)
|
||||||
image = imagetools.convert_to_srgb(image)
|
|
||||||
(image, exif) = imagetools.rotate_by_exif(image)
|
(image, exif) = imagetools.rotate_by_exif(image)
|
||||||
(image_width, image_height) = image.size
|
(image_width, image_height) = image.size
|
||||||
(new_width, new_height) = imagetools.fit_into_bounds(
|
(new_width, new_height) = imagetools.fit_into_bounds(
|
||||||
|
|
@ -260,23 +254,13 @@ def generate_image_thumbnail(*args, trusted_file=False, **kwargs) -> PIL.Image:
|
||||||
finally:
|
finally:
|
||||||
PIL.Image.MAX_IMAGE_PIXELS = _max_pixels
|
PIL.Image.MAX_IMAGE_PIXELS = _max_pixels
|
||||||
|
|
||||||
def image_is_mostly_black(image):
|
|
||||||
tiny = image.copy()
|
|
||||||
tiny.thumbnail((64, 64))
|
|
||||||
pixels = list(tiny.getdata())
|
|
||||||
black_count = 0
|
|
||||||
if tiny.mode == 'RGB':
|
|
||||||
black_count = sum(1 for pixel in pixels if sum(pixel) <= 24)
|
|
||||||
|
|
||||||
return (black_count / len(pixels)) > 0.5
|
|
||||||
|
|
||||||
def generate_video_thumbnail(filepath, width, height, **special) -> PIL.Image:
|
def generate_video_thumbnail(filepath, width, height, **special) -> PIL.Image:
|
||||||
file = pathclass.Path(filepath)
|
if not os.path.isfile(filepath):
|
||||||
file.assert_is_file()
|
raise FileNotFoundError(filepath)
|
||||||
probe = constants.ffmpeg.probe(filepath)
|
probe = constants.ffmpeg.probe(filepath)
|
||||||
|
|
||||||
if not probe or not probe.video:
|
if not probe or not probe.video:
|
||||||
return None
|
return False
|
||||||
|
|
||||||
size = imagetools.fit_into_bounds(
|
size = imagetools.fit_into_bounds(
|
||||||
image_width=probe.video.video_width,
|
image_width=probe.video.video_width,
|
||||||
|
|
@ -284,25 +268,26 @@ def generate_video_thumbnail(filepath, width, height, **special) -> PIL.Image:
|
||||||
frame_width=width,
|
frame_width=width,
|
||||||
frame_height=height,
|
frame_height=height,
|
||||||
)
|
)
|
||||||
|
size = '%dx%d' % size
|
||||||
duration = probe.video.duration
|
duration = probe.video.duration
|
||||||
|
|
||||||
if 'timestamp' in special:
|
if 'timestamp' in special:
|
||||||
timestamp_choices = [special['timestamp']]
|
timestamp = special['timestamp']
|
||||||
|
elif duration < 3:
|
||||||
|
timestamp = 0
|
||||||
else:
|
else:
|
||||||
timestamp_choices = list(range(0, int(duration), 3))
|
timestamp = 2
|
||||||
|
|
||||||
image = None
|
|
||||||
for this_time in timestamp_choices:
|
|
||||||
log.debug('Attempting video thumbnail at t=%d', this_time)
|
|
||||||
command = kkroening_ffmpeg.input(file.absolute_path, ss=this_time)
|
|
||||||
command = command.filter('scale', size[0], size[1])
|
|
||||||
command = command.output('pipe:', vcodec='bmp', format='image2pipe', vframes=1)
|
|
||||||
(out, trash) = command.run(capture_stdout=True, capture_stderr=True)
|
|
||||||
bio = io.BytesIO(out)
|
|
||||||
image = PIL.Image.open(bio)
|
|
||||||
if not image_is_mostly_black(image):
|
|
||||||
break
|
|
||||||
|
|
||||||
|
outfile = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
|
||||||
|
constants.ffmpeg.thumbnail(
|
||||||
|
filepath,
|
||||||
|
outfile=outfile.name,
|
||||||
|
quality=2,
|
||||||
|
size=size,
|
||||||
|
time=timestamp,
|
||||||
|
)
|
||||||
|
outfile.close()
|
||||||
|
image = PIL.Image.open(outfile.name)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def get_mimetype(extension) -> typing.Optional[str]:
|
def get_mimetype(extension) -> typing.Optional[str]:
|
||||||
|
|
|
||||||
|
|
@ -1150,7 +1150,7 @@ class PhotoDB(
|
||||||
self.COLUMNS = constants.SQL_COLUMNS
|
self.COLUMNS = constants.SQL_COLUMNS
|
||||||
self.COLUMN_INDEX = constants.SQL_INDEX
|
self.COLUMN_INDEX = constants.SQL_INDEX
|
||||||
|
|
||||||
def _init_sql(self, create=False, skip_version_check=False):
|
def _init_sql(self, create, skip_version_check):
|
||||||
if self.ephemeral:
|
if self.ephemeral:
|
||||||
existing_database = False
|
existing_database = False
|
||||||
self.sql_write = self._make_sqlite_write_connection(':memory:')
|
self.sql_write = self._make_sqlite_write_connection(':memory:')
|
||||||
|
|
|
||||||
|
|
@ -99,11 +99,8 @@ def before_request():
|
||||||
if site.localhost_only and not request.is_localhost:
|
if site.localhost_only and not request.is_localhost:
|
||||||
return flask.abort(403)
|
return flask.abort(403)
|
||||||
|
|
||||||
if request.url_rule is None:
|
# Since we don't define this route, I can't just add this where it belongs.
|
||||||
return flask.abort(404)
|
# Sorry.
|
||||||
|
|
||||||
# Since we don't define this route (/static/ is a default from flask),
|
|
||||||
# I can't just add this where it belongs. Sorry.
|
|
||||||
if request.url_rule.rule == '/static/<path:filename>':
|
if request.url_rule.rule == '/static/<path:filename>':
|
||||||
permission_manager.global_public()
|
permission_manager.global_public()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ def get_file(photo_id, basename=None):
|
||||||
@site.route('/photo/<photo_id>/thumbnail')
|
@site.route('/photo/<photo_id>/thumbnail')
|
||||||
@site.route('/photo/<photo_id>/thumbnail/<basename>')
|
@site.route('/photo/<photo_id>/thumbnail/<basename>')
|
||||||
@common.permission_manager.basic_decorator
|
@common.permission_manager.basic_decorator
|
||||||
@flasktools.cached_endpoint(max_age=common.BROWSER_CACHE_DURATION, etag_function=lambda: common.P.last_commit_id)
|
@flasktools.cached_endpoint(max_age=common.BROWSER_CACHE_DURATION)
|
||||||
def get_thumbnail(photo_id, basename=None):
|
def get_thumbnail(photo_id, basename=None):
|
||||||
photo_id = photo_id.split('.')[0]
|
photo_id = photo_id.split('.')[0]
|
||||||
photo = common.P_photo(photo_id, response_type='html')
|
photo = common.P_photo(photo_id, response_type='html')
|
||||||
|
|
@ -186,35 +186,16 @@ def post_batch_photos_remove_tag():
|
||||||
|
|
||||||
# Photo metadata operations ########################################################################
|
# Photo metadata operations ########################################################################
|
||||||
|
|
||||||
def post_photo_generate_thumbnail_core(photo_ids, special={}):
|
|
||||||
if isinstance(photo_ids, str):
|
|
||||||
photo_ids = stringtools.comma_space_split(photo_ids)
|
|
||||||
|
|
||||||
with common.P.transaction:
|
|
||||||
photos = list(common.P_photos(photo_ids, response_type='json'))
|
|
||||||
|
|
||||||
for photo in photos:
|
|
||||||
photo._uncache()
|
|
||||||
photo = common.P_photo(photo.id, response_type='json')
|
|
||||||
try:
|
|
||||||
photo.generate_thumbnail()
|
|
||||||
except Exception:
|
|
||||||
log.warning(traceback.format_exc())
|
|
||||||
|
|
||||||
return flasktools.json_response({})
|
|
||||||
|
|
||||||
@site.route('/photo/<photo_id>/generate_thumbnail', methods=['POST'])
|
@site.route('/photo/<photo_id>/generate_thumbnail', methods=['POST'])
|
||||||
def post_photo_generate_thumbnail(photo_id):
|
def post_photo_generate_thumbnail(photo_id):
|
||||||
common.permission_manager.basic()
|
common.permission_manager.basic()
|
||||||
special = request.form.to_dict()
|
special = request.form.to_dict()
|
||||||
response = post_photo_generate_thumbnail_core(photo_ids=photo_id, special=special)
|
|
||||||
return response
|
|
||||||
|
|
||||||
@site.route('/batch/photos/generate_thumbnail', methods=['POST'])
|
with common.P.transaction:
|
||||||
def post_batch_photos_generate_thumbnail():
|
photo = common.P_photo(photo_id, response_type='json')
|
||||||
common.permission_manager.basic()
|
photo.generate_thumbnail(**special)
|
||||||
special = request.form.to_dict()
|
|
||||||
response = post_photo_generate_thumbnail_core(photo_ids=request.form['photo_ids'], special=special)
|
response = flasktools.json_response({})
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def post_photo_refresh_metadata_core(photo_ids):
|
def post_photo_refresh_metadata_core(photo_ids):
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,6 @@ class SessionManager:
|
||||||
# Send the token back to the client
|
# Send the token back to the client
|
||||||
# but only if the endpoint didn't manually set the cookie.
|
# but only if the endpoint didn't manually set the cookie.
|
||||||
function_cookies = response.headers.get_all('Set-Cookie')
|
function_cookies = response.headers.get_all('Set-Cookie')
|
||||||
|
|
||||||
if not hasattr(request, 'session') or not request.session:
|
|
||||||
return response
|
|
||||||
|
|
||||||
if not any('etiquette_session=' in cookie for cookie in function_cookies):
|
if not any('etiquette_session=' in cookie for cookie in function_cookies):
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
'etiquette_session',
|
'etiquette_session',
|
||||||
|
|
|
||||||
|
|
@ -203,105 +203,3 @@ is hovered over.
|
||||||
{
|
{
|
||||||
flex: 2;
|
flex: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
html.theme_slate
|
|
||||||
{
|
|
||||||
--color_primary: #222;
|
|
||||||
--color_secondary: #3b4d5d;
|
|
||||||
|
|
||||||
--color_text_normal: #efefef;
|
|
||||||
--color_text_link: #1edeff;
|
|
||||||
--color_text_bubble: black;
|
|
||||||
|
|
||||||
--color_textfields: var(--color_secondary);
|
|
||||||
--color_text_placeholder: gray;
|
|
||||||
|
|
||||||
--color_transparency: rgba(255, 255, 255, 0.05);
|
|
||||||
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
|
||||||
--color_shadow: rgba(0, 0, 0, 0.5);
|
|
||||||
--color_highlight: rgba(255, 255, 255, 0.5);
|
|
||||||
|
|
||||||
--color_tag_card_bg: #e6e6e6;
|
|
||||||
--color_tag_card_fg: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme_slate button,
|
|
||||||
html.theme_slate button *
|
|
||||||
{
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
/******************************************************************************/
|
|
||||||
html.theme_hotdogstand
|
|
||||||
{
|
|
||||||
--color_primary: yellow;
|
|
||||||
--color_secondary: red;
|
|
||||||
|
|
||||||
--color_text_normal: black;
|
|
||||||
--color_text_link: rebeccapurple;
|
|
||||||
--color_text_bubble: black;
|
|
||||||
|
|
||||||
--color_textfields: var(--color_secondary);
|
|
||||||
--color_text_placeholder: black;
|
|
||||||
|
|
||||||
--color_transparency: yellow;
|
|
||||||
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
|
||||||
--color_shadow: rgba(0, 0, 0, 0.5);
|
|
||||||
--color_highlight: rgba(255, 255, 255, 0.5);
|
|
||||||
|
|
||||||
--color_tag_card_bg: red;
|
|
||||||
--color_tag_card_fg: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme_hotdogstand button,
|
|
||||||
html.theme_hotdogstand button *
|
|
||||||
{
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme_hotdogstand .panel
|
|
||||||
{
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
/******************************************************************************/
|
|
||||||
html.theme_pearl
|
|
||||||
{
|
|
||||||
--color_primary: #f6ffff;
|
|
||||||
--color_secondary: #aad7ff;
|
|
||||||
|
|
||||||
--color_text_normal: black;
|
|
||||||
--color_text_link: #00f;
|
|
||||||
--color_text_bubble: black;
|
|
||||||
|
|
||||||
--color_textfields: white;
|
|
||||||
--color_text_placeholder: gray;
|
|
||||||
|
|
||||||
--color_transparency: rgba(0, 0, 0, 0.1);
|
|
||||||
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
|
||||||
--color_shadow: rgba(0, 0, 0, 0.5);
|
|
||||||
--color_highlight: rgba(255, 255, 255, 0.5);
|
|
||||||
|
|
||||||
--color_tag_card_bg: #fff;
|
|
||||||
--color_tag_card_fg: black;
|
|
||||||
}
|
|
||||||
/******************************************************************************/
|
|
||||||
html.theme_turquoise
|
|
||||||
{
|
|
||||||
--color_primary: #00d8f4;
|
|
||||||
--color_secondary: #ffffd4;
|
|
||||||
|
|
||||||
--color_text_normal: black;
|
|
||||||
--color_text_link: blue;
|
|
||||||
--color_text_bubble: black;
|
|
||||||
|
|
||||||
--color_textfields: white;
|
|
||||||
--color_text_placeholder: gray;
|
|
||||||
|
|
||||||
--color_transparency: rgba(0, 0, 0, 0.1);
|
|
||||||
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
|
||||||
--color_shadow: rgba(0, 0, 0, 0.5);
|
|
||||||
--color_highlight: rgba(255, 255, 255, 0.5);
|
|
||||||
|
|
||||||
--color_tag_card_bg: #fff;
|
|
||||||
--color_tag_card_fg: blue;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
31
frontends/etiquette_flask/static/css/theme_hotdogstand.css
Normal file
31
frontends/etiquette_flask/static/css/theme_hotdogstand.css
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
:root
|
||||||
|
{
|
||||||
|
--color_primary: yellow;
|
||||||
|
--color_secondary: red;
|
||||||
|
|
||||||
|
--color_text_normal: black;
|
||||||
|
--color_text_link: rebeccapurple;
|
||||||
|
--color_text_bubble: black;
|
||||||
|
|
||||||
|
--color_textfields: var(--color_secondary);
|
||||||
|
--color_text_placeholder: black;
|
||||||
|
|
||||||
|
--color_transparency: yellow;
|
||||||
|
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
||||||
|
--color_shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
--color_highlight: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
--color_tag_card_bg: red;
|
||||||
|
--color_tag_card_fg: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
button *
|
||||||
|
{
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel
|
||||||
|
{
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
20
frontends/etiquette_flask/static/css/theme_pearl.css
Normal file
20
frontends/etiquette_flask/static/css/theme_pearl.css
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
:root
|
||||||
|
{
|
||||||
|
--color_primary: #f6ffff;
|
||||||
|
--color_secondary: #aad7ff;
|
||||||
|
|
||||||
|
--color_text_normal: black;
|
||||||
|
--color_text_link: #00f;
|
||||||
|
--color_text_bubble: black;
|
||||||
|
|
||||||
|
--color_textfields: white;
|
||||||
|
--color_text_placeholder: gray;
|
||||||
|
|
||||||
|
--color_transparency: rgba(0, 0, 0, 0.1);
|
||||||
|
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
||||||
|
--color_shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
--color_highlight: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
--color_tag_card_bg: #fff;
|
||||||
|
--color_tag_card_fg: black;
|
||||||
|
}
|
||||||
26
frontends/etiquette_flask/static/css/theme_slate.css
Normal file
26
frontends/etiquette_flask/static/css/theme_slate.css
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
:root
|
||||||
|
{
|
||||||
|
--color_primary: #222;
|
||||||
|
--color_secondary: #3b4d5d;
|
||||||
|
|
||||||
|
--color_text_normal: #efefef;
|
||||||
|
--color_text_link: #1edeff;
|
||||||
|
--color_text_bubble: black;
|
||||||
|
|
||||||
|
--color_textfields: var(--color_secondary);
|
||||||
|
--color_text_placeholder: gray;
|
||||||
|
|
||||||
|
--color_transparency: rgba(255, 255, 255, 0.05);
|
||||||
|
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
||||||
|
--color_shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
--color_highlight: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
--color_tag_card_bg: #e6e6e6;
|
||||||
|
--color_tag_card_fg: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
button *
|
||||||
|
{
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
20
frontends/etiquette_flask/static/css/theme_turquoise.css
Normal file
20
frontends/etiquette_flask/static/css/theme_turquoise.css
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
:root
|
||||||
|
{
|
||||||
|
--color_primary: #00d8f4;
|
||||||
|
--color_secondary: #ffffd4;
|
||||||
|
|
||||||
|
--color_text_normal: black;
|
||||||
|
--color_text_link: blue;
|
||||||
|
--color_text_bubble: black;
|
||||||
|
|
||||||
|
--color_textfields: white;
|
||||||
|
--color_text_placeholder: gray;
|
||||||
|
|
||||||
|
--color_transparency: rgba(0, 0, 0, 0.1);
|
||||||
|
--color_dropshadow: rgba(0, 0, 0, 0.25);
|
||||||
|
--color_shadow: rgba(0, 0, 0, 0.5);
|
||||||
|
--color_highlight: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
--color_tag_card_bg: #fff;
|
||||||
|
--color_tag_card_fg: blue;
|
||||||
|
}
|
||||||
|
|
@ -236,17 +236,7 @@ function batch_add_tag(photo_ids, tagname, callback)
|
||||||
return http.post({
|
return http.post({
|
||||||
url: "/batch/photos/add_tag",
|
url: "/batch/photos/add_tag",
|
||||||
data: {"photo_ids": photo_ids.join(","), "tagname": tagname},
|
data: {"photo_ids": photo_ids.join(","), "tagname": tagname},
|
||||||
callback: callback,
|
add_remove_tag_callback: callback,
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
api.photos.batch_generate_thumbnail =
|
|
||||||
function batch_generate_thumbnail(photo_ids, callback)
|
|
||||||
{
|
|
||||||
return http.post({
|
|
||||||
url: "/batch/photos/generate_thumbnail",
|
|
||||||
data: {"photo_ids": photo_ids.join(",")},
|
|
||||||
callback: callback,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +256,7 @@ function batch_remove_tag(photo_ids, tagname, callback)
|
||||||
return http.post({
|
return http.post({
|
||||||
url: "/batch/photos/remove_tag",
|
url: "/batch/photos/remove_tag",
|
||||||
data: {"photo_ids": photo_ids.join(","), "tagname": tagname},
|
data: {"photo_ids": photo_ids.join(","), "tagname": tagname},
|
||||||
callback: callback,
|
add_remove_tag_callback: callback,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,54 +71,6 @@ function join_and_trail(list, separator)
|
||||||
return list.join(separator) + separator
|
return list.join(separator) + separator
|
||||||
}
|
}
|
||||||
|
|
||||||
common.hms_render_colons =
|
|
||||||
function hms_render_colons(hours, minutes, seconds)
|
|
||||||
{
|
|
||||||
const parts = [];
|
|
||||||
if (hours !== null)
|
|
||||||
{
|
|
||||||
parts.push(hours.toLocaleString(undefined, {minimumIntegerDigits: 2}));
|
|
||||||
}
|
|
||||||
if (minutes !== null)
|
|
||||||
{
|
|
||||||
parts.push(minutes.toLocaleString(undefined, {minimumIntegerDigits: 2}));
|
|
||||||
}
|
|
||||||
parts.push(seconds.toLocaleString(undefined, {minimumIntegerDigits: 2}));
|
|
||||||
return parts.join(":")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.seconds_to_hms =
|
|
||||||
function seconds_to_hms(seconds, args)
|
|
||||||
{
|
|
||||||
args = args || {};
|
|
||||||
const renderer = args["renderer"] || common.hms_render_colons;
|
|
||||||
const force_minutes = args["force_minutes"] || false;
|
|
||||||
const force_hours = args["force_hours"] || false;
|
|
||||||
|
|
||||||
if (seconds > 0 && seconds < 1)
|
|
||||||
{
|
|
||||||
seconds = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
seconds = Math.round(seconds);
|
|
||||||
}
|
|
||||||
let minutes = Math.floor(seconds / 60);
|
|
||||||
seconds = seconds % 60;
|
|
||||||
let hours = Math.floor(minutes / 60);
|
|
||||||
minutes = minutes % 60;
|
|
||||||
|
|
||||||
if (hours == 0 && force_hours == false)
|
|
||||||
{
|
|
||||||
hours = null;
|
|
||||||
}
|
|
||||||
if (minutes == 0 && force_minutes == false)
|
|
||||||
{
|
|
||||||
minutes = null;
|
|
||||||
}
|
|
||||||
return renderer(hours, minutes, seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// HTML & DOM //////////////////////////////////////////////////////////////////////////////////////
|
// HTML & DOM //////////////////////////////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
<title>Admin control</title>
|
<title>Admin control</title>
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<link rel="icon" href="/favicon.png" type="image/png"/>
|
<link rel="icon" href="/favicon.png" type="image/png"/>
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/http.js"></script>
|
<script src="/static/js/http.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
|
|
||||||
{% macro shared_css() %}
|
{% macro shared_css() %}
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -89,6 +89,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/cards.js"></script>
|
<script src="/static/js/cards.js"></script>
|
||||||
|
|
@ -145,6 +146,7 @@ const ALBUM_ID = undefined;
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/album_autocomplete.js"></script>
|
<script src="/static/js/album_autocomplete.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "cards.html" as cards %}
|
{% import "cards.html" as cards %}
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/cards.js"></script>
|
<script src="/static/js/cards.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ ondrop="return cards.photos.drag_drop(event);"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
>
|
>
|
||||||
<div class="photo_card_filename">
|
<div class="photo_card_filename">
|
||||||
<a target="_blank" href="/photo/{{photo.id}}" draggable="false">{{photo.basename}}</a>
|
<a href="/photo/{{photo.id}}" draggable="false">{{photo.basename}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="photo_card_metadata">
|
<span class="photo_card_metadata">
|
||||||
|
|
@ -172,7 +172,7 @@ draggable="true"
|
||||||
{% set thumbnail_src = "/static/basic_thumbnails/" ~ thumbnail_src ~ ".png" %}
|
{% set thumbnail_src = "/static/basic_thumbnails/" ~ thumbnail_src ~ ".png" %}
|
||||||
{% endif -%}{# if thumbnail #}
|
{% endif -%}{# if thumbnail #}
|
||||||
|
|
||||||
<a class="photo_card_thumbnail" target="_blank" href="/photo/{{photo.id}}" draggable="false">
|
<a class="photo_card_thumbnail" href="/photo/{{photo.id}}" draggable="false">
|
||||||
<img loading="lazy" src="{{thumbnail_src}}" draggable="false">
|
<img loading="lazy" src="{{thumbnail_src}}" draggable="false">
|
||||||
</a>
|
</a>
|
||||||
{% endif %}{# if grid #}
|
{% endif %}{# if grid #}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "clipboard_tray.html" as clipboard_tray %}
|
{% import "clipboard_tray.html" as clipboard_tray %}
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/cards.js"></script>
|
<script src="/static/js/cards.js"></script>
|
||||||
|
|
@ -121,10 +122,6 @@
|
||||||
<button class="green_button button_with_spinner" id="refresh_metadata_button" data-spinner-delay="500" onclick="return refresh_metadata_form();">Refresh metadata</button>
|
<button class="green_button button_with_spinner" id="refresh_metadata_button" data-spinner-delay="500" onclick="return refresh_metadata_form();">Refresh metadata</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="generate_thumbnail_area">
|
|
||||||
<button class="green_button button_with_spinner" id="generate_thumbnail_button" data-spinner-delay="500" onclick="return generate_thumbnail_form();">Generate thumbnail</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="searchhidden_area">
|
<div id="searchhidden_area">
|
||||||
<button class="yellow_button" id="set_searchhidden_button" onclick="return set_searchhidden_form();">Searchhide</button>
|
<button class="yellow_button" id="set_searchhidden_button" onclick="return set_searchhidden_form();">Searchhide</button>
|
||||||
<button class="yellow_button" id="unset_searchhidden_button" onclick="return unset_searchhidden_form();">Unhide</button>
|
<button class="yellow_button" id="unset_searchhidden_button" onclick="return unset_searchhidden_form();">Unhide</button>
|
||||||
|
|
@ -372,42 +369,6 @@ function refresh_metadata_form()
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const generate_thumbnail_button = document.getElementById("generate_thumbnail_button");
|
|
||||||
|
|
||||||
function generate_thumbnail_callback(response)
|
|
||||||
{
|
|
||||||
window[generate_thumbnail_button.dataset.spinnerCloser]();
|
|
||||||
if (! response.meta.json_ok)
|
|
||||||
{
|
|
||||||
alert(JSON.stringify(response));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ("error_type" in response.data)
|
|
||||||
{
|
|
||||||
const message_area = document.getElementById("message_area");
|
|
||||||
const message_positivity = "message_negative";
|
|
||||||
const message_text = response.data.error_message;
|
|
||||||
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
common.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generate_thumbnail_form()
|
|
||||||
{
|
|
||||||
if (photo_clipboard.clipboard.size == 0)
|
|
||||||
{
|
|
||||||
return spinners.BAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const photo_ids = Array.from(photo_clipboard.clipboard);
|
|
||||||
api.photos.batch_generate_thumbnail(photo_ids, generate_thumbnail_callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
function set_unset_searchhidden_callback(response)
|
function set_unset_searchhidden_callback(response)
|
||||||
{
|
{
|
||||||
if (! response.meta.json_ok)
|
if (! response.meta.json_ok)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
<title>Login/Register</title>
|
<title>Login/Register</title>
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<link rel="icon" href="/favicon.png" type="image/png"/>
|
<link rel="icon" href="/favicon.png" type="image/png"/>
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/http.js"></script>
|
<script src="/static/js/http.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "cards.html" as cards %}
|
{% import "cards.html" as cards %}
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/hotkeys.js"></script>
|
<script src="/static/js/hotkeys.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Etiquette</title>
|
<title>Etiquette</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<link rel="icon" href="/favicon.png" type="image/png"/>
|
<link rel="icon" href="/favicon.png" type="image/png"/>
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/http.js"></script>
|
<script src="/static/js/http.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "cards.html" as cards %}
|
{% import "cards.html" as cards %}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/hotkeys.js"></script>
|
<script src="/static/js/hotkeys.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body
|
body
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "clipboard_tray.html" as clipboard_tray %}
|
{% import "clipboard_tray.html" as clipboard_tray %}
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
<link rel="stylesheet" href="/static/css/clipboard_tray.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/cards.js"></script>
|
<script src="/static/js/cards.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "cards.html" as cards %}
|
{% import "cards.html" as cards %}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/cards.js"></script>
|
<script src="/static/js/cards.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
<title>Flasksite</title>
|
<title>Flasksite</title>
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<link rel="icon" href="/favicon.png" type="image/png"/>
|
<link rel="icon" href="/favicon.png" type="image/png"/>
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/http.js"></script>
|
<script src="/static/js/http.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="theme_{{theme}}">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% import "header.html" as header %}
|
{% import "header.html" as header %}
|
||||||
{% import "cards.html" as cards %}
|
{% import "cards.html" as cards %}
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<link rel="stylesheet" href="/static/css/common.css">
|
<link rel="stylesheet" href="/static/css/common.css">
|
||||||
<link rel="stylesheet" href="/static/css/etiquette.css">
|
<link rel="stylesheet" href="/static/css/etiquette.css">
|
||||||
<link rel="stylesheet" href="/static/css/cards.css">
|
<link rel="stylesheet" href="/static/css/cards.css">
|
||||||
|
{% if theme %}<link rel="stylesheet" href="/static/css/theme_{{theme}}.css">{% endif %}
|
||||||
<script src="/static/js/common.js"></script>
|
<script src="/static/js/common.js"></script>
|
||||||
<script src="/static/js/api.js"></script>
|
<script src="/static/js/api.js"></script>
|
||||||
<script src="/static/js/editor.js"></script>
|
<script src="/static/js/editor.js"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue