This commit is contained in:
rlx 2019-01-29 16:12:46 +05:30
commit 760494dd6d
57 changed files with 224 additions and 155 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
[*]
end_of_line = lf
insert_final_newline = true
[*.{js,py,html}]
indent_style = space
indent_size = 4
charset = utf-8
[Makefile]
indent_style = tab

12
ctl
View File

@ -162,12 +162,14 @@ if [ "$1" == "open" ]; then
xdg-open "file://${BASE}/openmedialibrary/static/html/load.html" xdg-open "file://${BASE}/openmedialibrary/static/html/load.html"
fi fi
else else
$PYTHON "${NAME}/oml/gtkstatus.py" $@ #$PYTHON "${NAME}/oml/gtkstatus.py" $@
exit $? #exit $?
"$0" start &
fi fi
else else
$PYTHON "$NAME/oml/gtkstatus.py" $@ #$PYTHON "$NAME/oml/gtkstatus.py" $@
exit $? #exit $?
"$0" start &
fi fi
fi fi
exit 0 exit 0
@ -187,7 +189,7 @@ fi
if [ "$1" == "ui" ]; then if [ "$1" == "ui" ]; then
shift shift
$PYTHON "$NAME/oml/ui.py" $@ $PYTHON "$NAME/oml/ui.py" "$@"
exit $? exit $?
fi fi
if [ "$1" == "init" ]; then if [ "$1" == "init" ]; then

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
import sys import sys

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from os.path import normpath, dirname, abspath, join from os.path import normpath, dirname, abspath, join

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
from datetime import datetime from datetime import datetime

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from os.path import join, exists, dirname from os.path import join, exists, dirname

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
from threading import Thread from threading import Thread

View File

@ -9,39 +9,48 @@ import webbrowser
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib from gi.repository import Gtk, GLib
try:
gi.require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3 as appindicator
except:
appindicator = None
base = dirname(dirname(dirname(abspath(__file__)))) base = dirname(dirname(dirname(abspath(__file__))))
icon = os.path.join(base, 'openmedialibrary/static/png/oml.png') icon = os.path.join(base, 'openmedialibrary/static/svg/oml.svg')
title = "Open Media Library" title = "Open Media Library"
ctl = base + '/ctl' ctl = base + '/ctl'
class OMLIcon: def check_pid(pid):
if not os.path.exists(pid):
return False
try:
with open(pid, 'r') as fd:
pid = int(fd.read().strip())
os.kill(pid, 0)
except OSError:
return False
else:
return True
def preexec(): # Don't forward signals.
os.setpgrp()
class OMLStatus:
menu = None menu = None
icon = None icon = None
indicator = None
def __init__(self, autostart=False): def __init__(self, autostart=False):
self.autostart = autostart self.autostart = autostart
self.create_pid() self.create_pid()
if appindicator: self.win = Gtk.Window()
self.indicator = appindicator.Indicator.new("OML", icon, self.win.set_icon_from_file(icon)
appindicator.IndicatorCategory.APPLICATION_STATUS) self.win.set_title(title)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) #self.win.show_all()
self.menu = self.get_menu() self.win.iconify()
self.indicator.set_menu(self.menu) '''
else: self.icon = Gtk.StatusIcon()
self.icon = Gtk.StatusIcon() self.icon.set_from_file(icon)
self.icon.set_from_file(icon) self.icon.set_title(title)
self.icon.set_title(title) self.icon.connect("activate", self._click)
self.icon.connect("activate", self._click) self.icon.connect("popup-menu", self._click)
self.icon.connect("popup-menu", self._click) '''
subprocess.Popen([ctl, 'start'], close_fds=True, preexec_fn=preexec) subprocess.Popen([ctl, 'start'], close_fds=True, preexec_fn=preexec)
if not self.autostart: if not self.autostart:
GLib.timeout_add_seconds(1, self._open, None) GLib.timeout_add_seconds(1, self._open, None)
@ -81,7 +90,7 @@ class OMLIcon:
@classmethod @classmethod
def is_running(cls): def is_running(cls):
pid = cls.get_pid() pid = cls.get_pid()
if pid and os.path.exists(pid): if pid and check_pid(pid):
return True return True
else: else:
return False return False
@ -140,16 +149,14 @@ class OMLIcon:
url += '#%s' % port url += '#%s' % port
webbrowser.open_new_tab(url) webbrowser.open_new_tab(url)
def preexec(): # Don't forward signals.
os.setpgrp()
if __name__ == '__main__': if __name__ == '__main__':
import signal import signal
autostart = len(sys.argv) > 1 and sys.argv[1] == '--autostart' autostart = len(sys.argv) > 1 and sys.argv[1] == '--autostart'
if OMLIcon.is_running(): if OMLStatus.is_running():
OMLIcon.load() OMLStatus.load()
else: else:
oml = OMLIcon(autostart) oml = OMLStatus(autostart)
main_loop = GLib.MainLoop() main_loop = GLib.MainLoop()
try: try:
main_loop.run() main_loop.run()

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import json import json
import hashlib import hashlib
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime
@ -7,6 +6,7 @@ import mimetypes
import os import os
from urllib.request import quote from urllib.request import quote
import zipfile import zipfile
import base64
import ox import ox
@ -27,7 +27,42 @@ import state
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class OMLHandler(tornado.web.RequestHandler):
class OptionalBasicAuthMixin(object):
class SendChallenge(Exception):
pass
def prepare(self):
if settings.preferences.get('authentication'):
try:
self.authenticate_user()
except self.SendChallenge:
self.send_auth_challenge()
def send_auth_challenge(self):
realm = "Open Media Library"
hdr = 'Basic realm="%s"' % realm
self.set_status(401)
self.set_header('www-authenticate', hdr)
self.finish()
return False
def authenticate_user(self):
auth_header = self.request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Basic '):
raise self.SendChallenge()
auth_data = auth_header.split(None, 1)[-1]
auth_data = base64.b64decode(auth_data).decode('ascii')
username, password = auth_data.split(':', 1)
auth = settings.preferences.get('authentication')
if auth.get('username') == username and auth.get('password') == password:
self._current_user = username
else:
raise self.SendChallenge()
class OMLHandler(OptionalBasicAuthMixin, tornado.web.RequestHandler):
def initialize(self): def initialize(self):
pass pass
@ -141,7 +176,7 @@ class ReaderHandler(OMLHandler):
path = os.path.join(settings.static_path, html) path = os.path.join(settings.static_path, html)
return serve_static(self, path, 'text/html') return serve_static(self, path, 'text/html')
class UploadHandler(tornado.web.RequestHandler): class UploadHandler(OMLHandler):
def initialize(self, context=None): def initialize(self, context=None):
self._context = context self._context = context

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
import sqlite3 import sqlite3
@ -23,23 +22,32 @@ logger = logging.getLogger(__name__)
MAX_WORKERS = 4 MAX_WORKERS = 4
class Icons(dict): class Icons(dict):
def __init__(self, db): def __init__(self, db):
self._db = db self._db = db
self.create() self.create()
def is_available(self):
folder = os.path.dirname(self._db)
if os.path.exists(folder):
if not os.path.exists(self._db):
self.create()
return os.path.exists(self._db)
def connect(self): def connect(self):
conn = sqlite3.connect(self._db, timeout=90) conn = sqlite3.connect(self._db, timeout=90)
return conn return conn
def create(self): def create(self):
conn = self.connect() folder = os.path.dirname(self._db)
c = conn.cursor() if os.path.exists(folder):
c.execute('CREATE TABLE IF NOT EXISTS icon (id varchar(64) unique, data blob)') conn = self.connect()
c.execute('CREATE TABLE IF NOT EXISTS setting (key varchar(256) unique, value text)') c = conn.cursor()
if int(self.get_setting(c, 'version', 0)) < 1: c.execute('CREATE TABLE IF NOT EXISTS icon (id varchar(64) unique, data blob)')
self.set_setting(c, 'version', 1) c.execute('CREATE TABLE IF NOT EXISTS setting (key varchar(256) unique, value text)')
if int(self.get_setting(c, 'version', 0)) < 1:
self.set_setting(c, 'version', 1)
def get_setting(self, c, key, default=None): def get_setting(self, c, key, default=None):
c.execute('SELECT value FROM setting WHERE key = ?', (key, )) c.execute('SELECT value FROM setting WHERE key = ?', (key, ))
@ -105,7 +113,7 @@ class Icons(dict):
c.close() c.close()
conn.close() conn.close()
except: except:
logger.debug('failed to clear icon %s', id) logger.debug('failed to clear icon %s', prefix)
def vacuum(self, ids): def vacuum(self, ids):
conn = self.connect() conn = self.connect()
@ -140,6 +148,8 @@ def get_icons_db_path():
return icons_db_path return icons_db_path
def get_icon_sync(id, type_, size): def get_icon_sync(id, type_, size):
if not icons.is_available():
return ''
if size: if size:
skey = '%s:%s:%s' % (type_, id, size) skey = '%s:%s:%s' % (type_, id, size)
data = icons[skey] data = icons[skey]

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime
import base64 import base64
import hashlib import hashlib

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import unicodedata import unicodedata

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import unicodedata import unicodedata
from oxtornado import actions from oxtornado import actions

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
#does not work in sqlite #does not work in sqlite

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import hashlib import hashlib
import json import json
import unicodedata import unicodedata

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import json import json
import os import os
import time import time

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import socket import socket

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import base64 import base64

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import sys import sys

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import stdnum.isbn import stdnum.isbn

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import ox.web.duckduckgo import ox.web.duckduckgo

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from time import time, sleep from time import time, sleep
from urllib.parse import urlencode from urllib.parse import urlencode

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import re import re

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import hashlib import hashlib
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from user.models import User from user.models import User
@ -82,9 +81,9 @@ def api_upload(user_id, items):
from user.models import List from user.models import List
peer = User.get(user_id) peer = User.get(user_id)
if peer: if peer:
l = List.get_or_create(':Public') l = List.get_or_create(':Inbox')
if l: if l:
logger.debug('%s added items to public folder: %s', user_id, items) logger.debug('%s added items to inbox: %s', user_id, items)
l.add_items(items) l.add_items(items)
trigger_event('change', {}) trigger_event('change', {})
return True return True

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
from threading import Thread from threading import Thread
import base64 import base64

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from queue import Queue from queue import Queue
@ -462,7 +461,7 @@ class Node(Thread):
return False return False
def upload(self, items): def upload(self, items):
logger.debug('add items to %s\'s public folder: %s', self.id, items) logger.debug('add items to %s\'s inbox: %s', self.user_id, items)
r = self.request('upload', items) r = self.request('upload', items)
return bool(r) return bool(r)

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from contextlib import contextmanager from contextlib import contextmanager

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
import json import json

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime
import unicodedata import unicodedata

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os
import sys import sys

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import json import json
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import os import os

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import ssl import ssl
import http.client import http.client

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from queue import PriorityQueue from queue import PriorityQueue
from threading import Thread from threading import Thread
import json import json

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import ssl import ssl
import http.client import http.client

View File

@ -1,5 +1,4 @@
# encoding: utf-8 # encoding: utf-8
# vi:si:et:sw=4:sts=4:ts=4
import sys import sys
import os import os
try: try:
@ -105,7 +104,8 @@ if __name__ == '__main__':
if len(sys.argv) >= 3: if len(sys.argv) >= 3:
base = sys.argv[2] base = sys.argv[2]
base = os.path.expanduser(base) base = os.path.expanduser(base)
os.chdir(base) if os.path.exists(base):
os.chdir(base)
if len(sys.argv) >= 2 and sys.argv[1] == 'folder': if len(sys.argv) >= 2 and sys.argv[1] == 'folder':
print(ui.selectFolder({})) print(ui.selectFolder({}))
else: else:

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import json import json

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from contextlib import closing from contextlib import closing
import base64 import base64

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from copy import deepcopy from copy import deepcopy
@ -189,7 +188,7 @@ def getLists(data):
'type': 'libraries', 'type': 'libraries',
'user': None, 'user': None,
}) })
List.get_or_create(':Public') List.get_or_create(':Inbox')
for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)): for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)):
lists += u.lists_json() lists += u.lists_json()
return { return {
@ -290,7 +289,7 @@ def removeList(data):
} }
''' '''
l = models.List.get(data['id']) l = models.List.get(data['id'])
if l and l.name != 'Public': if l and l.name != 'Inbox':
l.remove() l.remove()
return {} return {}
actions.register(removeList, cache=False) actions.register(removeList, cache=False)
@ -309,12 +308,10 @@ def addListItems(data):
i = Item.get(item_id) i = Item.get(item_id)
i.queue_download() i.queue_download()
i.update() i.update()
elif data['list'] and (data['list'].startswith(':') or elif data['list'] and (data['list'].startswith(':') or data['list'].endswith(':')):
data['list'].endswith(':Public') or
data['list'].enswtih(':')):
l = models.List.get_or_create(data['list']) l = models.List.get_or_create(data['list'])
if l: if l:
if l.name in ('Public', '') and l.user_id != settings.USER_ID: if l.name in ('Inbox', '') and l.user_id != settings.USER_ID:
state.tasks.queue('upload', { state.tasks.queue('upload', {
'user': l.user_id, 'user': l.user_id,
'items': data['items'] 'items': data['items']
@ -351,12 +348,11 @@ def sortLists(data):
''' '''
n = 0 n = 0
logger.debug('sortLists %s', data) logger.debug('sortLists %s', data)
lists = ['Public']
for id in data['ids']: for id in data['ids']:
l = models.List.get(id) l = models.List.get(id)
l.index_ = n l.index_ = n
n += 1 n += 1
if l.type == 'static': if l.type == 'static' and l.name not in ('', 'Inbox'):
lists.append(l.name) lists.append(l.name)
state.db.session.add(l) state.db.session.add(l)
state.db.session.commit() state.db.session.commit()

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime
import json import json
import os import os
@ -218,7 +217,7 @@ class User(db.Model):
Changelog.record(self, 'edititem', item.id, item.meta, _commit=False) Changelog.record(self, 'edititem', item.id, item.meta, _commit=False)
lists = [] lists = []
for l in List.query.filter_by(user_id=self.id, type='static').order_by('index_'): for l in List.query.filter_by(user_id=self.id, type='static').order_by('index_'):
if l.name: if l.name and l.name != 'Inbox':
lists.append(l.name) lists.append(l.name)
Changelog.record(self, 'addlist', l.name, _commit=False) Changelog.record(self, 'addlist', l.name, _commit=False)
items = [i.id for i in l.get_items().options(load_only('id'))] items = [i.id for i in l.get_items().options(load_only('id'))]
@ -301,7 +300,7 @@ class List(db.Model):
state.db.session.add(l) state.db.session.add(l)
state.db.session.commit() state.db.session.commit()
if user_id == settings.USER_ID: if user_id == settings.USER_ID:
if l.type == 'static' and name != '': if l.type == 'static' and name != '' and name != 'Inbox':
add_record('addlist', l.name) add_record('addlist', l.name)
return l return l

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime from datetime import datetime

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from tornado.websocket import WebSocketHandler from tornado.websocket import WebSocketHandler

View File

@ -1,34 +1,56 @@
'use strict'; 'use strict';
oml.ui.annotation = function(data, $iframe) { oml.ui.annotation = function(annotation, $iframe) {
var $quote = Ox.Element().addClass('OxSelectable OMLQuote').css({ var $quote = Ox.Element().addClass('OxSelectable OMLQuote').css({
backgroundColor: 'white',
color: 'black', color: 'black',
fontFamily: 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif', fontFamily: 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif',
fontSize: '14px', fontSize: '14px',
lineHeight: '21px', lineHeight: '21px',
padding: '8px' padding: '8px'
}).html(Ox.encodeHTMLEntities(data.text).replace(/\n/g, '<br/>')).on({ }).html(Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '<br/>')).on({
click: function(event) { click: function(event) {
that.select() that.select()
$iframe.postMessage('selectAnnotation', { $iframe.postMessage('selectAnnotation', {
id: data.id id: annotation.id
}) })
} }
}) })
var $note = Ox.Editable({ var $note = Ox.ArrayEditable({
editing: true, editing: true,
items: (annotation.comments || []).map(function(comment) {
comment.editable = true
return comment
}),
type: 'textarea' type: 'textarea'
}).css({ }).css({
margin: '2px', margin: '2px',
minHeight: '12px' minHeight: '12px'
}).bindEvent({
submit: function(data) {
var comment = Ox.getObjectById(annotation.comments, data.id)
if (comment) {
comment.value = data.value
comment.modified = (new Date).toISOString()
} else {
annotation.comments.push({
created: data.created || (new Date).toISOString(),
modified: (new Date).toISOString(),
id: data.id,
value: data.value
})
}
that.triggerEvent('change')
}
}); });
var that = Ox.Element().attr({ var that = Ox.Element().attr({
id: 'a-' + data.id id: 'a-' + annotation.id
}).addClass( }).addClass(
'OxSelectable OMLAnnotation' 'OxSelectable OMLAnnotation'
).css({ ).css({
borderBottom: '1px solid rgb(208, 208, 208)', borderBottom: '1px solid rgb(208, 208, 208)',
}).append($quote).append($note); }).append($quote).append($note);
that.annotate = function() { that.annotate = function() {
var item = { var item = {
id: 'note', value: '', editable: true id: 'note', value: '', editable: true

View File

@ -3,8 +3,9 @@ oml.ui.annotationFolder = function() {
var ui = oml.user.ui, var ui = oml.user.ui,
that = Ox.Element().css({ that = Ox.Element().css({
overflowY: 'auto', overflowY: 'auto',
overflowX: 'hidden',
}); });
return that; return that;
}; };

View File

@ -13,7 +13,7 @@ oml.ui.folderList = function(options) {
src: Ox.UI.getImageURL( src: Ox.UI.getImageURL(
value == 'libraries' ? 'symbolData' value == 'libraries' ? 'symbolData'
: value == 'library' ? 'symbolUser' : value == 'library' ? 'symbolUser'
: data.name == 'Public' ? 'symbolPublish' : data.name == 'Inbox' ? 'symbolPublish'
: value == 'static' ? 'symbolClick' : value == 'static' ? 'symbolClick'
: 'symbolFind' : 'symbolFind'
) )

View File

@ -42,7 +42,7 @@ oml.ui.folders = function() {
}).indexOf(list.user) : null; }).indexOf(list.user) : null;
return list.id == '' ? oml.$ui.librariesList return list.id == '' ? oml.$ui.librariesList
: Ox.endsWith(list.id, ':') ? oml.$ui.libraryList[index] : Ox.endsWith(list.id, ':') ? oml.$ui.libraryList[index]
: Ox.endsWith(list.id, ':Public') ? oml.$ui.publicList[index] : Ox.endsWith(list.id, ':Inbox') ? oml.$ui.inboxList[index]
: oml.$ui.folderList[index]; : oml.$ui.folderList[index];
} }
@ -66,7 +66,7 @@ oml.ui.folders = function() {
!list ? 'libraryList' !list ? 'libraryList'
: 'folderList' : 'folderList'
][index] ][index]
: list == 'Public' ? oml.$ui.publicList[index] : list == 'Inbox' ? oml.$ui.inboxList[index]
: oml.$ui.folderList[index]; : oml.$ui.folderList[index];
$lists.forEach(function($list) { $lists.forEach(function($list) {
if ($list == $selectedList) { if ($list == $selectedList) {
@ -87,7 +87,7 @@ oml.ui.folders = function() {
oml.$ui.folder = []; oml.$ui.folder = [];
oml.$ui.libraryList = []; oml.$ui.libraryList = [];
oml.$ui.folderList = []; oml.$ui.folderList = [];
oml.$ui.publicList = []; oml.$ui.inboxList = [];
getUsersAndLists(function(users, lists) { getUsersAndLists(function(users, lists) {
@ -120,10 +120,11 @@ oml.ui.folders = function() {
var $content, var $content,
items = lists.filter(function(list) { items = lists.filter(function(list) {
return list.user === user.name return list.user === user.name
&& list.type != 'library' && list.name != 'Public'; && list.type != 'library' && list.name != 'Inbox';
}), }),
libraryId = user.name + ':', libraryId = user.name + ':',
publicId = user.name + ':Public'; inboxId = user.name + ':Inbox',
offset = 16;
userIndex[user.name] = index; userIndex[user.name] = index;
@ -175,7 +176,8 @@ oml.ui.folders = function() {
$content = oml.$ui.folder[index].$content $content = oml.$ui.folder[index].$content
.css({ .css({
height: (2 + items.length) * 16 + 'px' // user also has inbox
height: ((index ? 1 : 2) + items.length) * 16 + 'px'
}); });
$lists.push( $lists.push(
@ -193,7 +195,7 @@ oml.ui.folders = function() {
oml.UI.set({find: getFind(data.ids[0])}); oml.UI.set({find: getFind(data.ids[0])});
}, },
selectnext: function() { selectnext: function() {
oml.UI.set({find: getFind(publicId)}); oml.UI.set({find: getFind(inboxId)});
}, },
selectprevious: function() { selectprevious: function() {
// FIXME: ugly // FIXME: ugly
@ -216,27 +218,29 @@ oml.ui.folders = function() {
}) })
.appendTo($content) .appendTo($content)
); );
if (user.name == '') {
$lists.push( $lists.push(
oml.$ui.publicList[index] = oml.ui.folderList({ oml.$ui.inboxList[index] = oml.ui.folderList({
items: lists.filter(function(list) { items: lists.filter(function(list) {
return list.user == user.name && list.name == 'Public'; return list.user == user.name && list.name == 'Inbox';
})
}) })
}) .bindEvent({
.bindEvent({ select: function(data) {
select: function(data) { oml.UI.set({find: getFind(data.ids[0])});
oml.UI.set({find: getFind(data.ids[0])}); },
}, selectnext: function() {
selectnext: function() { oml.UI.set({find: getFind(items[0].id)});
oml.UI.set({find: getFind(items[0].id)}); },
}, selectprevious: function() {
selectprevious: function() { oml.UI.set({find: getFind(libraryId)});
oml.UI.set({find: getFind(libraryId)}); }
} })
}) .appendTo($content)
.appendTo($content) );
); oml.$ui.inboxList[index].$body.css({top: offset + 'px'});
oml.$ui.publicList[index].$body.css({top: '16px'}); offset += 16;
}
$lists.push( $lists.push(
oml.$ui.folderList[index] = oml.ui.folderList({ oml.$ui.folderList[index] = oml.ui.folderList({
@ -258,7 +262,7 @@ oml.ui.folders = function() {
} }
}, },
'delete': function(data) { 'delete': function(data) {
!index && !Ox.contains(data.ids, ':Public') && oml.ui.deleteListDialog().open(); !index && !Ox.contains(data.ids, ':Inbox') && oml.ui.deleteListDialog().open();
}, },
key_control_d: function() { key_control_d: function() {
oml.addList(ui._list); oml.addList(ui._list);
@ -275,7 +279,7 @@ oml.ui.folders = function() {
}); });
}, },
open: function(data) { open: function(data) {
!index && !Ox.contains(data.ids, ':Public') && oml.ui.listDialog().open(); !index && !Ox.contains(data.ids, ':Inbox') && oml.ui.listDialog().open();
}, },
select: function(data) { select: function(data) {
oml.UI.set({find: getFind(data.ids[0])}); oml.UI.set({find: getFind(data.ids[0])});
@ -290,14 +294,14 @@ oml.ui.folders = function() {
} }
}, },
selectprevious: function() { selectprevious: function() {
oml.UI.set({find: getFind(publicId)}); oml.UI.set({find: getFind(inboxId)});
} }
}) })
.css({height: items.length * 16 + 'px'}) .css({height: items.length * 16 + 'px'})
.appendTo($content) .appendTo($content)
); );
oml.$ui.folderList[index].$body.css({top: '32px'}); oml.$ui.folderList[index].$body.css({top: offset + 'px'});
}); });
@ -353,7 +357,7 @@ oml.ui.folders = function() {
oml.$ui.folder[index].options({title: Ox.encodeHTMLEntities(name)}); oml.$ui.folder[index].options({title: Ox.encodeHTMLEntities(name)});
oml.getLists(function(lists) { oml.getLists(function(lists) {
var items = lists.filter(function(list) { var items = lists.filter(function(list) {
return list.user == name && list.type != 'library' && list.name != 'Public'; return list.user == name && list.type != 'library' && list.name != 'Inbox';
}), }),
library = lists.filter(function(list) { library = lists.filter(function(list) {
return list.user == name && list.type == 'library'; return list.user == name && list.type == 'library';
@ -362,8 +366,9 @@ oml.ui.folders = function() {
oml.$ui.libraryList[index].options({ oml.$ui.libraryList[index].options({
items: library items: library
}); });
// library + inbox + lists
oml.$ui.folder[index].$content oml.$ui.folder[index].$content
.css({height: 16 + items.length * 16 + 'px'}); .css({height: (index ? 16 : 32) + items.length * 16 + 'px'});
oml.$ui.folderList[index].options({ oml.$ui.folderList[index].options({
items: items items: items
}) })

View File

@ -601,6 +601,7 @@ oml.ui.mainMenu = function() {
that[data.value ? 'enableItem' : 'disableItem']('book'); that[data.value ? 'enableItem' : 'disableItem']('book');
that[data.value ? 'disableItem' : 'enableItem']('showfilters'); that[data.value ? 'disableItem' : 'enableItem']('showfilters');
that[data.value ? 'enableItem' : 'disableItem']('showbrowser'); that[data.value ? 'enableItem' : 'disableItem']('showbrowser');
that[data.value ? 'enableItem' : 'disableItem']('showannotations');
} }
}, },
oml_itemview: function(data) { oml_itemview: function(data) {
@ -955,7 +956,7 @@ oml.ui.mainMenu = function() {
isLibrary = Ox.endsWith(ui._list, ':'), isLibrary = Ox.endsWith(ui._list, ':'),
isList = !isLibraries && !isLibrary, isList = !isLibraries && !isLibrary,
isOwnList = ui._list[0] == ':', isOwnList = ui._list[0] == ':',
isPublic = ui._list == ':Public'; isInbox = ui._list == ':Inbox';
if (oml.readOnly) { if (oml.readOnly) {
return { return {
@ -1019,13 +1020,13 @@ oml.ui.mainMenu = function() {
id: 'editlist', id: 'editlist',
title: Ox._('Edit List...'), title: Ox._('Edit List...'),
keyboard: 'return', keyboard: 'return',
disabled: !isList || !isOwnList || isPublic disabled: !isList || !isOwnList || isInbox
}, },
{ {
id: 'deletelist', id: 'deletelist',
title: Ox._('Delete List...'), title: Ox._('Delete List...'),
keyboard: 'delete', keyboard: 'delete',
disabled: !isList || !isOwnList || isPublic disabled: !isList || !isOwnList || isInbox
} }
]) ])
}; };

View File

@ -316,7 +316,7 @@ oml.enableDragAndDrop = function($list, canMove) {
$list.bindEvent({ $list.bindEvent({
draganddropstart: function(data) { draganddropstart: function(data) {
var $lists = oml.$ui.libraryList.concat(oml.$ui.publicList).concat(oml.$ui.folderList); var $lists = oml.$ui.libraryList.concat(oml.$ui.inboxList).concat(oml.$ui.folderList);
drag.action = 'copy'; drag.action = 'copy';
drag.ids = $list.options('selected'); drag.ids = $list.options('selected');
drag.item = drag.ids.length == 1 drag.item = drag.ids.length == 1
@ -335,9 +335,9 @@ oml.enableDragAndDrop = function($list, canMove) {
&& drag.source.user != '' && drag.source.user != ''
&& data.user == '' && data.user == ''
) || ( ) || (
data.type == 'static' data.type == 'library'
&& data.name == 'Public'
&& drag.source.user == '' && drag.source.user == ''
&& data.user != ''
), ),
selected: data.id == ui._list selected: data.id == ui._list
}, data); }, data);
@ -492,8 +492,12 @@ oml.enableDragAndDrop = function($list, canMove) {
text = Ox._('You cannot move books<br>out of a smart list.'); text = Ox._('You cannot move books<br>out of a smart list.');
} }
} else if (drag.target) { } else if (drag.target) {
console.log(drag.target)
targetText = drag.target.type == 'libraries' ? Ox._('a library') targetText = drag.target.type == 'libraries' ? Ox._('a library')
: drag.target.type == 'library' ? Ox._('your library') : drag.target.type == 'library' ?
drag.target.user == ''
? Ox._('your library')
: Ox._('{0}\'s library', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.user, 32))])
: Ox._('the list "{0}"', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.name, 32))]); : Ox._('the list "{0}"', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.name, 32))]);
if ( if (
( (
@ -507,7 +511,7 @@ oml.enableDragAndDrop = function($list, canMove) {
Ox._(itemText[0] == '"' ? '' : 'These ') + itemText, Ox._(itemText[0] == '"' ? '' : 'These ') + itemText,
targetText targetText
]); ]);
} else if (drag.target.user != '' && drag.target.name != 'Public') { } else if (drag.target.user != '' && drag.target.type != 'library') {
text = Ox._( text = Ox._(
'You can only {0} books<br>to your own {1}.', 'You can only {0} books<br>to your own {1}.',
[actionText, drag.target.type == 'library' ? 'library' : 'lists'] [actionText, drag.target.type == 'library' ? 'library' : 'lists']
@ -992,7 +996,7 @@ oml.resizeListFolders = function() {
oml.$ui.libraryList[index] oml.$ui.libraryList[index]
.css({width: width + 'px'}) .css({width: width + 'px'})
.resizeColumn('name', columnWidth); .resizeColumn('name', columnWidth);
oml.$ui.publicList[index] oml.$ui.inboxList[index] && oml.$ui.inboxList[index]
.css({width: width + 'px'}) .css({width: width + 'px'})
.resizeColumn('name', columnWidth); .resizeColumn('name', columnWidth);
oml.$ui.folderList[index] oml.$ui.folderList[index]

View File

@ -46,6 +46,7 @@ oml.ui.viewer = function() {
function saveAnnotations(data) { function saveAnnotations(data) {
if (data) { if (data) {
data.created = data.created || (new Date).toISOString(); data.created = data.created || (new Date).toISOString();
data.comments = data.comments || [];
annotations.push(data); annotations.push(data);
} }
localStorage[item + '.annotations'] = JSON.stringify(annotations) localStorage[item + '.annotations'] = JSON.stringify(annotations)
@ -57,6 +58,14 @@ oml.ui.viewer = function() {
saveAnnotations() saveAnnotations()
} }
var annotationEvents = {
change: function() {
console.log('change...')
console.log(annotations)
saveAnnotations()
}
}
that.updateElement = function() { that.updateElement = function() {
item = ui.item; item = ui.item;
if (item && item.length) { if (item && item.length) {
@ -75,7 +84,7 @@ oml.ui.viewer = function() {
if (event == 'addAnnotation') { if (event == 'addAnnotation') {
console.log('adding', data.id) console.log('adding', data.id)
saveAnnotations(data); saveAnnotations(data);
var $annotation = oml.ui.annotation(data, $iframe) var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
oml.$ui.annotationFolder.append($annotation); oml.$ui.annotationFolder.append($annotation);
$annotation.annotate(); $annotation.annotate();
} else if (event == 'removeAnnotation') { } else if (event == 'removeAnnotation') {
@ -93,7 +102,7 @@ oml.ui.viewer = function() {
init: function() { init: function() {
loadAnnotations(function(annotations) { loadAnnotations(function(annotations) {
annotations.forEach(function(data) { annotations.forEach(function(data) {
var $annotation = oml.ui.annotation(data, $iframe) var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
oml.$ui.annotationFolder.append($annotation); oml.$ui.annotationFolder.append($annotation);
}) })
// fixme: trigger loaded event from reader instead? // fixme: trigger loaded event from reader instead?

View File

@ -11,14 +11,21 @@ Ox.load({
console.log('got', event, 'data', data) console.log('got', event, 'data', data)
if (event == 'selectAnnotation') { if (event == 'selectAnnotation') {
var annotation = annotations.filter(function(a) { return a.id == data.id })[0] var annotation = annotations.filter(function(a) { return a.id == data.id })[0]
var delay = 0
if ( if (
annotation && annotation &&
annotation.page && annotation.page &&
PDFViewerApplication.pdfViewer.currentPageNumber != annotation.page PDFViewerApplication.pdfViewer.currentPageNumber != annotation.page
) { ) {
//FIXME: scroll into view
PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page; PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page;
delay = 250
} }
setTimeout(function() {
var el = document.querySelector('.a' + annotation.id);
if (el) {
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
}
}, delay)
selectAnnotation(data.id) selectAnnotation(data.id)
} else if (event == 'addAnnotations') { } else if (event == 'addAnnotations') {
data.annotations.forEach(function(annotation) { data.annotations.forEach(function(annotation) {
@ -144,7 +151,7 @@ function removeAnnotation(id) {
annotations = annotations.filter(function(annotation) { annotations = annotations.filter(function(annotation) {
return annotation.id != id return annotation.id != id
}) })
Ox.$parent.postMessage('removeAnnotation', {id: selected.dataset.id}) Ox.$parent.postMessage('removeAnnotation', {id: id})
} }
function loadAnnotations(page) { function loadAnnotations(page) {