new documents section
This commit is contained in:
parent
3fcbd59525
commit
e1f35b1ec8
74 changed files with 6737 additions and 631 deletions
|
@ -94,10 +94,20 @@ def load_config(init=False):
|
|||
for key in config['itemKeys']:
|
||||
config['keys'][key['id']] = key
|
||||
|
||||
# add entities if needed
|
||||
if len(config.get('entities', [])) and not [k for k in config['documentKeys'] if k['id'] == 'entites']:
|
||||
config['documentKeys'].append({
|
||||
'id': 'entity',
|
||||
'title': 'Entity',
|
||||
'type': 'string',
|
||||
'find': True
|
||||
})
|
||||
|
||||
# add missing defaults
|
||||
for section in sorted((
|
||||
'capabilities', 'cantPlay', 'entities', 'itemName', 'itemTitleKeys', 'media', 'posters',
|
||||
'capabilities', 'cantPlay',
|
||||
'documentKeys',
|
||||
'entities', 'itemName', 'itemTitleKeys', 'itemKeys', 'media', 'posters',
|
||||
'site', 'tv', 'user.ui', 'user.ui.part', 'user.ui.showFolder',
|
||||
'menuExtras', 'languages'
|
||||
)):
|
||||
|
|
|
@ -35,11 +35,13 @@
|
|||
*/
|
||||
"capabilities": {
|
||||
"canAddItems": {"staff": true, "admin": true},
|
||||
"canAddDocuments": {"staff": true, "admin": true},
|
||||
"canDownloadVideo": {"guest": -1, "member": -1, "friend": -1, "staff": -1, "admin": -1},
|
||||
"canEditAnnotations": {"staff": true, "admin": true},
|
||||
"canEditEntities": {"staff": true, "admin": true},
|
||||
"canEditDocuments": {"staff": true, "admin": true},
|
||||
"canEditEvents": {"staff": true, "admin": true},
|
||||
"canEditFeaturedCollections": {"staff": true, "admin": true},
|
||||
"canEditFeaturedEdits": {"staff": true, "admin": true},
|
||||
"canEditFeaturedLists": {"staff": true, "admin": true},
|
||||
"canEditFeaturedTexts": {"staff": true, "admin": true},
|
||||
|
@ -61,11 +63,13 @@
|
|||
"canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4},
|
||||
"canReadText": {"guest": 0, "member": 0, "friend": 1, "staff": 1, "admin": 1},
|
||||
"canRemoveItems": {"admin": true},
|
||||
"canRemoveDocuments": {"staff": true, "admin": true},
|
||||
"canSeeAccessed": {"staff": true, "admin": true},
|
||||
"canSeeAllTasks": {"staff": true, "admin": true},
|
||||
"canSeeDebugMenu": {"staff": true, "admin": true},
|
||||
"canSeeExtraItemViews": {"staff": true, "admin": true},
|
||||
"canSeeMedia": {"staff": true, "admin": true},
|
||||
"canSeeDocument": {"guest": 1, "member": 1, "firend": 4, "staff": 4, "admin": 4},
|
||||
"canSeeItem": {"guest": 3, "member": 3, "friend": 4, "staff": 4, "admin": 4},
|
||||
"canSeeSize": {"friend": true, "staff": true, "admin": true},
|
||||
"canSeeSoftwareVersion": {"staff": true, "admin": true},
|
||||
|
@ -91,6 +95,212 @@
|
|||
list means it will not be included in find annotations.
|
||||
*/
|
||||
"clipLayers": ["subtitles"],
|
||||
"documentKeys": [
|
||||
{
|
||||
"id": "*",
|
||||
"title": "All",
|
||||
"type": "text",
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"operator": "+",
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "title",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "type",
|
||||
"operator": "+",
|
||||
"title": "Type",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "author",
|
||||
"operator": "+",
|
||||
"title": "Author",
|
||||
"type": ["string"],
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "person",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "publisher",
|
||||
"operator": "+",
|
||||
"title": "Publisher",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "place",
|
||||
"title": "Place",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "date",
|
||||
"title": "Date",
|
||||
"type": "string",
|
||||
"columnWidth": 120,
|
||||
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "series",
|
||||
"title": "Series",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "edition",
|
||||
"title": "Edition",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "language",
|
||||
"title": "Language",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "id",
|
||||
"operator": "+",
|
||||
"title": "ID",
|
||||
"type": "string",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "extension",
|
||||
"operator": "+",
|
||||
"title": "Extension",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "dimensions",
|
||||
"operator": "-",
|
||||
"title": "Dimensions",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "size",
|
||||
"operator": "-",
|
||||
"title": "Size",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"format": {"type": "value", "args": ["B"]},
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "description",
|
||||
"operator": "+",
|
||||
"title": "Description",
|
||||
"type": "text",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "matches",
|
||||
"operator": "-",
|
||||
"title": "Matches",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "user",
|
||||
"operator": "+",
|
||||
"title": "User",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "created",
|
||||
"operator": "-",
|
||||
"title": "Created",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "modified",
|
||||
"operator": "-",
|
||||
"title": "Modified",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "accessed",
|
||||
"title": "Last Accessed",
|
||||
"type": "date",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 150,
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "timesaccessed",
|
||||
"title": "Times Accessed",
|
||||
"type": "integer",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 60,
|
||||
"format": {"type": "number", "args": []},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "rightslevel",
|
||||
"title": "Rights Level",
|
||||
"type": "enum",
|
||||
"columnWidth": 90,
|
||||
"format": {"type": "ColorLevel", "args": [
|
||||
["Public", "Out of Copyright", "Under Copyright", "Private"]
|
||||
]},
|
||||
"sort": true,
|
||||
"sortOperator": "+",
|
||||
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
|
||||
}
|
||||
],
|
||||
/*
|
||||
"entities" can be used to store arbitrary data. They can be referenced in
|
||||
annotations, info view, or elsewhere. Each entry defines a specific class
|
||||
|
@ -976,13 +1186,24 @@
|
|||
"calendarFind": "",
|
||||
"calendarSelection": "",
|
||||
"clipColumns": 2,
|
||||
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
|
||||
"collectionColumnWidth": {},
|
||||
"collectionSelection": [],
|
||||
"collectionSort": [
|
||||
{"key": "title", "operator": "+"},
|
||||
{"key": "extension", "operator": "+"}
|
||||
],
|
||||
"collectionView": "grid",
|
||||
"collections": {},
|
||||
"columns": {
|
||||
"Colors": {
|
||||
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
|
||||
"columnWidth": {}
|
||||
}
|
||||
},
|
||||
"document": "",
|
||||
"documents": {},
|
||||
"documentView": "view",
|
||||
"documentSize": 256,
|
||||
"documentsSelection": {},
|
||||
"documentsSort": [{"key": "name", "operator": "+"}],
|
||||
|
@ -1011,6 +1232,7 @@
|
|||
],
|
||||
"filtersSize": 176,
|
||||
"find": {"conditions": [], "operator": "&"},
|
||||
"findDocuments": {"conditions": [], "operator": "&"},
|
||||
"followPlayer": true,
|
||||
"help": "",
|
||||
"icons": "posters",
|
||||
|
@ -1076,7 +1298,7 @@
|
|||
"featured": true,
|
||||
"volumes": true
|
||||
},
|
||||
"texts": {
|
||||
"documents": {
|
||||
"personal": true,
|
||||
"favorite": true,
|
||||
"featured": true
|
||||
|
|
|
@ -36,11 +36,13 @@
|
|||
*/
|
||||
"capabilities": {
|
||||
"canAddItems": {"researcher": true, "staff": true, "admin": true},
|
||||
"canAddDocuments": {"researcher": true, "staff": true, "admin": true},
|
||||
"canDownloadVideo": {"guest": -1, "member": -1, "researcher": 3, "staff": 3, "admin": 3},
|
||||
"canEditAnnotations": {"staff": true, "admin": true},
|
||||
"canEditEntities": {"staff": true, "admin": true},
|
||||
"canEditDocuments": {"researcher": true, "staff": true, "admin": true},
|
||||
"canEditEntities": {"staff": true, "admin": true},
|
||||
"canEditEvents": {"researcher": true, "staff": true, "admin": true},
|
||||
"canEditFeaturedCollections": {"staff": true, "admin": true},
|
||||
"canEditFeaturedEdits": {"staff": true, "admin": true},
|
||||
"canEditFeaturedLists": {"staff": true, "admin": true},
|
||||
"canEditFeaturedTexts": {"staff": true, "admin": true},
|
||||
|
@ -63,11 +65,13 @@
|
|||
"canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3},
|
||||
"canReadText": {"guest": 0, "member": 0, "researcher": 1, "staff": 1, "admin": 1},
|
||||
"canRemoveItems": {"staff": true, "admin": true},
|
||||
"canRemoveDocuments": {"staff": true, "admin": true},
|
||||
"canSeeAccessed": {"researcher": true, "staff": true, "admin": true},
|
||||
"canSeeAllTasks": {"staff": true, "admin": true},
|
||||
"canSeeDebugMenu": {"researcher": true, "staff": true, "admin": true},
|
||||
"canSeeExtraItemViews": {"researcher": true, "staff": true, "admin": true},
|
||||
"canSeeMedia": {"researcher": true, "staff": true, "admin": true},
|
||||
"canSeeDocument": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
|
||||
"canSeeItem": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
|
||||
"canSeeSize": {"researcher": true, "staff": true, "admin": true},
|
||||
"canSeeSoftwareVersion": {"researcher": true, "staff": true, "admin": true},
|
||||
|
@ -94,6 +98,257 @@
|
|||
*/
|
||||
"clipLayers": ["subtitles", "keywords", "notes"],
|
||||
/*
|
||||
"documentKeys" defines the metadata associated with each document. Required keys
|
||||
are "*", "id" and "title".
|
||||
A documentKey must have the following properties:
|
||||
"id": The unique id of the key (as used by the server)
|
||||
"title": The title of the key (as displayed by the client)
|
||||
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
|
||||
"layer", "string", "text", "time" or ["..."] (list of values of
|
||||
this type). If type is "layer", this is a reference to the
|
||||
annotations layer with the same id.
|
||||
and can have any of the following properties:
|
||||
"additionalSort": Ordered list of {key, operator} objects, where key is
|
||||
another itemKey and operator is "+" or "-". This can be used to
|
||||
override user.ui.listSort when results are sorted by this key.
|
||||
"autocomplete": If true, the find element will provide autocomplete
|
||||
"autocompleteSort": Sort order of autocomplete suggestions
|
||||
"capability": A capability required to see data for this key
|
||||
"columnRequired": If true, the column can't be removed from list view
|
||||
"columnWidth": Default column width in px. If absent, no column for
|
||||
this key can be added in list view.
|
||||
"filter": If true, one can filter results by this key
|
||||
"find": If true, this key will appear as an option in the find element
|
||||
"flag": Can be "country" or "language". If set (and filter is true), a
|
||||
flag icon corresponding to the field's value will be displayed.
|
||||
"format": {type: string, args: [value, value, ...]}, used for special
|
||||
formatting. This will invoke Ox.formatType(args). For details, see
|
||||
https://oxjs.org/#doc/Ox.formatArea etc.
|
||||
"secondaryId": If true, loading the URL "/value" will redirect to the
|
||||
corresponding item, in case there is an exact match for this key
|
||||
"sort": If true, one can sort results by this key
|
||||
"sortOperator": Sort order ("+" or "-"), in case it differs from the
|
||||
default for the key's type ("+" for strings, "-" for numbers)
|
||||
"sortType": Special sort type ("person" or "title") which can be
|
||||
further configured in "Manage Names" or "Manage Titles"
|
||||
"value": {key: string, type: string} or {layer: string, type: string},
|
||||
for keys whose value is derived from other keys or layers (like
|
||||
"number of actors" or "words per minute"). Possible values for type
|
||||
are "length", "lengthperminute", "words", and "wordsperminute".
|
||||
Alternatively, "value" can be set to the string "capability", which
|
||||
results in an itemKey whose boolean value indicates the presence or
|
||||
absence of a userLevel-dependent capability. This can be used to
|
||||
create queries and lists like "all items this user can play" etc.
|
||||
"values": [value, value, ...] Ordered list of values, in case "type" is
|
||||
"enum"
|
||||
*/
|
||||
"documentKeys": [
|
||||
{
|
||||
"id": "*",
|
||||
"title": "All",
|
||||
"type": "text",
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"operator": "+",
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "title",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "type",
|
||||
"operator": "+",
|
||||
"title": "Type",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "author",
|
||||
"operator": "+",
|
||||
"title": "Author",
|
||||
"type": ["string"],
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "person",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "publisher",
|
||||
"operator": "+",
|
||||
"title": "Publisher",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "place",
|
||||
"title": "Place",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "date",
|
||||
"title": "Date",
|
||||
"type": "string",
|
||||
"columnWidth": 120,
|
||||
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "series",
|
||||
"title": "Series",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "edition",
|
||||
"title": "Edition",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "language",
|
||||
"title": "Language",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "id",
|
||||
"operator": "+",
|
||||
"title": "ID",
|
||||
"type": "string",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "extension",
|
||||
"operator": "+",
|
||||
"title": "Extension",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "dimensions",
|
||||
"operator": "-",
|
||||
"title": "Dimensions",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "size",
|
||||
"operator": "-",
|
||||
"title": "Size",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"format": {"type": "value", "args": ["B"]},
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "description",
|
||||
"operator": "+",
|
||||
"title": "Description",
|
||||
"type": "text",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "matches",
|
||||
"operator": "-",
|
||||
"title": "Matches",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "user",
|
||||
"operator": "+",
|
||||
"title": "User",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "created",
|
||||
"operator": "-",
|
||||
"title": "Created",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "modified",
|
||||
"operator": "-",
|
||||
"title": "Modified",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "accessed",
|
||||
"title": "Last Accessed",
|
||||
"type": "date",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 150,
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "timesaccessed",
|
||||
"title": "Times Accessed",
|
||||
"type": "integer",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 60,
|
||||
"format": {"type": "number", "args": []},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "rightslevel",
|
||||
"title": "Rights Level",
|
||||
"type": "enum",
|
||||
"columnWidth": 90,
|
||||
"format": {"type": "ColorLevel", "args": [
|
||||
["Public", "Out of Copyright", "Under Copyright", "Private"]
|
||||
]},
|
||||
"sort": true,
|
||||
"sortOperator": "+",
|
||||
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
|
||||
}
|
||||
],
|
||||
/*
|
||||
"entities" can be used to store arbitrary data. They can be referenced in
|
||||
annotations, info view, or elsewhere. Each entry defines a specific class
|
||||
of entity object, its properties and their types (for example an "actor"
|
||||
|
@ -1004,16 +1259,26 @@
|
|||
"calendarFind": "",
|
||||
"calendarSelection": "",
|
||||
"clipColumns": 2,
|
||||
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
|
||||
"collectionColumnWidth": {},
|
||||
"collectionSelection": [],
|
||||
"collectionSort": [
|
||||
{"key": "title", "operator": "+"},
|
||||
{"key": "extension", "operator": "+"}
|
||||
],
|
||||
"collectionView": "grid",
|
||||
"collections": {},
|
||||
"columns": {
|
||||
"Colors": {
|
||||
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
|
||||
"columnWidth": {}
|
||||
}
|
||||
},
|
||||
"documentView": "view",
|
||||
"documents": {},
|
||||
"documentSize": 256,
|
||||
"documentsSelection": {},
|
||||
"documentsSort": [{"key": "name", "operator": "+"}],
|
||||
"documentsSort": [{"key": "title", "operator": "+"}],
|
||||
"documentsView": "grid",
|
||||
"edit": "",
|
||||
"edits": {},
|
||||
|
@ -1039,6 +1304,7 @@
|
|||
],
|
||||
"filtersSize": 176,
|
||||
"find": {"conditions": [], "operator": "&"},
|
||||
"findDocuments": {"conditions": [], "operator": "&"},
|
||||
"followPlayer": true,
|
||||
"help": "",
|
||||
"icons": "posters",
|
||||
|
@ -1063,7 +1329,6 @@
|
|||
"page": "",
|
||||
"part": {
|
||||
"api": "",
|
||||
"documents": "",
|
||||
"entities": "",
|
||||
"faq": "",
|
||||
"help": "",
|
||||
|
@ -1104,6 +1369,11 @@
|
|||
"featured": true,
|
||||
"volumes": true
|
||||
},
|
||||
"documents": {
|
||||
"personal": true,
|
||||
"favorite": true,
|
||||
"featured": true
|
||||
},
|
||||
"texts": {
|
||||
"personal": true,
|
||||
"favorite": true,
|
||||
|
@ -1117,6 +1387,8 @@
|
|||
"sidebarSize": 256,
|
||||
"text": "",
|
||||
"texts": {},
|
||||
"documents": {},
|
||||
"document": "",
|
||||
"theme": "oxmedium",
|
||||
"updateAdvancedFindResults": false,
|
||||
"videoLoop": false,
|
||||
|
|
|
@ -35,11 +35,13 @@
|
|||
*/
|
||||
"capabilities": {
|
||||
"canAddItems": {"member": true, "staff": true, "admin": true},
|
||||
"canAddDocuments": {"member": true, "staff": true, "admin": true},
|
||||
"canDownloadVideo": {"guest": 0, "member": 0, "staff": 4, "admin": 4},
|
||||
"canEditAnnotations": {"staff": true, "admin": true},
|
||||
"canEditEntities": {"staff": true, "admin": true},
|
||||
"canEditDocuments": {"staff": true, "admin": true},
|
||||
"canEditEvents": {"staff": true, "admin": true},
|
||||
"canEditFeaturedCollections": {"staff": true, "admin": true},
|
||||
"canEditFeaturedEdits": {"staff": true, "admin": true},
|
||||
"canEditFeaturedLists": {"staff": true, "admin": true},
|
||||
"canEditFeaturedTexts": {"staff": true, "admin": true},
|
||||
|
@ -61,11 +63,13 @@
|
|||
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1},
|
||||
"canRemoveItems": {"admin": true},
|
||||
"canRemoveDocuments": {"staff": true, "admin": true},
|
||||
"canSeeAccessed": {"staff": true, "admin": true},
|
||||
"canSeeAllTasks": {"staff": true, "admin": true},
|
||||
"canSeeDebugMenu": {"staff": true, "admin": true},
|
||||
"canSeeExtraItemViews": {"staff": true, "admin": true},
|
||||
"canSeeMedia": {"staff": true, "admin": true},
|
||||
"canSeeDocument": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canSeeSize": {"staff": true, "admin": true},
|
||||
"canSeeSoftwareVersion": {"staff": true, "admin": true},
|
||||
|
@ -92,6 +96,257 @@
|
|||
*/
|
||||
"clipLayers": ["transcripts", "keywords", "places", "events", "descriptions"],
|
||||
/*
|
||||
"documentKeys" defines the metadata associated with each document. Required keys
|
||||
are "*", "id" and "title".
|
||||
A documentKey must have the following properties:
|
||||
"id": The unique id of the key (as used by the server)
|
||||
"title": The title of the key (as displayed by the client)
|
||||
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
|
||||
"layer", "string", "text", "time" or ["..."] (list of values of
|
||||
this type). If type is "layer", this is a reference to the
|
||||
annotations layer with the same id.
|
||||
and can have any of the following properties:
|
||||
"additionalSort": Ordered list of {key, operator} objects, where key is
|
||||
another itemKey and operator is "+" or "-". This can be used to
|
||||
override user.ui.listSort when results are sorted by this key.
|
||||
"autocomplete": If true, the find element will provide autocomplete
|
||||
"autocompleteSort": Sort order of autocomplete suggestions
|
||||
"capability": A capability required to see data for this key
|
||||
"columnRequired": If true, the column can't be removed from list view
|
||||
"columnWidth": Default column width in px. If absent, no column for
|
||||
this key can be added in list view.
|
||||
"filter": If true, one can filter results by this key
|
||||
"find": If true, this key will appear as an option in the find element
|
||||
"flag": Can be "country" or "language". If set (and filter is true), a
|
||||
flag icon corresponding to the field's value will be displayed.
|
||||
"format": {type: string, args: [value, value, ...]}, used for special
|
||||
formatting. This will invoke Ox.formatType(args). For details, see
|
||||
https://oxjs.org/#doc/Ox.formatArea etc.
|
||||
"secondaryId": If true, loading the URL "/value" will redirect to the
|
||||
corresponding item, in case there is an exact match for this key
|
||||
"sort": If true, one can sort results by this key
|
||||
"sortOperator": Sort order ("+" or "-"), in case it differs from the
|
||||
default for the key's type ("+" for strings, "-" for numbers)
|
||||
"sortType": Special sort type ("person" or "title") which can be
|
||||
further configured in "Manage Names" or "Manage Titles"
|
||||
"value": {key: string, type: string} or {layer: string, type: string},
|
||||
for keys whose value is derived from other keys or layers (like
|
||||
"number of actors" or "words per minute"). Possible values for type
|
||||
are "length", "lengthperminute", "words", and "wordsperminute".
|
||||
Alternatively, "value" can be set to the string "capability", which
|
||||
results in an itemKey whose boolean value indicates the presence or
|
||||
absence of a userLevel-dependent capability. This can be used to
|
||||
create queries and lists like "all items this user can play" etc.
|
||||
"values": [value, value, ...] Ordered list of values, in case "type" is
|
||||
"enum"
|
||||
*/
|
||||
"documentKeys": [
|
||||
{
|
||||
"id": "*",
|
||||
"title": "All",
|
||||
"type": "text",
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"operator": "+",
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "title",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "type",
|
||||
"operator": "+",
|
||||
"title": "Type",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "author",
|
||||
"operator": "+",
|
||||
"title": "Author",
|
||||
"type": ["string"],
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "person",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "publisher",
|
||||
"operator": "+",
|
||||
"title": "Publisher",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "place",
|
||||
"title": "Place",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "date",
|
||||
"title": "Date",
|
||||
"type": "string",
|
||||
"columnWidth": 120,
|
||||
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "series",
|
||||
"title": "Series",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "edition",
|
||||
"title": "Edition",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "language",
|
||||
"title": "Language",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "id",
|
||||
"operator": "+",
|
||||
"title": "ID",
|
||||
"type": "string",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "extension",
|
||||
"operator": "+",
|
||||
"title": "Extension",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "dimensions",
|
||||
"operator": "-",
|
||||
"title": "Dimensions",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "size",
|
||||
"operator": "-",
|
||||
"title": "Size",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"format": {"type": "value", "args": ["B"]},
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "description",
|
||||
"operator": "+",
|
||||
"title": "Description",
|
||||
"type": "text",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "matches",
|
||||
"operator": "-",
|
||||
"title": "Matches",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "user",
|
||||
"operator": "+",
|
||||
"title": "User",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "created",
|
||||
"operator": "-",
|
||||
"title": "Created",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "modified",
|
||||
"operator": "-",
|
||||
"title": "Modified",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "accessed",
|
||||
"title": "Last Accessed",
|
||||
"type": "date",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 150,
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "timesaccessed",
|
||||
"title": "Times Accessed",
|
||||
"type": "integer",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 60,
|
||||
"format": {"type": "number", "args": []},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "rightslevel",
|
||||
"title": "Rights Level",
|
||||
"type": "enum",
|
||||
"columnWidth": 90,
|
||||
"format": {"type": "ColorLevel", "args": [
|
||||
["Public", "Out of Copyright", "Under Copyright", "Private"]
|
||||
]},
|
||||
"sort": true,
|
||||
"sortOperator": "+",
|
||||
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
|
||||
}
|
||||
],
|
||||
/*
|
||||
"entities" can be used to store arbitrary data. They can be referenced in
|
||||
annotations, info view, or elsewhere. Each entry defines a specific class
|
||||
of entity object, its properties and their types (for example an "actor"
|
||||
|
@ -882,16 +1137,27 @@
|
|||
"calendarFind": "",
|
||||
"calendarSelection": "",
|
||||
"clipColumns": 2,
|
||||
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
|
||||
"collectionColumnWidth": {},
|
||||
"collectionSelection": [],
|
||||
"collectionSort": [
|
||||
{"key": "title", "operator": "+"},
|
||||
{"key": "extension", "operator": "+"}
|
||||
],
|
||||
"collectionView": "grid",
|
||||
"collections": {},
|
||||
"columns": {
|
||||
"Colors": {
|
||||
"columns": ["title", "source", "project", "language", "hue", "saturation", "brightness"],
|
||||
"columns": ["title", "director", "language", "hue", "saturation", "brightness"],
|
||||
"columnWidth": {}
|
||||
}
|
||||
},
|
||||
"document": "",
|
||||
"documents": {},
|
||||
"documentSize": 256,
|
||||
"documentView": "view",
|
||||
"documentsSelection": {},
|
||||
"documentsSort": [{"key": "name", "operator": "+"}],
|
||||
"documentsSort": [{"key": "title", "operator": "+"}],
|
||||
"documentsView": "grid",
|
||||
"edit": "",
|
||||
"edits": {},
|
||||
|
@ -917,6 +1183,7 @@
|
|||
],
|
||||
"filtersSize": 176,
|
||||
"find": {"conditions": [], "operator": "&"},
|
||||
"findDocuments": {"conditions": [], "operator": "&"},
|
||||
"followPlayer": true,
|
||||
"help": "",
|
||||
"icons": "frames",
|
||||
|
@ -937,7 +1204,6 @@
|
|||
"page": "",
|
||||
"part": {
|
||||
"api": "",
|
||||
"documents": "",
|
||||
"entities": "",
|
||||
"faq": "",
|
||||
"help": "",
|
||||
|
@ -981,7 +1247,7 @@
|
|||
"featured": true,
|
||||
"volumes": true
|
||||
},
|
||||
"texts": {
|
||||
"documents": {
|
||||
"personal": true,
|
||||
"favorite": true,
|
||||
"featured": true
|
||||
|
|
|
@ -39,11 +39,13 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
*/
|
||||
"capabilities": {
|
||||
"canAddItems": {"member": true, "staff": true, "admin": true},
|
||||
"canAddDocuments": {"member": true, "staff": true, "admin": true},
|
||||
"canDownloadVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canEditAnnotations": {"staff": true, "admin": true},
|
||||
"canEditDocuments": {"staff": true, "admin": true},
|
||||
"canEditEntities": {"staff": true, "admin": true},
|
||||
"canEditEvents": {"staff": true, "admin": true},
|
||||
"canEditFeaturedCollections": {"staff": true, "admin": true},
|
||||
"canEditFeaturedEdits": {"staff": true, "admin": true},
|
||||
"canEditFeaturedLists": {"staff": true, "admin": true},
|
||||
"canEditFeaturedTexts": {"staff": true, "admin": true},
|
||||
|
@ -64,12 +66,14 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1},
|
||||
"canRemoveItems": {"admin": true},
|
||||
"canRemoveItems": {"staff": true, "admin": true},
|
||||
"canRemoveDocuments": {"staff": true, "admin": true},
|
||||
"canSeeAccessed": {"staff": true, "admin": true},
|
||||
"canSeeAllTasks": {"staff": true, "admin": true},
|
||||
"canSeeDebugMenu": {"staff": true, "admin": true},
|
||||
"canSeeExtraItemViews": {"staff": true, "admin": true},
|
||||
"canSeeMedia": {"staff": true, "admin": true},
|
||||
"canSeeDocument": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
|
||||
"canSeeSize": {"staff": true, "admin": true},
|
||||
"canSeeSoftwareVersion": {"staff": true, "admin": true},
|
||||
|
@ -96,6 +100,257 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
*/
|
||||
"clipLayers": ["publicnotes", "keywords", "subtitles"],
|
||||
/*
|
||||
"documentKeys" defines the metadata associated with each document. Required keys
|
||||
are "*", "id" and "title".
|
||||
A documentKey must have the following properties:
|
||||
"id": The unique id of the key (as used by the server)
|
||||
"title": The title of the key (as displayed by the client)
|
||||
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
|
||||
"layer", "string", "text", "time" or ["..."] (list of values of
|
||||
this type). If type is "layer", this is a reference to the
|
||||
annotations layer with the same id.
|
||||
and can have any of the following properties:
|
||||
"additionalSort": Ordered list of {key, operator} objects, where key is
|
||||
another itemKey and operator is "+" or "-". This can be used to
|
||||
override user.ui.listSort when results are sorted by this key.
|
||||
"autocomplete": If true, the find element will provide autocomplete
|
||||
"autocompleteSort": Sort order of autocomplete suggestions
|
||||
"capability": A capability required to see data for this key
|
||||
"columnRequired": If true, the column can't be removed from list view
|
||||
"columnWidth": Default column width in px. If absent, no column for
|
||||
this key can be added in list view.
|
||||
"filter": If true, one can filter results by this key
|
||||
"find": If true, this key will appear as an option in the find element
|
||||
"flag": Can be "country" or "language". If set (and filter is true), a
|
||||
flag icon corresponding to the field's value will be displayed.
|
||||
"format": {type: string, args: [value, value, ...]}, used for special
|
||||
formatting. This will invoke Ox.formatType(args). For details, see
|
||||
https://oxjs.org/#doc/Ox.formatArea etc.
|
||||
"secondaryId": If true, loading the URL "/value" will redirect to the
|
||||
corresponding item, in case there is an exact match for this key
|
||||
"sort": If true, one can sort results by this key
|
||||
"sortOperator": Sort order ("+" or "-"), in case it differs from the
|
||||
default for the key's type ("+" for strings, "-" for numbers)
|
||||
"sortType": Special sort type ("person" or "title") which can be
|
||||
further configured in "Manage Names" or "Manage Titles"
|
||||
"value": {key: string, type: string} or {layer: string, type: string},
|
||||
for keys whose value is derived from other keys or layers (like
|
||||
"number of actors" or "words per minute"). Possible values for type
|
||||
are "length", "lengthperminute", "words", and "wordsperminute".
|
||||
Alternatively, "value" can be set to the string "capability", which
|
||||
results in an itemKey whose boolean value indicates the presence or
|
||||
absence of a userLevel-dependent capability. This can be used to
|
||||
create queries and lists like "all items this user can play" etc.
|
||||
"values": [value, value, ...] Ordered list of values, in case "type" is
|
||||
"enum"
|
||||
*/
|
||||
"documentKeys": [
|
||||
{
|
||||
"id": "*",
|
||||
"title": "All",
|
||||
"type": "text",
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"operator": "+",
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "title",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "type",
|
||||
"operator": "+",
|
||||
"title": "Type",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "author",
|
||||
"operator": "+",
|
||||
"title": "Author",
|
||||
"type": ["string"],
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"sortType": "person",
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "publisher",
|
||||
"operator": "+",
|
||||
"title": "Publisher",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "place",
|
||||
"title": "Place",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "date",
|
||||
"title": "Date",
|
||||
"type": "string",
|
||||
"columnWidth": 120,
|
||||
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "series",
|
||||
"title": "Series",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "edition",
|
||||
"title": "Edition",
|
||||
"type": "string",
|
||||
"columnWidth": 128,
|
||||
"find": true
|
||||
},
|
||||
{
|
||||
"id": "language",
|
||||
"title": "Language",
|
||||
"type": ["string"],
|
||||
"columnWidth": 128,
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "id",
|
||||
"operator": "+",
|
||||
"title": "ID",
|
||||
"type": "string",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "extension",
|
||||
"operator": "+",
|
||||
"title": "Extension",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "dimensions",
|
||||
"operator": "-",
|
||||
"title": "Dimensions",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "size",
|
||||
"operator": "-",
|
||||
"title": "Size",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"format": {"type": "value", "args": ["B"]},
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "description",
|
||||
"operator": "+",
|
||||
"title": "Description",
|
||||
"type": "text",
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"columnWidth": 256
|
||||
},
|
||||
{
|
||||
"id": "matches",
|
||||
"operator": "-",
|
||||
"title": "Matches",
|
||||
"type": "integer",
|
||||
"sort": true,
|
||||
"columnWidth": 64
|
||||
},
|
||||
{
|
||||
"id": "user",
|
||||
"operator": "+",
|
||||
"title": "User",
|
||||
"type": "string",
|
||||
"filter": true,
|
||||
"find": true,
|
||||
"sort": true,
|
||||
"autocomplete": true,
|
||||
"columnWidth": 128
|
||||
},
|
||||
{
|
||||
"id": "created",
|
||||
"operator": "-",
|
||||
"title": "Created",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "modified",
|
||||
"operator": "-",
|
||||
"title": "Modified",
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"type": "date",
|
||||
"sort": true,
|
||||
"columnWidth": 144
|
||||
},
|
||||
{
|
||||
"id": "accessed",
|
||||
"title": "Last Accessed",
|
||||
"type": "date",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 150,
|
||||
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "timesaccessed",
|
||||
"title": "Times Accessed",
|
||||
"type": "integer",
|
||||
"capability": "canSeeAccessed",
|
||||
"columnWidth": 60,
|
||||
"format": {"type": "number", "args": []},
|
||||
"sort": true
|
||||
},
|
||||
{
|
||||
"id": "rightslevel",
|
||||
"title": "Rights Level",
|
||||
"type": "enum",
|
||||
"columnWidth": 90,
|
||||
"format": {"type": "ColorLevel", "args": [
|
||||
["Public", "Out of Copyright", "Under Copyright", "Private"]
|
||||
]},
|
||||
"sort": true,
|
||||
"sortOperator": "+",
|
||||
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
|
||||
}
|
||||
],
|
||||
/*
|
||||
"entities" can be used to store arbitrary data. They can be referenced in
|
||||
annotations, info view, or elsewhere. Each entry defines a specific class
|
||||
of entity object, its properties and their types (for example an "actor"
|
||||
|
@ -822,16 +1077,27 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
"calendarFind": "",
|
||||
"calendarSelection": "",
|
||||
"clipColumns": 2,
|
||||
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
|
||||
"collectionColumnWidth": {},
|
||||
"collectionSelection": [],
|
||||
"collectionSort": [
|
||||
{"key": "title", "operator": "+"},
|
||||
{"key": "extension", "operator": "+"}
|
||||
],
|
||||
"collectionView": "grid",
|
||||
"collections": {},
|
||||
"columns": {
|
||||
"Colors": {
|
||||
"columns": ["title", "director", "language", "hue", "saturation", "brightness"],
|
||||
"columnWidth": {}
|
||||
}
|
||||
},
|
||||
"document": "",
|
||||
"documents": {},
|
||||
"documentSize": 256,
|
||||
"documentView": "view",
|
||||
"documentsSelection": {},
|
||||
"documentsSort": [{"key": "name", "operator": "+"}],
|
||||
"documentsSort": [{"key": "title", "operator": "+"}],
|
||||
"documentsView": "grid",
|
||||
"edit": "",
|
||||
"edits": {},
|
||||
|
@ -857,6 +1123,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
],
|
||||
"filtersSize": 176,
|
||||
"find": {"conditions": [], "operator": "&"},
|
||||
"findDocuments": {"conditions": [], "operator": "&"},
|
||||
"followPlayer": true,
|
||||
"help": "",
|
||||
"icons": "posters",
|
||||
|
@ -877,7 +1144,6 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
"page": "",
|
||||
"part": {
|
||||
"api": "",
|
||||
"documents": "",
|
||||
"entities": "",
|
||||
"faq": "",
|
||||
"help": "",
|
||||
|
@ -920,7 +1186,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
|||
"featured": true,
|
||||
"volumes": true
|
||||
},
|
||||
"texts": {
|
||||
"documents": {
|
||||
"personal": true,
|
||||
"favorite": true,
|
||||
"featured": true
|
||||
|
|
0
pandora/document/management/__init__.py
Normal file
0
pandora/document/management/__init__.py
Normal file
0
pandora/document/management/commands/__init__.py
Normal file
0
pandora/document/management/commands/__init__.py
Normal file
29
pandora/document/management/commands/rebuild_documentfind.py
Normal file
29
pandora/document/management/commands/rebuild_documentfind.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import print_function
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import fields
|
||||
from django.conf import settings
|
||||
|
||||
settings.RELOAD_CONFIG = False
|
||||
import app.monkey_patch
|
||||
from ... import models
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'update document find and sort values'
|
||||
args = ''
|
||||
|
||||
def handle(self, **options):
|
||||
ids = [i['id'] for i in models.Document.objects.all().values('id')]
|
||||
for id in ids:
|
||||
try:
|
||||
i = models.Document.objects.get(id=id)
|
||||
if i.file:
|
||||
i.get_info()
|
||||
i.get_ratio()
|
||||
#print(i, i.ratio)
|
||||
i.save()
|
||||
except:
|
||||
pass
|
|
@ -1,6 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
import unicodedata
|
||||
|
||||
from six import string_types
|
||||
from django.db.models import Q, Manager
|
||||
from django.conf import settings
|
||||
|
||||
import ox
|
||||
from oxdjango.query import QuerySet
|
||||
|
@ -8,14 +12,31 @@ from oxdjango.query import QuerySet
|
|||
import entity.managers
|
||||
from oxdjango.managers import get_operator
|
||||
|
||||
from documentcollection.models import Collection
|
||||
from item import utils
|
||||
|
||||
|
||||
keymap = {
|
||||
'user': 'user__username',
|
||||
'item': 'items__public_id',
|
||||
}
|
||||
default_key = 'name'
|
||||
default_key = 'title'
|
||||
|
||||
def parseCondition(condition, user, item=None):
|
||||
def get_key_type(k):
|
||||
key_type = (utils.get_by_id(settings.CONFIG['documentKeys'], k) or {'type': 'string'}).get('type')
|
||||
if isinstance(key_type, list):
|
||||
key_type = key_type[0]
|
||||
key_type = {
|
||||
'title': 'string',
|
||||
'person': 'string',
|
||||
'text': 'string',
|
||||
'year': 'string',
|
||||
'length': 'string',
|
||||
'layer': 'string',
|
||||
'list': 'list',
|
||||
}.get(key_type, key_type)
|
||||
return key_type
|
||||
|
||||
def parseCondition(condition, user, item=None, owner=None):
|
||||
'''
|
||||
'''
|
||||
k = condition.get('key', default_key)
|
||||
|
@ -33,17 +54,47 @@ def parseCondition(condition, user, item=None):
|
|||
op = '='
|
||||
|
||||
if op.startswith('!'):
|
||||
return ~buildCondition(k, op[1:], v)
|
||||
return buildCondition(k, op[1:], v, user, True, owner=owner)
|
||||
else:
|
||||
return buildCondition(k, op, v)
|
||||
return buildCondition(k, op, v, user, owner=owner)
|
||||
|
||||
|
||||
def buildCondition(k, op, v):
|
||||
def buildCondition(k, op, v, user, exclude=False, owner=None):
|
||||
import entity.models
|
||||
from . import models
|
||||
|
||||
# fixme: frontend should never call with list
|
||||
if k == 'list':
|
||||
print('fixme: frontend should never call with list', k, op, v)
|
||||
k = 'collection'
|
||||
|
||||
key_type = get_key_type(k)
|
||||
facet_keys = models.Document.facet_keys
|
||||
if k == 'id':
|
||||
v = ox.fromAZ(v)
|
||||
return Q(**{k: v})
|
||||
if isinstance(v, bool):
|
||||
q = Q(**{k: v})
|
||||
if exclude:
|
||||
q = ~Q(id__in=models.Document.objects.filter(q))
|
||||
return q
|
||||
elif k == 'groups':
|
||||
if op == '==' and v == '$my':
|
||||
if not owner:
|
||||
owner = user
|
||||
groups = owner.groups.all()
|
||||
else:
|
||||
key = 'name' + get_operator(op)
|
||||
groups = Group.objects.filter(**{key: v})
|
||||
if not groups.count():
|
||||
return Q(id=0)
|
||||
q = Q(groups__in=groups)
|
||||
if exclude:
|
||||
q = ~q
|
||||
return q
|
||||
elif k in ('oshash', 'items__public_id'):
|
||||
q = Q(**{k: v})
|
||||
if exclude:
|
||||
q = ~Q(id__in=models.Document.objects.filter(q))
|
||||
return q
|
||||
elif isinstance(v, bool):
|
||||
key = k
|
||||
elif k == 'entity':
|
||||
entity_key, entity_v = entity.managers.namePredicate(op, v)
|
||||
|
@ -51,13 +102,87 @@ def buildCondition(k, op, v):
|
|||
v = entity.models.DocumentProperties.objects.filter(**{
|
||||
'entity__' + entity_key: entity_v
|
||||
}).values_list('document_id', flat=True)
|
||||
elif k == 'collection':
|
||||
q = Q(id=0)
|
||||
l = v.split(":", 1)
|
||||
if len(l) >= 2:
|
||||
lqs = list(Collection.objects.filter(name=l[1], user__username=l[0]))
|
||||
if len(lqs) == 1 and lqs[0].accessible(user):
|
||||
l = lqs[0]
|
||||
if l.query.get('static', False) is False:
|
||||
data = l.query
|
||||
q = parseConditions(data.get('conditions', []),
|
||||
data.get('operator', '&'),
|
||||
user, owner=l.user)
|
||||
else:
|
||||
key = k + get_operator(op, 'istr')
|
||||
q = Q(id__in=l.documents.all())
|
||||
else:
|
||||
q = Q(id=0)
|
||||
return q
|
||||
elif key_type == 'boolean':
|
||||
q = Q(**{'find__key': k, 'find__value': v})
|
||||
if exclude:
|
||||
q = ~Q(id__in=models.Document.objects.filter(q))
|
||||
return q
|
||||
elif key_type == "string":
|
||||
in_find = True
|
||||
if in_find:
|
||||
value_key = 'find__value'
|
||||
else:
|
||||
value_key = k
|
||||
if isinstance(v, string_types):
|
||||
v = unicodedata.normalize('NFKD', v).lower()
|
||||
if k in facet_keys:
|
||||
in_find = False
|
||||
facet_value = 'facets__value' + get_operator(op, 'istr')
|
||||
v = models.Document.objects.filter(**{'facets__key': k, facet_value: v})
|
||||
value_key = 'id__in'
|
||||
else:
|
||||
value_key = value_key + get_operator(op)
|
||||
k = str(k)
|
||||
value_key = str(value_key)
|
||||
if k == '*':
|
||||
q = Q(**{value_key: v})
|
||||
elif in_find:
|
||||
q = Q(**{'find__key': k, value_key: v})
|
||||
else:
|
||||
q = Q(**{value_key: v})
|
||||
if exclude:
|
||||
q = ~Q(id__in=models.Document.objects.filter(q))
|
||||
return q
|
||||
elif key_type == 'date':
|
||||
def parse_date(d):
|
||||
while len(d) < 3:
|
||||
d.append(1)
|
||||
return datetime(*[int(i) for i in d])
|
||||
|
||||
#using sort here since find only contains strings
|
||||
v = parse_date(v.split('-'))
|
||||
vk = 'sort__%s%s' % (k, get_operator(op, 'int'))
|
||||
vk = str(vk)
|
||||
q = Q(**{vk: v})
|
||||
if exclude:
|
||||
q = ~q
|
||||
return q
|
||||
else: # integer, float, list, time
|
||||
#use sort table here
|
||||
if key_type == 'time':
|
||||
v = int(utils.parse_time(v))
|
||||
|
||||
vk = 'sort__%s%s' % (k, get_operator(op, 'int'))
|
||||
vk = str(vk)
|
||||
q = Q(**{vk: v})
|
||||
if exclude:
|
||||
q = ~q
|
||||
return q
|
||||
key = str(key)
|
||||
return Q(**{key: v})
|
||||
q = Q(**{key: v})
|
||||
if exclude:
|
||||
q = ~q
|
||||
return q
|
||||
|
||||
|
||||
def parseConditions(conditions, operator, user, item=None):
|
||||
def parseConditions(conditions, operator, user, item=None, owner=None):
|
||||
'''
|
||||
conditions: [
|
||||
{
|
||||
|
@ -80,12 +205,12 @@ def parseConditions(conditions, operator, user, item=None):
|
|||
for condition in conditions:
|
||||
if 'conditions' in condition:
|
||||
q = parseConditions(condition['conditions'],
|
||||
condition.get('operator', '&'), user, item)
|
||||
condition.get('operator', '&'), user, item, owner=owner)
|
||||
if q:
|
||||
conn.append(q)
|
||||
pass
|
||||
else:
|
||||
conn.append(parseCondition(condition, user, item))
|
||||
conn.append(parseCondition(condition, user, item, owner=owner))
|
||||
if conn:
|
||||
q = conn[0]
|
||||
for c in conn[1:]:
|
||||
|
@ -133,4 +258,21 @@ class DocumentManager(Manager):
|
|||
if conditions:
|
||||
qs = qs.filter(conditions)
|
||||
|
||||
#anonymous can only see public items
|
||||
if not user or user.is_anonymous():
|
||||
level = 'guest'
|
||||
allowed_level = settings.CONFIG['capabilities']['canSeeDocument'][level]
|
||||
qs = qs.filter(rightslevel__lte=allowed_level)
|
||||
rendered_q = Q(rendered=True)
|
||||
#users can see public items, there own items and items of there groups
|
||||
else:
|
||||
level = user.profile.get_level()
|
||||
allowed_level = settings.CONFIG['capabilities']['canSeeDocument'][level]
|
||||
q = Q(rightslevel__lte=allowed_level) | Q(user=user)
|
||||
rendered_q = Q(rendered=True) | Q(user=user)
|
||||
if user.groups.count():
|
||||
q |= Q(groups__in=user.groups.all())
|
||||
rendered_q |= Q(groups__in=user.groups.all())
|
||||
qs = qs.filter(q)
|
||||
|
||||
return qs
|
||||
|
|
26
pandora/document/migrations/0003_new_fields.py
Normal file
26
pandora/document/migrations/0003_new_fields.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-04 16:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import oxdjango.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('document', '0002_auto_20160219_1537'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='data',
|
||||
field=oxdjango.fields.DictField(default={}),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='rightslevel',
|
||||
field=models.IntegerField(db_index=True, default=0),
|
||||
),
|
||||
]
|
109
pandora/document/migrations/0004_migrate_text.py
Normal file
109
pandora/document/migrations/0004_migrate_text.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import ox
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Max
|
||||
from PIL import Image
|
||||
|
||||
import oxdjango.fields
|
||||
|
||||
|
||||
def migrate_texts(apps, schema_editor):
|
||||
import os
|
||||
import ox
|
||||
import shutil
|
||||
|
||||
import document.models
|
||||
from document import utils
|
||||
|
||||
Text = apps.get_model("text", "Text")
|
||||
Document = apps.get_model("document", "Document")
|
||||
Collection = apps.get_model("documentcollection", "Collection")
|
||||
CollectionDocument = apps.get_model("documentcollection", "CollectionDocument")
|
||||
User = apps.get_model("auth", "User")
|
||||
|
||||
def add(self, document):
|
||||
q = self.documents.filter(id=document.id)
|
||||
if q.count() == 0:
|
||||
l = CollectionDocument()
|
||||
l.collection = self
|
||||
l.document = document
|
||||
l.index = CollectionDocument.objects.filter(collection=self).aggregate(Max('index'))['index__max']
|
||||
if l.index is None:
|
||||
l.index = 0
|
||||
else:
|
||||
l.index += 1
|
||||
l.save()
|
||||
|
||||
def path(self, name=''):
|
||||
h = ox.toAZ(self.id)
|
||||
h = (7-len(h))*'0' + h
|
||||
return os.path.join('documents', h[:2], h[2:4], h[4:6], h[6:], name)
|
||||
|
||||
def update_info(self):
|
||||
pdf = self.file.path
|
||||
page = 1
|
||||
image = os.path.join(os.path.dirname(pdf), '1024p%d.jpg' % page)
|
||||
utils.extract_pdfpage(pdf, image, page)
|
||||
self.pages = utils.pdfpages(self.file.path)
|
||||
if os.path.exists(image):
|
||||
size = Image.open(image).size
|
||||
self.ratio = size[0] / size[1]
|
||||
|
||||
if Text.objects.filter(status='featured').count():
|
||||
first_user = User.objects.all()[0]
|
||||
featured, created = Collection.objects.get_or_create(user=first_user, name='Featured Texts')
|
||||
if created:
|
||||
featured.status = 'featured'
|
||||
featured.save()
|
||||
|
||||
for t in Text.objects.all():
|
||||
d = Document()
|
||||
d.extension = t.type
|
||||
if t.name == '':
|
||||
d.name = 'Index'
|
||||
else:
|
||||
d.name = t.name
|
||||
d.user = t.user
|
||||
d.description = t.description
|
||||
d.data['text'] = t.text
|
||||
d.data['embeds'] = t.embeds
|
||||
d.save()
|
||||
if t.type == 'pdf':
|
||||
d.file.name = path(d, 'data.pdf')
|
||||
os.makedirs(os.path.dirname(d.file.path))
|
||||
shutil.copy2(t.file.path, d.file.path)
|
||||
d.oshash = ox.oshash(d.file.path)
|
||||
update_info(d)
|
||||
d.save()
|
||||
Document.objects.filter(id=d.id).update(created=t.created, modified=t.modified)
|
||||
c, created = Collection.objects.get_or_create(user=t.user, name='Texts')
|
||||
add(c, d)
|
||||
if t.status == 'featured':
|
||||
add(featured, d)
|
||||
for user in t.subscribed_users.all():
|
||||
favorite, created = Collection.objects.get_or_create(user=user, name='Favorite Texts')
|
||||
add(favorite, d)
|
||||
|
||||
'''
|
||||
for d in document.models.Document.objects.filter(id__in=fix_info):
|
||||
d.get_info()
|
||||
d.get_ratio()
|
||||
d.save()
|
||||
'''
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '__first__'),
|
||||
('text', '__first__'),
|
||||
('document', '0003_new_fields'),
|
||||
('documentcollection', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_texts),
|
||||
]
|
32
pandora/document/migrations/0005_auto_20161008_1232.py
Normal file
32
pandora/document/migrations/0005_auto_20161008_1232.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-08 12:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('document', '0004_migrate_text'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Access',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('access', models.DateTimeField(auto_now=True)),
|
||||
('accessed', models.IntegerField(default=0)),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accessed', to='document.Document')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accessed_documents', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='access',
|
||||
unique_together=set([('document', 'user')]),
|
||||
),
|
||||
]
|
52
pandora/document/migrations/0006_auto_20161026_1259.py
Normal file
52
pandora/document/migrations/0006_auto_20161026_1259.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-26 12:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('document', '0005_auto_20161008_1232'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Find',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, max_length=200)),
|
||||
('value', models.TextField(blank=True, db_index=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Sort',
|
||||
fields=[
|
||||
('document', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='sort', serialize=False, to='document.Document')),
|
||||
('created', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||
('name', models.CharField(db_index=True, max_length=1000, null=True)),
|
||||
('id', models.CharField(db_index=True, max_length=1000, null=True)),
|
||||
('extension', models.CharField(db_index=True, max_length=1000, null=True)),
|
||||
('dimensions', models.BigIntegerField(blank=True, db_index=True, null=True)),
|
||||
('size', models.BigIntegerField(blank=True, db_index=True, null=True)),
|
||||
('description', models.CharField(db_index=True, max_length=1000, null=True)),
|
||||
('matches', models.BigIntegerField(blank=True, db_index=True, null=True)),
|
||||
('user', models.CharField(db_index=True, max_length=1000, null=True)),
|
||||
('modified', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||
('accessed', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||
('timesaccessed', models.BigIntegerField(blank=True, db_index=True, null=True)),
|
||||
('rightslevel', models.BigIntegerField(blank=True, db_index=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='find',
|
||||
name='document',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='find', to='document.Document'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='find',
|
||||
unique_together=set([('document', 'key')]),
|
||||
),
|
||||
]
|
55
pandora/document/migrations/0007_auto_20161026_1559.py
Normal file
55
pandora/document/migrations/0007_auto_20161026_1559.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-26 15:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
def migrate_data(apps, schema_editor):
|
||||
Document = apps.get_model('document', 'Document')
|
||||
for d in Document.objects.all():
|
||||
if 'title' not in d.data:
|
||||
d.data['title'] = d.name
|
||||
if 'description' not in d.data:
|
||||
d.data['description'] = d.description
|
||||
d.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('document', '0006_auto_20161026_1259'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_data),
|
||||
migrations.AlterField(
|
||||
model_name='document',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='document',
|
||||
unique_together=set([]),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='description_sort',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='dimensions_sort',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='name_sort',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='description',
|
||||
),
|
||||
]
|
30
pandora/document/migrations/0008_auto_20161026_1625.py
Normal file
30
pandora/document/migrations/0008_auto_20161026_1625.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-26 16:25
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('document', '0007_auto_20161026_1559'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Facet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, max_length=200)),
|
||||
('value', models.CharField(db_index=True, max_length=1000)),
|
||||
('sortvalue', models.CharField(db_index=True, max_length=1000)),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='facets', to='document.Document')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='facet',
|
||||
unique_together=set([('document', 'key', 'value')]),
|
||||
),
|
||||
]
|
21
pandora/document/migrations/0009_add_group.py
Normal file
21
pandora/document/migrations/0009_add_group.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-27 12:27
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '__first__'),
|
||||
('document', '0008_auto_20161026_1625'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='documents', to='auth.Group'),
|
||||
),
|
||||
]
|
|
@ -5,17 +5,23 @@ from __future__ import division, print_function, absolute_import
|
|||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
import unicodedata
|
||||
|
||||
from six import string_types
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q, Sum, Max
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.conf import settings
|
||||
|
||||
from PIL import Image
|
||||
import ox
|
||||
|
||||
|
||||
from oxdjango import fields
|
||||
from oxdjango.sortmodel import get_sort_field
|
||||
from person.models import get_name_sort
|
||||
from item.models import Item
|
||||
from archive.extract import resize_image
|
||||
from archive.chunk import save_chunk
|
||||
|
@ -23,57 +29,249 @@ from archive.chunk import save_chunk
|
|||
from . import managers
|
||||
from . import utils
|
||||
|
||||
def get_path(f, x): return f.path(x)
|
||||
|
||||
def get_path(f, x):
|
||||
return f.path(x)
|
||||
|
||||
class Document(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "name", "extension")
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
user = models.ForeignKey(User, related_name='files')
|
||||
name = models.CharField(max_length=255)
|
||||
user = models.ForeignKey(User, related_name='documents')
|
||||
groups = models.ManyToManyField(Group, blank=True, related_name='documents')
|
||||
|
||||
extension = models.CharField(max_length=255)
|
||||
size = models.IntegerField(default=0)
|
||||
matches = models.IntegerField(default=0)
|
||||
ratio = models.FloatField(default=1)
|
||||
ratio = models.FloatField(default=640/1024)
|
||||
pages = models.IntegerField(default=-1)
|
||||
width = models.IntegerField(default=-1)
|
||||
height = models.IntegerField(default=-1)
|
||||
description = models.TextField(default="")
|
||||
|
||||
oshash = models.CharField(max_length=16, unique=True, null=True)
|
||||
|
||||
file = models.FileField(default=None, blank=True,null=True, upload_to=get_path)
|
||||
file = models.FileField(default=None, blank=True, null=True, upload_to=get_path)
|
||||
|
||||
objects = managers.DocumentManager()
|
||||
uploading = models.BooleanField(default = False)
|
||||
|
||||
name_sort = models.CharField(max_length=255, null=True)
|
||||
description_sort = models.CharField(max_length=512, null=True)
|
||||
dimensions_sort = models.CharField(max_length=512)
|
||||
uploading = models.BooleanField(default=False)
|
||||
|
||||
items = models.ManyToManyField(Item, through='ItemProperties', related_name='documents')
|
||||
|
||||
rightslevel = models.IntegerField(db_index=True, default=0)
|
||||
data = fields.DictField(default={})
|
||||
|
||||
def update_access(self, user):
|
||||
if not user.is_authenticated():
|
||||
user = None
|
||||
access, created = Access.objects.get_or_create(document=self, user=user)
|
||||
if not created:
|
||||
access.save()
|
||||
|
||||
def update_facet(self, key):
|
||||
current_values = self.get_value(key, [])
|
||||
if key == 'name':
|
||||
current_values = []
|
||||
for k in settings.CONFIG['documentKeys']:
|
||||
if k.get('sortType') == 'person':
|
||||
current_values += self.get(k['id'], [])
|
||||
if not isinstance(current_values, list):
|
||||
if not current_values:
|
||||
current_values = []
|
||||
else:
|
||||
current_values = [unicode(current_values)]
|
||||
|
||||
filter_map = utils.get_by_id(settings.CONFIG['documentKeys'], key).get('filterMap')
|
||||
if filter_map:
|
||||
filter_map = re.compile(filter_map)
|
||||
_current_values = []
|
||||
for value in current_values:
|
||||
value = filter_map.findall(value)
|
||||
if value:
|
||||
_current_values.append(value[0])
|
||||
current_values = _current_values
|
||||
|
||||
current_values = list(set(current_values))
|
||||
current_values = [ox.decode_html(ox.strip_tags(v)) for v in current_values]
|
||||
current_values = [unicodedata.normalize('NFKD', v) for v in current_values]
|
||||
self.update_facet_values(key, current_values)
|
||||
|
||||
def update_facet_values(self, key, current_values):
|
||||
current_sortvalues = set([value.lower() for value in current_values])
|
||||
saved_values = [i.value.lower() for i in Facet.objects.filter(document=self, key=key)]
|
||||
removed_values = filter(lambda i: i not in current_sortvalues, saved_values)
|
||||
|
||||
if removed_values:
|
||||
q = Q()
|
||||
for v in removed_values:
|
||||
q |= Q(value__iexact=v)
|
||||
Facet.objects.filter(document=self, key=key).filter(q).delete()
|
||||
|
||||
for value in current_values:
|
||||
if value.lower() not in saved_values:
|
||||
sortvalue = value
|
||||
if key in self.person_keys + ['name']:
|
||||
sortvalue = get_name_sort(value)
|
||||
sortvalue = utils.sort_string(sortvalue).lower()[:900]
|
||||
f, created = Facet.objects.get_or_create(document=self, key=key, value=value, sortvalue=sortvalue)
|
||||
if created:
|
||||
Facet.objects.filter(document=self, key=key, value__iexact=value).exclude(value=value).delete()
|
||||
Facet.objects.filter(key=key, value__iexact=value).exclude(value=value).update(value=value)
|
||||
saved_values.append(value.lower())
|
||||
|
||||
def update_facets(self):
|
||||
for key in set(self.facet_keys + ['title']):
|
||||
self.update_facet(key)
|
||||
|
||||
def update_find(self):
|
||||
|
||||
def save(key, value):
|
||||
if value not in ('', None):
|
||||
f, created = Find.objects.get_or_create(document=self, key=key)
|
||||
if isinstance(value, bool):
|
||||
value = value and 'true' or 'false'
|
||||
if isinstance(value, string_types):
|
||||
value = ox.decode_html(ox.strip_tags(value.strip()))
|
||||
value = unicodedata.normalize('NFKD', value).lower()
|
||||
f.value = value
|
||||
f.save()
|
||||
else:
|
||||
Find.objects.filter(document=self, key=key).delete()
|
||||
|
||||
with transaction.atomic():
|
||||
data = self.json()
|
||||
for key in settings.CONFIG['documentKeys']:
|
||||
i = key['id']
|
||||
if i == 'rightslevel':
|
||||
save(i, self.rightslevel)
|
||||
elif i not in ('*', 'dimensions') and i not in self.facet_keys:
|
||||
value = data.get(i)
|
||||
if isinstance(value, list):
|
||||
value = u'\n'.join(value)
|
||||
save(i, value)
|
||||
|
||||
base_keys = ('id', 'size', 'dimensions', 'extension', 'matches')
|
||||
|
||||
def update_sort(self):
|
||||
try:
|
||||
s = self.sort
|
||||
except Sort.DoesNotExist:
|
||||
s = Sort(document=self)
|
||||
|
||||
s.id = self.id
|
||||
s.extension = self.extension
|
||||
s.size = self.size
|
||||
s.matches = self.matches
|
||||
if self.extension == 'pdf':
|
||||
s.dimensions = ox.sort_string('2') + ox.sort_string('%d' % self.pages)
|
||||
else:
|
||||
if self.extension == 'html':
|
||||
resolution_sort = self.dimensions
|
||||
s.dimensions = ox.sort_string('1') + ox.sort_string('%d' % resolution_sort)
|
||||
else:
|
||||
resolution_sort = self.width * self.height
|
||||
s.dimensions = ox.sort_string('0') + ox.sort_string('%d' % resolution_sort)
|
||||
|
||||
def sortNames(values):
|
||||
sort_value = u''
|
||||
if values:
|
||||
sort_value = u'; '.join([get_name_sort(name) for name in values])
|
||||
if not sort_value:
|
||||
sort_value = u''
|
||||
return sort_value
|
||||
|
||||
def set_value(s, name, value):
|
||||
if isinstance(value, string_types):
|
||||
value = ox.decode_html(value.lower())
|
||||
if not value:
|
||||
value = None
|
||||
setattr(s, name, value)
|
||||
|
||||
def get_value(source, key):
|
||||
if 'value' in key and 'layer' in key['value']:
|
||||
value = [a.value for a in self.annotations.filter(layer=key['value']['layer']).exclude(value='')]
|
||||
else:
|
||||
value = self.get_value(source)
|
||||
return value
|
||||
|
||||
def get_words(source, key):
|
||||
value = get_value(source, key)
|
||||
if isinstance(value, list):
|
||||
value = '\n'.join(value)
|
||||
value = len(value.split(' ')) if value else 0
|
||||
return value
|
||||
|
||||
for key in filter(lambda k: k.get('sort', False), settings.CONFIG['documentKeys']):
|
||||
name = key['id']
|
||||
if name not in self.base_keys:
|
||||
source = name
|
||||
sort_type = key.get('sortType', key['type'])
|
||||
if 'value' in key:
|
||||
if 'key' in key['value']:
|
||||
source = key['value']['key']
|
||||
sort_type = key['value'].get('type', sort_type)
|
||||
if isinstance(sort_type, list):
|
||||
sort_type = sort_type[0]
|
||||
if sort_type == 'title':
|
||||
value = self.get_value(source, u'Untitled')
|
||||
value = utils.sort_title(value)[:955]
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'person':
|
||||
value = sortNames(self.get_value(source, []))
|
||||
value = utils.sort_string(value)[:955]
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'string':
|
||||
value = self.get_value(source, u'')
|
||||
if isinstance(value, list):
|
||||
value = u','.join(value)
|
||||
value = utils.sort_string(value)[:955]
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'words':
|
||||
value = get_words(source, key) if s.duration else None
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'wordsperminute':
|
||||
value = get_words(source, key)
|
||||
value = value / (s.duration / 60) if value and s.duration else None
|
||||
set_value(s, name, value)
|
||||
elif sort_type in ('length', 'integer', 'time', 'float'):
|
||||
# can be length of strings or length of arrays, i.e. keywords
|
||||
if 'layer' in key.get('value', []):
|
||||
value = self.annotations.filter(layer=key['value']['layer']).count()
|
||||
else:
|
||||
value = self.get_value(source)
|
||||
if isinstance(value, list):
|
||||
value = len(value)
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'year':
|
||||
value = self.get_value(source)
|
||||
set_value(s, name, value)
|
||||
elif sort_type == 'date':
|
||||
value = self.get_value(source)
|
||||
if isinstance(value, string_types):
|
||||
value = datetime_safe.datetime.strptime(value, '%Y-%m-%d')
|
||||
set_value(s, name, value)
|
||||
s.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.uploading:
|
||||
if self.file:
|
||||
self.size = self.file.size
|
||||
self.get_info()
|
||||
if self.extension == 'html':
|
||||
self.size = len(self.data.get('text', ''))
|
||||
|
||||
self.name_sort = ox.sort_string(self.name or u'')[:255].lower()
|
||||
if self.description:
|
||||
self.description_sort = ox.sort_string(self.description)[:512].lower()
|
||||
if self.id:
|
||||
self.update_sort()
|
||||
self.update_find()
|
||||
self.update_facets()
|
||||
new = False
|
||||
else:
|
||||
self.description_sort = None
|
||||
if self.extension == 'pdf':
|
||||
self.dimensions_sort = ox.sort_string('1') + ox.sort_string('%d' % self.pages)
|
||||
else:
|
||||
resolution_sort = self.width * self.height
|
||||
self.dimensions_sort = ox.sort_string('0') + ox.sort_string('%d' % resolution_sort)
|
||||
|
||||
new = True
|
||||
super(Document, self).save(*args, **kwargs)
|
||||
if new:
|
||||
self.update_sort()
|
||||
self.update_find()
|
||||
self.update_facets()
|
||||
self.update_matches()
|
||||
|
||||
def __unicode__(self):
|
||||
|
@ -100,40 +298,61 @@ class Document(models.Model):
|
|||
def get_id(self):
|
||||
return ox.toAZ(self.id)
|
||||
|
||||
def accessible(self, user):
|
||||
return self.user == user or self.status in ('public', 'featured')
|
||||
|
||||
def editable(self, user, item=None):
|
||||
if not user or user.is_anonymous():
|
||||
return False
|
||||
if self.user == user or \
|
||||
user.is_staff or \
|
||||
user.profile.capability('canEditDocuments') == True or \
|
||||
user.profile.capability('canEditDocuments') is True or \
|
||||
(item and item.editable(user)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def edit(self, data, user, item=None):
|
||||
for key in data:
|
||||
if key == 'name':
|
||||
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
|
||||
if not data['name']:
|
||||
data['name'] = "Untitled"
|
||||
name = data['name']
|
||||
num = 1
|
||||
while Document.objects.filter(name=name, user=self.user, extension=self.extension).exclude(id=self.id).count()>0:
|
||||
num += 1
|
||||
name = data['name'] + ' [%d]' % num
|
||||
self.name = name
|
||||
elif key == 'description' and not item:
|
||||
self.description = ox.sanitize_html(data['description'])
|
||||
if item:
|
||||
p, created = ItemProperties.objects.get_or_create(item=item, document=self)
|
||||
if 'description' in data:
|
||||
p.description = ox.sanitize_html(data['description'])
|
||||
p.save()
|
||||
else:
|
||||
for key in data:
|
||||
k = list(filter(lambda i: i['id'] == key, settings.CONFIG['documentKeys']))
|
||||
ktype = k and k[0].get('type') or ''
|
||||
if key == 'text' and self.extension == 'html':
|
||||
self.data['text'] = ox.sanitize_html(data['text'], global_attributes=[
|
||||
'data-name',
|
||||
'data-type',
|
||||
'data-value',
|
||||
'lang'
|
||||
])
|
||||
elif ktype == 'text':
|
||||
self.data[key] = ox.sanitize_html(data[key])
|
||||
elif ktype == '[text]':
|
||||
self.data[key] = [ox.sanitize_html(t) for t in data[key]]
|
||||
elif ktype == '[string]':
|
||||
self.data[key] = [ox.escape_html(t) for t in data[key]]
|
||||
elif isinstance(data[key], string_types):
|
||||
self.data[key] = ox.escape_html(data[key])
|
||||
elif isinstance(data[key], list):
|
||||
def cleanup(i):
|
||||
if isinstance(i, string_types):
|
||||
i = ox.escape_html(i)
|
||||
return i
|
||||
self.data[key] = [cleanup(i) for i in data[key]]
|
||||
elif isinstance(data[key], int) or isinstance(data[key], float):
|
||||
self.data[key] = data[key]
|
||||
else:
|
||||
self.data[key] = ox.escape_html(data[key])
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
if self.extension == 'pdf':
|
||||
return self.pages
|
||||
elif self.extension == 'html':
|
||||
return len(self.data.get('text', '').split(' '))
|
||||
else:
|
||||
return self.resolution
|
||||
|
||||
|
@ -141,21 +360,43 @@ class Document(models.Model):
|
|||
def resolution(self):
|
||||
return [self.width, self.height]
|
||||
|
||||
def get_value(self, key, default=None):
|
||||
if key in (
|
||||
'extension',
|
||||
'id',
|
||||
'matches',
|
||||
'ratio',
|
||||
'size',
|
||||
):
|
||||
return getattr(self, key)
|
||||
elif key == 'user':
|
||||
return self.user.username
|
||||
else:
|
||||
return self.data.get(key, default)
|
||||
|
||||
def json(self, keys=None, user=None, item=None):
|
||||
if not keys:
|
||||
keys=[
|
||||
keys = [
|
||||
'description',
|
||||
'dimensions',
|
||||
'editable',
|
||||
'entities',
|
||||
'extension',
|
||||
'id',
|
||||
'name',
|
||||
'oshash',
|
||||
'title',
|
||||
'ratio',
|
||||
'matches',
|
||||
'size',
|
||||
'user',
|
||||
]
|
||||
if self.extension in ('html', 'txt'):
|
||||
keys.append('text')
|
||||
for key in settings.CONFIG['documentKeys']:
|
||||
if key['id'] in ('*', ):
|
||||
continue
|
||||
if key['id'] not in keys:
|
||||
keys.append(key['id'])
|
||||
response = {}
|
||||
_map = {
|
||||
}
|
||||
|
@ -166,6 +407,10 @@ class Document(models.Model):
|
|||
response[key] = self.editable(user)
|
||||
elif key == 'user':
|
||||
response[key] = self.user.username
|
||||
elif key == 'accessed':
|
||||
response[key] = self.accessed.aggregate(Max('access'))['access__max']
|
||||
elif key == 'timesaccessed':
|
||||
response[key] = self.accessed.aggregate(Sum('accessed'))['accessed__sum']
|
||||
elif key == 'entities':
|
||||
dps = self.documentproperties.select_related('entity').order_by('index')
|
||||
response[key] = entity_jsons = []
|
||||
|
@ -175,8 +420,12 @@ class Document(models.Model):
|
|||
entity_jsons.append(entity_json)
|
||||
elif key == 'items':
|
||||
response[key] = [i['public_id'] for i in self.items.all().values('public_id')]
|
||||
elif key in self.data:
|
||||
response[key] = self.data[key]
|
||||
elif hasattr(self, _map.get(key, key)):
|
||||
response[key] = getattr(self, _map.get(key,key)) or ''
|
||||
response[key] = getattr(self, _map.get(key, key)) or ''
|
||||
if self.extension == 'html':
|
||||
response['text'] = self.data.get('text', '')
|
||||
if item:
|
||||
if isinstance(item, string_types):
|
||||
item = Item.objects.get(public_id=item)
|
||||
|
@ -185,6 +434,10 @@ class Document(models.Model):
|
|||
if 'description' in keys and d[0].description:
|
||||
response['description'] = d[0].description
|
||||
response['index'] = d[0].index
|
||||
if keys:
|
||||
for key in list(response):
|
||||
if key not in keys:
|
||||
del response[key]
|
||||
return response
|
||||
|
||||
def path(self, name=''):
|
||||
|
@ -211,6 +464,9 @@ class Document(models.Model):
|
|||
return False, 0
|
||||
|
||||
def thumbnail(self, size=None, page=None):
|
||||
if not self.file:
|
||||
return os.path.join(settings.STATIC_ROOT, 'png/cover.png')
|
||||
return os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
|
||||
src = self.file.path
|
||||
folder = os.path.dirname(src)
|
||||
if size:
|
||||
|
@ -278,12 +534,12 @@ class Document(models.Model):
|
|||
try:
|
||||
size = Image.open(image).size
|
||||
except:
|
||||
size = [1,1]
|
||||
size = [1, 1]
|
||||
else:
|
||||
if self.width > 0:
|
||||
size = self.resolution
|
||||
else:
|
||||
size = [1,1]
|
||||
size = [640, 1024]
|
||||
self.ratio = size[0] / size[1]
|
||||
return self.ratio
|
||||
|
||||
|
@ -337,6 +593,97 @@ class ItemProperties(models.Model):
|
|||
if self.description:
|
||||
self.description_sort = ox.sort_string(self.description)[:512].lower()
|
||||
else:
|
||||
self.description_sort = self.document.description_sort
|
||||
self.description_sort = self.document.sort.description
|
||||
|
||||
super(ItemProperties, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class Access(models.Model):
|
||||
class Meta:
|
||||
unique_together = ("document", "user")
|
||||
|
||||
access = models.DateTimeField(auto_now=True)
|
||||
document = models.ForeignKey(Document, related_name='accessed')
|
||||
user = models.ForeignKey(User, null=True, related_name='accessed_documents')
|
||||
accessed = models.IntegerField(default=0)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.accessed:
|
||||
self.accessed = 0
|
||||
self.accessed += 1
|
||||
super(Access, self).save(*args, **kwargs)
|
||||
timesaccessed = Access.objects.filter(document=self.document).aggregate(Sum('accessed'))['accessed__sum']
|
||||
Sort.objects.filter(document=self.document).update(timesaccessed=timesaccessed, accessed=self.access)
|
||||
|
||||
def __unicode__(self):
|
||||
if self.user:
|
||||
return u"%s/%s/%s" % (self.user, self.document, self.access)
|
||||
return u"%s/%s" % (self.item, self.access)
|
||||
|
||||
class Facet(models.Model):
|
||||
'''
|
||||
used for keys that can have multiple values like people, languages etc.
|
||||
does not perform to well if total number of items goes above 10k
|
||||
this happens for keywords in 0xdb right now
|
||||
'''
|
||||
|
||||
class Meta:
|
||||
unique_together = ("document", "key", "value")
|
||||
|
||||
document = models.ForeignKey('Document', related_name='facets')
|
||||
key = models.CharField(max_length=200, db_index=True)
|
||||
value = models.CharField(max_length=1000, db_index=True)
|
||||
sortvalue = models.CharField(max_length=1000, db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s=%s" % (self.key, self.value)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.sortvalue:
|
||||
self.sortvalue = utils.sort_string(self.value).lower()[:900]
|
||||
self.sotvalue = self.sortvalue.lower()
|
||||
super(Facet, self).save(*args, **kwargs)
|
||||
|
||||
Document.facet_keys = []
|
||||
for key in settings.CONFIG['documentKeys']:
|
||||
if 'autocomplete' in key and 'autocompleteSortKey' not in key or \
|
||||
key.get('filter'):
|
||||
Document.facet_keys.append(key['id'])
|
||||
|
||||
Document.person_keys = []
|
||||
for key in settings.CONFIG['itemKeys']:
|
||||
if key.get('sortType') == 'person':
|
||||
Document.person_keys.append(key['id'])
|
||||
|
||||
class Find(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ('document', 'key')
|
||||
|
||||
document = models.ForeignKey('Document', related_name='find', db_index=True)
|
||||
key = models.CharField(max_length=200, db_index=True)
|
||||
value = models.TextField(blank=True, db_index=settings.DB_GIN_TRGM)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s=%s' % (self.key, self.value)
|
||||
|
||||
'''
|
||||
Sort
|
||||
table constructed based on info in settings.CONFIG['documentKeys']
|
||||
'''
|
||||
attrs = {
|
||||
'__module__': 'document.models',
|
||||
'document': models.OneToOneField('Document', related_name='sort', primary_key=True),
|
||||
'created': models.DateTimeField(null=True, blank=True, db_index=True),
|
||||
}
|
||||
for key in filter(lambda k: k.get('sort', False) or k['type'] in ('integer', 'time', 'float', 'date', 'enum'), settings.CONFIG['documentKeys']):
|
||||
name = key['id']
|
||||
sort_type = key.get('sortType', key['type'])
|
||||
if isinstance(sort_type, list):
|
||||
sort_type = sort_type[0]
|
||||
field = get_sort_field(sort_type)
|
||||
if name not in attrs:
|
||||
attrs[name] = field[0](**field[1])
|
||||
|
||||
Sort = type('Sort', (models.Model,), attrs)
|
||||
Sort.fields = [f.name for f in Sort._meta.fields]
|
||||
|
|
72
pandora/document/sync_sort.py
Normal file
72
pandora/document/sync_sort.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import print_function
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import fields
|
||||
from django.conf import settings
|
||||
|
||||
from . import models
|
||||
|
||||
def update_tables(debug=False):
|
||||
table_name = models.Sort._meta.db_table
|
||||
cursor = connection.cursor()
|
||||
db_rows = connection.introspection.get_table_description(cursor, table_name)
|
||||
db_fields = dict([(row[0], row) for row in db_rows])
|
||||
db_types = dict([(row[0],
|
||||
connection.introspection.data_types_reverse[row[1]]) for row in db_rows])
|
||||
|
||||
model_fields = ['document_id'] + [f.name for f in models.Sort._meta.fields]
|
||||
rebuild = False
|
||||
|
||||
changes = []
|
||||
for name in db_types:
|
||||
if name not in model_fields:
|
||||
sql = 'ALTER TABLE "%s" DROP COLUMN "%s"' % (table_name, name)
|
||||
changes.append(sql)
|
||||
|
||||
for f in models.Sort._meta.fields:
|
||||
if not f.primary_key:
|
||||
name = f.name
|
||||
col_type = f.db_type(connection)
|
||||
if name not in db_fields:
|
||||
sql = 'ALTER TABLE "%s" ADD COLUMN "%s" %s' % (table_name, name, col_type)
|
||||
changes.append(sql)
|
||||
sql = 'CREATE INDEX "%s_%s_idx" ON "%s" ("%s")' % (table_name, name,
|
||||
table_name, name)
|
||||
changes.append(sql)
|
||||
rebuild = True
|
||||
elif f.__class__.__name__ != db_types[name]:
|
||||
sql = 'ALTER TABLE "%s" DROP COLUMN "%s"' % (table_name, name)
|
||||
changes.append(sql)
|
||||
sql = 'ALTER TABLE "%s" ADD COLUMN "%s" %s' % (table_name, name, col_type)
|
||||
changes.append(sql)
|
||||
sql = 'CREATE INDEX "%s_%s_idx" ON "%s" ("%s")' % (table_name, name,
|
||||
table_name, name)
|
||||
changes.append(sql)
|
||||
rebuild = True
|
||||
elif db_types[name] == 'CharField' and db_fields[name][3] != f.max_length:
|
||||
sql = 'ALTER TABLE "%s" ALTER COLUMN "%s" TYPE %s' % (table_name, name,
|
||||
col_type)
|
||||
changes.append(sql)
|
||||
sql = 'ALTER TABLE "%s" ALTER COLUMN "%s" %s NOT NULL' % (table_name, name,
|
||||
f.null and "DROP" or "SET")
|
||||
changes.append(sql)
|
||||
rebuild = True
|
||||
|
||||
if changes:
|
||||
print("Updating document sort schema...")
|
||||
for sql in changes:
|
||||
if debug:
|
||||
print(sql)
|
||||
cursor.execute(sql)
|
||||
transaction.commit()
|
||||
if rebuild:
|
||||
print("Updating document sort values...")
|
||||
ids = [i['id'] for i in models.Document.objects.all().values('id')]
|
||||
for id in ids:
|
||||
d = models.Document.objects.get(pk=id)
|
||||
if debug:
|
||||
print(d)
|
||||
d.update_sort()
|
|
@ -1,8 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
|
||||
import subprocess
|
||||
|
||||
from item.utils import sort_title, sort_string, get_by_id
|
||||
|
||||
def pdfpages(pdf):
|
||||
return int(pdfinfo(pdf).get('pages', '0'))
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
import unicodedata
|
||||
|
||||
from six import string_types
|
||||
import ox
|
||||
|
@ -13,7 +15,8 @@ from oxdjango.decorators import login_required_json
|
|||
from oxdjango.http import HttpFileResponse
|
||||
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response, HttpErrorJson
|
||||
from django import forms
|
||||
from django.db.models import Sum
|
||||
from django.db.models import Count, Sum
|
||||
from django.conf import settings
|
||||
|
||||
from item import utils
|
||||
from item.models import Item
|
||||
|
@ -35,6 +38,13 @@ def get_document_or_404_json(id):
|
|||
@login_required_json
|
||||
def addDocument(request, data):
|
||||
'''
|
||||
Create new html document
|
||||
takes {
|
||||
title: string
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
Adds one or more documents to one or more items
|
||||
takes {
|
||||
item: string or [string], // one or more item ids (optional)
|
||||
|
@ -46,6 +56,14 @@ def addDocument(request, data):
|
|||
see: editDocument, findDocuments, getDocument, removeDocument, sortDocuments
|
||||
'''
|
||||
response = json_response()
|
||||
if 'title' in data:
|
||||
doc = models.Document(user=request.user, extension='html')
|
||||
doc.data['title'] = data['title']
|
||||
doc.save()
|
||||
response = json_response(status=200, text='created')
|
||||
response['data'] = doc.json(user=request.user)
|
||||
add_changelog(request, data, doc.get_id())
|
||||
else:
|
||||
if 'ids' in data:
|
||||
ids = data['ids']
|
||||
else:
|
||||
|
@ -95,7 +113,8 @@ def editDocument(request, data):
|
|||
Edits data for a document
|
||||
takes {
|
||||
id: string, // document id
|
||||
name: string, // new document name
|
||||
|
||||
key: value, // set new data
|
||||
description: string // new document description
|
||||
item: string // item id (optional)
|
||||
}
|
||||
|
@ -126,22 +145,26 @@ actions.register(editDocument, cache=False)
|
|||
|
||||
|
||||
def _order_query(qs, sort, item=None):
|
||||
prefix = 'sort__'
|
||||
order_by = []
|
||||
for e in sort:
|
||||
operator = e['operator']
|
||||
if operator != '-':
|
||||
operator = ''
|
||||
key = {
|
||||
'name': 'name_sort',
|
||||
'description': 'descriptions__description_sort'
|
||||
if item else 'description_sort',
|
||||
'dimensions': 'dimensions_sort',
|
||||
if item else 'description',
|
||||
'index': 'items__itemproperties__index',
|
||||
#fixme:
|
||||
'position': 'id',
|
||||
'name': 'title',
|
||||
}.get(e['key'], e['key'])
|
||||
if key == 'resolution':
|
||||
order_by.append('%swidth'%operator)
|
||||
order_by.append('%sheight'%operator)
|
||||
else:
|
||||
if '__' not in key:
|
||||
key = "%s%s" % (prefix, key)
|
||||
order = '%s%s' % (operator, key)
|
||||
order_by.append(order)
|
||||
if order_by:
|
||||
|
@ -149,6 +172,24 @@ def _order_query(qs, sort, item=None):
|
|||
qs = qs.distinct()
|
||||
return qs
|
||||
|
||||
def _order_by_group(query):
|
||||
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
|
||||
|
||||
def get_item(query):
|
||||
for c in query.get('conditions', []):
|
||||
if c.get('key') == 'item':
|
||||
|
@ -162,7 +203,7 @@ def parse_query(data, user):
|
|||
for key in ('keys', 'group', 'file', 'range', 'position', 'positions', 'sort'):
|
||||
if key in data:
|
||||
query[key] = data[key]
|
||||
query['qs'] = models.Document.objects.find(data, user).exclude(name='')
|
||||
query['qs'] = models.Document.objects.find(data, user)
|
||||
query['item'] = get_item(data.get('query', {}))
|
||||
return query
|
||||
|
||||
|
@ -192,7 +233,24 @@ def findDocuments(request, data):
|
|||
#order
|
||||
qs = _order_query(query['qs'], query['sort'], query['item'])
|
||||
response = json_response()
|
||||
if 'keys' in data:
|
||||
if 'group' in query:
|
||||
response['data']['items'] = []
|
||||
items = 'items'
|
||||
document_qs = query['qs']
|
||||
order_by = _order_by_group(query)
|
||||
qs = models.Facet.objects.filter(key=query['group']).filter(document__id__in=document_qs)
|
||||
qs = qs.values('value').annotate(items=Count('id')).order_by(*order_by)
|
||||
|
||||
if 'positions' in query:
|
||||
response['data']['positions'] = {}
|
||||
ids = [j['value'] for j in qs]
|
||||
response['data']['positions'] = utils.get_positions(ids, query['positions'])
|
||||
elif 'range' in data:
|
||||
qs = qs[query['range'][0]:query['range'][1]]
|
||||
response['data']['items'] = [{'name': i['value'], 'items': i[items]} for i in qs]
|
||||
else:
|
||||
response['data']['items'] = qs.count()
|
||||
elif 'keys' in data:
|
||||
qs = qs[query['range'][0]:query['range'][1]]
|
||||
|
||||
response['data']['items'] = [l.json(data['keys'], request.user, query['item']) for l in qs]
|
||||
|
@ -330,23 +388,15 @@ def upload(request):
|
|||
if 'chunk' in request.FILES:
|
||||
if file.editable(request.user):
|
||||
response = process_chunk(request, file.save_chunk)
|
||||
response['resultUrl'] = request.build_absolute_uri(file.get_absolute_url())
|
||||
response['resultUrl'] = file.get_absolute_url()
|
||||
# id is used to select document in dialog after upload
|
||||
response['id'] = file.get_id()
|
||||
return render_to_json_response(response)
|
||||
#init upload
|
||||
else:
|
||||
if not file:
|
||||
created = False
|
||||
num = 1
|
||||
_name = name
|
||||
while not created:
|
||||
file, created = models.Document.objects.get_or_create(
|
||||
user=request.user, name=name, extension=extension)
|
||||
if not created:
|
||||
num += 1
|
||||
name = _name + ' [%d]' % num
|
||||
file.name = name
|
||||
file = models.Document(user=request.user, extension=extension)
|
||||
file.data['title'] = name
|
||||
file.extension = extension
|
||||
file.uploading = True
|
||||
file.save()
|
||||
|
@ -361,10 +411,81 @@ def upload(request):
|
|||
file.width = -1
|
||||
file.pages = -1
|
||||
file.save()
|
||||
upload_url = request.build_absolute_uri('/api/upload/document?id=%s' % file.get_id())
|
||||
upload_url = '/api/upload/document?id=%s' % file.get_id()
|
||||
return render_to_json_response({
|
||||
'uploadUrl': upload_url,
|
||||
'url': request.build_absolute_uri(file.get_absolute_url()),
|
||||
'url': file.get_absolute_url(),
|
||||
'result': 1
|
||||
})
|
||||
return render_to_json_response(response)
|
||||
|
||||
def autocompleteDocuments(request, data):
|
||||
'''
|
||||
Returns autocomplete strings for a given documeny key and search string
|
||||
takes {
|
||||
key: string, // document key
|
||||
value: string, // search string
|
||||
operator: string, // '=', '==', '^', '$'
|
||||
query: object, // document query to limit results, see `find`
|
||||
range: [int, int] // range of results to return
|
||||
}
|
||||
returns {
|
||||
items: [string, ...] // list of matching strings
|
||||
}
|
||||
see: autocomplete, autocompleteEntities
|
||||
'''
|
||||
if 'range' not in data:
|
||||
data['range'] = [0, 10]
|
||||
op = data.get('operator', '=')
|
||||
|
||||
key = utils.get_by_id(settings.CONFIG['documentKeys'], data['key'])
|
||||
order_by = key.get('autocompleteSort', False)
|
||||
if order_by:
|
||||
for o in order_by:
|
||||
if o['operator'] != '-':
|
||||
o['operator'] = ''
|
||||
order_by = ['%(operator)ssort__%(key)s' % o for o in order_by]
|
||||
else:
|
||||
order_by = ['-items']
|
||||
|
||||
qs = parse_query({'query': data.get('query', {})}, request.user)['qs']
|
||||
response = json_response({})
|
||||
response['data']['items'] = []
|
||||
'''
|
||||
for d in qs:
|
||||
value = d.json().get(data['key'])
|
||||
add = False
|
||||
if value:
|
||||
if op == '=' and data['value'] in value:
|
||||
add = True
|
||||
elif op == '==' and data['value'].lower() == value.lower():
|
||||
add = True
|
||||
elif op == '^' and value.lower().startswith(data['value'].lower()):
|
||||
add = True
|
||||
if add and value not in response['data']['items']:
|
||||
response['data']['items'].append(value)
|
||||
|
||||
'''
|
||||
|
||||
sort_type = key.get('sortType', key.get('type', 'string'))
|
||||
qs = models.Facet.objects.filter(key=data['key'])
|
||||
if data['value']:
|
||||
value = unicodedata.normalize('NFKD', data['value']).lower()
|
||||
if op == '=':
|
||||
qs = qs.filter(value__icontains=value)
|
||||
elif op == '==':
|
||||
qs = qs.filter(value__iexact=value)
|
||||
elif op == '^':
|
||||
qs = qs.filter(value__istartswith=value)
|
||||
elif op == '$':
|
||||
qs = qs.filter(value__iendswith=value)
|
||||
if 'query' in data:
|
||||
document_query = parse_query({'query': data.get('query', {})}, request.user)['qs']
|
||||
qs = qs.filter(document__in=document_query)
|
||||
qs = qs.values('value').annotate(items=Count('id'))
|
||||
qs = qs.order_by(*order_by)
|
||||
qs = qs[data['range'][0]:data['range'][1]]
|
||||
response = json_response({})
|
||||
response['data']['items'] = [i['value'] for i in qs]
|
||||
return render_to_json_response(response)
|
||||
actions.register(autocompleteDocuments)
|
||||
|
|
0
pandora/documentcollection/__init__.py
Normal file
0
pandora/documentcollection/__init__.py
Normal file
132
pandora/documentcollection/managers.py
Normal file
132
pandora/documentcollection/managers.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
from django.db.models import Q, Manager
|
||||
|
||||
from oxdjango.managers import get_operator
|
||||
from oxdjango.query import QuerySet
|
||||
|
||||
keymap = {
|
||||
'user': 'user__username',
|
||||
}
|
||||
default_key = 'name'
|
||||
|
||||
def parseCondition(condition, user):
|
||||
'''
|
||||
'''
|
||||
k = condition.get('key', default_key)
|
||||
k = keymap.get(k, k)
|
||||
if not k:
|
||||
k = default_key
|
||||
v = condition.get('value', '')
|
||||
op = condition.get('operator')
|
||||
if not op:
|
||||
op = '='
|
||||
if op.startswith('!'):
|
||||
op = op[1:]
|
||||
exclude = True
|
||||
else:
|
||||
exclude = False
|
||||
if k == 'id':
|
||||
v = v.split(":")
|
||||
if len(v) >= 2:
|
||||
v = (v[0], ":".join(v[1:]))
|
||||
q = Q(user__username=v[0], name=v[1])
|
||||
else:
|
||||
q = Q(id__in=[])
|
||||
return q
|
||||
if k == 'subscribed':
|
||||
key = 'subscribed_users__username'
|
||||
v = user.username
|
||||
elif isinstance(v, bool):
|
||||
key = k
|
||||
else:
|
||||
key = k + get_operator(op, 'istr')
|
||||
key = str(key)
|
||||
if exclude:
|
||||
q = ~Q(**{key: v})
|
||||
else:
|
||||
q = Q(**{key: v})
|
||||
return q
|
||||
|
||||
def parseConditions(conditions, operator, user):
|
||||
'''
|
||||
conditions: [
|
||||
{
|
||||
value: "war"
|
||||
}
|
||||
{
|
||||
key: "year",
|
||||
value: "1970-1980,
|
||||
operator: "!="
|
||||
},
|
||||
{
|
||||
key: "country",
|
||||
value: "f",
|
||||
operator: "^"
|
||||
}
|
||||
],
|
||||
operator: "&"
|
||||
'''
|
||||
conn = []
|
||||
for condition in conditions:
|
||||
if 'conditions' in condition:
|
||||
q = parseConditions(condition['conditions'],
|
||||
condition.get('operator', '&'), user)
|
||||
if q:
|
||||
conn.append(q)
|
||||
pass
|
||||
else:
|
||||
conn.append(parseCondition(condition, user))
|
||||
if conn:
|
||||
q = conn[0]
|
||||
for c in conn[1:]:
|
||||
if operator == '|':
|
||||
q = q | c
|
||||
else:
|
||||
q = q & c
|
||||
return q
|
||||
return None
|
||||
|
||||
|
||||
class CollectionManager(Manager):
|
||||
|
||||
def get_query_set(self):
|
||||
return QuerySet(self.model)
|
||||
|
||||
def find(self, data, user):
|
||||
'''
|
||||
query: {
|
||||
conditions: [
|
||||
{
|
||||
value: "war"
|
||||
}
|
||||
{
|
||||
key: "year",
|
||||
value: "1970-1980,
|
||||
operator: "!="
|
||||
},
|
||||
{
|
||||
key: "country",
|
||||
value: "f",
|
||||
operator: "^"
|
||||
}
|
||||
],
|
||||
operator: "&"
|
||||
}
|
||||
'''
|
||||
|
||||
#join query with operator
|
||||
qs = self.get_query_set()
|
||||
query = data.get('query', {})
|
||||
conditions = parseConditions(query.get('conditions', []),
|
||||
query.get('operator', '&'),
|
||||
user)
|
||||
if conditions:
|
||||
qs = qs.filter(conditions)
|
||||
|
||||
if user.is_anonymous():
|
||||
qs = qs.filter(Q(status='public') | Q(status='featured'))
|
||||
else:
|
||||
qs = qs.filter(Q(status='public') | Q(status='featured') | Q(user=user))
|
||||
return qs
|
84
pandora/documentcollection/migrations/0001_initial.py
Normal file
84
pandora/documentcollection/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-08 12:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import documentcollection.models
|
||||
import oxdjango.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('document', '0003_new_fields'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Collection',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('status', models.CharField(default=b'private', max_length=20)),
|
||||
('query', oxdjango.fields.DictField(default={b'static': True})),
|
||||
('type', models.CharField(default=b'static', max_length=255)),
|
||||
('description', models.TextField(default=b'')),
|
||||
('icon', models.ImageField(blank=True, default=None, upload_to=documentcollection.models.get_icon_path)),
|
||||
('view', models.TextField(default=documentcollection.models.get_collectionview)),
|
||||
('sort', oxdjango.fields.TupleField(default=documentcollection.models.get_collectionsort, editable=False)),
|
||||
('poster_frames', oxdjango.fields.TupleField(default=[], editable=False)),
|
||||
('numberofdocuments', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CollectionDocument',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
('index', models.IntegerField(default=0)),
|
||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='documentcollection.Collection')),
|
||||
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='document.Document')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Position',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('section', models.CharField(max_length=255)),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='position', to='documentcollection.Collection')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collection_positions', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='documents',
|
||||
field=models.ManyToManyField(related_name='collections', through='documentcollection.CollectionDocument', to='document.Document'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='subscribed_users',
|
||||
field=models.ManyToManyField(related_name='subscribed_collections', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='collection',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collections', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='position',
|
||||
unique_together=set([('user', 'collection', 'section')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='collection',
|
||||
unique_together=set([('user', 'name')]),
|
||||
),
|
||||
]
|
0
pandora/documentcollection/migrations/__init__.py
Normal file
0
pandora/documentcollection/migrations/__init__.py
Normal file
321
pandora/documentcollection/models.py
Normal file
321
pandora/documentcollection/models.py
Normal file
|
@ -0,0 +1,321 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from glob import glob
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
import ox
|
||||
|
||||
from oxdjango.fields import DictField, TupleField
|
||||
|
||||
from archive import extract
|
||||
|
||||
from . import managers
|
||||
|
||||
|
||||
def get_path(f, x):
|
||||
return f.path(x)
|
||||
|
||||
def get_icon_path(f, x):
|
||||
return get_path(f, 'icon.jpg')
|
||||
|
||||
def get_collectionview():
|
||||
return settings.CONFIG['user']['ui']['collectionView']
|
||||
|
||||
def get_collectionsort():
|
||||
return tuple(settings.CONFIG['user']['ui']['collectionSort'])
|
||||
|
||||
class Collection(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "name")
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
user = models.ForeignKey(User, related_name='collections')
|
||||
name = models.CharField(max_length=255)
|
||||
status = models.CharField(max_length=20, default='private')
|
||||
_status = ['private', 'public', 'featured']
|
||||
query = DictField(default={"static": True})
|
||||
type = models.CharField(max_length=255, default='static')
|
||||
description = models.TextField(default='')
|
||||
|
||||
icon = models.ImageField(default=None, blank=True, upload_to=get_icon_path)
|
||||
|
||||
view = models.TextField(default=get_collectionview)
|
||||
sort = TupleField(default=get_collectionsort, editable=False)
|
||||
|
||||
poster_frames = TupleField(default=[], editable=False)
|
||||
|
||||
#is through table still required?
|
||||
documents = models.ManyToManyField('document.Document', related_name='collections',
|
||||
through='CollectionDocument')
|
||||
|
||||
numberofdocuments = models.IntegerField(default=0)
|
||||
subscribed_users = models.ManyToManyField(User, related_name='subscribed_collections')
|
||||
|
||||
objects = managers.CollectionManager()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.query.get('static', False):
|
||||
self.type = 'static'
|
||||
else:
|
||||
self.type = 'smart'
|
||||
if self.id:
|
||||
self.numberofdocuments = self.get_numberofdocuments(self.user)
|
||||
super(Collection, self).save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id):
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
return cls.objects.get(user__username=username, name=collectionname)
|
||||
|
||||
def get_documents(self, user=None):
|
||||
if self.query.get('static', False):
|
||||
return self.documents
|
||||
from document.models import Document
|
||||
return Document.objects.find({'query': self.query}, user)
|
||||
|
||||
def get_numberofdocuments(self, user=None):
|
||||
return self.get_documents(user).count()
|
||||
|
||||
def add(self, document):
|
||||
q = self.documents.filter(id=document.id)
|
||||
if q.count() == 0:
|
||||
l = CollectionDocument()
|
||||
l.collection = self
|
||||
l.document = document
|
||||
l.index = CollectionDocument.objects.filter(collection=self).aggregate(Max('index'))['index__max']
|
||||
if l.index is None:
|
||||
l.index = 0
|
||||
else:
|
||||
l.index += 1
|
||||
l.save()
|
||||
|
||||
def remove(self, document=None, documents=None):
|
||||
if document:
|
||||
CollectionDocument.objects.all().filter(document=document, collection=self).delete()
|
||||
if documents:
|
||||
CollectionDocument.objects.all().filter(document__id__in=documents, collection=self).delete()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.get_id()
|
||||
|
||||
def get_id(self):
|
||||
return u'%s:%s' % (self.user.username, self.name)
|
||||
|
||||
def accessible(self, user):
|
||||
return self.user == user or self.status in ('public', 'featured')
|
||||
|
||||
def editable(self, user):
|
||||
if user.is_anonymous():
|
||||
return False
|
||||
if self.user == user or \
|
||||
user.is_staff or \
|
||||
user.profile.capability('canEditFeaturedCollections') is True:
|
||||
return True
|
||||
return False
|
||||
|
||||
def edit(self, data, user):
|
||||
for key in data:
|
||||
if key == 'query' and not data['query']:
|
||||
setattr(self, key, {"static": True})
|
||||
elif key == 'query' and isinstance(data[key], dict):
|
||||
setattr(self, key, data[key])
|
||||
elif key == 'type':
|
||||
if data[key] == 'static':
|
||||
self.query = {"static": True}
|
||||
self.type = 'static'
|
||||
else:
|
||||
self.type = 'smart'
|
||||
if self.query.get('static', False):
|
||||
self.query = {}
|
||||
elif key == 'status':
|
||||
value = data[key]
|
||||
if value not in self._status:
|
||||
value = self._status[0]
|
||||
if value == 'private':
|
||||
for user in self.subscribed_users.all():
|
||||
self.subscribed_users.remove(user)
|
||||
qs = Position.objects.filter(user=user, collection=self)
|
||||
if qs.count() > 1:
|
||||
pos = qs[0]
|
||||
pos.section = 'personal'
|
||||
pos.save()
|
||||
elif value == 'featured':
|
||||
if user.profile.capability('canEditFeaturedCollections'):
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=user,
|
||||
section='featured')
|
||||
if created:
|
||||
qs = Position.objects.filter(user=user, section='featured')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
Position.objects.filter(collection=self).exclude(id=pos.id).delete()
|
||||
else:
|
||||
value = self.status
|
||||
elif self.status == 'featured' and value == 'public':
|
||||
Position.objects.filter(collection=self).delete()
|
||||
pos, created = Position.objects.get_or_create(collection=self,
|
||||
user=self.user, section='personal')
|
||||
qs = Position.objects.filter(user=self.user, section='personal')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
for u in self.subscribed_users.all():
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=u,
|
||||
section='public')
|
||||
qs = Position.objects.filter(user=u, section='public')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
|
||||
self.status = value
|
||||
elif key == 'name':
|
||||
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
|
||||
if not data['name']:
|
||||
data['name'] = "Untitled"
|
||||
name = data['name']
|
||||
num = 1
|
||||
while Collection.objects.filter(name=name, user=self.user).exclude(id=self.id).count() > 0:
|
||||
num += 1
|
||||
name = data['name'] + ' [%d]' % num
|
||||
self.name = name
|
||||
elif key == 'description':
|
||||
self.description = ox.sanitize_html(data['description'])
|
||||
|
||||
if 'position' in data:
|
||||
pos, created = Position.objects.get_or_create(collection=self, user=user)
|
||||
pos.position = data['position']
|
||||
pos.section = 'featured'
|
||||
if self.status == 'private':
|
||||
pos.section = 'personal'
|
||||
pos.save()
|
||||
if 'posterFrames' in data:
|
||||
self.poster_frames = tuple(data['posterFrames'])
|
||||
if 'view' in data:
|
||||
self.view = data['view']
|
||||
if 'sort' in data:
|
||||
self.sort = tuple(data['sort'])
|
||||
self.save()
|
||||
if 'posterFrames' in data:
|
||||
self.update_icon()
|
||||
|
||||
def json(self, keys=None, user=None):
|
||||
if not keys:
|
||||
keys = ['id', 'name', 'user', 'type', 'query', 'status', 'subscribed', 'posterFrames', 'description', 'view']
|
||||
response = {}
|
||||
for key in keys:
|
||||
if key in ('items', 'documents'):
|
||||
response[key] = self.get_numberofdocuments(user)
|
||||
elif key == 'id':
|
||||
response[key] = self.get_id()
|
||||
elif key == 'user':
|
||||
response[key] = self.user.username
|
||||
elif key == 'query':
|
||||
if not self.query.get('static', False):
|
||||
response[key] = self.query
|
||||
elif key == 'subscribers':
|
||||
response[key] = self.subscribed_users.all().count()
|
||||
elif key == 'subscribed':
|
||||
if user and not user.is_anonymous():
|
||||
response[key] = self.subscribed_users.filter(id=user.id).exists()
|
||||
else:
|
||||
response[key] = getattr(self, {
|
||||
'posterFrames': 'poster_frames'
|
||||
}.get(key, key))
|
||||
return response
|
||||
|
||||
def path(self, name=''):
|
||||
h = "%07d" % self.id
|
||||
return os.path.join('collections', h[:2], h[2:4], h[4:6], h[6:], name)
|
||||
|
||||
def update_icon(self):
|
||||
frames = []
|
||||
#fixme
|
||||
'''
|
||||
if not self.poster_frames:
|
||||
documents = self.get_documents(self.user)
|
||||
if documents.count():
|
||||
poster_frames = []
|
||||
for i in range(0, documents.count(), max(1, int(documents.count()/4))):
|
||||
poster_frames.append({
|
||||
'document': documents[int(i)].id,
|
||||
'position': documents[int(i)].poster_frame
|
||||
})
|
||||
self.poster_frames = tuple(poster_frames)
|
||||
self.save()
|
||||
for i in self.poster_frames:
|
||||
from document.models import Document
|
||||
qs = Document.objects.filter(id=i['document'])
|
||||
if qs.count() > 0:
|
||||
if i.get('position'):
|
||||
frame = qs[0].frame(i['position'])
|
||||
if frame:
|
||||
frames.append(frame)
|
||||
'''
|
||||
self.icon.name = self.path('icon.jpg')
|
||||
icon = self.icon.path
|
||||
if frames:
|
||||
while len(frames) < 4:
|
||||
frames += frames
|
||||
folder = os.path.dirname(icon)
|
||||
ox.makedirs(folder)
|
||||
for f in glob("%s/icon*.jpg" % folder):
|
||||
os.unlink(f)
|
||||
cmd = [
|
||||
settings.collection_ICON,
|
||||
'-f', ','.join(frames),
|
||||
'-o', icon
|
||||
]
|
||||
p = subprocess.Popen(cmd, close_fds=True)
|
||||
p.wait()
|
||||
self.save()
|
||||
|
||||
def get_icon(self, size=16):
|
||||
path = self.path('icon%d.jpg' % size)
|
||||
path = os.path.join(settings.MEDIA_ROOT, path)
|
||||
if not os.path.exists(path):
|
||||
folder = os.path.dirname(path)
|
||||
ox.makedirs(folder)
|
||||
if self.icon and os.path.exists(self.icon.path):
|
||||
source = self.icon.path
|
||||
max_size = min(self.icon.width, self.icon.height)
|
||||
else:
|
||||
source = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
|
||||
max_size = 256
|
||||
if size < max_size:
|
||||
extract.resize_image(source, path, size=size)
|
||||
else:
|
||||
path = source
|
||||
return path
|
||||
|
||||
class CollectionDocument(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
collection = models.ForeignKey(Collection)
|
||||
index = models.IntegerField(default=0)
|
||||
document = models.ForeignKey('document.Document')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s in %s' % (self.document, self.collection)
|
||||
|
||||
class Position(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "collection", "section")
|
||||
|
||||
collection = models.ForeignKey(Collection, related_name='position')
|
||||
user = models.ForeignKey(User, related_name='collection_positions')
|
||||
section = models.CharField(max_length=255)
|
||||
position = models.IntegerField(default=0)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s/%s/%s' % (self.section, self.position, self.collection)
|
||||
|
448
pandora/documentcollection/views.py
Normal file
448
pandora/documentcollection/views.py
Normal file
|
@ -0,0 +1,448 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
|
||||
from django.db.models import Max, Sum
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
from oxdjango.decorators import login_required_json
|
||||
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response
|
||||
from oxdjango.http import HttpFileResponse
|
||||
import ox
|
||||
|
||||
from . import models
|
||||
from oxdjango.api import actions
|
||||
from item import utils
|
||||
from document.models import Document
|
||||
from user.tasks import update_numberofcollections
|
||||
from changelog.models import add_changelog
|
||||
|
||||
def get_collection_or_404_json(id):
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
return get_object_or_404_json(models.Collection, user__username=username, name=collectionname)
|
||||
|
||||
def _order_query(qs, sort):
|
||||
order_by = []
|
||||
for e in sort:
|
||||
operator = e['operator']
|
||||
if operator != '-':
|
||||
operator = ''
|
||||
key = {
|
||||
'subscribed': 'subscribed_users',
|
||||
'items': 'numberofitems'
|
||||
}.get(e['key'], e['key'])
|
||||
order = '%s%s' % (operator, key)
|
||||
order_by.append(order)
|
||||
if key == 'subscribers':
|
||||
qs = qs.annotate(subscribers=Sum('subscribed_users'))
|
||||
if order_by:
|
||||
qs = qs.order_by(*order_by, nulls_last=True)
|
||||
qs = qs.distinct()
|
||||
return qs
|
||||
|
||||
def parse_query(data, user):
|
||||
query = {}
|
||||
query['range'] = [0, 100]
|
||||
query['sort'] = [{'key':'user', 'operator':'+'}, {'key':'name', 'operator':'+'}]
|
||||
for key in ('keys', 'group', 'collection', 'range', 'position', 'positions', 'sort'):
|
||||
if key in data:
|
||||
query[key] = data[key]
|
||||
query['qs'] = models.Collection.objects.find(data, user)
|
||||
return query
|
||||
|
||||
|
||||
def findCollections(request, data):
|
||||
'''
|
||||
Finds collections for a given query
|
||||
takes {
|
||||
query: object, // query object, see `find`
|
||||
sort: [], // collection of sort objects, see `find`
|
||||
range: [int, int], // range of results to return
|
||||
keys: [string] // collection of properties to return
|
||||
}
|
||||
returns {
|
||||
items: [object] // collection of collection objects
|
||||
}
|
||||
notes: Possible query keys are 'featured', 'name', 'subscribed' and 'user',
|
||||
possible keys are 'featured', 'name', 'query', 'subscribed' and 'user'.
|
||||
see: addCollection, editCollection, find, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
query = parse_query(data, request.user)
|
||||
|
||||
#order
|
||||
is_section_request = query['sort'] == [{u'operator': u'+', u'key': u'position'}]
|
||||
|
||||
def is_featured_condition(x):
|
||||
return x['key'] == 'status' and \
|
||||
x['value'] == 'featured' and \
|
||||
x['operator'] in ('=', '==')
|
||||
|
||||
is_featured = any(
|
||||
is_featured_condition(x)
|
||||
for x in data.get('query', {}).get('conditions', [])
|
||||
)
|
||||
|
||||
if is_section_request:
|
||||
qs = query['qs']
|
||||
if not is_featured and not request.user.is_anonymous():
|
||||
qs = qs.filter(position__in=models.Position.objects.filter(user=request.user))
|
||||
qs = qs.order_by('position__position')
|
||||
else:
|
||||
qs = _order_query(query['qs'], query['sort'])
|
||||
|
||||
response = json_response()
|
||||
if 'keys' in data:
|
||||
qs = qs[query['range'][0]:query['range'][1]]
|
||||
|
||||
response['data']['items'] = [l.json(data['keys'], request.user) for l in qs]
|
||||
elif 'position' in data:
|
||||
#FIXME: actually implement position requests
|
||||
response['data']['position'] = 0
|
||||
elif 'positions' in data:
|
||||
ids = [i.get_id() for i in qs]
|
||||
response['data']['positions'] = utils.get_positions(ids, query['positions'])
|
||||
else:
|
||||
response['data']['items'] = qs.count()
|
||||
return render_to_json_response(response)
|
||||
actions.register(findCollections)
|
||||
|
||||
def getCollection(request, data):
|
||||
'''
|
||||
Gets a collection by id
|
||||
takes {
|
||||
id: string // collection id
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
section: string, // collections section (like 'personal')
|
||||
... // more key/value pairs
|
||||
}
|
||||
see: addCollection, editCollection, findCollections, removeCollection, sortCollections
|
||||
'''
|
||||
if 'id' in data:
|
||||
response = json_response()
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
if collection.accessible(request.user):
|
||||
response['data'] = collection.json(user=request.user)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
else:
|
||||
response = json_response(status=404, text='not found')
|
||||
return render_to_json_response(response)
|
||||
actions.register(getCollection)
|
||||
|
||||
@login_required_json
|
||||
def addCollectionItems(request, data):
|
||||
'''
|
||||
Adds one or more items to a static collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
items: [string], // either collection of item ids
|
||||
query: object // or query object, see `find`
|
||||
}
|
||||
returns {}
|
||||
see: find, orderCollectionItems, removeCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
if 'items' in data:
|
||||
if collection.editable(request.user):
|
||||
with transaction.atomic():
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
for item in Document.objects.filter(id__in=items):
|
||||
collection.add(item)
|
||||
response = json_response(status=200, text='items added')
|
||||
add_changelog(request, data, data['collection'])
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
elif 'query' in data:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
else:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
return render_to_json_response(response)
|
||||
actions.register(addCollectionItems, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def removeCollectionItems(request, data):
|
||||
'''
|
||||
Removes one or more items from a static collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
items: [itemId], // either collection of item ids
|
||||
query: object // or query object, see `find`
|
||||
}
|
||||
returns {}
|
||||
see: addCollectionItems, find, orderCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
if 'items' in data:
|
||||
if collection.editable(request.user):
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
collection.remove(documents=items)
|
||||
response = json_response(status=200, text='items removed')
|
||||
add_changelog(request, data, data['collection'])
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
elif 'query' in data:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
|
||||
else:
|
||||
response = json_response(status=501, text='not implemented')
|
||||
return render_to_json_response(response)
|
||||
actions.register(removeCollectionItems, cache=False)
|
||||
|
||||
@login_required_json
|
||||
def orderCollectionItems(request, data):
|
||||
'''
|
||||
Sets the manual ordering of items in a given collection
|
||||
takes {
|
||||
collection: string, // collection id
|
||||
ids: [string] // ordered collection of item ids
|
||||
}
|
||||
returns {}
|
||||
notes: There is no UI for this yet.
|
||||
see: addCollectionItems, removeCollectionItems
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['collection'])
|
||||
response = json_response()
|
||||
if collection.editable(request.user) and collection.type == 'static':
|
||||
index = 0
|
||||
with transaction.atomic():
|
||||
for i in data['ids']:
|
||||
models.CollectionItem.objects.filter(collection=collection, item__public_id=i).update(index=index)
|
||||
index += 1
|
||||
else:
|
||||
response = json_response(status=403, text='permission denied')
|
||||
return render_to_json_response(response)
|
||||
actions.register(orderCollectionItems, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def addCollection(request, data):
|
||||
'''
|
||||
Adds a new collection
|
||||
takes {
|
||||
name: value, // collection name (optional)
|
||||
... // more key/value pairs
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
name: string, // collection name
|
||||
... // more key/value pairs
|
||||
}
|
||||
notes: Possible keys are 'description', 'items', 'name', 'query', 'sort',
|
||||
'type' and 'view'.
|
||||
see: editCollection, findCollections, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
data['name'] = re.sub(' \[\d+\]$', '', data.get('name', 'Untitled')).strip()
|
||||
name = data['name']
|
||||
if not name:
|
||||
name = "Untitled"
|
||||
num = 1
|
||||
created = False
|
||||
while not created:
|
||||
collection, created = models.Collection.objects.get_or_create(name=name, user=request.user)
|
||||
num += 1
|
||||
name = data['name'] + ' [%d]' % num
|
||||
|
||||
del data['name']
|
||||
if data:
|
||||
collection.edit(data, request.user)
|
||||
else:
|
||||
collection.save()
|
||||
update_numberofcollections.delay(request.user.username)
|
||||
|
||||
if 'items' in data:
|
||||
items = [ox.fromAZ(id) for id in data['items']]
|
||||
for item in Document.objects.filter(id__in=items):
|
||||
collection.add(item)
|
||||
|
||||
if collection.status == 'featured':
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection,
|
||||
user=request.user, section='featured')
|
||||
qs = models.Position.objects.filter(section='featured')
|
||||
else:
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection,
|
||||
user=request.user, section='personal')
|
||||
qs = models.Position.objects.filter(user=request.user, section='personal')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
response = json_response(status=200, text='created')
|
||||
response['data'] = collection.json()
|
||||
add_changelog(request, data, collection.get_id())
|
||||
return render_to_json_response(response)
|
||||
actions.register(addCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def editCollection(request, data):
|
||||
'''
|
||||
Edits a collection
|
||||
takes {
|
||||
id: string, // collection id
|
||||
key: value, // property id and new value
|
||||
... // more key/value pairs
|
||||
}
|
||||
returns {
|
||||
id: string, // collection id
|
||||
... // more key/value pairs
|
||||
}
|
||||
notes: Possible keys are 'name', 'position', 'posterFrames', 'query' and
|
||||
'status'. 'posterFrames' is an array of {item, position}. If you change
|
||||
'status', you have to pass 'position' (the position of the collection in its new
|
||||
collection folder).
|
||||
see: addCollection, findCollections, getCollection, removeCollection, sortCollections
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
if collection.editable(request.user):
|
||||
response = json_response()
|
||||
collection.edit(data, request.user)
|
||||
response['data'] = collection.json(user=request.user)
|
||||
add_changelog(request, data)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
return render_to_json_response(response)
|
||||
actions.register(editCollection, cache=False)
|
||||
|
||||
@login_required_json
|
||||
def removeCollection(request, data):
|
||||
'''
|
||||
Removes a collection
|
||||
takes {
|
||||
id: string // collection id
|
||||
}
|
||||
returns {}
|
||||
see: addCollection, editCollection, findCollections, getCollection, sortCollections
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
response = json_response()
|
||||
if collection.editable(request.user):
|
||||
add_changelog(request, data)
|
||||
collection.delete()
|
||||
update_numberofcollections.delay(request.user.username)
|
||||
else:
|
||||
response = json_response(status=403, text='not allowed')
|
||||
return render_to_json_response(response)
|
||||
actions.register(removeCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def subscribeToCollection(request, data):
|
||||
'''
|
||||
Adds a collection to favorites
|
||||
takes {
|
||||
id: string, // collection id
|
||||
user: string // username (admin-only)
|
||||
}
|
||||
returns {}
|
||||
see: unsubscribeFromCollection
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
user = request.user
|
||||
if collection.status == 'public' and \
|
||||
collection.subscribed_users.filter(username=user.username).count() == 0:
|
||||
collection.subscribed_users.add(user)
|
||||
pos, created = models.Position.objects.get_or_create(collection=collection, user=user, section='public')
|
||||
if created:
|
||||
qs = models.Position.objects.filter(user=user, section='public')
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
add_changelog(request, data)
|
||||
response = json_response()
|
||||
return render_to_json_response(response)
|
||||
actions.register(subscribeToCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def unsubscribeFromCollection(request, data):
|
||||
'''
|
||||
Removes a collection from favorites
|
||||
takes {
|
||||
id: string, // collection id
|
||||
user: string // username (admin-only)
|
||||
}
|
||||
returns {}
|
||||
see: subscribeToCollection
|
||||
'''
|
||||
collection = get_collection_or_404_json(data['id'])
|
||||
user = request.user
|
||||
collection.subscribed_users.remove(user)
|
||||
models.Position.objects.filter(collection=collection, user=user, section='public').delete()
|
||||
response = json_response()
|
||||
add_changelog(request, data)
|
||||
return render_to_json_response(response)
|
||||
actions.register(unsubscribeFromCollection, cache=False)
|
||||
|
||||
|
||||
@login_required_json
|
||||
def sortCollections(request, data):
|
||||
'''
|
||||
Sets the order of collections in a given section
|
||||
takes {
|
||||
section: string, // collections section
|
||||
ids: [string] // ordered collection of collections
|
||||
}
|
||||
returns {}
|
||||
notes: Possible sections are 'personal', 'favorite' and 'featured'. Setting
|
||||
the order of featured collections requires the appropriate capability.
|
||||
see: addCollection, editCollection, findCollections, getCollection, removeCollection
|
||||
'''
|
||||
position = 0
|
||||
section = data['section']
|
||||
section = {
|
||||
'favorite': 'public'
|
||||
}.get(section, section)
|
||||
#ids = collection(set(data['ids']))
|
||||
ids = data['ids']
|
||||
if section == 'featured' and not request.user.profile.capability('canEditFeaturedCollections'):
|
||||
response = json_response(status=403, text='not allowed')
|
||||
else:
|
||||
user = request.user
|
||||
if section == 'featured':
|
||||
for i in ids:
|
||||
l = get_collection_or_404_json(i)
|
||||
qs = models.Position.objects.filter(section=section, collection=l)
|
||||
if qs.count() > 0:
|
||||
pos = qs[0]
|
||||
else:
|
||||
pos = models.Position(collection=l, user=user, section=section)
|
||||
if pos.position != position:
|
||||
pos.position = position
|
||||
pos.save()
|
||||
position += 1
|
||||
models.Position.objects.filter(section=section, collection=l).exclude(id=pos.id).delete()
|
||||
else:
|
||||
for i in ids:
|
||||
l = get_collection_or_404_json(i)
|
||||
pos, created = models.Position.objects.get_or_create(collection=l,
|
||||
user=request.user, section=section)
|
||||
if pos.position != position:
|
||||
pos.position = position
|
||||
pos.save()
|
||||
position += 1
|
||||
|
||||
response = json_response()
|
||||
return render_to_json_response(response)
|
||||
actions.register(sortCollections, cache=False)
|
||||
|
||||
|
||||
def icon(request, id, size=16):
|
||||
if not size:
|
||||
size = 16
|
||||
|
||||
id = id.split(':')
|
||||
username = id[0]
|
||||
collectionname = ":".join(id[1:])
|
||||
qs = models.Collection.objects.filter(user__username=username, name=collectionname)
|
||||
if qs.count() == 1 and qs[0].accessible(request.user):
|
||||
collection = qs[0]
|
||||
icon = collection.get_icon(int(size))
|
||||
else:
|
||||
icon = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
|
||||
return HttpFileResponse(icon, content_type='image/jpeg')
|
|
@ -34,11 +34,13 @@ class Command(BaseCommand):
|
|||
|
||||
if settings.DB_GIN_TRGM:
|
||||
import entity.models
|
||||
import document.models
|
||||
for table, column in (
|
||||
(models.ItemFind._meta.db_table, 'value'), # Item Find
|
||||
(models.Clip._meta.db_table, 'findvalue'), # Clip Find
|
||||
(models.Annotation._meta.db_table, 'findvalue'), # Annotation Find
|
||||
(entity.models.Find._meta.db_table, 'value'), # Entity Find
|
||||
(document.models.Find._meta.db_table, 'value'), # Document Find
|
||||
):
|
||||
cursor = connection.cursor()
|
||||
indexes = connection.introspection.get_indexes(cursor, table)
|
||||
|
|
|
@ -52,7 +52,7 @@ class Command(BaseCommand):
|
|||
changes.append(sql)
|
||||
rebuild = True
|
||||
elif f.__class__.__name__ != db_types[name]:
|
||||
sql = 'ALTER TABLE "%s" DROP COLUMN "%s"' % (table_name, name )
|
||||
sql = 'ALTER TABLE "%s" DROP COLUMN "%s"' % (table_name, name)
|
||||
changes.append(sql)
|
||||
sql = 'ALTER TABLE "%s" ADD COLUMN "%s" %s' % (table_name, name, col_type)
|
||||
changes.append(sql)
|
||||
|
@ -116,3 +116,6 @@ class Command(BaseCommand):
|
|||
if options['debug']:
|
||||
print(i)
|
||||
i.update_sort()
|
||||
# and udpate doucments
|
||||
import document.sync_sort
|
||||
document.sync_sort.update_tables(options['debug'])
|
||||
|
|
|
@ -24,6 +24,7 @@ from django.utils import datetime_safe
|
|||
|
||||
import ox
|
||||
from oxdjango import fields
|
||||
from oxdjango.sortmodel import get_sort_field
|
||||
import ox.web.imdb
|
||||
import ox.image
|
||||
|
||||
|
@ -1741,27 +1742,9 @@ for key in filter(lambda k: k.get('sort', False) or k['type'] in ('integer', 'ti
|
|||
sort_type = key.get('sortType', key['type'])
|
||||
if isinstance(sort_type, list):
|
||||
sort_type = sort_type[0]
|
||||
model = {
|
||||
'char': (models.CharField, dict(null=True, max_length=1000, db_index=True)),
|
||||
'year': (models.CharField, dict(null=True, max_length=4, db_index=True)),
|
||||
'integer': (models.BigIntegerField, dict(null=True, blank=True, db_index=True)),
|
||||
'float': (models.FloatField, dict(null=True, blank=True, db_index=True)),
|
||||
'date': (models.DateTimeField, dict(null=True, blank=True, db_index=True))
|
||||
}[{
|
||||
'layer': 'char',
|
||||
'string': 'char',
|
||||
'title': 'char',
|
||||
'person': 'char',
|
||||
'year': 'year',
|
||||
'words': 'integer',
|
||||
'length': 'integer',
|
||||
'date': 'date',
|
||||
'hue': 'float',
|
||||
'time': 'integer',
|
||||
'enum': 'integer',
|
||||
}.get(sort_type, sort_type)]
|
||||
field = get_sort_field(sort_type)
|
||||
if name not in attrs:
|
||||
attrs[name] = model[0](**model[1])
|
||||
attrs[name] = field[0](**field[1])
|
||||
|
||||
ItemSort = type('ItemSort', (models.Model,), attrs)
|
||||
ItemSort.fields = [f.name for f in ItemSort._meta.fields]
|
||||
|
|
|
@ -326,7 +326,7 @@ def autocomplete(request, data):
|
|||
returns {
|
||||
items: [string, ...] // list of matching strings
|
||||
}
|
||||
see: autocompleteEntities
|
||||
see: autocompleteDocuments, autocompleteEntities
|
||||
'''
|
||||
if 'range' not in data:
|
||||
data['range'] = [0, 10]
|
||||
|
|
25
pandora/oxdjango/sortmodel.py
Normal file
25
pandora/oxdjango/sortmodel.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from django.db import models
|
||||
|
||||
FIELDS = {
|
||||
'char': (models.CharField, dict(null=True, max_length=1000, db_index=True)),
|
||||
'year': (models.CharField, dict(null=True, max_length=4, db_index=True)),
|
||||
'integer': (models.BigIntegerField, dict(null=True, blank=True, db_index=True)),
|
||||
'float': (models.FloatField, dict(null=True, blank=True, db_index=True)),
|
||||
'date': (models.DateTimeField, dict(null=True, blank=True, db_index=True))
|
||||
}
|
||||
|
||||
def get_sort_field(sort_type):
|
||||
return FIELDS[{
|
||||
'layer': 'char',
|
||||
'string': 'char',
|
||||
'text': 'char',
|
||||
'title': 'char',
|
||||
'person': 'char',
|
||||
'year': 'year',
|
||||
'words': 'integer',
|
||||
'length': 'integer',
|
||||
'date': 'date',
|
||||
'hue': 'float',
|
||||
'time': 'integer',
|
||||
'enum': 'integer',
|
||||
}.get(sort_type, sort_type)]
|
|
@ -138,6 +138,7 @@ INSTALLED_APPS = (
|
|||
'user',
|
||||
'urlalias',
|
||||
'tv',
|
||||
'documentcollection',
|
||||
'document',
|
||||
'entity',
|
||||
'websocket',
|
||||
|
|
|
@ -19,6 +19,7 @@ import oxdjango.api.urls
|
|||
import app.views
|
||||
import archive.views
|
||||
import document.views
|
||||
import documentcollection.views
|
||||
import text.views
|
||||
import user.views
|
||||
import edit.views
|
||||
|
@ -42,6 +43,7 @@ urlpatterns = [
|
|||
url(r'^file/(?P<oshash>.*)$', archive.views.lookup_file),
|
||||
url(r'^api/?', include(oxdjango.api.urls)),
|
||||
url(r'^resetUI$', user.views.reset_ui),
|
||||
url(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
|
||||
url(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
|
||||
url(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3})$', document.views.file),
|
||||
url(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-10-08 12:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0002_auto_20160219_1734'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sessiondata',
|
||||
name='numberofcollections',
|
||||
field=models.IntegerField(default=0, null=True),
|
||||
),
|
||||
]
|
|
@ -18,6 +18,7 @@ from ox.utils import json
|
|||
from itemlist.models import List, Position
|
||||
import text
|
||||
import edit
|
||||
import documentcollection.models
|
||||
|
||||
from . import managers
|
||||
from . import tasks
|
||||
|
@ -44,6 +45,7 @@ class SessionData(models.Model):
|
|||
browser = models.CharField(max_length=255, null=True)
|
||||
|
||||
numberoflists = models.IntegerField(default=0, null=True)
|
||||
numberofcollections = models.IntegerField(default=0, null=True)
|
||||
|
||||
objects = managers.SessionDataManager()
|
||||
|
||||
|
@ -96,6 +98,7 @@ class SessionData(models.Model):
|
|||
else:
|
||||
self.groupssort = None
|
||||
self.numberoflists = self.user.lists.count()
|
||||
self.numberofcollections = self.user.collections.count()
|
||||
else:
|
||||
self.groupssort = None
|
||||
super(SessionData, self).save(*args, **kwargs)
|
||||
|
@ -157,6 +160,7 @@ class SessionData(models.Model):
|
|||
'newsletter': False,
|
||||
'notes': '',
|
||||
'numberoflists': 0,
|
||||
'numberofcollections': 0,
|
||||
'screensize': self.screensize,
|
||||
'system': ua['system']['string'],
|
||||
'timesseen': self.timesseen,
|
||||
|
@ -173,6 +177,7 @@ class SessionData(models.Model):
|
|||
j['newsletter'] = p.newsletter
|
||||
j['notes'] = p.notes
|
||||
j['numberoflists'] = self.numberoflists
|
||||
j['numberofcollections'] = self.numberofcollections
|
||||
if keys:
|
||||
for key in list(j):
|
||||
if key not in keys:
|
||||
|
@ -249,9 +254,12 @@ def get_ui(user_ui, user=None):
|
|||
ui[key] = new[key]
|
||||
return ui
|
||||
ui = update_ui(ui, user_ui)
|
||||
if not 'lists' in ui:
|
||||
if 'lists' not in ui:
|
||||
ui['lists'] = {}
|
||||
|
||||
if 'collections' not in ui:
|
||||
ui['collections'] = {}
|
||||
|
||||
def add(lists, section):
|
||||
ids = []
|
||||
for l in lists:
|
||||
|
@ -279,6 +287,29 @@ def get_ui(user_ui, user=None):
|
|||
ids.append(id)
|
||||
return ids
|
||||
|
||||
def add_collections(collections, section):
|
||||
Position = documentcollection.models.Position
|
||||
ids = []
|
||||
for l in collections:
|
||||
qs = Position.objects.filter(section=section)
|
||||
if section == 'featured':
|
||||
try:
|
||||
pos = Position.objects.get(collection=l, section=section)
|
||||
created = False
|
||||
except Position.DoesNotExist:
|
||||
pos = Position(collection=l, section=section, user=l.user)
|
||||
pos.save()
|
||||
created = True
|
||||
else:
|
||||
pos, created = Position.objects.get_or_create(collection=l, user=user, section=section)
|
||||
qs = qs.filter(user=user)
|
||||
if created:
|
||||
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
|
||||
pos.save()
|
||||
id = l.get_id()
|
||||
ids.append(id)
|
||||
return ids
|
||||
|
||||
def add_texts(texts, section):
|
||||
P = text.models.Position
|
||||
ids = []
|
||||
|
@ -323,6 +354,7 @@ def get_ui(user_ui, user=None):
|
|||
ids.append(t.get_id())
|
||||
return ids
|
||||
|
||||
# lists (items)
|
||||
ids = ['']
|
||||
if user:
|
||||
ids += add(user.lists.exclude(status="featured"), 'personal')
|
||||
|
@ -331,11 +363,26 @@ def get_ui(user_ui, user=None):
|
|||
for i in list(ui['lists']):
|
||||
if i not in ids:
|
||||
del ui['lists'][i]
|
||||
|
||||
# collections (documents)
|
||||
ids = ['']
|
||||
if user:
|
||||
ids += add_collections(user.collections.exclude(status="featured"), 'personal')
|
||||
ids += add_collections(user.subscribed_collections.filter(status='public'), 'public')
|
||||
ids += add_collections(documentcollection.models.Collection.objects.filter(status='featured'), 'featured')
|
||||
for i in list(ui['collections']):
|
||||
if i not in ids:
|
||||
del ui['collections'][i]
|
||||
|
||||
# texts (remove)
|
||||
tids = ['']
|
||||
if user:
|
||||
tids += add_texts(user.texts.exclude(status="featured"), 'personal')
|
||||
tids += add_texts(user.subscribed_texts.filter(status='public'), 'public')
|
||||
tids += add_texts(text.models.Text.objects.filter(status='featured'), 'featured')
|
||||
|
||||
# edits
|
||||
tids = ['']
|
||||
if user:
|
||||
tids += add_edits(user.edits.exclude(status="featured"), 'personal')
|
||||
tids += add_edits(user.subscribed_edits.filter(status='public'), 'public')
|
||||
|
|
|
@ -46,3 +46,13 @@ def update_numberoflists(username):
|
|||
).update(
|
||||
numberoflists=user.lists.count()
|
||||
)
|
||||
|
||||
@task(ignore_results=True, queue='default')
|
||||
def update_numberofcollections(username):
|
||||
from . import models
|
||||
user = models.User.objects.get(username=username)
|
||||
models.SessionData.objects.filter(
|
||||
user=user
|
||||
).update(
|
||||
numberofcollections=user.collections.count()
|
||||
)
|
||||
|
|
|
@ -800,7 +800,10 @@ def setUI(request, data):
|
|||
access, created = Access.objects.get_or_create(item=item, user=None)
|
||||
if not created:
|
||||
access.save()
|
||||
|
||||
if data.get('document'):
|
||||
import document.models
|
||||
doc = get_object_or_404_json(document.models.Document, id=ox.fromAZ(data['document']))
|
||||
doc.update_access(request.user)
|
||||
response = json_response()
|
||||
return render_to_json_response(response)
|
||||
actions.register(setUI, cache=False)
|
||||
|
|
|
@ -28,6 +28,12 @@ pandora.UI = (function() {
|
|||
pandora.user.ui._findState = pandora.getFindState(
|
||||
pandora.user.ui.find
|
||||
);
|
||||
pandora.user.ui._collection = pandora.getCollectionState(
|
||||
pandora.user.ui.findDocuments
|
||||
);
|
||||
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(
|
||||
pandora.user.ui.findDocuments
|
||||
);
|
||||
Ox.Theme(pandora.user.ui.theme);
|
||||
pandora.$ui.appPanel.reload();
|
||||
});
|
||||
|
@ -40,6 +46,9 @@ pandora.UI = (function() {
|
|||
|
||||
var add = {},
|
||||
args,
|
||||
collection,
|
||||
collectionView,
|
||||
collectionSettings = pandora.site.collectionSettings,
|
||||
editSettings = pandora.site.editSettings,
|
||||
item,
|
||||
list,
|
||||
|
@ -69,6 +78,76 @@ pandora.UI = (function() {
|
|||
} else if (args.section == 'edits') {
|
||||
trigger.section = args.section;
|
||||
trigger.edit = args.edit;
|
||||
} else if (pandora.user.ui.section == 'documents' || args.section == 'documents') {
|
||||
if ('findDocuments' in args) {
|
||||
// the challenge here is that find may change list,
|
||||
// and list may then change listSort and listView,
|
||||
// which we don't want to trigger, since find triggers
|
||||
// (values we put in add will be changed, but won't trigger)
|
||||
collection = pandora.getCollectionState(args.findDocuments);
|
||||
pandora.user.ui._collection = collection;
|
||||
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(args.findDocuments);
|
||||
if (pandora.$ui.appPanel && !pandora.stayInItemView) {
|
||||
// if we're not on page load, and if find isn't a context change
|
||||
// caused by an edit, then switch from item view to list view
|
||||
args['document'] = '';
|
||||
}
|
||||
if (collection != self.previousUI._collection) {
|
||||
Ox.Log('UI', 'FIND HAS CHANGED COLLECTION', self.previousUI._collection, '>', collection);
|
||||
// if find has changed collection
|
||||
Ox.forEach(collectionSettings, function(collectionSetting, setting) {
|
||||
// then for each setting that corresponds to a collection setting
|
||||
if (!Ox.isUndefined(args[setting])) {
|
||||
add[setting] = args[setting];
|
||||
} else if (
|
||||
!pandora.user.ui.collections[collection]
|
||||
|| Ox.isUndefined(pandora.user.ui.collections[collection][collectionSetting])
|
||||
) {
|
||||
// either add the default setting
|
||||
add[setting] = pandora.site.user.ui[setting];
|
||||
} else {
|
||||
// or the existing collection setting
|
||||
add[setting] = pandora.user.ui.collections[collection][collectionSetting];
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
collection = self.previousUI._collection;
|
||||
}
|
||||
if (!pandora.user.ui.collections[collection]) {
|
||||
add['collections.' + that.encode(collection)] = {};
|
||||
}
|
||||
Ox.forEach(collectionSettings, function(collectionSetting, setting) {
|
||||
// for each setting that corresponds to a collection setting
|
||||
// set that collection setting to
|
||||
var key = 'collections.' + that.encode(collection) + '.' + collectionSetting;
|
||||
if (setting in args) {
|
||||
// the setting passed to UI.set
|
||||
add[key] = args[setting];
|
||||
} else if (setting in add) {
|
||||
// or the setting changed via find
|
||||
add[key] = add[setting];
|
||||
} else if (!pandora.user.ui.collections[collection]) {
|
||||
// or the default setting
|
||||
add[key] = pandora.site.user.ui[setting];
|
||||
}
|
||||
});
|
||||
// set nested lisColumnWidth updates
|
||||
Ox.forEach(args, function(value, key) {
|
||||
if (Ox.startsWith(key, 'collectionColumnWidth.')) {
|
||||
key = 'collections.' + that.encode(collection) + '.columnWidth.'
|
||||
+ key.slice('collectionColumnWidth.'.length);
|
||||
if (!(key in add)) {
|
||||
add[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (args.document) {
|
||||
// when switching to an item, update list selection
|
||||
add['collectionSelection'] = [args.document];
|
||||
add['collections.' + that.encode(collection) + '.selection'] = [args.document];
|
||||
}
|
||||
} else {
|
||||
if ('find' in args) {
|
||||
// the challenge here is that find may change list,
|
||||
|
|
|
@ -20,6 +20,7 @@ pandora.URL = (function() {
|
|||
Ox.contains(Object.keys(pandora.site.user.ui.part), state.page)
|
||||
) {
|
||||
state.part = pandora.user.ui.part[state.page];
|
||||
/*
|
||||
if (
|
||||
state.page == 'documents'
|
||||
&& pandora.user.ui.documents[state.part]
|
||||
|
@ -27,6 +28,7 @@ pandora.URL = (function() {
|
|||
) {
|
||||
state.span = pandora.user.ui.documents[state.part].position;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -65,6 +67,25 @@ pandora.URL = (function() {
|
|||
: []
|
||||
);
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
if (!pandora.user.ui.document) {
|
||||
state.view = pandora.user.ui.collectionView;
|
||||
state.sort = [pandora.user.ui.collectionSort[0]];
|
||||
state.find = pandora.user.ui.findDocuments;
|
||||
} else {
|
||||
var documentState = pandora.user.ui.documents[state.item] || {},
|
||||
position = documentState.position || 0;
|
||||
if (pandora.user.ui.documentView == 'view') {
|
||||
if (documentState.name) {
|
||||
state.span = documentState.name;
|
||||
} else if (position) {
|
||||
state.span = [position];
|
||||
}
|
||||
} else {
|
||||
state.view = pandora.user.ui.documentView;
|
||||
}
|
||||
state.sort = [pandora.user.ui.collectionSort[0]];
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'edits') {
|
||||
var editPoints = pandora.user.ui.edits[state.item] || {};
|
||||
if (state.item) {
|
||||
|
@ -118,10 +139,15 @@ pandora.URL = (function() {
|
|||
var set = {};
|
||||
|
||||
Ox.Log('URL', 'setState:', state);
|
||||
if (state.type == 'texts') {
|
||||
state.type = 'documents';
|
||||
}
|
||||
|
||||
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
|
||||
pandora.user.ui._filterState = pandora.getFilterState(pandora.user.ui.find);
|
||||
pandora.user.ui._findState = pandora.getFindState(pandora.user.ui.find);
|
||||
pandora.user.ui._collection = pandora.getCollectionState(pandora.user.ui.findDocuments);
|
||||
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(pandora.user.ui.findDocuments);
|
||||
|
||||
if (Ox.isEmpty(state)) {
|
||||
|
||||
|
@ -148,9 +174,6 @@ pandora.URL = (function() {
|
|||
) && state.part) {
|
||||
set['part.' + state.page] = state.part;
|
||||
}
|
||||
if (state.span) {
|
||||
set['documents.' + state.part] = {position: state.span};
|
||||
}
|
||||
pandora.UI.set(set);
|
||||
callback && callback();
|
||||
|
||||
|
@ -222,7 +245,16 @@ pandora.URL = (function() {
|
|||
set.find = pandora.site.user.ui.find;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state.type == 'documents') {
|
||||
if (state.view) {
|
||||
set[!state.item ? 'collectionView' : 'documentView'] = state.view;
|
||||
}
|
||||
if (state.span) {
|
||||
set['documents.' + state.item] = {position: state.span};
|
||||
}
|
||||
if (!state.item && state.find) {
|
||||
set.findDocuments = state.find;
|
||||
}
|
||||
} else if (state.type == 'edits') {
|
||||
|
||||
if (state.view) {
|
||||
|
@ -248,16 +280,6 @@ pandora.URL = (function() {
|
|||
set[key + '.clip'] = state.span;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state.type == 'texts') {
|
||||
|
||||
if (state.span) {
|
||||
set['texts.' + pandora.UI.encode(state.item)] = {
|
||||
position: Ox.isArray(state.span) ? state.span[0] : 0,
|
||||
name: Ox.isArray(state.span) ? '' : state.span
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ox.Request.cancel();
|
||||
|
@ -368,6 +390,19 @@ pandora.URL = (function() {
|
|||
.concat(pandora.site.itemKeys);
|
||||
});
|
||||
|
||||
// Documents
|
||||
views['documents'] = {
|
||||
list: ['grid', 'list'],
|
||||
item: ['view', 'info']
|
||||
};
|
||||
sortKeys['documents'] = {
|
||||
list: {
|
||||
list: pandora.site.documentKeys,
|
||||
grid: pandora.site.documentKeys
|
||||
},
|
||||
item: {}
|
||||
};
|
||||
|
||||
// Texts
|
||||
views['texts'] = {
|
||||
list: [],
|
||||
|
@ -377,7 +412,6 @@ pandora.URL = (function() {
|
|||
list: {},
|
||||
item: {}
|
||||
};
|
||||
|
||||
return {
|
||||
views: views,
|
||||
sortKeys: sortKeys
|
||||
|
@ -388,7 +422,7 @@ pandora.URL = (function() {
|
|||
that.init = function() {
|
||||
|
||||
var itemsSection = pandora.site.itemsSection,
|
||||
findKeys, spanType = {};
|
||||
findKeys = {}, spanType = {};
|
||||
|
||||
spanType[itemsSection] = {
|
||||
list: {
|
||||
|
@ -411,12 +445,25 @@ pandora.URL = (function() {
|
|||
annotations: 'duration'
|
||||
}
|
||||
};
|
||||
spanType['documents'] = {
|
||||
list: {},
|
||||
item: {
|
||||
view: 'string',
|
||||
}
|
||||
};
|
||||
spanType['texts'] = {
|
||||
list: {},
|
||||
item: {text: 'string'}
|
||||
};
|
||||
|
||||
findKeys = [{id: 'list', type: 'string'}].concat(pandora.site.itemKeys);
|
||||
findKeys[itemsSection] = [
|
||||
{id: 'list', type: 'string'},
|
||||
].concat(pandora.site.itemKeys);
|
||||
|
||||
findKeys['edits'] = [];
|
||||
findKeys['documents'] = [
|
||||
{id: 'collection', type: 'string'}
|
||||
].concat(pandora.site.documentKeys);
|
||||
|
||||
self.URL = Ox.URL(Ox.extend({
|
||||
findKeys: findKeys,
|
||||
|
@ -426,14 +473,14 @@ pandora.URL = (function() {
|
|||
getSort: pandora.getSort,
|
||||
getSpan: pandora.getSpan,
|
||||
pages: [].concat(
|
||||
['home', 'software', 'api', 'help', 'tv', 'documents', 'entities'],
|
||||
['home', 'software', 'api', 'help', 'tv', 'entities'],
|
||||
pandora.site.sitePages.map(function(page) {
|
||||
return page.id;
|
||||
}),
|
||||
['preferences', 'signup', 'signin', 'signout']
|
||||
),
|
||||
spanType: spanType,
|
||||
types: [pandora.site.itemName.plural.toLowerCase(), 'edits', 'texts'],
|
||||
types: [pandora.site.itemName.plural.toLowerCase(), 'edits', 'documents', 'texts'],
|
||||
}, getOptions()));
|
||||
|
||||
window.addEventListener('hashchange', function() {
|
||||
|
@ -508,7 +555,7 @@ pandora.URL = (function() {
|
|||
that.push = function(stateOrURL, expandURL) {
|
||||
var state,
|
||||
title = pandora.getPageTitle(stateOrURL)
|
||||
|| pandora.getDocumentTitle(),
|
||||
|| pandora.getWindowTitle(),
|
||||
url;
|
||||
pandora.replaceURL = expandURL;
|
||||
if (Ox.isObject(stateOrURL)) {
|
||||
|
@ -524,7 +571,7 @@ pandora.URL = (function() {
|
|||
that.replace = function(stateOrURL, title) {
|
||||
var state,
|
||||
title = pandora.getPageTitle(stateOrURL)
|
||||
|| pandora.getDocumentTitle(),
|
||||
|| pandora.getWindowTitle(),
|
||||
url;
|
||||
if (Ox.isObject(stateOrURL)) {
|
||||
state = stateOrURL;
|
||||
|
@ -576,7 +623,7 @@ pandora.URL = (function() {
|
|||
state = getState();
|
||||
self.URL[action](
|
||||
state,
|
||||
pandora.getPageTitle(state) || pandora.getDocumentTitle()
|
||||
pandora.getPageTitle(state) || pandora.getWindowTitle()
|
||||
);
|
||||
pandora.replaceURL = false;
|
||||
}
|
||||
|
|
109
static/js/addDocumentDialog.js
Normal file
109
static/js/addDocumentDialog.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
'use strict';
|
||||
|
||||
pandora.ui.addDocumentDialog = function(options) {
|
||||
options = options || {};
|
||||
|
||||
var input = '';
|
||||
|
||||
var selected = options.selected ? options.selected : 'upload';
|
||||
|
||||
var $button;
|
||||
|
||||
var $panel = Ox.TabPanel({
|
||||
content: function(id) {
|
||||
var $content = Ox.Element().css({padding: '8px'});
|
||||
var $input = Ox.Input({
|
||||
changeOnKeypress: true,
|
||||
disabled: id == 'upload',
|
||||
label: Ox._(id == 'add' ? 'Title' : id == 'upload' ? 'File': 'URL'),
|
||||
labelWidth: 64,
|
||||
placeholder: '',
|
||||
width: 512
|
||||
}).css({
|
||||
margin: '8px'
|
||||
}).bindEvent({
|
||||
change: function(data) {
|
||||
$button.options({disabled: !data.value});
|
||||
input = data.value;
|
||||
}
|
||||
}).appendTo($content);
|
||||
return $content;
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
id: 'add',
|
||||
title: Ox._('Add {0}', [Ox._('Document')]),
|
||||
disabled: false,
|
||||
selected: selected == 'add'
|
||||
},
|
||||
{
|
||||
id: 'upload',
|
||||
title: Ox._('Upload Documents'),
|
||||
selected: selected == 'upload'
|
||||
}
|
||||
]
|
||||
}).bindEvent({
|
||||
change: function(data) {
|
||||
selected = data.selected;
|
||||
that.options({buttons: [createButton()]});
|
||||
}
|
||||
});
|
||||
|
||||
var $screen = Ox.LoadingScreen({
|
||||
size: 16
|
||||
});
|
||||
|
||||
var that = Ox.Dialog({
|
||||
buttons: [createButton()],
|
||||
closeButton: true,
|
||||
content: $panel,
|
||||
height: 72,
|
||||
removeOnClose: true,
|
||||
title: Ox._('Add {0}', [Ox._('Document')]),
|
||||
width: 544
|
||||
});
|
||||
|
||||
function createButton() {
|
||||
$button = Ox[selected == 'upload' ? 'FileButton' : 'Button']({
|
||||
disabled: selected != 'upload',
|
||||
id: selected,
|
||||
title: selected == 'add'
|
||||
? Ox._('Add {0}', ['Document'])
|
||||
: Ox._('Select Documents'),
|
||||
width: selected == 'add' ? 192 : 128
|
||||
}).bindEvent({
|
||||
click: function(data) {
|
||||
if (selected == 'add') {
|
||||
that.options({content: $screen.start()});
|
||||
$button.options({disabled: true});
|
||||
pandora.api.addDocument({title: input}, function(result) {
|
||||
Ox.Request.clearCache('find');
|
||||
$screen.stop();
|
||||
that.close();
|
||||
pandora.UI.set({
|
||||
document: result.data.id
|
||||
});
|
||||
});
|
||||
} else if (selected == 'upload' && data.files.length > 0) {
|
||||
that.close();
|
||||
pandora.ui.uploadDocumentDialog({
|
||||
files: data.files
|
||||
}, function(files) {
|
||||
if (files) {
|
||||
Ox.Request.clearCache('findDocuments');
|
||||
if (pandora.user.ui.document) {
|
||||
pandora.UI.set({document: ''});
|
||||
} else {
|
||||
pandora.$ui.list && pandora.$ui.list.reloadList();
|
||||
}
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
});
|
||||
return $button;
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
|
@ -8,8 +8,10 @@ pandora.ui.allItems = function(section) {
|
|||
|
||||
var canAddItems = !pandora.site.itemRequiresVideo && pandora.site.capabilities.canAddItems[pandora.user.level],
|
||||
canUploadVideo = pandora.site.capabilities.canAddItems[pandora.user.level],
|
||||
canAddDocuments = pandora.site.capabilities.canAddDocuments[pandora.user.level],
|
||||
isSelected = pandora.user.ui._list || pandora.user.ui._collection,
|
||||
that = Ox.Element()
|
||||
.addClass('OxSelectableElement' + (pandora.user.ui._list ? '' : ' OxSelected'))
|
||||
.addClass('OxSelectableElement' + (isSelected ? '' : ' OxSelected'))
|
||||
.css({
|
||||
height: '16px',
|
||||
cursor: 'default',
|
||||
|
@ -19,13 +21,21 @@ pandora.ui.allItems = function(section) {
|
|||
click: function() {
|
||||
that.gainFocus();
|
||||
if (section == 'items') {
|
||||
pandora.user.ui._list && pandora.UI.set({find: {conditions: [], operator: '&'}});
|
||||
pandora.user.ui._list && pandora.UI.set({
|
||||
find: {conditions: [], operator: '&'}
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
pandora.user.ui._collection && pandora.UI.set({
|
||||
findDocuments: {conditions: [], operator: '&'}
|
||||
});
|
||||
} else {
|
||||
pandora.UI.set(section.slice(0, -1), '');
|
||||
}
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
pandora_document: updateSelected,
|
||||
pandora_finddocuments: updateSelected,
|
||||
pandora_edit: updateSelected,
|
||||
pandora_find: updateSelected,
|
||||
pandora_section: updateSelected,
|
||||
|
@ -95,6 +105,56 @@ pandora.ui.allItems = function(section) {
|
|||
}, function(result) {
|
||||
that.update(result.data.items);
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
$items = $('<div>')
|
||||
.css({
|
||||
float: 'left',
|
||||
width: '42px',
|
||||
margin: '1px 4px 1px 3px',
|
||||
textAlign: 'right'
|
||||
})
|
||||
.appendTo(that);
|
||||
$buttons[0] = Ox.Button({
|
||||
style: 'symbol',
|
||||
title: 'add',
|
||||
tooltip: canAddDocuments ? Ox._('Add {0}', [Ox._('Document')]) : '',
|
||||
type: 'image'
|
||||
})
|
||||
.css({opacity: canAddDocuments ? 1 : 0.25})
|
||||
.hide()
|
||||
.bindEvent({
|
||||
click: function(data) {
|
||||
if (canAddDocuments) {
|
||||
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog({
|
||||
selected: 'add'
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
$buttons[1] = Ox.Button({
|
||||
style: 'symbol',
|
||||
title: 'upload',
|
||||
tooltip: canAddDocuments ? Ox._('Upload {0}', [Ox._('Document')]) : '',
|
||||
type: 'image'
|
||||
})
|
||||
.css({opacity: canAddDocuments ? 1 : 0.25})
|
||||
.hide()
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
if (canAddDocuments) {
|
||||
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog({
|
||||
selected: 'upload'
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
pandora.api.findDocuments({
|
||||
query: {conditions: [], operator: '&'}
|
||||
}, function(result) {
|
||||
that.update(result.data.items);
|
||||
});
|
||||
} else if (section == 'texts') {
|
||||
$buttons[0] = Ox.Button({
|
||||
style: 'symbol',
|
||||
|
@ -124,6 +184,7 @@ pandora.ui.allItems = function(section) {
|
|||
function updateSelected() {
|
||||
that[
|
||||
(section == 'items' && pandora.user.ui._list)
|
||||
|| (section == 'documents' && pandora.user.ui._collection)
|
||||
|| (section == 'edits' && pandora.user.ui.edit)
|
||||
|| (section == 'texts' && pandora.user.ui.text)
|
||||
? 'removeClass' : 'addClass'
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
'use strict';
|
||||
pandora.ui.backButton = function() {
|
||||
var that = Ox.Button({
|
||||
title: Ox._('Back to {0}', [Ox._(pandora.site.itemName.plural)]),
|
||||
width: 96
|
||||
title: Ox._('Back to {0}', [
|
||||
pandora.user.ui.section == 'items'
|
||||
? Ox._(pandora.site.itemName.plural)
|
||||
: Ox._(Ox.toTitleCase(pandora.user.ui.section))
|
||||
]),
|
||||
width: pandora.user.ui.section == 'documents' ? 124 : 96
|
||||
}).css({
|
||||
float: 'left',
|
||||
margin: '4px 0 0 4px'
|
||||
|
@ -21,8 +25,12 @@ pandora.ui.backButton = function() {
|
|||
if (['accessed', 'timesaccessed'].indexOf(pandora.user.ui.listSort[0].key) > -1) {
|
||||
Ox.Request.clearCache('find');
|
||||
}
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
pandora.UI.set({document: ''});
|
||||
} else {
|
||||
pandora.UI.set({item: ''});
|
||||
}
|
||||
}
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
|
289
static/js/collection.js
Normal file
289
static/js/collection.js
Normal file
|
@ -0,0 +1,289 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
'use strict';
|
||||
|
||||
|
||||
pandora.ui.collection = function() {
|
||||
|
||||
var that,
|
||||
ui = pandora.user.ui,
|
||||
view = ui.collectionView,
|
||||
keys = [
|
||||
'description', 'dimensions', 'extension', 'id', 'title', 'ratio', 'size', 'user', 'entities', 'modified',
|
||||
'editable'
|
||||
];
|
||||
|
||||
if (view == 'list') {
|
||||
that = Ox.TableList({
|
||||
keys: keys,
|
||||
items: function(data, callback) {
|
||||
pandora.api.findDocuments(Ox.extend(data, {
|
||||
query: ui.findDocuments
|
||||
}), callback);
|
||||
return Ox.clone(data, true);
|
||||
},
|
||||
selected: ui.collectionSelection,
|
||||
sort: ui.collectionSort.concat([
|
||||
{key: 'extension', operator: '+'},
|
||||
{key: 'title', operator: '+'}
|
||||
]),
|
||||
unique: 'id',
|
||||
columns: pandora.site.documentSortKeys.filter(function(key) {
|
||||
return !key.capability
|
||||
|| pandora.site.capabilities[key.capability][pandora.user.level];
|
||||
}).map(function(key) {
|
||||
var position = ui.collectionColumns.indexOf(key.id);
|
||||
return {
|
||||
addable: key.id != 'random',
|
||||
align: ['string', 'text'].indexOf(
|
||||
Ox.isArray(key.type) ? key.type[0]: key.type
|
||||
) > -1 ? 'left' : key.type == 'list' ? 'center' : 'right',
|
||||
defaultWidth: key.columnWidth,
|
||||
format: (function() {
|
||||
return function(value, data) {
|
||||
return pandora.formatDocumentKey(key, data);
|
||||
}
|
||||
})(),
|
||||
id: key.id,
|
||||
operator: pandora.getDocumentSortOperator(key.id),
|
||||
position: position,
|
||||
removable: !key.columnRequired,
|
||||
title: Ox._(key.title),
|
||||
type: key.type,
|
||||
visible: position > -1,
|
||||
width: ui.collectionColumnWidth[key.id] || key.columnWidth
|
||||
};
|
||||
}),
|
||||
columnsVisible: true,
|
||||
scrollbarVisible: true,
|
||||
})
|
||||
} else if (view == 'grid') {
|
||||
that = Ox.IconList({
|
||||
borderRadius: 0,
|
||||
defaultRatio: 640/1024,
|
||||
draggable: true,
|
||||
id: 'list',
|
||||
item: function(data, sort, size) {
|
||||
var sortKey = sort[0].key,
|
||||
infoKey = sortKey == 'title' ? 'extension' : sortKey,
|
||||
key = Ox.getObjectById(pandora.site.documentKeys, infoKey),
|
||||
info = pandora.formatDocumentKey(key, data),
|
||||
size = size || 128;
|
||||
return {
|
||||
height: Math.round(data.ratio > 1 ? size / data.ratio : size),
|
||||
id: data.id,
|
||||
info: info,
|
||||
title: data.title,
|
||||
url: pandora.getMediaURL('/documents/' + data.id + '/256p.jpg?' + data.modified),
|
||||
width: Math.round(data.ratio > 1 ? size : size * data.ratio)
|
||||
};
|
||||
},
|
||||
items: function(data, callback) {
|
||||
pandora.api.findDocuments(Ox.extend(data, {
|
||||
query: ui.findDocuments
|
||||
}), callback);
|
||||
return Ox.clone(data, true);
|
||||
},
|
||||
keys: keys,
|
||||
selected: ui.collectionSelection,
|
||||
size: 128,
|
||||
sort: ui.collectionSort.concat([
|
||||
{key: 'extension', operator: '+'},
|
||||
{key: 'title', operator: '+'}
|
||||
]),
|
||||
unique: 'id'
|
||||
})
|
||||
.addClass('OxMedia');
|
||||
}
|
||||
|
||||
if (['list', 'grid'].indexOf(view) > -1) {
|
||||
// react to the resize event of the split panel
|
||||
that.bindEvent({
|
||||
resize: function(data) {
|
||||
that.size();
|
||||
},
|
||||
pandora_showbrowser: function(data) {
|
||||
that.size();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (['list', 'grid'].indexOf(view) > -1) {
|
||||
|
||||
//fixme
|
||||
|
||||
pandora.enableDragAndDrop(that, true);
|
||||
|
||||
that.bindEvent({
|
||||
closepreview: function(data) {
|
||||
pandora.$ui.previewDialog.close();
|
||||
delete pandora.$ui.previewDialog;
|
||||
},
|
||||
copy: function(data) {
|
||||
pandora.clipboard.copy(data.ids, 'document');
|
||||
},
|
||||
copyadd: function(data) {
|
||||
pandora.clipboard.add(data.ids, 'document');
|
||||
},
|
||||
cut: function(data) {
|
||||
var listData = pandora.getListData();
|
||||
if (listData.editable && listData.type == 'static') {
|
||||
pandora.clipboard.copy(data.ids, 'document');
|
||||
pandora.doHistory('cut', data.ids, ui._collection, function() {
|
||||
pandora.UI.set({collectionSelection: []});
|
||||
pandora.reloadList();
|
||||
});
|
||||
}
|
||||
},
|
||||
cutadd: function(data) {
|
||||
var listData = pandora.getListData();
|
||||
if (listData.editable && listData.type == 'static') {
|
||||
pandora.clipboard.add(data.ids, 'document');
|
||||
pandora.doHistory('cut', data.ids, ui._collection, function() {
|
||||
pandora.UI.set({collectionSelection: []});
|
||||
pandora.reloadList();
|
||||
});
|
||||
}
|
||||
},
|
||||
'delete': function(data) {
|
||||
var listData = pandora.getListData();
|
||||
if (listData.editable && listData.type == 'static') {
|
||||
//fixme use history
|
||||
//pandora.doHistory('delete', data.ids, ui._collection, function() {
|
||||
pandora.api.removeCollectionItems({
|
||||
collection: ui._collection,
|
||||
items: data.ids
|
||||
|
||||
}, function() {
|
||||
pandora.UI.set({collectionSelection: []});
|
||||
pandora.reloadList();
|
||||
});
|
||||
} else if (pandora.user.ui._collection == '' && data.ids.every(function(item) {
|
||||
return pandora.$ui.list.value(item, 'editable');
|
||||
})) {
|
||||
pandora.ui.deleteDocumentDialog(
|
||||
data.ids.map(function(id) {
|
||||
return pandora.$ui.list.value(id);
|
||||
}),
|
||||
function() {
|
||||
Ox.Request.clearCache();
|
||||
if (ui.document) {
|
||||
pandora.UI.set({document: ''});
|
||||
} else {
|
||||
pandora.$ui.list.reloadList()
|
||||
}
|
||||
}
|
||||
).open();
|
||||
}
|
||||
},
|
||||
init: function(data) {
|
||||
var folder, list;
|
||||
if (data.query.conditions.length == 0) {
|
||||
pandora.$ui.allItems.update(data.items);
|
||||
} else if (
|
||||
data.query.conditions.length == 1
|
||||
&& data.query.conditions[0].key == 'document'
|
||||
&& data.query.conditions[0].operator == '=='
|
||||
) {
|
||||
list = data.query.conditions[0].value;
|
||||
folder = pandora.getListData(list).folder;
|
||||
if (pandora.$ui.folderList[folder]
|
||||
&& !Ox.isEmpty(pandora.$ui.folderList[folder].value(list))) {
|
||||
pandora.$ui.folderList[folder].value(
|
||||
list, 'items', data.items
|
||||
);
|
||||
}
|
||||
}
|
||||
pandora.$ui.statusbar.set('total', data);
|
||||
data = [];
|
||||
pandora.site.totals.forEach(function(v) {
|
||||
data[v.id] = 0;
|
||||
});
|
||||
pandora.$ui.statusbar.set('selected', data);
|
||||
},
|
||||
open: function(data) {
|
||||
var set = {document: data.ids[0]};
|
||||
pandora.UI.set(set);
|
||||
},
|
||||
openpreview: function(data) {
|
||||
/*
|
||||
if (!pandora.$ui.previewDialog) {
|
||||
pandora.$ui.previewDialog = pandora.ui.previewDialog()
|
||||
.open()
|
||||
.bindEvent({
|
||||
close: function() {
|
||||
that.closePreview();
|
||||
delete pandora.$ui.previewDialog;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pandora.$ui.previewDialog.update();
|
||||
}
|
||||
*/
|
||||
},
|
||||
paste: function(data) {
|
||||
var items = pandora.clipboard.paste();
|
||||
if (items.length && pandora.clipboard.type() == 'document' && pandora.getListData().editable) {
|
||||
//fixme use history
|
||||
//pandora.doHistory('paste', items, ui._collection, function() {
|
||||
pandora.api.addCollectionItems({
|
||||
collection: ui._collection,
|
||||
items: items
|
||||
|
||||
}, function() {
|
||||
pandora.UI.set({collectionSelection: items});
|
||||
pandora.reloadList();
|
||||
});
|
||||
}
|
||||
},
|
||||
select: function(data) {
|
||||
var query;
|
||||
pandora.UI.set('collectionSelection', data.ids);
|
||||
if (data.ids.length == 0) {
|
||||
pandora.$ui.statusbar.set('selected', {items: 0});
|
||||
} else {
|
||||
if (Ox.isUndefined(data.rest)) {
|
||||
query = {
|
||||
conditions: data.ids.map(function(id) {
|
||||
return {
|
||||
key: 'id',
|
||||
value: id,
|
||||
operator: '=='
|
||||
}
|
||||
}),
|
||||
operator: '|'
|
||||
};
|
||||
} else {
|
||||
query = {
|
||||
conditions: [ui.find].concat(
|
||||
data.rest.map(function(id) {
|
||||
return {
|
||||
key: 'id',
|
||||
value: id,
|
||||
operator: '!='
|
||||
};
|
||||
})
|
||||
),
|
||||
operator: '&'
|
||||
};
|
||||
}
|
||||
pandora.api.find({
|
||||
query: query
|
||||
}, function(result) {
|
||||
pandora.$ui.statusbar.set('selected', result.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
pandora_collectionsort: function(data) {
|
||||
that.options({sort: data.value});
|
||||
},
|
||||
pandora_showdocument: function(data) {
|
||||
isItemView && that.toggleElement(1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
|
@ -31,7 +31,7 @@ pandora.ui.deleteDocumentDialog = function(files, callback) {
|
|||
})
|
||||
],
|
||||
content: files.length == 1
|
||||
? Ox._('Are you sure you want to delete the document "{0}"?', [files[0].name + '.' + files[0].extension])
|
||||
? Ox._('Are you sure you want to delete the document "{0}"?', [files[0].title + '.' + files[0].extension])
|
||||
: Ox._('Are you sure you want to delete {0} documents?', [files.length]),
|
||||
keys: {enter: 'delete', escape: 'keep'},
|
||||
title: files.length == 1
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
pandora.ui.deleteListDialog = function(list) {
|
||||
|
||||
var ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
listData = pandora.getListData(list),
|
||||
$folderList = pandora.$ui.folderList[listData.folder],
|
||||
|
@ -42,6 +42,14 @@ pandora.ui.deleteListDialog = function(list) {
|
|||
pandora.UI.set({
|
||||
find: pandora.site.user.ui.find
|
||||
});
|
||||
} else if (ui.section == 'documents') {
|
||||
pandora.UI.set(
|
||||
'collections.' + listData.id, null
|
||||
);
|
||||
pandora.UI.set({
|
||||
findDocuments: pandora.site.user.ui.findDocuments
|
||||
});
|
||||
|
||||
} else {
|
||||
pandora.UI.set(
|
||||
folderItem.toLowerCase(), ''
|
||||
|
|
282
static/js/document.js
Normal file
282
static/js/document.js
Normal file
|
@ -0,0 +1,282 @@
|
|||
|
||||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
'use strict';
|
||||
|
||||
pandora.ui.document = function() {
|
||||
var $toolbar = Ox.Bar({size: 16})
|
||||
.bindEvent({
|
||||
doubleclick: function(e) {
|
||||
if ($(e.target).is('.OxBar')) {
|
||||
pandora.$ui.text && pandora.$ui.text.animate({scrollTop:0}, 250);
|
||||
}
|
||||
}
|
||||
}),
|
||||
$content = Ox.Element(),
|
||||
that = Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: $toolbar,
|
||||
size: 16
|
||||
},
|
||||
{
|
||||
element: $content
|
||||
}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
})
|
||||
.bindEvent({
|
||||
pandora_showbrowser: function(data) {
|
||||
that.update();
|
||||
}
|
||||
}),
|
||||
item,
|
||||
$find,
|
||||
$nextButton,
|
||||
$currentButton,
|
||||
$previousButton;
|
||||
|
||||
pandora.api.getDocument({
|
||||
id: pandora.user.ui.document
|
||||
}, function(result) {
|
||||
if (pandora.user.ui.document != result.data.id) {
|
||||
return;
|
||||
}
|
||||
item = result.data;
|
||||
var documentTitle = pandora.getWindowTitle(item);
|
||||
document.title = pandora.getPageTitle(document.location.pathname) || documentTitle;
|
||||
pandora.$ui.itemTitle
|
||||
.options({title: '<b>' + (pandora.getDocumentTitle(item)) + '</b>'})
|
||||
.show();
|
||||
|
||||
if (pandora.user.ui.documentView == 'info') {
|
||||
$content.replaceWith(
|
||||
$content = pandora.ui.documentInfoView(result.data)
|
||||
);
|
||||
} else {
|
||||
setContent();
|
||||
}
|
||||
});
|
||||
|
||||
function setContent() {
|
||||
that.replaceElement(1, $content = (
|
||||
item.extension == 'pdf'
|
||||
? Ox.PDFViewer({
|
||||
height: that.height(),
|
||||
page: pandora.user.ui.documents[item.id]
|
||||
? pandora.user.ui.documents[item.id].position
|
||||
: 1,
|
||||
url: '/documents/' + item.id + '/'
|
||||
+ item.title + '.' + item.extension,
|
||||
width: that.width(),
|
||||
zoom: 'fit'
|
||||
})
|
||||
: item.extension == 'html'
|
||||
? pandora.$ui.textPanel = textPanel(item).css({
|
||||
})
|
||||
: Ox.ImageViewer({
|
||||
area: pandora.user.ui.documents[item.id]
|
||||
? pandora.user.ui.documents[item.id].position
|
||||
: [],
|
||||
height: that.height(),
|
||||
imageHeight: item.dimensions[1],
|
||||
imagePreviewURL: pandora.getMediaURL('/documents/' + item.id + '/256p.jpg?' + item.modified),
|
||||
imageURL: pandora.getMediaURL('/documents/' + item.id + '/'
|
||||
+ item.title + '.' + item.extension + '?' + item.modified),
|
||||
imageWidth: item.dimensions[0],
|
||||
width: that.width()
|
||||
}).css({
|
||||
//prevents image overflow on zoom, fixme: fix in Ox.ImageViewer
|
||||
position: 'absolute'
|
||||
})
|
||||
)
|
||||
.bindEvent({
|
||||
center: function(data) {
|
||||
pandora.UI.set(
|
||||
'documents.' + item.id,
|
||||
{position: $content.getArea().map(Math.round)}
|
||||
);
|
||||
},
|
||||
key_escape: function() {
|
||||
// ...
|
||||
},
|
||||
page: function(data) {
|
||||
pandora.UI.set(
|
||||
'documents.' + item.id,
|
||||
{position: data.page}
|
||||
);
|
||||
},
|
||||
zoom: function(data) {
|
||||
pandora.UI.set(
|
||||
'documents.' + item.id,
|
||||
{position: $content.getArea().map(Math.round)}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
if (item.extension == 'html') {
|
||||
that.css({
|
||||
'overflow-y': 'auto'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function textPanel(text) {
|
||||
var textElement,
|
||||
embedURLs = getEmbedURLs(text.text),
|
||||
that = Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: pandora.$ui.text = textElement = pandora.ui.textHTML(text)
|
||||
},
|
||||
{
|
||||
element: pandora.$ui.textEmbed = pandora.ui.textEmbed(),
|
||||
collapsed: !embedURLs.length,
|
||||
size: pandora.user.ui.embedSize,
|
||||
resizable: true,
|
||||
resize: [192, 256, 320, 384, 448, 512]
|
||||
}
|
||||
],
|
||||
orientation: 'horizontal'
|
||||
}),
|
||||
selected = -1,
|
||||
selectedURL;
|
||||
/*
|
||||
$find = Ox.Input({
|
||||
clear: true,
|
||||
placeholder: Ox._('Find in Texts'),
|
||||
value: pandora.user.ui.textFind,
|
||||
width: 188
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
})
|
||||
.bindEvent({
|
||||
submit: function(data) {
|
||||
Ox.print('SUBMIT', data);
|
||||
}
|
||||
})
|
||||
.appendTo($toolbar);
|
||||
*/
|
||||
$nextButton = Ox.Button({
|
||||
disabled: embedURLs.length < 2,
|
||||
title: 'arrowRight',
|
||||
tooltip: Ox._('Next Reference'),
|
||||
type: 'image'
|
||||
})
|
||||
.css({
|
||||
'margin-right': (pandora.user.ui.embedSize + Ox.SCROLLBAR_SIZE) + 'px',
|
||||
float: 'right',
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
that.selectEmbed(
|
||||
selected < embedURLs.length - 1 ? selected + 1 : 0,
|
||||
true
|
||||
);
|
||||
}
|
||||
})
|
||||
.appendTo($toolbar);
|
||||
|
||||
$currentButton = Ox.Button({
|
||||
disabled: embedURLs.length < 1,
|
||||
title: 'center',
|
||||
tooltip: Ox._('Current Reference'),
|
||||
type: 'image'
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
})
|
||||
.bindEvent({
|
||||
click: scrollToSelectedEmbed
|
||||
})
|
||||
.appendTo($toolbar);
|
||||
|
||||
$previousButton = Ox.Button({
|
||||
disabled: embedURLs.length < 2,
|
||||
title: 'arrowLeft',
|
||||
tooltip: Ox._('Previous Reference'),
|
||||
type: 'image'
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
that.selectEmbed(
|
||||
selected ? selected - 1 : embedURLs.length - 1,
|
||||
true
|
||||
);
|
||||
}
|
||||
})
|
||||
.appendTo($toolbar);
|
||||
|
||||
function getEmbedURLs(text) {
|
||||
var matches = text.match(/<a [^<>]*?href="(.+?)".*?>/gi),
|
||||
urls = [];
|
||||
if (matches) {
|
||||
matches.forEach(function(match) {
|
||||
var url = match.match(/"(.+?)"/)[1];
|
||||
if (pandora.isEmbedURL(url)) {
|
||||
urls.push(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
function scrollToSelectedEmbed() {
|
||||
var scrollTop = Math.max(
|
||||
textElement[0].scrollTop + $('#embed' + selected).offset().top - (
|
||||
pandora.user.ui.showBrowser
|
||||
? pandora.$ui.documentContentPanel.options().elements[0].size
|
||||
: 0
|
||||
) - 48,
|
||||
0),
|
||||
position = 100 * scrollTop / Math.max(1, textElement[0].scrollHeight);
|
||||
textElement.scrollTo(position);
|
||||
window.text = textElement;
|
||||
}
|
||||
|
||||
that.selectEmbed = function(index, scroll) {
|
||||
if (index != selected) {
|
||||
selected = index;
|
||||
selectedURL = embedURLs[selected]
|
||||
$('.OxSpecialLink').removeClass('OxActive');
|
||||
selected > -1 && $('#embed' + selected).addClass('OxActive');
|
||||
pandora.$ui.textEmbed.update(selectedURL);
|
||||
scroll && scrollToSelectedEmbed();
|
||||
}
|
||||
};
|
||||
|
||||
that.update = function(text) {
|
||||
var index;
|
||||
embedURLs = getEmbedURLs(text);
|
||||
index = embedURLs.indexOf(selectedURL);
|
||||
if (embedURLs.length && (index == -1 || index >= embedURLs.length)) {
|
||||
index = 0;
|
||||
}
|
||||
selected = -1;
|
||||
that.selectEmbed(index);
|
||||
};
|
||||
|
||||
embedURLs.length && that.selectEmbed(0);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.info = function() {
|
||||
return item;
|
||||
};
|
||||
|
||||
that.update = function() {
|
||||
$content.options({
|
||||
height: that.height(),
|
||||
width: that.width()
|
||||
});
|
||||
$nextButton && $nextButton.css({
|
||||
'margin-right': (pandora.user.ui.embedSize + Ox.SCROLLBAR_SIZE) + 'px',
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
94
static/js/documentBrowser.js
Normal file
94
static/js/documentBrowser.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
'use strict';
|
||||
pandora.ui.documentBrowser = function() {
|
||||
var that;
|
||||
if (!pandora.user.ui.document) {
|
||||
that = Ox.Element().html('fixme');
|
||||
} else {
|
||||
var that = Ox.IconList({
|
||||
borderRadius: 0,
|
||||
centered: true,
|
||||
defaultRatio: 640/1024,
|
||||
draggable: true,
|
||||
id: 'list',
|
||||
item: function(data, sort, size) {
|
||||
size = size || 64;
|
||||
var sortKey = sort[0].key,
|
||||
infoKey = sortKey == 'title' ? 'extension' : sortKey,
|
||||
key = Ox.getObjectById(pandora.site.documentKeys, infoKey),
|
||||
info = pandora.formatDocumentKey(key, data),
|
||||
size = size || 128;
|
||||
return {
|
||||
height: Math.round(data.ratio > 1 ? size / data.ratio : size),
|
||||
id: data.id,
|
||||
info: info,
|
||||
title: data.title,
|
||||
url: pandora.getMediaURL('/documents/' + data.id + '/256p.jpg?' + data.modified),
|
||||
width: Math.round(data.ratio > 1 ? size : size * data.ratio)
|
||||
};
|
||||
},
|
||||
items: function(data, callback) {
|
||||
pandora.api.findDocuments(Ox.extend(data, {
|
||||
query: pandora.user.ui.findDocuments
|
||||
}), callback);
|
||||
return Ox.clone(data, true);
|
||||
},
|
||||
keys: ['description', 'dimensions', 'extension', 'id', 'title', 'ratio', 'size', 'user', 'entities', 'modified'],
|
||||
max: 1,
|
||||
min: 1,
|
||||
orientation: 'horizontal',
|
||||
pageLength: 32,
|
||||
selected: [pandora.user.ui.document],
|
||||
size: 64,
|
||||
sort: getSort(),
|
||||
unique: 'id'
|
||||
})
|
||||
.addClass('OxMedia')
|
||||
.bindEvent({
|
||||
copy: function() {
|
||||
pandora.clipboard.copy(pandora.user.ui.item, 'document');
|
||||
},
|
||||
copyadd: function() {
|
||||
pandora.clipboard.add(pandora.user.ui.item, 'document');
|
||||
},
|
||||
gainfocus: function() {
|
||||
pandora.$ui.mainMenu.replaceItemMenu();
|
||||
},
|
||||
open: function() {
|
||||
that.scrollToSelection();
|
||||
},
|
||||
openpreview: function() {
|
||||
if (pandora.isVideoView()) {
|
||||
pandora.$ui[pandora.user.ui.itemView].gainFocus().triggerEvent('key_space');
|
||||
}
|
||||
},
|
||||
select: function(data) {
|
||||
data.ids.length && pandora.UI.set({
|
||||
'document': data.ids[0]
|
||||
});
|
||||
},
|
||||
toggle: function(data) {
|
||||
pandora.UI.set({showBrowser: !data.collapsed});
|
||||
}
|
||||
})
|
||||
.bindEventOnce({
|
||||
load: function() {
|
||||
// gain focus if we're on page load or if we've just switched
|
||||
// to an item and the not-yet-garbage-collected list still has
|
||||
// focus
|
||||
if (!Ox.Focus.focusedElement() || (
|
||||
pandora.$ui.list && pandora.$ui.list.hasFocus()
|
||||
)) {
|
||||
that.gainFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
that.css({overflowY: 'hidden'}); // this fixes a bug in firefox
|
||||
pandora.enableDragAndDrop(that, false);
|
||||
}
|
||||
function getSort() {
|
||||
return pandora.user.ui.collectionSort;
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
63
static/js/documentContentPanel.js
Normal file
63
static/js/documentContentPanel.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
'use strict';
|
||||
|
||||
pandora.ui.documentContentPanel = function() {
|
||||
var that = Ox.SplitPanel({
|
||||
elements: !pandora.user.ui.document ? [
|
||||
{
|
||||
collapsed: true,
|
||||
collapsible: false, //fixme
|
||||
element: pandora.$ui.documentBrowser = pandora.ui.documentBrowser(),
|
||||
resizable: false, //fixme
|
||||
resize: [96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256],
|
||||
size: 96,
|
||||
tooltip: '' /* fixme:
|
||||
Ox._('filters') + ' <span class="OxBright">'
|
||||
+ Ox.SYMBOLS.shift + 'F</span>'
|
||||
*/
|
||||
},
|
||||
{
|
||||
element: pandora.$ui.list = pandora.ui.collection()
|
||||
},
|
||||
{
|
||||
element: pandora.$ui.statusbar = pandora.ui.statusbar(),
|
||||
size: 16
|
||||
}
|
||||
] : [
|
||||
{
|
||||
collapsed: !pandora.user.ui.showBrowser,
|
||||
collapsible: true,
|
||||
element: pandora.$ui.documentBrowser = pandora.ui.documentBrowser(),
|
||||
size: 112 + Ox.UI.SCROLLBAR_SIZE,
|
||||
tooltip: Ox._('{0} browser', [Ox._('document')])
|
||||
+ ' <span class="OxBright">'
|
||||
+ Ox.SYMBOLS.shift + 'B</span>'
|
||||
},
|
||||
{
|
||||
element: pandora.$ui.document = pandora.ui.document()
|
||||
}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
})
|
||||
.bindEvent({
|
||||
resize: function(data) {
|
||||
Ox.print('split resize');
|
||||
},
|
||||
pandora_document: function(data) {
|
||||
if (data.value && data.previousValue) {
|
||||
that.replaceElement(1, pandora.$ui.document = pandora.ui.document());
|
||||
}
|
||||
},
|
||||
pandora_documentview: function(data) {
|
||||
that.replaceElement(1, pandora.$ui.document = pandora.ui.document());
|
||||
},
|
||||
pandora_collectionview: function() {
|
||||
!pandora.user.ui.document && that.replaceElement(1,
|
||||
pandora.$ui.list = pandora.ui.collection());
|
||||
},
|
||||
pandora_showbrowser: function(data) {
|
||||
data.value == that.options('elements')[0].collapsed && that.toggleElement(0);
|
||||
},
|
||||
});
|
||||
return that;
|
||||
};
|
|
@ -19,7 +19,7 @@ pandora.openDocumentDialog = function(options) {
|
|||
operator: '|'
|
||||
},
|
||||
range: [0, options.ids.length],
|
||||
keys: ['description', 'dimensions', 'extension', 'id', 'name', 'modified']
|
||||
keys: ['description', 'dimensions', 'extension', 'id', 'title', 'modified']
|
||||
}, function(result) {
|
||||
var i = 0,
|
||||
documents = Ox.sort(result.data.items, function(item) {
|
||||
|
@ -173,7 +173,7 @@ pandora.ui.documentDialog = function(options) {
|
|||
? pandora.user.ui.documents[item.id].position
|
||||
: 1,
|
||||
url: '/documents/' + item.id + '/'
|
||||
+ item.name + '.' + item.extension,
|
||||
+ item.title + '.' + item.extension,
|
||||
width: dialogWidth,
|
||||
zoom: 'fit'
|
||||
})
|
||||
|
@ -185,7 +185,7 @@ pandora.ui.documentDialog = function(options) {
|
|||
imageHeight: item.dimensions[1],
|
||||
imagePreviewURL: pandora.getMediaURL('/documents/' + item.id + '/256p.jpg?' + item.modified),
|
||||
imageURL: pandora.getMediaURL('/documents/' + item.id + '/'
|
||||
+ item.name + '.' + item.extension + '?' + item.modified),
|
||||
+ item.title + '.' + item.extension + '?' + item.modified),
|
||||
imageWidth: item.dimensions[0],
|
||||
width: dialogWidth
|
||||
})
|
||||
|
@ -217,7 +217,7 @@ pandora.ui.documentDialog = function(options) {
|
|||
}
|
||||
|
||||
function setTitle() {
|
||||
that.options({title: item.name + '.' + item.extension});
|
||||
that.options({title: item.title + '.' + item.extension});
|
||||
}
|
||||
|
||||
that.getItems = function() {
|
||||
|
|
98
static/js/documentFilterForm.js
Normal file
98
static/js/documentFilterForm.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
|
||||
'use strict';
|
||||
|
||||
pandora.ui.documentFilterForm = function(options) {
|
||||
// mode can be find, collection, embed
|
||||
var collection = options.list,
|
||||
mode = options.mode,
|
||||
that = Ox.Element();
|
||||
|
||||
if (mode == 'list') {
|
||||
mode = 'collection';
|
||||
}
|
||||
|
||||
pandora.api.findCollections({
|
||||
query: {
|
||||
conditions: [{key: 'type', value: 'static', operator: '='}],
|
||||
operator: '&'
|
||||
},
|
||||
keys: ['id'],
|
||||
range: [0, 1000],
|
||||
sort: [{key: 'user', operator: '+'}, {key: 'name', operator: '+'}]
|
||||
}, function(result) {
|
||||
that.append(
|
||||
that.$filter = Ox.Filter({
|
||||
findKeys: pandora.site.documentKeys.map(function(documentKey) {
|
||||
var key = Ox.clone(documentKey, true);
|
||||
key.title = Ox._(key.title);
|
||||
if (key.format && key.format.type == 'ColorPercent') {
|
||||
key.format.type = 'percent';
|
||||
}
|
||||
Ox.print(key);
|
||||
return key;
|
||||
}).concat([{
|
||||
id: 'collection',
|
||||
title: Ox._('Collection'),
|
||||
type: 'list',
|
||||
values: result.data.items.map(function(item) {
|
||||
return item.id;
|
||||
})
|
||||
}]),
|
||||
list: mode == 'find' ? {
|
||||
sort: pandora.user.ui.collectionSort,
|
||||
view: pandora.user.ui.collectionView
|
||||
} : null,
|
||||
sortKeys: pandora.site.documentSortKeys,
|
||||
value: Ox.clone(mode == 'collection' ? collection.query : pandora.user.ui.documentFind, true),
|
||||
viewKeys: pandora.site.collectionViews
|
||||
})
|
||||
.css(mode == 'embed' ? {} : {padding: '16px'})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
if (mode == 'find') {
|
||||
if (pandora.user.ui.updateAdvancedFindResults) {
|
||||
that.updateResults();
|
||||
}
|
||||
} else if (mode == 'collection') {
|
||||
pandora.api.editCollection({
|
||||
id: collection.id,
|
||||
query: data.value
|
||||
}, function(result) {
|
||||
if (pandora.user.ui.updateAdvancedFindResults) {
|
||||
that.updateResults();
|
||||
}
|
||||
});
|
||||
}
|
||||
that.triggerEvent('change', data);
|
||||
}
|
||||
})
|
||||
);
|
||||
that.getList = that.$filter.getList;
|
||||
that.value = that.$filter.value;
|
||||
});
|
||||
that.updateResults = function() {
|
||||
if (mode == 'collection') {
|
||||
Ox.Request.clearCache(collection.id);
|
||||
pandora.$ui.list && pandora.$ui.list
|
||||
.bindEventOnce({
|
||||
init: function(data) {
|
||||
pandora.$ui.folderList[
|
||||
pandora.getListData().folder
|
||||
].value(collection.id, 'query', that.$filter.options('value'));
|
||||
}
|
||||
})
|
||||
.reloadList();
|
||||
/*
|
||||
pandora.$ui.filters && pandora.$ui.filters.forEach(function($filter) {
|
||||
$filter.reloadList();
|
||||
});
|
||||
*/
|
||||
} else {
|
||||
pandora.UI.set({find: Ox.clone(that.$filter.options('value'), true)});
|
||||
pandora.$ui.findElement.updateElement();
|
||||
}
|
||||
};
|
||||
return that;
|
||||
};
|
||||
|
572
static/js/documentInfoView.js
Normal file
572
static/js/documentInfoView.js
Normal file
|
@ -0,0 +1,572 @@
|
|||
'use strict';
|
||||
|
||||
pandora.ui.documentInfoView = function(data) {
|
||||
|
||||
var ui = pandora.user.ui,
|
||||
canEdit = pandora.site.capabilities.canEditMetadata[pandora.user.level] || data.editable,
|
||||
canRemove = pandora.site.capabilities.canRemoveItems[pandora.user.level],
|
||||
css = {
|
||||
marginTop: '4px',
|
||||
textAlign: 'justify'
|
||||
},
|
||||
html,
|
||||
iconRatio = data.ratio,
|
||||
iconSize = ui.infoIconSize,
|
||||
iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio),
|
||||
iconHeight = iconRatio < 1 ? iconSize : Math.round(iconSize / iconRatio),
|
||||
iconLeft = iconSize == 256 ? Math.floor((iconSize - iconWidth) / 2) : 0,
|
||||
borderRadius = ui.icons == 'posters' ? 0 : iconSize / 8,
|
||||
margin = 16,
|
||||
nameKeys = pandora.site.documentKeys.filter(function(key) {
|
||||
return key.sortType == 'person';
|
||||
}).map(function(key) {
|
||||
return key.id;
|
||||
}),
|
||||
listKeys = pandora.site.documentKeys.filter(function(key) {
|
||||
return Ox.isArray(key.type);
|
||||
}).map(function(key){
|
||||
return key.id;
|
||||
}),
|
||||
posterKeys = nameKeys.concat(['title', 'year']),
|
||||
statisticsWidth = 128,
|
||||
|
||||
$bar = Ox.Bar({size: 16})
|
||||
.bindEvent({
|
||||
doubleclick: function(e) {
|
||||
if ($(e.target).is('.OxBar')) {
|
||||
$info.animate({scrollTop: 0}, 250);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
$options = Ox.MenuButton({
|
||||
items: [
|
||||
{
|
||||
id: 'delete',
|
||||
title: Ox._('Delete {0}...', [Ox._('Document')]),
|
||||
disabled: !canRemove
|
||||
}
|
||||
],
|
||||
style: 'square',
|
||||
title: 'set',
|
||||
tooltip: Ox._('Options'),
|
||||
type: 'image',
|
||||
})
|
||||
.css({
|
||||
float: 'left',
|
||||
borderColor: 'rgba(0, 0, 0, 0)',
|
||||
background: 'rgba(0, 0, 0, 0)'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function(data_) {
|
||||
if (data_.id == 'delete') {
|
||||
pandora.ui.deleteDocumentDialog(
|
||||
[data],
|
||||
function() {
|
||||
Ox.Request.clearCache();
|
||||
if (ui.document) {
|
||||
pandora.UI.set({document: ''});
|
||||
} else {
|
||||
pandora.$ui.list.reloadList()
|
||||
}
|
||||
}
|
||||
).open();
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo($bar),
|
||||
|
||||
$edit = Ox.MenuButton({
|
||||
items: [
|
||||
{
|
||||
id: 'insert',
|
||||
title: Ox._('Insert HTML...'),
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
style: 'square',
|
||||
title: 'edit',
|
||||
tooltip: Ox._('Edit'),
|
||||
type: 'image',
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
borderColor: 'rgba(0, 0, 0, 0)',
|
||||
background: 'rgba(0, 0, 0, 0)'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function(data) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
.appendTo($bar),
|
||||
|
||||
$info = Ox.Element().css({overflowY: 'auto'}),
|
||||
|
||||
that = Ox.SplitPanel({
|
||||
elements: [
|
||||
{element: $bar, size: 16},
|
||||
{element: $info}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
}),
|
||||
|
||||
$icon = Ox.Element({
|
||||
element: '<img>',
|
||||
})
|
||||
.attr({
|
||||
src: '/documents/' + data.id + '/512p.jpg?' + data.modified
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: margin + iconLeft + 'px',
|
||||
top: margin + 'px',
|
||||
width: iconWidth + 'px',
|
||||
height: iconHeight + 'px',
|
||||
borderRadius: borderRadius + 'px',
|
||||
cursor: 'pointer'
|
||||
})
|
||||
.bindEvent({
|
||||
singleclick: open
|
||||
})
|
||||
.appendTo($info),
|
||||
|
||||
$reflection = $('<div>')
|
||||
.addClass('OxReflection')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: margin + 'px',
|
||||
top: margin + iconHeight + 'px',
|
||||
width: iconSize + 'px',
|
||||
height: iconSize / 2 + 'px',
|
||||
overflow: 'hidden'
|
||||
})
|
||||
.appendTo($info),
|
||||
|
||||
$reflectionIcon = $('<img>')
|
||||
.attr({
|
||||
src: '/documents/' + data.id + '/512p.jpg?' + data.modified
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: iconLeft + 'px',
|
||||
width: iconWidth + 'px',
|
||||
height: iconHeight + 'px',
|
||||
borderRadius: borderRadius + 'px'
|
||||
})
|
||||
.appendTo($reflection),
|
||||
|
||||
$reflectionGradient = $('<div>')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: iconSize + 'px',
|
||||
height: iconSize / 2 + 'px'
|
||||
})
|
||||
.appendTo($reflection),
|
||||
|
||||
$text = Ox.Element()
|
||||
.addClass('OxTextPage')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: margin + (iconSize == 256 ? 256 : iconWidth) + margin + 'px',
|
||||
top: margin + 'px',
|
||||
right: margin + statisticsWidth + margin + 'px',
|
||||
})
|
||||
.appendTo($info),
|
||||
|
||||
$statistics = $('<div>')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: statisticsWidth + 'px',
|
||||
top: margin + 'px',
|
||||
right: margin + 'px'
|
||||
})
|
||||
.appendTo($info),
|
||||
|
||||
$capabilities;
|
||||
|
||||
[$options, $edit].forEach(function($element) {
|
||||
$element.find('input').css({
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
padding: '3px'
|
||||
});
|
||||
});
|
||||
|
||||
listKeys.forEach(function(key) {
|
||||
if (Ox.isString(data[key])) {
|
||||
data[key] = [data[key]];
|
||||
}
|
||||
});
|
||||
|
||||
if (!canEdit) {
|
||||
pandora.createLinks($info);
|
||||
}
|
||||
|
||||
// Title -------------------------------------------------------------------
|
||||
|
||||
$('<div>')
|
||||
.css({
|
||||
marginTop: '-2px',
|
||||
})
|
||||
.append(
|
||||
Ox.EditableContent({
|
||||
editable: canEdit,
|
||||
tooltip: canEdit ? pandora.getEditTooltip() : '',
|
||||
value: data.title || ''
|
||||
})
|
||||
.css({
|
||||
marginBottom: '-3px',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '13px'
|
||||
})
|
||||
.bindEvent({
|
||||
submit: function(event) {
|
||||
editMetadata('title', event.value);
|
||||
}
|
||||
})
|
||||
)
|
||||
.appendTo($text);
|
||||
|
||||
// Director, Year and Country ----------------------------------------------
|
||||
|
||||
renderGroup(['author', 'type', 'date']);
|
||||
renderGroup(['publisher', 'place', 'series', 'edition']);
|
||||
renderGroup(['language']);
|
||||
renderGroup(['extension', 'dimensions', 'size', 'user', 'matches']);
|
||||
|
||||
|
||||
// Description -----------------------------------------------------------------
|
||||
|
||||
if (canEdit || data.description) {
|
||||
$('<div>')
|
||||
.append(
|
||||
Ox.EditableContent({
|
||||
clickLink: pandora.clickLink,
|
||||
editable: canEdit,
|
||||
format: function(value) {
|
||||
return value.replace(
|
||||
/<img src=/g,
|
||||
'<img style="float: left; max-width: 256px; max-height: 256px; margin: 0 16px 16px 0" src='
|
||||
);
|
||||
},
|
||||
maxHeight: Infinity,
|
||||
placeholder: formatLight(Ox._('No Description')),
|
||||
tooltip: canEdit ? pandora.getEditTooltip() : '',
|
||||
type: 'textarea',
|
||||
value: data.description || ''
|
||||
})
|
||||
.css(css)
|
||||
.css({
|
||||
marginTop: '12px',
|
||||
overflow: 'hidden'
|
||||
})
|
||||
.bindEvent({
|
||||
submit: function(event) {
|
||||
editMetadata('description', event.value);
|
||||
}
|
||||
})
|
||||
)
|
||||
.appendTo($text);
|
||||
}
|
||||
|
||||
// Created and Modified ----------------------------------------------------
|
||||
|
||||
['created', 'modified'].forEach(function(key) {
|
||||
$('<div>')
|
||||
.css({marginBottom: '4px'})
|
||||
.append(formatKey(key, 'statistics'))
|
||||
.append(
|
||||
$('<div>').html(Ox.formatDate(data[key], '%B %e, %Y'))
|
||||
)
|
||||
.appendTo($statistics);
|
||||
});
|
||||
$('<div>')
|
||||
.css({marginBottom: '4px'})
|
||||
.append(formatKey('timesaccessed', 'statistics'))
|
||||
.append(
|
||||
$('<div>').html(data.timesaccessed)
|
||||
)
|
||||
.appendTo($statistics);
|
||||
|
||||
// Rights Level ------------------------------------------------------------
|
||||
|
||||
/*
|
||||
var $rightsLevel = $('<div>');
|
||||
$('<div>')
|
||||
.css({marginBottom: '4px'})
|
||||
.append(formatKey('Rights Level', 'statistics'))
|
||||
.append($rightsLevel)
|
||||
.appendTo($statistics);
|
||||
renderRightsLevel();
|
||||
*/
|
||||
|
||||
|
||||
function editMetadata(key, value) {
|
||||
if (value != data[key]) {
|
||||
var edit = {id: data.id};
|
||||
if (key == 'title') {
|
||||
edit[key] = value;
|
||||
} else if (listKeys.indexOf(key) >= 0) {
|
||||
edit[key] = value ? value.split(', ') : [];
|
||||
} else {
|
||||
edit[key] = value ? value : null;
|
||||
}
|
||||
pandora.api.editDocument(edit, function(result) {
|
||||
var src;
|
||||
data[key] = result.data[key];
|
||||
Ox.Request.clearCache(); // fixme: too much? can change filter/list etc
|
||||
if (result.data.id != data.id) {
|
||||
pandora.UI.set({document: result.data.id});
|
||||
pandora.$ui.browser.value(data.id, 'id', result.data.id);
|
||||
}
|
||||
//pandora.updateItemContext();
|
||||
//pandora.$ui.browser.value(result.data.id, key, result.data[key]);
|
||||
pandora.$ui.itemTitle
|
||||
.options({title: '<b>' + (pandora.getDocumentTitle(result.data)) + '</b>'});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatKey(key, mode) {
|
||||
var item = Ox.getObjectById(pandora.site.itemKeys, key);
|
||||
key = Ox._(item ? item.title : key);
|
||||
mode = mode || 'text';
|
||||
return mode == 'text'
|
||||
? '<span style="font-weight: bold">' + Ox.toTitleCase(key) + ':</span> '
|
||||
: mode == 'description'
|
||||
? Ox.toTitleCase(key)
|
||||
: Ox.Element()
|
||||
.css({marginBottom: '4px', fontWeight: 'bold'})
|
||||
.html(Ox.toTitleCase(key)
|
||||
.replace(' Per ', ' per '));
|
||||
}
|
||||
|
||||
function formatLight(str) {
|
||||
return '<span class="OxLight">' + str + '</span>';
|
||||
}
|
||||
|
||||
function formatLink(value, key) {
|
||||
return (Ox.isArray(value) ? value : [value]).map(function(value) {
|
||||
return key
|
||||
? '<a href="/documents/' + key + '=' + value + '">' + value + '</a>'
|
||||
: value;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
function formatValue(key, value) {
|
||||
var ret;
|
||||
if (key == 'date') {
|
||||
ret = value ? Ox.formatDate(value,
|
||||
['', '%Y', '%B %Y', '%B %e, %Y'][value.split('-').length],
|
||||
true
|
||||
) : '';
|
||||
} else if (nameKeys.indexOf(key) > -1) {
|
||||
ret = formatLink(value.split(', '), key);
|
||||
} else if (listKeys.indexOf(key) > -1) {
|
||||
ret = formatLink(value.split(', '), key);
|
||||
} else if (['type'].indexOf(key) > -1) {
|
||||
ret = formatLink(value, key);
|
||||
} else {
|
||||
ret = pandora.formatDocumentKey(Ox.getObjectById(pandora.site.documentKeys, key), data);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getRightsLevelElement(rightsLevel) {
|
||||
return Ox.Theme.formatColorLevel(
|
||||
rightsLevel,
|
||||
pandora.site.rightsLevels.map(function(rightsLevel) {
|
||||
return rightsLevel.name;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getValue(key, value) {
|
||||
return !value ? ''
|
||||
: Ox.contains(listKeys, key) ? value.join(', ')
|
||||
: value;
|
||||
}
|
||||
|
||||
function renderCapabilities(rightsLevel) {
|
||||
var capabilities = [].concat(
|
||||
canEdit ? [{name: 'canSeeItem', symbol: 'Find'}] : [],
|
||||
[
|
||||
{name: 'canPlayClips', symbol: 'PlayInToOut'},
|
||||
{name: 'canPlayVideo', symbol: 'Play'},
|
||||
{name: 'canDownloadVideo', symbol: 'Download'}
|
||||
]
|
||||
),
|
||||
userLevels = canEdit ? pandora.site.userLevels : [pandora.user.level];
|
||||
$capabilities.empty();
|
||||
userLevels.forEach(function(userLevel, i) {
|
||||
var $element,
|
||||
$line = $('<div>')
|
||||
.css({
|
||||
height: '16px',
|
||||
marginBottom: '4px'
|
||||
})
|
||||
.appendTo($capabilities);
|
||||
if (canEdit) {
|
||||
$element = Ox.Theme.formatColorLevel(i, userLevels.map(function(userLevel) {
|
||||
return Ox.toTitleCase(userLevel);
|
||||
}), [0, 240]);
|
||||
Ox.Label({
|
||||
textAlign: 'center',
|
||||
title: Ox.toTitleCase(userLevel),
|
||||
width: 60
|
||||
})
|
||||
.addClass('OxColor OxColorGradient')
|
||||
.css({
|
||||
float: 'left',
|
||||
height: '12px',
|
||||
paddingTop: '2px',
|
||||
background: $element.css('background'),
|
||||
fontSize: '8px',
|
||||
color: $element.css('color')
|
||||
})
|
||||
.data({OxColor: $element.data('OxColor')})
|
||||
.appendTo($line);
|
||||
}
|
||||
capabilities.forEach(function(capability) {
|
||||
var hasCapability = pandora.site.capabilities[capability.name][userLevel] >= rightsLevel,
|
||||
$element = Ox.Theme.formatColorLevel(hasCapability, ['', '']);
|
||||
Ox.Button({
|
||||
tooltip: (canEdit ? Ox.toTitleCase(userLevel) : 'You') + ' '
|
||||
+ (hasCapability ? 'can' : 'can\'t') + ' '
|
||||
+ Ox.toSlashes(capability.name)
|
||||
.split('/').slice(1).join(' ')
|
||||
.toLowerCase(),
|
||||
title: capability.symbol,
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxColor OxColorGradient')
|
||||
.css({background: $element.css('background')})
|
||||
.css('margin' + (canEdit ? 'Left' : 'Right'), '4px')
|
||||
.data({OxColor: $element.data('OxColor')})
|
||||
.appendTo($line);
|
||||
});
|
||||
if (!canEdit) {
|
||||
Ox.Button({
|
||||
title: Ox._('Help'),
|
||||
tooltip: Ox._('About Rights'),
|
||||
type: 'image'
|
||||
})
|
||||
.css({marginLeft: '52px'})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
pandora.UI.set({page: 'rights'});
|
||||
}
|
||||
})
|
||||
.appendTo($line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderGroup(keys) {
|
||||
var $element;
|
||||
if (canEdit || keys.filter(function(key) {
|
||||
return data[key];
|
||||
}).length) {
|
||||
$element = $('<div>').addClass('OxSelectable').css(css);
|
||||
keys.forEach(function(key, i) {
|
||||
if (canEdit || data[key]) {
|
||||
if ($element.children().length) {
|
||||
$('<span>').html('; ').appendTo($element);
|
||||
}
|
||||
$('<span>').html(formatKey(key)).appendTo($element);
|
||||
Ox.EditableContent({
|
||||
clickLink: pandora.clickLink,
|
||||
format: function(value) {
|
||||
return formatValue(key, value);
|
||||
},
|
||||
placeholder: formatLight(Ox._('unknown')),
|
||||
tooltip: canEdit ? pandora.getEditTooltip() : '',
|
||||
value: getValue(key, data[key])
|
||||
})
|
||||
.bindEvent({
|
||||
submit: function(data) {
|
||||
editMetadata(key, data.value);
|
||||
}
|
||||
})
|
||||
.appendTo($element);
|
||||
}
|
||||
});
|
||||
$element.appendTo($text);
|
||||
}
|
||||
}
|
||||
|
||||
function renderRightsLevel() {
|
||||
var $rightsLevelElement = getRightsLevelElement(data.rightslevel),
|
||||
$rightsLevelSelect;
|
||||
$rightsLevel.empty();
|
||||
if (canEdit) {
|
||||
$rightsLevelSelect = Ox.Select({
|
||||
items: pandora.site.rightsLevels.map(function(rightsLevel, i) {
|
||||
return {id: i, title: rightsLevel.name};
|
||||
}),
|
||||
width: 128,
|
||||
value: data.rightslevel
|
||||
})
|
||||
.addClass('OxColor OxColorGradient')
|
||||
.css({
|
||||
marginBottom: '4px',
|
||||
background: $rightsLevelElement.css('background')
|
||||
})
|
||||
.data({OxColor: $rightsLevelElement.data('OxColor')})
|
||||
.bindEvent({
|
||||
change: function(event) {
|
||||
var rightsLevel = event.value;
|
||||
$rightsLevelElement = getRightsLevelElement(rightsLevel);
|
||||
$rightsLevelSelect
|
||||
.css({background: $rightsLevelElement.css('background')})
|
||||
.data({OxColor: $rightsLevelElement.data('OxColor')})
|
||||
renderCapabilities(rightsLevel);
|
||||
pandora.api.editDocument({id: data.id, rightslevel: rightsLevel}, function(result) {
|
||||
// ...
|
||||
});
|
||||
}
|
||||
})
|
||||
.appendTo($rightsLevel);
|
||||
} else {
|
||||
$rightsLevelElement
|
||||
.css({
|
||||
marginBottom: '4px'
|
||||
})
|
||||
.appendTo($rightsLevel);
|
||||
}
|
||||
$capabilities = $('<div>').appendTo($rightsLevel);
|
||||
renderCapabilities(data.rightslevel);
|
||||
}
|
||||
|
||||
function open() {
|
||||
pandora.UI.set({
|
||||
documentView: 'view',
|
||||
});
|
||||
}
|
||||
|
||||
that.reload = function() {
|
||||
var src = '/documents/' + data.id + '/512p.jpg?' + data.modified;
|
||||
$icon.attr({src: src});
|
||||
$reflectionIcon.attr({src: src});
|
||||
iconSize = iconSize == 256 ? 512 : 256;
|
||||
iconRatio = ui.icons == 'posters'
|
||||
? (ui.showSitePosters ? pandora.site.posters.ratio : data.posterRatio) : 1;
|
||||
toggleIconSize();
|
||||
};
|
||||
|
||||
that.bindEvent({
|
||||
mousedown: function() {
|
||||
setTimeout(function() {
|
||||
!Ox.Focus.focusedElementIsInput() && that.gainFocus();
|
||||
});
|
||||
},
|
||||
pandora_icons: that.reload,
|
||||
pandora_showsiteposters: function() {
|
||||
ui.icons == 'posters' && that.reload();
|
||||
}
|
||||
});
|
||||
|
||||
return that;
|
||||
|
||||
};
|
30
static/js/documentPanel.js
Normal file
30
static/js/documentPanel.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
|
||||
'use strict';
|
||||
|
||||
pandora.ui.documentPanel = function() {
|
||||
var that = Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: pandora.$ui.toolbar = pandora.ui.documentToolbar(),
|
||||
size: 24
|
||||
},
|
||||
{
|
||||
element: pandora.$ui.documentContentPanel = pandora.ui.documentContentPanel()
|
||||
}
|
||||
],
|
||||
id: 'documentPanel',
|
||||
orientation: 'vertical'
|
||||
})
|
||||
.bindEvent({
|
||||
resize: function(data) {
|
||||
if (!pandora.user.ui.document) {
|
||||
pandora.$ui.list && pandora.$ui.list.size();
|
||||
} else {
|
||||
pandora.$ui.document && pandora.$ui.document.update();
|
||||
}
|
||||
},
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
208
static/js/documentToolbar.js
Normal file
208
static/js/documentToolbar.js
Normal file
|
@ -0,0 +1,208 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
|
||||
'use strict';
|
||||
|
||||
pandora.ui.documentToolbar = function() {
|
||||
var ui = pandora.user.ui,
|
||||
isNavigationView = !ui.item,
|
||||
that = Ox.Bar({
|
||||
size: 24
|
||||
}).css({
|
||||
zIndex: 2 // fixme: remove later
|
||||
}),
|
||||
$viewSelect,
|
||||
$sortView;
|
||||
|
||||
ui.document && that.append(
|
||||
pandora.$ui.backButton = pandora.ui.backButton()
|
||||
);
|
||||
|
||||
$viewSelect = documentViewSelect().appendTo(that);
|
||||
if (!ui.document) {
|
||||
$sortView = documentSortSelect().appendTo(that);
|
||||
}
|
||||
|
||||
that.append(
|
||||
!ui.document
|
||||
? pandora.$ui.listTitle = Ox.Label({
|
||||
textAlign: 'center',
|
||||
title: getCollectionName(pandora.user.ui._collection)
|
||||
})
|
||||
.addClass('OxSelectable')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: getListTitleLeft() + 'px',
|
||||
top: '4px',
|
||||
right: (ui._collection ? 340 : 326) + 'px',
|
||||
width: 'auto'
|
||||
})
|
||||
: pandora.$ui.itemTitle = Ox.Label({
|
||||
textAlign: 'center'
|
||||
})
|
||||
.addClass('OxSelectable')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: '266px',
|
||||
top: '4px',
|
||||
right: (ui._collection ? 340 : 326) + 'px',
|
||||
width: 'auto'
|
||||
})
|
||||
.hide()
|
||||
);
|
||||
(!ui.document ? pandora.$ui.listTitle : pandora.$ui.itemTitle).bindEvent({
|
||||
doubleclick: function() {
|
||||
if (!ui.document) {
|
||||
pandora.$ui.list && (
|
||||
ui.collectionView == 'list'
|
||||
? pandora.$ui.list.$body
|
||||
: pandora.$ui.list
|
||||
).animate({
|
||||
scrollTop: 0
|
||||
}, 250);
|
||||
} else {
|
||||
//fixme:
|
||||
pandora.$ui.browser.scrollToSelection();
|
||||
}
|
||||
}
|
||||
})
|
||||
that.append(
|
||||
pandora.$ui.findDocumentsElement = pandora.ui.findDocumentsElement(function(data) {
|
||||
var key = data.key,
|
||||
value = data.value,
|
||||
conditions;
|
||||
if (key == 'all') {
|
||||
key = 'title'
|
||||
}
|
||||
conditions = [
|
||||
{key: key, operator: '=', value: value}
|
||||
];
|
||||
if (pandora.user.ui._collection) {
|
||||
conditions.push({
|
||||
key: 'collection',
|
||||
value: pandora.user.ui._collection,
|
||||
operator: '=='
|
||||
});
|
||||
}
|
||||
pandora.UI.set({findDocuments: {conditions: conditions, operator: '&'}});
|
||||
})
|
||||
);
|
||||
that.bindEvent({
|
||||
pandora_collectionsort: function(data) {
|
||||
$sortView.updateElement();
|
||||
},
|
||||
pandora_documentview: function(data) {
|
||||
$viewSelect.options({
|
||||
value: data.value
|
||||
});
|
||||
}
|
||||
});
|
||||
function getCollectionName(listId) {
|
||||
return '<b>' + (
|
||||
listId == ''
|
||||
? Ox._('All {0}', [Ox._(Ox.toTitleCase(ui.section))])
|
||||
: Ox.encodeHTMLEntities(listId.slice(listId.indexOf(':') + 1))
|
||||
) + '</b>';
|
||||
}
|
||||
function getListTitleLeft() {
|
||||
return 284;
|
||||
}
|
||||
|
||||
function documentSortSelect() {
|
||||
var $orderButton = Ox.Button({
|
||||
overlap: 'left',
|
||||
title: getOrderButtonTitle(),
|
||||
tooltip: getOrderButtonTooltip(),
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
pandora.UI.set({collectionSort: [{
|
||||
key: ui.collectionSort[0].key,
|
||||
operator: ui.collectionSort[0].operator == '+' ? '-' : '+'
|
||||
}]});
|
||||
},
|
||||
}),
|
||||
$sortSelect = Ox.Select({
|
||||
items: pandora.site.documentKeys.filter(function(key) {
|
||||
return key.sort;
|
||||
}).map(function(key) {
|
||||
return {
|
||||
id: key.id,
|
||||
title: Ox._('Sort by {0}', [key.title])
|
||||
};
|
||||
}),
|
||||
value: ui.collectionSort[0].key,
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
var key = data.value;
|
||||
pandora.UI.set({collectionSort: [{
|
||||
key: key,
|
||||
operator: pandora.getDocumentSortOperator(key)
|
||||
}]});
|
||||
}
|
||||
}),
|
||||
that = Ox.FormElementGroup({
|
||||
elements: [$sortSelect, $orderButton],
|
||||
float: 'right'
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px'})
|
||||
|
||||
function getOrderButtonTitle() {
|
||||
return ui.collectionSort[0].operator == '+' ? 'up' : 'down';
|
||||
}
|
||||
|
||||
function getOrderButtonTooltip() {
|
||||
return Ox._(ui.collectionSort[0].operator == '+' ? 'Ascending' : 'Descending');
|
||||
}
|
||||
|
||||
that.updateElement = function() {
|
||||
$sortSelect.value(ui.collectionSort[0].key);
|
||||
$orderButton.options({
|
||||
title: getOrderButtonTitle(),
|
||||
tooltip: getOrderButtonTooltip()
|
||||
});
|
||||
};
|
||||
return that;
|
||||
}
|
||||
|
||||
function documentViewSelect() {
|
||||
var that;
|
||||
if (!ui.document) {
|
||||
that = Ox.Select({
|
||||
items: pandora.site.collectionViews,
|
||||
value: ui.collectionView,
|
||||
width: 128
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px 4px 4px'})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
pandora.UI.set({collectionView: data.value});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that = Ox.Select({
|
||||
items: [
|
||||
{id: 'info', title: Ox._('View Info')},
|
||||
{id: 'view', title: Ox._('View Document')}
|
||||
],
|
||||
value: ui.documentView,
|
||||
width: 128
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px 4px 4px'})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
pandora.UI.set({documentView: data.value});
|
||||
},
|
||||
});
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
that.updateListName = function(listId) {
|
||||
pandora.$ui.listTitle.options({title: getCollectionName(listId)});
|
||||
};
|
||||
return that;
|
||||
};
|
||||
|
|
@ -2,22 +2,12 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
pandora.ui.documentsPanel = function(options) {
|
||||
|
||||
var ui = pandora.user.ui,
|
||||
hasItemView = ui.section == 'items' && ui.item,
|
||||
hasListSelection = ui.section == 'items' && !ui.item && ui.listSelection.length,
|
||||
isItemView = options.isItemView,
|
||||
listLoaded = false,
|
||||
allFindKeys = ['user', 'name', 'entity', 'extension', 'description'].filter(function(key) {
|
||||
return key != 'entity' || pandora.site.entities.length;
|
||||
}),
|
||||
|
||||
columns = [
|
||||
pandora.documentColumns = [
|
||||
{
|
||||
id: 'name',
|
||||
id: 'title',
|
||||
operator: '+',
|
||||
title: Ox._('Name'),
|
||||
title: Ox._('Title'),
|
||||
find: true,
|
||||
visible: true,
|
||||
width: 256
|
||||
},
|
||||
|
@ -35,15 +25,16 @@ pandora.ui.documentsPanel = function(options) {
|
|||
id: 'extension',
|
||||
operator: '+',
|
||||
title: Ox._('Extension'),
|
||||
find: true,
|
||||
visible: true,
|
||||
width: 64
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: function(value) {
|
||||
format: function(value, data) {
|
||||
return Ox.isArray(value)
|
||||
? Ox.formatDimensions(value, 'px')
|
||||
: Ox.formatCount(value, 'page');
|
||||
: Ox.formatCount(value, data.extension == 'html' ? 'word' : 'page');
|
||||
},
|
||||
id: 'dimensions',
|
||||
operator: '-',
|
||||
|
@ -66,6 +57,7 @@ pandora.ui.documentsPanel = function(options) {
|
|||
id: 'description',
|
||||
operator: '+',
|
||||
title: Ox._('Description'),
|
||||
find: true,
|
||||
visible: true,
|
||||
width: 256
|
||||
},
|
||||
|
@ -81,6 +73,7 @@ pandora.ui.documentsPanel = function(options) {
|
|||
id: 'user',
|
||||
operator: '+',
|
||||
title: Ox._('User'),
|
||||
find: true,
|
||||
visible: true,
|
||||
width: 128
|
||||
},
|
||||
|
@ -106,46 +99,10 @@ pandora.ui.documentsPanel = function(options) {
|
|||
visible: true,
|
||||
width: 144
|
||||
}
|
||||
],
|
||||
|
||||
$listBar = Ox.Bar({size: 24}),
|
||||
|
||||
$viewSelect = Ox.Select({
|
||||
items: [
|
||||
{id: 'list', title: Ox._('View as List')},
|
||||
{id: 'grid', title: Ox._('View as Grid')}
|
||||
],
|
||||
value: ui.documentsView,
|
||||
width: 128
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px 4px 4px'})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
pandora.UI.set({documentsView: data.value});
|
||||
}
|
||||
})
|
||||
.appendTo($listBar),
|
||||
|
||||
$sortSelect = Ox.Select({
|
||||
items: columns.map(function(column) {
|
||||
return {
|
||||
id: column.id,
|
||||
title: Ox._('Sort by {0}', [column.title])
|
||||
};
|
||||
}),
|
||||
value: ui.documentsSort[0].key,
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
var key = data.value;
|
||||
pandora.UI.set({documentsSort: [{
|
||||
key: key,
|
||||
operator: Ox.getObjectById(columns, key).operator
|
||||
}]});
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
pandora.ui.documentSortSelect = function() {
|
||||
var ui = pandora.user.ui,
|
||||
$orderButton = Ox.Button({
|
||||
overlap: 'left',
|
||||
title: getOrderButtonTitle(),
|
||||
|
@ -160,55 +117,85 @@ pandora.ui.documentsPanel = function(options) {
|
|||
}]});
|
||||
}
|
||||
}),
|
||||
|
||||
$sortElement = Ox.FormElementGroup({
|
||||
elements: [$sortSelect, $orderButton],
|
||||
float: 'right'
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px'})
|
||||
.appendTo($listBar),
|
||||
|
||||
$findSelect = Ox.Select({
|
||||
items: [
|
||||
{id: 'all', title: Ox._('Find: All')},
|
||||
{id: 'name', title: Ox._('Find: Name')},
|
||||
{id: 'user', title: Ox._('Find: User')},
|
||||
{id: 'entity', title: Ox._('Find: Entity')}
|
||||
].filter(function(item) {
|
||||
if (item.id == 'user') {
|
||||
return !isItemView;
|
||||
} else if (item.id == 'entity') {
|
||||
return pandora.site.entities.length;
|
||||
}
|
||||
return true;
|
||||
$sortSelect = Ox.Select({
|
||||
items: pandora.documentColumns.map(function(column) {
|
||||
return {
|
||||
id: column.id,
|
||||
title: Ox._('Sort by {0}', [column.title])
|
||||
};
|
||||
}),
|
||||
overlap: 'right',
|
||||
type: 'image'
|
||||
value: ui.documentsSort[0].key,
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
$findInput.options({placeholder: data.title}).focusInput();
|
||||
var key = data.value;
|
||||
pandora.UI.set({documentsSort: [{
|
||||
key: key,
|
||||
operator: Ox.getObjectById(pandora.documentColumns, key).operator
|
||||
}]});
|
||||
}
|
||||
}),
|
||||
|
||||
$findInput = Ox.Input({
|
||||
changeOnKeypress: true,
|
||||
clear: true,
|
||||
placeholder: Ox._('Find: All'),
|
||||
width: 192
|
||||
that = Ox.FormElementGroup({
|
||||
elements: [$sortSelect, $orderButton],
|
||||
float: 'right'
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px'});
|
||||
|
||||
function getOrderButtonTitle() {
|
||||
return ui.documentsSort[0].operator == '+' ? 'up' : 'down';
|
||||
}
|
||||
|
||||
function getOrderButtonTooltip() {
|
||||
return Ox._(ui.documentsSort[0].operator == '+' ? 'Ascending' : 'Descending');
|
||||
}
|
||||
|
||||
that.sortValue = function(value) {
|
||||
$sortSelect.value(value);
|
||||
$orderButton.options({
|
||||
title: getOrderButtonTitle(),
|
||||
tooltip: getOrderButtonTooltip()
|
||||
});
|
||||
};
|
||||
return that;
|
||||
};
|
||||
|
||||
pandora.ui.documentViewSelect = function() {
|
||||
var ui = pandora.user.ui,
|
||||
that = Ox.Select({
|
||||
items: [
|
||||
{id: 'list', title: Ox._('View as List')},
|
||||
{id: 'grid', title: Ox._('View as Grid')}
|
||||
],
|
||||
value: ui.documentsView,
|
||||
width: 128
|
||||
})
|
||||
.css({float: 'left', margin: '4px 2px 4px 4px'})
|
||||
.bindEvent({
|
||||
change: updateList
|
||||
change: function(data) {
|
||||
pandora.UI.set({documentsView: data.value});
|
||||
}
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
||||
pandora.ui.documentsPanel = function(options) {
|
||||
|
||||
var ui = pandora.user.ui,
|
||||
hasItemView = ui.section == 'items' && ui.item,
|
||||
hasListSelection = ui.section == 'items' && !ui.item && ui.listSelection.length,
|
||||
isItemView = options.isItemView,
|
||||
listLoaded = false,
|
||||
allFindKeys = ['user', 'title', 'entity', 'extension', 'description'].filter(function(key) {
|
||||
return key != 'entity' || pandora.site.entities.length;
|
||||
}),
|
||||
|
||||
$findElement = Ox.FormElementGroup({
|
||||
elements: [
|
||||
$findSelect,
|
||||
$findInput
|
||||
]
|
||||
})
|
||||
.css({float: 'right', margin: '4px 4px 4px 2px'})
|
||||
.appendTo($listBar),
|
||||
$listBar = Ox.Bar({size: 24}),
|
||||
|
||||
$viewSelect = pandora.ui.documentViewSelect().appendTo($listBar),
|
||||
$sortElement = pandora.ui.documentSortSelect().appendTo($listBar),
|
||||
|
||||
$findElement = findElement(updateList, isItemView).appendTo($listBar),
|
||||
|
||||
$list = renderList(),
|
||||
|
||||
|
@ -389,7 +376,7 @@ pandora.ui.documentsPanel = function(options) {
|
|||
that.size(1, data.value);
|
||||
},
|
||||
pandora_documentssort: function(data) {
|
||||
updateSortElement();
|
||||
$sortElement.sortValue(ui.documentsSort[0].key);
|
||||
$list.options({sort: data.value});
|
||||
},
|
||||
pandora_documentsview: function(data) {
|
||||
|
@ -410,12 +397,6 @@ pandora.ui.documentsPanel = function(options) {
|
|||
pandora.$ui.documentsList = $list;
|
||||
}
|
||||
|
||||
// to determine the width of the find input inside
|
||||
// the documents dialog, that dialog has to be present
|
||||
setTimeout(function() {
|
||||
$findInput.options({width: getFindInputWidth()});
|
||||
});
|
||||
|
||||
function addDocuments() {
|
||||
var ids = ui.documentsSelection[''];
|
||||
pandora.api.addDocument({
|
||||
|
@ -460,12 +441,59 @@ pandora.ui.documentsPanel = function(options) {
|
|||
openDocumentsDialog();
|
||||
}
|
||||
|
||||
function getOrderButtonTitle() {
|
||||
return ui.documentsSort[0].operator == '+' ? 'up' : 'down';
|
||||
function findElement(callback, isItemView) {
|
||||
var $findSelect = Ox.Select({
|
||||
items: [
|
||||
{id: 'all', title: Ox._('Find: All')},
|
||||
{id: 'title', title: Ox._('Find: Title')},
|
||||
{id: 'user', title: Ox._('Find: User')},
|
||||
{id: 'entity', title: Ox._('Find: Entity')}
|
||||
].filter(function(item) {
|
||||
if (item.id == 'user') {
|
||||
return !isItemView;
|
||||
} else if (item.id == 'entity') {
|
||||
return pandora.site.entities.length;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
overlap: 'right',
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
$findInput.options({placeholder: data.title}).focusInput();
|
||||
}
|
||||
}),
|
||||
|
||||
function getOrderButtonTooltip() {
|
||||
return Ox._(ui.documentsSort[0].operator == '+' ? 'Ascending' : 'Descending');
|
||||
$findInput = Ox.Input({
|
||||
changeOnKeypress: true,
|
||||
clear: true,
|
||||
placeholder: Ox._('Find: All'),
|
||||
width: 192
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
data.key = $findSelect.value();
|
||||
data.value = $findInput.value()
|
||||
callback(data);
|
||||
}
|
||||
}),
|
||||
|
||||
that = Ox.FormElementGroup({
|
||||
elements: [
|
||||
$findSelect,
|
||||
$findInput
|
||||
]
|
||||
})
|
||||
.css({float: 'right', margin: '4px 4px 4px 2px'});
|
||||
|
||||
// to determine the width of the find input inside
|
||||
// the documents dialog, that dialog has to be present
|
||||
setTimeout(function() {
|
||||
$findInput.options({width: getFindInputWidth()});
|
||||
});
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
function getFindInputWidth() {
|
||||
|
@ -564,8 +592,8 @@ pandora.ui.documentsPanel = function(options) {
|
|||
.append(
|
||||
$name = Ox.EditableContent({
|
||||
editable: editable,
|
||||
tooltip: editable ? pandora.getEditTooltip('name') : '',
|
||||
value: item.name,
|
||||
tooltip: editable ? pandora.getEditTooltip('title') : '',
|
||||
value: item.title,
|
||||
width: width
|
||||
})
|
||||
.css({
|
||||
|
@ -580,11 +608,11 @@ pandora.ui.documentsPanel = function(options) {
|
|||
},
|
||||
submit: function(data) {
|
||||
pandora.api.editDocument({
|
||||
name: data.value,
|
||||
title: data.value,
|
||||
id: item.id,
|
||||
item: ui.item,
|
||||
}, function(result) {
|
||||
$name.options({value: result.data.name});
|
||||
$name.options({value: result.data.title});
|
||||
Ox.Request.clearCache('findDocuments');
|
||||
$list.reloadList();
|
||||
});
|
||||
|
@ -631,10 +659,10 @@ pandora.ui.documentsPanel = function(options) {
|
|||
items: [
|
||||
Ox.Input({
|
||||
disabled: !editable,
|
||||
id: 'name',
|
||||
label: Ox._('Name'),
|
||||
id: 'title',
|
||||
label: Ox._('Title'),
|
||||
labelWidth: labelWidth,
|
||||
value: item.name,
|
||||
value: item.title,
|
||||
width: width
|
||||
}),
|
||||
Ox.Input({
|
||||
|
@ -839,7 +867,7 @@ pandora.ui.documentsPanel = function(options) {
|
|||
function renderList() {
|
||||
var options = {
|
||||
items: pandora.api.findDocuments,
|
||||
keys: ['description', 'dimensions', 'extension', 'id', 'name', 'ratio', 'size', 'user', 'entities', 'modified'],
|
||||
keys: ['description', 'dimensions', 'extension', 'id', 'title', 'ratio', 'size', 'user', 'entities', 'modified'],
|
||||
query: {
|
||||
conditions: isItemView ? [{ key: 'item', value: ui.item, operator: '==' }] : [],
|
||||
operator: '&'
|
||||
|
@ -847,27 +875,27 @@ pandora.ui.documentsPanel = function(options) {
|
|||
selected: ui.documentsSelection[isItemView ? ui.item : ''] || [],
|
||||
sort: ui.documentsSort.concat([
|
||||
{key: 'extension', operator: '+'},
|
||||
{key: 'name', operator: '+'}
|
||||
{key: 'title', operator: '+'}
|
||||
]),
|
||||
unique: 'id'
|
||||
};
|
||||
return (ui.documentsView == 'list' ? Ox.TableList(Ox.extend(options, {
|
||||
columns: columns,
|
||||
columns: pandora.documentColumns,
|
||||
columnsVisible: true,
|
||||
scrollbarVisible: true,
|
||||
})) : Ox.IconList(Ox.extend(options, {
|
||||
item: function(data, sort, size) {
|
||||
var sortKey = sort[0].key,
|
||||
infoKey = sortKey == 'name' ? 'extension' : sortKey,
|
||||
infoKey = sortKey == 'title' ? 'extension' : sortKey,
|
||||
info = (
|
||||
Ox.getObjectById(columns, infoKey).format || Ox.identity
|
||||
Ox.getObjectById(pandora.documentColumns, infoKey).format || Ox.identity
|
||||
)(data[infoKey]),
|
||||
size = size || 128;
|
||||
return {
|
||||
height: Math.round(data.ratio > 1 ? size / data.ratio : size),
|
||||
id: data.id,
|
||||
info: info,
|
||||
title: data.name,
|
||||
title: data.title,
|
||||
url: pandora.getMediaURL('/documents/' + data.id + '/256p.jpg?' + data.modified),
|
||||
width: Math.round(data.ratio > 1 ? size : size * data.ratio)
|
||||
};
|
||||
|
@ -879,6 +907,9 @@ pandora.ui.documentsPanel = function(options) {
|
|||
// we can't open upload dialog via control+n
|
||||
isItemView && openDocumentsDialog();
|
||||
},
|
||||
copy: function(data) {
|
||||
pandora.clipboard.copy(data.ids, 'document');
|
||||
},
|
||||
closepreview: closeDocuments,
|
||||
'delete': isItemView ? removeDocuments : deleteDocuments,
|
||||
init: function(data) {
|
||||
|
@ -892,6 +923,22 @@ pandora.ui.documentsPanel = function(options) {
|
|||
},
|
||||
open: openDocuments,
|
||||
openpreview: openDocuments,
|
||||
paste: function(data) {
|
||||
if (isItemView) {
|
||||
//fixme permissions!
|
||||
var items = pandora.clipboard.paste();
|
||||
if (items.length && pandora.clipboard.type() == 'document') {
|
||||
//fixme use history
|
||||
pandora.api.addDocument({
|
||||
item: ui.item,
|
||||
ids: items
|
||||
}, function(result) {
|
||||
Ox.Request.clearCache('findDocuments');
|
||||
$list.reloadList();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
select: function(data) {
|
||||
pandora.UI.set(
|
||||
'documentsSelection.' + (isItemView ? ui.item : ''), data.ids
|
||||
|
@ -1017,9 +1064,10 @@ pandora.ui.documentsPanel = function(options) {
|
|||
);
|
||||
}
|
||||
|
||||
function updateList() {
|
||||
var key = $findSelect.value(),
|
||||
value = $findInput.value(),
|
||||
function updateList(data) {
|
||||
|
||||
var key = data.key,
|
||||
value = data.value,
|
||||
itemCondition = isItemView
|
||||
? {key: 'item', operator: '==', value: ui.item}
|
||||
: null,
|
||||
|
@ -1040,11 +1088,7 @@ pandora.ui.documentsPanel = function(options) {
|
|||
}
|
||||
|
||||
function updateSortElement() {
|
||||
$sortSelect.value(ui.documentsSort[0].key);
|
||||
$orderButton.options({
|
||||
title: getOrderButtonTitle(),
|
||||
tooltip: getOrderButtonTooltip()
|
||||
});
|
||||
$sortElement.sortValue(ui.documentsSort[0].key);
|
||||
}
|
||||
|
||||
function uploadDocuments(data) {
|
||||
|
|
159
static/js/findDocumentsElement.js
Normal file
159
static/js/findDocumentsElement.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||||
'use strict';
|
||||
|
||||
pandora.ui.findDocumentsElement = function() {
|
||||
var findIndex = pandora.user.ui._findDocumentsState.index,
|
||||
findKey = pandora.user.ui._findDocumentsState.key,
|
||||
findValue = pandora.user.ui._findDocumentsState.value,
|
||||
hasPressedClear = false,
|
||||
previousFindKey = findKey,
|
||||
$findCollectionSelect,
|
||||
$findSelect,
|
||||
$findInput,
|
||||
that = Ox.FormElementGroup({
|
||||
elements: [].concat(pandora.user.ui._collection ? [
|
||||
$findCollectionSelect = Ox.Select({
|
||||
items: [
|
||||
{id: 'all', title: Ox._('Find: All {0}', [Ox._('Documents')])},
|
||||
{id: 'collection', title: Ox._('Find: This Collection')}
|
||||
],
|
||||
overlap: 'right',
|
||||
type: 'image',
|
||||
tooltip: Ox._('Find: This Collection'),
|
||||
value: 'collection'
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
$findCollectionSelect.options({
|
||||
tooltip: Ox.getObjectById(
|
||||
$findCollectionSelect.options('items'),
|
||||
data.value
|
||||
).title
|
||||
});
|
||||
$findInput.focusInput(true);
|
||||
}
|
||||
}),
|
||||
] : [], [
|
||||
$findSelect = Ox.Select({
|
||||
id: 'select',
|
||||
items: pandora.site.documentKeys.filter(function(key) {
|
||||
return key.find;
|
||||
}).map(function(key) {
|
||||
return {
|
||||
id: key.id,
|
||||
title: Ox._('Find: {0}', [Ox._(key.title)])
|
||||
};
|
||||
}),
|
||||
overlap: 'right',
|
||||
value: findKey,
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(data) {
|
||||
//pandora.$ui.mainMenu.checkItem('findMenu_find_' + data.value);
|
||||
$findInput.options({
|
||||
autocomplete: autocompleteFunction(),
|
||||
placeholder: ''
|
||||
}).focusInput(true);
|
||||
previousFindKey = data.value;
|
||||
}
|
||||
}),
|
||||
$findInput = Ox.Input({
|
||||
autocomplete: autocompleteFunction(),
|
||||
autocompleteSelect: true,
|
||||
autocompleteSelectHighlight: true,
|
||||
autocompleteSelectMaxWidth: 256,
|
||||
autocompleteSelectSubmit: true,
|
||||
clear: true,
|
||||
clearTooltip: Ox._('Click to clear or doubleclick to reset query'),
|
||||
id: 'input',
|
||||
placeholder: findKey == 'advanced' ? Ox._('Edit Query...') : '',
|
||||
value: findValue,
|
||||
width: 192
|
||||
})
|
||||
.bindEvent({
|
||||
clear: function() {
|
||||
hasPressedClear = true;
|
||||
},
|
||||
focus: function(data) {
|
||||
if ($findSelect.value() == 'advanced') {
|
||||
if (hasPressedClear) {
|
||||
pandora.UI.set({find: pandora.site.user.ui.find});
|
||||
that.updateElement();
|
||||
hasPressedClear = false;
|
||||
}
|
||||
$findInput.blurInput();
|
||||
//fixme advanced find dialog for documents
|
||||
//pandora.$ui.filterDialog = pandora.ui.filterDialog().open();
|
||||
}
|
||||
},
|
||||
submit: function(data) {
|
||||
var findInList = pandora.user.ui._collection
|
||||
&& $findCollectionSelect.value() == 'collection',
|
||||
key = $findSelect.value(),
|
||||
conditions = [].concat(
|
||||
findInList ? [{
|
||||
key: 'collection',
|
||||
value: pandora.user.ui._collection,
|
||||
operator: '=='
|
||||
}] : [],
|
||||
data.value ? [{
|
||||
key: key,
|
||||
value: data.value,
|
||||
operator: '='
|
||||
}] : []
|
||||
);
|
||||
pandora.UI.set({
|
||||
findDocuments: {conditions: conditions, operator: '&'}
|
||||
});
|
||||
}
|
||||
})
|
||||
]),
|
||||
id: 'findElement'
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
margin: '4px'
|
||||
});
|
||||
function autocompleteFunction() {
|
||||
var key = !that
|
||||
? pandora.user.ui._findDocumentsState.key
|
||||
: that.value()[pandora.user.ui._collection ? 1 : 0],
|
||||
findKey = Ox.getObjectById(pandora.site.documentFindKeys, key);
|
||||
return findKey && findKey.autocomplete ? function(value, callback) {
|
||||
value === '' && Ox.Log('', 'Warning: autocomplete function should never be called with empty value');
|
||||
pandora.api.autocompleteDocuments({
|
||||
key: key,
|
||||
query: {
|
||||
conditions: pandora.user.ui._collection
|
||||
&& $findCollectionSelect.value() == 'collection'
|
||||
? [{key: 'collection', value: pandora.user.ui._collection, operator: '=='}] : [],
|
||||
operator: '&'
|
||||
},
|
||||
range: [0, 20],
|
||||
sort: findKey.autocompleteSort,
|
||||
value: value
|
||||
}, function(result) {
|
||||
callback(result.data.items.map(function(item) {
|
||||
return Ox.decodeHTMLEntities(item);
|
||||
}));
|
||||
});
|
||||
} : null;
|
||||
}
|
||||
that.updateElement = function() {
|
||||
var findState = pandora.user.ui._findDocumentsState;
|
||||
$findSelect.value(findState.key);
|
||||
$findInput.options(
|
||||
findState.key == 'advanced' ? {
|
||||
placeholder: Ox._('Edit Query...'),
|
||||
value: ''
|
||||
} : {
|
||||
autocomplete: autocompleteFunction(),
|
||||
placeholder: '',
|
||||
value: findState.value
|
||||
}
|
||||
);
|
||||
};
|
||||
return that;
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
pandora.ui.folderBrowserBar = function(id, section) {
|
||||
section = section || pandora.user.ui.section;
|
||||
var ui = pandora.user.ui,
|
||||
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section),
|
||||
folderItems = pandora.getFolderItems(section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
that = Ox.Bar({
|
||||
size: 24
|
||||
|
|
|
@ -7,7 +7,7 @@ pandora.ui.folderBrowserList = function(id, section) {
|
|||
var ui = pandora.user.ui,
|
||||
columnWidth = (ui.sidebarSize - Ox.UI.SCROLLBAR_SIZE - (section != 'texts' ? 96 : 48)) / 2,
|
||||
i = Ox.getIndexById(pandora.site.sectionFolders[section], id),
|
||||
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section),
|
||||
folderItems = pandora.getFolderItems(section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
that = Ox.TableList({
|
||||
columns: [
|
||||
|
@ -152,7 +152,12 @@ pandora.ui.folderBrowserList = function(id, section) {
|
|||
// not-featured list may be in the user's favorites folder
|
||||
keys: id == 'featured' ? ['subscribed'] : [],
|
||||
pageLength: 1000,
|
||||
selected: pandora.getListData().folder == id ? [section == 'items' ? ui._list : ui[section.slice(0, -1)]] : [],
|
||||
selected: pandora.getListData().folder == id
|
||||
? [{
|
||||
items: ui._list,
|
||||
documents: ui._documentlist
|
||||
}[section] || ui[section.slice(0, -1)]]
|
||||
: [],
|
||||
sort: [{key: 'name', operator: '+'}],
|
||||
unique: 'id'
|
||||
})
|
||||
|
@ -226,6 +231,15 @@ pandora.ui.folderBrowserList = function(id, section) {
|
|||
operator: '&'
|
||||
}
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
pandora.UI.set({
|
||||
findDocuments: {
|
||||
conditions: list ? [
|
||||
{key: 'list', value: data.ids[0], operator: '=='}
|
||||
] : [],
|
||||
operator: '&'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pandora.UI.set(section.slice(0, -1), list);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ pandora.ui.folderList = function(id, section) {
|
|||
section = section || pandora.user.section;
|
||||
var ui = pandora.user.ui,
|
||||
i = Ox.getIndexById(pandora.site.sectionFolders[section], id),
|
||||
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section),
|
||||
folderItems = pandora.getFolderItems(section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
|
||||
$placeholder,
|
||||
|
@ -20,9 +20,7 @@ pandora.ui.folderList = function(id, section) {
|
|||
},
|
||||
format: function(value, data) {
|
||||
return $('<img>').attr({
|
||||
src: '/' + folderItem.toLowerCase() + '/'
|
||||
+ encodeURIComponent(data.id) + '/icon.jpg?'
|
||||
+ data.modified
|
||||
src: pandora.getListIcon(section, data.id, '', data.modified)
|
||||
}).css({
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
|
@ -337,7 +335,7 @@ pandora.ui.folderList = function(id, section) {
|
|||
pandora.api['unsubscribeFrom' + folderItem]({
|
||||
id: data.ids[0]
|
||||
}, function(result) {
|
||||
Ox.Request.clearCache('findList');
|
||||
Ox.Request.clearCache('find' + folderItems);
|
||||
that.reloadList();
|
||||
});
|
||||
} else if (id == 'featured' && canEditFeatured) {
|
||||
|
@ -346,7 +344,7 @@ pandora.ui.folderList = function(id, section) {
|
|||
id: data.ids[0],
|
||||
status: 'public'
|
||||
}, function(result) {
|
||||
Ox.Request.clearCache('findList');
|
||||
Ox.Request.clearCache('find' + folderItems);
|
||||
// fixme: duplicated
|
||||
if (result.data.user == pandora.user.username || result.data.subscribed) {
|
||||
pandora.$ui.folderList[
|
||||
|
@ -415,6 +413,20 @@ pandora.ui.folderList = function(id, section) {
|
|||
: that.value(list).view
|
||||
: void 0
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
pandora.UI.set({
|
||||
findDocuments: {
|
||||
conditions: list ? [
|
||||
{key: 'collection', value: list, operator: '=='}
|
||||
] : [],
|
||||
operator: '&'
|
||||
},
|
||||
collectionView: list
|
||||
? pandora.user.ui.collections[list]
|
||||
? pandora.user.ui.collections[list].view
|
||||
: that.value(list).view
|
||||
: void 0
|
||||
});
|
||||
} else {
|
||||
pandora.UI.set(section.slice(0, -1), list);
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@ pandora.ui.folderPlaceholder = function(id, section) {
|
|||
.css({
|
||||
height: '14px',
|
||||
padding: '1px 4px',
|
||||
});
|
||||
}),
|
||||
folderItems = pandora.getFolderItems(section);
|
||||
that.updateText = function(string, isFind) {
|
||||
return that.html(
|
||||
string != 'volumes'
|
||||
? Ox._('No {0} {1}' + (isFind ? ' found' : ''),
|
||||
[Ox._(string), Ox._(section == 'items' ? 'lists' : section)])
|
||||
[Ox._(string), Ox._(folderItems.toLowerCase())])
|
||||
: Ox._('No local volumes')
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,10 +10,13 @@ pandora.ui.folders = function(section) {
|
|||
pandora.resizeFolders();
|
||||
}
|
||||
}),
|
||||
editable = (ui[
|
||||
section == 'items' ? '_list' : section.slice(0, -1)
|
||||
] || '').split(':')[0] == pandora.user.username,
|
||||
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section),
|
||||
editable = (ui[{
|
||||
items: '_list',
|
||||
edits: 'edit',
|
||||
documents: '_collection',
|
||||
texts: 'text'
|
||||
}[section]] || '').split(':')[0] == pandora.user.username,
|
||||
folderItems = pandora.getFolderItems(section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
|
||||
initCounter = 0,
|
||||
|
@ -38,21 +41,21 @@ pandora.ui.folders = function(section) {
|
|||
: Ox._('To create and share your own {0}, please sign up or sign in.', [section])
|
||||
)];
|
||||
} else {
|
||||
if (section == 'items') {
|
||||
if (Ox.contains(pandora.site.listSections, section)) {
|
||||
extras = [
|
||||
pandora.$ui.personalListsMenu = Ox.MenuButton({
|
||||
items: [
|
||||
{ id: 'newlist', title: Ox._('New List'), keyboard: 'control n' },
|
||||
{ id: 'newlistfromselection', title: Ox._('New List from Selection'), keyboard: 'shift control n', disabled: ui.listSelection.length == 0 },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart List'), keyboard: 'alt control n' },
|
||||
{ id: 'newsmartlistfromresults', title: Ox._('New Smart List from Results'), keyboard: 'shift alt control n' },
|
||||
{ id: 'newlist', title: Ox._('New {0}', [Ox._(folderItem)]), keyboard: 'control n' },
|
||||
{ id: 'newlistfromselection', title: Ox._('New {0} from Selection', [Ox._(folderItem)]), keyboard: 'shift control n', disabled: ui.listSelection.length == 0 },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart {0}', [Ox._(folderItem)]), keyboard: 'alt control n' },
|
||||
{ id: 'newsmartlistfromresults', title: Ox._('New Smart {0} from Results', [Ox._(folderItem)]), keyboard: 'shift alt control n' },
|
||||
{},
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected List'), keyboard: 'control d', disabled: !ui._list },
|
||||
{ id: 'editlist', title: Ox._('Edit Selected List...'), keyboard: 'control e', disabled: !editable },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected List...'), keyboard: 'delete', disabled: !editable }
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected {0}', [Ox._(folderItem)]), keyboard: 'control d', disabled: !ui._list },
|
||||
{ id: 'editlist', title: Ox._('Edit Selected {0}...', [Ox._(folderItem)]), keyboard: 'control e', disabled: !editable },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected {0}...', [Ox._(folderItem)]), keyboard: 'delete', disabled: !editable }
|
||||
],
|
||||
title: 'edit',
|
||||
tooltip: Ox._('Manage Personal Lists'),
|
||||
tooltip: Ox._('Manage Personal ' + folderItems),
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
|
@ -64,7 +67,7 @@ pandora.ui.folders = function(section) {
|
|||
], data.id)) {
|
||||
pandora.addList(data.id.indexOf('smart') > -1, data.id.indexOf('from') > -1);
|
||||
} else if (data.id == 'duplicatelist') {
|
||||
pandora.addList(pandora.user.ui._list);
|
||||
pandora.addList(ui._list);
|
||||
} else if (data.id == 'editlist') {
|
||||
pandora.ui.listDialog().open();
|
||||
} else if (data.id == 'deletelist') {
|
||||
|
@ -222,9 +225,17 @@ pandora.ui.folders = function(section) {
|
|||
pandora.$ui.folderList.featured.options({selected: [listData.id]});
|
||||
} else {
|
||||
// and nowhere else
|
||||
if (section == 'items') {
|
||||
pandora.UI.set({
|
||||
find: pandora.site.user.ui.find
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
pandora.UI.set({
|
||||
findDocuments: pandora.site.user.ui.findDocuments
|
||||
});
|
||||
} else {
|
||||
Ox.print('unknown section', section);
|
||||
}
|
||||
}
|
||||
}
|
||||
pandora.$ui.folderBrowser.favorite.replaceWith(
|
||||
|
@ -280,9 +291,17 @@ pandora.ui.folders = function(section) {
|
|||
pandora.$ui.folderList.favorite.options({selected: [listData.id]});
|
||||
} else {
|
||||
// and nowhere else
|
||||
if (section == 'items') {
|
||||
pandora.UI.set({
|
||||
find: pandora.site.user.ui.find
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
pandora.UI.set({
|
||||
findDocuments: pandora.site.user.ui.findDocuments
|
||||
});
|
||||
} else {
|
||||
Ox.print('unknown section', section);
|
||||
}
|
||||
}
|
||||
}
|
||||
pandora.$ui.folderBrowser.featured.replaceWith(
|
||||
|
@ -336,7 +355,7 @@ pandora.ui.folders = function(section) {
|
|||
},
|
||||
toggle: function(data) {
|
||||
data.collapsed && pandora.$ui.folderList[folder.id].loseFocus();
|
||||
pandora.UI.set('showFolder.items.' + folder.id, !data.collapsed);
|
||||
pandora.UI.set('showFolder.' + section + '.' + folder.id, !data.collapsed);
|
||||
pandora.resizeFolders();
|
||||
}
|
||||
});
|
||||
|
@ -378,7 +397,7 @@ pandora.ui.folders = function(section) {
|
|||
}).bindEvent({
|
||||
click: function() {
|
||||
var $dialog = pandora.ui.iconDialog({
|
||||
buttons: title != Ox._('Featured Lists') ? [
|
||||
buttons: title != Ox._('Featured ' + folderItems) ? [
|
||||
Ox.Button({title: Ox._('Sign Up...')}).bindEvent({
|
||||
click: function() {
|
||||
$dialog.close();
|
||||
|
@ -441,6 +460,17 @@ pandora.ui.folders = function(section) {
|
|||
}
|
||||
*/
|
||||
},
|
||||
pandora_finddocuments: function() {
|
||||
var folder = pandora.getListData().folder,
|
||||
list = pandora.user.ui._collection,
|
||||
previousList = pandora.UI.getPrevious()._collection;
|
||||
if (list != previousList) {
|
||||
Ox.forEach(pandora.$ui.folderList, function($list, id) {
|
||||
id != folder && $list.options('selected', []);
|
||||
});
|
||||
folder && pandora.$ui.folderList[folder].options({selected: [list]});
|
||||
}
|
||||
},
|
||||
pandora_text: function() {
|
||||
if (!pandora.user.ui.text) {
|
||||
Ox.forEach(pandora.$ui.folderList, function($list, id) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
pandora.ui.info = function() {
|
||||
|
||||
var ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
view = getView(),
|
||||
|
||||
|
@ -13,6 +13,11 @@ pandora.ui.info = function() {
|
|||
toggle: function(data) {
|
||||
pandora.UI.set({showInfo: !data.collapsed});
|
||||
},
|
||||
pandora_documentlist: function() {
|
||||
if (pandora.user.ui._collection != pandora.UI.getPrevious('_collection')) {
|
||||
updateInfo();
|
||||
}
|
||||
},
|
||||
pandora_edit: updateInfo,
|
||||
pandora_find: function() {
|
||||
if (pandora.user.ui._list != pandora.UI.getPrevious('_list')) {
|
||||
|
@ -29,6 +34,8 @@ pandora.ui.info = function() {
|
|||
updateInfo();
|
||||
}
|
||||
},
|
||||
pandora_document: updateInfo,
|
||||
pandora_collectionselection: updateInfo,
|
||||
pandora_text: updateInfo
|
||||
});
|
||||
|
||||
|
@ -184,16 +191,14 @@ pandora.ui.info = function() {
|
|||
|
||||
pandora.ui.listInfo = function() {
|
||||
var ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
list = pandora.user.ui.section == 'items' ? pandora.user.ui._list : ui[folderItem.toLowerCase()],
|
||||
canEditFeaturedLists = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
|
||||
that = Ox.Element().css({padding: '16px', textAlign: 'center'}),
|
||||
$icon = Ox.Element('<img>')
|
||||
.attr({
|
||||
src: list
|
||||
? '/' + folderItem.toLowerCase() + '/' + encodeURIComponent(list) + '/icon256.jpg?' + Ox.uid()
|
||||
: '/static/png/icon.png'
|
||||
src: list ? pandora.getListIcon(ui.section, list, 256) : '/static/png/icon.png'
|
||||
})
|
||||
.css(getIconCSS())
|
||||
.appendTo(that),
|
||||
|
|
|
@ -22,7 +22,7 @@ pandora.ui.item = function() {
|
|||
if (result.status.code == 200) {
|
||||
// we want to cache the title in any way, so that after closing
|
||||
// a dialog and getting to this item, the title is correct
|
||||
var documentTitle = pandora.getDocumentTitle(result.data);
|
||||
var documentTitle = pandora.getWindowTitle(result.data);
|
||||
document.title = pandora.getPageTitle(document.location.pathname) || documentTitle;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ pandora.ui.listDialog = function(section) {
|
|||
),
|
||||
ui = pandora.user.ui,
|
||||
width = getWidth(section),
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(pandora.user.ui.section),
|
||||
folderItem = folderItems.slice(0, -1);
|
||||
Ox.getObjectById(tabs, section).selected = true;
|
||||
|
||||
|
@ -26,7 +26,10 @@ pandora.ui.listDialog = function(section) {
|
|||
} else if (id == 'icon') {
|
||||
return pandora.$ui.listIconPanel = pandora.ui.listIconPanel(listData);
|
||||
} else if (id == 'query') {
|
||||
return pandora.$ui.filterForm = pandora.ui.filterForm({
|
||||
return pandora.$ui.filterForm = (pandora.user.ui.section == 'documents'
|
||||
? pandora.ui.documentFilterForm
|
||||
: pandora.ui.filterForm
|
||||
)({
|
||||
mode: 'list',
|
||||
list: listData
|
||||
})
|
||||
|
@ -158,7 +161,7 @@ pandora.ui.listDialog = function(section) {
|
|||
pandora.ui.listGeneralPanel = function(listData) {
|
||||
var that = Ox.Element(),
|
||||
ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1);
|
||||
pandora.api['find' + folderItems]({
|
||||
query: {conditions: [{key: 'id', value: listData.id, operator: '=='}]},
|
||||
|
@ -171,7 +174,7 @@ pandora.ui.listGeneralPanel = function(listData) {
|
|||
tooltip: Ox._('Doubleclick to edit icon')
|
||||
})
|
||||
.attr({
|
||||
src: pandora.getMediaURL('/' + folderItem.toLowerCase() + '/' + encodeURIComponent(listData.id) + '/icon256.jpg?' + Ox.uid())
|
||||
src: pandora.getListIcon(ui.section, listData.id, 256)
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
|
@ -382,13 +385,16 @@ pandora.ui.listIconPanel = function(listData) {
|
|||
quarters = ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
|
||||
ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
|
||||
|
||||
$iconPanel = Ox.Element(),
|
||||
|
||||
$icon = $('<img>')
|
||||
.attr({src: pandora.getMediaURL('/' + folderItem.toLowerCase() + '/' + encodeURIComponent(listData.id) + '/icon256.jpg?' + Ox.uid())})
|
||||
.attr({
|
||||
src: pandora.getListIcon(ui.section, listData.id, 256)
|
||||
})
|
||||
.css({position: 'absolute', borderRadius: '64px', margin: '16px'})
|
||||
.appendTo($iconPanel),
|
||||
|
||||
|
@ -399,8 +405,6 @@ pandora.ui.listIconPanel = function(listData) {
|
|||
$list = Ox.Element(),
|
||||
|
||||
ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItem = folderItems.slice(0, -1),
|
||||
|
||||
that = Ox.SplitPanel({
|
||||
elements: [
|
||||
|
@ -586,9 +590,7 @@ pandora.ui.listIconPanel = function(listData) {
|
|||
posterFrames: posterFrames
|
||||
}, function() {
|
||||
$icon.attr({
|
||||
src: pandora.getMediaURL('/' + folderItem.toLowerCase()
|
||||
+ '/' + encodeURIComponent(listData.id) + '/icon256.jpg?' + Ox.uid()
|
||||
)
|
||||
src: pandora.getListIcon(ui.section, listData.id, 256)
|
||||
});
|
||||
pandora.$ui.folderList[listData.folder].$element
|
||||
.find('img[src*="'
|
||||
|
@ -596,10 +598,7 @@ pandora.ui.listIconPanel = function(listData) {
|
|||
+ '/"]'
|
||||
)
|
||||
.attr({
|
||||
src: pandora.getMediaURL('/' + folderItem.toLowerCase()
|
||||
+ '/' + encodeURIComponent(listData.id)
|
||||
+ '/icon.jpg?' + Ox.uid()
|
||||
)
|
||||
src: pandora.getListIcon(ui.section, listData.id, 256)
|
||||
});
|
||||
pandora.$ui.info.updateListInfo();
|
||||
});
|
||||
|
@ -619,7 +618,7 @@ pandora.ui.listIconPanel = function(listData) {
|
|||
pandora.api.find(Ox.extend(data, {
|
||||
query: {
|
||||
conditions: (
|
||||
ui.section == 'items'
|
||||
Ox.contains(pandora.site.listSections, ui.section)
|
||||
? [{key: 'list', value: listData.id, operator: '=='}]
|
||||
: []).concat(
|
||||
value !== ''
|
||||
|
|
|
@ -253,6 +253,14 @@ pandora.ui.mainMenu = function() {
|
|||
} else {
|
||||
that.checkItem('allitems');
|
||||
}
|
||||
} else if (ui.section == 'documents') {
|
||||
if (data.checked) {
|
||||
pandora.UI.set({
|
||||
findDocuments: {conditions: [], operator: '&'}
|
||||
});
|
||||
} else {
|
||||
that.checkItem('allitems');
|
||||
}
|
||||
} else {
|
||||
pandora.UI.set(ui.section.slice(0, -1), '');
|
||||
}
|
||||
|
@ -268,6 +276,10 @@ pandora.ui.mainMenu = function() {
|
|||
} else {
|
||||
pandora.UI.set({itemSort: [{key: value, operator: pandora.getSortOperator(value)}]});
|
||||
}
|
||||
} else if (data.id == 'documentorder') {
|
||||
pandora.UI.set({collectionSort: [{key: ui.collectionSort[0].key, operator: value == 'ascending' ? '+' : '-'}]});
|
||||
} else if (data.id == 'documentsort') {
|
||||
pandora.UI.set({collectionSort: [{key: value, operator: pandora.getDocumentSortOperator(value)}]});
|
||||
} else if (data.id == 'find') {
|
||||
if (value) {
|
||||
pandora.$ui.findSelect.value(value);
|
||||
|
@ -351,6 +363,15 @@ pandora.ui.mainMenu = function() {
|
|||
operator: '&'
|
||||
}
|
||||
});
|
||||
} else if (ui.section == 'documents') {
|
||||
pandora.UI.set({
|
||||
findDocuments: {
|
||||
conditions: data.checked ? [
|
||||
{key: 'collection', value: data.id.slice(8).replace(/\t/g, '_'), operator: '=='}
|
||||
] : [],
|
||||
operator: '&'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pandora.UI.set(ui.section.slice(0, -1), data.id.slice(8).replace(/\t/g, '_'));
|
||||
}
|
||||
|
@ -389,7 +410,11 @@ pandora.ui.mainMenu = function() {
|
|||
} else if (data.id == 'editlist') {
|
||||
pandora.ui.listDialog().open();
|
||||
} else if (data.id == 'add') {
|
||||
if (ui.section == 'documents') {
|
||||
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog().open();
|
||||
} else {
|
||||
pandora.$ui.addItemDialog = pandora.ui.addItemDialog().open();
|
||||
}
|
||||
} else if (data.id == 'edit') {
|
||||
pandora.ui.editItemDialog().open();
|
||||
} else if (data.id == 'deletelist') {
|
||||
|
@ -454,6 +479,13 @@ pandora.ui.mainMenu = function() {
|
|||
pandora.UI.set({listSelection: items});
|
||||
pandora.reloadList();
|
||||
});
|
||||
} else if (ui.section == 'documents') {
|
||||
var items = pandora.clipboard.paste('document');
|
||||
items.length && pandora.doHistory('paste', items, ui._collection, function() {
|
||||
//fixme:
|
||||
//pandora.UI.set({listSelection: items});
|
||||
//pandora.reloadList();
|
||||
});
|
||||
} else if (ui.section == 'edits') {
|
||||
var clips = pandora.clipboard.paste('clip');
|
||||
clips.length && pandora.doHistory('paste', clips, ui.edit, function(result) {
|
||||
|
@ -489,6 +521,26 @@ pandora.ui.mainMenu = function() {
|
|||
}).open();
|
||||
});
|
||||
}
|
||||
} else if (ui.section == 'documents') {
|
||||
var files;
|
||||
if (ui.document) {
|
||||
files = [pandora.$ui.document.info()];
|
||||
} else {
|
||||
files = pandora.$ui.list.options('selected').map(function(id) {
|
||||
return pandora.$ui.list.value(id);
|
||||
});
|
||||
}
|
||||
pandora.ui.deleteDocumentDialog(
|
||||
files,
|
||||
function() {
|
||||
Ox.Request.clearCache();
|
||||
if (ui.document) {
|
||||
pandora.UI.set({document: ''});
|
||||
} else {
|
||||
pandora.$ui.list.reloadList()
|
||||
}
|
||||
}
|
||||
).open();
|
||||
} else if (ui.section == 'edits') {
|
||||
var clips = pandora.$ui.editPanel.getSelectedClips();
|
||||
pandora.doHistory('delete', clips, ui.edit, function(result) {
|
||||
|
@ -594,6 +646,30 @@ pandora.ui.mainMenu = function() {
|
|||
pandora.$ui.errorlogsDialog = pandora.ui.errorlogsDialog().open();
|
||||
}
|
||||
},
|
||||
pandora_collectionsort: function(data) {
|
||||
that.checkItem('sortMenu_sortitems_' + data.value[0].key);
|
||||
that.checkItem('sortMenu_orderitems_' + (
|
||||
data.value[0].operator == '+' ? 'ascending' : 'descending')
|
||||
);
|
||||
},
|
||||
pandora_finddocuments: function() {
|
||||
var action = pandora.getListData().editable ? 'enableItem' : 'disableItem',
|
||||
list = ui._collection,
|
||||
previousList = pandora.UI.getPrevious()._collection;
|
||||
if (list != previousList) {
|
||||
that.uncheckItem(previousList == '' ? 'allitems' : 'viewlist' + previousList.replace(/_/g, Ox.char(9)));
|
||||
that.checkItem(list == '' ? 'allitems' : 'viewlist' + list.replace(/_/g, '\t'));
|
||||
}
|
||||
that[ui._list ? 'enableItem' : 'disableItem']('duplicatelist');
|
||||
that[action]('editlist');
|
||||
that[action]('deletelist');
|
||||
that[ui.listSelection.length ? 'enableItem' : 'disableItem']('newlistfromselection');
|
||||
that.replaceMenu('itemMenu', getItemMenu());
|
||||
that[ui.find.conditions.length ? 'enableItem' : 'disableItem']('clearquery');
|
||||
that[Ox.sum(ui._filterState.map(function(filterState) {
|
||||
return filterState.selected.length;
|
||||
})) > 0 ? 'enableItem' : 'disableItem']('clearfilters');
|
||||
},
|
||||
pandora_edit: function() {
|
||||
var action = pandora.getListData().editable ? 'enableItem' : 'disableItem',
|
||||
edit = ui.edit,
|
||||
|
@ -690,6 +766,14 @@ pandora.ui.mainMenu = function() {
|
|||
pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem'
|
||||
]('findsimilar');
|
||||
},
|
||||
pandora_collectionselection: function(data) {
|
||||
var action = data.value.length ? 'enableItem' : 'disableItem';
|
||||
that[action]('newlistfromselection');
|
||||
that.replaceMenu('itemMenu', getItemMenu());
|
||||
that[
|
||||
pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem'
|
||||
]('findsimilar');
|
||||
},
|
||||
pandora_listselection: function(data) {
|
||||
var action = data.value.length ? 'enableItem' : 'disableItem';
|
||||
that[action]('newlistfromselection');
|
||||
|
@ -983,6 +1067,156 @@ pandora.ui.mainMenu = function() {
|
|||
elements[Ox.mod((index + direction), elements.length)].gainFocus();
|
||||
}
|
||||
|
||||
function getDocumentMenu() {
|
||||
var listData = pandora.getListData(),
|
||||
deleteVerb = ui._collection ? Ox._('Remove') : Ox._('Delete'),
|
||||
isEditable = listData.editable && listData.type == 'static',
|
||||
isListView = !ui.document,
|
||||
listName = ui._collection ? Ox._('from List') : Ox._('from Archive'),
|
||||
listItemsName = 'Documents',
|
||||
selectionItems = ui.collectionSelection.length,
|
||||
selectionItemName = (
|
||||
selectionItems > 1 ? Ox.formatNumber(selectionItems) + ' ' : ''
|
||||
) + Ox._(selectionItems == 1 ? 'Document' : 'Documents'),
|
||||
clipboardItems = pandora.clipboard.items('document'),
|
||||
clipboardItemName = clipboardItems == 0 ? ''
|
||||
: (
|
||||
clipboardItems > 1 ? Ox.formatNumber(clipboardItems) + ' ' : ''
|
||||
) + Ox._(clipboardItems == 1 ? 'Document' : 'Documents'),
|
||||
canEdit = false, //fixme
|
||||
canDelete = (
|
||||
ui.document || ui.collectionSelection.length
|
||||
) && (
|
||||
pandora.site.capabilities.canRemoveDocuments[pandora.user.level] ||
|
||||
ui.collectionSelection.every(function(item) {
|
||||
return pandora.$ui.list.value(item, 'editable');
|
||||
})
|
||||
),
|
||||
canSelect = isListView,
|
||||
canCopy = ui.collectionSelection.length,
|
||||
canCut = canCopy && isEditable,
|
||||
canPaste = isListView && isEditable,
|
||||
canAdd = canCopy && clipboardItems > 0,
|
||||
historyItems = pandora.history.items(),
|
||||
undoText = pandora.history.undoText(),
|
||||
redoText = pandora.history.redoText();
|
||||
return { id: 'itemMenu', title: Ox._('Item'), items: [
|
||||
{ id: 'add', title: Ox._('Add {0}...', [Ox._('Document')]), disabled: !pandora.site.capabilities.canAddItems[pandora.user.level] },
|
||||
{ id: 'edit', title: Ox._('Edit {0}...', [Ox._('Document')]), disabled: true /*fixme: !canEdit */ },
|
||||
{},
|
||||
{ id: 'selectall', title: Ox._('Select All {0}', [listItemsName]), disabled: !canSelect, keyboard: 'control a' },
|
||||
{ id: 'selectnone', title: Ox._('Select None'), disabled: !canSelect, keyboard: 'shift control a' },
|
||||
{ id: 'invertselection', title: Ox._('Invert Selection'), disabled: !canSelect, keyboard: 'alt control a' },
|
||||
{},
|
||||
{ id: 'cut', title: Ox._('Cut {0}', [selectionItemName]), disabled: !canCut, keyboard: 'control x' },
|
||||
{ id: 'cutadd', title: Ox._('Cut and Add to Clipboard'), disabled: !canCut || !canAdd, keyboard: 'shift control x' },
|
||||
{ id: 'copy', title: Ox._('Copy {0}', [selectionItemName]), disabled: !canCopy, keyboard: 'control c' },
|
||||
{ id: 'copyadd', title: Ox._('Copy and Add to Clipboard'), disabled: !canCopy || !canAdd, keyboard: 'shift control c' },
|
||||
{ id: 'paste', title: clipboardItems == 0 ? Ox._('Paste') : Ox._('Paste {0}', [clipboardItemName]), disabled: !canPaste, keyboard: 'control v' },
|
||||
{ id: 'clearclipboard', title: Ox._('Clear Clipboard'), disabled: !clipboardItems},
|
||||
{},
|
||||
{ id: 'delete', title: Ox._('{0} {1} {2}', [deleteVerb, selectionItemName, listName]), disabled: !canDelete, keyboard: 'delete' },
|
||||
{},
|
||||
{ id: 'undo', title: undoText ? Ox._('Undo {0}', [undoText]) : Ox._('Undo'), disabled: !undoText, keyboard: 'control z' },
|
||||
{ id: 'redo', title: redoText ? Ox._('Redo {0}', [redoText]) : Ox._('Redo'), disabled: !redoText, keyboard: 'shift control z' },
|
||||
{ id: 'clearhistory', title: Ox._('Clear History'), disabled: !historyItems }
|
||||
] };
|
||||
|
||||
}
|
||||
|
||||
function getCollectionMenu() {
|
||||
var itemNamePlural = pandora.getFolderItems(ui.section),
|
||||
itemNameSingular = itemNamePlural.slice(0, -1),
|
||||
disableEdit = isGuest || !ui._collection,
|
||||
disableFromSelection = isGuest || ui.collectionSelection.length == 0;
|
||||
|
||||
return { id: 'listMenu', title: Ox._(itemNameSingular == 'Collection' ? 'File' : itemNameSingular), items: [].concat(
|
||||
{
|
||||
id: 'allitems',
|
||||
title: pandora.getAllItemsTitle(),
|
||||
checked: !ui._collection,
|
||||
keyboard: 'shift control w'
|
||||
},
|
||||
['personal', 'favorite', 'featured'].map(function(folder) {
|
||||
return {
|
||||
id: folder + 'lists',
|
||||
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
|
||||
items: Ox.isUndefined(lists[folder])
|
||||
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
|
||||
: lists[folder].length == 0
|
||||
? [{id: 'nolists', title: Ox._('No {0} {1}',
|
||||
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
|
||||
: lists[folder].map(function(list) {
|
||||
return {
|
||||
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
|
||||
title: Ox.encodeHTMLEntities((
|
||||
folder == 'favorite' ? list.user + ': ' : ''
|
||||
) + list.name),
|
||||
checked: list.id == ui._collection
|
||||
};
|
||||
})
|
||||
};
|
||||
}),
|
||||
[
|
||||
{},
|
||||
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
|
||||
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableFromSelection, keyboard: 'shift control n' },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
|
||||
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
|
||||
{},
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
|
||||
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' },
|
||||
{},
|
||||
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' }
|
||||
]
|
||||
)};
|
||||
};
|
||||
|
||||
function getEditMenu() {
|
||||
var itemNameSingular = 'Edit',
|
||||
itemNamePlural = 'Edits',
|
||||
disableEdit = isGuest || !ui.edit;
|
||||
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
|
||||
{
|
||||
id: 'allitems',
|
||||
title: pandora.getAllItemsTitle(),
|
||||
checked: !ui.edit,
|
||||
keyboard: 'shift control w'
|
||||
},
|
||||
['personal', 'favorite', 'featured'].map(function(folder) {
|
||||
return {
|
||||
id: folder + 'lists',
|
||||
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
|
||||
items: Ox.isUndefined(lists[folder])
|
||||
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
|
||||
: lists[folder].length == 0
|
||||
? [{id: 'nolists', title: Ox._('No {0} {1}',
|
||||
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
|
||||
: lists[folder].map(function(list) {
|
||||
return {
|
||||
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
|
||||
title: Ox.encodeHTMLEntities((
|
||||
folder == 'favorite' ? list.user + ': ' : ''
|
||||
) + list.name),
|
||||
checked: list.id == ui.edit
|
||||
};
|
||||
})
|
||||
};
|
||||
}),
|
||||
[
|
||||
{},
|
||||
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
|
||||
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableEdit, keyboard: 'shift control n' },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
|
||||
{},
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
|
||||
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' }
|
||||
]
|
||||
)};
|
||||
}
|
||||
|
||||
function getFindMenu() {
|
||||
return { id: 'findMenu', title: Ox._('Find'), items: [
|
||||
{ id: 'find', title: Ox._('Find'), items: [
|
||||
|
@ -1005,7 +1239,62 @@ pandora.ui.mainMenu = function() {
|
|||
] };
|
||||
}
|
||||
|
||||
function getItemListMenu() {
|
||||
var itemNameSingular = 'List',
|
||||
itemNamePlural = 'Lists',
|
||||
disableEdit = isGuest || !ui._list,
|
||||
disableFromSelection = isGuest || ui.listSelection.length == 0;
|
||||
|
||||
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
|
||||
{
|
||||
id: 'allitems',
|
||||
title: pandora.getAllItemsTitle(),
|
||||
checked: !ui._list,
|
||||
keyboard: 'shift control w'
|
||||
},
|
||||
['personal', 'favorite', 'featured'].map(function(folder) {
|
||||
return {
|
||||
id: folder + 'lists',
|
||||
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
|
||||
items: Ox.isUndefined(lists[folder])
|
||||
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
|
||||
: lists[folder].length == 0
|
||||
? [{id: 'nolists', title: Ox._('No {0} {1}',
|
||||
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
|
||||
: lists[folder].map(function(list) {
|
||||
return {
|
||||
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
|
||||
title: Ox.encodeHTMLEntities((
|
||||
folder == 'favorite' ? list.user + ': ' : ''
|
||||
) + list.name),
|
||||
checked: list.id == ui._list
|
||||
};
|
||||
})
|
||||
};
|
||||
}),
|
||||
[
|
||||
{},
|
||||
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
|
||||
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableFromSelection, keyboard: 'shift control n' },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
|
||||
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
|
||||
{ id: 'neweditfromselection', title: Ox._('New Edit from Selection'), disabled: disableFromSelection },
|
||||
{ id: 'newsmarteditfromresults', title: Ox._('New Smart Edit from Results'), disabled: isGuest },
|
||||
{},
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
|
||||
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' },
|
||||
{},
|
||||
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' },
|
||||
{ id: 'tv', title: Ox._('TV'), keyboard: 'control space' }
|
||||
]
|
||||
)};
|
||||
};
|
||||
|
||||
function getItemMenu() {
|
||||
if (ui.section == 'documents') {
|
||||
return getDocumentMenu();
|
||||
}
|
||||
var listData = pandora.getListData(),
|
||||
deleteVerb = ui._list ? Ox._('Remove') : Ox._('Delete'),
|
||||
isEditable = listData.editable && listData.type == 'static',
|
||||
|
@ -1021,7 +1310,7 @@ pandora.ui.mainMenu = function() {
|
|||
&& ui.editView != 'annotations', // FIXME: focus
|
||||
listName = isVideoView || isClipView ? ''
|
||||
: ui.section == 'items' ? (
|
||||
ui._list ? Ox._('from List') : Ox._('from Archive')
|
||||
ui._? Ox._('from List') : Ox._('from Archive')
|
||||
)
|
||||
: Ox._('from Edit'),
|
||||
listItemsName = Ox._(
|
||||
|
@ -1057,15 +1346,18 @@ pandora.ui.mainMenu = function() {
|
|||
)
|
||||
) && pandora.$ui.list.value(ui.listSelection[0], 'editable')
|
||||
),
|
||||
canDelete = pandora.site.capabilities.canRemoveItems[pandora.user.level] || (
|
||||
canDelete = (
|
||||
ui.section == 'items' && (
|
||||
ui.item || (
|
||||
Ox.contains(['list', 'grid', 'clips', 'timelines'], ui.listView)
|
||||
&& ui.listSelection.length
|
||||
)
|
||||
) && ui.listSelection.every(function(item) {
|
||||
) && (
|
||||
pandora.site.capabilities.canRemoveItems[pandora.user.level] ||
|
||||
ui.listSelection.every(function(item) {
|
||||
return pandora.$ui.list.value(item, 'editable');
|
||||
})
|
||||
)
|
||||
),
|
||||
canSelect = isListView || isClipView || isEditView,
|
||||
canCopy = isListView ? ui.listSelection.length
|
||||
|
@ -1106,15 +1398,22 @@ pandora.ui.mainMenu = function() {
|
|||
}
|
||||
|
||||
function getListMenu() {
|
||||
var itemNameSingular = ui.section == 'items' ? 'List' : ui.section == 'edits' ? 'Edit' : 'Text',
|
||||
itemNamePlural = ui.section == 'items' ? 'Lists' : ui.section == 'edits' ? 'Edits' : 'Texts';
|
||||
return ({
|
||||
items: getItemListMenu,
|
||||
documents: getCollectionMenu,
|
||||
edits: getEditMenu,
|
||||
texts: getTextMenu
|
||||
}[ui.section])();
|
||||
}
|
||||
|
||||
function getTextMenu() {
|
||||
var itemNameSingular = 'Text',
|
||||
itemNamePlural = 'Texts';
|
||||
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
|
||||
{
|
||||
id: 'allitems',
|
||||
title: pandora.getAllItemsTitle(),
|
||||
checked: ui.section == 'items' ? !ui.item && !ui._list
|
||||
: ui.section == 'edits' ? !ui.edit
|
||||
: !ui.text,
|
||||
checked: !ui.text,
|
||||
keyboard: 'shift control w'
|
||||
},
|
||||
['personal', 'favorite', 'featured'].map(function(folder) {
|
||||
|
@ -1132,9 +1431,7 @@ pandora.ui.mainMenu = function() {
|
|||
title: Ox.encodeHTMLEntities((
|
||||
folder == 'favorite' ? list.user + ': ' : ''
|
||||
) + list.name),
|
||||
checked: ui.section == 'items' ? list.id == ui._list
|
||||
: ui.section == 'edits' ? list.id == ui.edit
|
||||
: list.id == ui.text
|
||||
checked: list.id == ui.text
|
||||
};
|
||||
})
|
||||
};
|
||||
|
@ -1142,38 +1439,47 @@ pandora.ui.mainMenu = function() {
|
|||
[
|
||||
{},
|
||||
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
|
||||
],
|
||||
ui.section == 'items' ? [
|
||||
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: isGuest || ui.listSelection.length == 0, keyboard: 'shift control n' },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
|
||||
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
|
||||
{ id: 'neweditfromselection', title: Ox._('New Edit from Selection'), disabled: isGuest || ui.listSelection.length == 0 },
|
||||
{ id: 'newsmarteditfromresults', title: Ox._('New Smart Edit from Results'), disabled: isGuest }
|
||||
] : ui.section == 'edits' ? [
|
||||
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: isGuest || !ui.edit, keyboard: 'shift control n' },
|
||||
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' }
|
||||
] : [
|
||||
{ id: 'newpdf', title: Ox._('New PDF'), disabled: isGuest, keyboard: 'alt control n' },
|
||||
],
|
||||
[
|
||||
{}
|
||||
],
|
||||
ui.section != 'texts' ? [
|
||||
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'control d' }
|
||||
] : [],
|
||||
[
|
||||
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'control e' },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'delete' }
|
||||
],
|
||||
ui.section == 'items' ? [
|
||||
{},
|
||||
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' },
|
||||
{ id: 'tv', title: Ox._('TV'), keyboard: 'control space' }
|
||||
] : []
|
||||
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: isGuest, keyboard: 'control e' },
|
||||
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: isGuest, keyboard: 'delete' }
|
||||
]
|
||||
)};
|
||||
};
|
||||
}
|
||||
|
||||
function getCollectionSortMenu() {
|
||||
var isClipView = false,
|
||||
clipItems = [].concat(!ui.document ? pandora.site.documentSortKeys.map(function(key) {
|
||||
return Ox.extend({
|
||||
checked: ui.collectionSort[0].key == key.id
|
||||
}, key);
|
||||
}) : []);
|
||||
return { id: 'sortMenu', title: Ox._('Sort'), items: [
|
||||
{ id: 'sortitems', title: Ox._('Sort {0} by', [Ox._('Documents')]), disabled: ui.document, items: [
|
||||
{ group: 'documentsort', min: 1, max: 1, items: pandora.site.documentSortKeys.map(function(key) {
|
||||
return Ox.extend({
|
||||
checked: ui.collectionSort[0].key == key.id
|
||||
}, key, {
|
||||
title: Ox._(key.title)
|
||||
});
|
||||
}) }
|
||||
] },
|
||||
{ id: 'orderitems', title: Ox._('Order {0}', [Ox._('Documents')]), disabled: ui.document, items: [
|
||||
{ group: 'documentorder', min: 1, max: 1, items: [
|
||||
{ id: 'ascending', title: Ox._('Ascending'), checked: (ui.collectionSort[0].operator || pandora.getSortOperator(ui.collectionSort[0].key)) == '+' },
|
||||
{ id: 'descending', title: Ox._('Descending'), checked: (ui.collectionSort[0].operator || pandora.getSortOperator(ui.collectionSort[0].key)) == '-' }
|
||||
]}
|
||||
] },
|
||||
{ id: 'advancedsort', title: Ox._('Advanced Sort...'), keyboard: 'shift control s', disabled: true },
|
||||
] };
|
||||
}
|
||||
|
||||
function getSortMenu() {
|
||||
|
||||
if (ui.section == 'documents') {
|
||||
return getCollectionSortMenu();
|
||||
}
|
||||
//fixme split items/clips menu
|
||||
var isClipView = pandora.isClipView(),
|
||||
clipItems = (isClipView ? pandora.site.clipKeys.map(function(key) {
|
||||
return Ox.extend(Ox.clone(key), {
|
||||
|
|
|
@ -21,6 +21,17 @@ pandora.ui.mainPanel = function() {
|
|||
orientation: 'horizontal'
|
||||
})
|
||||
.bindEvent({
|
||||
pandora_finddocuments: function() {
|
||||
var previousUI = pandora.UI.getPrevious();
|
||||
if (!previousUI.document && ui._list == previousUI._list) {
|
||||
that.replaceElement(1, pandora.$ui.documentPanel = pandora.ui.documentPanel());
|
||||
}
|
||||
},
|
||||
pandora_document: function(data) {
|
||||
if (!data.value || !data.previousValue) {
|
||||
that.replaceElement(1, pandora.$ui.documentPanel = pandora.ui.documentPanel());
|
||||
}
|
||||
},
|
||||
pandora_edit: function(data) {
|
||||
that.replaceElement(1, pandora.$ui.editPanel = pandora.ui.editPanel());
|
||||
},
|
||||
|
@ -92,6 +103,7 @@ pandora.ui.mainPanel = function() {
|
|||
function getRightPanel() {
|
||||
return ui.section == 'items' ? pandora.$ui.rightPanel = pandora.ui.rightPanel()
|
||||
: ui.section == 'edits' ? pandora.$ui.editPanel = pandora.ui.editPanel()
|
||||
: ui.section == 'documents' ? pandora.$ui.documentPanel = pandora.ui.documentPanel()
|
||||
: pandora.$ui.textPanel = pandora.ui.textPanel();
|
||||
}
|
||||
return that;
|
||||
|
|
|
@ -351,7 +351,11 @@ appPanel
|
|||
findKeys: data.site.itemKeys.filter(function(key) {
|
||||
return key.find;
|
||||
}),
|
||||
documentFindKeys: data.site.documentKeys.filter(function(key) {
|
||||
return key.find;
|
||||
}),
|
||||
itemsSection: pandora.site.itemName.plural.toLowerCase(),
|
||||
listSections: ['items', 'documents'],
|
||||
map: data.site.layers.some(function(layer) {
|
||||
return layer.type == 'place'
|
||||
}) ? 'manual' : data.site.layers.some(function(layer) {
|
||||
|
@ -364,6 +368,11 @@ appPanel
|
|||
{id: 'featured', title: 'Featured Lists', showBrowser: false},
|
||||
{id: 'volumes', title: 'Local Volumes'}
|
||||
],
|
||||
documents: [
|
||||
{id: 'personal', title: 'Personal Collections'},
|
||||
{id: 'favorite', title: 'Favorite Collections', showBrowser: false},
|
||||
{id: 'featured', title: 'Featured Collections', showBrowser: false}
|
||||
],
|
||||
edits: [
|
||||
{id: 'personal', title: 'Personal Edits'},
|
||||
{id: 'favorite', title: 'Favorite Edits', showBrowser: false},
|
||||
|
@ -375,7 +384,12 @@ appPanel
|
|||
{id: 'featured', title: 'Featured Texts', showBrowser: false}
|
||||
]
|
||||
},
|
||||
sortKeys: pandora.getSortKeys()
|
||||
sortKeys: pandora.getSortKeys(),
|
||||
documentSortKeys: pandora.getDocumentSortKeys(),
|
||||
collectionViews: [
|
||||
{id: 'list', title: Ox._('View as List')},
|
||||
{id: 'grid', title: Ox._('View as Grid')}
|
||||
]
|
||||
});
|
||||
pandora.site.listSettings = {};
|
||||
Ox.forEach(pandora.site.user.ui, function(val, key) {
|
||||
|
@ -383,6 +397,12 @@ appPanel
|
|||
pandora.site.listSettings[key] = key[4].toLowerCase()+ key.slice(5);
|
||||
}
|
||||
});
|
||||
pandora.site.collectionSettings = {};
|
||||
Ox.forEach(pandora.site.user.ui, function(val, key) {
|
||||
if (/^collection[A-Z]/.test(key)) {
|
||||
pandora.site.collectionSettings[key] = key[10].toLowerCase()+ key.slice(11);
|
||||
}
|
||||
});
|
||||
pandora.site.editSettings = {
|
||||
clip: '',
|
||||
'in': 0,
|
||||
|
|
|
@ -5,7 +5,7 @@ pandora.ui.sectionButtons = function(section) {
|
|||
buttons: [
|
||||
{id: 'items', title: Ox._(pandora.site.itemName.plural)},
|
||||
{id: 'edits', title: Ox._('Edits')},
|
||||
{id: 'texts', title: Ox._('Texts')}
|
||||
{id: 'documents', title: Ox._('Documents')}
|
||||
],
|
||||
id: 'sectionButtons',
|
||||
selectable: true,
|
||||
|
|
|
@ -7,7 +7,7 @@ pandora.ui.sectionSelect = function(section) {
|
|||
items: [
|
||||
{id: 'items', title: Ox._(pandora.site.itemName.plural)},
|
||||
{id: 'edits', title: Ox._('Edits')},
|
||||
{id: 'texts', title: Ox._('Texts')}
|
||||
{id: 'documents', title: Ox._('Documents')}
|
||||
],
|
||||
value: section || pandora.user.ui.section
|
||||
}).css({
|
||||
|
|
|
@ -11,6 +11,7 @@ pandora.ui.textPanel = function() {
|
|||
orientation: 'vertical'
|
||||
}),
|
||||
embedURLs,
|
||||
scrolling = false,
|
||||
selected = -1,
|
||||
selectedURL;
|
||||
|
||||
|
@ -233,13 +234,24 @@ pandora.ui.textHTML = function(text) {
|
|||
scroll: function(event) {
|
||||
var position = Math.round(100 * that[0]. scrollTop / Math.max(1,
|
||||
that[0].scrollHeight - that.height())),
|
||||
settings;
|
||||
if (pandora.user.ui.section == 'texts') {
|
||||
settings = pandora.user.ui.texts[pandora.user.ui.text];
|
||||
} else {
|
||||
settings = pandora.user.ui.documents[pandora.user.ui.document] || {};
|
||||
}
|
||||
position = position - position % 10;
|
||||
if (!scrolling && settings && (settings.name || (position != settings.position))) {
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
pandora.UI.set('documents.' + pandora.user.ui.document, {
|
||||
position: position ? position : 0
|
||||
});
|
||||
} else {
|
||||
pandora.UI.set('texts.' + pandora.UI.encode(pandora.user.ui.text), {
|
||||
position: position ? position : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
scrolling = false;
|
||||
},
|
||||
})
|
||||
|
@ -248,6 +260,9 @@ pandora.ui.textHTML = function(text) {
|
|||
that.update();
|
||||
},
|
||||
})
|
||||
.bindEvent('pandora_documents.' + text.id.toLowerCase(), function(data) {
|
||||
data.value && data.value.name && scrollToPosition();
|
||||
})
|
||||
.bindEvent('pandora_texts.' + text.id.toLowerCase(), function(data) {
|
||||
data.value && data.value.name && scrollToPosition();
|
||||
}),
|
||||
|
@ -257,10 +272,10 @@ pandora.ui.textHTML = function(text) {
|
|||
.appendTo(that),
|
||||
|
||||
$title = Ox.EditableContent({
|
||||
editable: text.name ? text.editable : false,
|
||||
editable: text.title ? text.editable : false,
|
||||
placeholder: text.editable ? Ox._('Doubleclick to edit title') : Ox._('Untitled'),
|
||||
tooltip: text.editable ? pandora.getEditTooltip('title') : '',
|
||||
value: text.name || Ox._('{0} Texts', [pandora.site.site.name]),
|
||||
value: text.title || Ox._('{0} Texts', [pandora.site.site.name]),
|
||||
width: width
|
||||
})
|
||||
.css({
|
||||
|
@ -271,6 +286,19 @@ pandora.ui.textHTML = function(text) {
|
|||
})
|
||||
.bindEvent({
|
||||
submit: function(data) {
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
pandora.api.editDocument({
|
||||
id: pandora.user.ui.document,
|
||||
name: data.value
|
||||
}, function(result) {
|
||||
if (result.data.name != data.value) {
|
||||
Ox.Request.clearCache();
|
||||
$title.options({
|
||||
value: result.data.title
|
||||
})
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Ox.Request.clearCache('getText');
|
||||
pandora.api.editText({
|
||||
id: pandora.user.ui.text,
|
||||
|
@ -283,6 +311,7 @@ pandora.ui.textHTML = function(text) {
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo($content),
|
||||
|
||||
|
@ -373,6 +402,16 @@ pandora.ui.textHTML = function(text) {
|
|||
})
|
||||
.bindEvent({
|
||||
submit: function(data) {
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
Ox.Request.clearCache('getDocument');
|
||||
pandora.api.editDocument({
|
||||
id: pandora.user.ui.document,
|
||||
text: data.value
|
||||
}, function(result) {
|
||||
//fixme: just reload as it was done with textPanel
|
||||
pandora.$ui.document = pandora.ui.document();
|
||||
});
|
||||
} else {
|
||||
Ox.Request.clearCache('getText');
|
||||
pandora.api.editText({
|
||||
id: pandora.user.ui.text,
|
||||
|
@ -380,6 +419,7 @@ pandora.ui.textHTML = function(text) {
|
|||
});
|
||||
pandora.$ui.textPanel.update(data.value);
|
||||
}
|
||||
}
|
||||
})
|
||||
.appendTo($content);
|
||||
|
||||
|
@ -404,7 +444,9 @@ pandora.ui.textHTML = function(text) {
|
|||
}
|
||||
|
||||
function scrollToPosition() {
|
||||
var settings = pandora.user.ui.texts[pandora.user.ui.text] || {},
|
||||
var settings = (pandora.user.ui.section == 'documents'
|
||||
? pandora.user.ui.documents[pandora.user.ui.document]
|
||||
: pandora.user.ui.texts[pandora.user.ui.text]) || {},
|
||||
position = settings.position || 0,
|
||||
element,
|
||||
scrollTop;
|
||||
|
@ -505,6 +547,7 @@ pandora.ui.textEmbed = function() {
|
|||
resize: function(data) {
|
||||
pandora.user.ui.embedSize = data.size;
|
||||
pandora.$ui.text.update();
|
||||
pandora.$ui.document && pandora.$ui.document.update();
|
||||
},
|
||||
resizeend: function(data) {
|
||||
$iframe.attr('src') && $overlay.hide();
|
||||
|
|
|
@ -46,7 +46,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
|||
if (title == Ox._('Cancel Upload')) {
|
||||
upload && upload.abort();
|
||||
} else if (title == Ox._('Done')) {
|
||||
callback({
|
||||
callback && callback({
|
||||
ids: ids
|
||||
});
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
|||
+ (extension == 'jpeg' ? 'jpg' : extension);
|
||||
valid && Ox.oshash(file, function(oshash) {
|
||||
pandora.api.findDocuments({
|
||||
keys: ['id', 'user', 'name', 'extension'],
|
||||
keys: ['id', 'user', 'title', 'extension'],
|
||||
query: {
|
||||
conditions: [{
|
||||
key: 'oshash',
|
||||
|
@ -91,10 +91,10 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
|||
operator: '&'
|
||||
},
|
||||
range: [0, 1],
|
||||
sort: [{key: 'name', operator: '+'}]
|
||||
sort: [{key: 'title', operator: '+'}]
|
||||
}, function(result) {
|
||||
if (result.data.items.length) {
|
||||
var id = result.data.items[0].name + '.'
|
||||
var id = result.data.items[0].title + '.'
|
||||
+ result.data.items[0].extension;
|
||||
valid && errorDialog(
|
||||
filename == id
|
||||
|
@ -161,7 +161,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
|||
ids.push(data.response.id);
|
||||
if (part == files.length) {
|
||||
$progress.options({progress: data.progress});
|
||||
callback({ids: ids});
|
||||
callback && callback({ids: ids});
|
||||
$uploadDialog.close();
|
||||
} else {
|
||||
uploadFile(part);
|
||||
|
|
|
@ -40,6 +40,9 @@ pandora.addFolderItem = function(section) {
|
|||
if (!isSmart) {
|
||||
if (isItems) {
|
||||
data.items = ui.listSelection;
|
||||
} else if (section == 'documents') {
|
||||
//fixme
|
||||
data.items = ui.collectionSelection;
|
||||
} else {
|
||||
data.clips = pandora.getClipData(
|
||||
ui.section == 'items'
|
||||
|
@ -66,7 +69,11 @@ pandora.addFolderItem = function(section) {
|
|||
if (data.type == 'smart') {
|
||||
data.query = listData.query;
|
||||
}
|
||||
pandora.api[isItems ? 'findLists' : 'findEdits']({
|
||||
pandora.api[{
|
||||
items: 'findLists',
|
||||
documents: 'findCollections',
|
||||
edits: 'findEdits',
|
||||
}[section]]({
|
||||
query: {conditions: [{
|
||||
key: 'id',
|
||||
operator: '==',
|
||||
|
@ -103,6 +110,9 @@ pandora.addFolderItem = function(section) {
|
|||
addList();
|
||||
}
|
||||
});
|
||||
} else if(section == 'documents') {
|
||||
//fixme
|
||||
addList();
|
||||
} else {
|
||||
pandora.api.getEdit({
|
||||
id: list,
|
||||
|
@ -127,7 +137,11 @@ pandora.addFolderItem = function(section) {
|
|||
});
|
||||
}
|
||||
function addList() {
|
||||
pandora.api[isItems ? 'addList' : 'addEdit'](data, function(result) {
|
||||
pandora.api[{
|
||||
items: 'addList',
|
||||
documents: 'addCollection',
|
||||
edits: 'addEdit'
|
||||
}[section]](data, function(result) {
|
||||
getPosterFrames(result.data.id);
|
||||
});
|
||||
}
|
||||
|
@ -136,23 +150,41 @@ pandora.addFolderItem = function(section) {
|
|||
sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes')
|
||||
? 'votes' : 'timesaccessed';
|
||||
if (!isDuplicate) {
|
||||
(isItems ? Ox.noop : pandora.api.getEdit)({
|
||||
({
|
||||
items: Ox.noop,
|
||||
documents: Ox.noop,
|
||||
edits: pandora.api.getEdit
|
||||
}[section])({
|
||||
id: newList,
|
||||
keys: ['clips']
|
||||
}, function(result) {
|
||||
query = isItems ? {
|
||||
conditions: [{key: 'list', value: newList, operator: '=='}],
|
||||
if (Ox.contains(pandora.site.listSections, section)) {
|
||||
query = {
|
||||
conditions: [{
|
||||
key: section == 'documents' ? 'collection' : 'list',
|
||||
value: newList, operator: '=='
|
||||
}],
|
||||
operator: '&'
|
||||
} : {
|
||||
};
|
||||
} else{
|
||||
query = {
|
||||
conditions: Ox.unique(result.data.clips.map(function(clip) {
|
||||
return {key: 'id', value: clip.item, operator: '=='};
|
||||
})),
|
||||
operator: '|'
|
||||
};
|
||||
(isItems ? pandora.api.find : Ox.noop)({
|
||||
}
|
||||
({
|
||||
items: pandora.api.find,
|
||||
documents: pandora.api.findDocuments,
|
||||
edits: Ox.noop
|
||||
}[section])({
|
||||
query: {
|
||||
conditions: [
|
||||
{key: 'list', value: newList, operator: '=='}
|
||||
{
|
||||
key: section == 'documents' ? 'collection' : 'list',
|
||||
value: newList, operator: '=='
|
||||
}
|
||||
],
|
||||
operator: '&'
|
||||
},
|
||||
|
@ -181,7 +213,11 @@ pandora.addFolderItem = function(section) {
|
|||
});
|
||||
});
|
||||
} else {
|
||||
pandora.api[isItems ? 'findLists' : 'findEdits']({
|
||||
pandora.api[{
|
||||
items: 'findLists',
|
||||
documents: 'findCollections',
|
||||
edits: 'findEdits'
|
||||
}[section]]({
|
||||
query: {
|
||||
conditions: [{key: 'id', value: list, operator: '=='}],
|
||||
operator: '&'
|
||||
|
@ -193,7 +229,11 @@ pandora.addFolderItem = function(section) {
|
|||
}
|
||||
}
|
||||
function setPosterFrames(newList, posterFrames) {
|
||||
pandora.api[isItems ? 'editList' : 'editEdit']({
|
||||
pandora.api[{
|
||||
items: 'editList',
|
||||
documents: 'editCollection',
|
||||
edits: 'editEdit'
|
||||
}[section]]({
|
||||
id: newList,
|
||||
posterFrames: posterFrames
|
||||
}, function() {
|
||||
|
@ -206,22 +246,37 @@ pandora.addFolderItem = function(section) {
|
|||
// (same applies to addText, below)
|
||||
$folderList = pandora.$ui.folderList.personal;
|
||||
pandora.$ui.folder[0].options({collapsed: false});
|
||||
Ox.Request.clearCache(isItems ? 'findLists' : 'findEdits');
|
||||
Ox.Request.clearCache({
|
||||
items: 'findLists',
|
||||
documents: 'findCollections',
|
||||
edits: 'findEdits'
|
||||
}[section]);
|
||||
$folderList.bindEventOnce({
|
||||
load: function() {
|
||||
$folderList.gainFocus()
|
||||
.options({selected: [newList]})
|
||||
.editCell(newList, 'name', true);
|
||||
pandora.UI.set(isItems ? {
|
||||
pandora.UI.set({
|
||||
items: {
|
||||
find: {
|
||||
conditions: [
|
||||
{key: 'list', value: newList, operator: '=='}
|
||||
],
|
||||
operator: '&'
|
||||
}
|
||||
} : {
|
||||
},
|
||||
documents: {
|
||||
findDocuments: {
|
||||
conditions: [
|
||||
{key: 'collection', value: newList, operator: '=='}
|
||||
],
|
||||
operator: '&'
|
||||
}
|
||||
},
|
||||
edits: {
|
||||
edit: newList
|
||||
});
|
||||
}
|
||||
}[section]);
|
||||
}
|
||||
}).reloadList();
|
||||
}
|
||||
|
@ -229,7 +284,7 @@ pandora.addFolderItem = function(section) {
|
|||
|
||||
pandora.addList = function() {
|
||||
// addList(isSmart, isFrom) or addList(list) [=duplicate]
|
||||
pandora.addFolderItem.apply(null, ['items'].concat(Ox.slice(arguments)));
|
||||
pandora.addFolderItem.apply(null, [pandora.user.ui.section].concat(Ox.slice(arguments)));
|
||||
};
|
||||
|
||||
pandora.addText = function(options) {
|
||||
|
@ -271,8 +326,7 @@ pandora.beforeUnloadWindow = function() {
|
|||
|
||||
pandora.changeFolderItemStatus = function(id, status, callback) {
|
||||
var ui = pandora.user.ui,
|
||||
folderItems = ui.section == 'items'
|
||||
? 'Lists' : Ox.toTitleCase(ui.section),
|
||||
folderItems = pandora.getFolderItems(ui.section),
|
||||
folderItem = folderItems.slice(0, -1);
|
||||
if (status == 'private') {
|
||||
pandora.api['find' + folderItems]({
|
||||
|
@ -496,6 +550,30 @@ pandora.createLinks = function($element) {
|
|||
callback(null, []);
|
||||
}
|
||||
});
|
||||
} else if (type == 'documents') {
|
||||
//fixme
|
||||
pandora.api.findDocuments({
|
||||
query: {
|
||||
conditions: [{key: 'collection', operator: '==', value: target}],
|
||||
operator: '&'
|
||||
},
|
||||
positions: items
|
||||
}, function(result) {
|
||||
var existingItems = Object.keys(result.data.positions),
|
||||
addedItems = items.filter(function(item) {
|
||||
return !Ox.contains(existingItems, item);
|
||||
});
|
||||
if (addedItems.length) {
|
||||
pandora.api.addCollectionItems({
|
||||
items: addedItems,
|
||||
collection: target
|
||||
}, function(result) {
|
||||
callback(result, addedItems);
|
||||
});
|
||||
} else {
|
||||
callback(null, []);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pandora.api.addClips({
|
||||
clips: pandora.getClipData(items),
|
||||
|
@ -540,6 +618,31 @@ pandora.createLinks = function($element) {
|
|||
// FIXME: Why is this timeout needed?
|
||||
setTimeout(pandora.reloadList, 250);
|
||||
}
|
||||
} else if (type == 'document' && ui.section == 'documents') {
|
||||
Ox.Request.clearCache('findDocuments');
|
||||
object.targets.filter(function(list) {
|
||||
return list != ui._list;
|
||||
}).forEach(function(list) {
|
||||
listData = pandora.getListData(list);
|
||||
pandora.api.findDocuments({
|
||||
query: {
|
||||
conditions: [{
|
||||
key: 'collection',
|
||||
operator: '==',
|
||||
value: list
|
||||
}],
|
||||
operator: '&'
|
||||
}
|
||||
}, function(result) {
|
||||
pandora.$ui.folderList[listData.folder].value(
|
||||
list, 'items', result.data.items
|
||||
);
|
||||
});
|
||||
});
|
||||
if (Ox.contains(object.targets, ui._list)) {
|
||||
// FIXME: Why is this timeout needed?
|
||||
setTimeout(pandora.reloadList, 250);
|
||||
}
|
||||
} else if (type == 'clip' && ui.section == 'edits') {
|
||||
// FIXME: update edit list (once it has item count)
|
||||
if (Ox.contains(object.targets, ui.edit)) {
|
||||
|
@ -743,6 +846,36 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
|
|||
});
|
||||
drag.action == 'move' && pandora.reloadList();
|
||||
});
|
||||
} else if (section == 'documents') {
|
||||
var targets = drag.action == 'copy' ? drag.target.id
|
||||
: [pandora.user.ui._collection, drag.target.id];
|
||||
//fixme use history
|
||||
//pandora.doHistory(drag.action, drag.ids, targets, function() {
|
||||
pandora.api.addCollectionItems({
|
||||
collection: drag.target.id,
|
||||
items: drag.ids
|
||||
|
||||
}, function() {
|
||||
Ox.Request.clearCache('find');
|
||||
pandora.api.findDocuments({
|
||||
query: {
|
||||
conditions: [{
|
||||
key: 'collection',
|
||||
operator: '==',
|
||||
value: drag.target.id
|
||||
}],
|
||||
operator: '&'
|
||||
}
|
||||
}, function(result) {
|
||||
var folder = drag.target.status != 'featured'
|
||||
? 'personal' : 'featured';
|
||||
pandora.$ui.folderList[folder].value(
|
||||
drag.target.id, 'items', result.data.items
|
||||
);
|
||||
cleanup(250);
|
||||
});
|
||||
drag.action == 'move' && pandora.reloadList();
|
||||
});
|
||||
} else if (section == 'edits') {
|
||||
var targets = drag.action == 'copy' ? drag.target.id
|
||||
: [pandora.user.ui.edit, drag.target.id];
|
||||
|
@ -751,6 +884,9 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
|
|||
pandora.$ui.editPanel && pandora.$ui.editPanel.updatePanel();
|
||||
cleanup(250);
|
||||
});
|
||||
} else {
|
||||
Ox.print('no drop support for', section);
|
||||
cleanup(250);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -781,13 +917,20 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
|
|||
itemName = section == 'items' ? {
|
||||
plural: Ox._(pandora.site.itemName.plural.toLowerCase()),
|
||||
singular: Ox._(pandora.site.itemName.singular.toLowerCase())
|
||||
} : {
|
||||
} : section == 'documents' ? {
|
||||
plural: Ox._('Documents'),
|
||||
singular: Ox._('Document')
|
||||
|
||||
} :{
|
||||
plural: Ox._('clips'),
|
||||
singular: Ox._('clip')
|
||||
},
|
||||
targetName = section == 'items' ? {
|
||||
plural: Ox._('lists'),
|
||||
singular: Ox._('list')
|
||||
} : section == 'documents' ? {
|
||||
plural: Ox._('collections'),
|
||||
singular: Ox._('collection')
|
||||
} : {
|
||||
plural: Ox._('edits'),
|
||||
singular: Ox._('edit')
|
||||
|
@ -946,11 +1089,36 @@ pandora.exitFullscreen = function() {
|
|||
}
|
||||
};
|
||||
|
||||
pandora.formatDocumentKey = function(key, data) {
|
||||
var value;
|
||||
if (key.format) {
|
||||
value = (
|
||||
/^color/.test(key.format.type.toLowerCase()) ? Ox.Theme : Ox
|
||||
)['format' + Ox.toTitleCase(key.format.type)].apply(
|
||||
this, [data[key.id]].concat(key.format.args || [])
|
||||
);
|
||||
if (key.id == 'rightslevel') {
|
||||
value.css({width: size * 0.75 + 'px'});
|
||||
}
|
||||
} else {
|
||||
value = data[key.id];
|
||||
if (key.id == 'extension') {
|
||||
value = value.toUpperCase();
|
||||
} else if (key.id == 'dimensions') {
|
||||
value = Ox.isArray(value)
|
||||
? Ox.formatDimensions(value, 'px')
|
||||
: Ox.formatCount(value, data.extension == 'html' ? 'word' : 'page');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
pandora.getAllItemsTitle = function(section) {
|
||||
section = section || pandora.user.ui.section;
|
||||
return section == 'items'
|
||||
? Ox._('All {0}', [Ox._(pandora.site.itemName.plural)])
|
||||
: Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]);
|
||||
return {
|
||||
items: Ox._('All {0}', [Ox._(pandora.site.itemName.plural)]),
|
||||
documents: Ox._('All {0}', [Ox._('Documents')])
|
||||
}[section] || Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]);
|
||||
};
|
||||
|
||||
pandora.getClipData = function(items) {
|
||||
|
@ -1068,14 +1236,20 @@ pandora.getClipVideos = function(clip, resolution) {
|
|||
};
|
||||
|
||||
(function() {
|
||||
var itemTitles = {};
|
||||
pandora.getDocumentTitle = function(itemData) {
|
||||
var itemTitles = {}, documentTitles = {};
|
||||
pandora.getWindowTitle = function(itemData) {
|
||||
var parts = [];
|
||||
if (itemData) {
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
documentTitles[pandora.user.ui.document] = Ox.decodeHTMLEntities(
|
||||
pandora.getDocumentTitle(itemData)
|
||||
);
|
||||
} else {
|
||||
itemTitles[pandora.user.ui.item] = Ox.decodeHTMLEntities(
|
||||
pandora.getItemTitle(itemData)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (pandora.user.ui.section == 'items') {
|
||||
if (!pandora.user.ui.item) {
|
||||
parts.push(
|
||||
|
@ -1094,16 +1268,33 @@ pandora.getClipVideos = function(clip, resolution) {
|
|||
Ox._(Ox.toTitleCase(pandora.user.ui.itemView))
|
||||
]));
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
if (!pandora.user.ui.document) {
|
||||
parts.push(
|
||||
pandora.user.ui._collection
|
||||
? pandora.user.ui._collection.split(':').slice(1).join(':')
|
||||
: pandora.getAllItemsTitle('documents')
|
||||
);
|
||||
parts.push(Ox._('{0} View', [
|
||||
Ox._(Ox.toTitleCase(pandora.user.ui.collectionView))
|
||||
]));
|
||||
parts.push(Ox._('Documents'));
|
||||
} else {
|
||||
parts.push(
|
||||
documentTitles[pandora.user.ui.document] || pandora.user.ui.document
|
||||
);
|
||||
/*
|
||||
parts.push(Ox._('{0} View', [
|
||||
Ox._(Ox.toTitleCase(pandora.user.ui.documentView))
|
||||
]));
|
||||
*/
|
||||
parts.push(Ox._('Document'));
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'edits') {
|
||||
if (pandora.user.ui.edit) {
|
||||
parts.push(pandora.user.ui.edit.split(':').slice(1).join(':'));
|
||||
}
|
||||
parts.push(Ox._('Edits'));
|
||||
} else if (pandora.user.ui.section == 'texts') {
|
||||
if (pandora.user.ui.text) {
|
||||
parts.push(pandora.user.ui.text.split(':').slice(1).join(':'));
|
||||
}
|
||||
parts.push(Ox._('Texts'));
|
||||
}
|
||||
parts.push(pandora.site.site.name);
|
||||
return parts.join(' – ');
|
||||
|
@ -1138,6 +1329,12 @@ pandora.getFilterSizes = function() {
|
|||
);
|
||||
};
|
||||
|
||||
pandora.getFolderItems = function(section) {
|
||||
return section == 'items' ? 'Lists'
|
||||
: section == 'documents' ? 'Collections'
|
||||
: Ox.toTitleCase(section);
|
||||
}
|
||||
|
||||
pandora.getFoldersHeight = function(section) {
|
||||
section = section || pandora.user.ui.section;
|
||||
var height = 0;
|
||||
|
@ -1299,6 +1496,16 @@ pandora.getItem = function(state, str, callback) {
|
|||
});
|
||||
}
|
||||
});
|
||||
} else if (state.type == 'documents') {
|
||||
pandora.api.getDocument({id: str, keys: ['id']}, function(result) {
|
||||
if (result.status.code == 200) {
|
||||
state.item = result.data.id;
|
||||
callback();
|
||||
} else {
|
||||
state.item = '';
|
||||
callback();
|
||||
}
|
||||
});
|
||||
} else if (state.type == 'edits') {
|
||||
pandora.api.getEdit({id: str, keys: ['id']}, function(result) {
|
||||
if (result.status.code == 200) {
|
||||
|
@ -1310,21 +1517,23 @@ pandora.getItem = function(state, str, callback) {
|
|||
}
|
||||
});
|
||||
} else if (state.type == 'texts') {
|
||||
pandora.api.getText({
|
||||
id: str,
|
||||
keys: ['id', 'names', 'pages', 'type']
|
||||
pandora.api.findDocuments({
|
||||
query: {
|
||||
conditions: [
|
||||
{key: 'user', value: str.split(':')[0]},
|
||||
{key: 'title', value: str.split(':').slice(1).join(':')}
|
||||
],
|
||||
operator: '&'},
|
||||
keys: ['id', 'extension'],
|
||||
range: [0, 2]
|
||||
}, function(result) {
|
||||
if (result.status.code == 200) {
|
||||
state.item = result.data.id;
|
||||
callback();
|
||||
state.type = 'documents';
|
||||
if (result.data.items.length == 1) {
|
||||
state.item = result.data.items[0].id;
|
||||
} else {
|
||||
// FIXME: add findText call here?
|
||||
// FIXME: it's obscure that in the texts case,
|
||||
// we have to set item to '', while for videos,
|
||||
// it remains undefined
|
||||
state.item = '';
|
||||
callback();
|
||||
}
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
|
@ -1471,12 +1680,19 @@ pandora.getLargeEditTimelineURL = function(edit, type, i, callback) {
|
|||
};
|
||||
|
||||
pandora.getListData = function(list) {
|
||||
var data = {}, folder;
|
||||
var data = {}, folder, _list = pandora.user.ui._list;
|
||||
if (Ox.isUndefined(list)) {
|
||||
list = pandora.user.ui[
|
||||
pandora.user.ui.section == 'items' ? '_list'
|
||||
: pandora.user.ui.section.slice(0, -1)
|
||||
];
|
||||
if (pandora.user.ui.section == 'items') {
|
||||
list = pandora.user.ui._list;
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
list = pandora.user.ui._collection;
|
||||
_list = pandora.user.ui._collection;
|
||||
} else {
|
||||
list = pandora.user.ui.section.slice(0, -1)
|
||||
}
|
||||
}
|
||||
if (pandora.user.ui.section == 'documents') {
|
||||
_list = pandora.user.ui._collection;
|
||||
}
|
||||
if (list && pandora.$ui.folderList) {
|
||||
Ox.forEach(pandora.$ui.folderList, function($list, id) {
|
||||
|
@ -1485,7 +1701,7 @@ pandora.getListData = function(list) {
|
|||
// folder it is selected, since for example, a personal
|
||||
// list may appear again in the featured lists browser
|
||||
if (
|
||||
(list == pandora.user.ui._list && $list.options('selected').length)
|
||||
(list == _list && $list.options('selected').length)
|
||||
|| !Ox.isEmpty($list.value(list))
|
||||
) {
|
||||
folder = id;
|
||||
|
@ -1506,6 +1722,16 @@ pandora.getListData = function(list) {
|
|||
return data;
|
||||
};
|
||||
|
||||
pandora.getListIcon = function(section, id, size, modified) {
|
||||
var folderItems = pandora.getFolderItems(section),
|
||||
folderItem = folderItems.slice(0, -1);
|
||||
size = size || '';
|
||||
modified = modified || Ox.uid();
|
||||
return pandora.getMediaURL('/'
|
||||
+ folderItem.toLowerCase() + '/'
|
||||
+ encodeURIComponent(id) + '/icon' + size + '.jpg?' + modified);
|
||||
};
|
||||
|
||||
pandora.getPageTitle = function(stateOrURL) {
|
||||
var pages = [
|
||||
{id: '', title: ''},
|
||||
|
@ -1529,6 +1755,7 @@ pandora.getPageTitle = function(stateOrURL) {
|
|||
};
|
||||
|
||||
pandora.getPart = function(state, str, callback) {
|
||||
Ox.Log('URL', 'getPart', state, str);
|
||||
if (state.page == 'api') {
|
||||
pandora.api.api(function(result) {
|
||||
if (Ox.contains(Object.keys(result.data.actions), str)) {
|
||||
|
@ -1656,6 +1883,8 @@ pandora.getSort = function(state, val, callback) {
|
|||
// TODO in the future: If str is index, fall back if list is smart
|
||||
// (but this can only be tested after find has been parsed)
|
||||
callback();
|
||||
} else if (state.type == 'documents') {
|
||||
callback();
|
||||
} else if (state.type == 'edits') {
|
||||
if (val[0].key == 'index') {
|
||||
pandora.api.getEdit({id: state.item, keys: ['id', 'type']}, function(result) {
|
||||
|
@ -1675,8 +1904,6 @@ pandora.getSort = function(state, val, callback) {
|
|||
} else {
|
||||
callback();
|
||||
}
|
||||
} else if (state.type == 'texts') {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1705,6 +1932,29 @@ pandora.getSortOperator = function(key) {
|
|||
) > -1 ? '+' : '-';
|
||||
};
|
||||
|
||||
pandora.getDocumentSortKeys = function() {
|
||||
return pandora.site.documentKeys.filter(function(key) {
|
||||
return key.sort && (
|
||||
!key.capability
|
||||
|| pandora.site.capabilities[key.capability][pandora.user.level]
|
||||
);
|
||||
}).map(function(key) {
|
||||
return Ox.extend(key, {
|
||||
operator: pandora.getDocumentSortOperator(key.id)
|
||||
});
|
||||
});
|
||||
};
|
||||
pandora.getDocumentSortOperator = function(key) {
|
||||
var data = Ox.getObjectById(pandora.site.documentKeys, key);
|
||||
return data.sortOperator || ['string', 'text'].indexOf(
|
||||
Ox.isArray(data.type) ? data.type[0] : data.type
|
||||
) > -1 ? '+' : '-';
|
||||
};
|
||||
|
||||
pandora.getDocumentTitle = function(data) {
|
||||
return data.title || Ox._('Untitled');
|
||||
};
|
||||
|
||||
pandora.getSpan = function(state, val, callback) {
|
||||
// For a given item, or none (state.item), and a given view, or any
|
||||
// (state.view), this takes a value (array of numbers or string) and checks
|
||||
|
@ -1712,9 +1962,10 @@ pandora.getSpan = function(state, val, callback) {
|
|||
// event/place name (string), and in that case sets state.span, and may
|
||||
// modify state.view.
|
||||
// fixme: "subtitles:23" is still missing
|
||||
if (state.page == 'documents') {
|
||||
Ox.Log('URL', 'getSpan', state, val);
|
||||
if (state.type == 'documents') {
|
||||
pandora.api.getDocument({
|
||||
id: state.part,
|
||||
id: state.item,
|
||||
keys: ['dimensions', 'extension']
|
||||
}, function(result) {
|
||||
var dimensions = result.data.dimensions,
|
||||
|
@ -1722,6 +1973,8 @@ pandora.getSpan = function(state, val, callback) {
|
|||
values;
|
||||
if (Ox.contains(['epub', 'pdf', 'txt'], extension)) {
|
||||
state.span = Ox.limit(parseInt(val), 1, dimensions);
|
||||
} else if (Ox.contains(['html'], extension)) {
|
||||
state.span = Ox.limit(parseInt(val), 0, 100);
|
||||
} else if (Ox.contains(['gif', 'jpg', 'png'], extension)) {
|
||||
values = val.split(',');
|
||||
if (values.length == 4) {
|
||||
|
@ -1738,6 +1991,7 @@ pandora.getSpan = function(state, val, callback) {
|
|||
state.span = '';
|
||||
}
|
||||
}
|
||||
Ox.Log('URL', 'getSpan result', state);
|
||||
callback();
|
||||
});
|
||||
} else if (state.type == pandora.site.itemName.plural.toLowerCase()) {
|
||||
|
@ -1817,24 +2071,6 @@ pandora.getSpan = function(state, val, callback) {
|
|||
callback();
|
||||
});
|
||||
}
|
||||
} else if (state.type == 'texts') {
|
||||
pandora.api.getText({id: state.item}, function(result) {
|
||||
if (isArray) {
|
||||
if (result.data.type == 'html') {
|
||||
state.span = Ox.limit(val[0], 0, 100);
|
||||
} else {
|
||||
state.span = Math.floor(
|
||||
Ox.limit(val[0], 1, result.data.pages)
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
result.data.type == 'html'
|
||||
&& Ox.contains(result.data.names, val)
|
||||
) {
|
||||
state.span = val;
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function getId(type, callback) {
|
||||
|
@ -1898,6 +2134,9 @@ pandora.getStatusText = function(data) {
|
|||
data.items == 1 ? 'singular' : 'plural'
|
||||
]),
|
||||
parts = [];
|
||||
if (ui.section == 'documents') {
|
||||
itemName = Ox._(Ox.toTitleCase(data.items == 1 ? ui.section.slice(0, -1) : ui.section));
|
||||
}
|
||||
parts.push(Ox.formatNumber(data.items) + ' '+ itemName);
|
||||
if (data.runtime) {
|
||||
parts.push(Ox.formatDuration(data.runtime, 'short'));
|
||||
|
@ -2263,6 +2502,8 @@ pandora.signin = function(data) {
|
|||
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
|
||||
pandora.user.ui._filterState = pandora.getFilterState(pandora.user.ui.find);
|
||||
pandora.user.ui._findState = pandora.getFindState(pandora.user.ui.find);
|
||||
pandora.user.ui._collection = pandora.getCollectionState(pandora.user.ui.findDocuments);
|
||||
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(pandora.user.ui.findDocuments);
|
||||
pandora.site.sortKeys = pandora.getSortKeys();
|
||||
pandora.URL.init();
|
||||
pandora.URL.update();
|
||||
|
@ -2277,6 +2518,8 @@ pandora.signout = function(data) {
|
|||
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
|
||||
pandora.user.ui._filterState = pandora.getFilterState(pandora.user.ui.find);
|
||||
pandora.user.ui._findState = pandora.getFindState(pandora.user.ui.find);
|
||||
pandora.user.ui._collection = pandora.getCollectionState(pandora.user.ui.findDocuments);
|
||||
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(pandora.user.ui.findDocuments);
|
||||
pandora.site.sortKeys = pandora.getSortKeys();
|
||||
pandora.URL.init();
|
||||
pandora.URL.update();
|
||||
|
@ -2288,9 +2531,11 @@ pandora.reloadList = function() {
|
|||
Ox.Log('', 'reloadList')
|
||||
var listData = pandora.getListData();
|
||||
Ox.Request.clearCache(); // fixme: remove
|
||||
if (pandora.$ui.filters) {
|
||||
pandora.$ui.filters.forEach(function($filter) {
|
||||
$filter.reloadList();
|
||||
});
|
||||
}
|
||||
pandora.$ui.list
|
||||
.bindEvent({
|
||||
init: function(data) {
|
||||
|
@ -2336,6 +2581,20 @@ pandora.renameList = function(oldId, newId, newName, folder) {
|
|||
}
|
||||
}, false);
|
||||
pandora.UI.set('lists.' + pandora.UI.encode(oldId), null, false);
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
pandora.replaceURL = true;
|
||||
pandora.UI.set(
|
||||
'collections.' + pandora.UI.encode(newId),
|
||||
pandora.user.ui.lists[oldId],
|
||||
false
|
||||
);
|
||||
pandora.UI.set({
|
||||
findDocuments: {
|
||||
conditions: [{key: 'collection', value: newId, operator: '=='}],
|
||||
operator: '&'
|
||||
}
|
||||
}, false);
|
||||
pandora.UI.set('collections.' + pandora.UI.encode(oldId), null, false);
|
||||
} else {
|
||||
pandora.replaceURL = true;
|
||||
pandora.UI.set(
|
||||
|
@ -2380,7 +2639,7 @@ pandora.resizeFolders = function(section) {
|
|||
userColumnWidth = Math.round(columnWidth * 0.4),
|
||||
nameColumnWidth = columnWidth - userColumnWidth;
|
||||
pandora.$ui.allItems && pandora.$ui.allItems.resizeElement((
|
||||
section == 'items' ? columnWidth
|
||||
Ox.contains(pandora.site.listSections, section) ? columnWidth
|
||||
: section == 'edits' ? width - 16
|
||||
: width - 48
|
||||
) - 8);
|
||||
|
@ -2489,6 +2748,12 @@ pandora.resizeWindow = function() {
|
|||
pandora.$ui.calendar.resizeCalendar();
|
||||
}
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
if (pandora.user.ui.document) {
|
||||
pandora.$ui.document && pandora.$ui.document.update();
|
||||
} else {
|
||||
pandora.$ui.list && pandora.$ui.list.size();
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'edits') {
|
||||
if (!pandora.user.ui.edit) {
|
||||
// ...
|
||||
|
@ -2537,6 +2802,38 @@ pandora.selectList = function() {
|
|||
}
|
||||
});
|
||||
}
|
||||
} else if (pandora.user.ui.section == 'documents') {
|
||||
if (pandora.user.ui._collection) {
|
||||
pandora.api.findCollections({
|
||||
keys: ['status', 'user'],
|
||||
query: {
|
||||
conditions: [{
|
||||
key: 'id',
|
||||
operator: '==',
|
||||
value: pandora.user.ui._collection
|
||||
}],
|
||||
operator: ''
|
||||
},
|
||||
range: [0, 1]
|
||||
}, function(result) {
|
||||
var folder, list;
|
||||
if (result.data.items.length) {
|
||||
list = result.data.items[0];
|
||||
folder = list.status == 'featured' ? 'featured' : (
|
||||
list.user == pandora.user.username
|
||||
? 'personal' : 'favorite'
|
||||
);
|
||||
pandora.$ui.folderList[folder]
|
||||
.options({selected: [pandora.user.ui._collection]});
|
||||
if (
|
||||
!pandora.hasDialogOrScreen()
|
||||
&& !Ox.Focus.focusedElementIsInput()
|
||||
) {
|
||||
pandora.$ui.folderList[folder].gainFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)],
|
||||
section = Ox.toTitleCase(pandora.user.ui.section.slice(0, -1));
|
||||
|
@ -2891,18 +3188,76 @@ pandora.wait = function(id, callback, timeout) {
|
|||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
pandora.getListState = function(find) {
|
||||
// A list is selected if exactly one condition in an & query has "list"
|
||||
// as key and "==" as operator
|
||||
function getState(find, key) {
|
||||
var index, state = '';
|
||||
if (find.operator == '&') {
|
||||
index = oneCondition(find.conditions, 'list', '==');
|
||||
index = oneCondition(find.conditions, key, '==');
|
||||
if (index > -1) {
|
||||
state = find.conditions[index].value;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
pandora.getCollectionState = function(find) {
|
||||
// A collection is selected if exactly one condition in an & query has "collection"
|
||||
// as key and "==" as operator
|
||||
return getState(find, 'collection');
|
||||
};
|
||||
|
||||
pandora.getListState = function(find) {
|
||||
// A list is selected if exactly one condition in an & query has "list"
|
||||
// as key and "==" as operator
|
||||
return getState(find, 'list');
|
||||
};
|
||||
|
||||
pandora.getFindDocumentsState = function(find) {
|
||||
// The find element is populated if exactly one condition in an & query
|
||||
// has a findKey as key and "=" as operator (and all other conditions
|
||||
// are either list or filters), or if all conditions in an | query have
|
||||
// the same filter id as key and "==" as operator
|
||||
Ox.Log('Find', 'getFindDocumentsState', find)
|
||||
var conditions, indices, state = {index: -1, key: '*', value: ''};
|
||||
if (find.operator == '&') {
|
||||
// number of conditions that are not list or filters
|
||||
conditions = find.conditions.length
|
||||
- !!pandora.user.ui._collection;
|
||||
/*
|
||||
- pandora.user.ui._filterState.filter(function(filter) {
|
||||
return filter.index > -1;
|
||||
}).length;
|
||||
*/
|
||||
// indices of non-advanced find queries
|
||||
indices = pandora.site.documentKeys.map(function(findKey) {
|
||||
return oneCondition(find.conditions, findKey.id, '=');
|
||||
}).filter(function(index) {
|
||||
return index > -1;
|
||||
});
|
||||
state = conditions == 1 && indices.length == 1 ? {
|
||||
index: indices[0],
|
||||
key: find.conditions[indices[0]].key,
|
||||
value: Ox.decodeURIComponent(find.conditions[indices[0]].value)
|
||||
} : {
|
||||
index: -1,
|
||||
key: conditions == 0 && indices.length == 0 ? '*' : 'advanced',
|
||||
value: ''
|
||||
};
|
||||
} else {
|
||||
state = {
|
||||
index: -1,
|
||||
key: 'advanced',
|
||||
value: ''
|
||||
};
|
||||
/*
|
||||
Ox.forEach(pandora.user.ui.documentFilters, function(key) {
|
||||
if (everyCondition(find.conditions, key, '==')) {
|
||||
state.key = '*';
|
||||
return false;
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
}());
|
||||
|
|
BIN
static/png/cover.png
Normal file
BIN
static/png/cover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -216,6 +216,8 @@ if __name__ == "__main__":
|
|||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
||||
update_service('pandora-encoding')
|
||||
update_service('pandora-tasks')
|
||||
if old <= 5673:
|
||||
run('./pandora/manage.py', 'rebuild_documentfind')
|
||||
else:
|
||||
if len(sys.argv) == 1:
|
||||
release = get_release()
|
||||
|
|
Loading…
Reference in a new issue