From d3858531868689957feade6c83c42c25923432bf Mon Sep 17 00:00:00 2001 From: j Date: Wed, 14 May 2014 11:57:11 +0200 Subject: [PATCH] meta --- config.json | 16 +- ctl | 1 - migrations/versions/1a7c813a17c2_.py | 28 +++ migrations/versions/21589282102d_.py | 36 +++ migrations/versions/2350803a5a2d_.py | 26 +++ migrations/versions/7bb11a24276_.py | 36 +++ oml/changelog.py | 5 +- oml/item/api.py | 61 ++---- oml/item/models.py | 42 ++-- oml/item/query.py | 27 +-- oml/localnodes.py | 14 +- oml/meta/__init__.py | 50 +++++ oml/meta/abebooks.py | 38 ++++ oml/meta/{lccn.py => loc.py} | 33 ++- oml/meta/lookupbyisbn.py | 53 +++++ oml/meta/{ol.py => openlibrary.py} | 79 +++++-- oml/meta/scraper.py | 2 +- oml/meta/utils.py | 5 + oml/meta/worldcat.py | 69 ++++++ oml/node/cert.py | 42 ++++ oml/node/gencert.py | 25 --- oml/node/server.py | 16 +- oml/nodes.py | 83 +++++-- oml/oxflask/api.py | 8 +- oml/server.py | 9 +- oml/settings.py | 2 + oml/ssl_request.py | 69 ++++++ oml/user/api.py | 2 - oml/utils.py | 5 +- oml/websocket.py | 9 +- static/js/UI.js | 4 + static/js/appDialog.js | 82 +++---- static/js/deleteListDialog.js | 21 +- static/js/filter.js | 6 +- static/js/filtersOuterPanel.js | 27 +++ static/js/findDialog.js | 56 +++++ static/js/findElement.js | 13 +- static/js/findForm.js | 67 ++++++ static/js/folders.js | 8 +- static/js/identifyDialog.js | 191 ++++++++++++++++ static/js/infoView.js | 316 ++++++++++++--------------- static/js/listDialog.js | 13 +- static/js/mainMenu.js | 34 ++- static/js/oml.js | 1 + static/js/preferencesDialog.js | 2 +- static/js/resetUIDialog.js | 29 +-- static/js/utils.js | 68 +++--- static/json/js.json | 3 + 48 files changed, 1344 insertions(+), 488 deletions(-) create mode 100644 migrations/versions/1a7c813a17c2_.py create mode 100644 migrations/versions/21589282102d_.py create mode 100644 migrations/versions/2350803a5a2d_.py create mode 100644 migrations/versions/7bb11a24276_.py create mode 100644 oml/meta/abebooks.py rename oml/meta/{lccn.py => loc.py} (65%) create mode 100644 oml/meta/lookupbyisbn.py rename oml/meta/{ol.py => openlibrary.py} (58%) create mode 100644 oml/meta/utils.py create mode 100644 oml/meta/worldcat.py create mode 100644 oml/node/cert.py delete mode 100644 oml/node/gencert.py create mode 100644 oml/ssl_request.py create mode 100644 static/js/findDialog.js create mode 100644 static/js/findForm.js create mode 100644 static/js/identifyDialog.js diff --git a/config.json b/config.json index 87dcbb6..0ad448e 100644 --- a/config.json +++ b/config.json @@ -163,8 +163,12 @@ "id": "mediastate", "title": "Media State", "type": "string", - "find": true, - "sort": true + "sort": true, + "values": [ + {"id": "available", "title": "Available"}, + {"id": "transferring", "title": "Transferring"}, + {"id": "unavailable", "title": "Unavailable"} + ] }, { "id": "transferadded", @@ -201,6 +205,13 @@ "columnWidth": 96, "sort": true }, + { + "id": "asin", + "title": "ASIN", + "type": "string", + "columnWidth": 96, + "sort": true + }, { "id": "lccn", "title": "LCCN", @@ -360,6 +371,7 @@ "showSidebar": true, "sidebarSize": 256, "theme": "oxlight", + "updateAdvancedFindResults": false, "usersSelection": [] } } diff --git a/ctl b/ctl index c25e6aa..0560fe7 100755 --- a/ctl +++ b/ctl @@ -44,7 +44,6 @@ if [ "$1" == "debug" ]; then exit 1 fi shift - echo Open browser at http://$HOST python oml server $@ exit $? fi diff --git a/migrations/versions/1a7c813a17c2_.py b/migrations/versions/1a7c813a17c2_.py new file mode 100644 index 0000000..3f59366 --- /dev/null +++ b/migrations/versions/1a7c813a17c2_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 1a7c813a17c2 +Revises: 7bb11a24276 +Create Date: 2014-05-14 01:41:03.495320 + +""" + +# revision identifiers, used by Alembic. +revision = '1a7c813a17c2' +down_revision = '7bb11a24276' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('item', sa.Column('sort_asin', sa.String(length=1000), nullable=True)) + op.create_index(op.f('ix_item_sort_asin'), 'item', ['sort_asin'], unique=False) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_item_sort_asin'), table_name='item') + op.drop_column('item', 'sort_asin') + ### end Alembic commands ### diff --git a/migrations/versions/21589282102d_.py b/migrations/versions/21589282102d_.py new file mode 100644 index 0000000..6aaa857 --- /dev/null +++ b/migrations/versions/21589282102d_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 21589282102d +Revises: 2350803a5a2d +Create Date: 2014-05-13 15:47:29.747858 + +""" + +# revision identifiers, used by Alembic. +revision = '21589282102d' +down_revision = '2350803a5a2d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('filter', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('item_id', sa.String(length=32), nullable=True), + sa.Column('key', sa.String(length=200), nullable=True), + sa.Column('value', sa.Text(), nullable=True), + sa.Column('findvalue', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['item_id'], ['item.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_filter_key'), 'filter', ['key'], unique=False) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_filter_key'), table_name='filter') + op.drop_table('filter') + ### end Alembic commands ### diff --git a/migrations/versions/2350803a5a2d_.py b/migrations/versions/2350803a5a2d_.py new file mode 100644 index 0000000..9d13c2f --- /dev/null +++ b/migrations/versions/2350803a5a2d_.py @@ -0,0 +1,26 @@ +"""empty message + +Revision ID: 2350803a5a2d +Revises: 1ead68a53597 +Create Date: 2014-05-13 15:43:51.840049 + +""" + +# revision identifiers, used by Alembic. +revision = '2350803a5a2d' +down_revision = '1ead68a53597' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + pass + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + pass + ### end Alembic commands ### diff --git a/migrations/versions/7bb11a24276_.py b/migrations/versions/7bb11a24276_.py new file mode 100644 index 0000000..2320851 --- /dev/null +++ b/migrations/versions/7bb11a24276_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 7bb11a24276 +Revises: 21589282102d +Create Date: 2014-05-13 18:28:46.214059 + +""" + +# revision identifiers, used by Alembic. +revision = '7bb11a24276' +down_revision = '21589282102d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filter') + op.add_column('find', sa.Column('findvalue', sa.Text(), nullable=True)) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('find', 'findvalue') + op.create_table('filter', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('item_id', sa.VARCHAR(length=32), nullable=True), + sa.Column('key', sa.VARCHAR(length=200), nullable=True), + sa.Column('value', sa.TEXT(), nullable=True), + sa.Column('findvalue', sa.TEXT(), nullable=True), + sa.ForeignKeyConstraint(['item_id'], [u'item.id'], ), + sa.PrimaryKeyConstraint('id') + ) + ### end Alembic commands ### diff --git a/oml/changelog.py b/oml/changelog.py index 5757159..3bb75fe 100644 --- a/oml/changelog.py +++ b/oml/changelog.py @@ -49,7 +49,7 @@ class Changelog(db.Model): db.session.add(c) db.session.commit() if state.online: - state.nodes.queue('online', 'pushChanges', [c.json()]) + state.nodes.queue('peered', 'pushChanges', [c.json()]) @property def timestamp(self): @@ -123,6 +123,7 @@ class Changelog(db.Model): i = Item.get_or_create(itemid, info) i.users.append(user) i.update() + trigger_event('itemchange', {'fixme': 'new remote changes'}) return True def action_edititem(self, user, timestamp, itemid, meta): @@ -137,6 +138,7 @@ class Changelog(db.Model): elif meta[key] and (i.meta.get('mainid') != key or meta[key] != i.meta.get(key)): print 'new mapping', key, meta[key], 'currently', i.meta.get('mainid'), i.meta.get(i.meta.get('mainid')) i.update_mainid(key, meta[key]) + trigger_event('itemchange', {'fixme': 'new remote changes'}) return True def action_removeitem(self, user, timestamp, itemid): @@ -150,6 +152,7 @@ class Changelog(db.Model): else: db.session.delete(i) db.session.commit() + trigger_event('itemchange', {'fixme': 'new remote changes'}) return True def action_addlist(self, user, timestamp, name, query=None): diff --git a/oml/item/api.py b/oml/item/api.py index c8ca11a..7ace6d1 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -25,44 +25,21 @@ def find(request): data = json.loads(request.form['data']) if 'data' in request.form else {} q = query.parse(data) if 'group' in q: - response['items'] = [] - ''' - items = 'items' - item_qs = q['qs'] - order_by = query.order_by_group(q) - qs = models.Facet.objects.filter(key=q['group']).filter(item__id__in=item_qs) - qs = qs.values('value').annotate(items=Count('id')).order_by(*order_by) - - if 'positions' in q: - response['positions'] = {} - ids = [j['value'] for j in qs] - response['positions'] = utils.get_positions(ids, q['positions']) - elif 'range' in data: - qs = qs[q['range'][0]:q['range'][1]] - response['items'] = [{'name': i['value'], 'items': i[items]} for i in qs] - else: - response['items'] = qs.count() - ''' - _g = {} - key = utils.get_by_id(settings.config['itemKeys'], q['group']) - for item in q['qs']: - i = item.json() - if q['group'] in i: - values = i[q['group']] - if isinstance(values, basestring): - values = [values] - for value in values: - if key.get('filterMap') and value: - value = re.compile(key.get('filterMap')).findall(value) - if value: - value = value[0] - else: - continue - if value not in _g: - _g[value] = 0 - _g[value] += 1 - g = [{'name': k, 'items': _g[k]} for k in _g] - if 'sort' in data: # parse adds default sort to q! + names = {} + groups = {} + items = [i.id for i in q['qs']] + qs = models.Find.query.filter_by(key=q['group']) + if items: + qs = qs.filter(models.Find.item_id.in_(items)) + for f in qs.values('value', 'findvalue'): + value = f[0] + findvalue = f[1] + if findvalue not in groups: + groups[findvalue] = 0 + groups[findvalue] += 1 + names[findvalue] = value + g = [{'name': names[k], 'items': groups[k]} for k in groups] + if 'sort' in q: g.sort(key=lambda k: k[q['sort'][0]['key']]) if q['sort'][0]['operator'] == '-': g.reverse() @@ -90,9 +67,11 @@ def find(request): j = i.json() response['items'].append({k:j[k] for k in j if not data['keys'] or k in data['keys']}) else: - items = [i.json() for i in q['qs']] - response['items'] = len(items) - response['size'] = sum([i.get('size',0) for i in items]) + response['items'] = q['qs'].count() + #from sqlalchemy.sql import func + #models.db.session.query(func.sum(models.Item.sort_size).label("size")) + #response['size'] = x.scalar() + response['size'] = sum([i.sort_size or 0 for i in q['qs']]) return response actions.register(find) diff --git a/oml/item/models.py b/oml/item/models.py index 0febda0..e3884f5 100644 --- a/oml/item/models.py +++ b/oml/item/models.py @@ -20,7 +20,7 @@ from user.models import User from person import get_sort_name import media -from meta import scraper +import meta import state import utils @@ -201,6 +201,13 @@ class Item(db.Model): setattr(self, 'sort_%s' % key['id'], value) def update_find(self): + + def add(k, v): + f = Find(item_id=self.id, key=k) + f.findvalue = v.lower().strip() + f.value = v + db.session.add(f) + for key in config['itemKeys']: if key.get('find') or key.get('filter'): value = self.json().get(key['id'], None) @@ -208,16 +215,11 @@ class Item(db.Model): value = re.compile(key.get('filterMap')).findall(value) if value: value = value[0] if value: - if isinstance(value, list): - Find.query.filter_by(item_id=self.id, key=key['id']).delete() - for v in value: - f = Find(item_id=self.id, key=key['id']) - f.value = v.lower() - db.session.add(f) - else: - f = Find.get_or_create(self.id, key['id']) - f.value = value.lower() - db.session.add(f) + Find.query.filter_by(item_id=self.id, key=key['id']).delete() + if not isinstance(value, list): + value = [value] + for v in value: + add(key['id'], v) else: f = Find.get(self.id, key['id']) if f: @@ -313,16 +315,9 @@ class Item(db.Model): def scrape(self): mainid = self.meta.get('mainid') print 'scrape', mainid, self.meta.get(mainid) - if mainid == 'olid': - scraper.update_ol(self) - scraper.add_lookupbyisbn(self) - elif mainid in ('isbn10', 'isbn13'): - scraper.add_lookupbyisbn(self) - elif mainid == 'lccn': - import meta.lccn - info = meta.lccn.info(self.meta[mainid]) - for key in info: - self.meta[key] = info[key] + if mainid: + m = meta.lookup(mainid, self.meta[mainid]) + self.meta.update(m) else: print 'FIX UPDATE', mainid self.update() @@ -380,7 +375,7 @@ for key in config['itemKeys']: Item.id_keys = ['isbn10', 'isbn13', 'lccn', 'olid', 'oclc'] Item.item_keys = config['itemKeys'] -Item.filter_keys = [] +Item.filter_keys = [k['id'] for k in config['itemKeys'] if k.get('filter')] class Find(db.Model): id = db.Column(db.Integer(), primary_key=True) @@ -388,9 +383,10 @@ class Find(db.Model): item = db.relationship('Item', backref=db.backref('find', lazy='dynamic')) key = db.Column(db.String(200), index=True) value = db.Column(db.Text()) + findvalue = db.Column(db.Text()) def __repr__(self): - return (u'%s=%s' % (self.key, self.value)).encode('utf-8') + return (u'%s=%s' % (self.key, self.findvalue)).encode('utf-8') @classmethod def get(cls, item, key): diff --git a/oml/item/query.py b/oml/item/query.py index 5efa199..59ff044 100644 --- a/oml/item/query.py +++ b/oml/item/query.py @@ -11,7 +11,8 @@ from sqlalchemy.sql.expression import nullslast def parse(data): query = {} query['range'] = [0, 100] - query['sort'] = [{'key':'title', 'operator':'+'}] + if not 'group' in data: + query['sort'] = [{'key':'title', 'operator':'+'}] for key in ('keys', 'group', 'list', 'range', 'sort', 'query'): if key in data: query[key] = data[key] @@ -25,9 +26,7 @@ def parse(data): query['qs'] = models.Item.query.join( models.Find, models.Find.item_id==models.Item.id).filter( models.Find.value.contains(value)) - if 'group' in query: - query['qs'] = order_by_group(query['qs'], query['sort']) - else: + if not 'group' in query: query['qs'] = order(query['qs'], query['sort']) return query @@ -61,23 +60,3 @@ def order(qs, sort, prefix='sort_'): order_by = _order_by qs = qs.order_by(*order_by) return qs - -def order_by_group(qs, sort): - return qs - if 'sort' in query: - if len(query['sort']) == 1 and query['sort'][0]['key'] == 'items': - order_by = query['sort'][0]['operator'] == '-' and '-items' or 'items' - if query['group'] == "year": - secondary = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue' - order_by = (order_by, secondary) - elif query['group'] != "keyword": - order_by = (order_by, 'sortvalue') - else: - order_by = (order_by, 'value') - else: - order_by = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue' - order_by = (order_by, 'items') - else: - order_by = ('-sortvalue', 'items') - return order_by - diff --git a/oml/localnodes.py b/oml/localnodes.py index 42cd1f9..09bc7b5 100644 --- a/oml/localnodes.py +++ b/oml/localnodes.py @@ -47,6 +47,7 @@ class LocalNodes(Thread): 'username': preferences.get('username', 'anonymous'), 'host': self.host, 'port': server['node_port'], + 'cert': server['cert'] }) sig = sk.sign(message, encoding='base64') packet = json.dumps([sig, USER_ID, message]) @@ -65,11 +66,12 @@ class LocalNodes(Thread): data = data[:-1] # Strip trailing \0's data = self.verify(data) if data: - if data['id'] not in self._nodes: - thread.start_new_thread(self.new_node, (data, )) - else: - print 'UPDATE NODE', data - self._nodes[data['id']] = data + if data['id'] != USER_ID: + if data['id'] not in self._nodes: + thread.start_new_thread(self.new_node, (data, )) + #else: + # print 'UPDATE NODE', data + self._nodes[data['id']] = data def verify(self, data): try: @@ -81,7 +83,7 @@ class LocalNodes(Thread): if valid(user_id, data, sig): message = json.loads(data) message['id'] = user_id - for key in ['id', 'username', 'host', 'port']: + for key in ['id', 'username', 'host', 'port', 'cert']: if key not in message: return None return message diff --git a/oml/meta/__init__.py b/oml/meta/__init__.py index e69de29..7a933a3 100644 --- a/oml/meta/__init__.py +++ b/oml/meta/__init__.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + +import abebooks +import loc +import lookupbyisbn +import openlibrary +import worldcat + +providers = [ + ('openlibrary', 'olid'), + ('loc', 'lccn'), + ('worldcat', 'oclc'), + ('lookupbyisbn', 'asin'), + ('abebooks', 'isbn10') +] + +def find(title, author=None, publisher=None, year=None): + return [] + +def lookup(key, value): + data = {key: value} + ids = [(key, value)] + provider_data = {} + done = False + while not done: + done = True + for provider, id in providers: + for key, value in ids: + for kv in globals()[provider].get_ids(key, value): + if not kv in ids: + ids.append(kv) + done = False + print ids + for k, v in ids: + for provider, id in providers: + if id == k: + provider_data[provider] = globals()[provider].lookup(v) + for provider in sorted( + provider_data.keys(), + key=lambda x: -len(provider_data[x]) + ): + print provider, len(provider_data[provider]) + for k_, v_ in provider_data[provider].iteritems(): + if not k_ in data: + data[k_] = v_ + return data + + diff --git a/oml/meta/abebooks.py b/oml/meta/abebooks.py new file mode 100644 index 0000000..804968d --- /dev/null +++ b/oml/meta/abebooks.py @@ -0,0 +1,38 @@ +from ox.cache import read_url +import re +import lxml.html + +def get_ids(key, value): + ids = [] + if key in ('isbn10', 'isbn13'): + base = 'http://www.abebooks.com' + url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id) + data = read_url(url) + urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data) + if urls: + ids.append((key, value)) + if ids: + print 'abebooks.get_ids', key, value + print ids + return ids + +def lookup(id): + print 'abebooks.lookup', id + return {} + +def get_data(id): + info = {} + base = 'http://www.abebooks.com' + url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id) + data = read_url(url) + urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data) + if urls: + details = '%s%s' % (base, urls[0]) + data = read_url(details) + doc = lxml.html.document_fromstring(data) + for e in doc.xpath("//*[contains(@id, 'biblio')]"): + key = e.attrib['id'].replace('biblio-', '') + value = e.text_content() + if value and key not in ('bookcondition', 'binding'): + info[key] = value + return info diff --git a/oml/meta/lccn.py b/oml/meta/loc.py similarity index 65% rename from oml/meta/lccn.py rename to oml/meta/loc.py index f660518..4c4384a 100644 --- a/oml/meta/lccn.py +++ b/oml/meta/loc.py @@ -4,18 +4,35 @@ from __future__ import division import ox from ox.cache import read_url +import re import xml.etree.ElementTree as ET from utils import normalize_isbn from marc_countries import COUNTRIES -def info(id): +def get_ids(key, value): + ids = [] + if key in ['isbn10', 'isbn13']: + url = 'http://www.loc.gov/search/?q=%s&all=true' % value + html = ox.cache.read_url(url) + match = re.search('"http://lccn.loc.gov/(\d+)"', html) + if match: + ids.append(('lccn', match.group(1))) + if ids: + print 'loc.get_ids', key, value + print ids + return ids + +def lookup(id): + print 'loc.lookup', id ns = '{http://www.loc.gov/mods/v3}' url = 'http://lccn.loc.gov/%s/mods' % id data = read_url(url) mods = ET.fromstring(data) - info = {} + info = { + 'lccn': id + } info['title'] = ''.join([e.text for e in mods.findall(ns + 'titleInfo')[0]]) origin = mods.findall(ns + 'originInfo') if origin: @@ -28,7 +45,9 @@ def info(id): elif terms and terms[0].attrib['type'] == 'code': e = terms[0] info['country'] = COUNTRIES.get(e.text, e.text) - info['publisher'] = ''.join([e.text for e in origin[0].findall(ns + 'publisher')]) + publisher = [e.text for e in origin[0].findall(ns + 'publisher')] + if publisher: + info['publisher'] = publisher[0] info['date'] = ''.join([e.text for e in origin[0].findall(ns + 'dateIssued')]) for i in mods.findall(ns + 'identifier'): if i.attrib['type'] == 'oclc': @@ -43,10 +62,12 @@ def info(id): info['classification'] = i.text info['author'] = [] for a in mods.findall(ns + 'name'): - if a.attrib['usage'] == 'primary': - info['author'].append(''.join([e.text for e in a.findall(ns + 'namePart')])) - info['author'] = [ox.normalize_name(a[:-1]) for a in info['author']] + if a.attrib.get('usage') == 'primary': + info['author'].append(' '.join([e.text for e in a.findall(ns + 'namePart') if not e.attrib.get('type') in ('date', )])) + info['author'] = [ox.normalize_name(a) for a in info['author']] for key in info.keys(): if not info[key]: del info[key] return info + +info = lookup diff --git a/oml/meta/lookupbyisbn.py b/oml/meta/lookupbyisbn.py new file mode 100644 index 0000000..15da05c --- /dev/null +++ b/oml/meta/lookupbyisbn.py @@ -0,0 +1,53 @@ +from ox.cache import read_url +from ox import find_re, strip_tags +import re + +base = 'http://www.lookupbyisbn.com' + +def get_ids(key, value): + ids = [] + if key in ('isbn10', 'isbn13', 'asin'): + url = '%s/Search/Book/%s/1' % (base, value) + data = read_url(url).decode('utf-8') + m = re.compile('href="(/Lookup/Book/[^"]+?)"').findall(data) + if m: + asin = m[0].split('/')[-3] + ids.append(('asin', asin)) + if ids: + print 'lookupbyisbn.get_ids', key, value + print ids + return ids + +def lookup(id): + print 'lookupbyisbn.lookup', id + r = { + 'asin': id + } + url = '%s/Lookup/Book/%s/%s/1' % (base, id, id) + data = read_url(url).decode('utf-8') + r["title"] = find_re(data, "

(.*?)

") + keys = { + 'author': 'Author(s)', + 'publisher': 'Publisher', + 'date': 'Publication date', + 'edition': 'Edition', + 'binding': 'Binding', + 'volume': 'Volume(s)', + 'pages': 'Pages', + } + for key in keys: + r[key] = find_re(data, '%s:(.*?)'% re.escape(keys[key])) + if r[key] == '--': + r[key] = '' + if key == 'pages' and r[key]: + r[key] = int(r[key]) + desc = find_re(data, '

Description:<\/h2>(.*?)

', ' ').replace('
', ' ').replace('
', ' ') + r['description'] = strip_tags(desc).strip() + if r['description'] == u'Description of this item is not available at this time.': + r['description'] = '' + r['cover'] = find_re(data, 'Book cover').replace('._SL160_', '')
+    if 'author' in r and isinstance(r['author'], basestring):
+        r['author'] = [r['author']]
+    return r
+
diff --git a/oml/meta/ol.py b/oml/meta/openlibrary.py
similarity index 58%
rename from oml/meta/ol.py
rename to oml/meta/openlibrary.py
index bee15e7..8b9d343 100644
--- a/oml/meta/ol.py
+++ b/oml/meta/openlibrary.py
@@ -5,30 +5,39 @@ from __future__ import division
 from ox.cache import read_url
 import json
 
-from utils import normalize_isbn
 from marc_countries import COUNTRIES
+from utils import normalize_isbn
 
-def find(query):
-    url = 'https://openlibrary.org/search.json?q=%s' % query
-    data = json.loads(read_url(url))
-    return data
+def get_ids(key, value):
+    ids = []
+    if key == 'olid':
+        data = lookup(value, True)
+        for id in ('isbn10', 'isbn13', 'lccn', 'oclc'):
+            if id in data:
+                for v in data[id]:
+                    if (id, v) not in ids:
+                        ids.append((id, v))
+    elif key in ('isbn10', 'isbn13'):
+        print 'openlibraryid.get_ids', key, value
+        r = find('isbn:%s' % value)
+        for d in sorted(r.get('docs', []), key=lambda d: -d['last_modified_i']):
+            if 'edition_key' in d:
+                v = d['edition_key']
+                if isinstance(v, list):
+                    v = v[0]
+                for kv in [('olid', v)] + get_ids('olid', v):
+                    if kv not in ids:
+                        ids.append(kv)
+    if ids:
+        print 'openlibraryid.get_ids', key, value
+        print ids
+    return ids
 
-def authors(authors):
-    return resolve_names(authors)
-
-def resolve_names(objects, key='name'):
-    r = []
-    for o in objects:
-        url = 'https://openlibrary.org%s.json' % o['key']
-        data = json.loads(read_url(url))
-        r.append(data[key])
-    return r
-
-def languages(languages):
-    return resolve_names(languages)
-
-def info(id):
-    data = {}
+def lookup(id, return_all=False):
+    #print 'openlibrary.lookup', id
+    data = {
+        'olid': id
+    }
     url = 'https://openlibrary.org/books/%s.json' % id
     info = json.loads(read_url(url))
     keys = {
@@ -58,10 +67,34 @@ def info(id):
                 value = COUNTRIES.get(value, value)
             elif key == 'languages':
                 value = languages(value)
-            elif isinstance(value, list) and key not in ('publish_places'):
+            elif not return_all and isinstance(value, list) and key not in ('publish_places'):
                 value = value[0]
             if key in ('isbn_10', 'isbn_13'):
-                value = normalize_isbn(value)
+                if isinstance(value, list):
+                    value = map(normalize_isbn, value)
+                else:
+                    value = normalize_isbn(value)
             data[keys[key]] = value
     return data
 
+info = lookup
+
+def find(query):
+    url = 'https://openlibrary.org/search.json?q=%s' % query
+    data = json.loads(read_url(url))
+    return data
+
+def authors(authors):
+    return resolve_names(authors)
+
+def resolve_names(objects, key='name'):
+    r = []
+    for o in objects:
+        url = 'https://openlibrary.org%s.json' % o['key']
+        data = json.loads(read_url(url))
+        r.append(data[key])
+    return r
+
+def languages(languages):
+    return resolve_names(languages)
+
diff --git a/oml/meta/scraper.py b/oml/meta/scraper.py
index 2a55321..3996cc1 100644
--- a/oml/meta/scraper.py
+++ b/oml/meta/scraper.py
@@ -4,7 +4,7 @@ import ox.web.lookupbyisbn
 
 from utils import normalize_isbn
 
-import ol
+import openlibrary as ol
 
 def add_lookupbyisbn(item): 
     isbn = item.meta.get('isbn10', item.meta.get('isbn13'))
diff --git a/oml/meta/utils.py b/oml/meta/utils.py
new file mode 100644
index 0000000..3812044
--- /dev/null
+++ b/oml/meta/utils.py
@@ -0,0 +1,5 @@
+
+
+def normalize_isbn(value):
+    return ''.join([s for s in value if s.isdigit() or s == 'X'])
+
diff --git a/oml/meta/worldcat.py b/oml/meta/worldcat.py
new file mode 100644
index 0000000..1bf8cbd
--- /dev/null
+++ b/oml/meta/worldcat.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# vi:si:et:sw=4:sts=4:ts=4
+from __future__ import division
+
+from ox.cache import read_url
+import lxml.html
+import re
+from utils import normalize_isbn
+import stdnum.isbn
+
+base_url = 'http://www.worldcat.org'
+
+def get_ids(key, value):
+    ids = []
+    if key in ['isbn10', 'isbn13']:
+        url = '%s/search?qt=worldcat_org_bks&q=%s' % (base_url, value)
+        html = read_url(url)
+        matches = re.compile('/title.*?oclc/(\d+).*?') + $panel = Ox.TabPanel({ + content: function(id) { + var $logo = Ox.Element(), + $text = Ox.Element() + .addClass('OxTextPage'), + title = Ox.getObjectById( + Ox.getObjectById(oml.config.pages, 'app').parts, + id + ).title; + $('') .attr({ src: '/static/png/oml.png' }) .css({ - position: 'absolute', - left: '16px', - top: '16px', + position: 'absolute', + left: '16px', + top: '16px', width: '192px', height: '192px' }) .appendTo($logo); $('
') - .css({ - position: 'absolute', - left: '16px', - right: '24px', - top: '24px', - overflowY: 'auto' - }) - .html( - '

' + title + '

' - + '

The lazy brown fox jumped over the lazy black fox, but otherwise not really much happened here since you last checked.' - ) - .appendTo($text); + .css({ + position: 'absolute', + left: '16px', + right: '24px', + top: '24px', + overflowY: 'auto' + }) + .html( + '

' + title + '

' + + '

The lazy brown fox jumped over the lazy black fox, but otherwise not really much happened here since you last checked.' + ) + .appendTo($text); return Ox.SplitPanel({ elements: [ { @@ -58,14 +58,14 @@ oml.ui.appDialog = function() { ], orientation: 'horizontal' }); - }, - tabs: tabs - }) - .bindEvent({ - change: function(data) { + }, + tabs: tabs + }) + .bindEvent({ + change: function(data) { oml.UI.set({'part.app': data.selected}); - } - }), + } + }), that = Ox.Dialog({ buttons: [ @@ -87,7 +87,7 @@ oml.ui.appDialog = function() { width: 768 }) .bindEvent({ - close: function() { + close: function() { if (ui.page == 'app') { oml.UI.set({page: ''}); } @@ -100,7 +100,7 @@ oml.ui.appDialog = function() { }); that.update = function(section) { - $panel.selectTab(section); + $panel.selectTab(section); }; return that; diff --git a/static/js/deleteListDialog.js b/static/js/deleteListDialog.js index 8dcbf1f..c53d7a3 100644 --- a/static/js/deleteListDialog.js +++ b/static/js/deleteListDialog.js @@ -19,17 +19,18 @@ oml.ui.deleteListDialog = function() { oml.api.removeList({ id: ui._list }, function() { - oml.UI.set({ - find: { - conditions: [{ - key: 'list', - operator: '==', - value: ':' - }], - operator: '&' - } + oml.updateLists(function() { + oml.UI.set({ + find: { + conditions: [{ + key: 'list', + operator: '==', + value: ':' + }], + operator: '&' + } + }); }); - oml.updateLists(); }); }); diff --git a/static/js/filter.js b/static/js/filter.js index 7c861f4..318e360 100644 --- a/static/js/filter.js +++ b/static/js/filter.js @@ -133,12 +133,8 @@ oml.ui.filter = function(id) { }, sort: function(data) { var filters = Ox.clone(ui.filters, true); - filters[filterIndex].sort = [Ox.clone(data)]; + filters[filterIndex].sort = [{key: data.key, operator: data.operator}]; oml.UI.set({filters: filters}); - }, - oml_find: function() { - Ox.print('%%%%', 'RELOADING FILTER') - that.reloadList(true); } }), diff --git a/static/js/filtersOuterPanel.js b/static/js/filtersOuterPanel.js index e8148a0..20876dc 100644 --- a/static/js/filtersOuterPanel.js +++ b/static/js/filtersOuterPanel.js @@ -56,6 +56,33 @@ oml.ui.filtersOuterPanel = function() { }); oml.updateFilterMenus(); } + }, + oml_find: function() { + var previousUI = oml.UI.getPrevious(); + Ox.print('WTF', ui, oml.user.ui, Object.keys(ui), Object.keys(previousUI)); + ui._filterState.forEach(function(data, index) { + if (!Ox.isEqual(data.selected, previousUI._filterState[index].selected)) { + oml.$ui.filters[index].options( + ui.showFilters ? { + selected: data.selected + } : { + _selected: data.selected, + selected: [] + } + ); + } + if (!Ox.isEqual(data.find, previousUI._filterState[index].find)) { + Ox.print('::::', index, 'UNEQUAL', data.find, previousUI._filterState[index].find) + if (!ui.showFilters) { + oml.$ui.filters[index].options({ + _selected: data.selected + }); + } + // we can call reloadList here, since the items function + // handles the hidden filters case without making requests + oml.$ui.filters[index].reloadList(); + } + }); } }); diff --git a/static/js/findDialog.js b/static/js/findDialog.js new file mode 100644 index 0000000..9b23736 --- /dev/null +++ b/static/js/findDialog.js @@ -0,0 +1,56 @@ +'use strict'; + +oml.ui.findDialog = function() { + + var ui = oml.user.ui, + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'done', + title: Ox._('Done') + }) + .bindEvent({ + click: function() { + var list = oml.$ui.findForm.getList(); + if (list.save) { + oml.addList({ + name: list.name, + query: list.query + }); + } + that.close() + } + }) + ], + closeButton: true, + content: oml.$ui.findForm = oml.ui.findForm() + .css({margin: '16px'}), + fixedSize: true, + height: 264, + removeOnClose: true, + title: Ox._('Advanced Find'), + width: 648 + Ox.UI.SCROLLBAR_SIZE + }), + + $updateCheckbox = Ox.Checkbox({ + title: Ox._('Update Results in the Background'), + value: oml.user.ui.updateAdvancedFindResults + }) + .css({ + float: 'left', + margin: '4px' + }) + .bindEvent({ + change: function(data) { + oml.UI.set({updateAdvancedFindResults: data.value}); + //data.value && oml.$ui.findForm.updateResults(); + } + }); + + $($updateCheckbox.find('.OxButton')[0]).css({margin: 0}); + $(that.find('.OxBar')[1]).append($updateCheckbox); + + return that; + +}; \ No newline at end of file diff --git a/static/js/findElement.js b/static/js/findElement.js index d91f05f..62f8f81 100644 --- a/static/js/findElement.js +++ b/static/js/findElement.js @@ -36,12 +36,17 @@ oml.ui.findElement = function() { }) .bindEvent({ change: function(data) { + var menu = 'findMenu_finditems_' + data.value, + previousMenu = 'findMenu_finditems_' + previousFindKey; + oml.$ui.mainMenu.checkItem(menu); + oml.$ui.mainMenu.setItemKeyboard(previousMenu, ''); + oml.$ui.mainMenu.setItemKeyboard(menu, 'control f'); if (data.value == 'advanced') { - oml.$ui.mainMenu.checkItem('findMenu_find_' + previousFindKey); + // FIXME: control f when advanced that.updateElement(); - oml.$ui.filterDialog = oml.ui.filterDialog().open(); + oml.$ui.findDialog = oml.ui.findDialog().open(); } else { - oml.$ui.mainMenu.checkItem('findMenu_find_' + data.value); + oml.$ui.findInput.options({ autocomplete: getAutocomplete(), placeholder: '' @@ -77,7 +82,7 @@ oml.ui.findElement = function() { hasPressedClear = false; } oml.$ui.findInput.blurInput(); - oml.$ui.filterDialog = oml.ui.filterDialog().open(); + oml.$ui.findDialog = oml.ui.findDialog().open(); } }, submit: function(data) { diff --git a/static/js/findForm.js b/static/js/findForm.js new file mode 100644 index 0000000..52491ed --- /dev/null +++ b/static/js/findForm.js @@ -0,0 +1,67 @@ +'use strict'; + +oml.ui.findForm = function(list) { + + //Ox.print('FIND FORM LIST QUERY', list.query); + + var ui = oml.user.ui, + + that = Ox.Element(), + + $filter = Ox.Filter({ + findKeys: oml.config.itemKeys.map(function(key) { + return Ox.extend({}, key, { + title: Ox._(key.title), + type: key.id == 'mediastate' ? 'item' : key.type + }); + }).concat([{ + id: 'list', + title: Ox._('List'), + type: 'item', + values: ui._lists.filter(function(list) { + return list.type != 'smart' + }).map(function(list) { + return {id: list.id, title: list.title}; + }) + }]), + list: list ? null : { + sort: ui.listSort, + view: ui.listView + }, + sortKeys: oml.config.sortKeys, + value: Ox.clone(list ? list.query : ui.find, true), + viewKeys: oml.config.listViews + }) + .bindEvent({ + change: function(data) { + (list ? oml.api.editList : Ox.noop)(list ? { + id: list.id, + query: data.value + } : {}, function(result) { + if (ui.updateAdvancedFindResults) { + updateResults(); + } + }); + } + }) + .appendTo(that); + + function updateResults() { + if (list) { + Ox.Request.clearCache(list.id); + oml.$ui.list.reloadList(); + oml.$ui.filters.forEach(function($filter) { + $filter.reloadList(); + }); + } else { + oml.UI.set({find: Ox.clone($filter.options('value'), true)}); + oml.$ui.findElement.updateElement(); + } + } + + that.getList = $filter.getList; + that.value = $filter.value; + + return that; + +}; \ No newline at end of file diff --git a/static/js/folders.js b/static/js/folders.js index afc884e..f225e5e 100644 --- a/static/js/folders.js +++ b/static/js/folders.js @@ -33,9 +33,6 @@ oml.ui.folders = function() { oml.api.find({query: getFind()}, function(result) { oml.$ui.librariesList.value('', 'items', result.data.items); }); - }, - open: function() { - }, select: function() { oml.UI.set({find: getFind('')}); @@ -148,9 +145,6 @@ oml.ui.folders = function() { ); }); }, - open: function() { - oml.$ui.listDialog = oml.ui.listDialog().open(); - }, select: function(data) { oml.UI.set({find: getFind(data.ids[0])}); }, @@ -206,7 +200,7 @@ oml.ui.folders = function() { }); }, open: function() { - oml.ui.listDialog().open(); + !index && oml.ui.listDialog().open(); }, select: function(data) { oml.UI.set({find: getFind(data.ids[0])}); diff --git a/static/js/identifyDialog.js b/static/js/identifyDialog.js new file mode 100644 index 0000000..7d5d6fe --- /dev/null +++ b/static/js/identifyDialog.js @@ -0,0 +1,191 @@ +'use strict'; + +oml.ui.identifyDialog = function(data) { + + var ui = oml.user.ui, + + ids = [ + 'isbn10', 'isbn13', 'asin', 'lccn', 'oclc', 'olid' + ].map(function(id) { + return { + id: id, + title: Ox._(Ox.getObjectById(oml.config.itemKeys, id).title) + }; + }), + + keys = [ + 'title', 'author', 'publisher', 'date' + ].map(function(id) { + return { + id: id, + title: Ox._(Ox.getObjectById(oml.config.itemKeys, id).title) + }; + }), + + $input = Ox.FormElementGroup({ + elements: [ + Ox.Select({ + items: ids, + overlap: 'right', + value: 'isbn10', + width: 128 + }), + Ox.Input({ + value: data['isbn10'] || '', + width: 610 + }) + ] + }) + .css({margin: '16px'}), + + $preview = Ox.Element(), + + $idPanel = Ox.SplitPanel({ + elements: [ + {element: Ox.Element().append($input), size: 48}, + {element: $preview} + ], + orientation: 'vertical' + }), + + $form = Ox.Form({ + items: keys.map(function(key) { + return Ox.Input({ + id: key.id, + labelWidth: 128, + label: key.title, + value: key == 'author' + ? (data[key.id] || []).join(', ') + : data[key.id], + width: 736 + }); + }) + }) + .css({padding: '16px'}) + .bindEvent({ + change: function(data) { + Ox.print('FORM CHANGE', data); + } + }), + + $list = Ox.TableList({ + columns: [ + { + id: 'index' + }, + { + id: 'title', + visible: true, + width: 288, + }, + { + id: 'author', + visible: true, + width: 224 + }, + { + id: 'publisher', + visible: true, + width: 160 + }, + { + id: 'date', + visible: true, + width: 96 + } + ], + items: [], + max: 1, + sort: [{key: 'index', operator: '+'}], + unique: 'index' + }) + .bindEvent({ + select: function(data) { + $that.options('buttons')[1].options({ + disabled: data.ids.length == 0 + }); + } + }), + + $titlePanel = Ox.SplitPanel({ + elements: [ + {element: Ox.Element().append($form), size: 120}, + {element: $list} + ], + orientation: 'vertical' + }), + + $bar = Ox.Bar({size: 24}), + + $buttons = Ox.ButtonGroup({ + buttons: [ + {id: 'id', title: Ox._('Look Up by ID')}, + {id: 'title', title: Ox._('Find by Title')} + ], + selectable: true, + selected: 'id' + }) + .css({ + width: '768px', + padding: '4px 0', + textAlign: 'center' + }) + .bindEvent({ + change: function(data) { + $innerPanel.options({selected: data.value}); + } + }) + .appendTo($bar), + + $innerPanel = Ox.SlidePanel({ + elements: [ + {id: 'id', element: $idPanel}, + {id: 'title', element: $titlePanel} + ], + orientation: 'horizontal', + selected: 'id', + size: 768 + }), + + $outerPanel = Ox.SplitPanel({ + elements: [ + {element: $bar, size: 24}, + {element: $innerPanel} + ], + orientation: 'vertical' + }), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'dontupdate', + title: Ox._('No, Don\'t Update') + }) + .bindEvent({ + click: function() { + that.close(); + } + }), + Ox.Button({ + disabled: true, + id: 'update', + title: Ox._('Yes, Update') + }) + .bindEvent({ + click: function() { + Ox.print('NOT IMPLEMENTED'); + that.close(); + } + }) + ], + closeButton: true, + content: $outerPanel, + fixedSize: true, + height: 384, + title: Ox._('Identify Book'), + width: 768 + }); + + return that; + +}; \ No newline at end of file diff --git a/static/js/infoView.js b/static/js/infoView.js index d732886..42dcacb 100644 --- a/static/js/infoView.js +++ b/static/js/infoView.js @@ -20,45 +20,6 @@ oml.ui.infoView = function() { } }), - $identifyPanel = Ox.SplitPanel({ - elements: [ - {element: Ox.Element(), size: 120}, - {element: Ox.Element()} - ], - orientation: 'vertical' - }), - - $identifyDialog = Ox.Dialog({ - buttons: [ - Ox.Button({ - id: 'dontupdate', - title: Ox._('No, Don\'t Update') - }) - .bindEvent({ - click: function() { - $identifyDialog.close(); - } - }), - Ox.Button({ - disabled: true, - id: 'update', - title: Ox._('Yes, Update') - }) - .bindEvent({ - click: function() { - $identifyDialog.close(); - } - }) - ], - closeButton: true, - content: $identifyPanel, - fixedSize: true, - height: 384, - //removeOnClose: true, - title: Ox._('Identify Book'), - width: 768 - }), - $cover = Ox.Element() .css({ position: 'absolute', @@ -88,71 +49,27 @@ oml.ui.infoView = function() { }) .appendTo(that); + function formatLight(str) { + return '' + str + ''; + } + + function formatValue(value, key) { + return (Ox.isArray(value) ? value : [value]).map(function(value) { + return key ? + '' + value + '' + : value; + }).join(', '); + } + function identify(data) { - var $form = Ox.Form({ - items: [ - 'title', 'author', 'publisher', 'date' - ].map(function(key) { - return Ox.Input({ - id: key, - labelWidth: 128, - label: Ox.getObjectById(oml.config.itemKeys, key).title, - value: key == 'author' ? (data[key] || []).join(', ') : data[key], - width: 736 - }); - }) - }) - .css({padding: '16px'}) - .bindEvent({ - change: function(data) { - Ox.print('FORM CHANGE', data); - } - }), - $list = Ox.TableList({ - columns: [ - { - id: 'index' - }, - { - id: 'title', - visible: true, - width: 288, - }, - { - id: 'author', - visible: true, - width: 224 - }, - { - id: 'publisher', - visible: true, - width: 160 - }, - { - id: 'date', - visible: true, - width: 96 - } - ], - items: [], - max: 1, - sort: [{key: 'index', operator: '+'}], - unique: 'index' - }) - .bindEvent({ - select: function(data) { - $identifyDialog.options('buttons')[1].options({ - disabled: data.ids.length == 0 - }); - } - }); - $identifyPanel.replaceElement(0, $form); - $identifyPanel.replaceElement(1, $list); + oml.ui.identifyDialog(data).open(); + return; + $identifyPanel.select('id'); $identifyDialog.open(); identify(data); function identify(data) { oml.api.identify(data, function(result) { - $list.options({ + $identifyList.options({ items: result.data.items.map(function(item, index) { return Ox.extend(item, {index: index}); }) @@ -162,7 +79,38 @@ oml.ui.infoView = function() { } function renderMediaButton(data) { - return data.mediastate == 'unavailable' + function getListItems() { + var items = []; + if (ui._lists) { + items = ui._lists.filter(function(list) { + return list.user == oml.user.preferences.username + && list.type != 'smart'; + }).map(function(list) { + return { + id: list.id, + title: Ox._('Download to {0}', [list.name]) + }; + }); + items.splice(1, 0, [{}]); + } + return items; + } + function setListItems() { + if ($element && ui._lists) { + $element.options({ + disabled: false + }).options('elements')[1].options({ + items: getListItems() + }); + } else { + setTimeout(setListItems, 100); + } + } + + if (data.mediastate == 'unavailable' && !ui._lists) { + setListItems(); + } + var $element = data.mediastate == 'unavailable' ? Ox.FormElementGroup({ elements: [ Ox.Button({ @@ -179,17 +127,24 @@ oml.ui.infoView = function() { } }), Ox.MenuButton({ - items: [ - {id: '', title: Ox._('Library')} - ], + disabled: !ui._lists, + items: getListItems(), overlap: 'left', title: 'list', tooltip: Ox._('Download Book to a List'), type: 'image' }) .bindEvent({ - click: function() { - // ... + click: function(data) { + data.mediastate = 'transferring'; + that.update(data, $data); + oml.api.download(Ox.extend({ + id: ui.item, + }, data.id == ':' ? {} : { + list: data.id.slice(1) + }), function(result) { + // ... + }); } }) ], @@ -234,12 +189,13 @@ oml.ui.infoView = function() { oml.UI.set({itemView: 'book'}); } }); + return $element; } that.update = function(idOrData, $elements) { var data = Ox.isObject(idOrData) ? idOrData : null, - id = data ? null : idOrData; + id = data ? null : idOrData, $elements = $elements ? Ox.makeArray($elements) @@ -256,6 +212,7 @@ oml.ui.infoView = function() { Ox.print('BOOK DATA', data) var $reflection, $mediaButton, + isEditable = !data.mainid && data.mediastate == 'available', ratio = data.coverRatio || 0.75, size = 256, width = Math.round(ratio >= 1 ? size : size * ratio), @@ -312,22 +269,47 @@ oml.ui.infoView = function() { } else if ($element == $info) { $('

') - .css({ - marginTop: '-4px', - fontSize: '13px', - fontWeight: 'bold' - }) - .text(data.title || '') + .css({marginTop: '-2px'}) + .append( + Ox.EditableContent({ + clickLink: oml.clickLink, + editable: isEditable, + tooltip: isEditable ? oml.getEditTooltip() : '', + value: data.title + }) + .css({ + fontSize: '13px', + fontWeight: 'bold' + }) + ) .appendTo($info); - $('
') - .css({ - marginTop: '4px', - fontSize: '13px', - fontWeight: 'bold' - }) - .text((data.author || []).join(', ')) - .appendTo($info); + if (data.author || isEditable) { + $('
') + .css({ + marginTop: '4px', + fontSize: '13px', + fontWeight: 'bold' + }) + .append( + Ox.EditableContent({ + clickLink: oml.clickLink, + editable: isEditable, + format: function(value) { + return formatValue(value.split(', '), 'author'); + }, + placeholder: formatLight(Ox._('Unknown Author')), + tooltip: isEditable ? oml.getEditTooltip() : '', + value: data.author ? data.author.join(', ') : '' + }) + .css({ + fontSize: '13px', + fontWeight: 'bold' + }) + ) + .appendTo($info); + } + $('
') .css({ @@ -342,17 +324,16 @@ oml.ui.infoView = function() { ) .appendTo($info); - $('
') - .css({ - marginTop: '8px', - textAlign: 'justify' - }) - .html( - data.description - ? Ox.encodeHTMLEntities(data.description) - : 'No description' - ) - .appendTo($info); + if (data.description) { + $('
') + .css({ + marginTop: '8px', + textAlign: 'justify' + }) + .html(Ox.encodeHTMLEntities(data.description)) + .appendTo($info); + } + } else if ($element == $data) { @@ -362,7 +343,7 @@ oml.ui.infoView = function() { $('
') .addClass('OxSelectable') .css({ - marginTop: '8px', + marginTop: '10px', }) .text( [ @@ -383,39 +364,31 @@ oml.ui.infoView = function() { }) .text(title) .appendTo($data); - Ox.EditableContent({ - editable: false, - format: function(value) { - return value ? Ox.formatDate(value, '%b %e, %Y') : ''; - }, - placeholder: Ox._('unknown'), - value: data[id] || '' - }) + $('
') + .text(Ox.formatDate(data[id], '%b %e, %Y')) .appendTo($data); } }); - if (data.mediastate == 'available') { - - Ox.Button({ - title: Ox._('Identify Book...'), - width: 128 - }) - .css({marginTop: '16px'}) - .bindEvent({ - click: function() { - identify(data); - } - }) - .appendTo($data); - - [ - 'isbn10', 'isbn13', 'lccn', 'olid', 'oclc', 'mainid' - ].forEach(function(id, index) { - - var title = Ox.getObjectById(oml.config.itemKeys, id).title, - placeholder = id == 'mainid' ? 'none' : 'unknown'; + Ox.Button({ + disabled: data.mediastate != 'available', + title: Ox._('Identify Book...'), + width: 128 + }) + .css({marginTop: '16px'}) + .bindEvent({ + click: function() { + identify(data); + } + }) + .appendTo($data); + [ + 'isbn10', 'isbn13', 'asin', 'lccn', 'oclc', 'olid' + ].forEach(function(id, index) { + var title; + if (data[id]) { + title = Ox.getObjectById(oml.config.itemKeys, id).title; $('
') .css({ marginTop: (index == 0 ? 10 : 6) + 'px', @@ -423,28 +396,13 @@ oml.ui.infoView = function() { }) .text(title) .appendTo($data); - Ox.EditableContent({ - editable: true, - format: function(value) { - return id == 'mainid' - ? Ox.getObjectById(oml.config.itemKeys, value).title - : value; - }, - placeholder: placeholder, - tooltip: Ox._('Doubleclick to edit'), - value: data[id] || '' - }) - .bindEvent({ - submit: function(data) { - editMetadata(id, data.value); - } + editable: false, + value: data[id] }) .appendTo($data); - - }); - - } + } + }); $('
').css({height: '16px'}).appendTo($data); diff --git a/static/js/listDialog.js b/static/js/listDialog.js index ea3ddd3..639c794 100644 --- a/static/js/listDialog.js +++ b/static/js/listDialog.js @@ -25,7 +25,7 @@ oml.ui.listDialog = function() { .bindEvent({ click: function() { that.close(); - oml.deleteList(); + oml.ui.deleteListDialog().open(); } }) ] : []).concat([ @@ -42,7 +42,7 @@ oml.ui.listDialog = function() { ]), closeButton: true, content: Ox.LoadingScreen().start(), - height: 256, + height: 264, title: Ox._('List – {0}', [ ui._list == '' ? Ox._('All Libraries') : ui._list @@ -91,8 +91,15 @@ oml.ui.listDialog = function() { }); } }) + .appendTo($content), + $findForm; + Ox.print('DEBUG:', list, listData) + if (listData.type == 'smart') { + $findForm = oml.ui.findForm(listData) + .css({marginTop: '8px'}) .appendTo($content); - that.options({content: $content}) + } + that.options({content: $content}); }); return that; diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index 7340b18..91bde1d 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -228,7 +228,7 @@ oml.ui.mainMenu = function() { title: Ox._('Find'), items: [ { - id: 'find', + id: 'finditems', title: Ox._('Find'), items: [ { @@ -413,6 +413,9 @@ oml.ui.mainMenu = function() { Ox.print('MAIN MENU DOES NOT YET HANDLE', id); } }, + key_backtick: function() { + changeFocus(1); + }, key_control_comma: function() { if (!oml.hasDialogOrScreen()) { oml.UI.set({page: 'preferences'}); @@ -429,6 +432,35 @@ oml.ui.mainMenu = function() { } } }, + key_control_m: function() { + if (!oml.hasDialogOrScreen() && !that.isSelected()) { + that.options('menus')[0].element.trigger('click'); + } + }, + key_control_shift_w: function() { + if (!oml.hasDialogOrScreen()) { + oml.UI.set({ + find: oml.config.user.ui.find, + item: '' + }); + } + }, + key_control_shift_z: function() { + oml.redoHistory(); + }, + key_control_slash: function() { + if (!oml.hasDialogOrScreen()) { + oml.UI.set({page: 'help'}); + } + }, + key_control_w: function() { + if (!oml.hasDialogOrScreen()) { + oml.UI.set({item: ''}); + } + }, + key_control_z: function() { + oml.undoHistory(); + }, key_shift_b: function() { ui.item && oml.UI.set({showBrowser: !ui.showBrowser}); }, diff --git a/static/js/oml.js b/static/js/oml.js index 09e3f94..d5ca508 100644 --- a/static/js/oml.js +++ b/static/js/oml.js @@ -73,6 +73,7 @@ ? Ox['format' + Ox.toTitleCase(key.format.type)].apply( this, [value].concat(key.format.args || []) ) + : Ox.isArray(key.type) ? value.join(', ') : value; } }); diff --git a/static/js/preferencesDialog.js b/static/js/preferencesDialog.js index 0205bf0..a4227dd 100644 --- a/static/js/preferencesDialog.js +++ b/static/js/preferencesDialog.js @@ -107,7 +107,7 @@ oml.ui.preferencesDialog = function() { }, { id: 'resetUI', - title: 'Reset UI Settings', + title: 'Reset UI Settings...', click: function() { oml.$ui.resetUIDialog = oml.ui.resetUIDialog().open(); }, diff --git a/static/js/resetUIDialog.js b/static/js/resetUIDialog.js index 10171fd..94e9a14 100644 --- a/static/js/resetUIDialog.js +++ b/static/js/resetUIDialog.js @@ -2,32 +2,21 @@ oml.ui.resetUIDialog = function(data) { - var that = oml.ui.iconDialog({ + var that = oml.ui.confirmDialog({ buttons: [ Ox.Button({ - id: 'cancel', - title: Ox._('Don\'t Reset') - }) - .bindEvent({ - click: function() { - that.close(); - } - }), + title: Ox._('No, Don\'t Reset') + }), Ox.Button({ - id: 'reset', - title: Ox._('Reset') - }).bindEvent({ - click: function() { - that.close(); - oml.$ui.preferencesDialog.close(); - oml.UI.set({page: ''}); - oml.UI.reset(); - } - }) + title: Ox._('Yes, Reset') + }) ], content: Ox._('Are you sure you want to reset all UI settings?'), - keys: {enter: 'reset', escape: 'cancel'}, title: Ox._('Reset UI Settings') + }, function() { + oml.$ui.preferencesDialog.close(); + oml.UI.set({page: ''}); + oml.UI.reset(); }); return that; diff --git a/static/js/utils.js b/static/js/utils.js index 1ef8980..9b6083a 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -19,9 +19,13 @@ oml.addList = function() { name: oml.validateName(Ox._('Untitled'), listNames), type: !isSmart ? 'static' : 'smart' }; - if (isFrom) { - if (!isSmart) { + if (!isSmart) { + if (isFrom) { data.items = ui.listSelection; + } + } else { + if (!isFrom) { + data.query = oml.config.user.ui.find; } else { data.query = ui.find; } @@ -532,6 +536,16 @@ oml.enableDragAndDrop = function($list, canMove) { }; +oml.getEditTooltip = function(title) { + return function(e) { + var $target = $(e.target); + return ( + $target.is('a') || $target.parents('a').length + ? Ox._('Shift+doubleclick to edit') : Ox._('Doubleclick to edit') + ) + (title ? ' ' + Ox._(title) : ''); + } +}; + (function() { // Note: getFindState has to run after getListState and getFilterState @@ -705,14 +719,9 @@ oml.getInfoHeight = function() { }; oml.getListData = function(list) { - var data = {}, ui = oml.user.ui; - if (Ox.isUndefined(list)) { - list = ui._list; - } - if (ui._lists) { - data = ui._lists[list]; - } - return data; + var ui = oml.user.ui; + list = Ox.isUndefined(list) ? ui._list : list; + return ui._lists ? Ox.getObjectById(ui._lists, list) : {}; }; oml.getListFoldersHeight = function() { @@ -769,25 +778,30 @@ oml.getUsersAndLists = function(callback) { return user.peered; }) ); - users.forEach(function(user) { - lists.push({ - id: (user.nickname == username ? '' : user.nickname) + ':', - name: Ox._('Library'), - type: 'library', - user: user.nickname - }); - }); oml.api.getLists(function(result) { - lists = lists.concat(result.data.lists); + users.forEach(function(user) { + lists = lists.concat([{ + id: (user.nickname == username ? '' : user.nickname) + ':', + name: Ox._('Library'), + type: 'library', + user: user.nickname + }].concat( + result.data.lists.filter(function(list) { + return list.user == user.nickname; + }) + )); + }); + lists = lists.map(function(list) { + return Ox.extend(list, { + editable: list.user == username && list.type == 'static', + title: (list.user ? list.user + ': ' : '') + list.name + }); + }) if (!ui.lists) { oml.$ui.mainMenu.update(); } - ui._lists = {}; - Ox.forEach(lists, function(list) { - ui._lists[list.id] = Ox.extend(list, { - editable: list.user == username && list.type == 'static', - }); - }); + ui._lists = lists; + Ox.print('UI._LISTS', JSON.stringify(ui._lists)); callback(users, lists); }); }) @@ -857,8 +871,8 @@ oml.updateFilterMenus = function() { oml.updateLists = function(callback) { // FIXME: can this go somewhere else? Ox.Request.clearCache('getLists'); - oml.api.getLists(function(result) { - var items = result.data.lists.filter(function(list) { + oml.getUsersAndLists(function(users, lists) { + var items = lists.filter(function(list) { return list.user == oml.user.preferences.username; }); oml.$ui.folderList[0].options({ diff --git a/static/json/js.json b/static/json/js.json index 27a9105..0d5dfa2 100644 --- a/static/json/js.json +++ b/static/json/js.json @@ -15,13 +15,16 @@ "filter.js", "filtersInnerPanel.js", "filtersOuterPanel.js", + "findDialog.js", "findElement.js", + "findForm.js", "folderList.js", "folderPlaceholder.js", "folders.js", "fullscreenButton.js", "gridView.js", "iconDialog.js", + "identifyDialog.js", "info.js", "infoView.js", "itemInnerPanel.js",