Compare commits
9 Commits
ef2b56e0a9
...
e0cba14d6a
Author | SHA1 | Date |
---|---|---|
j | e0cba14d6a | |
j | 131a6a3215 | |
j | dad9b53b54 | |
j | ad2d763fbb | |
j | 497c2bb6be | |
j | 81e943625d | |
j | 46e679f2b9 | |
j | 925967ddf0 | |
j | 0c2ad46e71 |
13
config.json
13
config.json
|
@ -241,6 +241,18 @@
|
|||
"format": {"type": "boolean", "args": []},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "quotes",
|
||||
"title": "Quotes",
|
||||
"find": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "notes",
|
||||
"title": "Notes",
|
||||
"find": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "fulltext",
|
||||
"title": "Full Text",
|
||||
|
@ -368,6 +380,7 @@
|
|||
"showFilters": true,
|
||||
"showIconInfo": true,
|
||||
"showInfo": true,
|
||||
"showPeers": true,
|
||||
"showSection": {
|
||||
"notifications": {
|
||||
"received": true,
|
||||
|
|
4
ctl
4
ctl
|
@ -180,14 +180,10 @@ if [ "$1" == "open" ]; then
|
|||
if ps -p `cat "$PID"` > /dev/null; then
|
||||
open_linux
|
||||
else
|
||||
#$PYTHON "${NAME}/oml/gtkstatus.py" $@
|
||||
#exit $?
|
||||
open_linux
|
||||
"$0" start &
|
||||
fi
|
||||
else
|
||||
#$PYTHON "$NAME/oml/gtkstatus.py" $@
|
||||
#exit $?
|
||||
open_linux
|
||||
"$0" start &
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from oxtornado import actions
|
||||
from . import models
|
||||
import settings
|
||||
import state
|
||||
from changelog import add_record
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getAnnotations(data):
|
||||
response = {}
|
||||
response['annotations'] = models.Annotation.get_by_item(data['id'])
|
||||
return response
|
||||
actions.register(getAnnotations)
|
||||
|
||||
|
||||
def addAnnotation(data):
|
||||
item_id = data.pop('item')
|
||||
a = models.Annotation.create(item_id, settings.USER_ID, data)
|
||||
a.add_record('addannotation')
|
||||
response = a.json()
|
||||
return response
|
||||
actions.register(addAnnotation)
|
||||
|
||||
|
||||
def editNote(data):
|
||||
a = models.Annotation.get(state.user(), data['item'], data['annotation'])
|
||||
if a:
|
||||
a.data['notes'] = data['notes']
|
||||
a.add_record('editannotation')
|
||||
a.save()
|
||||
response = a.json()
|
||||
else:
|
||||
response = {}
|
||||
return response
|
||||
actions.register(editNote)
|
||||
|
||||
|
||||
def removeAnnotation(data):
|
||||
a = models.Annotation.get(state.user(), data['item'], data['annotation'])
|
||||
if a:
|
||||
a.add_record('removeannotation')
|
||||
a.delete()
|
||||
response = {}
|
||||
return response
|
||||
actions.register(removeAnnotation)
|
|
@ -0,0 +1,124 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import unicodedata
|
||||
|
||||
from sqlalchemy.orm import load_only
|
||||
import ox
|
||||
import sqlalchemy as sa
|
||||
|
||||
from changelog import add_record
|
||||
from db import MutableDict
|
||||
import db
|
||||
import json_pickler
|
||||
import settings
|
||||
import state
|
||||
import utils
|
||||
import media
|
||||
from websocket import trigger_event
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Annotation(db.Model):
|
||||
__tablename__ = 'annotation'
|
||||
|
||||
_id = sa.Column(sa.Integer(), primary_key=True)
|
||||
id = sa.Column(sa.String(43))
|
||||
created = sa.Column(sa.DateTime())
|
||||
modified = sa.Column(sa.DateTime())
|
||||
|
||||
user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id'))
|
||||
user = sa.orm.relationship('User', backref=sa.orm.backref('annotations', lazy='dynamic'))
|
||||
item_id = sa.Column(sa.String(43), sa.ForeignKey('item.id'))
|
||||
item = sa.orm.relationship('Item', backref=sa.orm.backref('items', lazy='dynamic'))
|
||||
data = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
||||
|
||||
findquotes = sa.Column(sa.Text(), index=True)
|
||||
findnotes = sa.Column(sa.Text(), index=True)
|
||||
|
||||
def __init__(self, item_id, user_id, data):
|
||||
self.created = datetime.utcnow()
|
||||
self.modified = datetime.utcnow()
|
||||
self.item_id = item_id
|
||||
self.user_id = user_id
|
||||
self.data = data
|
||||
|
||||
@classmethod
|
||||
def create(cls, item_id, user_id, data):
|
||||
a = cls(item_id, user_id, data)
|
||||
a.save()
|
||||
return a
|
||||
|
||||
def delete(self):
|
||||
state.db.session.delete(self)
|
||||
state.db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def get(cls, user, item_id, annotation_id):
|
||||
if isinstance(user, str):
|
||||
qs = cls.query.filter_by(item_id=item_id, user_id=user, id=annotation_id)
|
||||
else:
|
||||
qs = cls.query.filter_by(item_id=item_id, user=user, id=annotation_id)
|
||||
for a in qs:
|
||||
return a
|
||||
|
||||
@classmethod
|
||||
def get_by_item(cls, item_id):
|
||||
annotations = []
|
||||
for a in cls.query.filter_by(item_id=item_id):
|
||||
annotations.append(a.json())
|
||||
return annotations
|
||||
|
||||
def save(self):
|
||||
id = self.data.get('id')
|
||||
if id:
|
||||
self.id = id
|
||||
self.findquotes = unicodedata.normalize('NFKD', self.data.get('text', '')).lower()
|
||||
note = self.data.get('notes', {})
|
||||
if isinstance(note, list) and note:
|
||||
note = note[0]
|
||||
if isinstance(note, dict):
|
||||
note = note.get('value', '')
|
||||
else:
|
||||
note = ''
|
||||
self.findnotes = unicodedata.normalize('NFKD', note).lower()
|
||||
state.db.session.add(self)
|
||||
state.db.session.commit()
|
||||
|
||||
def json(self):
|
||||
data = self.data.copy()
|
||||
data['created'] = self.created
|
||||
data['modified'] = self.modified
|
||||
data['user'] = self.user_id
|
||||
data['_id'] = ox.toAZ(self._id)
|
||||
if isinstance(data.get('notes'), dict):
|
||||
note = data['notes']
|
||||
if self.user_id != settings.USER_ID:
|
||||
note['user'] = self.user_id
|
||||
if not note.get('id'):
|
||||
note['id'] = 'A'
|
||||
data['notes'] = [data['notes']]
|
||||
if 'notes' not in data:
|
||||
data['notes'] = []
|
||||
return data
|
||||
|
||||
def add_record(self, action):
|
||||
args = [self.item_id]
|
||||
if action == 'addannotation':
|
||||
args.append(self.data)
|
||||
elif action == 'editannotation':
|
||||
args.append(self.id)
|
||||
args.append({
|
||||
'notes': self.data['notes']
|
||||
})
|
||||
elif action == 'removeannotation':
|
||||
args.append(self.id)
|
||||
else:
|
||||
raise Exception('unknown action')
|
||||
add_record(action, *args)
|
||||
|
|
@ -12,6 +12,7 @@ from oxtornado import actions
|
|||
|
||||
import item.api
|
||||
import user.api
|
||||
import annotation.api
|
||||
import update
|
||||
import utils
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.sql.expression import text
|
||||
|
@ -76,6 +76,10 @@ class Changelog(db.Model):
|
|||
addpeer peerid peername
|
||||
removepeer peerid peername
|
||||
editpeer peerid {username: string, contact: string}
|
||||
|
||||
addannotation itemid data
|
||||
editannotation itemid annotationid data
|
||||
removeannotation itemid annotationid
|
||||
'''
|
||||
__tablename__ = 'changelog'
|
||||
id = sa.Column(sa.Integer(), primary_key=True)
|
||||
|
@ -327,6 +331,27 @@ class Changelog(db.Model):
|
|||
peer.save()
|
||||
return True
|
||||
|
||||
def action_addannotation(self, user, timestamp, itemid, data):
|
||||
from annotation.models import Annotation
|
||||
Annotation.create(item_id=itemid, user_id=user.id, data=data)
|
||||
return True
|
||||
|
||||
def action_editannotation(self, user, timestamp, itemid, annotationid, data):
|
||||
from annotation.models import Annotation
|
||||
a = Annotation.get(user, itemid, annotationid)
|
||||
if a:
|
||||
for key in data:
|
||||
a.data[key] = data[key]
|
||||
a.save()
|
||||
return True
|
||||
|
||||
def action_removeannotation(self, user, timestamp, itemid, annotationid):
|
||||
from annotation.models import Annotation
|
||||
a = Annotation.get(user, itemid, annotationid)
|
||||
if a:
|
||||
a.delete()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def aggregated_changes(cls, since=None, user_id=None):
|
||||
from item.models import Item
|
||||
|
|
|
@ -190,6 +190,33 @@ class Peer(object):
|
|||
self.info['username'] = args[0]
|
||||
elif action == 'editcontact':
|
||||
self.info['contact'] = args[0]
|
||||
elif action == 'addannotation':
|
||||
from annotation.models import Annotation
|
||||
if len(args) == 2:
|
||||
itemid, data = args
|
||||
Annotation.create(item_id=itemid, user_id=self.id, data=data)
|
||||
else:
|
||||
logger.error('invalid entry %s %s', action, args)
|
||||
elif action == 'editannotation':
|
||||
from annotation.models import Annotation
|
||||
if len(args) == 3:
|
||||
itemid, annotationid, data = args
|
||||
a = Annotation.get(self.id, itemid, annotationid)
|
||||
if a:
|
||||
for key in data:
|
||||
a.data[key] = data[key]
|
||||
a.save()
|
||||
else:
|
||||
logger.error('invalid entry %s %s', action, args)
|
||||
elif action == 'removeannotation':
|
||||
from annotation.models import Annotation
|
||||
if len(args) == 2:
|
||||
itemid, annotationid = args
|
||||
a = Annotation.get(self.id, itemid, annotationid)
|
||||
if a:
|
||||
a.delete()
|
||||
else:
|
||||
logger.error('invalid entry %s %s', action, args)
|
||||
else:
|
||||
logger.debug('UNKNOWN ACTION:', action)
|
||||
self.info['revision'] = revision
|
||||
|
|
|
@ -152,8 +152,8 @@ class LocalNodes(dict):
|
|||
if state.tasks:
|
||||
state.tasks.queue('removelocalinfo', id)
|
||||
|
||||
def get(self, user_id):
|
||||
data = super().get(user_id)
|
||||
def get_data(self, user_id):
|
||||
data = self.get(user_id)
|
||||
if data and can_connect(data):
|
||||
return data
|
||||
return None
|
||||
|
|
12
oml/nodes.py
12
oml/nodes.py
|
@ -124,9 +124,12 @@ class Node(Thread):
|
|||
self.local = None
|
||||
self.port = 9851
|
||||
|
||||
def is_local(self):
|
||||
return self._nodes and self.user_id in self._nodes.local
|
||||
|
||||
def get_local(self):
|
||||
if self._nodes and self._nodes.local:
|
||||
return self._nodes.local.get(self.user_id)
|
||||
return self._nodes.local.get_data(self.user_id)
|
||||
return None
|
||||
|
||||
def request(self, action, *args):
|
||||
|
@ -216,7 +219,7 @@ class Node(Thread):
|
|||
return False
|
||||
|
||||
def is_online(self):
|
||||
return self.online or self.get_local() is not None
|
||||
return self.online or self.is_local()
|
||||
|
||||
def send_response(self):
|
||||
self._q.put('send_response')
|
||||
|
@ -523,7 +526,8 @@ class Nodes(Thread):
|
|||
while not state.shutdown:
|
||||
args = self._q.get()
|
||||
if args:
|
||||
logger.debug('processing nodes queue: next: "%s", %s entries in queue', args[0], self._q.qsize())
|
||||
if DEBUG_NODES:
|
||||
logger.debug('processing nodes queue: next: "%s", %s entries in queue', args[0], self._q.qsize())
|
||||
if args[0] == 'add':
|
||||
self._add(*args[1:])
|
||||
elif args[0] == 'pull':
|
||||
|
@ -532,7 +536,7 @@ class Nodes(Thread):
|
|||
self._call(*args)
|
||||
|
||||
def queue(self, *args):
|
||||
if args:
|
||||
if args and DEBUG_NODES:
|
||||
logger.debug('queue "%s", %s entries in queue', args, self._q.qsize())
|
||||
self._q.put(list(args))
|
||||
|
||||
|
|
|
@ -116,7 +116,18 @@ class Parser(object):
|
|||
elif k == 'fulltext':
|
||||
ids = find_fulltext(v)
|
||||
return self.in_ids(ids, exclude)
|
||||
|
||||
elif k in ('notes', 'quotes'):
|
||||
from annotation.models import Annotation
|
||||
if isinstance(v, str):
|
||||
v = unicodedata.normalize('NFKD', v).lower()
|
||||
ids = set()
|
||||
if k == 'notes':
|
||||
qs = Annotation.query.filter(get_operator('=')(Annotation.findnotes, v))
|
||||
elif k == 'quotes':
|
||||
qs = Annotation.query.filter(get_operator('=')(Annotation.findquotes, v))
|
||||
for a in qs:
|
||||
ids.add(a.item_id)
|
||||
return self.in_ids(ids, exclude)
|
||||
elif key_type in ("string", "text"):
|
||||
if isinstance(v, str):
|
||||
v = unicodedata.normalize('NFKD', v).lower()
|
||||
|
|
|
@ -170,8 +170,8 @@ def run():
|
|||
import bandwidth
|
||||
state.bandwidth = bandwidth.Bandwidth()
|
||||
state.tor = tor.Tor()
|
||||
state.node = node.server.start()
|
||||
state.nodes = nodes.Nodes()
|
||||
state.node = node.server.start()
|
||||
|
||||
def publish():
|
||||
if not state.tor.is_online():
|
||||
|
|
|
@ -82,7 +82,7 @@ if 'modules' in release and 'openmedialibrary' in release['modules']:
|
|||
else:
|
||||
MINOR_VERSION = 'git'
|
||||
|
||||
NODE_PROTOCOL = "0.8"
|
||||
NODE_PROTOCOL = "0.9"
|
||||
VERSION = "%s.%s" % (NODE_PROTOCOL, MINOR_VERSION)
|
||||
|
||||
USER_AGENT = 'OpenMediaLibrary/%s' % VERSION
|
||||
|
@ -95,4 +95,4 @@ FULLTEXT_SUPPORT = fulltext.platform_supported()
|
|||
if not FULLTEXT_SUPPORT:
|
||||
config['itemKeys'] = [k for k in config['itemKeys'] if k['id'] != 'fulltext']
|
||||
|
||||
DB_VERSION = 17
|
||||
DB_VERSION = 18
|
||||
|
|
16
oml/setup.py
16
oml/setup.py
|
@ -151,6 +151,22 @@ CREATE TABLE listitem (
|
|||
FOREIGN KEY(item_id) REFERENCES item (id),
|
||||
FOREIGN KEY(list_id) REFERENCES list (id)
|
||||
);
|
||||
CREATE TABLE annotation (
|
||||
_id INTEGER NOT NULL,
|
||||
id VARCHAR(43),
|
||||
created DATETIME,
|
||||
modified DATETIME,
|
||||
user_id VARCHAR(43),
|
||||
item_id VARCHAR(43),
|
||||
data BLOB,
|
||||
findquotes TEXT,
|
||||
findnotes TEXT,
|
||||
PRIMARY KEY (_id),
|
||||
FOREIGN KEY(user_id) REFERENCES user (id),
|
||||
FOREIGN KEY(item_id) REFERENCES item (id)
|
||||
);
|
||||
CREATE INDEX ix_annotation_findquotes ON annotation (findquotes);
|
||||
CREATE INDEX ix_annotation_findnotes ON annotation (findnotes);
|
||||
PRAGMA journal_mode=WAL
|
||||
'''
|
||||
for statement in sql.split(';'):
|
||||
|
|
|
@ -377,6 +377,8 @@ class Update(Thread):
|
|||
db_version = migrate_16()
|
||||
if db_version < 17:
|
||||
db_version = migrate_17()
|
||||
if db_version < 18:
|
||||
db_version = migrate_18()
|
||||
settings.server['db_version'] = db_version
|
||||
|
||||
def run(self):
|
||||
|
@ -674,3 +676,25 @@ def migrate_17():
|
|||
lists.append(l.name)
|
||||
add_record('orderlists', lists)
|
||||
return 17
|
||||
|
||||
def migrate_18():
|
||||
db.run_sql([
|
||||
'''CREATE TABLE annotation (
|
||||
_id INTEGER NOT NULL,
|
||||
id VARCHAR(43),
|
||||
created DATETIME,
|
||||
modified DATETIME,
|
||||
user_id VARCHAR(43),
|
||||
item_id VARCHAR(43),
|
||||
data BLOB,
|
||||
findquotes TEXT,
|
||||
findnotes TEXT,
|
||||
PRIMARY KEY (_id),
|
||||
FOREIGN KEY(user_id) REFERENCES user (id),
|
||||
FOREIGN KEY(item_id) REFERENCES item (id)
|
||||
)'''])
|
||||
db.run_sql([
|
||||
'CREATE INDEX ix_annotation_findquotes ON annotation (findquotes)',
|
||||
'CREATE INDEX ix_annotation_findnotes ON annotation (findnotes)'
|
||||
])
|
||||
return 18
|
||||
|
|
|
@ -474,7 +474,7 @@ def removePeering(data):
|
|||
if len(data.get('id', '')) not in (16, 43):
|
||||
logger.debug('invalid user id')
|
||||
return {}
|
||||
u = models.User.get(data['id'], for_udpate=True)
|
||||
u = models.User.get(data['id'], for_update=True)
|
||||
if u:
|
||||
u.info['message'] = data.get('message', '')
|
||||
u.update_peering(False)
|
||||
|
|
|
@ -187,7 +187,9 @@ class User(db.Model):
|
|||
|
||||
def cleanup(self):
|
||||
from item.models import user_items, Item
|
||||
from annotation.models import Annotation
|
||||
List.query.filter_by(user_id=self.id).delete()
|
||||
Annotation.query.filter_by(user_id=self.id).delete()
|
||||
c_user_id = user_items.columns['user_id']
|
||||
q = user_items.delete().where(c_user_id.is_(self.id))
|
||||
state.db.session.execute(q)
|
||||
|
@ -197,6 +199,7 @@ class User(db.Model):
|
|||
state.peers[self.id].remove()
|
||||
del state.peers[self.id]
|
||||
|
||||
|
||||
def update_name(self):
|
||||
if self.id == settings.USER_ID:
|
||||
name = settings.preferences.get('username', 'anonymous')
|
||||
|
|
|
@ -6,9 +6,16 @@ oml.ui.annotation = function(annotation, $iframe) {
|
|||
.html(Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '<br/>'))
|
||||
.on({
|
||||
click: function(event) {
|
||||
that.select()
|
||||
var id
|
||||
if (event.ctrlKey) {
|
||||
that.deselect()
|
||||
id = null
|
||||
} else {
|
||||
that.select()
|
||||
id = annotation.id
|
||||
}
|
||||
$iframe.postMessage('selectAnnotation', {
|
||||
id: annotation.id
|
||||
id: id
|
||||
})
|
||||
}
|
||||
});
|
||||
|
@ -39,14 +46,22 @@ oml.ui.annotation = function(annotation, $iframe) {
|
|||
note.value = data.value
|
||||
note.modified = (new Date).toISOString()
|
||||
} else {
|
||||
annotation.notes.push({
|
||||
annotation.notes.push(note = {
|
||||
created: data.created || (new Date).toISOString(),
|
||||
modified: (new Date).toISOString(),
|
||||
id: data.id,
|
||||
user: '',
|
||||
value: data.value
|
||||
})
|
||||
}
|
||||
oml.api.editNote({
|
||||
item: oml.user.ui.item,
|
||||
annotation: annotation.id,
|
||||
notes: {
|
||||
created: note.created,
|
||||
modified: note.modified,
|
||||
value: note.value
|
||||
}
|
||||
})
|
||||
that.triggerEvent('change')
|
||||
}
|
||||
});
|
||||
|
@ -82,6 +97,11 @@ oml.ui.annotation = function(annotation, $iframe) {
|
|||
addNote()
|
||||
}
|
||||
}
|
||||
that.delete = function() {
|
||||
that.triggerEvent('delete', {
|
||||
id: annotation.id
|
||||
})
|
||||
}
|
||||
that.deselect = function() {
|
||||
that.removeClass('selected')
|
||||
that.loseFocus()
|
||||
|
@ -94,7 +114,8 @@ oml.ui.annotation = function(annotation, $iframe) {
|
|||
selected && selected.classList.remove('selected')
|
||||
that.addClass('selected')
|
||||
that.gainFocus()
|
||||
that[0].scrollIntoViewIfNeeded()
|
||||
oml.$ui.annotationPanel.updateSelection(false)
|
||||
that[0].scrollIntoViewIfNeeded && that[0].scrollIntoViewIfNeeded()
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
oml.ui.annotationPanel = function() {
|
||||
var ui = oml.user.ui;
|
||||
|
||||
var ui = oml.user.ui;
|
||||
|
||||
var $bar = Ox.Bar({size: 16});
|
||||
|
||||
var $button = Ox.Button({
|
||||
var $addQuote = Ox.Button({
|
||||
disabled: true,
|
||||
style: 'symbol',
|
||||
title: 'add',
|
||||
|
@ -18,6 +19,20 @@ oml.ui.annotationPanel = function() {
|
|||
}
|
||||
}).appendTo($bar);
|
||||
|
||||
var $deleteQuote = Ox.Button({
|
||||
disabled: true,
|
||||
style: 'symbol',
|
||||
title: 'remove',
|
||||
tooltip: Ox._('Delete Quote'),
|
||||
type: 'image'
|
||||
}).bindEvent({
|
||||
click: function() {
|
||||
var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected')
|
||||
$annotation.length && $annotation.delete()
|
||||
$deleteQuote.options({disabled: true})
|
||||
}
|
||||
}).appendTo($bar);
|
||||
|
||||
var $menuButton = Ox.MenuButton({
|
||||
items: [
|
||||
{id: 'addAnnotation', title: 'Add Annotation', disabled: true},
|
||||
|
@ -67,6 +82,21 @@ oml.ui.annotationPanel = function() {
|
|||
}, function(result) {
|
||||
oml.ui.exportAnnotationsDialog(result.data).open()
|
||||
})
|
||||
} else {
|
||||
console.log('click', id, data)
|
||||
}
|
||||
},
|
||||
change: function(data) {
|
||||
var id = data.id;
|
||||
console.log('change', data)
|
||||
if (id == 'show') {
|
||||
console.log('show', data)
|
||||
oml.UI.set({annotationsShow: data.checked[0].id});
|
||||
} else if (id == 'sort') {
|
||||
console.log('sort', data)
|
||||
oml.UI.set({annotationsSort: data.checked[0].id});
|
||||
} else {
|
||||
console.log('change', id, data)
|
||||
}
|
||||
}
|
||||
}).appendTo($bar);
|
||||
|
@ -86,9 +116,13 @@ oml.ui.annotationPanel = function() {
|
|||
});
|
||||
|
||||
that.updateSelection = function(selection) {
|
||||
$button.options({
|
||||
$addQuote.options({
|
||||
disabled: !selection
|
||||
})
|
||||
var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected')
|
||||
$deleteQuote.options({
|
||||
disabled: !$annotation.length
|
||||
})
|
||||
}
|
||||
|
||||
return that;
|
||||
|
|
|
@ -47,7 +47,8 @@ oml.ui.exportAnnotationsDialog = function(data) {
|
|||
var annotations = oml.$ui.viewer.getAnnotations()
|
||||
var text = 'Annotations for ' + data.title + ' (' + data.author.join(', ') + ')\n\n\n\n'
|
||||
text += annotations.map(function(annotation) {
|
||||
var text = 'Quote:\n\n' + annotation.text
|
||||
var page = annotation.pageLabel || annotation.page
|
||||
var text = 'Quote' + (page ? ' Page ' + page : '' )+ ':\n\n' + annotation.text
|
||||
if (annotation.notes.length) {
|
||||
text += '\n\nNotes:\n' + annotation.notes.map(function(note) {
|
||||
return note.value
|
||||
|
|
|
@ -20,6 +20,9 @@ oml.ui.folders = function() {
|
|||
oml_showfolder: function() {
|
||||
oml.resizeListFolders();
|
||||
},
|
||||
oml_showpeers: function() {
|
||||
that.updateElement();
|
||||
},
|
||||
oml_showinfo: function() {
|
||||
oml.resizeListFolders();
|
||||
}
|
||||
|
@ -48,9 +51,15 @@ oml.ui.folders = function() {
|
|||
|
||||
function getUsersAndLists(callback) {
|
||||
oml.getUsers(function(users_) {
|
||||
users = users_.filter(function(user) {
|
||||
return user.id == oml.user.id || user.peered;
|
||||
});
|
||||
if (ui.showPeers) {
|
||||
users = users_.filter(function(user) {
|
||||
return user.id == oml.user.id || user.peered;
|
||||
});
|
||||
} else {
|
||||
users = users_.filter(function(user) {
|
||||
return user.id == oml.user.id;
|
||||
});
|
||||
}
|
||||
oml.getLists(function(lists) {
|
||||
callback(users, lists);
|
||||
});
|
||||
|
|
|
@ -214,6 +214,12 @@ oml.ui.mainMenu = function() {
|
|||
title: Ox._((ui.showSidebar ? 'Hide' : 'Show') + ' Sidebar'),
|
||||
keyboard: 'shift s'
|
||||
},
|
||||
{
|
||||
id: 'showpeers',
|
||||
title: Ox._((ui.showPeers ? 'Hide' : 'Show') + ' Peers'),
|
||||
keyboard: 'shift p',
|
||||
disabled: !ui.showSidebar
|
||||
},
|
||||
{
|
||||
id: 'showinfo',
|
||||
title: Ox._((ui.showInfo ? 'Hide' : 'Show') + ' Info'),
|
||||
|
@ -539,6 +545,8 @@ oml.ui.mainMenu = function() {
|
|||
oml.history.clear();
|
||||
} else if (id == 'showsidebar') {
|
||||
oml.UI.set({showSidebar: !ui.showSidebar});
|
||||
} else if (id == 'showpeers') {
|
||||
oml.UI.set({showPeers: !ui.showPeers});
|
||||
} else if (id == 'showinfo') {
|
||||
oml.UI.set({showInfo: !ui.showInfo});
|
||||
} else if (id == 'showfilters') {
|
||||
|
@ -636,12 +644,16 @@ oml.ui.mainMenu = function() {
|
|||
that[action]('viewMenu_iconsSubmenu_extension');
|
||||
that[action]('viewMenu_iconsSubmenu_size');
|
||||
},
|
||||
oml_showpeers: function(data) {
|
||||
that.setItemTitle('showpeers', Ox._((data.value ? 'Hide' : 'Show') + ' Peers'));
|
||||
},
|
||||
oml_showinfo: function(data) {
|
||||
that.setItemTitle('showinfo', Ox._((data.value ? 'Hide' : 'Show') + ' Info'));
|
||||
},
|
||||
oml_showsidebar: function(data) {
|
||||
that.setItemTitle('showsidebar', Ox._((data.value ? 'Hide' : 'Show') + ' Sidebar'));
|
||||
that[data.value ? 'enableItem' : 'disableItem']('showinfo');
|
||||
that[data.value ? 'enableItem' : 'disableItem']('showpeers');
|
||||
},
|
||||
});
|
||||
Ox.Event.bind({
|
||||
|
@ -726,8 +738,17 @@ oml.ui.mainMenu = function() {
|
|||
key_shift_f: function() {
|
||||
!ui.item && oml.UI.set({showFilters: !ui.showFilters});
|
||||
},
|
||||
key_shift_i: function() {
|
||||
ui.showSidebar && oml.UI.set({showInfo: !ui.showInfo});
|
||||
key_shift_p: function(event, name, target) {
|
||||
// FIXME: event triggers twice
|
||||
if (target.hasClass('OxFocus')) {
|
||||
ui.showSidebar && oml.UI.set({showPeers: !ui.showPeers});
|
||||
}
|
||||
},
|
||||
key_shift_i: function(event, name, target) {
|
||||
// FIXME: event triggers twice
|
||||
if (target.hasClass('OxFocus')) {
|
||||
ui.showSidebar && oml.UI.set({showInfo: !ui.showInfo});
|
||||
}
|
||||
},
|
||||
key_shift_s: function() {
|
||||
oml.UI.set({showSidebar: !ui.showSidebar});
|
||||
|
|
|
@ -15,6 +15,12 @@ oml.ui.viewer = function() {
|
|||
},
|
||||
oml_showannotations: function() {
|
||||
panel.toggleElement(1);
|
||||
},
|
||||
oml_sortannotations: function(data) {
|
||||
that.renderAnnotations()
|
||||
},
|
||||
oml_annotationusers: function(data) {
|
||||
that.renderAnnotations()
|
||||
}
|
||||
}),
|
||||
panel = Ox.SplitPanel({
|
||||
|
@ -40,38 +46,81 @@ oml.ui.viewer = function() {
|
|||
$iframe, item;
|
||||
|
||||
function loadAnnotations(callback) {
|
||||
annotations = JSON.parse(localStorage[item + '.annotations'] || '[]')
|
||||
annotations.forEach(function(data) {
|
||||
if (data.comments && !data.notes) {
|
||||
data.notes = data.comments
|
||||
delete data.comments
|
||||
}
|
||||
data.notes = data.notes || [];
|
||||
})
|
||||
callback && callback(annotations)
|
||||
}
|
||||
function saveAnnotations(data) {
|
||||
if (data) {
|
||||
data.created = data.created || (new Date).toISOString();
|
||||
if (data.comments && !data.notes) {
|
||||
data.notes = data.comments
|
||||
delete data.comments
|
||||
}
|
||||
data.notes = data.notes || [];
|
||||
annotations.push(data);
|
||||
if (localStorage[item + '.annotations']) {
|
||||
annotations = JSON.parse(localStorage[item + '.annotations'] || '[]')
|
||||
var ids = []
|
||||
annotations.forEach(function(data) {
|
||||
if (data.comments && !data.notes) {
|
||||
data.notes = data.comments
|
||||
delete data.comments
|
||||
}
|
||||
if (!Ox.contains(ids, data.id)) {
|
||||
ids.push(data.id)
|
||||
var note
|
||||
if (data.notes && data.notes.length) {
|
||||
note = data.notes[0]
|
||||
delete data.notes
|
||||
}
|
||||
addAnnotation(data, false)
|
||||
if (note) {
|
||||
data.notes = [note]
|
||||
} else {
|
||||
data.notes = []
|
||||
}
|
||||
if (data.notes.length) {
|
||||
var note = data.notes[0]
|
||||
oml.api.editNote({
|
||||
item: ui.item,
|
||||
annotation: data.id,
|
||||
notes: {
|
||||
created: note.created,
|
||||
modified: note.modified,
|
||||
value: note.value
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log('ignore second time', data)
|
||||
}
|
||||
})
|
||||
localStorage[oml.user.ui.item + '.annotations_'] = localStorage[oml.user.ui.item + '.annotations']
|
||||
delete localStorage[oml.user.ui.item + '.annotations']
|
||||
callback && callback(annotations)
|
||||
} else {
|
||||
oml.api.getAnnotations({
|
||||
id: ui.item
|
||||
}, function(result) {
|
||||
console.log(result)
|
||||
annotations = result.data.annotations
|
||||
callback && callback(annotations)
|
||||
})
|
||||
}
|
||||
localStorage[item + '.annotations'] = JSON.stringify(annotations)
|
||||
}
|
||||
function addAnnotation(data, save) {
|
||||
var a = Ox.extend({}, data)
|
||||
a.created = a.created || (new Date).toISOString();
|
||||
a.item = ui.item
|
||||
if (save !== false) {
|
||||
oml.api.addAnnotation(a)
|
||||
}
|
||||
data.notes = data.notes || [];
|
||||
annotations.push(data);
|
||||
}
|
||||
|
||||
function removeAnnotation(id) {
|
||||
annotations = annotations.filter(function(annotation) {
|
||||
return annotation.id != id
|
||||
oml.api.removeAnnotation({
|
||||
item: ui.item,
|
||||
annotation: id
|
||||
}, function(result) {
|
||||
annotations = annotations.filter(function(annotation) {
|
||||
return annotation.id != id
|
||||
})
|
||||
})
|
||||
saveAnnotations()
|
||||
}
|
||||
|
||||
var annotationEvents = {
|
||||
change: function() {
|
||||
saveAnnotations()
|
||||
change: function(data) {
|
||||
// console.log('change', data)
|
||||
},
|
||||
'delete': function(data) {
|
||||
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
||||
|
@ -93,14 +142,16 @@ oml.ui.viewer = function() {
|
|||
height: '100%',
|
||||
border: 0
|
||||
}).onMessage(function(data, event) {
|
||||
console.log('got', event, data)
|
||||
if (event == 'addAnnotation') {
|
||||
saveAnnotations(data);
|
||||
addAnnotation(data);
|
||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
||||
oml.$ui.annotationFolder.append($annotation);
|
||||
$annotation.annotate();
|
||||
oml.$ui.annotationPanel.updateSelection(false)
|
||||
} else if (event == 'removeAnnotation') {
|
||||
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
||||
removeAnnotation(data.id)
|
||||
data.id && removeAnnotation(data.id)
|
||||
} else if (event == 'selectAnnotation') {
|
||||
if (data.id) {
|
||||
var $annotation = oml.$ui.annotationFolder.find('#a-' + data.id)
|
||||
|
@ -109,22 +160,17 @@ oml.ui.viewer = function() {
|
|||
var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected')
|
||||
$annotation.length && $annotation.deselect()
|
||||
}
|
||||
oml.$ui.annotationPanel.updateSelection(false)
|
||||
} else if (event == 'selectText') {
|
||||
oml.$ui.annotationPanel.updateSelection(data)
|
||||
} else {
|
||||
console.log('got', event, data)
|
||||
console.log('trigger unknwon event', event, data)
|
||||
that.triggerEvent(event, data);
|
||||
}
|
||||
}).bindEvent({
|
||||
init: function() {
|
||||
loadAnnotations(function(annotations) {
|
||||
that.renderAnnotations()
|
||||
// fixme: trigger loaded event from reader instead?
|
||||
setTimeout(function() {
|
||||
that.postMessage('addAnnotations', {
|
||||
annotations: annotations
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
}).appendTo(frame);
|
||||
|
@ -143,12 +189,34 @@ oml.ui.viewer = function() {
|
|||
return annotations;
|
||||
}
|
||||
that.renderAnnotations = function() {
|
||||
var sortKey = ui.sortAnnotations
|
||||
if (sortKey == 'date') {
|
||||
sortKey = 'created'
|
||||
}
|
||||
if (sortKey == 'date') {
|
||||
sortKey = 'created'
|
||||
}
|
||||
if (sortKey == 'quote') {
|
||||
sortKey = 'text'
|
||||
}
|
||||
annotations = Ox.sortBy(annotations, sortKey)
|
||||
oml.$ui.annotationFolder.empty();
|
||||
Ox.sortBy(annotations, ui.sortAnnotations);
|
||||
var visibleAnnotations = [];
|
||||
annotations.forEach(function(data) {
|
||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
||||
oml.$ui.annotationFolder.append($annotation);
|
||||
//that.postMessage('removeAnnotation', {id: data.id})
|
||||
if (ui.showAnnotationUsers == 'all' || data.user == oml.user.id) {
|
||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
||||
oml.$ui.annotationFolder.append($annotation);
|
||||
visibleAnnotations.push(data)
|
||||
}
|
||||
})
|
||||
// fixme: trigger loaded event from reader instead?
|
||||
setTimeout(function() {
|
||||
that.postMessage('addAnnotations', {
|
||||
annotations: visibleAnnotations,
|
||||
replace: true
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
return that.updateElement();
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ var id = document.location.pathname.split('/')[1];
|
|||
var annotations = [];
|
||||
var currentSelection;
|
||||
var fontSize = parseInt(localStorage.epubFontSize || '100', 10)
|
||||
var justSelected = false;
|
||||
|
||||
Ox.load({
|
||||
'UI': {
|
||||
|
@ -21,6 +22,12 @@ Ox.load({
|
|||
} else if (event == 'addAnnotation') {
|
||||
createAnnotation()
|
||||
} else if (event == 'addAnnotations') {
|
||||
if (data.replace) {
|
||||
annotations.forEach(function(a) {
|
||||
reader.rendition.annotations.remove(a.cfiRange)
|
||||
})
|
||||
annotations = []
|
||||
}
|
||||
data.annotations.forEach(function(annotation) {
|
||||
annotations.push(annotation)
|
||||
renderAnnotation(annotation)
|
||||
|
@ -50,6 +57,7 @@ function createAnnotation() {
|
|||
document.querySelectorAll('.epubjs-hl.selected').forEach(function(other) {
|
||||
other.classList.remove('selected')
|
||||
})
|
||||
console.log('create annot')
|
||||
currentSelection = null
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +152,8 @@ document.onreadystatechange = function () {
|
|||
}
|
||||
if (event.key == 'n' || event.keyCode == 13) {
|
||||
var selected = document.querySelector('.epubjs-hl.selected')
|
||||
console.log('!!', currentSelection, selected)
|
||||
|
||||
if (currentSelection) {
|
||||
if (selected) {
|
||||
deselectAllAnnotations()
|
||||
|
@ -151,7 +161,6 @@ document.onreadystatechange = function () {
|
|||
createAnnotation()
|
||||
} else if (selected) {
|
||||
console.log('editNote?', selected.dataset.id)
|
||||
|
||||
}
|
||||
}
|
||||
if (event.keyCode == 61 && event.shiftKey) {
|
||||
|
@ -171,7 +180,7 @@ document.onreadystatechange = function () {
|
|||
event.preventDefault()
|
||||
}
|
||||
}).on('mouseup', function(event) {
|
||||
if (currentSelection) {
|
||||
if (!justSelected) {
|
||||
var selection = window.getSelection()
|
||||
if (selection.isCollapsed) {
|
||||
currentSelection = null
|
||||
|
@ -180,15 +189,20 @@ document.onreadystatechange = function () {
|
|||
Ox.$parent.postMessage('selectText', false)
|
||||
}
|
||||
}
|
||||
deselectAllAnnotations()
|
||||
justSelected = false
|
||||
})
|
||||
rendition.on("mark", function(cfiRange, contents) {
|
||||
console.log('!! mark', cfiRange)
|
||||
})
|
||||
rendition.on("selected", function(cfiRange, contents) {
|
||||
justSelected = true
|
||||
getText(book, cfiRange, function(text) {
|
||||
var position = cfiRange;
|
||||
currentSelection = {
|
||||
id: Ox.SHA1(cfiRange),
|
||||
cfiRange: cfiRange,
|
||||
position: position,
|
||||
text: text,
|
||||
contents: contents
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ Ox.load({
|
|||
}
|
||||
}, function() {
|
||||
Ox.$parent.bindMessage(function(data, event) {
|
||||
console.log('got', event, 'data', data)
|
||||
if (event == 'selectAnnotation') {
|
||||
var annotation = annotations.filter(function(a) { return a.id == data.id })[0]
|
||||
var delay = 0
|
||||
|
@ -20,7 +19,7 @@ Ox.load({
|
|||
PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page;
|
||||
delay = 250
|
||||
}
|
||||
setTimeout(function() {
|
||||
annotation && setTimeout(function() {
|
||||
var el = document.querySelector('.a' + annotation.id);
|
||||
if (el && !isInView(el)) {
|
||||
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
|
||||
|
@ -30,12 +29,20 @@ Ox.load({
|
|||
} else if (event == 'addAnnotation') {
|
||||
createAnnotation()
|
||||
} else if (event == 'addAnnotations') {
|
||||
if (data.replace) {
|
||||
document.querySelectorAll('.oml-annotation').forEach(function(a) {
|
||||
a.remove()
|
||||
})
|
||||
annotations = []
|
||||
}
|
||||
data.annotations.forEach(function(annotation) {
|
||||
annotations.push(annotation)
|
||||
renderAnnotation(annotation)
|
||||
})
|
||||
} else if (event == 'removeAnnotation') {
|
||||
removeAnnotation(data.id)
|
||||
} else {
|
||||
console.log('got', event, 'data', data)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -47,14 +54,16 @@ window.addEventListener('keydown', function(event) {
|
|||
removeAnnotation(selected.dataset.id)
|
||||
}
|
||||
} else if (event.key == 'n' || event.keyCode == 13) {
|
||||
var selected = document.querySelector('.oml-annotation.selected')
|
||||
if (!window.getSelection().isCollapsed) {
|
||||
createAnnotation()
|
||||
} else if (selected) {
|
||||
console.log('editNote?', selected.dataset.id)
|
||||
if (event.target.nodeName != 'INPUT') {
|
||||
var selected = document.querySelector('.oml-annotation.selected')
|
||||
if (!window.getSelection().isCollapsed) {
|
||||
createAnnotation()
|
||||
} else if (selected) {
|
||||
console.log('editNote?', selected.dataset.id)
|
||||
}
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
window.addEventListener('mouseup', function(event) {
|
||||
|
@ -91,8 +100,11 @@ function getHighlight() {
|
|||
viewport.convertToPdfPoint(r.right - pageRect.x, r.bottom - pageRect.y));
|
||||
});
|
||||
var text = selection.toString();
|
||||
var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]);
|
||||
return {
|
||||
page: pageNumber,
|
||||
pageLabel: PDFViewerApplication.pdfViewer.currentPageLabel,
|
||||
position: position,
|
||||
coords: selected,
|
||||
text: text,
|
||||
id: Ox.SHA1(pageNumber.toString() + JSON.stringify(selected))
|
||||
|
@ -172,7 +184,7 @@ function deselectAllAnnotations() {
|
|||
g.classList.remove('selected')
|
||||
g.style.backgroundColor = 'yellow'
|
||||
var id = $(g).parents('.oml-annotation').data('id')
|
||||
console.log('deselect', g, id)
|
||||
//console.log('deselect', g, id)
|
||||
if (!Ox.contains(ids, id)) {
|
||||
ids.push(id)
|
||||
Ox.$parent.postMessage('selectAnnotation', {id: null})
|
||||
|
|
Loading…
Reference in New Issue