store annotations in db and sync with peers
This commit is contained in:
parent
131a6a3215
commit
e0cba14d6a
21 changed files with 385 additions and 63 deletions
12
config.json
12
config.json
|
@ -241,6 +241,18 @@
|
||||||
"format": {"type": "boolean", "args": []},
|
"format": {"type": "boolean", "args": []},
|
||||||
"sort": true
|
"sort": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "quotes",
|
||||||
|
"title": "Quotes",
|
||||||
|
"find": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "notes",
|
||||||
|
"title": "Notes",
|
||||||
|
"find": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "fulltext",
|
"id": "fulltext",
|
||||||
"title": "Full Text",
|
"title": "Full Text",
|
||||||
|
|
48
oml/annotation/api.py
Normal file
48
oml/annotation/api.py
Normal file
|
@ -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)
|
|
@ -4,6 +4,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
from sqlalchemy.orm import load_only
|
from sqlalchemy.orm import load_only
|
||||||
import ox
|
import ox
|
||||||
|
@ -31,10 +32,15 @@ class Annotation(db.Model):
|
||||||
created = sa.Column(sa.DateTime())
|
created = sa.Column(sa.DateTime())
|
||||||
modified = 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'))
|
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'))
|
item = sa.orm.relationship('Item', backref=sa.orm.backref('items', lazy='dynamic'))
|
||||||
data = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
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):
|
def __init__(self, item_id, user_id, data):
|
||||||
self.created = datetime.utcnow()
|
self.created = datetime.utcnow()
|
||||||
self.modified = datetime.utcnow()
|
self.modified = datetime.utcnow()
|
||||||
|
@ -43,28 +49,44 @@ class Annotation(db.Model):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, **kwargs):
|
def create(cls, item_id, user_id, data):
|
||||||
a = cls(**kwargs)
|
a = cls(item_id, user_id, data)
|
||||||
state.db.session.add(a)
|
a.save()
|
||||||
|
return a
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
state.db.session.delete(self)
|
||||||
state.db.session.commit()
|
state.db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, user, item_id, annotation_id):
|
def get(cls, user, item_id, annotation_id):
|
||||||
for a in cls.query.filter_by(item_id=item_id, user=user, _id=annotation_id):
|
if isinstance(user, str):
|
||||||
if a.data.get('id') == annotation_id:
|
qs = cls.query.filter_by(item_id=item_id, user_id=user, id=annotation_id)
|
||||||
return a
|
else:
|
||||||
|
qs = cls.query.filter_by(item_id=item_id, user=user, id=annotation_id)
|
||||||
|
for a in qs:
|
||||||
|
return a
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_item(cls, user, item_id):
|
def get_by_item(cls, item_id):
|
||||||
annotations = []
|
annotations = []
|
||||||
for a in cls.query.filter_by(item_id=item_id):
|
for a in cls.query.filter_by(item_id=item_id):
|
||||||
annotations.append(a.json())
|
annotations.append(a.json())
|
||||||
return annotations
|
return annotations
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
_id = self.data.get('id')
|
id = self.data.get('id')
|
||||||
if _id:
|
if id:
|
||||||
self._id = _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.add(self)
|
||||||
state.db.session.commit()
|
state.db.session.commit()
|
||||||
|
|
||||||
|
@ -73,6 +95,30 @@ class Annotation(db.Model):
|
||||||
data['created'] = self.created
|
data['created'] = self.created
|
||||||
data['modified'] = self.modified
|
data['modified'] = self.modified
|
||||||
data['user'] = self.user_id
|
data['user'] = self.user_id
|
||||||
data['_id'] = ox.toAZ(self.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
|
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 item.api
|
||||||
import user.api
|
import user.api
|
||||||
|
import annotation.api
|
||||||
import update
|
import update
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.sql.expression import text
|
from sqlalchemy.sql.expression import text
|
||||||
|
@ -76,6 +76,10 @@ class Changelog(db.Model):
|
||||||
addpeer peerid peername
|
addpeer peerid peername
|
||||||
removepeer peerid peername
|
removepeer peerid peername
|
||||||
editpeer peerid {username: string, contact: string}
|
editpeer peerid {username: string, contact: string}
|
||||||
|
|
||||||
|
addannotation itemid data
|
||||||
|
editannotation itemid annotationid data
|
||||||
|
removeannotation itemid annotationid
|
||||||
'''
|
'''
|
||||||
__tablename__ = 'changelog'
|
__tablename__ = 'changelog'
|
||||||
id = sa.Column(sa.Integer(), primary_key=True)
|
id = sa.Column(sa.Integer(), primary_key=True)
|
||||||
|
@ -327,6 +331,27 @@ class Changelog(db.Model):
|
||||||
peer.save()
|
peer.save()
|
||||||
return True
|
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
|
@classmethod
|
||||||
def aggregated_changes(cls, since=None, user_id=None):
|
def aggregated_changes(cls, since=None, user_id=None):
|
||||||
from item.models import Item
|
from item.models import Item
|
||||||
|
|
|
@ -190,6 +190,33 @@ class Peer(object):
|
||||||
self.info['username'] = args[0]
|
self.info['username'] = args[0]
|
||||||
elif action == 'editcontact':
|
elif action == 'editcontact':
|
||||||
self.info['contact'] = args[0]
|
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:
|
else:
|
||||||
logger.debug('UNKNOWN ACTION:', action)
|
logger.debug('UNKNOWN ACTION:', action)
|
||||||
self.info['revision'] = revision
|
self.info['revision'] = revision
|
||||||
|
|
|
@ -152,8 +152,8 @@ class LocalNodes(dict):
|
||||||
if state.tasks:
|
if state.tasks:
|
||||||
state.tasks.queue('removelocalinfo', id)
|
state.tasks.queue('removelocalinfo', id)
|
||||||
|
|
||||||
def get(self, user_id):
|
def get_data(self, user_id):
|
||||||
data = super().get(user_id)
|
data = self.get(user_id)
|
||||||
if data and can_connect(data):
|
if data and can_connect(data):
|
||||||
return data
|
return data
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -124,9 +124,12 @@ class Node(Thread):
|
||||||
self.local = None
|
self.local = None
|
||||||
self.port = 9851
|
self.port = 9851
|
||||||
|
|
||||||
|
def is_local(self):
|
||||||
|
return self._nodes and self.user_id in self._nodes.local
|
||||||
|
|
||||||
def get_local(self):
|
def get_local(self):
|
||||||
if self._nodes and self._nodes.local:
|
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
|
return None
|
||||||
|
|
||||||
def request(self, action, *args):
|
def request(self, action, *args):
|
||||||
|
@ -216,7 +219,7 @@ class Node(Thread):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_online(self):
|
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):
|
def send_response(self):
|
||||||
self._q.put('send_response')
|
self._q.put('send_response')
|
||||||
|
|
|
@ -116,7 +116,18 @@ class Parser(object):
|
||||||
elif k == 'fulltext':
|
elif k == 'fulltext':
|
||||||
ids = find_fulltext(v)
|
ids = find_fulltext(v)
|
||||||
return self.in_ids(ids, exclude)
|
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"):
|
elif key_type in ("string", "text"):
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = unicodedata.normalize('NFKD', v).lower()
|
v = unicodedata.normalize('NFKD', v).lower()
|
||||||
|
|
|
@ -170,8 +170,8 @@ def run():
|
||||||
import bandwidth
|
import bandwidth
|
||||||
state.bandwidth = bandwidth.Bandwidth()
|
state.bandwidth = bandwidth.Bandwidth()
|
||||||
state.tor = tor.Tor()
|
state.tor = tor.Tor()
|
||||||
state.node = node.server.start()
|
|
||||||
state.nodes = nodes.Nodes()
|
state.nodes = nodes.Nodes()
|
||||||
|
state.node = node.server.start()
|
||||||
|
|
||||||
def publish():
|
def publish():
|
||||||
if not state.tor.is_online():
|
if not state.tor.is_online():
|
||||||
|
|
|
@ -82,7 +82,7 @@ if 'modules' in release and 'openmedialibrary' in release['modules']:
|
||||||
else:
|
else:
|
||||||
MINOR_VERSION = 'git'
|
MINOR_VERSION = 'git'
|
||||||
|
|
||||||
NODE_PROTOCOL = "0.8"
|
NODE_PROTOCOL = "0.9"
|
||||||
VERSION = "%s.%s" % (NODE_PROTOCOL, MINOR_VERSION)
|
VERSION = "%s.%s" % (NODE_PROTOCOL, MINOR_VERSION)
|
||||||
|
|
||||||
USER_AGENT = 'OpenMediaLibrary/%s' % VERSION
|
USER_AGENT = 'OpenMediaLibrary/%s' % VERSION
|
||||||
|
@ -95,4 +95,4 @@ FULLTEXT_SUPPORT = fulltext.platform_supported()
|
||||||
if not FULLTEXT_SUPPORT:
|
if not FULLTEXT_SUPPORT:
|
||||||
config['itemKeys'] = [k for k in config['itemKeys'] if k['id'] != 'fulltext']
|
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(item_id) REFERENCES item (id),
|
||||||
FOREIGN KEY(list_id) REFERENCES list (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
|
PRAGMA journal_mode=WAL
|
||||||
'''
|
'''
|
||||||
for statement in sql.split(';'):
|
for statement in sql.split(';'):
|
||||||
|
|
|
@ -377,6 +377,8 @@ class Update(Thread):
|
||||||
db_version = migrate_16()
|
db_version = migrate_16()
|
||||||
if db_version < 17:
|
if db_version < 17:
|
||||||
db_version = migrate_17()
|
db_version = migrate_17()
|
||||||
|
if db_version < 18:
|
||||||
|
db_version = migrate_18()
|
||||||
settings.server['db_version'] = db_version
|
settings.server['db_version'] = db_version
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -674,3 +676,25 @@ def migrate_17():
|
||||||
lists.append(l.name)
|
lists.append(l.name)
|
||||||
add_record('orderlists', lists)
|
add_record('orderlists', lists)
|
||||||
return 17
|
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):
|
if len(data.get('id', '')) not in (16, 43):
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id')
|
||||||
return {}
|
return {}
|
||||||
u = models.User.get(data['id'], for_udpate=True)
|
u = models.User.get(data['id'], for_update=True)
|
||||||
if u:
|
if u:
|
||||||
u.info['message'] = data.get('message', '')
|
u.info['message'] = data.get('message', '')
|
||||||
u.update_peering(False)
|
u.update_peering(False)
|
||||||
|
|
|
@ -187,7 +187,9 @@ class User(db.Model):
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
from item.models import user_items, Item
|
from item.models import user_items, Item
|
||||||
|
from annotation.models import Annotation
|
||||||
List.query.filter_by(user_id=self.id).delete()
|
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']
|
c_user_id = user_items.columns['user_id']
|
||||||
q = user_items.delete().where(c_user_id.is_(self.id))
|
q = user_items.delete().where(c_user_id.is_(self.id))
|
||||||
state.db.session.execute(q)
|
state.db.session.execute(q)
|
||||||
|
@ -197,6 +199,7 @@ class User(db.Model):
|
||||||
state.peers[self.id].remove()
|
state.peers[self.id].remove()
|
||||||
del state.peers[self.id]
|
del state.peers[self.id]
|
||||||
|
|
||||||
|
|
||||||
def update_name(self):
|
def update_name(self):
|
||||||
if self.id == settings.USER_ID:
|
if self.id == settings.USER_ID:
|
||||||
name = settings.preferences.get('username', 'anonymous')
|
name = settings.preferences.get('username', 'anonymous')
|
||||||
|
|
|
@ -46,14 +46,22 @@ oml.ui.annotation = function(annotation, $iframe) {
|
||||||
note.value = data.value
|
note.value = data.value
|
||||||
note.modified = (new Date).toISOString()
|
note.modified = (new Date).toISOString()
|
||||||
} else {
|
} else {
|
||||||
annotation.notes.push({
|
annotation.notes.push(note = {
|
||||||
created: data.created || (new Date).toISOString(),
|
created: data.created || (new Date).toISOString(),
|
||||||
modified: (new Date).toISOString(),
|
modified: (new Date).toISOString(),
|
||||||
id: data.id,
|
id: data.id,
|
||||||
user: '',
|
|
||||||
value: data.value
|
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')
|
that.triggerEvent('change')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
oml.ui.annotationPanel = function() {
|
oml.ui.annotationPanel = function() {
|
||||||
|
var ui = oml.user.ui;
|
||||||
|
|
||||||
var ui = oml.user.ui;
|
var ui = oml.user.ui;
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ oml.ui.annotationPanel = function() {
|
||||||
click: function() {
|
click: function() {
|
||||||
var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected')
|
var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected')
|
||||||
$annotation.length && $annotation.delete()
|
$annotation.length && $annotation.delete()
|
||||||
|
$deleteQuote.options({disabled: true})
|
||||||
}
|
}
|
||||||
}).appendTo($bar);
|
}).appendTo($bar);
|
||||||
|
|
||||||
|
@ -80,6 +82,21 @@ oml.ui.annotationPanel = function() {
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
oml.ui.exportAnnotationsDialog(result.data).open()
|
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);
|
}).appendTo($bar);
|
||||||
|
|
|
@ -47,7 +47,8 @@ oml.ui.exportAnnotationsDialog = function(data) {
|
||||||
var annotations = oml.$ui.viewer.getAnnotations()
|
var annotations = oml.$ui.viewer.getAnnotations()
|
||||||
var text = 'Annotations for ' + data.title + ' (' + data.author.join(', ') + ')\n\n\n\n'
|
var text = 'Annotations for ' + data.title + ' (' + data.author.join(', ') + ')\n\n\n\n'
|
||||||
text += annotations.map(function(annotation) {
|
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) {
|
if (annotation.notes.length) {
|
||||||
text += '\n\nNotes:\n' + annotation.notes.map(function(note) {
|
text += '\n\nNotes:\n' + annotation.notes.map(function(note) {
|
||||||
return note.value
|
return note.value
|
||||||
|
|
|
@ -15,6 +15,12 @@ oml.ui.viewer = function() {
|
||||||
},
|
},
|
||||||
oml_showannotations: function() {
|
oml_showannotations: function() {
|
||||||
panel.toggleElement(1);
|
panel.toggleElement(1);
|
||||||
|
},
|
||||||
|
oml_sortannotations: function(data) {
|
||||||
|
that.renderAnnotations()
|
||||||
|
},
|
||||||
|
oml_annotationusers: function(data) {
|
||||||
|
that.renderAnnotations()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
panel = Ox.SplitPanel({
|
panel = Ox.SplitPanel({
|
||||||
|
@ -40,38 +46,81 @@ oml.ui.viewer = function() {
|
||||||
$iframe, item;
|
$iframe, item;
|
||||||
|
|
||||||
function loadAnnotations(callback) {
|
function loadAnnotations(callback) {
|
||||||
annotations = JSON.parse(localStorage[item + '.annotations'] || '[]')
|
if (localStorage[item + '.annotations']) {
|
||||||
annotations.forEach(function(data) {
|
annotations = JSON.parse(localStorage[item + '.annotations'] || '[]')
|
||||||
if (data.comments && !data.notes) {
|
var ids = []
|
||||||
data.notes = data.comments
|
annotations.forEach(function(data) {
|
||||||
delete data.comments
|
if (data.comments && !data.notes) {
|
||||||
}
|
data.notes = data.comments
|
||||||
data.notes = data.notes || [];
|
delete data.comments
|
||||||
})
|
}
|
||||||
callback && callback(annotations)
|
if (!Ox.contains(ids, data.id)) {
|
||||||
}
|
ids.push(data.id)
|
||||||
function saveAnnotations(data) {
|
var note
|
||||||
if (data) {
|
if (data.notes && data.notes.length) {
|
||||||
data.created = data.created || (new Date).toISOString();
|
note = data.notes[0]
|
||||||
if (data.comments && !data.notes) {
|
delete data.notes
|
||||||
data.notes = data.comments
|
}
|
||||||
delete data.comments
|
addAnnotation(data, false)
|
||||||
}
|
if (note) {
|
||||||
data.notes = data.notes || [];
|
data.notes = [note]
|
||||||
annotations.push(data);
|
} 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) {
|
function removeAnnotation(id) {
|
||||||
annotations = annotations.filter(function(annotation) {
|
oml.api.removeAnnotation({
|
||||||
return annotation.id != id
|
item: ui.item,
|
||||||
|
annotation: id
|
||||||
|
}, function(result) {
|
||||||
|
annotations = annotations.filter(function(annotation) {
|
||||||
|
return annotation.id != id
|
||||||
|
})
|
||||||
})
|
})
|
||||||
saveAnnotations()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var annotationEvents = {
|
var annotationEvents = {
|
||||||
change: function() {
|
change: function(data) {
|
||||||
saveAnnotations()
|
// console.log('change', data)
|
||||||
},
|
},
|
||||||
'delete': function(data) {
|
'delete': function(data) {
|
||||||
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
||||||
|
@ -95,14 +144,14 @@ oml.ui.viewer = function() {
|
||||||
}).onMessage(function(data, event) {
|
}).onMessage(function(data, event) {
|
||||||
console.log('got', event, data)
|
console.log('got', event, data)
|
||||||
if (event == 'addAnnotation') {
|
if (event == 'addAnnotation') {
|
||||||
saveAnnotations(data);
|
addAnnotation(data);
|
||||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
||||||
oml.$ui.annotationFolder.append($annotation);
|
oml.$ui.annotationFolder.append($annotation);
|
||||||
$annotation.annotate();
|
$annotation.annotate();
|
||||||
oml.$ui.annotationPanel.updateSelection(false)
|
oml.$ui.annotationPanel.updateSelection(false)
|
||||||
} else if (event == 'removeAnnotation') {
|
} else if (event == 'removeAnnotation') {
|
||||||
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
oml.$ui.annotationFolder.find('#a-' + data.id).remove()
|
||||||
removeAnnotation(data.id)
|
data.id && removeAnnotation(data.id)
|
||||||
} else if (event == 'selectAnnotation') {
|
} else if (event == 'selectAnnotation') {
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
var $annotation = oml.$ui.annotationFolder.find('#a-' + data.id)
|
var $annotation = oml.$ui.annotationFolder.find('#a-' + data.id)
|
||||||
|
@ -115,18 +164,13 @@ oml.ui.viewer = function() {
|
||||||
} else if (event == 'selectText') {
|
} else if (event == 'selectText') {
|
||||||
oml.$ui.annotationPanel.updateSelection(data)
|
oml.$ui.annotationPanel.updateSelection(data)
|
||||||
} else {
|
} else {
|
||||||
|
console.log('trigger unknwon event', event, data)
|
||||||
that.triggerEvent(event, data);
|
that.triggerEvent(event, data);
|
||||||
}
|
}
|
||||||
}).bindEvent({
|
}).bindEvent({
|
||||||
init: function() {
|
init: function() {
|
||||||
loadAnnotations(function(annotations) {
|
loadAnnotations(function(annotations) {
|
||||||
that.renderAnnotations()
|
that.renderAnnotations()
|
||||||
// fixme: trigger loaded event from reader instead?
|
|
||||||
setTimeout(function() {
|
|
||||||
that.postMessage('addAnnotations', {
|
|
||||||
annotations: annotations
|
|
||||||
})
|
|
||||||
}, 500)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).appendTo(frame);
|
}).appendTo(frame);
|
||||||
|
@ -145,12 +189,34 @@ oml.ui.viewer = function() {
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
that.renderAnnotations = function() {
|
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();
|
oml.$ui.annotationFolder.empty();
|
||||||
Ox.sortBy(annotations, ui.sortAnnotations);
|
var visibleAnnotations = [];
|
||||||
annotations.forEach(function(data) {
|
annotations.forEach(function(data) {
|
||||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
//that.postMessage('removeAnnotation', {id: data.id})
|
||||||
oml.$ui.annotationFolder.append($annotation);
|
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();
|
return that.updateElement();
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,12 @@ Ox.load({
|
||||||
} else if (event == 'addAnnotation') {
|
} else if (event == 'addAnnotation') {
|
||||||
createAnnotation()
|
createAnnotation()
|
||||||
} else if (event == 'addAnnotations') {
|
} else if (event == 'addAnnotations') {
|
||||||
|
if (data.replace) {
|
||||||
|
annotations.forEach(function(a) {
|
||||||
|
reader.rendition.annotations.remove(a.cfiRange)
|
||||||
|
})
|
||||||
|
annotations = []
|
||||||
|
}
|
||||||
data.annotations.forEach(function(annotation) {
|
data.annotations.forEach(function(annotation) {
|
||||||
annotations.push(annotation)
|
annotations.push(annotation)
|
||||||
renderAnnotation(annotation)
|
renderAnnotation(annotation)
|
||||||
|
|
|
@ -8,7 +8,6 @@ Ox.load({
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
Ox.$parent.bindMessage(function(data, event) {
|
Ox.$parent.bindMessage(function(data, event) {
|
||||||
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
|
var delay = 0
|
||||||
|
@ -20,7 +19,7 @@ Ox.load({
|
||||||
PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page;
|
PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page;
|
||||||
delay = 250
|
delay = 250
|
||||||
}
|
}
|
||||||
setTimeout(function() {
|
annotation && setTimeout(function() {
|
||||||
var el = document.querySelector('.a' + annotation.id);
|
var el = document.querySelector('.a' + annotation.id);
|
||||||
if (el && !isInView(el)) {
|
if (el && !isInView(el)) {
|
||||||
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
|
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
|
||||||
|
@ -30,12 +29,20 @@ Ox.load({
|
||||||
} else if (event == 'addAnnotation') {
|
} else if (event == 'addAnnotation') {
|
||||||
createAnnotation()
|
createAnnotation()
|
||||||
} else if (event == 'addAnnotations') {
|
} else if (event == 'addAnnotations') {
|
||||||
|
if (data.replace) {
|
||||||
|
document.querySelectorAll('.oml-annotation').forEach(function(a) {
|
||||||
|
a.remove()
|
||||||
|
})
|
||||||
|
annotations = []
|
||||||
|
}
|
||||||
data.annotations.forEach(function(annotation) {
|
data.annotations.forEach(function(annotation) {
|
||||||
annotations.push(annotation)
|
annotations.push(annotation)
|
||||||
renderAnnotation(annotation)
|
renderAnnotation(annotation)
|
||||||
})
|
})
|
||||||
} else if (event == 'removeAnnotation') {
|
} else if (event == 'removeAnnotation') {
|
||||||
removeAnnotation(data.id)
|
removeAnnotation(data.id)
|
||||||
|
} else {
|
||||||
|
console.log('got', event, 'data', data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -96,6 +103,7 @@ function getHighlight() {
|
||||||
var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]);
|
var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]);
|
||||||
return {
|
return {
|
||||||
page: pageNumber,
|
page: pageNumber,
|
||||||
|
pageLabel: PDFViewerApplication.pdfViewer.currentPageLabel,
|
||||||
position: position,
|
position: position,
|
||||||
coords: selected,
|
coords: selected,
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -176,7 +184,7 @@ function deselectAllAnnotations() {
|
||||||
g.classList.remove('selected')
|
g.classList.remove('selected')
|
||||||
g.style.backgroundColor = 'yellow'
|
g.style.backgroundColor = 'yellow'
|
||||||
var id = $(g).parents('.oml-annotation').data('id')
|
var id = $(g).parents('.oml-annotation').data('id')
|
||||||
console.log('deselect', g, id)
|
//console.log('deselect', g, id)
|
||||||
if (!Ox.contains(ids, id)) {
|
if (!Ox.contains(ids, id)) {
|
||||||
ids.push(id)
|
ids.push(id)
|
||||||
Ox.$parent.postMessage('selectAnnotation', {id: null})
|
Ox.$parent.postMessage('selectAnnotation', {id: null})
|
||||||
|
|
Loading…
Reference in a new issue