1
0
mirror of https://github.com/aquatix/digimarks.git synced 2025-12-06 22:05:09 +01:00

110 Commits

Author SHA1 Message Date
abf9141019 Release 1.1.0 2017-07-22 22:09:42 +02:00
Michiel Scholten
8ca54bf364 Merge pull request #7 from aquatix/theme
Theme
2017-07-22 21:55:03 +02:00
0be05d07e9 Default theme is now a constant; linting fixes 2017-07-22 21:51:07 +02:00
a66360a0e3 Note on the added DB field 2017-07-22 21:44:06 +02:00
1545c17472 Moved label colours to template/theme; added freshgreen and dark 2017-07-22 21:42:12 +02:00
dd0a9d8283 Typo 2017-07-22 21:16:33 +02:00
652e3a89af Theme support, with default 'green' 2017-07-22 21:11:09 +02:00
133a139b79 Merge branch 'master' into theme 2017-07-22 20:36:05 +02:00
b556978640 Updated MaterializeCSS 2017-07-22 19:39:52 +02:00
8bb9e11088 More changes 2017-07-05 12:45:09 +02:00
85e94b71e5 Some reformatting according to keepachangelog.com 2017-07-05 12:32:00 +02:00
a895774001 Changes 2017-07-03 15:08:50 +02:00
7598ba6a61 Updated requirements 2017-07-03 15:07:45 +02:00
c5dcf579d3 Updated MaterializeCSS 2017-07-03 15:07:19 +02:00
590095659e 20170519: default to 'green' 2017-06-15 08:41:01 +02:00
d5d71b4f51 Cache User settings 2017-04-28 16:33:42 +02:00
7f2da49d26 Update Materialize to 0.98.2 2017-04-26 15:22:20 +02:00
0f0e4e03c3 Small requirements updates 2017-04-26 15:20:23 +02:00
044f507aa2 Started with theming support 2017-04-15 09:01:49 +02:00
b0da3a4454 Updated Materialize and jQuery 2017-03-22 12:34:33 +01:00
17e1db12bf Minor requirements updates 2017-03-22 12:32:57 +01:00
480f27797f requirements updates 2017-03-11 21:32:32 +01:00
1729ecd540 Add pkg-resources==0.0.0 as otherwise venv will break 2017-03-01 21:06:59 +01:00
d54b26dafb Updated requirements 2017-03-01 21:03:16 +01:00
2c76ffaf12 Cache buster to force loading of the latest styling 2017-02-04 10:28:07 +01:00
e54a033984 Indentation and styling fixes, comments 2017-02-04 10:27:05 +01:00
4e0d89c25e Better size for the hamburger icon 2017-02-04 10:24:39 +01:00
30e4f74ed9 Fix for misalignment of hamburger icon 2017-02-04 09:20:42 +01:00
1beadb676c Updated MaterializeCSS 2017-02-04 09:20:40 +01:00
eea63398ba Try-out of scrolling tags at 2016-11-07, did not work out yet 2017-02-04 09:18:32 +01:00
d5021fd7c4 Try-out of scrolling tags at 2016-11-07, did not work out yet 2017-02-04 09:18:10 +01:00
c03d0c2458 Recompiled requirements, lowercase package names 2017-01-24 09:48:09 +01:00
ff08ea4930 Explicit requirements through pip-compile from pip-tools 2017-01-16 12:01:08 +01:00
2e9457ee1a Catch case where favicon could not be saved 2017-01-16 11:38:43 +01:00
63dd636c25 Show 404 page if bookmark is not found when editing 2017-01-16 11:22:56 +01:00
412b4a93c7 Fixed indention 2016-12-29 20:59:45 +01:00
abdc11361a Added missing itertools function 2016-12-29 20:59:04 +01:00
Michiel Scholten
7a98de4b3f Merge pull request #3 from jelmer/optional-venv
Make running in a virtualenv optional.
2016-12-29 20:49:16 +01:00
Michiel Scholten
59f5365edc Merge pull request #2 from jelmer/utilkit
Drop dependency on utilkit.
2016-12-29 20:48:42 +01:00
Michiel Scholten
b0aedcd6e4 Merge pull request #1 from jelmer/unique-everseen
Avoid dependency on more_itertools.
2016-12-29 20:47:32 +01:00
6b61e43c5d Upcoming changes 2016-12-29 17:01:55 +01:00
edc1a86b75 Rolled latest changes into a version 1.0.0 release 2016-12-29 16:26:24 +01:00
Jelmer Vernooij
570af2d62e Make running in a virtualenv optional. 2016-11-28 23:34:28 +00:00
Jelmer Vernooij
8a19df2741 Drop dependency on utilkit.
utilkit is only used for one one-liner function.
2016-11-28 23:30:51 +00:00
Jelmer Vernooij
9e6d67a0ff Avoid dependency on more_itertools.
Instead, use version of unique_everseen as seen in itertools
documentation.
2016-11-28 23:25:50 +00:00
698a417bac Removed workaround for fix released upstream 2016-11-07 21:29:49 +01:00
4cdea81f16 Updated MaterializeCSS and jQuery 2016-10-31 16:11:53 +01:00
0e659a4f16 Fixes for public cards 2016-09-23 13:50:49 +02:00
2cdb0ebccb Put the tag selection in a collapsible element to prevent clutter 2016-09-14 12:50:37 +02:00
9e2bdac9ee Open in new tab/window, prevent
http://davidebove.com/blog/2016/05/05/target_blank-the-vulnerability-in-your-browser/
2016-09-13 09:53:20 +02:00
5288c9fe9c Clear error message on connection error 2016-09-05 15:21:42 +02:00
0e60f8f3cb Fix for the error catching 2016-09-05 15:18:47 +02:00
5fd8db8f43 Catch connection timeouts and such 2016-09-05 15:14:11 +02:00
0894770f12 Show url domain name along with 'no title' for items without title 2016-08-29 13:49:29 +02:00
197cb776bf Helpful texts 2016-08-26 16:54:56 +02:00
c433c2c1e8 If redirect detected, button with final uri 2016-08-26 16:45:56 +02:00
b5d1377109 Changes, todos 2016-08-19 14:13:02 +02:00
85d4466de5 Filter on bookmarks with a note 2016-08-19 14:10:21 +02:00
7609becf00 More logical order of fields, tags with tags 2016-08-19 13:41:20 +02:00
a6d6ce5c13 Show stats on notes 2016-08-17 22:15:27 +02:00
e448f69f38 Count notes 2016-08-17 16:48:06 +02:00
17c36ab8fe Note about notes 2016-08-16 21:58:49 +02:00
9a15a9946c Different note icon, was same as title 2016-08-16 21:44:31 +02:00
40b7937876 Better alignment of icons in the cards 2016-08-16 21:42:10 +02:00
38155cafaa Actually show the note in title, but truncate to be safe 2016-08-16 21:34:03 +02:00
b12974fc31 Workaround for when an existing bookmark has a null note 2016-08-16 21:29:01 +02:00
d39d793460 Support for notes 2016-08-16 21:24:40 +02:00
7c24057d35 On tag filter, also use the cached tags 2016-08-15 16:03:54 +02:00
5fde397c4a Sort reverse chronologically on tag pages too 2016-08-15 16:02:12 +02:00
62117699f8 Don't show empty mobile menu on welcome page, add Home button 2016-08-15 14:34:31 +02:00
14052148f7 Prettify the mobile nav with waves and icons 2016-08-15 14:22:35 +02:00
fe44928816 Delete link/button in card is red now 2016-08-15 14:11:23 +02:00
e5c3824034 Fix for card reveal not working in Firefox 2016-08-15 10:51:59 +02:00
d8850d33f4 Test case for card not working in Firefox 2016-08-14 20:54:07 +02:00
2117c451b2 Filter text fixes 2016-08-14 14:43:11 +02:00
9abc17d517 Star/broken status filter 2016-08-14 14:39:45 +02:00
978c34fcf3 Filter on 'star' status, 'broken' status (non-http-200-OK) 2016-08-14 14:38:20 +02:00
7fdc0706bf Changes 2016-08-14 14:06:47 +02:00
c460f713ca Stats on bookmarks without http status 200 OK 2016-08-14 13:19:26 +02:00
2de7472e27 'Delete' button in action cardback 2016-08-13 20:50:24 +02:00
a6082caf0d Undo delete bookmark. Enables unescaped rendering of message 2016-08-13 20:46:09 +02:00
ac0ac849cb Whitespace good 2016-08-13 20:34:33 +02:00
390cc0137a Cleaner stats table, now with starred and deleted too 2016-08-13 20:33:17 +02:00
2e031db2a7 Stats on total public tag pages 2016-08-13 16:23:38 +02:00
1b513e98b2 Better text 2016-08-13 16:20:00 +02:00
bb258a2a68 Better total stats, now also info about nr bookmarks 2016-08-13 16:18:51 +02:00
72ad3c0d04 Total amount of tags, number of bookmarks per tag 2016-08-13 15:35:36 +02:00
78bed962d0 Delete public tag, create link 2016-08-13 14:54:11 +02:00
c70ac6d781 Way better tag overview page, with public links 2016-08-13 14:42:15 +02:00
53b96adc79 Show pointer mousepointer on tags in add/edit bookmark 2016-08-13 14:18:23 +02:00
4e86faf2aa Updated public bookmarks with new look 2016-08-13 14:14:07 +02:00
4b17721fb0 Removed favicon from card reveal; better layout in reveal 2016-08-13 14:07:17 +02:00
870418712f Merge branch 'master' of github.com:aquatix/digimarks 2016-08-13 14:03:40 +02:00
068c6b8d94 Prevent duplicate form submission; indentation fixes 2016-08-13 13:48:54 +02:00
60734c55ce Merge branch 'master' of github.com:aquatix/digimarks 2016-08-12 08:03:40 +02:00
e5c5e5f5a9 Don't have content run out of the bottom of the card 2016-08-12 08:03:23 +02:00
16ed20bd9b Better layout of Star and Warning icons 2016-08-05 20:11:05 +02:00
5840baa657 Determine of favicon is ico, decompress gzip'ed content 2016-08-05 13:30:00 +02:00
0271a9339d Use different favicon service, 60x60 icons, cleaner cards, refresh
favicons admin endpoint
2016-08-05 12:36:10 +02:00
8a28d841e3 Merge branch 'master' of github.com:aquatix/digimarks 2016-08-05 11:54:02 +02:00
04447390fc Tweaked FAB placement 2016-08-05 11:53:41 +02:00
4ee925de45 Use referrer when url encode doesn't work because of security scope 2016-08-03 21:34:44 +02:00
271f2fa4a0 Initialise with 'url' url parameter if exists, enabling bookmarklet 2016-08-03 14:14:59 +02:00
cfbab3f98b Fix for json response of single bookmark 2016-08-02 19:30:08 +02:00
62145ecfe2 Updated public tag page card design with current cards 2016-08-02 14:47:08 +02:00
42e4021ef5 Fix for link to public tag page (server part was missing) 2016-08-02 14:35:35 +02:00
e7125c07e9 Changes 2016-08-02 14:27:01 +02:00
bcc28c6a8e Feed link 2016-08-02 14:25:52 +02:00
fab1554c70 Atom feed for public tag 2016-08-02 14:16:35 +02:00
05d636fc08 json view of public tag page 2016-08-02 14:05:17 +02:00
14 changed files with 772 additions and 211 deletions

View File

@@ -1,18 +1,80 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## TODO ## TODO
- Sorting of bookmarks - Sorting of bookmarks
- Sort by title - Sort by title
- Sort by date - Sort by date
- Logging of actions - Logging of actions
- json/rss views of public tag pages - Change tags to the MaterializeCSS tags: http://materializecss.com/chips.html
- Do calls to the API endpoint of an existing bookmark when editing properties
(for example to update tags, title and such, also to already suggest title)
- Look into compatibility with del.icio.us, so we can make use of existing browser integration
## [Unreleased]
## [1.1.0] - 2017-07-22
### Added
- Show 404 page if bookmark is not found when editing
- Cache buster to force loading of the latest styling
- Theming support, default is 'green'
- Themes need an extra `theme` field in the User table
### Changed
- Make running in a virtualenv optional
- Fix for misalignment and size of hamburger icon
- Updated Python (pip) dependencies
- Updated MaterializeCSS and jQuery
### Removed
- Removed dependency on more_itertools
- Removed dependency on utilkit
## [1.0.0] - 2016-12-29
- json view of public tag pages, returns all items
- feed (rss/atom) view of public tag pages, returns latest 15
- feed link on public tag page
- Support for bookmarklets
- UI tweaks
- Redesigned cards with bigger favicon. Looks cleaner
- Different favicon service with 60x60px icons
- Prevent duplicate form submission on add/edit bookmark
- Delete bookmark from bookmark card
- Undo delete link in "Bookmark has been deleted" message
- Delete public tag page
- On tags overview page: - On tags overview page:
- Show which tags have public pages, with link - Show which tags have public pages, with link
- How many bookmarks each tag has - How many bookmarks each tag has
- Statistics on:
- total tags
- number of public tag pages
- total number of bookmarks
- number of starred bookmarks
- number of bookmarks with a non-OK http status
- number of deleted bookmarks
- Filter on 'star' status, 'broken' status (non-http-200-OK)
- Bookmark can have a note now
- Note icon on card with text in title (desktop browser)
- Filter on bookmarks with a note
- Show url domain name along with 'no title' for items without title
- Catch connection timeouts and such
- Open in new tab/window, prevent
http://davidebove.com/blog/2016/05/05/target_blank-the-vulnerability-in-your-browser/
- Put the tag selection in a collapsible element to prevent clutter in edit window
- Updated MaterializeCSS and jQuery
## v0.2.0 ## [0.2.0] - 2016-08-02
2016-08-02
- Favicon courtesy Freepik on flaticon.com - Favicon courtesy Freepik on flaticon.com
- Tag tags for easy adding of tags - Tag tags for easy adding of tags
@@ -23,9 +85,7 @@
- Option to strip parameters from url (like '?utm_source=social') - Option to strip parameters from url (like '?utm_source=social')
## v0.1.0 ## [0.1.0] - 2016-07-26
2016-07-26
- Initial release - Initial release
- Flask application with functionality to add users, add and edit bookmarks, - Flask application with functionality to add users, add and edit bookmarks,

View File

@@ -45,6 +45,16 @@ url's when wanted.
Url's are of the form https://marks.example.com/<userkey>/<action> Url's are of the form https://marks.example.com/<userkey>/<action>
Bookmarklet
~~~~~~~~~~~
To easily save a link from your browser, open its bookmark manager and create a new bookmark with as url:
.. code-block:: javascript
javascript:location.href='http://marks.example.com/1234567890abcdef/add?url='+encodeURIComponent(location.href);
Creating a new user Creating a new user
------------------- -------------------

View File

@@ -1,21 +1,78 @@
from __future__ import print_function from __future__ import print_function
import datetime import datetime
import gzip
import hashlib import hashlib
import os import os
import sys import sys
import requests import requests
import shutil import shutil
import bs4 import bs4
from more_itertools import unique_everseen from urlparse import urlparse, urlunparse, urljoin
from urlparse import urlparse, urlunparse
from utilkit import datetimeutil from flask import Flask, abort, redirect, render_template, request, url_for, jsonify
from werkzeug.contrib.atom import AtomFeed
from flask import Flask, abort, redirect, render_template, request, url_for
from flask_peewee.db import Database from flask_peewee.db import Database
#from flask_peewee.utils import get_object_or_404 #from flask_peewee.utils import get_object_or_404
from peewee import * # noqa from peewee import * # noqa
DEFAULT_THEME = 'green'
themes = {
'green': {
'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',
'CARD_BACKGROUND': 'green darken-3',
'CARD_TEXT': 'white-text',
'FAB': 'red',
'STAR': 'yellow-text',
'PROBLEM': 'red-text',
'COMMENT': '',
},
'freshgreen': {
'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',
'CARD_BACKGROUND': 'green darken-1',
'CARD_TEXT': 'white-text',
'FAB': 'red',
'STAR': 'yellow-text',
'PROBLEM': 'red-text',
'COMMENT': '',
},
'dark': {
'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',
'CARD_BACKGROUND': 'grey darken-3',
'CARD_TEXT': 'grey-text lighten-1',
'FAB': 'red',
'STAR': 'yellow-text',
'PROBLEM': 'red-text',
'COMMENT': '',
}
}
try: try:
import settings import settings
except ImportError: except ImportError:
@@ -46,8 +103,35 @@ except AttributeError:
# Cache the tags # Cache the tags
all_tags = {} all_tags = {}
settings = {}
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): def clean_tags(tags_list):
tags_res = [x.strip() for x in tags_list] tags_res = [x.strip() for x in tags_list]
tags_res = list(unique_everseen(tags_res)) tags_res = list(unique_everseen(tags_res))
@@ -57,10 +141,28 @@ def clean_tags(tags_list):
return tags_res return tags_res
magic_dict = {
"\x1f\x8b\x08": "gz",
"\x42\x5a\x68": "bz2",
"\x50\x4b\x03\x04": "zip"
}
max_len = max(len(x) for x in magic_dict)
def file_type(filename):
with open(filename) 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 User(db.Model): class User(db.Model):
""" User account """ """ User account """
username = CharField() username = CharField()
key = CharField() key = CharField()
theme = CharField(default=DEFAULT_THEME)
created_date = DateTimeField(default=datetime.datetime.now) created_date = DateTimeField(default=datetime.datetime.now)
def generate_key(self): def generate_key(self):
@@ -76,6 +178,7 @@ class Bookmark(db.Model):
title = CharField(default='') title = CharField(default='')
url = CharField() url = CharField()
note = TextField(default='')
#image = CharField(default='') #image = CharField(default='')
url_hash = CharField(default='') url_hash = CharField(default='')
tags = CharField(default='') tags = CharField(default='')
@@ -85,7 +188,13 @@ class Bookmark(db.Model):
favicon = CharField(null=True) favicon = CharField(null=True)
# Status code: 200 is OK, 404 is not found, for example (showing an error) # Status code: 200 is OK, 404 is not found, for example (showing an error)
HTTP_CONNECTIONERROR = 0
HTTP_OK = 200
HTTP_MOVEDTEMPORARILY = 304
HTTP_NOTFOUND = 404
http_status = IntegerField(default=200) http_status = IntegerField(default=200)
redirect_uri = None
created_date = DateTimeField(default=datetime.datetime.now) created_date = DateTimeField(default=datetime.datetime.now)
modified_date = DateTimeField(null=True) modified_date = DateTimeField(null=True)
@@ -133,8 +242,11 @@ class Bookmark(db.Model):
def set_status_code(self): def set_status_code(self):
""" Check the HTTP status of the url, as it might not exist for example """ """ Check the HTTP status of the url, as it might not exist for example """
result = requests.head(self.url) try:
self.http_status = result.status_code result = requests.head(self.url)
self.http_status = result.status_code
except requests.ConnectionError:
self.http_status = self.HTTP_CONNECTIONERROR
return self.http_status return self.http_status
def set_favicon(self): def set_favicon(self):
@@ -142,13 +254,28 @@ class Bookmark(db.Model):
# http://codingclues.eu/2009/retrieve-the-favicon-for-any-url-thanks-to-google/ # http://codingclues.eu/2009/retrieve-the-favicon-for-any-url-thanks-to-google/
u = urlparse(self.url) u = urlparse(self.url)
domain = u.netloc domain = u.netloc
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + '.png')
# if file exists, don't re-download it # if file exists, don't re-download it
response = requests.get('http://www.google.com/s2/favicons?domain=' + domain, stream=True) #response = requests.get('http://www.google.com/s2/favicons?domain=' + domain, stream=True)
fileextension = '.png'
meta = requests.head('http://icons.better-idea.org/icon?size=60&url=' + domain, allow_redirects=True)
if meta.url[-3:].lower() == 'ico':
fileextension = '.ico'
response = requests.get('http://icons.better-idea.org/icon?size=60&url=' + domain, stream=True)
filename = os.path.join(MEDIA_ROOT, 'favicons/' + domain + fileextension)
with open(filename, 'wb') as out_file: with open(filename, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file) shutil.copyfileobj(response.raw, out_file)
del response del response
self.favicon = domain + '.png' filetype = file_type(filename)
if filetype == 'gz':
# decompress
orig = gzip.GzipFile(filename, 'rb')
origcontent = orig.read()
orig.close()
os.remove(filename)
new = file(filename, 'wb')
new.write(origcontent)
new.close()
self.favicon = domain + fileextension
def set_tags(self, tags): def set_tags(self, tags):
""" Set tags from `tags`, strip and sort them """ """ Set tags from `tags`, strip and sort them """
@@ -156,6 +283,21 @@ class Bookmark(db.Model):
tags_clean = clean_tags(tags_split) tags_clean = clean_tags(tags_split)
self.tags = ','.join(tags_clean) 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)
self.http_status = result.status_code
self.redirect_uri = result.url
return result.url
else:
return None
def get_uri_domain(self):
parsed = urlparse(self.url)
return parsed.hostname
@classmethod @classmethod
def strip_url_params(cls, url): def strip_url_params(cls, url):
parsed = urlparse(url) parsed = urlparse(url)
@@ -174,7 +316,7 @@ class Bookmark(db.Model):
result = { result = {
'title': self.title, 'title': self.title,
'url': self.url, 'url': self.url,
'created': datetimeutil.datetime_to_string(self.created_date), 'created': self.created_date.strftime('%Y-%m-%d %H:%M:%S'),
'url_hash': self.url_hash, 'url_hash': self.url_hash,
'tags': self.tags, 'tags': self.tags,
} }
@@ -210,6 +352,18 @@ def get_cached_tags(userkey):
return [] return []
def get_theme(userkey):
try:
usertheme = settings[userkey]['theme']
return themes[usertheme]
except KeyError:
return themes[DEFAULT_THEME] # default
def make_external(url):
return urljoin(request.url_root, url)
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template('404.html', error=e), 404 return render_template('404.html', error=e), 404
@@ -218,12 +372,14 @@ def page_not_found(e):
@app.route('/') @app.route('/')
def index(): def index():
""" Homepage, point visitors to project page """ """ Homepage, point visitors to project page """
return render_template('index.html') theme = themes[DEFAULT_THEME]
return render_template('index.html', theme=theme)
@app.route('/<userkey>', methods=['GET', 'POST']) @app.route('/<userkey>', methods=['GET', 'POST'])
@app.route('/<userkey>/filter/<filtermethod>', methods=['GET', 'POST'])
@app.route('/<userkey>/sort/<sortmethod>', methods=['GET', 'POST']) @app.route('/<userkey>/sort/<sortmethod>', methods=['GET', 'POST'])
def bookmarks(userkey, sortmethod = None): def bookmarks(userkey, filtermethod = None, sortmethod = None):
""" User homepage, list their bookmarks, optionally filtered and/or sorted """ """ User homepage, list their bookmarks, optionally filtered and/or sorted """
#return object_list('bookmarks.html', Bookmark.select()) #return object_list('bookmarks.html', Bookmark.select())
#user = User.select(key=userkey) #user = User.select(key=userkey)
@@ -235,14 +391,39 @@ def bookmarks(userkey, sortmethod = None):
message = request.args.get('message') message = request.args.get('message')
tags = get_cached_tags(userkey) tags = get_cached_tags(userkey)
if request.method == 'POST': filter_text = ''
filter_on = request.form['filter'] if request.form:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.title.contains(filter_on), filter_text = request.form['filter_text']
filter_starred = False
if filtermethod and filtermethod.lower() == 'starred':
filter_starred = True
filter_broken = False
if filtermethod and filtermethod.lower() == 'broken':
filter_broken = True
filter_note = False
if filtermethod and filtermethod.lower() == 'note':
filter_note = True
if filter_text:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.title.contains(filter_text),
Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc()) Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc())
return render_template('bookmarks.html', bookmarks=bookmarks, userkey=userkey, tags=tags, filter=filter_on, message=message) elif filter_starred:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey,
Bookmark.starred == True).order_by(Bookmark.created_date.desc())
elif filter_broken:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey,
Bookmark.http_status != 200).order_by(Bookmark.created_date.desc())
elif filter_note:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey,
Bookmark.note != '').order_by(Bookmark.created_date.desc())
else: else:
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc()) bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc())
return render_template('bookmarks.html', bookmarks=bookmarks, userkey=userkey, tags=tags, message=message)
theme = get_theme(userkey)
return render_template('bookmarks.html', bookmarks=bookmarks, userkey=userkey, tags=tags, filter_text=filter_text, message=message, theme=theme)
@@ -256,8 +437,8 @@ def bookmarks(userkey, sortmethod = None):
@app.route('/<userkey>/<urlhash>/json') @app.route('/<userkey>/<urlhash>/json')
def viewbookmarkjson(userkey, urlhash): def viewbookmarkjson(userkey, urlhash):
""" Serialise bookmark to json """ """ Serialise bookmark to json """
bookmark = Bookmark.select(Bookmark.url_hash == urlhash, Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE) bookmark = Bookmark.select(Bookmark.url_hash == urlhash, Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE)[0]
return bookmark.to_dict() return jsonify(bookmark.to_dict())
@app.route('/<userkey>/<urlhash>') @app.route('/<userkey>/<urlhash>')
@@ -265,19 +446,32 @@ def viewbookmarkjson(userkey, urlhash):
def editbookmark(userkey, urlhash): def editbookmark(userkey, urlhash):
""" Bookmark edit form """ """ Bookmark edit form """
# bookmark = getbyurlhash() # bookmark = getbyurlhash()
bookmark = Bookmark.get(Bookmark.url_hash == urlhash, Bookmark.userkey == userkey) try:
bookmark = Bookmark.get(Bookmark.url_hash == urlhash, Bookmark.userkey == userkey)
except Bookmark.DoesNotExist:
abort(404)
message = request.args.get('message') message = request.args.get('message')
tags = get_cached_tags(userkey) tags = get_cached_tags(userkey)
return render_template('edit.html', action='Edit bookmark', userkey=userkey, bookmark=bookmark, message=message, formaction='edit', tags=tags) if not bookmark.note:
# Workaround for when an existing bookmark has a null note
bookmark.note = ''
theme = get_theme(userkey)
return render_template('edit.html', action='Edit bookmark', userkey=userkey, bookmark=bookmark, message=message, formaction='edit', tags=tags, theme=theme)
@app.route('/<userkey>/add') @app.route('/<userkey>/add')
def addbookmark(userkey): def addbookmark(userkey):
""" Bookmark add form """ """ Bookmark add form """
bookmark = Bookmark(title='', url='', tags='') url = request.args.get('url')
if not url:
url = ''
if request.args.get('referrer'):
url = request.referrer
bookmark = Bookmark(title='', url=url, tags='')
message = request.args.get('message') message = request.args.get('message')
tags = get_cached_tags(userkey) tags = get_cached_tags(userkey)
return render_template('edit.html', action='Add bookmark', userkey=userkey, bookmark=bookmark, tags=tags, message=message) theme = get_theme(userkey)
return render_template('edit.html', action='Add bookmark', userkey=userkey, bookmark=bookmark, tags=tags, message=message, theme=theme)
def updatebookmark(userkey, request, urlhash = None): def updatebookmark(userkey, request, urlhash = None):
@@ -285,6 +479,7 @@ def updatebookmark(userkey, request, urlhash = None):
title = request.form.get('title') title = request.form.get('title')
url = request.form.get('url') url = request.form.get('url')
tags = request.form.get('tags') tags = request.form.get('tags')
note = request.form.get('note')
starred = False starred = False
if request.form.get('starred'): if request.form.get('starred'):
starred = True starred = True
@@ -313,6 +508,7 @@ def updatebookmark(userkey, request, urlhash = None):
bookmark.url = url bookmark.url = url
bookmark.starred = starred bookmark.starred = starred
bookmark.set_tags(tags) bookmark.set_tags(tags)
bookmark.note = note
bookmark.set_hash() bookmark.set_hash()
#bookmark.fetch_image() #bookmark.fetch_image()
if not title: if not title:
@@ -322,7 +518,11 @@ def updatebookmark(userkey, request, urlhash = None):
bookmark.set_status_code() bookmark.set_status_code()
if bookmark.http_status == 200: if bookmark.http_status == 200:
bookmark.set_favicon() try:
bookmark.set_favicon()
except IOError:
# Icon file could not be saved possibly, don't bail completely
pass
bookmark.save() bookmark.save()
return bookmark return bookmark
@@ -363,7 +563,17 @@ def deletingbookmark(userkey, urlhash):
query.execute() query.execute()
query = Bookmark.update(deleted_date = datetime.datetime.now()).where(Bookmark.userkey==userkey, Bookmark.url_hash==urlhash) query = Bookmark.update(deleted_date = datetime.datetime.now()).where(Bookmark.userkey==userkey, Bookmark.url_hash==urlhash)
query.execute() query.execute()
message = 'Bookmark deleted' message = 'Bookmark deleted. <a href="{}">Undo deletion</a>'.format(url_for('undeletebookmark', userkey=userkey, urlhash=urlhash))
all_tags[userkey] = get_tags_for_user(userkey)
return redirect(url_for('bookmarks', userkey=userkey, message=message))
@app.route('/<userkey>/<urlhash>/undelete')
def undeletebookmark(userkey, urlhash):
""" Undo deletion of the bookmark identified by urlhash """
query = Bookmark.update(status=Bookmark.VISIBLE).where(Bookmark.userkey==userkey, Bookmark.url_hash==urlhash)
query.execute()
message = 'Bookmark restored'
all_tags[userkey] = get_tags_for_user(userkey) all_tags[userkey] = get_tags_for_user(userkey)
return redirect(url_for('bookmarks', userkey=userkey, message=message)) return redirect(url_for('bookmarks', userkey=userkey, message=message))
@@ -372,14 +582,34 @@ def deletingbookmark(userkey, urlhash):
def tags(userkey): def tags(userkey):
""" Overview of all tags used by user """ """ Overview of all tags used by user """
tags = get_cached_tags(userkey) tags = get_cached_tags(userkey)
return render_template('tags.html', tags=tags, userkey=userkey) #publictags = PublicTag.select().where(Bookmark.userkey == userkey)
alltags = []
for tag in tags:
try:
publictag = PublicTag.get(PublicTag.userkey == userkey, PublicTag.tag == tag)
except PublicTag.DoesNotExist:
publictag = None
total = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.tags.contains(tag), Bookmark.status == Bookmark.VISIBLE).count()
alltags.append({'tag': tag, 'publictag': publictag, 'total': total})
totaltags = len(alltags)
totalbookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.status == Bookmark.VISIBLE).count()
totalpublic = PublicTag.select().where(PublicTag.userkey == userkey).count()
totalstarred = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.starred == True).count()
totaldeleted = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.status == Bookmark.DELETED).count()
totalnotes = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.note != '').count()
totalhttperrorstatus = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.http_status != 200).count()
theme = get_theme(userkey)
return render_template('tags.html', tags=alltags, totaltags=totaltags, totalpublic=totalpublic, totalbookmarks=totalbookmarks,
totaldeleted=totaldeleted, totalstarred=totalstarred, totalhttperrorstatus=totalhttperrorstatus,
totalnotes=totalnotes, userkey=userkey, theme=theme)
@app.route('/<userkey>/tag/<tag>') @app.route('/<userkey>/tag/<tag>')
def tag(userkey, tag): def tag(userkey, tag):
""" Overview of all bookmarks with a certain tag """ """ Overview of all bookmarks with a certain tag """
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.tags.contains(tag), Bookmark.status == Bookmark.VISIBLE) bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.tags.contains(tag), Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc())
tags = get_tags_for_user(userkey) tags = get_cached_tags(userkey)
pageheader = 'tag: ' + tag pageheader = 'tag: ' + tag
message = request.args.get('message') message = request.args.get('message')
@@ -388,17 +618,56 @@ def tag(userkey, tag):
except PublicTag.DoesNotExist: except PublicTag.DoesNotExist:
publictag = None publictag = None
return render_template('bookmarks.html', bookmarks=bookmarks, userkey=userkey, tags=tags, tag=tag, publictag=publictag, action=pageheader, message=message) theme = get_theme(userkey)
return render_template('bookmarks.html', bookmarks=bookmarks, userkey=userkey, tags=tags, tag=tag, publictag=publictag, action=pageheader,
message=message, theme=theme)
@app.route('/pub/<tagkey>') @app.route('/pub/<tagkey>')
def publictag(tagkey): def publictag(tagkey):
""" Read-only overview of the bookmarks in the userkey/tag of this PublicTag """ """ Read-only overview of the bookmarks in the userkey/tag of this PublicTag """
#this_tag = get_object_or_404(PublicTag.select().where(PublicTag.tagkey == tagkey)) #this_tag = get_object_or_404(PublicTag.select().where(PublicTag.tagkey == tagkey))
try:
this_tag = PublicTag.get(PublicTag.tagkey == tagkey)
bookmarks = Bookmark.select().where(Bookmark.userkey == this_tag.userkey, Bookmark.tags.contains(this_tag.tag), Bookmark.status == Bookmark.VISIBLE).order_by(Bookmark.created_date.desc())
theme = themes[DEFAULT_THEME]
return render_template('publicbookmarks.html', bookmarks=bookmarks, tag=tag, action=this_tag.tag, tagkey=tagkey, theme=theme)
except PublicTag.DoesNotExist:
abort(404)
@app.route('/pub/<tagkey>/json')
def publictagjson(tagkey):
""" json representation of the Read-only overview of the bookmarks in the userkey/tag of this PublicTag """
try: try:
this_tag = PublicTag.get(PublicTag.tagkey == tagkey) this_tag = PublicTag.get(PublicTag.tagkey == tagkey)
bookmarks = Bookmark.select().where(Bookmark.userkey == this_tag.userkey, Bookmark.tags.contains(this_tag.tag), Bookmark.status == Bookmark.VISIBLE) bookmarks = Bookmark.select().where(Bookmark.userkey == this_tag.userkey, Bookmark.tags.contains(this_tag.tag), Bookmark.status == Bookmark.VISIBLE)
return render_template('publicbookmarks.html', bookmarks=bookmarks, tag=tag, action=this_tag.tag) result = {'count': len(bookmarks), 'items': []}
for bookmark in bookmarks:
result['items'].append(bookmark.to_dict())
return jsonify(result)
except PublicTag.DoesNotExist:
abort(404)
@app.route('/pub/<tagkey>/feed')
def publictagfeed(tagkey):
""" rss/atom representation of the Read-only overview of the bookmarks in the userkey/tag of this PublicTag """
try:
this_tag = PublicTag.get(PublicTag.tagkey == tagkey)
bookmarks = Bookmark.select().where(Bookmark.userkey == this_tag.userkey, Bookmark.tags.contains(this_tag.tag), Bookmark.status == Bookmark.VISIBLE).limit(15)
feed = AtomFeed(this_tag.tag, feed_url=request.url, url=make_external(url_for('publictag', tagkey=tagkey)))
for bookmark in bookmarks:
updated_date = bookmark.modified_date
if not bookmark.modified_date:
updated_date = bookmark.created_date
feed.add(bookmark.title,
content_type='html',
author='digimarks',
url=bookmark.url,
updated=updated_date,
published=bookmark.created_date)
return feed.get_response()
except PublicTag.DoesNotExist: except PublicTag.DoesNotExist:
abort(404) abort(404)
@@ -428,6 +697,14 @@ def addpublictag(userkey, tag):
return redirect(url_for('tag', userkey=userkey, tag=tag, message=message)) return redirect(url_for('tag', userkey=userkey, tag=tag, message=message))
@app.route('/<userkey>/<tag>/removepublic/<tagkey>', methods=['GET', 'POST'])
def removepublictag(userkey, tag, tagkey):
q = PublicTag.delete().where(PublicTag.userkey == userkey, PublicTag.tag == tag, PublicTag.tagkey == tagkey)
q.execute()
message = 'Public link deleted'
return redirect(url_for('tag', userkey=userkey, tag=tag, message=message))
@app.route('/<systemkey>/adduser') @app.route('/<systemkey>/adduser')
def adduser(systemkey): def adduser(systemkey):
""" Add user endpoint, convenience """ """ Add user endpoint, convenience """
@@ -442,8 +719,25 @@ def adduser(systemkey):
abort(404) abort(404)
# Initialise @app.route('/<systemkey>/refreshfavicons')
# create the bookmark, user and public tag tables if they do not exist def refreshfavicons(systemkey):
""" Add user endpoint, convenience """
if systemkey == settings.SYSTEMKEY:
bookmarks = Bookmark.select()
for bookmark in bookmarks:
if bookmark.favicon:
try:
filename = os.path.join(MEDIA_ROOT, 'favicons/' + bookmark.favicon)
os.remove(filename)
except OSError as e:
print(e)
bookmark.set_favicon()
return redirect('/')
else:
abort(404)
# Initialisation == create the bookmark, user and public tag tables if they do not exist
Bookmark.create_table(True) Bookmark.create_table(True)
User.create_table(True) User.create_table(True)
PublicTag.create_table(True) PublicTag.create_table(True)
@@ -452,8 +746,10 @@ users = User.select()
print('Current user keys:') print('Current user keys:')
for user in users: for user in users:
all_tags[user.key] = get_tags_for_user(user.key) all_tags[user.key] = get_tags_for_user(user.key)
settings[user.key] = {'theme': user.theme}
print(user.key) print(user.key)
# Run when called standalone
if __name__ == '__main__': if __name__ == '__main__':
# run the application # run the application
app.run(port=9999, debug=True) app.run(port=9999, debug=True)

View File

@@ -1,4 +1,4 @@
# Virtualenv to use with the wsgi file # Virtualenv to use with the wsgi file (optional)
VENV = '/srv/marks.example.com/venv/bin/activate_this.py' VENV = '/srv/marks.example.com/venv/bin/activate_this.py'
PORT = 8086 PORT = 8086

7
requirements.in Normal file
View File

@@ -0,0 +1,7 @@
pkg-resources==0.0.0
flask
peewee
flask-peewee
bs4
requests

View File

@@ -1,7 +1,24 @@
flask #
peewee # This file is autogenerated by pip-compile
flask-peewee # To update, run:
bs4 #
more_itertools # pip-compile --output-file requirements.txt requirements.in
requests #
utilkit beautifulsoup4==4.6.0 # via bs4
bs4==0.0.1
certifi==2017.4.17 # via requests
chardet==3.0.4 # via requests
click==6.7 # via flask
flask-peewee==0.6.7
flask==0.12.2
idna==2.5 # via requests
itsdangerous==0.24 # via flask
jinja2==2.9.6 # via flask, flask-peewee
markupsafe==1.0 # via jinja2
peewee==2.10.1
pkg-resources==0.0.0
requests==2.18.1
urllib3==1.21.1 # via requests
werkzeug==0.12.2 # via flask, flask-peewee
wtf-peewee==0.2.6 # via flask-peewee
wtforms==2.1 # via flask-peewee, wtf-peewee

View File

@@ -26,7 +26,7 @@ setup(
# third part for minor release # third part for minor release
# second when api changes # second when api changes
# first when it becomes stable someday # first when it becomes stable someday
version='0.2.0', version='1.1.0',
author='Michiel Scholten', author='Michiel Scholten',
author_email='michiel@diginaut.net', author_email='michiel@diginaut.net',
@@ -35,7 +35,7 @@ setup(
# as a practice no need to hard code version unless you know program wont # as a practice no need to hard code version unless you know program wont
# work unless the specific versions are used # work unless the specific versions are used
install_requires=['Flask', 'Peewee', 'Flask-Peewee', 'requests', 'utilkit'], install_requires=['Flask', 'Peewee', 'Flask-Peewee', 'requests'],
py_modules=['digimarks'], py_modules=['digimarks'],

View File

@@ -1,59 +1,93 @@
/**
* digimarks styling
*/
/* label color */ /** Navigation **/
.input-field label {
color: #000;
}
/* label focus color */
.input-field input[type=text]:focus + label {
color: #000;
}
/* label underline focus color */
.input-field input[type=text]:focus {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* valid color */
.input-field input[type=text].valid {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* invalid color */
.input-field input[type=text].invalid {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* icon prefix focus color */
.input-field .prefix.active {
color: #000;
}
nav .button-collapse
{
/* Fix for misalignment of hamburger icon */
margin: 0;
}
/* Card title anchor colour */ nav .button-collapse i
.white-text .card-title a, {
.white-text a /* Make the hamburger icon great again */
{ font-size: 2.7rem;
color: #FFF; }
}
.chip a, /** Form input fields **/
.white-text .chip a
{
color: #1b5e20; /* green darken-4 */
}
.card.tiny /* label underline focus color */
{ .input-field input[type=text]:focus
height: 140px; {
} border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* valid color */
.input-field input[type=text].valid
{
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* invalid color */
.input-field input[type=text].invalid
{
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
/* icon prefix focus color */
.input-field .prefix.active
{
color: #000;
}
.card .card-reveal .digimark-card-header, /** Cards and tags **/
.card .digimark-card-header.activator
{
cursor: pointer;
/*display: block;*/
}
.card .digimark-card-content /* Card title anchor colour */
{ .white-text .card-title a,
padding-top: 10px; .white-text a
} {
color: #FFF;
}
.chip a,
.white-text .chip a
{
color: #1b5e20; /* green darken-4 */
}
.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-content
{
padding-top: 10px;
}
.card-image i
{
padding: 5px 0 0 15px;
}
.card.horizontal .card-image img.favicon
{
height: 60px;
width: 60px;
}

View File

@@ -17,12 +17,24 @@
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <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://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/0.97.7/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
<link href="{{ url_for('static', filename='css/digimarks.css') }}" type="text/css" rel="stylesheet" media="screen,projection"/> <style>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script> /* label color */
.input-field label
{
color: {{ theme.TEXTHEX }};
}
/* label focus color */
.input-field input[type=text]:focus + label
{
color: {{ theme.TEXTHEX }};
}
</style>
<link href="{{ url_for('static', filename='css/digimarks.css') }}?20170722" type="text/css" rel="stylesheet" media="screen,projection"/>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
</head> </head>
<body class="grey lighten-4"> <body class="{{ theme.BODY }} {{ theme.TEXT }}">
<nav class="green darken-3" role="navigation"> <nav class="{{ theme.NAV }}" role="navigation">
<div class="nav-wrapper container"><a id="logo-container" href="{% if userkey %}{{ url_for('bookmarks', userkey=userkey) }}{% else %}{{ url_for('index') }}{% endif %}" class="brand-logo">digimarks</a> <div class="nav-wrapper container"><a id="logo-container" href="{% if userkey %}{{ url_for('bookmarks', userkey=userkey) }}{% else %}{{ url_for('index') }}{% endif %}" class="brand-logo">digimarks</a>
<ul class="right hide-on-med-and-down"> <ul class="right hide-on-med-and-down">
{% if userkey %} {% if userkey %}
@@ -31,18 +43,19 @@
{% endif %} {% endif %}
</ul> </ul>
{% if userkey %}
<ul id="nav-mobile" class="side-nav"> <ul id="nav-mobile" class="side-nav">
{% if userkey %} <li><a class="waves-effect" href="{{ url_for('bookmarks', userkey=userkey) }}"><i class="material-icons">turned_in</i>Home</a></li>
<li><a href="{{ url_for('tags', userkey=userkey) }}">Tags</a></li> <li><a class="waves-effect" href="{{ url_for('tags', userkey=userkey) }}"><i class="material-icons">label</i>Tags</a></li>
<li><a href="{{ url_for('addbookmark', userkey=userkey) }}">Add bookmark</a></li> <li><a class="waves-effect" href="{{ url_for('addbookmark', userkey=userkey) }}"><i class="material-icons">add</i>Add bookmark</a></li>
{% endif %}
</ul> </ul>
<a href="#" data-activates="nav-mobile" class="button-collapse"><i class="material-icons">menu</i></a> <a href="#" data-activates="nav-mobile" class="button-collapse"><i class="material-icons">menu</i></a>
{% endif %}
</div> </div>
</nav> </nav>
<div class="section no-pad-bot" id="index-banner"> <div class="section no-pad-bot" id="index-banner">
<div class="container"> <div class="container">
<div class="header grey-text lighten-5"> <div class="header {{ theme.PAGEHEADER }}">
<h1>{% block pageheader %}Bookmarks{% endblock %}</h1> <h1>{% block pageheader %}Bookmarks{% endblock %}</h1>
</div> </div>
</div> </div>
@@ -55,7 +68,7 @@
</div> </div>
<!-- Scripts --> <!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/js/materialize.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/js/materialize.min.js"></script>
<script src="{{ url_for('static', filename='js/init.js') }}"></script> <script src="{{ url_for('static', filename='js/init.js') }}"></script>
</body> </body>

View File

@@ -23,9 +23,9 @@
{% if message %} {% if message %}
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<div class="card-panel orange lighten-2"> <div class="card-panel {{ theme.MESSAGE_BACKGROUND }}">
<span class="white-text"> <span class="{{ theme.MESSAGE_TEXT }}">
{{ message }} {{ message|safe }}
</span> </span>
</div> </div>
</div> </div>
@@ -35,7 +35,7 @@
<div class="row"> <div class="row">
<form action="{{ url_for('bookmarks', userkey=userkey) }}" method="POST"> <form action="{{ url_for('bookmarks', userkey=userkey) }}" method="POST">
<div class="input-field col l10 m10 s8"> <div class="input-field col l10 m10 s8">
<input placeholder="filter" type="text" name="filter" id="filter" value="{{ filter }}" class="validate" /> <input placeholder="search text" type="text" name="filter_text" id="filter_text" value="{{ filter_text }}" class="validate" />
</div> </div>
<div class="input-field col l2 m2 s4"> <div class="input-field col l2 m2 s4">
@@ -46,13 +46,28 @@
{% if tags %} {% if tags %}
<div class="row"> <div class="row">
<p> <div class="col s12">
{% for tag in tags %} <ul class="collapsible" data-collapsible="expandable">
<div class="chip"> <li>
<a href="{{ url_for('tag', userkey=userkey, tag=tag) }}">{{ tag }}</a> <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', userkey=userkey, filtermethod='starred') }}"><i class="tiny material-icons {{ theme.STAR }}">star</i></a>
</div>
<div class="chip">
<a href="{{ url_for('bookmarks', userkey=userkey, filtermethod='broken') }}"><i class="tiny material-icons {{ theme.PROBLEM }}">report_problem</i></a>
</div>
<div class="chip">
<a href="{{ url_for('bookmarks', 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', userkey=userkey, tag=tag) }}">{{ tag }}</a>
</div>
{% endfor %}
</li>
</ul>
</div> </div>
{% endfor %}
</p>
</div> </div>
{% endif %} {% endif %}
@@ -68,43 +83,49 @@
<p>{{ bookmark.created_date.strftime("%m/%d/%Y %H:%M") }}</p> <p>{{ bookmark.created_date.strftime("%m/%d/%Y %H:%M") }}</p>
</div> </div>
#} #}
<div class="card tiny green darken-3"> <div class="card horizontal tiny {{ theme.CARD_BACKGROUND }}">
<div class="card-content white-text"> <div class="card-image">
<span class="digimark-card-header activator">
{% for tag in bookmark.tags_list %}
<div class="chip">
<a href="{{ url_for('tag', userkey=userkey, tag=tag) }}">{{ tag }}</a>
</div>
{% endfor %}
<i class="material-icons right">more_vert</i>
</span>
<div class="valign-wrapper digimark-card-content">
<div class="valign">
{% if bookmark.http_status != 200 %}
<i class="tiny material-icons" title="HTTP status {{ bookmark.http_status }}">report_problem</i>
{% endif %}
{% if bookmark.favicon %}
<img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" />&nbsp;
{% endif %}
<a href="{{ bookmark.url }}" title="{{ bookmark.url }}" rel="noreferrer">
{% if bookmark.starred == True %}
<i class="tiny material-icons yellow-text">star</i>
{% endif %}
{% if bookmark.title %}
{{ bookmark.title }}
{% else %}
[ no title ]
{% endif %}
</a></div>
</div>
</div>
<div class="card-reveal green darken-3">
<span class="card-title white-text valign-wrapper">Added @ {{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}<i class="material-icons right">close</i></span>
<div class="white-text valign">
{% if bookmark.favicon %} {% if bookmark.favicon %}
<img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" />&nbsp;&nbsp;&nbsp; <div><img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" class="favicon" /></div>
{% endif %} {% 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">
<span class="digimark-card-header-tags">
{% for tag in bookmark.tags_list %}
<div class="chip">
<a href="{{ url_for('tag', userkey=userkey, tag=tag) }}">{{ tag }}</a>
</div>
{% endfor %}
</span>
<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>
<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('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> </div>
</div> </div>
</div> </div>
@@ -119,8 +140,8 @@
#} #}
</div> </div>
<div class="fixed-action-btn" style="bottom: 45px; right: 24px;"> <div class="fixed-action-btn" style="bottom: 20px; right: 20px;">
<a class="btn-floating btn-large red" href="{{ url_for('addbookmark', userkey=userkey) }}"> <a class="btn-floating btn-large {{ theme.FAB }}" href="{{ url_for('addbookmark', userkey=userkey) }}">
<i class="large material-icons">add</i> <i class="large material-icons">add</i>
</a> </a>
</div> </div>

View File

@@ -3,13 +3,17 @@
{% block pageheader %}{{ action }}{% endblock %} {% block pageheader %}{{ action }}{% endblock %}
{% block pagecontent %} {% block pagecontent %}
{% if bookmark.http_status != 200 %} {% if bookmark.http_status != 200 and bookmark.http_status != 304 %}
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<div class="card-panel red darken-1"> <div class="card-panel {{ theme.ERRORMESSAGE_BACKGROUND }}">
<span class="white-text"> <span class="{{ theme.ERRORMESSAGE_TEXT }}">
{% if bookmark.http_status == 404 %} {% if bookmark.http_status == 404 %}
<i class="material-icons">report_problem</i>&nbsp;&nbsp;URL not found (404), broken/outdated link? <i class="material-icons">report_problem</i>&nbsp;&nbsp;URL not found (404), broken/outdated link?
{% elif bookmark.http_status == 302 %}
<i class="material-icons">report_problem</i>&nbsp;&nbsp;HTTP status (302), moved temporarily. Use button for new target
{% elif bookmark.http_status == bookmark.HTTP_CONNECTIONERROR %}
<i class="material-icons">report_problem</i>&nbsp;&nbsp;Connection error, server might have been offline at the time of last edit
{% else %} {% else %}
<i class="material-icons">report_problem</i>&nbsp;&nbsp;HTTP status {{ bookmark.http_status }} <i class="material-icons">report_problem</i>&nbsp;&nbsp;HTTP status {{ bookmark.http_status }}
{% endif %} {% endif %}
@@ -22,8 +26,8 @@
{% if message %} {% if message %}
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<div class="card-panel orange lighten-2"> <div class="card-panel {{ theme.MESSAGE_BACKGROUND }}">
<span class="white-text"> <span class="{{ theme.MESSAGE_TEXT }}">
{{ message }} {{ message }}
</span> </span>
</div> </div>
@@ -31,17 +35,16 @@
</div> </div>
{% endif %} {% endif %}
{% if formaction and formaction == 'edit' %} {% if formaction and formaction == 'edit' %}
<form action="{{ url_for('editingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST"> <form class="digimark" action="{{ url_for('editingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST">
{% else %} {% else %}
<form action="{{ url_for('addingbookmark', userkey=userkey) }}" method="POST"> <form class="digimark" action="{{ url_for('addingbookmark', userkey=userkey) }}" method="POST">
{% endif %} {% endif %}
<div class="row">
<div class="row">
<div class="input-field col s12"> <div class="input-field col s12">
<i class="material-icons prefix">description</i> <i class="material-icons prefix">description</i>
<input placeholder="title" type="text" name="title" id="title" value="{{ bookmark.title }}" class="validate" /> <input placeholder="title (leave empty for autofetch)" type="text" name="title" id="title" value="{{ bookmark.title }}" class="validate" />
<label for="title">Title</label> <label for="title">Title</label>
</div> </div>
@@ -49,6 +52,25 @@
<i class="material-icons prefix">turned_in</i> <i class="material-icons prefix">turned_in</i>
<input placeholder="url" type="text" name="url" id="url" value="{{ bookmark.url }}" class="validate" /> <input placeholder="url" type="text" name="url" id="url" value="{{ bookmark.url }}" class="validate" />
<label for="url">URL</label> <label for="url">URL</label>
{% if bookmark.get_redirect_uri() %}
<div>
<a class="waves-effect waves-light btn" id="btn_urlupdate"><i class="material-icons left">turned_in</i>{{ bookmark.get_redirect_uri() }}</a>
</div>
<script type="text/javascript">
$(function () {
$('#btn_urlupdate').on('click', function () {
var text = $('#url');
text.val('{{ 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 }}" class="validate" />
<label for="note">Note</label>
</div> </div>
<div class="input-field col s12"> <div class="input-field col s12">
@@ -60,11 +82,18 @@
{% if tags %} {% if tags %}
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
{% for tag in tags %} <ul class="collapsible" data-collapsible="expandable">
<div class="chip" id="tag_{{ tag }}"> <li>
{{ tag }} <div class="collapsible-header"><i class="material-icons">label</i>Existing tags</div>
</div> <div class="collapsible-body" style="padding: 10px;">
{% endfor %} {% for tag in tags %}
<div class="chip clickable" id="tag_{{ tag }}">
{{ tag }}
</div>
{% endfor %}
</div>
</li>
</ul>
</div> </div>
{% for tag in tags %} {% for tag in tags %}
<script type="text/javascript"> <script type="text/javascript">
@@ -119,15 +148,25 @@
{% endif %} {% endif %}
<div class="input-field col l2 m3 s4"> <div class="input-field col l2 m3 s4">
<p class="left-align"><button class="btn waves-effect waves-light" type="submit" name="submit">Save <i class="material-icons right">send</i></button></p> <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> </div>
</form>
{% if bookmark.url_hash %} {% if bookmark.url_hash %}
</form>
<div class="input-field col l2 m3 s4"> <div class="input-field col l2 m3 s4">
<form action="{{ url_for('deletingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST"> <form action="{{ url_for('deletingbookmark', userkey=userkey, urlhash=bookmark.url_hash) }}" method="POST">
<p class="left-align"><button class="btn waves-effect waves-light" type="submit" name="delete">Delete <i class="material-icons right">delete</i></button></p> <p class="left-align"><button class="btn waves-effect waves-light" type="submit" name="delete">Delete <i class="material-icons right">delete</i></button></p>
</form> </form>
</div> </div>
</div>
{% else %}
</div>
</form>
{% endif %} {% endif %}
</div>
<script>
$(function() {
console.log('woei');
$('form.digimark').on('submit',function(){$("#submit").prop("disabled", true); return true;})
});
</script>
{% endblock %} {% endblock %}

View File

@@ -18,36 +18,48 @@
</div> </div>
{% endif %} {% endif %}
<div class="row">
<div class="col s12">
<a href="{{ url_for('publictagfeed', tagkey=tagkey) }}"><i class="material-icons tiny">rss_feed</i> feed</a>
</div>
</div>
<div class="row"> <div class="row">
{% for bookmark in bookmarks %} {% for bookmark in bookmarks %}
<div class="col s12 m6 l4"> <div class="col s12 m6 l4">
<div class="card green darken-3"> <div class="card horizontal tiny green darken-3">
<div class="card-content white-text"> <div class="card-image">
<span class="card-title activator"><i class="material-icons right">more_vert</i></span> {% if bookmark.favicon %}
{% if bookmark.http_status != 200 %} <div><img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" class="favicon" /></div>
<i class="material-icons" title="HTTP status {{ bookmark.http_status }}">report_problem</i> {% endif %}
{% endif %} {% if bookmark.http_status != 200 %}
{% if bookmark.favicon %} <i class="small material-icons red-text" title="HTTP status {{ bookmark.http_status }}">report_problem</i><br />
<img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" />&nbsp;&nbsp;&nbsp; {% endif %}
{% endif %} {% if bookmark.starred == True %}
<p><a href="{{ bookmark.url }}" title="{{ bookmark.url }}" rel="noreferrer"> <i class="small material-icons yellow-text">star</i>
{% if bookmark.starred == True %} {% endif %}
<i class="material-icons">star</i> {% if bookmark.note %}
{% endif %} <div><i class="small material-icons white-text" title="{{ bookmark.note|truncate(100) }}">comment</i></div>
{% if bookmark.title %} {% endif %}
{{ bookmark.title }} </div>
{% else %} <div class="card-stacked">
[ no title ] <div class="card-content white-text">
{% endif %} <span class="digimark-card-header activator">
</a></p> <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>
<div class="card-reveal green darken-3"> <div class="card-reveal green darken-3">
<span class="card-title white-text">Added @ {{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}<i class="material-icons right">close</i></span> <span class="card-title white-text">Added @ {{ bookmark.created_date.strftime('%Y-%m-%d %H:%M') }}<i class="material-icons right">close</i></span>
<div class="white-text">
{% if bookmark.favicon %}
<img src="{{ url_for('static', filename='favicons/' + bookmark.favicon) }}" />&nbsp;&nbsp;&nbsp;
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,11 +4,62 @@
{% block pagecontent %} {% block pagecontent %}
<div class="row"> <div class="row">
{% for tag in tags %} <div class="col s12">
<div class="chip"> <table class="centered">
<a href="{{ url_for('tag', userkey=userkey, tag=tag) }}">{{ tag }}</a> <thead>
</div> <tr>
{% endfor %} <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', userkey=userkey, tag=tag['tag']) }}">{{ tag['tag'] }}</a>
</td>
<td>
{% if tag['publictag'] %}
<a href="{{ url_for('publictag', 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> </div>
{% endblock %} {% endblock %}

View File

@@ -1,7 +1,8 @@
# Activate virtualenv # Activate virtualenv
import settings import settings
activate_this = settings.VENV activate_this = getattr(settings, 'VENV', None)
execfile(activate_this, dict(__file__=activate_this)) if activate_this:
execfile(activate_this, dict(__file__=activate_this))
from digimarks import app as application from digimarks import app as application