mirror of
https://github.com/aquatix/digimarks.git
synced 2025-12-06 22:05:09 +01:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abf9141019 | |||
|
|
8ca54bf364 | ||
| 0be05d07e9 | |||
| a66360a0e3 | |||
| 1545c17472 | |||
| dd0a9d8283 | |||
| 652e3a89af | |||
| 133a139b79 | |||
| b556978640 | |||
| 8bb9e11088 | |||
| 85e94b71e5 | |||
| a895774001 | |||
| 7598ba6a61 | |||
| c5dcf579d3 | |||
| 590095659e | |||
| d5d71b4f51 | |||
| 7f2da49d26 | |||
| 0f0e4e03c3 | |||
| 044f507aa2 | |||
| b0da3a4454 | |||
| 17e1db12bf | |||
| 480f27797f | |||
| 1729ecd540 | |||
| d54b26dafb | |||
| 2c76ffaf12 | |||
| e54a033984 | |||
| 4e0d89c25e | |||
| 30e4f74ed9 | |||
| 1beadb676c | |||
| eea63398ba | |||
| d5021fd7c4 | |||
| c03d0c2458 | |||
| ff08ea4930 | |||
| 2e9457ee1a | |||
| 63dd636c25 | |||
| 412b4a93c7 | |||
| abdc11361a | |||
|
|
7a98de4b3f | ||
|
|
59f5365edc | ||
|
|
b0aedcd6e4 | ||
| 6b61e43c5d | |||
| edc1a86b75 | |||
|
|
570af2d62e | ||
|
|
8a19df2741 | ||
|
|
9e6d67a0ff | ||
| 698a417bac | |||
| 4cdea81f16 | |||
| 0e659a4f16 | |||
| 2cdb0ebccb | |||
| 9e2bdac9ee | |||
| 5288c9fe9c | |||
| 0e60f8f3cb | |||
| 5fd8db8f43 | |||
| 0894770f12 | |||
| 197cb776bf | |||
| c433c2c1e8 | |||
| b5d1377109 | |||
| 85d4466de5 | |||
| 7609becf00 | |||
| a6d6ce5c13 | |||
| e448f69f38 | |||
| 17c36ab8fe | |||
| 9a15a9946c | |||
| 40b7937876 | |||
| 38155cafaa | |||
| b12974fc31 | |||
| d39d793460 | |||
| 7c24057d35 | |||
| 5fde397c4a | |||
| 62117699f8 | |||
| 14052148f7 | |||
| fe44928816 | |||
| e5c3824034 | |||
| d8850d33f4 | |||
| 2117c451b2 | |||
| 9abc17d517 | |||
| 978c34fcf3 | |||
| 7fdc0706bf | |||
| c460f713ca | |||
| 2de7472e27 | |||
| a6082caf0d | |||
| ac0ac849cb | |||
| 390cc0137a | |||
| 2e031db2a7 | |||
| 1b513e98b2 | |||
| bb258a2a68 | |||
| 72ad3c0d04 | |||
| 78bed962d0 | |||
| c70ac6d781 | |||
| 53b96adc79 | |||
| 4e86faf2aa | |||
| 4b17721fb0 | |||
| 870418712f | |||
| 068c6b8d94 | |||
| 60734c55ce | |||
| e5c5e5f5a9 | |||
| 16ed20bd9b | |||
| 5840baa657 | |||
| 0271a9339d | |||
| 8a28d841e3 | |||
| 04447390fc | |||
| 4ee925de45 | |||
| 271f2fa4a0 | |||
| cfbab3f98b | |||
| 62145ecfe2 | |||
| 42e4021ef5 | |||
| e7125c07e9 | |||
| bcc28c6a8e | |||
| fab1554c70 | |||
| 05d636fc08 | |||
| 62fcf737d0 | |||
| 607a45ca8b | |||
| d5c6b751f9 | |||
| 50df87fb3e | |||
| 3f05bc36cc | |||
| ec9e7701da | |||
| 9ab2710114 | |||
| 8f1e70f51e | |||
| 7864cddd32 | |||
| 00c0e77c52 | |||
| 0111795e46 | |||
| 5fc780dac6 | |||
| 4bf7af08d5 | |||
| 6be78117c2 | |||
| 5f0e3bb730 | |||
| b230c5848d | |||
| 8bb8aff6c4 | |||
| 974fadf3d9 | |||
| 062b59dc50 | |||
| 6b7f7cfa16 | |||
| 4953f17364 | |||
| 798466f497 | |||
| 000d5be73e |
83
CHANGELOG.md
83
CHANGELOG.md
@@ -1,14 +1,91 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## v0.2.0 (unreleased)
|
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
|
||||||
|
|
||||||
- Sorting of bookmarks
|
- Sorting of bookmarks
|
||||||
- Sort by title
|
- Sort by title
|
||||||
- Sort by date
|
- Sort by date
|
||||||
|
- Logging of actions
|
||||||
|
- 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
|
||||||
|
|
||||||
|
|
||||||
## v0.1.0
|
## [Unreleased]
|
||||||
|
|
||||||
2016-07-26
|
|
||||||
|
## [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:
|
||||||
|
- Show which tags have public pages, with link
|
||||||
|
- 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
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.0] - 2016-08-02
|
||||||
|
|
||||||
|
- Favicon courtesy Freepik on flaticon.com
|
||||||
|
- Tag tags for easy adding of tags
|
||||||
|
- Updates to MaterializeCSS and jQuery
|
||||||
|
- Several bug- and code style fixes
|
||||||
|
- Styling tweaks
|
||||||
|
- Added 'Add bookmark' FAB to the bookmarks overview
|
||||||
|
- Option to strip parameters from url (like '?utm_source=social')
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.0] - 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,
|
||||||
|
|||||||
17
README.rst
17
README.rst
@@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@@ -72,6 +82,12 @@ What's new?
|
|||||||
See the `Changelog`_.
|
See the `Changelog`_.
|
||||||
|
|
||||||
|
|
||||||
|
Attributions
|
||||||
|
------------
|
||||||
|
|
||||||
|
'M' favicon by `Freepik`_.
|
||||||
|
|
||||||
|
|
||||||
.. _digimarks: https://github.com/aquatix/digimarks
|
.. _digimarks: https://github.com/aquatix/digimarks
|
||||||
.. _webhook: https://en.wikipedia.org/wiki/Webhook
|
.. _webhook: https://en.wikipedia.org/wiki/Webhook
|
||||||
.. |PyPI version| image:: https://img.shields.io/pypi/v/digimarks.svg
|
.. |PyPI version| image:: https://img.shields.io/pypi/v/digimarks.svg
|
||||||
@@ -87,3 +103,4 @@ See the `Changelog`_.
|
|||||||
.. _vhost for Apache2.4: https://github.com/aquatix/digimarks/blob/master/example_config/apache_vhost.conf
|
.. _vhost for Apache2.4: https://github.com/aquatix/digimarks/blob/master/example_config/apache_vhost.conf
|
||||||
.. _uwsgi.ini: https://github.com/aquatix/digimarks/blob/master/example_config/uwsgi.ini
|
.. _uwsgi.ini: https://github.com/aquatix/digimarks/blob/master/example_config/uwsgi.ini
|
||||||
.. _Changelog: https://github.com/aquatix/digimarks/blob/master/CHANGELOG.md
|
.. _Changelog: https://github.com/aquatix/digimarks/blob/master/CHANGELOG.md
|
||||||
|
.. _Freepik: http://www.flaticon.com/free-icon/letter-m_2041
|
||||||
|
|||||||
409
digimarks.py
409
digimarks.py
@@ -1,19 +1,77 @@
|
|||||||
|
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
|
|
||||||
|
|
||||||
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 *
|
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
|
||||||
@@ -45,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))
|
||||||
@@ -56,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):
|
||||||
@@ -75,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='')
|
||||||
@@ -84,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)
|
||||||
@@ -132,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):
|
||||||
@@ -141,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 """
|
||||||
@@ -155,6 +283,26 @@ 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
|
||||||
|
def strip_url_params(cls, url):
|
||||||
|
parsed = urlparse(url)
|
||||||
|
return urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, '', parsed.fragment))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tags_list(self):
|
def tags_list(self):
|
||||||
""" Get the tags as a list, iterable in template """
|
""" Get the tags as a list, iterable in template """
|
||||||
@@ -168,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,
|
||||||
}
|
}
|
||||||
@@ -196,20 +344,42 @@ def get_tags_for_user(userkey):
|
|||||||
return clean_tags(tags)
|
return clean_tags(tags)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cached_tags(userkey):
|
||||||
|
""" Fail-safe way to get the cached tags for `userkey` """
|
||||||
|
try:
|
||||||
|
return all_tags[userkey]
|
||||||
|
except KeyError:
|
||||||
|
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'), 404
|
return render_template('404.html', error=e), 404
|
||||||
|
|
||||||
|
|
||||||
@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)
|
||||||
@@ -219,14 +389,41 @@ def bookmarks(userkey, sortmethod = None):
|
|||||||
#else:
|
#else:
|
||||||
# abort(404)
|
# abort(404)
|
||||||
message = request.args.get('message')
|
message = request.args.get('message')
|
||||||
if request.method == 'POST':
|
tags = get_cached_tags(userkey)
|
||||||
filter_on = request.form['filter']
|
|
||||||
bookmarks = Bookmark.select().where(Bookmark.userkey == userkey, Bookmark.title.contains(filter_on),
|
filter_text = ''
|
||||||
|
if request.form:
|
||||||
|
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=all_tags[userkey], 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=all_tags[userkey], 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -240,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>')
|
||||||
@@ -249,16 +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')
|
||||||
return render_template('edit.html', action='Edit bookmark', userkey=userkey, bookmark=bookmark, message=message, formaction='edit')
|
tags = get_cached_tags(userkey)
|
||||||
|
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')
|
||||||
return render_template('edit.html', action='Add bookmark', userkey=userkey, bookmark=bookmark)
|
if not url:
|
||||||
|
url = ''
|
||||||
|
if request.args.get('referrer'):
|
||||||
|
url = request.referrer
|
||||||
|
bookmark = Bookmark(title='', url=url, tags='')
|
||||||
|
message = request.args.get('message')
|
||||||
|
tags = get_cached_tags(userkey)
|
||||||
|
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):
|
||||||
@@ -266,9 +479,13 @@ 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
|
||||||
|
strip_params = False
|
||||||
|
if request.form.get('strip'):
|
||||||
|
strip_params = True
|
||||||
|
|
||||||
if url and not urlhash:
|
if url and not urlhash:
|
||||||
# New bookmark
|
# New bookmark
|
||||||
@@ -278,14 +495,20 @@ def updatebookmark(userkey, request, urlhash = None):
|
|||||||
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash, message=message))
|
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash, message=message))
|
||||||
elif url:
|
elif url:
|
||||||
# Existing bookmark, get from DB
|
# Existing bookmark, get from DB
|
||||||
bookmark = Bookmark.get(userkey == userkey, Bookmark.url_hash == urlhash)
|
bookmark = Bookmark.get(Bookmark.userkey == userkey, Bookmark.url_hash == urlhash)
|
||||||
# Editing this bookmark, set modified_date to now
|
# Editing this bookmark, set modified_date to now
|
||||||
bookmark.modified_date = datetime.datetime.now()
|
bookmark.modified_date = datetime.datetime.now()
|
||||||
|
else:
|
||||||
|
# No url was supplied, abort. @TODO: raise exception?
|
||||||
|
return None
|
||||||
|
|
||||||
bookmark.title = title
|
bookmark.title = title
|
||||||
|
if strip_params:
|
||||||
|
url = Bookmark.strip_url_params(url)
|
||||||
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:
|
||||||
@@ -295,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
|
||||||
@@ -305,14 +532,17 @@ def updatebookmark(userkey, request, urlhash = None):
|
|||||||
#@app.route('/<userkey>/adding')
|
#@app.route('/<userkey>/adding')
|
||||||
def addingbookmark(userkey):
|
def addingbookmark(userkey):
|
||||||
""" Add the bookmark from form submit by /add """
|
""" Add the bookmark from form submit by /add """
|
||||||
|
tags = get_cached_tags(userkey)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
bookmark = updatebookmark(userkey, request)
|
bookmark = updatebookmark(userkey, request)
|
||||||
|
if not bookmark:
|
||||||
|
return redirect(url_for('addbookmark', userkey=userkey, message='No url provided', tags=tags))
|
||||||
if type(bookmark).__name__ == 'Response':
|
if type(bookmark).__name__ == 'Response':
|
||||||
return bookmark
|
return bookmark
|
||||||
all_tags[userkey] = get_tags_for_user(userkey)
|
all_tags[userkey] = get_tags_for_user(userkey)
|
||||||
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash))
|
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash))
|
||||||
return redirect(url_for('add'))
|
return redirect(url_for('addbookmark', userkey=userkey, tags=tags))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/<userkey>/<urlhash>/editing', methods=['GET', 'POST'])
|
@app.route('/<userkey>/<urlhash>/editing', methods=['GET', 'POST'])
|
||||||
@@ -323,7 +553,7 @@ def editingbookmark(userkey, urlhash):
|
|||||||
bookmark = updatebookmark(userkey, request, urlhash=urlhash)
|
bookmark = updatebookmark(userkey, request, urlhash=urlhash)
|
||||||
all_tags[userkey] = get_tags_for_user(userkey)
|
all_tags[userkey] = get_tags_for_user(userkey)
|
||||||
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash))
|
return redirect(url_for('editbookmark', userkey=userkey, urlhash=bookmark.url_hash))
|
||||||
return redirect(url_for('add'))
|
return redirect(url_for('editbookmark', userkey=userkey, urlhash=urlhash))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/<userkey>/<urlhash>/delete', methods=['GET', 'POST'])
|
@app.route('/<userkey>/<urlhash>/delete', methods=['GET', 'POST'])
|
||||||
@@ -333,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))
|
||||||
|
|
||||||
@@ -341,16 +581,35 @@ def deletingbookmark(userkey, urlhash):
|
|||||||
@app.route('/<userkey>/tags')
|
@app.route('/<userkey>/tags')
|
||||||
def tags(userkey):
|
def tags(userkey):
|
||||||
""" Overview of all tags used by user """
|
""" Overview of all tags used by user """
|
||||||
tags = get_tags_for_user(userkey)
|
tags = get_cached_tags(userkey)
|
||||||
print tags
|
#publictags = PublicTag.select().where(Bookmark.userkey == userkey)
|
||||||
return render_template('tags.html', tags=tags, 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')
|
||||||
|
|
||||||
@@ -359,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)
|
||||||
|
|
||||||
@@ -399,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 """
|
||||||
@@ -413,18 +719,37 @@ 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)
|
||||||
|
|
||||||
users = User.select()
|
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)
|
||||||
print user.key
|
settings[user.key] = {'theme': user.theme}
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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
7
requirements.in
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pkg-resources==0.0.0
|
||||||
|
|
||||||
|
flask
|
||||||
|
peewee
|
||||||
|
flask-peewee
|
||||||
|
bs4
|
||||||
|
requests
|
||||||
@@ -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
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -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.1.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'],
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
<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"/>
|
<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 -->
|
<!-- Chrome, Firefox OS and Opera -->
|
||||||
<meta name="theme-color" content="#2e7d32" />
|
<meta name="theme-color" content="#2e7d32" />
|
||||||
@@ -16,11 +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.6/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>
|
||||||
|
/* 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 %}
|
||||||
@@ -29,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>
|
||||||
@@ -53,8 +68,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/js/materialize.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/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>
|
||||||
|
|||||||
@@ -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,24 +35,39 @@
|
|||||||
<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">
|
||||||
<p class="left-align"><button class="btn btn-large waves-effect waves-light" type="submit" name="submit">Filter</button></p>
|
<p class="left-align"><button class="btn waves-effect waves-light" type="submit" name="submit">Filter</button></p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% 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) }}" />
|
|
||||||
{% 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) }}" />
|
<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>
|
||||||
@@ -118,4 +139,10 @@
|
|||||||
</div>
|
</div>
|
||||||
#}
|
#}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<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 %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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> URL not found (404), broken/outdated link?
|
<i class="material-icons">report_problem</i> URL not found (404), broken/outdated link?
|
||||||
|
{% 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 %}
|
{% else %}
|
||||||
<i class="material-icons">report_problem</i> HTTP status {{ bookmark.http_status }}
|
<i class="material-icons">report_problem</i> 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">
|
||||||
@@ -56,6 +78,36 @@
|
|||||||
<input placeholder="tags, divided by comma's" type="text" name="tags" id="tags" value="{{ bookmark.tags }}" class="validate" />
|
<input placeholder="tags, divided by comma's" type="text" name="tags" id="tags" value="{{ bookmark.tags }}" class="validate" />
|
||||||
<label for="tags">Tags</label>
|
<label for="tags">Tags</label>
|
||||||
</div>
|
</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="tag_{{ tag }}">
|
||||||
|
{{ tag }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('#tag_{{ tag }}').on('click', function () {
|
||||||
|
var text = $('#tags');
|
||||||
|
text.val(text.val() + ', {{ tag }}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
<div class="input-field col s12">
|
<div class="input-field col s12">
|
||||||
{#<i class="material-icons prefix">star</i>#}
|
{#<i class="material-icons prefix">star</i>#}
|
||||||
@@ -65,7 +117,7 @@
|
|||||||
|
|
||||||
<div class="input-field col s12">
|
<div class="input-field col s12">
|
||||||
<input type="checkbox" name="strip" id="strip" />
|
<input type="checkbox" name="strip" id="strip" />
|
||||||
<label for="strip">Strip parameters from url</label>
|
<label for="strip">Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if bookmark.url_hash %}
|
{% if bookmark.url_hash %}
|
||||||
@@ -96,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 btn-large 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 btn-large 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 %}
|
||||||
|
|||||||
@@ -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) }}" />
|
{% 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) }}" />
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
5
wsgi.py
5
wsgi.py
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user