diff --git a/config.json b/config.json index de8f773..0a7072a 100644 --- a/config.json +++ b/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", diff --git a/oml/annotation/api.py b/oml/annotation/api.py new file mode 100644 index 0000000..005127b --- /dev/null +++ b/oml/annotation/api.py @@ -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) diff --git a/oml/annotation/models.py b/oml/annotation/models.py index 78fd835..1b144f9 100644 --- a/oml/annotation/models.py +++ b/oml/annotation/models.py @@ -4,6 +4,7 @@ import json import logging import os import shutil +import unicodedata from sqlalchemy.orm import load_only import ox @@ -31,10 +32,15 @@ class Annotation(db.Model): 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() @@ -43,28 +49,44 @@ class Annotation(db.Model): self.data = data @classmethod - def create(cls, **kwargs): - a = cls(**kwargs) - state.db.session.add(a) + 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): - for a in cls.query.filter_by(item_id=item_id, user=user, _id=annotation_id): - if a.data.get('id') == annotation_id: - return a + 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, user, item_id): + 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 + 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() @@ -73,6 +95,30 @@ class Annotation(db.Model): data['created'] = self.created data['modified'] = self.modified 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 + 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) + diff --git a/oml/api.py b/oml/api.py index 1975c7d..8cd4eb8 100644 --- a/oml/api.py +++ b/oml/api.py @@ -12,6 +12,7 @@ from oxtornado import actions import item.api import user.api +import annotation.api import update import utils diff --git a/oml/changelog.py b/oml/changelog.py index c16834c..3f89f47 100644 --- a/oml/changelog.py +++ b/oml/changelog.py @@ -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 diff --git a/oml/library.py b/oml/library.py index 362826d..3c17ed0 100644 --- a/oml/library.py +++ b/oml/library.py @@ -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 diff --git a/oml/localnodes.py b/oml/localnodes.py index 20562e7..5178f0a 100644 --- a/oml/localnodes.py +++ b/oml/localnodes.py @@ -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 diff --git a/oml/nodes.py b/oml/nodes.py index 8c28530..3e4c575 100644 --- a/oml/nodes.py +++ b/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') diff --git a/oml/queryparser.py b/oml/queryparser.py index 939611b..8562218 100644 --- a/oml/queryparser.py +++ b/oml/queryparser.py @@ -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() diff --git a/oml/server.py b/oml/server.py index b01d598..fbae2fc 100644 --- a/oml/server.py +++ b/oml/server.py @@ -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(): diff --git a/oml/settings.py b/oml/settings.py index 1d5d9ca..39df3a2 100644 --- a/oml/settings.py +++ b/oml/settings.py @@ -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 diff --git a/oml/setup.py b/oml/setup.py index 69024d0..1581e83 100644 --- a/oml/setup.py +++ b/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(';'): diff --git a/oml/update.py b/oml/update.py index e51ed08..3ae467a 100644 --- a/oml/update.py +++ b/oml/update.py @@ -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 diff --git a/oml/user/api.py b/oml/user/api.py index b3aff56..53448b3 100644 --- a/oml/user/api.py +++ b/oml/user/api.py @@ -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) diff --git a/oml/user/models.py b/oml/user/models.py index 77ff81e..ffe60f5 100644 --- a/oml/user/models.py +++ b/oml/user/models.py @@ -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') diff --git a/static/js/annotation.js b/static/js/annotation.js index 40af8f0..13887c6 100644 --- a/static/js/annotation.js +++ b/static/js/annotation.js @@ -46,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') } }); diff --git a/static/js/annotationPanel.js b/static/js/annotationPanel.js index 81196e6..c189abc 100644 --- a/static/js/annotationPanel.js +++ b/static/js/annotationPanel.js @@ -1,6 +1,7 @@ 'use strict'; oml.ui.annotationPanel = function() { + var ui = oml.user.ui; var ui = oml.user.ui; @@ -28,6 +29,7 @@ oml.ui.annotationPanel = function() { click: function() { var $annotation = oml.$ui.annotationFolder.find('.OMLAnnotation.selected') $annotation.length && $annotation.delete() + $deleteQuote.options({disabled: true}) } }).appendTo($bar); @@ -80,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); diff --git a/static/js/exportAnnotationsDialog.js b/static/js/exportAnnotationsDialog.js index f891efa..6078e19 100644 --- a/static/js/exportAnnotationsDialog.js +++ b/static/js/exportAnnotationsDialog.js @@ -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 diff --git a/static/js/viewer.js b/static/js/viewer.js index a340da1..362e178 100644 --- a/static/js/viewer.js +++ b/static/js/viewer.js @@ -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() @@ -95,14 +144,14 @@ oml.ui.viewer = function() { }).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) @@ -115,18 +164,13 @@ oml.ui.viewer = function() { } else if (event == 'selectText') { oml.$ui.annotationPanel.updateSelection(data) } else { + 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); @@ -145,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(); }; diff --git a/static/reader/epub.js b/static/reader/epub.js index bc5b491..57fd996 100644 --- a/static/reader/epub.js +++ b/static/reader/epub.js @@ -22,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) diff --git a/static/reader/pdf.js b/static/reader/pdf.js index df62fe6..0a0b50b 100644 --- a/static/reader/pdf.js +++ b/static/reader/pdf.js @@ -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) } }) }) @@ -96,6 +103,7 @@ function getHighlight() { 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, @@ -176,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})