mirror of
https://github.com/aquatix/digimarks.git
synced 2025-12-07 00:15:10 +01:00
Compare commits
8 Commits
5eb9c606f0
...
modules
| Author | SHA1 | Date | |
|---|---|---|---|
| c1024485a3 | |||
| 9712b269ff | |||
| 51b94e4b64 | |||
| 099e6d7ed7 | |||
| bba8d86de1 | |||
| 1c5cedf759 | |||
| a7c7dae9fc | |||
| 958ff11a99 |
427
digimarks.py
427
digimarks.py
@@ -1,6 +1,5 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import binascii
|
||||
import datetime
|
||||
import gzip
|
||||
import hashlib
|
||||
@@ -15,6 +14,8 @@ from flask import (Flask, abort, jsonify, redirect, render_template, request,
|
||||
from peewee import * # noqa
|
||||
from werkzeug.contrib.atom import AtomFeed
|
||||
|
||||
from digimarks import models, themes, views
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import urljoin, urlparse, urlunparse
|
||||
@@ -25,130 +26,6 @@ except ImportError:
|
||||
|
||||
DIGIMARKS_USER_AGENT = 'digimarks/1.2.0-dev'
|
||||
|
||||
DEFAULT_THEME = 'freshgreen'
|
||||
themes = {
|
||||
'green': {
|
||||
'BROWSERCHROME': '#2e7d32', # green darken-2
|
||||
'BODY': 'grey lighten-4',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'green darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#1b5e20', # green darken-4
|
||||
'BUTTON_ACTIVE': '#43a047', # green darken-1
|
||||
'LINK_TEXT': '#1b5e20', # green darken-4
|
||||
'CARD_BACKGROUND': 'green darken-3',
|
||||
'CARD_TEXT': 'white-text',
|
||||
'CARD_LINK': '#FFF', # white-text
|
||||
'CHIP_TEXT': '#1b5e20', # green darken-4
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'freshgreen': {
|
||||
'BROWSERCHROME': '#43a047', # green darken-1
|
||||
'BODY': 'grey lighten-5',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'green darken-1',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#1b5e20', # green darken-4
|
||||
'BUTTON_ACTIVE': '#43a047', # green darken-1
|
||||
'LINK_TEXT': '#1b5e20', # green darken-4
|
||||
'CARD_BACKGROUND': 'green darken-1',
|
||||
'CARD_TEXT': 'white-text',
|
||||
'CARD_LINK': '#FFF', # white-text
|
||||
'CHIP_TEXT': '#1b5e20', # green darken-4
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'lightblue': {
|
||||
'BROWSERCHROME': '#0288d1', # light-blue darken-2
|
||||
'BODY': 'white',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'light-blue darken-2',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#FFF', # white
|
||||
'CARD_BACKGROUND': 'light-blue lighten-2',
|
||||
'CARD_TEXT': 'black-text',
|
||||
'CARD_LINK': '#263238', # blue-grey-text darken-4
|
||||
'CHIP_TEXT': '#FFF', # white
|
||||
'FAB': 'light-blue darken-4',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'dark': {
|
||||
'BROWSERCHROME': '#212121', # grey darken-4
|
||||
'BODY': 'grey darken-4',
|
||||
'TEXT': 'grey-text lighten-1',
|
||||
'TEXTHEX': '#bdbdbd',
|
||||
'NAV': 'grey darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-1',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'CARD_BACKGROUND': 'grey darken-3',
|
||||
'CARD_TEXT': 'grey-text lighten-1',
|
||||
'CARD_LINK': '#fb8c00', # orange-text darken-1
|
||||
'CHIP_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'amoled': {
|
||||
'BROWSERCHROME': '#000', # grey darken-4
|
||||
'BODY': 'black',
|
||||
'TEXT': 'grey-text lighten-1',
|
||||
'TEXTHEX': '#bdbdbd',
|
||||
'NAV': 'grey darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-1',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'CARD_BACKGROUND': 'grey darken-3',
|
||||
'CARD_TEXT': 'grey-text lighten-1',
|
||||
'CARD_LINK': '#fb8c00', # orange-text darken-1
|
||||
'CHIP_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
import settings
|
||||
except ImportError:
|
||||
@@ -159,17 +36,10 @@ except ImportError:
|
||||
APP_ROOT = os.path.dirname(os.path.realpath(__file__))
|
||||
MEDIA_ROOT = os.path.join(APP_ROOT, 'static')
|
||||
MEDIA_URL = '/static/'
|
||||
DATABASE = {
|
||||
'name': os.path.join(APP_ROOT, 'bookmarks.db'),
|
||||
'engine': 'peewee.SqliteDatabase',
|
||||
}
|
||||
#PHANTOM = '/usr/local/bin/phantomjs'
|
||||
#SCRIPT = os.path.join(APP_ROOT, 'screenshot.js')
|
||||
|
||||
# create our flask app and a database wrapper
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
database = SqliteDatabase(os.path.join(APP_ROOT, 'bookmarks.db'))
|
||||
|
||||
# Strip unnecessary whitespace due to jinja2 codeblocks
|
||||
app.jinja_env.trim_blocks = True
|
||||
@@ -186,299 +56,6 @@ all_tags = {}
|
||||
usersettings = {}
|
||||
|
||||
|
||||
def ifilterfalse(predicate, iterable):
|
||||
# ifilterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
|
||||
if predicate is None:
|
||||
predicate = bool
|
||||
for x in iterable:
|
||||
if not predicate(x):
|
||||
yield x
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"List unique elements, preserving order. Remember all elements ever seen."
|
||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
if key is None:
|
||||
for element in ifilterfalse(seen.__contains__, iterable):
|
||||
seen_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
def clean_tags(tags_list):
|
||||
tags_res = [x.strip() for x in tags_list]
|
||||
tags_res = list(unique_everseen(tags_res))
|
||||
tags_res.sort()
|
||||
if tags_res and tags_res[0] == '':
|
||||
del tags_res[0]
|
||||
return tags_res
|
||||
|
||||
|
||||
magic_dict = {
|
||||
b"\x1f\x8b\x08": "gz",
|
||||
b"\x42\x5a\x68": "bz2",
|
||||
b"\x50\x4b\x03\x04": "zip"
|
||||
}
|
||||
|
||||
max_len = max(len(x) for x in magic_dict)
|
||||
|
||||
def file_type(filename):
|
||||
with open(filename, "rb") as f:
|
||||
file_start = f.read(max_len)
|
||||
for magic, filetype in magic_dict.items():
|
||||
if file_start.startswith(magic):
|
||||
return filetype
|
||||
return "no match"
|
||||
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
""" User account """
|
||||
username = CharField()
|
||||
key = CharField()
|
||||
theme = CharField(default=DEFAULT_THEME)
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
def generate_key(self):
|
||||
""" Generate userkey """
|
||||
self.key = binascii.hexlify(os.urandom(24))
|
||||
return self.key
|
||||
|
||||
|
||||
class Bookmark(BaseModel):
|
||||
""" Bookmark instance, connected to User """
|
||||
# Foreign key to User
|
||||
userkey = CharField()
|
||||
|
||||
title = CharField(default='')
|
||||
url = CharField()
|
||||
note = TextField(default='')
|
||||
#image = CharField(default='')
|
||||
url_hash = CharField(default='')
|
||||
tags = CharField(default='')
|
||||
starred = BooleanField(default=False)
|
||||
|
||||
# Website (domain) favicon
|
||||
favicon = CharField(null=True)
|
||||
|
||||
# Status code: 200 is OK, 404 is not found, for example (showing an error)
|
||||
HTTP_CONNECTIONERROR = 0
|
||||
HTTP_OK = 200
|
||||
HTTP_ACCEPTED = 202
|
||||
HTTP_MOVEDTEMPORARILY = 304
|
||||
HTTP_NOTFOUND = 404
|
||||
|
||||
http_status = IntegerField(default=200)
|
||||
redirect_uri = None
|
||||
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
modified_date = DateTimeField(null=True)
|
||||
deleted_date = DateTimeField(null=True)
|
||||
|
||||
# Bookmark status; deleting doesn't remove from DB
|
||||
VISIBLE = 0
|
||||
DELETED = 1
|
||||
status = IntegerField(default=VISIBLE)
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = (('created_date', 'desc'),)
|
||||
|
||||
def set_hash(self):
|
||||
""" Generate hash """
|
||||
self.url_hash = hashlib.md5(self.url.encode('utf-8')).hexdigest()
|
||||
|
||||
def set_title_from_source(self):
|
||||
""" Request the title by requesting the source url """
|
||||
try:
|
||||
result = requests.get(self.url, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
except:
|
||||
# For example 'MissingSchema: Invalid URL 'abc': No schema supplied. Perhaps you meant http://abc?'
|
||||
self.http_status = 404
|
||||
if self.http_status == 200 or self.http_status == 202:
|
||||
html = bs4.BeautifulSoup(result.text, 'html.parser')
|
||||
try:
|
||||
self.title = html.title.text.strip()
|
||||
except AttributeError:
|
||||
self.title = ''
|
||||
return self.title
|
||||
|
||||
def set_status_code(self):
|
||||
""" Check the HTTP status of the url, as it might not exist for example """
|
||||
try:
|
||||
result = requests.head(self.url, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
except requests.ConnectionError:
|
||||
self.http_status = self.HTTP_CONNECTIONERROR
|
||||
return self.http_status
|
||||
|
||||
def _set_favicon_with_iconsbetterideaorg(self, domain):
|
||||
""" Fetch favicon for the domain """
|
||||
fileextension = '.png'
|
||||
meta = requests.head(
|
||||
'http://icons.better-idea.org/icon?size=60&url=' + domain,
|
||||
allow_redirects=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT}
|
||||
)
|
||||
if meta.url[-3:].lower() == 'ico':
|
||||
fileextension = '.ico'
|
||||
response = requests.get(
|
||||
'http://icons.better-idea.org/icon?size=60&url=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT}
|
||||
)
|
||||
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + fileextension)
|
||||
with open(filename, 'wb') as out_file:
|
||||
shutil.copyfileobj(response.raw, out_file)
|
||||
del response
|
||||
filetype = file_type(filename)
|
||||
if filetype == 'gz':
|
||||
# decompress
|
||||
orig = gzip.GzipFile(filename, 'rb')
|
||||
origcontent = orig.read()
|
||||
orig.close()
|
||||
os.remove(filename)
|
||||
with open(filename, 'wb') as new:
|
||||
new.write(origcontent)
|
||||
self.favicon = domain + fileextension
|
||||
|
||||
def _set_favicon_with_realfavicongenerator(self, domain):
|
||||
""" Fetch favicon for the domain """
|
||||
response = requests.get(
|
||||
'https://realfavicongenerator.p.mashape.com/favicon/icon?platform=android_chrome&site=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT, 'X-Mashape-Key': settings.MASHAPE_API_KEY}
|
||||
)
|
||||
if response.status_code == 404:
|
||||
# Fall back to desktop favicon
|
||||
response = requests.get(
|
||||
'https://realfavicongenerator.p.mashape.com/favicon/icon?platform=desktop&site=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT, 'X-Mashape-Key': settings.MASHAPE_API_KEY}
|
||||
)
|
||||
# Debug for the moment
|
||||
print(domain)
|
||||
print(response.headers)
|
||||
if 'Content-Length' in response.headers and response.headers['Content-Length'] == '0':
|
||||
# No favicon found, likely
|
||||
print('Skipping this favicon, needs fallback')
|
||||
return
|
||||
# Default to 'image/png'
|
||||
fileextension = '.png'
|
||||
if response.headers['content-type'] == 'image/jpeg':
|
||||
fileextension = '.jpg'
|
||||
if response.headers['content-type'] == 'image/x-icon':
|
||||
fileextension = '.ico'
|
||||
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + fileextension)
|
||||
with open(filename, 'wb') as out_file:
|
||||
shutil.copyfileobj(response.raw, out_file)
|
||||
del response
|
||||
filetype = file_type(filename)
|
||||
if filetype == 'gz':
|
||||
# decompress
|
||||
orig = gzip.GzipFile(filename, 'rb')
|
||||
origcontent = orig.read()
|
||||
orig.close()
|
||||
os.remove(filename)
|
||||
with open(filename, 'wb') as new:
|
||||
new.write(origcontent)
|
||||
self.favicon = domain + fileextension
|
||||
|
||||
def set_favicon(self):
|
||||
""" Fetch favicon for the domain """
|
||||
u = urlparse(self.url)
|
||||
domain = u.netloc
|
||||
if os.path.isfile(os.path.join(MEDIA_ROOT, 'favicons/' + domain + '.png')):
|
||||
# If file exists, don't re-download it
|
||||
self.favicon = domain + '.png'
|
||||
return
|
||||
if os.path.isfile(os.path.join(MEDIA_ROOT, 'favicons/' + domain + '.ico')):
|
||||
# If file exists, don't re-download it
|
||||
self.favicon = domain + '.ico'
|
||||
return
|
||||
#self._set_favicon_with_iconsbetterideaorg(domain)
|
||||
self._set_favicon_with_realfavicongenerator(domain)
|
||||
|
||||
def set_tags(self, newtags):
|
||||
""" Set tags from `tags`, strip and sort them """
|
||||
tags_split = newtags.split(',')
|
||||
tags_clean = clean_tags(tags_split)
|
||||
self.tags = ','.join(tags_clean)
|
||||
|
||||
def get_redirect_uri(self):
|
||||
if self.redirect_uri:
|
||||
return self.redirect_uri
|
||||
if self.http_status == 301 or self.http_status == 302:
|
||||
result = requests.head(self.url, allow_redirects=True, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
self.redirect_uri = result.url
|
||||
return result.url
|
||||
return None
|
||||
|
||||
def get_uri_domain(self):
|
||||
parsed = urlparse(self.url)
|
||||
return parsed.hostname
|
||||
|
||||
@classmethod
|
||||
def strip_url_params(cls, url):
|
||||
parsed = urlparse(url)
|
||||
return urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, '', parsed.fragment))
|
||||
|
||||
@property
|
||||
def tags_list(self):
|
||||
""" Get the tags as a list, iterable in template """
|
||||
if self.tags:
|
||||
return self.tags.split(',')
|
||||
return []
|
||||
|
||||
def to_dict(self):
|
||||
result = {
|
||||
'title': self.title,
|
||||
'url': self.url,
|
||||
'created': self.created_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'url_hash': self.url_hash,
|
||||
'tags': self.tags,
|
||||
}
|
||||
return result
|
||||
|
||||
@property
|
||||
def serialize(self):
|
||||
return self.to_dict()
|
||||
|
||||
|
||||
class PublicTag(BaseModel):
|
||||
""" Publicly shared tag """
|
||||
tagkey = CharField()
|
||||
userkey = CharField()
|
||||
tag = CharField()
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
def generate_key(self):
|
||||
""" Generate hash-based key for publicly shared tag """
|
||||
self.tagkey = binascii.hexlify(os.urandom(16))
|
||||
|
||||
|
||||
def get_tags_for_user(userkey):
|
||||
""" Extract all tags from the bookmarks """
|
||||
bookmarks = Bookmark.select().filter(Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE)
|
||||
tags = []
|
||||
for bookmark in bookmarks:
|
||||
tags += bookmark.tags_list
|
||||
return clean_tags(tags)
|
||||
|
||||
|
||||
def get_cached_tags(userkey):
|
||||
""" Fail-safe way to get the cached tags for `userkey` """
|
||||
try:
|
||||
|
||||
321
digimarks/models.py
Normal file
321
digimarks/models.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""digimarks data models and accompanying convenience functions"""
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import datetime
|
||||
|
||||
from peewee import * # noqa
|
||||
|
||||
from . import themes
|
||||
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from urllib.parse import urljoin, urlparse, urlunparse
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urlparse import urljoin, urlparse, urlunparse
|
||||
|
||||
|
||||
DATABASE_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
if 'DIGIMARKS_DB_PATH' in os.environ:
|
||||
DATABASE_PATH = os.environ['DIGIMARKS_DB_PATH']
|
||||
database = SqliteDatabase(os.path.join(DATABASE_PATH, 'bookmarks.db'))
|
||||
|
||||
|
||||
def ifilterfalse(predicate, iterable):
|
||||
# ifilterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
|
||||
if predicate is None:
|
||||
predicate = bool
|
||||
for x in iterable:
|
||||
if not predicate(x):
|
||||
yield x
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"List unique elements, preserving order. Remember all elements ever seen."
|
||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
if key is None:
|
||||
for element in ifilterfalse(seen.__contains__, iterable):
|
||||
seen_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
def clean_tags(tags_list):
|
||||
tags_res = [x.strip() for x in tags_list]
|
||||
tags_res = list(unique_everseen(tags_res))
|
||||
tags_res.sort()
|
||||
if tags_res and tags_res[0] == '':
|
||||
del tags_res[0]
|
||||
return tags_res
|
||||
|
||||
|
||||
magic_dict = {
|
||||
b"\x1f\x8b\x08": "gz",
|
||||
b"\x42\x5a\x68": "bz2",
|
||||
b"\x50\x4b\x03\x04": "zip"
|
||||
}
|
||||
|
||||
max_len = max(len(x) for x in magic_dict)
|
||||
|
||||
|
||||
def file_type(filename):
|
||||
with open(filename, "rb") as f:
|
||||
file_start = f.read(max_len)
|
||||
for magic, filetype in magic_dict.items():
|
||||
if file_start.startswith(magic):
|
||||
return filetype
|
||||
return "no match"
|
||||
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
database = database
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
""" User account """
|
||||
username = CharField()
|
||||
key = CharField()
|
||||
theme = CharField(default=themes.DEFAULT_THEME)
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
def generate_key(self):
|
||||
""" Generate userkey """
|
||||
self.key = binascii.hexlify(os.urandom(24))
|
||||
return self.key
|
||||
|
||||
|
||||
class Bookmark(BaseModel):
|
||||
""" Bookmark instance, connected to User """
|
||||
# Foreign key to User
|
||||
userkey = CharField()
|
||||
|
||||
title = CharField(default='')
|
||||
url = CharField()
|
||||
note = TextField(default='')
|
||||
#image = CharField(default='')
|
||||
url_hash = CharField(default='')
|
||||
tags = CharField(default='')
|
||||
starred = BooleanField(default=False)
|
||||
|
||||
# Website (domain) favicon
|
||||
favicon = CharField(null=True)
|
||||
|
||||
# Status code: 200 is OK, 404 is not found, for example (showing an error)
|
||||
HTTP_CONNECTIONERROR = 0
|
||||
HTTP_OK = 200
|
||||
HTTP_ACCEPTED = 202
|
||||
HTTP_MOVEDTEMPORARILY = 304
|
||||
HTTP_NOTFOUND = 404
|
||||
|
||||
http_status = IntegerField(default=200)
|
||||
redirect_uri = None
|
||||
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
modified_date = DateTimeField(null=True)
|
||||
deleted_date = DateTimeField(null=True)
|
||||
|
||||
# Bookmark status; deleting doesn't remove from DB
|
||||
VISIBLE = 0
|
||||
DELETED = 1
|
||||
status = IntegerField(default=VISIBLE)
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = (('created_date', 'desc'),)
|
||||
|
||||
def set_hash(self):
|
||||
""" Generate hash """
|
||||
self.url_hash = hashlib.md5(self.url.encode('utf-8')).hexdigest()
|
||||
|
||||
def set_title_from_source(self):
|
||||
""" Request the title by requesting the source url """
|
||||
try:
|
||||
result = requests.get(self.url, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
except:
|
||||
# For example 'MissingSchema: Invalid URL 'abc': No schema supplied. Perhaps you meant http://abc?'
|
||||
self.http_status = 404
|
||||
if self.http_status == 200 or self.http_status == 202:
|
||||
html = bs4.BeautifulSoup(result.text, 'html.parser')
|
||||
try:
|
||||
self.title = html.title.text.strip()
|
||||
except AttributeError:
|
||||
self.title = ''
|
||||
return self.title
|
||||
|
||||
def set_status_code(self):
|
||||
""" Check the HTTP status of the url, as it might not exist for example """
|
||||
try:
|
||||
result = requests.head(self.url, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
except requests.ConnectionError:
|
||||
self.http_status = self.HTTP_CONNECTIONERROR
|
||||
return self.http_status
|
||||
|
||||
def _set_favicon_with_iconsbetterideaorg(self, domain):
|
||||
""" Fetch favicon for the domain """
|
||||
fileextension = '.png'
|
||||
meta = requests.head(
|
||||
'http://icons.better-idea.org/icon?size=60&url=' + domain,
|
||||
allow_redirects=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT}
|
||||
)
|
||||
if meta.url[-3:].lower() == 'ico':
|
||||
fileextension = '.ico'
|
||||
response = requests.get(
|
||||
'http://icons.better-idea.org/icon?size=60&url=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT}
|
||||
)
|
||||
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + fileextension)
|
||||
with open(filename, 'wb') as out_file:
|
||||
shutil.copyfileobj(response.raw, out_file)
|
||||
del response
|
||||
filetype = file_type(filename)
|
||||
if filetype == 'gz':
|
||||
# decompress
|
||||
orig = gzip.GzipFile(filename, 'rb')
|
||||
origcontent = orig.read()
|
||||
orig.close()
|
||||
os.remove(filename)
|
||||
with open(filename, 'wb') as new:
|
||||
new.write(origcontent)
|
||||
self.favicon = domain + fileextension
|
||||
|
||||
def _set_favicon_with_realfavicongenerator(self, domain):
|
||||
""" Fetch favicon for the domain """
|
||||
response = requests.get(
|
||||
'https://realfavicongenerator.p.mashape.com/favicon/icon?platform=android_chrome&site=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT, 'X-Mashape-Key': settings.MASHAPE_API_KEY}
|
||||
)
|
||||
if response.status_code == 404:
|
||||
# Fall back to desktop favicon
|
||||
response = requests.get(
|
||||
'https://realfavicongenerator.p.mashape.com/favicon/icon?platform=desktop&site=' + domain,
|
||||
stream=True,
|
||||
headers={'User-Agent': DIGIMARKS_USER_AGENT, 'X-Mashape-Key': settings.MASHAPE_API_KEY}
|
||||
)
|
||||
# Debug for the moment
|
||||
print(domain)
|
||||
print(response.headers)
|
||||
if 'Content-Length' in response.headers and response.headers['Content-Length'] == '0':
|
||||
# No favicon found, likely
|
||||
print('Skipping this favicon, needs fallback')
|
||||
return
|
||||
# Default to 'image/png'
|
||||
fileextension = '.png'
|
||||
if response.headers['content-type'] == 'image/jpeg':
|
||||
fileextension = '.jpg'
|
||||
if response.headers['content-type'] == 'image/x-icon':
|
||||
fileextension = '.ico'
|
||||
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + fileextension)
|
||||
with open(filename, 'wb') as out_file:
|
||||
shutil.copyfileobj(response.raw, out_file)
|
||||
del response
|
||||
filetype = file_type(filename)
|
||||
if filetype == 'gz':
|
||||
# decompress
|
||||
orig = gzip.GzipFile(filename, 'rb')
|
||||
origcontent = orig.read()
|
||||
orig.close()
|
||||
os.remove(filename)
|
||||
with open(filename, 'wb') as new:
|
||||
new.write(origcontent)
|
||||
self.favicon = domain + fileextension
|
||||
|
||||
def set_favicon(self):
|
||||
""" Fetch favicon for the domain """
|
||||
u = urlparse(self.url)
|
||||
domain = u.netloc
|
||||
if os.path.isfile(os.path.join(MEDIA_ROOT, 'favicons/' + domain + '.png')):
|
||||
# If file exists, don't re-download it
|
||||
self.favicon = domain + '.png'
|
||||
return
|
||||
if os.path.isfile(os.path.join(MEDIA_ROOT, 'favicons/' + domain + '.ico')):
|
||||
# If file exists, don't re-download it
|
||||
self.favicon = domain + '.ico'
|
||||
return
|
||||
#self._set_favicon_with_iconsbetterideaorg(domain)
|
||||
self._set_favicon_with_realfavicongenerator(domain)
|
||||
|
||||
def set_tags(self, newtags):
|
||||
""" Set tags from `tags`, strip and sort them """
|
||||
tags_split = newtags.split(',')
|
||||
tags_clean = clean_tags(tags_split)
|
||||
self.tags = ','.join(tags_clean)
|
||||
|
||||
def get_redirect_uri(self):
|
||||
if self.redirect_uri:
|
||||
return self.redirect_uri
|
||||
if self.http_status == 301 or self.http_status == 302:
|
||||
result = requests.head(self.url, allow_redirects=True, headers={'User-Agent': DIGIMARKS_USER_AGENT})
|
||||
self.http_status = result.status_code
|
||||
self.redirect_uri = result.url
|
||||
return result.url
|
||||
return None
|
||||
|
||||
def get_uri_domain(self):
|
||||
parsed = urlparse(self.url)
|
||||
return parsed.hostname
|
||||
|
||||
@classmethod
|
||||
def strip_url_params(cls, url):
|
||||
parsed = urlparse(url)
|
||||
return urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, '', parsed.fragment))
|
||||
|
||||
@property
|
||||
def tags_list(self):
|
||||
""" Get the tags as a list, iterable in template """
|
||||
if self.tags:
|
||||
return self.tags.split(',')
|
||||
return []
|
||||
|
||||
def to_dict(self):
|
||||
result = {
|
||||
'title': self.title,
|
||||
'url': self.url,
|
||||
'created': self.created_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'url_hash': self.url_hash,
|
||||
'tags': self.tags.split(','),
|
||||
'favicon': self.favicon,
|
||||
'http_status': self.http_status,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
}
|
||||
return result
|
||||
|
||||
@property
|
||||
def serialize(self):
|
||||
return self.to_dict()
|
||||
|
||||
|
||||
class PublicTag(BaseModel):
|
||||
""" Publicly shared tag """
|
||||
tagkey = CharField()
|
||||
userkey = CharField()
|
||||
tag = CharField()
|
||||
created_date = DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
def generate_key(self):
|
||||
""" Generate hash-based key for publicly shared tag """
|
||||
self.tagkey = binascii.hexlify(os.urandom(16))
|
||||
|
||||
|
||||
def get_tags_for_user(userkey):
|
||||
""" Extract all tags from the bookmarks """
|
||||
bookmarks = Bookmark.select().filter(Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE)
|
||||
tags = []
|
||||
for bookmark in bookmarks:
|
||||
tags += bookmark.tags_list
|
||||
return clean_tags(tags)
|
||||
126
digimarks/themes.py
Normal file
126
digimarks/themes.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""digimarks theme definitions"""
|
||||
|
||||
DEFAULT_THEME = 'freshgreen'
|
||||
themes = {
|
||||
'green': {
|
||||
'BROWSERCHROME': '#2e7d32', # green darken-2
|
||||
'BODY': 'grey lighten-4',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'green darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#1b5e20', # green darken-4
|
||||
'BUTTON_ACTIVE': '#43a047', # green darken-1
|
||||
'LINK_TEXT': '#1b5e20', # green darken-4
|
||||
'CARD_BACKGROUND': 'green darken-3',
|
||||
'CARD_TEXT': 'white-text',
|
||||
'CARD_LINK': '#FFF', # white-text
|
||||
'CHIP_TEXT': '#1b5e20', # green darken-4
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'freshgreen': {
|
||||
'BROWSERCHROME': '#43a047', # green darken-1
|
||||
'BODY': 'grey lighten-5',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'green darken-1',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#1b5e20', # green darken-4
|
||||
'BUTTON_ACTIVE': '#43a047', # green darken-1
|
||||
'LINK_TEXT': '#1b5e20', # green darken-4
|
||||
'CARD_BACKGROUND': 'green darken-1',
|
||||
'CARD_TEXT': 'white-text',
|
||||
'CARD_LINK': '#FFF', # white-text
|
||||
'CHIP_TEXT': '#1b5e20', # green darken-4
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'lightblue': {
|
||||
'BROWSERCHROME': '#0288d1', # light-blue darken-2
|
||||
'BODY': 'white',
|
||||
'TEXT': 'black-text',
|
||||
'TEXTHEX': '#000',
|
||||
'NAV': 'light-blue darken-2',
|
||||
'PAGEHEADER': 'grey-text lighten-5',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#FFF', # white
|
||||
'CARD_BACKGROUND': 'light-blue lighten-2',
|
||||
'CARD_TEXT': 'black-text',
|
||||
'CARD_LINK': '#263238', # blue-grey-text darken-4
|
||||
'CHIP_TEXT': '#FFF', # white
|
||||
'FAB': 'light-blue darken-4',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'dark': {
|
||||
'BROWSERCHROME': '#212121', # grey darken-4
|
||||
'BODY': 'grey darken-4',
|
||||
'TEXT': 'grey-text lighten-1',
|
||||
'TEXTHEX': '#bdbdbd',
|
||||
'NAV': 'grey darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-1',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'CARD_BACKGROUND': 'grey darken-3',
|
||||
'CARD_TEXT': 'grey-text lighten-1',
|
||||
'CARD_LINK': '#fb8c00', # orange-text darken-1
|
||||
'CHIP_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
},
|
||||
'amoled': {
|
||||
'BROWSERCHROME': '#000', # grey darken-4
|
||||
'BODY': 'black',
|
||||
'TEXT': 'grey-text lighten-1',
|
||||
'TEXTHEX': '#bdbdbd',
|
||||
'NAV': 'grey darken-3',
|
||||
'PAGEHEADER': 'grey-text lighten-1',
|
||||
'MESSAGE_BACKGROUND': 'orange lighten-2',
|
||||
'MESSAGE_TEXT': 'white-text',
|
||||
'ERRORMESSAGE_BACKGROUND': 'red darken-1',
|
||||
'ERRORMESSAGE_TEXT': 'white-text',
|
||||
'BUTTON': '#fb8c00', # orange darken-1
|
||||
'BUTTON_ACTIVE': '#ffa726', # orange lighten-1
|
||||
'LINK_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'CARD_BACKGROUND': 'grey darken-3',
|
||||
'CARD_TEXT': 'grey-text lighten-1',
|
||||
'CARD_LINK': '#fb8c00', # orange-text darken-1
|
||||
'CHIP_TEXT': '#fb8c00', # orange-text darken-1
|
||||
'FAB': 'red',
|
||||
|
||||
'STAR': 'yellow-text',
|
||||
'PROBLEM': 'red-text',
|
||||
'COMMENT': '',
|
||||
}
|
||||
}
|
||||
|
||||
2
digimarks/views.py
Normal file
2
digimarks/views.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""digimarks views"""
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
astroid==2.0.4 # via pylint
|
||||
beautifulsoup4==4.6.3 # via bs4
|
||||
bs4==0.0.1
|
||||
certifi==2018.8.24 # via requests
|
||||
certifi==2018.10.15 # via requests
|
||||
chardet==3.0.4 # via requests
|
||||
click==6.7 # via flask
|
||||
click==7.0 # via flask
|
||||
flask==1.0.2
|
||||
idna==2.7 # via requests
|
||||
isort==4.3.4 # via pylint
|
||||
itsdangerous==0.24 # via flask
|
||||
itsdangerous==1.1.0 # via flask
|
||||
jinja2==2.10 # via flask
|
||||
lazy-object-proxy==1.3.1 # via astroid
|
||||
markupsafe==1.0 # via jinja2
|
||||
markupsafe==1.1.0 # via jinja2
|
||||
mccabe==0.6.1 # via pylint
|
||||
peewee==3.7.0
|
||||
peewee==3.7.1
|
||||
pylint==2.1.1
|
||||
requests==2.19.1
|
||||
requests==2.20.1
|
||||
six==1.11.0 # via astroid
|
||||
typed-ast==1.1.0 # via astroid
|
||||
urllib3==1.23 # via requests
|
||||
urllib3==1.24.1 # via requests
|
||||
werkzeug==0.14.1 # via flask
|
||||
wrapt==1.10.11 # via astroid
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
#
|
||||
beautifulsoup4==4.6.3 # via bs4
|
||||
bs4==0.0.1
|
||||
certifi==2018.8.24 # via requests
|
||||
certifi==2018.10.15 # via requests
|
||||
chardet==3.0.4 # via requests
|
||||
click==6.7 # via flask
|
||||
click==7.0 # via flask
|
||||
flask==1.0.2
|
||||
idna==2.7 # via requests
|
||||
itsdangerous==0.24 # via flask
|
||||
itsdangerous==1.1.0 # via flask
|
||||
jinja2==2.10 # via flask
|
||||
markupsafe==1.0 # via jinja2
|
||||
peewee==3.7.0
|
||||
requests==2.19.1
|
||||
urllib3==1.23 # via requests
|
||||
markupsafe==1.1.0 # via jinja2
|
||||
peewee==3.7.1
|
||||
requests==2.20.1
|
||||
urllib3==1.24.1 # via requests
|
||||
werkzeug==0.14.1 # via flask
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* digimarks styling
|
||||
*/
|
||||
|
||||
/** Navigation **/
|
||||
|
||||
nav .sidenav-trigger
|
||||
{
|
||||
/* Fix for misalignment of hamburger icon */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav .sidenav-trigger i
|
||||
{
|
||||
/* Make the hamburger icon great again */
|
||||
font-size: 2.7rem;
|
||||
}
|
||||
|
||||
/** Cards and tags **/
|
||||
|
||||
.card .card-content,
|
||||
.card .card-reveal
|
||||
{
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card.tiny
|
||||
{
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card.tiny .card-title
|
||||
{
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card .card-reveal .digimark-card-header,
|
||||
.card .digimark-card-header.activator,
|
||||
.chip.clickable
|
||||
{
|
||||
cursor: pointer;
|
||||
/*display: block;*/
|
||||
}
|
||||
|
||||
.card .digimark-card-header-tags
|
||||
{
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.card-image
|
||||
{
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.card-image i,
|
||||
.list-image i
|
||||
{
|
||||
padding: 5px 0 0 15px;
|
||||
}
|
||||
|
||||
.card.horizontal .card-image img.favicon,
|
||||
.list-image img.favicon
|
||||
{
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 254 B |
@@ -1,11 +0,0 @@
|
||||
/* global M */
|
||||
|
||||
var options = {};
|
||||
var elem = document.querySelector(".sidenav");
|
||||
var instance = M.Sidenav.init(elem, options);
|
||||
|
||||
elem = document.querySelector(".collapsible");
|
||||
instance = M.Collapsible.init(elem, {
|
||||
// inDuration: 1000,
|
||||
// outDuration: 1000
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}404: Page not found{% endblock %}
|
||||
{% block pageheader %}404: Page not found{% endblock %}
|
||||
{% block pagecontent %}
|
||||
The page you requested was not found.
|
||||
{% endblock %}
|
||||
@@ -1,126 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %} - digimarks</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0"/>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"/>
|
||||
|
||||
<!-- Chrome, Firefox OS and Opera -->
|
||||
<meta name="theme-color" content="{{ theme.BROWSERCHROME }}" />
|
||||
<!-- Windows Phone -->
|
||||
<meta name="msapplication-navbutton-color" content="{{ theme.BROWSERCHROME }}">
|
||||
<!-- iOS Safari -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
||||
<meta name="referrer" content="never">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto+Mono&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
|
||||
<style>
|
||||
a
|
||||
{
|
||||
color: {{ theme.LINK_TEXT }};
|
||||
}
|
||||
.card-content a
|
||||
{
|
||||
color: {{ theme.CARD_LINK }};
|
||||
}
|
||||
.chip a
|
||||
{
|
||||
color: {{ theme.CHIP_TEXT }};
|
||||
}
|
||||
/* label color */
|
||||
.input-field .prefix ~ input, .input-field .prefix ~ textarea, .input-field .prefix ~ label, .input-field .prefix ~ .validate ~ label, .input-field .prefix ~ .autocomplete-content, .input-field input[type=text]
|
||||
{
|
||||
color: {{ theme.TEXTHEX }};
|
||||
}
|
||||
/* label focus color */
|
||||
.input-field input[type=text]:focus + label,
|
||||
.input-field .prefix ~ input[type=text]:focus + label
|
||||
{
|
||||
color: {{ theme.BUTTON }};
|
||||
}
|
||||
/* label underline focus color */
|
||||
.input-field input[type=text]:focus,
|
||||
.input-field .prefix ~ input[type=text]:focus,
|
||||
.input-field input[type=text].autocomplete:focus
|
||||
{
|
||||
border-bottom: 1px solid {{ theme.BUTTON }};
|
||||
box-shadow: 0 1px 0 0 {{ theme.BUTTON }};
|
||||
}
|
||||
/* icon prefix focus color */
|
||||
.input-field .prefix.active
|
||||
{
|
||||
color: {{ theme.BUTTON }};
|
||||
}
|
||||
.btn, .btn:visited
|
||||
{
|
||||
background-color: {{ theme.BUTTON }};
|
||||
}
|
||||
.btn:hover, .btn:active
|
||||
{
|
||||
background-color: {{ theme.BUTTON_ACTIVE }};
|
||||
}
|
||||
.deletebtn
|
||||
{
|
||||
background-color: red;
|
||||
}
|
||||
.deletebtn:hover
|
||||
{
|
||||
background-color: #ef5350; /* red lighten-1 */
|
||||
}
|
||||
</style>
|
||||
<link href="{{ url_for('static', filename='css/digimarks.css') }}?20180330" type="text/css" rel="stylesheet" media="screen,projection"/>
|
||||
{% if not sortmethod %}
|
||||
{% set sortmethod = None %}
|
||||
{% endif %}
|
||||
{% if not show_as %}
|
||||
{% set show_as = None %}
|
||||
{% endif %}
|
||||
</head>
|
||||
<body class="{{ theme.BODY }} {{ theme.TEXT }}">
|
||||
<nav class="{{ theme.NAV }}" role="navigation">
|
||||
<div class="nav-wrapper container"><a id="logo-container" href="{% if userkey %}{{ url_for('bookmarks_page', userkey=userkey, sortmethod=sortmethod, show_as=show_as) }}{% else %}{{ url_for('index') }}{% endif %}" class="brand-logo">digimarks</a>
|
||||
<ul class="right hide-on-med-and-down">
|
||||
{% if userkey %}
|
||||
<li><a href="{{ url_for('tags_page', userkey=userkey) }}" class="waves-effect waves-light btn"><i class="material-icons left">label</i>Tags</a></li>
|
||||
<li><a href="{{ url_for('addbookmark', userkey=userkey) }}" class="waves-effect waves-light btn"><i class="material-icons left">add</i>Add bookmark</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if userkey %}
|
||||
<ul id="nav-mobile" class="sidenav">
|
||||
<li><a class="waves-effect" href="{{ url_for('bookmarks_page', userkey=userkey) }}"><i class="material-icons left">turned_in</i>Home</a></li>
|
||||
<li><a class="waves-effect" href="{{ url_for('tags_page', userkey=userkey) }}"><i class="material-icons left">label</i>Tags</a></li>
|
||||
<li><a class="waves-effect" href="{{ url_for('addbookmark', userkey=userkey) }}"><i class="material-icons left">add</i>Add bookmark</a></li>
|
||||
</ul>
|
||||
<a href="#" data-target="nav-mobile" class="sidenav-trigger"><i class="material-icons">menu</i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<div class="header {{ theme.PAGEHEADER }}">
|
||||
<h1>{% block pageheader %}Bookmarks{% endblock %}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
{% block pagecontent %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/init.js') }}?20180309"></script>
|
||||
|
||||
{% block extrajs %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,113 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% if not action %}
|
||||
{% set action = 'Bookmarks' %}
|
||||
{% endif %}
|
||||
{% block title %}{{ action }}{% endblock %}
|
||||
{% block pageheader %}{{ action }}{% endblock %}
|
||||
{% block pagecontent %}
|
||||
|
||||
{% if tag and not publictag %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<a href="{{ url_for('addpublictag', userkey=userkey, tag=tag) }}">Create public page <i class="material-icons right">tag</i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if tag and publictag %}
|
||||
<div class="row">
|
||||
<div class="col s12"><a href="{{ url_for('publictag_page', tagkey=publictag.tagkey) }}">Public link</a></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if message %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel {{ theme.MESSAGE_BACKGROUND }}">
|
||||
<span class="{{ theme.MESSAGE_TEXT }}">
|
||||
{{ message|safe }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<form action="{{ url_for('bookmarks_page', userkey=userkey) }}" name="filterForm" method="POST" autocomplete="off">
|
||||
<div class="input-field col l9 m9 s8">
|
||||
<input placeholder="search text" type="text" name="filter_text" id="filter_text" class="autocomplete" value="{{ filter_text }}" autocomplete="false" />
|
||||
</div>
|
||||
|
||||
<div class="input-field col l3 m3 s4">
|
||||
<p class="right-align"><button class="btn waves-effect waves-light" type="submit" name="submitBtn" title="Find"><i class="material-icons">search</i></button>
|
||||
{% if show_as and show_as == 'list' %}
|
||||
<a href="{{ url_for('bookmarks_page', userkey=userkey, filtermethod=filtermethod, sortmethod=sortmethod, show_as=None) }}" class="waves-effect waves-light btn" title="Show as cards"><i class="material-icons">apps</i></a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('bookmarks_page', userkey=userkey, filtermethod=filtermethod, sortmethod=sortmethod, show_as='list') }}" class="waves-effect waves-light btn" title="Show as list"><i class="material-icons">reorder</i></a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if tags %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<ul class="collapsible" data-collapsible="expandable">
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">label</i>Filter on star/problem/comment/tag</div>
|
||||
<div class="collapsible-body" style="padding: 10px;">
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('bookmarks_page', userkey=userkey, filtermethod='starred') }}"><i class="tiny material-icons {{ theme.STAR }}">star</i></a>
|
||||
</div>
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('bookmarks_page', userkey=userkey, filtermethod='broken') }}"><i class="tiny material-icons {{ theme.PROBLEM }}">report_problem</i></a>
|
||||
</div>
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('bookmarks_page', userkey=userkey, filtermethod='note') }}"><i class="tiny material-icons {{ theme.COMMENT }}">comment</i></a>
|
||||
</div>
|
||||
{% for tag in tags %}
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('tag_page', userkey=userkey, tag=tag) }}">{{ tag }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if show_as and show_as == 'list' %}
|
||||
{% include 'list.html' %}
|
||||
{% else %}
|
||||
{% include 'cards.html' %}
|
||||
{% endif %}
|
||||
|
||||
<div class="fixed-action-btn" style="bottom: 20px; right: 20px;">
|
||||
<a class="btn-floating btn-large {{ theme.FAB }}" href="{{ url_for('addbookmark', userkey=userkey) }}">
|
||||
<i class="large material-icons">add</i>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extrajs %}
|
||||
<script>
|
||||
function submitFilter() {
|
||||
document.filterForm.submit();
|
||||
}
|
||||
/* Search filter autocomplete */
|
||||
var options = {
|
||||
onAutocomplete: submitFilter,
|
||||
minLength: 3,
|
||||
limit: 10,
|
||||
data: {
|
||||
},
|
||||
}
|
||||
var elem = document.querySelector('.autocomplete');
|
||||
var instance = M.Autocomplete.init(elem, options);
|
||||
/* TODO: fetch from API
|
||||
instance.updateData({
|
||||
});
|
||||
*/
|
||||
</script>
|
||||
<script src="{{ url_for('bookmarks_js', userkey=userkey) }}" ></script>
|
||||
{% endblock %}
|
||||
@@ -1,11 +0,0 @@
|
||||
var elem = document.querySelector('.autocomplete');
|
||||
var instance = M.Autocomplete.getInstance(elem);
|
||||
instance.updateData({
|
||||
{% for bookmark in bookmarks %}
|
||||
{% if bookmark.favicon %}
|
||||
"{{ bookmark.title | replace('"', '\\"') | replace('\n', '') | replace('\r', '') }}": "{{ url_for('static', filename='favicons/' + bookmark.favicon) }}",
|
||||
{% else %}
|
||||
"{{ bookmark.title | replace('"', '\\"') | replace('\n', '') | replace('\r', '') }}": null,
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
<div class="row">
|
||||
{% for bookmark in bookmarks %}
|
||||
<div class="col s12 m6 l4">
|
||||
<div class="card horizontal tiny {{ theme.CARD_BACKGROUND }}">
|
||||
<div class="card-image">
|
||||
{% if bookmark.favicon %}
|
||||
<div><img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" class="favicon" /></div>
|
||||
{% else %}
|
||||
<div><img src="{{ url_for('static', filename='faviconfallback.png') }}" class="favicon" /></div>
|
||||
{% endif %}
|
||||
{% if bookmark.http_status != 200 and bookmark.http_status != 304 %}
|
||||
<div><i class="small material-icons {{ theme.PROBLEM }}" title="HTTP status {{ bookmark.http_status }}">report_problem</i></div>
|
||||
{% endif %}
|
||||
{% if bookmark.starred == True %}
|
||||
<div><i class="small material-icons {{ theme.STAR }}">star</i></div>
|
||||
{% endif %}
|
||||
{% if bookmark.note %}
|
||||
<div><i class="small material-icons {{ theme.CARD_TEXT }}" title="{{ bookmark.note|truncate(100) }}">comment</i></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-stacked">
|
||||
<div class="card-content {{ theme.CARD_TEXT }}">
|
||||
<span class="digimark-card-header activator">
|
||||
<i class="material-icons right">more_vert</i>
|
||||
</span>
|
||||
<div class="digimark-card-content">
|
||||
<a href="{{ bookmark.url }}" title="{{ bookmark.url }}" rel="noreferrer noopener" target="_blank">
|
||||
{% if bookmark.title %}
|
||||
{{ bookmark.title }}
|
||||
{% else %}
|
||||
{{ bookmark.get_uri_domain() }} (no title)
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-reveal {{ theme.CARD_BACKGROUND }}">
|
||||
<span class="card-title {{ theme.CARD_TEXT }}">Added @ {{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}<i class="material-icons right">close</i></span>
|
||||
{% if editable %}
|
||||
<div class="{{ theme.CARD_TEXT }}" style="padding-top: 10px;">
|
||||
<a href="{{ url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" style="padding: 3px"><i class="tiny material-icons">mode_edit</i> EDIT</a>
|
||||
<a href="{{ url_for('deletingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" style="padding: 3px" class="red-text"><i class="tiny material-icons">delete</i> DELETE</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if showtags %}
|
||||
<div class="digimark-card-header-tags">
|
||||
{% for tag in bookmark.tags_list %}
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('tag_page', userkey=userkey, tag=tag) }}">{{ tag }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{#
|
||||
<div class="pagination">
|
||||
{% if page > 1 %}<a href="./?page={{ page - 1 }}">Previous</a>{% endif %}
|
||||
{% if pagination.get_pages() > page %}<a href="./?page={{ page + 1 }}">Next</a>{% endif %}
|
||||
</div>
|
||||
#}
|
||||
</div>
|
||||
@@ -1,175 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ action }}{% endblock %}
|
||||
{% block pageheader %}{{ action }}{% endblock %}
|
||||
{% block pagecontent %}
|
||||
|
||||
{% if bookmark.http_status != 200 and bookmark.http_status != 202 and bookmark.http_status != 304 %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel {{ theme.ERRORMESSAGE_BACKGROUND }}">
|
||||
<span class="{{ theme.ERRORMESSAGE_TEXT }}">
|
||||
{% if bookmark.http_status == 404 %}
|
||||
<i class="material-icons">report_problem</i> URL not found (404), broken/outdated link?
|
||||
{% elif bookmark.http_status == 301 %}
|
||||
<i class="material-icons">report_problem</i> HTTP status (301), moved permanently. Use button for new target
|
||||
{% elif bookmark.http_status == 302 %}
|
||||
<i class="material-icons">report_problem</i> HTTP status (302), moved temporarily. Use button for new target
|
||||
{% elif bookmark.http_status == bookmark.HTTP_CONNECTIONERROR %}
|
||||
<i class="material-icons">report_problem</i> Connection error, server might have been offline at the time of last edit
|
||||
{% else %}
|
||||
<i class="material-icons">report_problem</i> HTTP status {{ bookmark.http_status }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if message %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel {{ theme.MESSAGE_BACKGROUND }}">
|
||||
<span class="{{ theme.MESSAGE_TEXT }}">
|
||||
{{ message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if formaction and formaction == 'edit' %}
|
||||
<form class="digimark" id="digimark" action="{{ url_for('editingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST" onsubmit="return onSubmitForm();" autocomplete="off">
|
||||
{% else %}
|
||||
<form class="digimark" id="digimark" action="{{ url_for('addingbookmark', userkey=userkey) }}" method="POST" onsubmit="return onSubmitForm();" autocomplete="off">
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">description</i>
|
||||
<input placeholder="title (leave empty for autofetch)" type="text" name="title" id="title" value="{{ bookmark.title }}" autocomplete="false" />
|
||||
<label for="title">Title</label>
|
||||
{# <span class="helper-text">Leave title empty for autofetching from the page</span>#}
|
||||
</div>
|
||||
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">turned_in</i>
|
||||
<input placeholder="url" type="text" name="url" id="url" value="{{ bookmark.url }}" autocomplete="false" />
|
||||
<label for="url">URL</label>
|
||||
{% if bookmark.get_redirect_uri() %}
|
||||
<div>
|
||||
<a class="waves-effect waves-light btn" id="btn_urlupdate" onclick="updateURL()"><i class="material-icons left">turned_in</i>{{ bookmark.get_redirect_uri() }}</a>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function updateURL() {
|
||||
var text = document.getElementById('url');
|
||||
text.value = '{{ bookmark.get_redirect_uri() }}';
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">comment</i>
|
||||
<input placeholder="note" type="text" name="note" id="note" value="{{ bookmark.note }}" autocomplete="false" />
|
||||
<label for="note">Note</label>
|
||||
</div>
|
||||
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">label</i>
|
||||
<input placeholder="tags, divided by comma's" type="text" name="tags" id="tags" value="{{ bookmark.tags }}" autocomplete="false" />
|
||||
<label for="tags">Tags</label>
|
||||
</div>
|
||||
</div>
|
||||
{% if tags %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<ul class="collapsible" data-collapsible="expandable">
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">label</i>Existing tags</div>
|
||||
<div class="collapsible-body" style="padding: 10px;">
|
||||
{% for tag in tags %}
|
||||
<div class="chip clickable" id="chip_{{ tag }}" onclick="addTag('{{ tag }}');">
|
||||
{{ tag }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col s12">
|
||||
{#<i class="material-icons prefix">star</i>#}
|
||||
<label>
|
||||
<input type="checkbox" name="starred" id="starred" {% if bookmark.starred == True %}checked{% endif %} />
|
||||
<span>Starred</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<label>
|
||||
<input type="checkbox" name="strip" id="strip" />
|
||||
<span>Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{% if bookmark.url_hash %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col l4 m6 s12">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Added</th>
|
||||
<td>{{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% if bookmark.modified_date %}
|
||||
<tr>
|
||||
<th>Modified</th>
|
||||
<td>{{ bookmark.modified_date.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if bookmark.deleted_date %}
|
||||
<tr>
|
||||
<th>Deleted</th>
|
||||
<td>{{ bookmark.deleted_date.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% endif %}
|
||||
|
||||
<div class="input-field col l2 m3 s4">
|
||||
<p class="left-align"><button class="btn waves-effect waves-light" type="submit" name="submit" id="submit">Save <i class="material-icons right">send</i></button></p>
|
||||
</div>
|
||||
{% if bookmark.url_hash %}
|
||||
</form>
|
||||
<div class="input-field col l4 m4 s6">
|
||||
<form action="{{ url_for('deletingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST">
|
||||
<p class="left-align"><button class="btn waves-effect waves-light deletebtn" type="submit" name="delete">Delete <i class="material-icons right">delete</i></button></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function onSubmitForm()
|
||||
{
|
||||
var theForm = document.getElementById('digimark');
|
||||
var submitButton = document.getElementById('submit');
|
||||
theForm.onsubmit = submitButton.setAttribute("disabled", true);
|
||||
return true;
|
||||
}
|
||||
function addTag(tagText)
|
||||
{
|
||||
var text = document.getElementById('tags');
|
||||
text.value = text.value + ', ' + tagText;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,17 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}digimarks{% endblock %}
|
||||
{% block pageheader %}digimarks{% endblock %}
|
||||
{% block pagecontent %}
|
||||
<p>Please visit your personal url, or <a href="https://github.com/aquatix/digimarks">see the digimarks project page</a>.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel orange lighten-2">
|
||||
<span class="black-text">
|
||||
If you forgot/lost your personal url, contact your digimarks administrator. On startup, the personal codes are printed to the standard output (so should be findable in a log). Of course, bookmarks.db contains the user information too.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,62 +0,0 @@
|
||||
<div class="row">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Bookmark</th>
|
||||
<th>Added</th>
|
||||
{% if showtags %}
|
||||
<th>Tags</th>
|
||||
{% endif %}
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bookmark in bookmarks %}
|
||||
<tr>
|
||||
<td class="list-image">
|
||||
{% if bookmark.favicon %}
|
||||
<img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" class="favicon" />
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='faviconfallback.png') }}" class="favicon" />
|
||||
{% endif %}
|
||||
{% if bookmark.http_status != 200 and bookmark.http_status != 304 %}
|
||||
<i class="small material-icons {{ theme.PROBLEM }}" title="HTTP status {{ bookmark.http_status }}">report_problem</i>
|
||||
{% endif %}
|
||||
{% if bookmark.starred == True %}
|
||||
<i class="small material-icons {{ theme.STAR }}">star</i>
|
||||
{% endif %}
|
||||
{% if bookmark.note %}
|
||||
<i class="small material-icons {{ theme.CARD_TEXT }}" title="{{ bookmark.note|truncate(100) }}">comment</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ bookmark.url }}" title="{{ bookmark.url }}" rel="noreferrer noopener" target="_blank">
|
||||
{% if bookmark.title %}
|
||||
{{ bookmark.title }}
|
||||
{% else %}
|
||||
{{ bookmark.get_uri_domain() }} (no title)
|
||||
{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
{% if showtags %}
|
||||
<td>
|
||||
{% for tag in bookmark.tags_list %}
|
||||
<div class="chip">
|
||||
<a href="{{ url_for('tag_page', userkey=userkey, tag=tag) }}">{{ tag }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if editable %}
|
||||
<a href="{{ url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" class="waves-effect waves-light btn" title="Edit"><i class="tiny material-icons">mode_edit</i></a>
|
||||
<a href="{{ url_for('deletingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" class="waves-effect waves-light btn red" title="DELETE"><i class="tiny material-icons">delete</i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% if not action %}
|
||||
{% set action = 'Bookmarks' %}
|
||||
{% endif %}
|
||||
{% block title %}{{ action }}{% endblock %}
|
||||
{% block pageheader %}{{ action }}{% endblock %}
|
||||
{% block pagecontent %}
|
||||
|
||||
{% if message %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel orange lighten-2">
|
||||
<span class="white-text">
|
||||
{{ message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<a href="{{ url_for('publictag_feed', tagkey=tagkey) }}"><i class="material-icons tiny">rss_feed</i> feed</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'cards.html' %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,16 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirecting - digimarks</title>
|
||||
<meta name="referrer" content="never">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta http-equiv=refresh content="3; URL={{ url }}">
|
||||
<style>
|
||||
body { background-color: #000; color: #FFF; }
|
||||
a { color: #fb8c00; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>You're being redirected. If nothing happens, <a href="{{ url }}">click here instead</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,65 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Tags{% endblock %}
|
||||
{% block pageheader %}Tags{% endblock %}
|
||||
{% block pagecontent %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col s12">
|
||||
<table class="centered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="material-icons" title="Unique labels">label</i></th>
|
||||
<th><i class="material-icons green-text" title="Public tag pages">present_to_all</i></th>
|
||||
<th><i class="material-icons" title="Total bookmarks">turned_in</i></th>
|
||||
<th><i class="material-icons" title="Bookmarks with notes">comment</i></th>
|
||||
<th><i class="material-icons yellow-text" title="Starred bookmarks">star</i></th>
|
||||
<th><i class="material-icons orange-text" title="HTTP status is not 200 OK">warning</i></th>
|
||||
<th><i class="material-icons red-text" title="Deleted bookmarks">delete</i></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ totaltags }}</td>
|
||||
<td>{{ totalpublic }}</td>
|
||||
<td>{{ totalbookmarks }}</td>
|
||||
<td>{{ totalnotes }}</td>
|
||||
<td>{{ totalstarred }}</td>
|
||||
<td>{{ totalhttperrorstatus }}</td>
|
||||
<td>{{ totaldeleted }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<th>Public link</th>
|
||||
<th>Number of bookmarks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tag in tags %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('tag_page', userkey=userkey, tag=tag['tag']) }}">{{ tag['tag'] }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if tag['publictag'] %}
|
||||
<a href="{{ url_for('publictag_page', tagkey=tag['publictag'].tagkey) }}">Public link</a> (<a href="{{ url_for('removepublictag', tag=tag['tag'], tagkey=tag['publictag'].tagkey, userkey=userkey) }}">Delete</a> <i class="tiny material-icons red-text">warning</i>)
|
||||
{% else %}
|
||||
<a href="{{ url_for('addpublictag', userkey=userkey, tag=tag['tag']) }}">Create</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ tag['total'] }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user