new documents section

This commit is contained in:
j 2016-10-05 00:00:03 +02:00
parent 3fcbd59525
commit e1f35b1ec8
74 changed files with 6737 additions and 631 deletions

View file

@ -94,10 +94,20 @@ def load_config(init=False):
for key in config['itemKeys']: for key in config['itemKeys']:
config['keys'][key['id']] = key 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 # add missing defaults
for section in sorted(( 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', 'site', 'tv', 'user.ui', 'user.ui.part', 'user.ui.showFolder',
'menuExtras', 'languages' 'menuExtras', 'languages'
)): )):

View file

@ -35,11 +35,13 @@
*/ */
"capabilities": { "capabilities": {
"canAddItems": {"staff": true, "admin": true}, "canAddItems": {"staff": true, "admin": true},
"canAddDocuments": {"staff": true, "admin": true},
"canDownloadVideo": {"guest": -1, "member": -1, "friend": -1, "staff": -1, "admin": -1}, "canDownloadVideo": {"guest": -1, "member": -1, "friend": -1, "staff": -1, "admin": -1},
"canEditAnnotations": {"staff": true, "admin": true}, "canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true}, "canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true}, "canEditDocuments": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true}, "canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true}, "canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true}, "canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true}, "canEditFeaturedTexts": {"staff": true, "admin": true},
@ -61,11 +63,13 @@
"canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4}, "canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4},
"canReadText": {"guest": 0, "member": 0, "friend": 1, "staff": 1, "admin": 1}, "canReadText": {"guest": 0, "member": 0, "friend": 1, "staff": 1, "admin": 1},
"canRemoveItems": {"admin": true}, "canRemoveItems": {"admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"staff": true, "admin": true}, "canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true}, "canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true}, "canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true}, "canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"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}, "canSeeItem": {"guest": 3, "member": 3, "friend": 4, "staff": 4, "admin": 4},
"canSeeSize": {"friend": true, "staff": true, "admin": true}, "canSeeSize": {"friend": true, "staff": true, "admin": true},
"canSeeSoftwareVersion": {"staff": true, "admin": true}, "canSeeSoftwareVersion": {"staff": true, "admin": true},
@ -91,6 +95,212 @@
list means it will not be included in find annotations. list means it will not be included in find annotations.
*/ */
"clipLayers": ["subtitles"], "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 "entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class annotations, info view, or elsewhere. Each entry defines a specific class
@ -976,13 +1186,24 @@
"calendarFind": "", "calendarFind": "",
"calendarSelection": "", "calendarSelection": "",
"clipColumns": 2, "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": { "columns": {
"Colors": { "Colors": {
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"], "columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
"columnWidth": {} "columnWidth": {}
} }
}, },
"document": "",
"documents": {}, "documents": {},
"documentView": "view",
"documentSize": 256, "documentSize": 256,
"documentsSelection": {}, "documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}], "documentsSort": [{"key": "name", "operator": "+"}],
@ -1011,6 +1232,7 @@
], ],
"filtersSize": 176, "filtersSize": 176,
"find": {"conditions": [], "operator": "&"}, "find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true, "followPlayer": true,
"help": "", "help": "",
"icons": "posters", "icons": "posters",
@ -1076,7 +1298,7 @@
"featured": true, "featured": true,
"volumes": true "volumes": true
}, },
"texts": { "documents": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,
"featured": true "featured": true

View file

@ -36,11 +36,13 @@
*/ */
"capabilities": { "capabilities": {
"canAddItems": {"researcher": true, "staff": true, "admin": true}, "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}, "canDownloadVideo": {"guest": -1, "member": -1, "researcher": 3, "staff": 3, "admin": 3},
"canEditAnnotations": {"staff": true, "admin": true}, "canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"researcher": true, "staff": true, "admin": true}, "canEditDocuments": {"researcher": true, "staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditEvents": {"researcher": true, "staff": true, "admin": true}, "canEditEvents": {"researcher": true, "staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true}, "canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true}, "canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true}, "canEditFeaturedTexts": {"staff": true, "admin": true},
@ -63,11 +65,13 @@
"canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3}, "canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3},
"canReadText": {"guest": 0, "member": 0, "researcher": 1, "staff": 1, "admin": 1}, "canReadText": {"guest": 0, "member": 0, "researcher": 1, "staff": 1, "admin": 1},
"canRemoveItems": {"staff": true, "admin": true}, "canRemoveItems": {"staff": true, "admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"researcher": true, "staff": true, "admin": true}, "canSeeAccessed": {"researcher": true, "staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true}, "canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"researcher": true, "staff": true, "admin": true}, "canSeeDebugMenu": {"researcher": true, "staff": true, "admin": true},
"canSeeExtraItemViews": {"researcher": true, "staff": true, "admin": true}, "canSeeExtraItemViews": {"researcher": true, "staff": true, "admin": true},
"canSeeMedia": {"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}, "canSeeItem": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
"canSeeSize": {"researcher": true, "staff": true, "admin": true}, "canSeeSize": {"researcher": true, "staff": true, "admin": true},
"canSeeSoftwareVersion": {"researcher": true, "staff": true, "admin": true}, "canSeeSoftwareVersion": {"researcher": true, "staff": true, "admin": true},
@ -94,6 +98,257 @@
*/ */
"clipLayers": ["subtitles", "keywords", "notes"], "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 "entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor" of entity object, its properties and their types (for example an "actor"
@ -1004,16 +1259,26 @@
"calendarFind": "", "calendarFind": "",
"calendarSelection": "", "calendarSelection": "",
"clipColumns": 2, "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": { "columns": {
"Colors": { "Colors": {
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"], "columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
"columnWidth": {} "columnWidth": {}
} }
}, },
"documentView": "view",
"documents": {}, "documents": {},
"documentSize": 256, "documentSize": 256,
"documentsSelection": {}, "documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}], "documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid", "documentsView": "grid",
"edit": "", "edit": "",
"edits": {}, "edits": {},
@ -1039,6 +1304,7 @@
], ],
"filtersSize": 176, "filtersSize": 176,
"find": {"conditions": [], "operator": "&"}, "find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true, "followPlayer": true,
"help": "", "help": "",
"icons": "posters", "icons": "posters",
@ -1063,7 +1329,6 @@
"page": "", "page": "",
"part": { "part": {
"api": "", "api": "",
"documents": "",
"entities": "", "entities": "",
"faq": "", "faq": "",
"help": "", "help": "",
@ -1104,6 +1369,11 @@
"featured": true, "featured": true,
"volumes": true "volumes": true
}, },
"documents": {
"personal": true,
"favorite": true,
"featured": true
},
"texts": { "texts": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,
@ -1117,6 +1387,8 @@
"sidebarSize": 256, "sidebarSize": 256,
"text": "", "text": "",
"texts": {}, "texts": {},
"documents": {},
"document": "",
"theme": "oxmedium", "theme": "oxmedium",
"updateAdvancedFindResults": false, "updateAdvancedFindResults": false,
"videoLoop": false, "videoLoop": false,

View file

@ -35,11 +35,13 @@
*/ */
"capabilities": { "capabilities": {
"canAddItems": {"member": true, "staff": true, "admin": true}, "canAddItems": {"member": true, "staff": true, "admin": true},
"canAddDocuments": {"member": true, "staff": true, "admin": true},
"canDownloadVideo": {"guest": 0, "member": 0, "staff": 4, "admin": 4}, "canDownloadVideo": {"guest": 0, "member": 0, "staff": 4, "admin": 4},
"canEditAnnotations": {"staff": true, "admin": true}, "canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true}, "canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true}, "canEditDocuments": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true}, "canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true}, "canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true}, "canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true}, "canEditFeaturedTexts": {"staff": true, "admin": true},
@ -61,11 +63,13 @@
"canPlayVideo": {"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}, "canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1},
"canRemoveItems": {"admin": true}, "canRemoveItems": {"admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"staff": true, "admin": true}, "canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true}, "canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true}, "canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true}, "canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"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}, "canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeSize": {"staff": true, "admin": true}, "canSeeSize": {"staff": true, "admin": true},
"canSeeSoftwareVersion": {"staff": true, "admin": true}, "canSeeSoftwareVersion": {"staff": true, "admin": true},
@ -92,6 +96,257 @@
*/ */
"clipLayers": ["transcripts", "keywords", "places", "events", "descriptions"], "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 "entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor" of entity object, its properties and their types (for example an "actor"
@ -882,16 +1137,27 @@
"calendarFind": "", "calendarFind": "",
"calendarSelection": "", "calendarSelection": "",
"clipColumns": 2, "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": { "columns": {
"Colors": { "Colors": {
"columns": ["title", "source", "project", "language", "hue", "saturation", "brightness"], "columns": ["title", "director", "language", "hue", "saturation", "brightness"],
"columnWidth": {} "columnWidth": {}
} }
}, },
"document": "",
"documents": {}, "documents": {},
"documentSize": 256, "documentSize": 256,
"documentView": "view",
"documentsSelection": {}, "documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}], "documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid", "documentsView": "grid",
"edit": "", "edit": "",
"edits": {}, "edits": {},
@ -917,6 +1183,7 @@
], ],
"filtersSize": 176, "filtersSize": 176,
"find": {"conditions": [], "operator": "&"}, "find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true, "followPlayer": true,
"help": "", "help": "",
"icons": "frames", "icons": "frames",
@ -937,7 +1204,6 @@
"page": "", "page": "",
"part": { "part": {
"api": "", "api": "",
"documents": "",
"entities": "", "entities": "",
"faq": "", "faq": "",
"help": "", "help": "",
@ -981,7 +1247,7 @@
"featured": true, "featured": true,
"volumes": true "volumes": true
}, },
"texts": { "documents": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,
"featured": true "featured": true

View file

@ -39,11 +39,13 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
*/ */
"capabilities": { "capabilities": {
"canAddItems": {"member": true, "staff": true, "admin": true}, "canAddItems": {"member": true, "staff": true, "admin": true},
"canAddDocuments": {"member": true, "staff": true, "admin": true},
"canDownloadVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, "canDownloadVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canEditAnnotations": {"staff": true, "admin": true}, "canEditAnnotations": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true}, "canEditDocuments": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true}, "canEditEntities": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true}, "canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true}, "canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true}, "canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"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}, "canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canPlayVideo": {"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}, "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}, "canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true}, "canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true}, "canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true}, "canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"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}, "canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeSize": {"staff": true, "admin": true}, "canSeeSize": {"staff": true, "admin": true},
"canSeeSoftwareVersion": {"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"], "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 "entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor" 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": "", "calendarFind": "",
"calendarSelection": "", "calendarSelection": "",
"clipColumns": 2, "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": { "columns": {
"Colors": { "Colors": {
"columns": ["title", "director", "language", "hue", "saturation", "brightness"], "columns": ["title", "director", "language", "hue", "saturation", "brightness"],
"columnWidth": {} "columnWidth": {}
} }
}, },
"document": "",
"documents": {}, "documents": {},
"documentSize": 256, "documentSize": 256,
"documentView": "view",
"documentsSelection": {}, "documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}], "documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid", "documentsView": "grid",
"edit": "", "edit": "",
"edits": {}, "edits": {},
@ -857,6 +1123,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
], ],
"filtersSize": 176, "filtersSize": 176,
"find": {"conditions": [], "operator": "&"}, "find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true, "followPlayer": true,
"help": "", "help": "",
"icons": "posters", "icons": "posters",
@ -877,7 +1144,6 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"page": "", "page": "",
"part": { "part": {
"api": "", "api": "",
"documents": "",
"entities": "", "entities": "",
"faq": "", "faq": "",
"help": "", "help": "",
@ -920,7 +1186,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"featured": true, "featured": true,
"volumes": true "volumes": true
}, },
"texts": { "documents": {
"personal": true, "personal": true,
"favorite": true, "favorite": true,
"featured": true "featured": true

View file

View 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

View file

@ -1,6 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # 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.db.models import Q, Manager
from django.conf import settings
import ox import ox
from oxdjango.query import QuerySet from oxdjango.query import QuerySet
@ -8,14 +12,31 @@ from oxdjango.query import QuerySet
import entity.managers import entity.managers
from oxdjango.managers import get_operator from oxdjango.managers import get_operator
from documentcollection.models import Collection
from item import utils
keymap = { keymap = {
'user': 'user__username',
'item': 'items__public_id', '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) k = condition.get('key', default_key)
@ -33,17 +54,47 @@ def parseCondition(condition, user, item=None):
op = '=' op = '='
if op.startswith('!'): if op.startswith('!'):
return ~buildCondition(k, op[1:], v) return buildCondition(k, op[1:], v, user, True, owner=owner)
else: else:
return buildCondition(k, op, v) return buildCondition(k, op, v, user, owner=owner)
def buildCondition(k, op, v, user, exclude=False, owner=None):
def buildCondition(k, op, v):
import entity.models 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': if k == 'id':
v = ox.fromAZ(v) v = ox.fromAZ(v)
return Q(**{k: v}) q = Q(**{k: v})
if isinstance(v, bool): 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 key = k
elif k == 'entity': elif k == 'entity':
entity_key, entity_v = entity.managers.namePredicate(op, v) entity_key, entity_v = entity.managers.namePredicate(op, v)
@ -51,13 +102,87 @@ def buildCondition(k, op, v):
v = entity.models.DocumentProperties.objects.filter(**{ v = entity.models.DocumentProperties.objects.filter(**{
'entity__' + entity_key: entity_v 'entity__' + entity_key: entity_v
}).values_list('document_id', flat=True) }).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: 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) 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: [ conditions: [
{ {
@ -80,12 +205,12 @@ def parseConditions(conditions, operator, user, item=None):
for condition in conditions: for condition in conditions:
if 'conditions' in condition: if 'conditions' in condition:
q = parseConditions(condition['conditions'], q = parseConditions(condition['conditions'],
condition.get('operator', '&'), user, item) condition.get('operator', '&'), user, item, owner=owner)
if q: if q:
conn.append(q) conn.append(q)
pass pass
else: else:
conn.append(parseCondition(condition, user, item)) conn.append(parseCondition(condition, user, item, owner=owner))
if conn: if conn:
q = conn[0] q = conn[0]
for c in conn[1:]: for c in conn[1:]:
@ -133,4 +258,21 @@ class DocumentManager(Manager):
if conditions: if conditions:
qs = qs.filter(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 return qs

View 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),
),
]

View 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),
]

View 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')]),
),
]

View 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')]),
),
]

View 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',
),
]

View 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')]),
),
]

View 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'),
),
]

View file

@ -5,17 +5,23 @@ from __future__ import division, print_function, absolute_import
import os import os
import re import re
from glob import glob from glob import glob
import unicodedata
from six import string_types from six import string_types
from six.moves.urllib.parse import quote, unquote from six.moves.urllib.parse import quote, unquote
from django.db import models from django.db import models, transaction
from django.db.models import Max from django.db.models import Q, Sum, Max
from django.contrib.auth.models import User from django.contrib.auth.models import User, Group
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete
from django.conf import settings
from PIL import Image from PIL import Image
import ox 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 item.models import Item
from archive.extract import resize_image from archive.extract import resize_image
from archive.chunk import save_chunk from archive.chunk import save_chunk
@ -23,57 +29,249 @@ from archive.chunk import save_chunk
from . import managers from . import managers
from . import utils 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 Document(models.Model):
class Meta:
unique_together = ("user", "name", "extension")
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, related_name='files') user = models.ForeignKey(User, related_name='documents')
name = models.CharField(max_length=255) groups = models.ManyToManyField(Group, blank=True, related_name='documents')
extension = models.CharField(max_length=255) extension = models.CharField(max_length=255)
size = models.IntegerField(default=0) size = models.IntegerField(default=0)
matches = 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) pages = models.IntegerField(default=-1)
width = models.IntegerField(default=-1) width = models.IntegerField(default=-1)
height = models.IntegerField(default=-1) height = models.IntegerField(default=-1)
description = models.TextField(default="")
oshash = models.CharField(max_length=16, unique=True, null=True) 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() objects = managers.DocumentManager()
uploading = models.BooleanField(default = False) 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)
items = models.ManyToManyField(Item, through='ItemProperties', related_name='documents') 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): def save(self, *args, **kwargs):
if not self.uploading: if not self.uploading:
if self.file: if self.file:
self.size = self.file.size self.size = self.file.size
self.get_info() 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.id:
if self.description: self.update_sort()
self.description_sort = ox.sort_string(self.description)[:512].lower() self.update_find()
self.update_facets()
new = False
else: else:
self.description_sort = None new = True
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)
super(Document, self).save(*args, **kwargs) super(Document, self).save(*args, **kwargs)
if new:
self.update_sort()
self.update_find()
self.update_facets()
self.update_matches() self.update_matches()
def __unicode__(self): def __unicode__(self):
@ -100,40 +298,61 @@ class Document(models.Model):
def get_id(self): def get_id(self):
return ox.toAZ(self.id) 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): def editable(self, user, item=None):
if not user or user.is_anonymous(): if not user or user.is_anonymous():
return False return False
if self.user == user or \ if self.user == user or \
user.is_staff or \ user.is_staff or \
user.profile.capability('canEditDocuments') == True or \ user.profile.capability('canEditDocuments') is True or \
(item and item.editable(user)): (item and item.editable(user)):
return True return True
return False return False
def edit(self, data, user, item=None): 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: if item:
p, created = ItemProperties.objects.get_or_create(item=item, document=self) p, created = ItemProperties.objects.get_or_create(item=item, document=self)
if 'description' in data: if 'description' in data:
p.description = ox.sanitize_html(data['description']) p.description = ox.sanitize_html(data['description'])
p.save() 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 @property
def dimensions(self): def dimensions(self):
if self.extension == 'pdf': if self.extension == 'pdf':
return self.pages return self.pages
elif self.extension == 'html':
return len(self.data.get('text', '').split(' '))
else: else:
return self.resolution return self.resolution
@ -141,21 +360,43 @@ class Document(models.Model):
def resolution(self): def resolution(self):
return [self.width, self.height] 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): def json(self, keys=None, user=None, item=None):
if not keys: if not keys:
keys=[ keys = [
'description', 'description',
'dimensions', 'dimensions',
'editable', 'editable',
'entities', 'entities',
'extension', 'extension',
'id', 'id',
'name',
'oshash', 'oshash',
'title',
'ratio', 'ratio',
'matches',
'size', 'size',
'user', '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 = {} response = {}
_map = { _map = {
} }
@ -166,6 +407,10 @@ class Document(models.Model):
response[key] = self.editable(user) response[key] = self.editable(user)
elif key == 'user': elif key == 'user':
response[key] = self.user.username 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': elif key == 'entities':
dps = self.documentproperties.select_related('entity').order_by('index') dps = self.documentproperties.select_related('entity').order_by('index')
response[key] = entity_jsons = [] response[key] = entity_jsons = []
@ -175,8 +420,12 @@ class Document(models.Model):
entity_jsons.append(entity_json) entity_jsons.append(entity_json)
elif key == 'items': elif key == 'items':
response[key] = [i['public_id'] for i in self.items.all().values('public_id')] 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)): 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 item:
if isinstance(item, string_types): if isinstance(item, string_types):
item = Item.objects.get(public_id=item) item = Item.objects.get(public_id=item)
@ -185,6 +434,10 @@ class Document(models.Model):
if 'description' in keys and d[0].description: if 'description' in keys and d[0].description:
response['description'] = d[0].description response['description'] = d[0].description
response['index'] = d[0].index response['index'] = d[0].index
if keys:
for key in list(response):
if key not in keys:
del response[key]
return response return response
def path(self, name=''): def path(self, name=''):
@ -211,6 +464,9 @@ class Document(models.Model):
return False, 0 return False, 0
def thumbnail(self, size=None, page=None): 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 src = self.file.path
folder = os.path.dirname(src) folder = os.path.dirname(src)
if size: if size:
@ -278,12 +534,12 @@ class Document(models.Model):
try: try:
size = Image.open(image).size size = Image.open(image).size
except: except:
size = [1,1] size = [1, 1]
else: else:
if self.width > 0: if self.width > 0:
size = self.resolution size = self.resolution
else: else:
size = [1,1] size = [640, 1024]
self.ratio = size[0] / size[1] self.ratio = size[0] / size[1]
return self.ratio return self.ratio
@ -337,6 +593,97 @@ class ItemProperties(models.Model):
if self.description: if self.description:
self.description_sort = ox.sort_string(self.description)[:512].lower() self.description_sort = ox.sort_string(self.description)[:512].lower()
else: else:
self.description_sort = self.document.description_sort self.description_sort = self.document.sort.description
super(ItemProperties, self).save(*args, **kwargs) 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]

View 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()

View file

@ -1,8 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
import subprocess import subprocess
from item.utils import sort_title, sort_string, get_by_id
def pdfpages(pdf): def pdfpages(pdf):
return int(pdfinfo(pdf).get('pages', '0')) return int(pdfinfo(pdf).get('pages', '0'))

View file

@ -3,7 +3,9 @@
from __future__ import division, print_function, absolute_import from __future__ import division, print_function, absolute_import
import os import os
import re
from glob import glob from glob import glob
import unicodedata
from six import string_types from six import string_types
import ox import ox
@ -13,7 +15,8 @@ from oxdjango.decorators import login_required_json
from oxdjango.http import HttpFileResponse from oxdjango.http import HttpFileResponse
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response, HttpErrorJson from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response, HttpErrorJson
from django import forms 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 import utils
from item.models import Item from item.models import Item
@ -35,6 +38,13 @@ def get_document_or_404_json(id):
@login_required_json @login_required_json
def addDocument(request, data): def addDocument(request, data):
''' '''
Create new html document
takes {
title: string
}
or
Adds one or more documents to one or more items Adds one or more documents to one or more items
takes { takes {
item: string or [string], // one or more item ids (optional) item: string or [string], // one or more item ids (optional)
@ -46,6 +56,14 @@ def addDocument(request, data):
see: editDocument, findDocuments, getDocument, removeDocument, sortDocuments see: editDocument, findDocuments, getDocument, removeDocument, sortDocuments
''' '''
response = json_response() 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: if 'ids' in data:
ids = data['ids'] ids = data['ids']
else: else:
@ -95,7 +113,8 @@ def editDocument(request, data):
Edits data for a document Edits data for a document
takes { takes {
id: string, // document id id: string, // document id
name: string, // new document name
key: value, // set new data
description: string // new document description description: string // new document description
item: string // item id (optional) item: string // item id (optional)
} }
@ -126,22 +145,26 @@ actions.register(editDocument, cache=False)
def _order_query(qs, sort, item=None): def _order_query(qs, sort, item=None):
prefix = 'sort__'
order_by = [] order_by = []
for e in sort: for e in sort:
operator = e['operator'] operator = e['operator']
if operator != '-': if operator != '-':
operator = '' operator = ''
key = { key = {
'name': 'name_sort',
'description': 'descriptions__description_sort' 'description': 'descriptions__description_sort'
if item else 'description_sort', if item else 'description',
'dimensions': 'dimensions_sort',
'index': 'items__itemproperties__index', 'index': 'items__itemproperties__index',
#fixme:
'position': 'id',
'name': 'title',
}.get(e['key'], e['key']) }.get(e['key'], e['key'])
if key == 'resolution': if key == 'resolution':
order_by.append('%swidth'%operator) order_by.append('%swidth'%operator)
order_by.append('%sheight'%operator) order_by.append('%sheight'%operator)
else: else:
if '__' not in key:
key = "%s%s" % (prefix, key)
order = '%s%s' % (operator, key) order = '%s%s' % (operator, key)
order_by.append(order) order_by.append(order)
if order_by: if order_by:
@ -149,6 +172,24 @@ def _order_query(qs, sort, item=None):
qs = qs.distinct() qs = qs.distinct()
return qs 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): def get_item(query):
for c in query.get('conditions', []): for c in query.get('conditions', []):
if c.get('key') == 'item': if c.get('key') == 'item':
@ -162,7 +203,7 @@ def parse_query(data, user):
for key in ('keys', 'group', 'file', 'range', 'position', 'positions', 'sort'): for key in ('keys', 'group', 'file', 'range', 'position', 'positions', 'sort'):
if key in data: if key in data:
query[key] = data[key] 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', {})) query['item'] = get_item(data.get('query', {}))
return query return query
@ -192,7 +233,24 @@ def findDocuments(request, data):
#order #order
qs = _order_query(query['qs'], query['sort'], query['item']) qs = _order_query(query['qs'], query['sort'], query['item'])
response = json_response() 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]] qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [l.json(data['keys'], request.user, query['item']) for l in qs] 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 'chunk' in request.FILES:
if file.editable(request.user): if file.editable(request.user):
response = process_chunk(request, file.save_chunk) 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 # id is used to select document in dialog after upload
response['id'] = file.get_id() response['id'] = file.get_id()
return render_to_json_response(response) return render_to_json_response(response)
#init upload #init upload
else: else:
if not file: if not file:
created = False file = models.Document(user=request.user, extension=extension)
num = 1 file.data['title'] = name
_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.extension = extension file.extension = extension
file.uploading = True file.uploading = True
file.save() file.save()
@ -361,10 +411,81 @@ def upload(request):
file.width = -1 file.width = -1
file.pages = -1 file.pages = -1
file.save() 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({ return render_to_json_response({
'uploadUrl': upload_url, 'uploadUrl': upload_url,
'url': request.build_absolute_uri(file.get_absolute_url()), 'url': file.get_absolute_url(),
'result': 1 'result': 1
}) })
return render_to_json_response(response) 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)

View file

View 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

View 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')]),
),
]

View 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)

View 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')

View file

@ -34,11 +34,13 @@ class Command(BaseCommand):
if settings.DB_GIN_TRGM: if settings.DB_GIN_TRGM:
import entity.models import entity.models
import document.models
for table, column in ( for table, column in (
(models.ItemFind._meta.db_table, 'value'), # Item Find (models.ItemFind._meta.db_table, 'value'), # Item Find
(models.Clip._meta.db_table, 'findvalue'), # Clip Find (models.Clip._meta.db_table, 'findvalue'), # Clip Find
(models.Annotation._meta.db_table, 'findvalue'), # Annotation Find (models.Annotation._meta.db_table, 'findvalue'), # Annotation Find
(entity.models.Find._meta.db_table, 'value'), # Entity Find (entity.models.Find._meta.db_table, 'value'), # Entity Find
(document.models.Find._meta.db_table, 'value'), # Document Find
): ):
cursor = connection.cursor() cursor = connection.cursor()
indexes = connection.introspection.get_indexes(cursor, table) indexes = connection.introspection.get_indexes(cursor, table)

View file

@ -52,7 +52,7 @@ class Command(BaseCommand):
changes.append(sql) changes.append(sql)
rebuild = True rebuild = True
elif f.__class__.__name__ != db_types[name]: 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) changes.append(sql)
sql = 'ALTER TABLE "%s" ADD COLUMN "%s" %s' % (table_name, name, col_type) sql = 'ALTER TABLE "%s" ADD COLUMN "%s" %s' % (table_name, name, col_type)
changes.append(sql) changes.append(sql)
@ -116,3 +116,6 @@ class Command(BaseCommand):
if options['debug']: if options['debug']:
print(i) print(i)
i.update_sort() i.update_sort()
# and udpate doucments
import document.sync_sort
document.sync_sort.update_tables(options['debug'])

View file

@ -24,6 +24,7 @@ from django.utils import datetime_safe
import ox import ox
from oxdjango import fields from oxdjango import fields
from oxdjango.sortmodel import get_sort_field
import ox.web.imdb import ox.web.imdb
import ox.image 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']) sort_type = key.get('sortType', key['type'])
if isinstance(sort_type, list): if isinstance(sort_type, list):
sort_type = sort_type[0] sort_type = sort_type[0]
model = { field = get_sort_field(sort_type)
'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)]
if name not in attrs: if name not in attrs:
attrs[name] = model[0](**model[1]) attrs[name] = field[0](**field[1])
ItemSort = type('ItemSort', (models.Model,), attrs) ItemSort = type('ItemSort', (models.Model,), attrs)
ItemSort.fields = [f.name for f in ItemSort._meta.fields] ItemSort.fields = [f.name for f in ItemSort._meta.fields]

View file

@ -326,7 +326,7 @@ def autocomplete(request, data):
returns { returns {
items: [string, ...] // list of matching strings items: [string, ...] // list of matching strings
} }
see: autocompleteEntities see: autocompleteDocuments, autocompleteEntities
''' '''
if 'range' not in data: if 'range' not in data:
data['range'] = [0, 10] data['range'] = [0, 10]

View 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)]

View file

@ -138,6 +138,7 @@ INSTALLED_APPS = (
'user', 'user',
'urlalias', 'urlalias',
'tv', 'tv',
'documentcollection',
'document', 'document',
'entity', 'entity',
'websocket', 'websocket',

View file

@ -19,6 +19,7 @@ import oxdjango.api.urls
import app.views import app.views
import archive.views import archive.views
import document.views import document.views
import documentcollection.views
import text.views import text.views
import user.views import user.views
import edit.views import edit.views
@ -42,6 +43,7 @@ urlpatterns = [
url(r'^file/(?P<oshash>.*)$', archive.views.lookup_file), url(r'^file/(?P<oshash>.*)$', archive.views.lookup_file),
url(r'^api/?', include(oxdjango.api.urls)), url(r'^api/?', include(oxdjango.api.urls)),
url(r'^resetUI$', user.views.reset_ui), 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<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'^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), url(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),

View file

@ -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),
),
]

View file

@ -18,6 +18,7 @@ from ox.utils import json
from itemlist.models import List, Position from itemlist.models import List, Position
import text import text
import edit import edit
import documentcollection.models
from . import managers from . import managers
from . import tasks from . import tasks
@ -44,6 +45,7 @@ class SessionData(models.Model):
browser = models.CharField(max_length=255, null=True) browser = models.CharField(max_length=255, null=True)
numberoflists = models.IntegerField(default=0, null=True) numberoflists = models.IntegerField(default=0, null=True)
numberofcollections = models.IntegerField(default=0, null=True)
objects = managers.SessionDataManager() objects = managers.SessionDataManager()
@ -96,6 +98,7 @@ class SessionData(models.Model):
else: else:
self.groupssort = None self.groupssort = None
self.numberoflists = self.user.lists.count() self.numberoflists = self.user.lists.count()
self.numberofcollections = self.user.collections.count()
else: else:
self.groupssort = None self.groupssort = None
super(SessionData, self).save(*args, **kwargs) super(SessionData, self).save(*args, **kwargs)
@ -157,6 +160,7 @@ class SessionData(models.Model):
'newsletter': False, 'newsletter': False,
'notes': '', 'notes': '',
'numberoflists': 0, 'numberoflists': 0,
'numberofcollections': 0,
'screensize': self.screensize, 'screensize': self.screensize,
'system': ua['system']['string'], 'system': ua['system']['string'],
'timesseen': self.timesseen, 'timesseen': self.timesseen,
@ -173,6 +177,7 @@ class SessionData(models.Model):
j['newsletter'] = p.newsletter j['newsletter'] = p.newsletter
j['notes'] = p.notes j['notes'] = p.notes
j['numberoflists'] = self.numberoflists j['numberoflists'] = self.numberoflists
j['numberofcollections'] = self.numberofcollections
if keys: if keys:
for key in list(j): for key in list(j):
if key not in keys: if key not in keys:
@ -249,9 +254,12 @@ def get_ui(user_ui, user=None):
ui[key] = new[key] ui[key] = new[key]
return ui return ui
ui = update_ui(ui, user_ui) ui = update_ui(ui, user_ui)
if not 'lists' in ui: if 'lists' not in ui:
ui['lists'] = {} ui['lists'] = {}
if 'collections' not in ui:
ui['collections'] = {}
def add(lists, section): def add(lists, section):
ids = [] ids = []
for l in lists: for l in lists:
@ -279,6 +287,29 @@ def get_ui(user_ui, user=None):
ids.append(id) ids.append(id)
return ids 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): def add_texts(texts, section):
P = text.models.Position P = text.models.Position
ids = [] ids = []
@ -323,6 +354,7 @@ def get_ui(user_ui, user=None):
ids.append(t.get_id()) ids.append(t.get_id())
return ids return ids
# lists (items)
ids = [''] ids = ['']
if user: if user:
ids += add(user.lists.exclude(status="featured"), 'personal') 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']): for i in list(ui['lists']):
if i not in ids: if i not in ids:
del ui['lists'][i] 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 = [''] tids = ['']
if user: if user:
tids += add_texts(user.texts.exclude(status="featured"), 'personal') tids += add_texts(user.texts.exclude(status="featured"), 'personal')
tids += add_texts(user.subscribed_texts.filter(status='public'), 'public') tids += add_texts(user.subscribed_texts.filter(status='public'), 'public')
tids += add_texts(text.models.Text.objects.filter(status='featured'), 'featured') tids += add_texts(text.models.Text.objects.filter(status='featured'), 'featured')
# edits
tids = ['']
if user: if user:
tids += add_edits(user.edits.exclude(status="featured"), 'personal') tids += add_edits(user.edits.exclude(status="featured"), 'personal')
tids += add_edits(user.subscribed_edits.filter(status='public'), 'public') tids += add_edits(user.subscribed_edits.filter(status='public'), 'public')

View file

@ -46,3 +46,13 @@ def update_numberoflists(username):
).update( ).update(
numberoflists=user.lists.count() 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()
)

View file

@ -800,7 +800,10 @@ def setUI(request, data):
access, created = Access.objects.get_or_create(item=item, user=None) access, created = Access.objects.get_or_create(item=item, user=None)
if not created: if not created:
access.save() 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() response = json_response()
return render_to_json_response(response) return render_to_json_response(response)
actions.register(setUI, cache=False) actions.register(setUI, cache=False)

View file

@ -28,6 +28,12 @@ pandora.UI = (function() {
pandora.user.ui._findState = pandora.getFindState( pandora.user.ui._findState = pandora.getFindState(
pandora.user.ui.find 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); Ox.Theme(pandora.user.ui.theme);
pandora.$ui.appPanel.reload(); pandora.$ui.appPanel.reload();
}); });
@ -40,6 +46,9 @@ pandora.UI = (function() {
var add = {}, var add = {},
args, args,
collection,
collectionView,
collectionSettings = pandora.site.collectionSettings,
editSettings = pandora.site.editSettings, editSettings = pandora.site.editSettings,
item, item,
list, list,
@ -69,6 +78,76 @@ pandora.UI = (function() {
} else if (args.section == 'edits') { } else if (args.section == 'edits') {
trigger.section = args.section; trigger.section = args.section;
trigger.edit = args.edit; 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 { } else {
if ('find' in args) { if ('find' in args) {
// the challenge here is that find may change list, // the challenge here is that find may change list,

View file

@ -20,6 +20,7 @@ pandora.URL = (function() {
Ox.contains(Object.keys(pandora.site.user.ui.part), state.page) Ox.contains(Object.keys(pandora.site.user.ui.part), state.page)
) { ) {
state.part = pandora.user.ui.part[state.page]; state.part = pandora.user.ui.part[state.page];
/*
if ( if (
state.page == 'documents' state.page == 'documents'
&& pandora.user.ui.documents[state.part] && pandora.user.ui.documents[state.part]
@ -27,6 +28,7 @@ pandora.URL = (function() {
) { ) {
state.span = pandora.user.ui.documents[state.part].position; state.span = pandora.user.ui.documents[state.part].position;
} }
*/
} }
} else { } 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') { } else if (pandora.user.ui.section == 'edits') {
var editPoints = pandora.user.ui.edits[state.item] || {}; var editPoints = pandora.user.ui.edits[state.item] || {};
if (state.item) { if (state.item) {
@ -118,10 +139,15 @@ pandora.URL = (function() {
var set = {}; var set = {};
Ox.Log('URL', 'setState:', state); 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._list = pandora.getListState(pandora.user.ui.find);
pandora.user.ui._filterState = pandora.getFilterState(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._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)) { if (Ox.isEmpty(state)) {
@ -148,9 +174,6 @@ pandora.URL = (function() {
) && state.part) { ) && state.part) {
set['part.' + state.page] = state.part; set['part.' + state.page] = state.part;
} }
if (state.span) {
set['documents.' + state.part] = {position: state.span};
}
pandora.UI.set(set); pandora.UI.set(set);
callback && callback(); callback && callback();
@ -222,7 +245,16 @@ pandora.URL = (function() {
set.find = pandora.site.user.ui.find; 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') { } else if (state.type == 'edits') {
if (state.view) { if (state.view) {
@ -248,16 +280,6 @@ pandora.URL = (function() {
set[key + '.clip'] = state.span; 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(); Ox.Request.cancel();
@ -368,6 +390,19 @@ pandora.URL = (function() {
.concat(pandora.site.itemKeys); .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 // Texts
views['texts'] = { views['texts'] = {
list: [], list: [],
@ -377,7 +412,6 @@ pandora.URL = (function() {
list: {}, list: {},
item: {} item: {}
}; };
return { return {
views: views, views: views,
sortKeys: sortKeys sortKeys: sortKeys
@ -388,7 +422,7 @@ pandora.URL = (function() {
that.init = function() { that.init = function() {
var itemsSection = pandora.site.itemsSection, var itemsSection = pandora.site.itemsSection,
findKeys, spanType = {}; findKeys = {}, spanType = {};
spanType[itemsSection] = { spanType[itemsSection] = {
list: { list: {
@ -411,12 +445,25 @@ pandora.URL = (function() {
annotations: 'duration' annotations: 'duration'
} }
}; };
spanType['documents'] = {
list: {},
item: {
view: 'string',
}
};
spanType['texts'] = { spanType['texts'] = {
list: {}, list: {},
item: {text: 'string'} 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({ self.URL = Ox.URL(Ox.extend({
findKeys: findKeys, findKeys: findKeys,
@ -426,14 +473,14 @@ pandora.URL = (function() {
getSort: pandora.getSort, getSort: pandora.getSort,
getSpan: pandora.getSpan, getSpan: pandora.getSpan,
pages: [].concat( pages: [].concat(
['home', 'software', 'api', 'help', 'tv', 'documents', 'entities'], ['home', 'software', 'api', 'help', 'tv', 'entities'],
pandora.site.sitePages.map(function(page) { pandora.site.sitePages.map(function(page) {
return page.id; return page.id;
}), }),
['preferences', 'signup', 'signin', 'signout'] ['preferences', 'signup', 'signin', 'signout']
), ),
spanType: spanType, spanType: spanType,
types: [pandora.site.itemName.plural.toLowerCase(), 'edits', 'texts'], types: [pandora.site.itemName.plural.toLowerCase(), 'edits', 'documents', 'texts'],
}, getOptions())); }, getOptions()));
window.addEventListener('hashchange', function() { window.addEventListener('hashchange', function() {
@ -508,7 +555,7 @@ pandora.URL = (function() {
that.push = function(stateOrURL, expandURL) { that.push = function(stateOrURL, expandURL) {
var state, var state,
title = pandora.getPageTitle(stateOrURL) title = pandora.getPageTitle(stateOrURL)
|| pandora.getDocumentTitle(), || pandora.getWindowTitle(),
url; url;
pandora.replaceURL = expandURL; pandora.replaceURL = expandURL;
if (Ox.isObject(stateOrURL)) { if (Ox.isObject(stateOrURL)) {
@ -524,7 +571,7 @@ pandora.URL = (function() {
that.replace = function(stateOrURL, title) { that.replace = function(stateOrURL, title) {
var state, var state,
title = pandora.getPageTitle(stateOrURL) title = pandora.getPageTitle(stateOrURL)
|| pandora.getDocumentTitle(), || pandora.getWindowTitle(),
url; url;
if (Ox.isObject(stateOrURL)) { if (Ox.isObject(stateOrURL)) {
state = stateOrURL; state = stateOrURL;
@ -576,7 +623,7 @@ pandora.URL = (function() {
state = getState(); state = getState();
self.URL[action]( self.URL[action](
state, state,
pandora.getPageTitle(state) || pandora.getDocumentTitle() pandora.getPageTitle(state) || pandora.getWindowTitle()
); );
pandora.replaceURL = false; pandora.replaceURL = false;
} }

View 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;
};

View file

@ -8,8 +8,10 @@ pandora.ui.allItems = function(section) {
var canAddItems = !pandora.site.itemRequiresVideo && pandora.site.capabilities.canAddItems[pandora.user.level], var canAddItems = !pandora.site.itemRequiresVideo && pandora.site.capabilities.canAddItems[pandora.user.level],
canUploadVideo = 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() that = Ox.Element()
.addClass('OxSelectableElement' + (pandora.user.ui._list ? '' : ' OxSelected')) .addClass('OxSelectableElement' + (isSelected ? '' : ' OxSelected'))
.css({ .css({
height: '16px', height: '16px',
cursor: 'default', cursor: 'default',
@ -19,13 +21,21 @@ pandora.ui.allItems = function(section) {
click: function() { click: function() {
that.gainFocus(); that.gainFocus();
if (section == 'items') { 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 { } else {
pandora.UI.set(section.slice(0, -1), ''); pandora.UI.set(section.slice(0, -1), '');
} }
} }
}) })
.bindEvent({ .bindEvent({
pandora_document: updateSelected,
pandora_finddocuments: updateSelected,
pandora_edit: updateSelected, pandora_edit: updateSelected,
pandora_find: updateSelected, pandora_find: updateSelected,
pandora_section: updateSelected, pandora_section: updateSelected,
@ -95,6 +105,56 @@ pandora.ui.allItems = function(section) {
}, function(result) { }, function(result) {
that.update(result.data.items); 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') { } else if (section == 'texts') {
$buttons[0] = Ox.Button({ $buttons[0] = Ox.Button({
style: 'symbol', style: 'symbol',
@ -124,6 +184,7 @@ pandora.ui.allItems = function(section) {
function updateSelected() { function updateSelected() {
that[ that[
(section == 'items' && pandora.user.ui._list) (section == 'items' && pandora.user.ui._list)
|| (section == 'documents' && pandora.user.ui._collection)
|| (section == 'edits' && pandora.user.ui.edit) || (section == 'edits' && pandora.user.ui.edit)
|| (section == 'texts' && pandora.user.ui.text) || (section == 'texts' && pandora.user.ui.text)
? 'removeClass' : 'addClass' ? 'removeClass' : 'addClass'

View file

@ -2,8 +2,12 @@
'use strict'; 'use strict';
pandora.ui.backButton = function() { pandora.ui.backButton = function() {
var that = Ox.Button({ var that = Ox.Button({
title: Ox._('Back to {0}', [Ox._(pandora.site.itemName.plural)]), title: Ox._('Back to {0}', [
width: 96 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({ }).css({
float: 'left', float: 'left',
margin: '4px 0 0 4px' margin: '4px 0 0 4px'
@ -21,8 +25,12 @@ pandora.ui.backButton = function() {
if (['accessed', 'timesaccessed'].indexOf(pandora.user.ui.listSort[0].key) > -1) { if (['accessed', 'timesaccessed'].indexOf(pandora.user.ui.listSort[0].key) > -1) {
Ox.Request.clearCache('find'); Ox.Request.clearCache('find');
} }
if (pandora.user.ui.section == 'documents') {
pandora.UI.set({document: ''});
} else {
pandora.UI.set({item: ''}); pandora.UI.set({item: ''});
} }
}
}); });
return that; return that;
}; };

289
static/js/collection.js Normal file
View 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;
};

View file

@ -31,7 +31,7 @@ pandora.ui.deleteDocumentDialog = function(files, callback) {
}) })
], ],
content: files.length == 1 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]), : Ox._('Are you sure you want to delete {0} documents?', [files.length]),
keys: {enter: 'delete', escape: 'keep'}, keys: {enter: 'delete', escape: 'keep'},
title: files.length == 1 title: files.length == 1

View file

@ -5,7 +5,7 @@
pandora.ui.deleteListDialog = function(list) { pandora.ui.deleteListDialog = function(list) {
var ui = pandora.user.ui, var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
listData = pandora.getListData(list), listData = pandora.getListData(list),
$folderList = pandora.$ui.folderList[listData.folder], $folderList = pandora.$ui.folderList[listData.folder],
@ -42,6 +42,14 @@ pandora.ui.deleteListDialog = function(list) {
pandora.UI.set({ pandora.UI.set({
find: pandora.site.user.ui.find 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 { } else {
pandora.UI.set( pandora.UI.set(
folderItem.toLowerCase(), '' folderItem.toLowerCase(), ''

282
static/js/document.js Normal file
View 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;
};

View 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;
};

View 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;
};

View file

@ -19,7 +19,7 @@ pandora.openDocumentDialog = function(options) {
operator: '|' operator: '|'
}, },
range: [0, options.ids.length], range: [0, options.ids.length],
keys: ['description', 'dimensions', 'extension', 'id', 'name', 'modified'] keys: ['description', 'dimensions', 'extension', 'id', 'title', 'modified']
}, function(result) { }, function(result) {
var i = 0, var i = 0,
documents = Ox.sort(result.data.items, function(item) { documents = Ox.sort(result.data.items, function(item) {
@ -173,7 +173,7 @@ pandora.ui.documentDialog = function(options) {
? pandora.user.ui.documents[item.id].position ? pandora.user.ui.documents[item.id].position
: 1, : 1,
url: '/documents/' + item.id + '/' url: '/documents/' + item.id + '/'
+ item.name + '.' + item.extension, + item.title + '.' + item.extension,
width: dialogWidth, width: dialogWidth,
zoom: 'fit' zoom: 'fit'
}) })
@ -185,7 +185,7 @@ pandora.ui.documentDialog = function(options) {
imageHeight: item.dimensions[1], imageHeight: item.dimensions[1],
imagePreviewURL: pandora.getMediaURL('/documents/' + item.id + '/256p.jpg?' + item.modified), imagePreviewURL: pandora.getMediaURL('/documents/' + item.id + '/256p.jpg?' + item.modified),
imageURL: pandora.getMediaURL('/documents/' + item.id + '/' imageURL: pandora.getMediaURL('/documents/' + item.id + '/'
+ item.name + '.' + item.extension + '?' + item.modified), + item.title + '.' + item.extension + '?' + item.modified),
imageWidth: item.dimensions[0], imageWidth: item.dimensions[0],
width: dialogWidth width: dialogWidth
}) })
@ -217,7 +217,7 @@ pandora.ui.documentDialog = function(options) {
} }
function setTitle() { function setTitle() {
that.options({title: item.name + '.' + item.extension}); that.options({title: item.title + '.' + item.extension});
} }
that.getItems = function() { that.getItems = function() {

View 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;
};

View 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;
};

View 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;
};

View 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;
};

View file

@ -2,22 +2,12 @@
'use strict'; 'use strict';
pandora.ui.documentsPanel = function(options) { pandora.documentColumns = [
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 = [
{ {
id: 'name', id: 'title',
operator: '+', operator: '+',
title: Ox._('Name'), title: Ox._('Title'),
find: true,
visible: true, visible: true,
width: 256 width: 256
}, },
@ -35,15 +25,16 @@ pandora.ui.documentsPanel = function(options) {
id: 'extension', id: 'extension',
operator: '+', operator: '+',
title: Ox._('Extension'), title: Ox._('Extension'),
find: true,
visible: true, visible: true,
width: 64 width: 64
}, },
{ {
align: 'right', align: 'right',
format: function(value) { format: function(value, data) {
return Ox.isArray(value) return Ox.isArray(value)
? Ox.formatDimensions(value, 'px') ? Ox.formatDimensions(value, 'px')
: Ox.formatCount(value, 'page'); : Ox.formatCount(value, data.extension == 'html' ? 'word' : 'page');
}, },
id: 'dimensions', id: 'dimensions',
operator: '-', operator: '-',
@ -66,6 +57,7 @@ pandora.ui.documentsPanel = function(options) {
id: 'description', id: 'description',
operator: '+', operator: '+',
title: Ox._('Description'), title: Ox._('Description'),
find: true,
visible: true, visible: true,
width: 256 width: 256
}, },
@ -81,6 +73,7 @@ pandora.ui.documentsPanel = function(options) {
id: 'user', id: 'user',
operator: '+', operator: '+',
title: Ox._('User'), title: Ox._('User'),
find: true,
visible: true, visible: true,
width: 128 width: 128
}, },
@ -106,46 +99,10 @@ pandora.ui.documentsPanel = function(options) {
visible: true, visible: true,
width: 144 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({ $orderButton = Ox.Button({
overlap: 'left', overlap: 'left',
title: getOrderButtonTitle(), title: getOrderButtonTitle(),
@ -160,55 +117,85 @@ pandora.ui.documentsPanel = function(options) {
}]}); }]});
} }
}), }),
$sortSelect = Ox.Select({
$sortElement = Ox.FormElementGroup({ items: pandora.documentColumns.map(function(column) {
elements: [$sortSelect, $orderButton], return {
float: 'right' id: column.id,
}) title: Ox._('Sort by {0}', [column.title])
.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;
}), }),
overlap: 'right', value: ui.documentsSort[0].key,
type: 'image' width: 128
}) })
.bindEvent({ .bindEvent({
change: function(data) { 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
}]});
} }
}), }),
that = Ox.FormElementGroup({
$findInput = Ox.Input({ elements: [$sortSelect, $orderButton],
changeOnKeypress: true, float: 'right'
clear: true,
placeholder: Ox._('Find: All'),
width: 192
}) })
.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({ .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({ $listBar = Ox.Bar({size: 24}),
elements: [
$findSelect, $viewSelect = pandora.ui.documentViewSelect().appendTo($listBar),
$findInput $sortElement = pandora.ui.documentSortSelect().appendTo($listBar),
]
}) $findElement = findElement(updateList, isItemView).appendTo($listBar),
.css({float: 'right', margin: '4px 4px 4px 2px'})
.appendTo($listBar),
$list = renderList(), $list = renderList(),
@ -389,7 +376,7 @@ pandora.ui.documentsPanel = function(options) {
that.size(1, data.value); that.size(1, data.value);
}, },
pandora_documentssort: function(data) { pandora_documentssort: function(data) {
updateSortElement(); $sortElement.sortValue(ui.documentsSort[0].key);
$list.options({sort: data.value}); $list.options({sort: data.value});
}, },
pandora_documentsview: function(data) { pandora_documentsview: function(data) {
@ -410,12 +397,6 @@ pandora.ui.documentsPanel = function(options) {
pandora.$ui.documentsList = $list; 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() { function addDocuments() {
var ids = ui.documentsSelection['']; var ids = ui.documentsSelection[''];
pandora.api.addDocument({ pandora.api.addDocument({
@ -460,12 +441,59 @@ pandora.ui.documentsPanel = function(options) {
openDocumentsDialog(); openDocumentsDialog();
} }
function getOrderButtonTitle() { function findElement(callback, isItemView) {
return ui.documentsSort[0].operator == '+' ? 'up' : 'down'; 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() { $findInput = Ox.Input({
return Ox._(ui.documentsSort[0].operator == '+' ? 'Ascending' : 'Descending'); 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() { function getFindInputWidth() {
@ -564,8 +592,8 @@ pandora.ui.documentsPanel = function(options) {
.append( .append(
$name = Ox.EditableContent({ $name = Ox.EditableContent({
editable: editable, editable: editable,
tooltip: editable ? pandora.getEditTooltip('name') : '', tooltip: editable ? pandora.getEditTooltip('title') : '',
value: item.name, value: item.title,
width: width width: width
}) })
.css({ .css({
@ -580,11 +608,11 @@ pandora.ui.documentsPanel = function(options) {
}, },
submit: function(data) { submit: function(data) {
pandora.api.editDocument({ pandora.api.editDocument({
name: data.value, title: data.value,
id: item.id, id: item.id,
item: ui.item, item: ui.item,
}, function(result) { }, function(result) {
$name.options({value: result.data.name}); $name.options({value: result.data.title});
Ox.Request.clearCache('findDocuments'); Ox.Request.clearCache('findDocuments');
$list.reloadList(); $list.reloadList();
}); });
@ -631,10 +659,10 @@ pandora.ui.documentsPanel = function(options) {
items: [ items: [
Ox.Input({ Ox.Input({
disabled: !editable, disabled: !editable,
id: 'name', id: 'title',
label: Ox._('Name'), label: Ox._('Title'),
labelWidth: labelWidth, labelWidth: labelWidth,
value: item.name, value: item.title,
width: width width: width
}), }),
Ox.Input({ Ox.Input({
@ -839,7 +867,7 @@ pandora.ui.documentsPanel = function(options) {
function renderList() { function renderList() {
var options = { var options = {
items: pandora.api.findDocuments, 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: { query: {
conditions: isItemView ? [{ key: 'item', value: ui.item, operator: '==' }] : [], conditions: isItemView ? [{ key: 'item', value: ui.item, operator: '==' }] : [],
operator: '&' operator: '&'
@ -847,27 +875,27 @@ pandora.ui.documentsPanel = function(options) {
selected: ui.documentsSelection[isItemView ? ui.item : ''] || [], selected: ui.documentsSelection[isItemView ? ui.item : ''] || [],
sort: ui.documentsSort.concat([ sort: ui.documentsSort.concat([
{key: 'extension', operator: '+'}, {key: 'extension', operator: '+'},
{key: 'name', operator: '+'} {key: 'title', operator: '+'}
]), ]),
unique: 'id' unique: 'id'
}; };
return (ui.documentsView == 'list' ? Ox.TableList(Ox.extend(options, { return (ui.documentsView == 'list' ? Ox.TableList(Ox.extend(options, {
columns: columns, columns: pandora.documentColumns,
columnsVisible: true, columnsVisible: true,
scrollbarVisible: true, scrollbarVisible: true,
})) : Ox.IconList(Ox.extend(options, { })) : Ox.IconList(Ox.extend(options, {
item: function(data, sort, size) { item: function(data, sort, size) {
var sortKey = sort[0].key, var sortKey = sort[0].key,
infoKey = sortKey == 'name' ? 'extension' : sortKey, infoKey = sortKey == 'title' ? 'extension' : sortKey,
info = ( info = (
Ox.getObjectById(columns, infoKey).format || Ox.identity Ox.getObjectById(pandora.documentColumns, infoKey).format || Ox.identity
)(data[infoKey]), )(data[infoKey]),
size = size || 128; size = size || 128;
return { return {
height: Math.round(data.ratio > 1 ? size / data.ratio : size), height: Math.round(data.ratio > 1 ? size / data.ratio : size),
id: data.id, id: data.id,
info: info, info: info,
title: data.name, title: data.title,
url: pandora.getMediaURL('/documents/' + data.id + '/256p.jpg?' + data.modified), url: pandora.getMediaURL('/documents/' + data.id + '/256p.jpg?' + data.modified),
width: Math.round(data.ratio > 1 ? size : size * data.ratio) 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 // we can't open upload dialog via control+n
isItemView && openDocumentsDialog(); isItemView && openDocumentsDialog();
}, },
copy: function(data) {
pandora.clipboard.copy(data.ids, 'document');
},
closepreview: closeDocuments, closepreview: closeDocuments,
'delete': isItemView ? removeDocuments : deleteDocuments, 'delete': isItemView ? removeDocuments : deleteDocuments,
init: function(data) { init: function(data) {
@ -892,6 +923,22 @@ pandora.ui.documentsPanel = function(options) {
}, },
open: openDocuments, open: openDocuments,
openpreview: 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) { select: function(data) {
pandora.UI.set( pandora.UI.set(
'documentsSelection.' + (isItemView ? ui.item : ''), data.ids 'documentsSelection.' + (isItemView ? ui.item : ''), data.ids
@ -1017,9 +1064,10 @@ pandora.ui.documentsPanel = function(options) {
); );
} }
function updateList() { function updateList(data) {
var key = $findSelect.value(),
value = $findInput.value(), var key = data.key,
value = data.value,
itemCondition = isItemView itemCondition = isItemView
? {key: 'item', operator: '==', value: ui.item} ? {key: 'item', operator: '==', value: ui.item}
: null, : null,
@ -1040,11 +1088,7 @@ pandora.ui.documentsPanel = function(options) {
} }
function updateSortElement() { function updateSortElement() {
$sortSelect.value(ui.documentsSort[0].key); $sortElement.sortValue(ui.documentsSort[0].key);
$orderButton.options({
title: getOrderButtonTitle(),
tooltip: getOrderButtonTooltip()
});
} }
function uploadDocuments(data) { function uploadDocuments(data) {

View 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;
};

View file

@ -3,7 +3,7 @@
pandora.ui.folderBrowserBar = function(id, section) { pandora.ui.folderBrowserBar = function(id, section) {
section = section || pandora.user.ui.section; section = section || pandora.user.ui.section;
var ui = pandora.user.ui, var ui = pandora.user.ui,
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section), folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
that = Ox.Bar({ that = Ox.Bar({
size: 24 size: 24

View file

@ -7,7 +7,7 @@ pandora.ui.folderBrowserList = function(id, section) {
var ui = pandora.user.ui, var ui = pandora.user.ui,
columnWidth = (ui.sidebarSize - Ox.UI.SCROLLBAR_SIZE - (section != 'texts' ? 96 : 48)) / 2, columnWidth = (ui.sidebarSize - Ox.UI.SCROLLBAR_SIZE - (section != 'texts' ? 96 : 48)) / 2,
i = Ox.getIndexById(pandora.site.sectionFolders[section], id), i = Ox.getIndexById(pandora.site.sectionFolders[section], id),
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section), folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
that = Ox.TableList({ that = Ox.TableList({
columns: [ columns: [
@ -152,7 +152,12 @@ pandora.ui.folderBrowserList = function(id, section) {
// not-featured list may be in the user's favorites folder // not-featured list may be in the user's favorites folder
keys: id == 'featured' ? ['subscribed'] : [], keys: id == 'featured' ? ['subscribed'] : [],
pageLength: 1000, 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: '+'}], sort: [{key: 'name', operator: '+'}],
unique: 'id' unique: 'id'
}) })
@ -226,6 +231,15 @@ pandora.ui.folderBrowserList = function(id, section) {
operator: '&' operator: '&'
} }
}); });
} else if (section == 'documents') {
pandora.UI.set({
findDocuments: {
conditions: list ? [
{key: 'list', value: data.ids[0], operator: '=='}
] : [],
operator: '&'
}
});
} else { } else {
pandora.UI.set(section.slice(0, -1), list); pandora.UI.set(section.slice(0, -1), list);
} }

View file

@ -5,7 +5,7 @@ pandora.ui.folderList = function(id, section) {
section = section || pandora.user.section; section = section || pandora.user.section;
var ui = pandora.user.ui, var ui = pandora.user.ui,
i = Ox.getIndexById(pandora.site.sectionFolders[section], id), i = Ox.getIndexById(pandora.site.sectionFolders[section], id),
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section), folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level], canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
$placeholder, $placeholder,
@ -20,9 +20,7 @@ pandora.ui.folderList = function(id, section) {
}, },
format: function(value, data) { format: function(value, data) {
return $('<img>').attr({ return $('<img>').attr({
src: '/' + folderItem.toLowerCase() + '/' src: pandora.getListIcon(section, data.id, '', data.modified)
+ encodeURIComponent(data.id) + '/icon.jpg?'
+ data.modified
}).css({ }).css({
width: '14px', width: '14px',
height: '14px', height: '14px',
@ -337,7 +335,7 @@ pandora.ui.folderList = function(id, section) {
pandora.api['unsubscribeFrom' + folderItem]({ pandora.api['unsubscribeFrom' + folderItem]({
id: data.ids[0] id: data.ids[0]
}, function(result) { }, function(result) {
Ox.Request.clearCache('findList'); Ox.Request.clearCache('find' + folderItems);
that.reloadList(); that.reloadList();
}); });
} else if (id == 'featured' && canEditFeatured) { } else if (id == 'featured' && canEditFeatured) {
@ -346,7 +344,7 @@ pandora.ui.folderList = function(id, section) {
id: data.ids[0], id: data.ids[0],
status: 'public' status: 'public'
}, function(result) { }, function(result) {
Ox.Request.clearCache('findList'); Ox.Request.clearCache('find' + folderItems);
// fixme: duplicated // fixme: duplicated
if (result.data.user == pandora.user.username || result.data.subscribed) { if (result.data.user == pandora.user.username || result.data.subscribed) {
pandora.$ui.folderList[ pandora.$ui.folderList[
@ -415,6 +413,20 @@ pandora.ui.folderList = function(id, section) {
: that.value(list).view : that.value(list).view
: void 0 : 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 { } else {
pandora.UI.set(section.slice(0, -1), list); pandora.UI.set(section.slice(0, -1), list);
} }

View file

@ -6,12 +6,13 @@ pandora.ui.folderPlaceholder = function(id, section) {
.css({ .css({
height: '14px', height: '14px',
padding: '1px 4px', padding: '1px 4px',
}); }),
folderItems = pandora.getFolderItems(section);
that.updateText = function(string, isFind) { that.updateText = function(string, isFind) {
return that.html( return that.html(
string != 'volumes' string != 'volumes'
? Ox._('No {0} {1}' + (isFind ? ' found' : ''), ? Ox._('No {0} {1}' + (isFind ? ' found' : ''),
[Ox._(string), Ox._(section == 'items' ? 'lists' : section)]) [Ox._(string), Ox._(folderItems.toLowerCase())])
: Ox._('No local volumes') : Ox._('No local volumes')
); );
}; };

View file

@ -10,10 +10,13 @@ pandora.ui.folders = function(section) {
pandora.resizeFolders(); pandora.resizeFolders();
} }
}), }),
editable = (ui[ editable = (ui[{
section == 'items' ? '_list' : section.slice(0, -1) items: '_list',
] || '').split(':')[0] == pandora.user.username, edits: 'edit',
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section), documents: '_collection',
texts: 'text'
}[section]] || '').split(':')[0] == pandora.user.username,
folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level], canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
initCounter = 0, 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]) : Ox._('To create and share your own {0}, please sign up or sign in.', [section])
)]; )];
} else { } else {
if (section == 'items') { if (Ox.contains(pandora.site.listSections, section)) {
extras = [ extras = [
pandora.$ui.personalListsMenu = Ox.MenuButton({ pandora.$ui.personalListsMenu = Ox.MenuButton({
items: [ items: [
{ id: 'newlist', title: Ox._('New List'), keyboard: 'control n' }, { id: 'newlist', title: Ox._('New {0}', [Ox._(folderItem)]), keyboard: 'control n' },
{ id: 'newlistfromselection', title: Ox._('New List from Selection'), keyboard: 'shift control n', disabled: ui.listSelection.length == 0 }, { 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 List'), keyboard: 'alt control n' }, { id: 'newsmartlist', title: Ox._('New Smart {0}', [Ox._(folderItem)]), keyboard: 'alt control n' },
{ id: 'newsmartlistfromresults', title: Ox._('New Smart List from Results'), keyboard: 'shift 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: 'duplicatelist', title: Ox._('Duplicate Selected {0}', [Ox._(folderItem)]), keyboard: 'control d', disabled: !ui._list },
{ id: 'editlist', title: Ox._('Edit Selected List...'), keyboard: 'control e', disabled: !editable }, { id: 'editlist', title: Ox._('Edit Selected {0}...', [Ox._(folderItem)]), keyboard: 'control e', disabled: !editable },
{ id: 'deletelist', title: Ox._('Delete Selected List...'), keyboard: 'delete', disabled: !editable } { id: 'deletelist', title: Ox._('Delete Selected {0}...', [Ox._(folderItem)]), keyboard: 'delete', disabled: !editable }
], ],
title: 'edit', title: 'edit',
tooltip: Ox._('Manage Personal Lists'), tooltip: Ox._('Manage Personal ' + folderItems),
type: 'image' type: 'image'
}) })
.bindEvent({ .bindEvent({
@ -64,7 +67,7 @@ pandora.ui.folders = function(section) {
], data.id)) { ], data.id)) {
pandora.addList(data.id.indexOf('smart') > -1, data.id.indexOf('from') > -1); pandora.addList(data.id.indexOf('smart') > -1, data.id.indexOf('from') > -1);
} else if (data.id == 'duplicatelist') { } else if (data.id == 'duplicatelist') {
pandora.addList(pandora.user.ui._list); pandora.addList(ui._list);
} else if (data.id == 'editlist') { } else if (data.id == 'editlist') {
pandora.ui.listDialog().open(); pandora.ui.listDialog().open();
} else if (data.id == 'deletelist') { } else if (data.id == 'deletelist') {
@ -222,9 +225,17 @@ pandora.ui.folders = function(section) {
pandora.$ui.folderList.featured.options({selected: [listData.id]}); pandora.$ui.folderList.featured.options({selected: [listData.id]});
} else { } else {
// and nowhere else // and nowhere else
if (section == 'items') {
pandora.UI.set({ pandora.UI.set({
find: pandora.site.user.ui.find 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( pandora.$ui.folderBrowser.favorite.replaceWith(
@ -280,9 +291,17 @@ pandora.ui.folders = function(section) {
pandora.$ui.folderList.favorite.options({selected: [listData.id]}); pandora.$ui.folderList.favorite.options({selected: [listData.id]});
} else { } else {
// and nowhere else // and nowhere else
if (section == 'items') {
pandora.UI.set({ pandora.UI.set({
find: pandora.site.user.ui.find 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( pandora.$ui.folderBrowser.featured.replaceWith(
@ -336,7 +355,7 @@ pandora.ui.folders = function(section) {
}, },
toggle: function(data) { toggle: function(data) {
data.collapsed && pandora.$ui.folderList[folder.id].loseFocus(); 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(); pandora.resizeFolders();
} }
}); });
@ -378,7 +397,7 @@ pandora.ui.folders = function(section) {
}).bindEvent({ }).bindEvent({
click: function() { click: function() {
var $dialog = pandora.ui.iconDialog({ var $dialog = pandora.ui.iconDialog({
buttons: title != Ox._('Featured Lists') ? [ buttons: title != Ox._('Featured ' + folderItems) ? [
Ox.Button({title: Ox._('Sign Up...')}).bindEvent({ Ox.Button({title: Ox._('Sign Up...')}).bindEvent({
click: function() { click: function() {
$dialog.close(); $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() { pandora_text: function() {
if (!pandora.user.ui.text) { if (!pandora.user.ui.text) {
Ox.forEach(pandora.$ui.folderList, function($list, id) { Ox.forEach(pandora.$ui.folderList, function($list, id) {

View file

@ -3,7 +3,7 @@
pandora.ui.info = function() { pandora.ui.info = function() {
var ui = pandora.user.ui, var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
view = getView(), view = getView(),
@ -13,6 +13,11 @@ pandora.ui.info = function() {
toggle: function(data) { toggle: function(data) {
pandora.UI.set({showInfo: !data.collapsed}); pandora.UI.set({showInfo: !data.collapsed});
}, },
pandora_documentlist: function() {
if (pandora.user.ui._collection != pandora.UI.getPrevious('_collection')) {
updateInfo();
}
},
pandora_edit: updateInfo, pandora_edit: updateInfo,
pandora_find: function() { pandora_find: function() {
if (pandora.user.ui._list != pandora.UI.getPrevious('_list')) { if (pandora.user.ui._list != pandora.UI.getPrevious('_list')) {
@ -29,6 +34,8 @@ pandora.ui.info = function() {
updateInfo(); updateInfo();
} }
}, },
pandora_document: updateInfo,
pandora_collectionselection: updateInfo,
pandora_text: updateInfo pandora_text: updateInfo
}); });
@ -184,16 +191,14 @@ pandora.ui.info = function() {
pandora.ui.listInfo = function() { pandora.ui.listInfo = function() {
var ui = pandora.user.ui, var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
list = pandora.user.ui.section == 'items' ? pandora.user.ui._list : ui[folderItem.toLowerCase()], list = pandora.user.ui.section == 'items' ? pandora.user.ui._list : ui[folderItem.toLowerCase()],
canEditFeaturedLists = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level], canEditFeaturedLists = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
that = Ox.Element().css({padding: '16px', textAlign: 'center'}), that = Ox.Element().css({padding: '16px', textAlign: 'center'}),
$icon = Ox.Element('<img>') $icon = Ox.Element('<img>')
.attr({ .attr({
src: list src: list ? pandora.getListIcon(ui.section, list, 256) : '/static/png/icon.png'
? '/' + folderItem.toLowerCase() + '/' + encodeURIComponent(list) + '/icon256.jpg?' + Ox.uid()
: '/static/png/icon.png'
}) })
.css(getIconCSS()) .css(getIconCSS())
.appendTo(that), .appendTo(that),

View file

@ -22,7 +22,7 @@ pandora.ui.item = function() {
if (result.status.code == 200) { if (result.status.code == 200) {
// we want to cache the title in any way, so that after closing // we want to cache the title in any way, so that after closing
// a dialog and getting to this item, the title is correct // 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; document.title = pandora.getPageTitle(document.location.pathname) || documentTitle;
} }

View file

@ -15,7 +15,7 @@ pandora.ui.listDialog = function(section) {
), ),
ui = pandora.user.ui, ui = pandora.user.ui,
width = getWidth(section), width = getWidth(section),
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(pandora.user.ui.section),
folderItem = folderItems.slice(0, -1); folderItem = folderItems.slice(0, -1);
Ox.getObjectById(tabs, section).selected = true; Ox.getObjectById(tabs, section).selected = true;
@ -26,7 +26,10 @@ pandora.ui.listDialog = function(section) {
} else if (id == 'icon') { } else if (id == 'icon') {
return pandora.$ui.listIconPanel = pandora.ui.listIconPanel(listData); return pandora.$ui.listIconPanel = pandora.ui.listIconPanel(listData);
} else if (id == 'query') { } 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', mode: 'list',
list: listData list: listData
}) })
@ -158,7 +161,7 @@ pandora.ui.listDialog = function(section) {
pandora.ui.listGeneralPanel = function(listData) { pandora.ui.listGeneralPanel = function(listData) {
var that = Ox.Element(), var that = Ox.Element(),
ui = pandora.user.ui, ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1); folderItem = folderItems.slice(0, -1);
pandora.api['find' + folderItems]({ pandora.api['find' + folderItems]({
query: {conditions: [{key: 'id', value: listData.id, operator: '=='}]}, query: {conditions: [{key: 'id', value: listData.id, operator: '=='}]},
@ -171,7 +174,7 @@ pandora.ui.listGeneralPanel = function(listData) {
tooltip: Ox._('Doubleclick to edit icon') tooltip: Ox._('Doubleclick to edit icon')
}) })
.attr({ .attr({
src: pandora.getMediaURL('/' + folderItem.toLowerCase() + '/' + encodeURIComponent(listData.id) + '/icon256.jpg?' + Ox.uid()) src: pandora.getListIcon(ui.section, listData.id, 256)
}) })
.css({ .css({
position: 'absolute', position: 'absolute',
@ -382,13 +385,16 @@ pandora.ui.listIconPanel = function(listData) {
quarters = ['top-left', 'top-right', 'bottom-left', 'bottom-right'], quarters = ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
ui = pandora.user.ui, ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section), folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1), folderItem = folderItems.slice(0, -1),
$iconPanel = Ox.Element(), $iconPanel = Ox.Element(),
$icon = $('<img>') $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'}) .css({position: 'absolute', borderRadius: '64px', margin: '16px'})
.appendTo($iconPanel), .appendTo($iconPanel),
@ -399,8 +405,6 @@ pandora.ui.listIconPanel = function(listData) {
$list = Ox.Element(), $list = Ox.Element(),
ui = pandora.user.ui, ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
that = Ox.SplitPanel({ that = Ox.SplitPanel({
elements: [ elements: [
@ -586,9 +590,7 @@ pandora.ui.listIconPanel = function(listData) {
posterFrames: posterFrames posterFrames: posterFrames
}, function() { }, function() {
$icon.attr({ $icon.attr({
src: pandora.getMediaURL('/' + folderItem.toLowerCase() src: pandora.getListIcon(ui.section, listData.id, 256)
+ '/' + encodeURIComponent(listData.id) + '/icon256.jpg?' + Ox.uid()
)
}); });
pandora.$ui.folderList[listData.folder].$element pandora.$ui.folderList[listData.folder].$element
.find('img[src*="' .find('img[src*="'
@ -596,10 +598,7 @@ pandora.ui.listIconPanel = function(listData) {
+ '/"]' + '/"]'
) )
.attr({ .attr({
src: pandora.getMediaURL('/' + folderItem.toLowerCase() src: pandora.getListIcon(ui.section, listData.id, 256)
+ '/' + encodeURIComponent(listData.id)
+ '/icon.jpg?' + Ox.uid()
)
}); });
pandora.$ui.info.updateListInfo(); pandora.$ui.info.updateListInfo();
}); });
@ -619,7 +618,7 @@ pandora.ui.listIconPanel = function(listData) {
pandora.api.find(Ox.extend(data, { pandora.api.find(Ox.extend(data, {
query: { query: {
conditions: ( conditions: (
ui.section == 'items' Ox.contains(pandora.site.listSections, ui.section)
? [{key: 'list', value: listData.id, operator: '=='}] ? [{key: 'list', value: listData.id, operator: '=='}]
: []).concat( : []).concat(
value !== '' value !== ''

View file

@ -253,6 +253,14 @@ pandora.ui.mainMenu = function() {
} else { } else {
that.checkItem('allitems'); that.checkItem('allitems');
} }
} else if (ui.section == 'documents') {
if (data.checked) {
pandora.UI.set({
findDocuments: {conditions: [], operator: '&'}
});
} else {
that.checkItem('allitems');
}
} else { } else {
pandora.UI.set(ui.section.slice(0, -1), ''); pandora.UI.set(ui.section.slice(0, -1), '');
} }
@ -268,6 +276,10 @@ pandora.ui.mainMenu = function() {
} else { } else {
pandora.UI.set({itemSort: [{key: value, operator: pandora.getSortOperator(value)}]}); 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') { } else if (data.id == 'find') {
if (value) { if (value) {
pandora.$ui.findSelect.value(value); pandora.$ui.findSelect.value(value);
@ -351,6 +363,15 @@ pandora.ui.mainMenu = function() {
operator: '&' 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 { } else {
pandora.UI.set(ui.section.slice(0, -1), data.id.slice(8).replace(/\t/g, '_')); 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') { } else if (data.id == 'editlist') {
pandora.ui.listDialog().open(); pandora.ui.listDialog().open();
} else if (data.id == 'add') { } else if (data.id == 'add') {
if (ui.section == 'documents') {
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog().open();
} else {
pandora.$ui.addItemDialog = pandora.ui.addItemDialog().open(); pandora.$ui.addItemDialog = pandora.ui.addItemDialog().open();
}
} else if (data.id == 'edit') { } else if (data.id == 'edit') {
pandora.ui.editItemDialog().open(); pandora.ui.editItemDialog().open();
} else if (data.id == 'deletelist') { } else if (data.id == 'deletelist') {
@ -454,6 +479,13 @@ pandora.ui.mainMenu = function() {
pandora.UI.set({listSelection: items}); pandora.UI.set({listSelection: items});
pandora.reloadList(); 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') { } else if (ui.section == 'edits') {
var clips = pandora.clipboard.paste('clip'); var clips = pandora.clipboard.paste('clip');
clips.length && pandora.doHistory('paste', clips, ui.edit, function(result) { clips.length && pandora.doHistory('paste', clips, ui.edit, function(result) {
@ -489,6 +521,26 @@ pandora.ui.mainMenu = function() {
}).open(); }).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') { } else if (ui.section == 'edits') {
var clips = pandora.$ui.editPanel.getSelectedClips(); var clips = pandora.$ui.editPanel.getSelectedClips();
pandora.doHistory('delete', clips, ui.edit, function(result) { pandora.doHistory('delete', clips, ui.edit, function(result) {
@ -594,6 +646,30 @@ pandora.ui.mainMenu = function() {
pandora.$ui.errorlogsDialog = pandora.ui.errorlogsDialog().open(); 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() { pandora_edit: function() {
var action = pandora.getListData().editable ? 'enableItem' : 'disableItem', var action = pandora.getListData().editable ? 'enableItem' : 'disableItem',
edit = ui.edit, edit = ui.edit,
@ -690,6 +766,14 @@ pandora.ui.mainMenu = function() {
pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem' pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem'
]('findsimilar'); ]('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) { pandora_listselection: function(data) {
var action = data.value.length ? 'enableItem' : 'disableItem'; var action = data.value.length ? 'enableItem' : 'disableItem';
that[action]('newlistfromselection'); that[action]('newlistfromselection');
@ -983,6 +1067,156 @@ pandora.ui.mainMenu = function() {
elements[Ox.mod((index + direction), elements.length)].gainFocus(); 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() { function getFindMenu() {
return { id: 'findMenu', title: Ox._('Find'), items: [ return { id: 'findMenu', title: Ox._('Find'), items: [
{ id: 'find', 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() { function getItemMenu() {
if (ui.section == 'documents') {
return getDocumentMenu();
}
var listData = pandora.getListData(), var listData = pandora.getListData(),
deleteVerb = ui._list ? Ox._('Remove') : Ox._('Delete'), deleteVerb = ui._list ? Ox._('Remove') : Ox._('Delete'),
isEditable = listData.editable && listData.type == 'static', isEditable = listData.editable && listData.type == 'static',
@ -1021,7 +1310,7 @@ pandora.ui.mainMenu = function() {
&& ui.editView != 'annotations', // FIXME: focus && ui.editView != 'annotations', // FIXME: focus
listName = isVideoView || isClipView ? '' listName = isVideoView || isClipView ? ''
: ui.section == 'items' ? ( : ui.section == 'items' ? (
ui._list ? Ox._('from List') : Ox._('from Archive') ui._? Ox._('from List') : Ox._('from Archive')
) )
: Ox._('from Edit'), : Ox._('from Edit'),
listItemsName = Ox._( listItemsName = Ox._(
@ -1057,15 +1346,18 @@ pandora.ui.mainMenu = function() {
) )
) && pandora.$ui.list.value(ui.listSelection[0], 'editable') ) && pandora.$ui.list.value(ui.listSelection[0], 'editable')
), ),
canDelete = pandora.site.capabilities.canRemoveItems[pandora.user.level] || ( canDelete = (
ui.section == 'items' && ( ui.section == 'items' && (
ui.item || ( ui.item || (
Ox.contains(['list', 'grid', 'clips', 'timelines'], ui.listView) Ox.contains(['list', 'grid', 'clips', 'timelines'], ui.listView)
&& ui.listSelection.length && 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'); return pandora.$ui.list.value(item, 'editable');
}) })
)
), ),
canSelect = isListView || isClipView || isEditView, canSelect = isListView || isClipView || isEditView,
canCopy = isListView ? ui.listSelection.length canCopy = isListView ? ui.listSelection.length
@ -1106,15 +1398,22 @@ pandora.ui.mainMenu = function() {
} }
function getListMenu() { function getListMenu() {
var itemNameSingular = ui.section == 'items' ? 'List' : ui.section == 'edits' ? 'Edit' : 'Text', return ({
itemNamePlural = ui.section == 'items' ? 'Lists' : ui.section == 'edits' ? 'Edits' : 'Texts'; 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( return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
{ {
id: 'allitems', id: 'allitems',
title: pandora.getAllItemsTitle(), title: pandora.getAllItemsTitle(),
checked: ui.section == 'items' ? !ui.item && !ui._list checked: !ui.text,
: ui.section == 'edits' ? !ui.edit
: !ui.text,
keyboard: 'shift control w' keyboard: 'shift control w'
}, },
['personal', 'favorite', 'featured'].map(function(folder) { ['personal', 'favorite', 'featured'].map(function(folder) {
@ -1132,9 +1431,7 @@ pandora.ui.mainMenu = function() {
title: Ox.encodeHTMLEntities(( title: Ox.encodeHTMLEntities((
folder == 'favorite' ? list.user + ': ' : '' folder == 'favorite' ? list.user + ': ' : ''
) + list.name), ) + list.name),
checked: ui.section == 'items' ? list.id == ui._list checked: list.id == ui.text
: ui.section == 'edits' ? list.id == ui.edit
: list.id == ui.text
}; };
}) })
}; };
@ -1142,38 +1439,47 @@ pandora.ui.mainMenu = function() {
[ [
{}, {},
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' }, { 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' }, { 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: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: isGuest, keyboard: 'control e' },
{ id: 'tv', title: Ox._('TV'), keyboard: 'control space' } { 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() { function getSortMenu() {
if (ui.section == 'documents') {
return getCollectionSortMenu();
}
//fixme split items/clips menu
var isClipView = pandora.isClipView(), var isClipView = pandora.isClipView(),
clipItems = (isClipView ? pandora.site.clipKeys.map(function(key) { clipItems = (isClipView ? pandora.site.clipKeys.map(function(key) {
return Ox.extend(Ox.clone(key), { return Ox.extend(Ox.clone(key), {

View file

@ -21,6 +21,17 @@ pandora.ui.mainPanel = function() {
orientation: 'horizontal' orientation: 'horizontal'
}) })
.bindEvent({ .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) { pandora_edit: function(data) {
that.replaceElement(1, pandora.$ui.editPanel = pandora.ui.editPanel()); that.replaceElement(1, pandora.$ui.editPanel = pandora.ui.editPanel());
}, },
@ -92,6 +103,7 @@ pandora.ui.mainPanel = function() {
function getRightPanel() { function getRightPanel() {
return ui.section == 'items' ? pandora.$ui.rightPanel = pandora.ui.rightPanel() return ui.section == 'items' ? pandora.$ui.rightPanel = pandora.ui.rightPanel()
: ui.section == 'edits' ? pandora.$ui.editPanel = pandora.ui.editPanel() : ui.section == 'edits' ? pandora.$ui.editPanel = pandora.ui.editPanel()
: ui.section == 'documents' ? pandora.$ui.documentPanel = pandora.ui.documentPanel()
: pandora.$ui.textPanel = pandora.ui.textPanel(); : pandora.$ui.textPanel = pandora.ui.textPanel();
} }
return that; return that;

View file

@ -351,7 +351,11 @@ appPanel
findKeys: data.site.itemKeys.filter(function(key) { findKeys: data.site.itemKeys.filter(function(key) {
return key.find; return key.find;
}), }),
documentFindKeys: data.site.documentKeys.filter(function(key) {
return key.find;
}),
itemsSection: pandora.site.itemName.plural.toLowerCase(), itemsSection: pandora.site.itemName.plural.toLowerCase(),
listSections: ['items', 'documents'],
map: data.site.layers.some(function(layer) { map: data.site.layers.some(function(layer) {
return layer.type == 'place' return layer.type == 'place'
}) ? 'manual' : data.site.layers.some(function(layer) { }) ? 'manual' : data.site.layers.some(function(layer) {
@ -364,6 +368,11 @@ appPanel
{id: 'featured', title: 'Featured Lists', showBrowser: false}, {id: 'featured', title: 'Featured Lists', showBrowser: false},
{id: 'volumes', title: 'Local Volumes'} {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: [ edits: [
{id: 'personal', title: 'Personal Edits'}, {id: 'personal', title: 'Personal Edits'},
{id: 'favorite', title: 'Favorite Edits', showBrowser: false}, {id: 'favorite', title: 'Favorite Edits', showBrowser: false},
@ -375,7 +384,12 @@ appPanel
{id: 'featured', title: 'Featured Texts', showBrowser: false} {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 = {}; pandora.site.listSettings = {};
Ox.forEach(pandora.site.user.ui, function(val, key) { 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.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 = { pandora.site.editSettings = {
clip: '', clip: '',
'in': 0, 'in': 0,

View file

@ -5,7 +5,7 @@ pandora.ui.sectionButtons = function(section) {
buttons: [ buttons: [
{id: 'items', title: Ox._(pandora.site.itemName.plural)}, {id: 'items', title: Ox._(pandora.site.itemName.plural)},
{id: 'edits', title: Ox._('Edits')}, {id: 'edits', title: Ox._('Edits')},
{id: 'texts', title: Ox._('Texts')} {id: 'documents', title: Ox._('Documents')}
], ],
id: 'sectionButtons', id: 'sectionButtons',
selectable: true, selectable: true,

View file

@ -7,7 +7,7 @@ pandora.ui.sectionSelect = function(section) {
items: [ items: [
{id: 'items', title: Ox._(pandora.site.itemName.plural)}, {id: 'items', title: Ox._(pandora.site.itemName.plural)},
{id: 'edits', title: Ox._('Edits')}, {id: 'edits', title: Ox._('Edits')},
{id: 'texts', title: Ox._('Texts')} {id: 'documents', title: Ox._('Documents')}
], ],
value: section || pandora.user.ui.section value: section || pandora.user.ui.section
}).css({ }).css({

View file

@ -11,6 +11,7 @@ pandora.ui.textPanel = function() {
orientation: 'vertical' orientation: 'vertical'
}), }),
embedURLs, embedURLs,
scrolling = false,
selected = -1, selected = -1,
selectedURL; selectedURL;
@ -233,13 +234,24 @@ pandora.ui.textHTML = function(text) {
scroll: function(event) { scroll: function(event) {
var position = Math.round(100 * that[0]. scrollTop / Math.max(1, var position = Math.round(100 * that[0]. scrollTop / Math.max(1,
that[0].scrollHeight - that.height())), that[0].scrollHeight - that.height())),
settings;
if (pandora.user.ui.section == 'texts') {
settings = pandora.user.ui.texts[pandora.user.ui.text]; settings = pandora.user.ui.texts[pandora.user.ui.text];
} else {
settings = pandora.user.ui.documents[pandora.user.ui.document] || {};
}
position = position - position % 10; position = position - position % 10;
if (!scrolling && settings && (settings.name || (position != settings.position))) { 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), { pandora.UI.set('texts.' + pandora.UI.encode(pandora.user.ui.text), {
position: position ? position : 0 position: position ? position : 0
}); });
} }
}
scrolling = false; scrolling = false;
}, },
}) })
@ -248,6 +260,9 @@ pandora.ui.textHTML = function(text) {
that.update(); that.update();
}, },
}) })
.bindEvent('pandora_documents.' + text.id.toLowerCase(), function(data) {
data.value && data.value.name && scrollToPosition();
})
.bindEvent('pandora_texts.' + text.id.toLowerCase(), function(data) { .bindEvent('pandora_texts.' + text.id.toLowerCase(), function(data) {
data.value && data.value.name && scrollToPosition(); data.value && data.value.name && scrollToPosition();
}), }),
@ -257,10 +272,10 @@ pandora.ui.textHTML = function(text) {
.appendTo(that), .appendTo(that),
$title = Ox.EditableContent({ $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'), placeholder: text.editable ? Ox._('Doubleclick to edit title') : Ox._('Untitled'),
tooltip: text.editable ? pandora.getEditTooltip('title') : '', 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 width: width
}) })
.css({ .css({
@ -271,6 +286,19 @@ pandora.ui.textHTML = function(text) {
}) })
.bindEvent({ .bindEvent({
submit: function(data) { 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'); Ox.Request.clearCache('getText');
pandora.api.editText({ pandora.api.editText({
id: pandora.user.ui.text, id: pandora.user.ui.text,
@ -283,6 +311,7 @@ pandora.ui.textHTML = function(text) {
} }
}); });
} }
}
}) })
.appendTo($content), .appendTo($content),
@ -373,6 +402,16 @@ pandora.ui.textHTML = function(text) {
}) })
.bindEvent({ .bindEvent({
submit: function(data) { 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'); Ox.Request.clearCache('getText');
pandora.api.editText({ pandora.api.editText({
id: pandora.user.ui.text, id: pandora.user.ui.text,
@ -380,6 +419,7 @@ pandora.ui.textHTML = function(text) {
}); });
pandora.$ui.textPanel.update(data.value); pandora.$ui.textPanel.update(data.value);
} }
}
}) })
.appendTo($content); .appendTo($content);
@ -404,7 +444,9 @@ pandora.ui.textHTML = function(text) {
} }
function scrollToPosition() { 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, position = settings.position || 0,
element, element,
scrollTop; scrollTop;
@ -505,6 +547,7 @@ pandora.ui.textEmbed = function() {
resize: function(data) { resize: function(data) {
pandora.user.ui.embedSize = data.size; pandora.user.ui.embedSize = data.size;
pandora.$ui.text.update(); pandora.$ui.text.update();
pandora.$ui.document && pandora.$ui.document.update();
}, },
resizeend: function(data) { resizeend: function(data) {
$iframe.attr('src') && $overlay.hide(); $iframe.attr('src') && $overlay.hide();

View file

@ -46,7 +46,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
if (title == Ox._('Cancel Upload')) { if (title == Ox._('Cancel Upload')) {
upload && upload.abort(); upload && upload.abort();
} else if (title == Ox._('Done')) { } else if (title == Ox._('Done')) {
callback({ callback && callback({
ids: ids ids: ids
}); });
} }
@ -81,7 +81,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
+ (extension == 'jpeg' ? 'jpg' : extension); + (extension == 'jpeg' ? 'jpg' : extension);
valid && Ox.oshash(file, function(oshash) { valid && Ox.oshash(file, function(oshash) {
pandora.api.findDocuments({ pandora.api.findDocuments({
keys: ['id', 'user', 'name', 'extension'], keys: ['id', 'user', 'title', 'extension'],
query: { query: {
conditions: [{ conditions: [{
key: 'oshash', key: 'oshash',
@ -91,10 +91,10 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
operator: '&' operator: '&'
}, },
range: [0, 1], range: [0, 1],
sort: [{key: 'name', operator: '+'}] sort: [{key: 'title', operator: '+'}]
}, function(result) { }, function(result) {
if (result.data.items.length) { if (result.data.items.length) {
var id = result.data.items[0].name + '.' var id = result.data.items[0].title + '.'
+ result.data.items[0].extension; + result.data.items[0].extension;
valid && errorDialog( valid && errorDialog(
filename == id filename == id
@ -161,7 +161,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
ids.push(data.response.id); ids.push(data.response.id);
if (part == files.length) { if (part == files.length) {
$progress.options({progress: data.progress}); $progress.options({progress: data.progress});
callback({ids: ids}); callback && callback({ids: ids});
$uploadDialog.close(); $uploadDialog.close();
} else { } else {
uploadFile(part); uploadFile(part);

View file

@ -40,6 +40,9 @@ pandora.addFolderItem = function(section) {
if (!isSmart) { if (!isSmart) {
if (isItems) { if (isItems) {
data.items = ui.listSelection; data.items = ui.listSelection;
} else if (section == 'documents') {
//fixme
data.items = ui.collectionSelection;
} else { } else {
data.clips = pandora.getClipData( data.clips = pandora.getClipData(
ui.section == 'items' ui.section == 'items'
@ -66,7 +69,11 @@ pandora.addFolderItem = function(section) {
if (data.type == 'smart') { if (data.type == 'smart') {
data.query = listData.query; data.query = listData.query;
} }
pandora.api[isItems ? 'findLists' : 'findEdits']({ pandora.api[{
items: 'findLists',
documents: 'findCollections',
edits: 'findEdits',
}[section]]({
query: {conditions: [{ query: {conditions: [{
key: 'id', key: 'id',
operator: '==', operator: '==',
@ -103,6 +110,9 @@ pandora.addFolderItem = function(section) {
addList(); addList();
} }
}); });
} else if(section == 'documents') {
//fixme
addList();
} else { } else {
pandora.api.getEdit({ pandora.api.getEdit({
id: list, id: list,
@ -127,7 +137,11 @@ pandora.addFolderItem = function(section) {
}); });
} }
function addList() { 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); getPosterFrames(result.data.id);
}); });
} }
@ -136,23 +150,41 @@ pandora.addFolderItem = function(section) {
sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes') sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes')
? 'votes' : 'timesaccessed'; ? 'votes' : 'timesaccessed';
if (!isDuplicate) { if (!isDuplicate) {
(isItems ? Ox.noop : pandora.api.getEdit)({ ({
items: Ox.noop,
documents: Ox.noop,
edits: pandora.api.getEdit
}[section])({
id: newList, id: newList,
keys: ['clips'] keys: ['clips']
}, function(result) { }, function(result) {
query = isItems ? { if (Ox.contains(pandora.site.listSections, section)) {
conditions: [{key: 'list', value: newList, operator: '=='}], query = {
conditions: [{
key: section == 'documents' ? 'collection' : 'list',
value: newList, operator: '=='
}],
operator: '&' operator: '&'
} : { };
} else{
query = {
conditions: Ox.unique(result.data.clips.map(function(clip) { conditions: Ox.unique(result.data.clips.map(function(clip) {
return {key: 'id', value: clip.item, operator: '=='}; return {key: 'id', value: clip.item, operator: '=='};
})), })),
operator: '|' operator: '|'
}; };
(isItems ? pandora.api.find : Ox.noop)({ }
({
items: pandora.api.find,
documents: pandora.api.findDocuments,
edits: Ox.noop
}[section])({
query: { query: {
conditions: [ conditions: [
{key: 'list', value: newList, operator: '=='} {
key: section == 'documents' ? 'collection' : 'list',
value: newList, operator: '=='
}
], ],
operator: '&' operator: '&'
}, },
@ -181,7 +213,11 @@ pandora.addFolderItem = function(section) {
}); });
}); });
} else { } else {
pandora.api[isItems ? 'findLists' : 'findEdits']({ pandora.api[{
items: 'findLists',
documents: 'findCollections',
edits: 'findEdits'
}[section]]({
query: { query: {
conditions: [{key: 'id', value: list, operator: '=='}], conditions: [{key: 'id', value: list, operator: '=='}],
operator: '&' operator: '&'
@ -193,7 +229,11 @@ pandora.addFolderItem = function(section) {
} }
} }
function setPosterFrames(newList, posterFrames) { function setPosterFrames(newList, posterFrames) {
pandora.api[isItems ? 'editList' : 'editEdit']({ pandora.api[{
items: 'editList',
documents: 'editCollection',
edits: 'editEdit'
}[section]]({
id: newList, id: newList,
posterFrames: posterFrames posterFrames: posterFrames
}, function() { }, function() {
@ -206,22 +246,37 @@ pandora.addFolderItem = function(section) {
// (same applies to addText, below) // (same applies to addText, below)
$folderList = pandora.$ui.folderList.personal; $folderList = pandora.$ui.folderList.personal;
pandora.$ui.folder[0].options({collapsed: false}); 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({ $folderList.bindEventOnce({
load: function() { load: function() {
$folderList.gainFocus() $folderList.gainFocus()
.options({selected: [newList]}) .options({selected: [newList]})
.editCell(newList, 'name', true); .editCell(newList, 'name', true);
pandora.UI.set(isItems ? { pandora.UI.set({
items: {
find: { find: {
conditions: [ conditions: [
{key: 'list', value: newList, operator: '=='} {key: 'list', value: newList, operator: '=='}
], ],
operator: '&' operator: '&'
} }
} : { },
documents: {
findDocuments: {
conditions: [
{key: 'collection', value: newList, operator: '=='}
],
operator: '&'
}
},
edits: {
edit: newList edit: newList
}); }
}[section]);
} }
}).reloadList(); }).reloadList();
} }
@ -229,7 +284,7 @@ pandora.addFolderItem = function(section) {
pandora.addList = function() { pandora.addList = function() {
// addList(isSmart, isFrom) or addList(list) [=duplicate] // 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) { pandora.addText = function(options) {
@ -271,8 +326,7 @@ pandora.beforeUnloadWindow = function() {
pandora.changeFolderItemStatus = function(id, status, callback) { pandora.changeFolderItemStatus = function(id, status, callback) {
var ui = pandora.user.ui, var ui = pandora.user.ui,
folderItems = ui.section == 'items' folderItems = pandora.getFolderItems(ui.section),
? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1); folderItem = folderItems.slice(0, -1);
if (status == 'private') { if (status == 'private') {
pandora.api['find' + folderItems]({ pandora.api['find' + folderItems]({
@ -496,6 +550,30 @@ pandora.createLinks = function($element) {
callback(null, []); 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 { } else {
pandora.api.addClips({ pandora.api.addClips({
clips: pandora.getClipData(items), clips: pandora.getClipData(items),
@ -540,6 +618,31 @@ pandora.createLinks = function($element) {
// FIXME: Why is this timeout needed? // FIXME: Why is this timeout needed?
setTimeout(pandora.reloadList, 250); 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') { } else if (type == 'clip' && ui.section == 'edits') {
// FIXME: update edit list (once it has item count) // FIXME: update edit list (once it has item count)
if (Ox.contains(object.targets, ui.edit)) { if (Ox.contains(object.targets, ui.edit)) {
@ -743,6 +846,36 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
}); });
drag.action == 'move' && pandora.reloadList(); 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') { } else if (section == 'edits') {
var targets = drag.action == 'copy' ? drag.target.id var targets = drag.action == 'copy' ? drag.target.id
: [pandora.user.ui.edit, 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(); pandora.$ui.editPanel && pandora.$ui.editPanel.updatePanel();
cleanup(250); cleanup(250);
}); });
} else {
Ox.print('no drop support for', section);
cleanup(250);
} }
} }
} else { } else {
@ -781,13 +917,20 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
itemName = section == 'items' ? { itemName = section == 'items' ? {
plural: Ox._(pandora.site.itemName.plural.toLowerCase()), plural: Ox._(pandora.site.itemName.plural.toLowerCase()),
singular: Ox._(pandora.site.itemName.singular.toLowerCase()) singular: Ox._(pandora.site.itemName.singular.toLowerCase())
} : { } : section == 'documents' ? {
plural: Ox._('Documents'),
singular: Ox._('Document')
} :{
plural: Ox._('clips'), plural: Ox._('clips'),
singular: Ox._('clip') singular: Ox._('clip')
}, },
targetName = section == 'items' ? { targetName = section == 'items' ? {
plural: Ox._('lists'), plural: Ox._('lists'),
singular: Ox._('list') singular: Ox._('list')
} : section == 'documents' ? {
plural: Ox._('collections'),
singular: Ox._('collection')
} : { } : {
plural: Ox._('edits'), plural: Ox._('edits'),
singular: Ox._('edit') 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) { pandora.getAllItemsTitle = function(section) {
section = section || pandora.user.ui.section; section = section || pandora.user.ui.section;
return section == 'items' return {
? Ox._('All {0}', [Ox._(pandora.site.itemName.plural)]) items: Ox._('All {0}', [Ox._(pandora.site.itemName.plural)]),
: Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]); documents: Ox._('All {0}', [Ox._('Documents')])
}[section] || Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]);
}; };
pandora.getClipData = function(items) { pandora.getClipData = function(items) {
@ -1068,14 +1236,20 @@ pandora.getClipVideos = function(clip, resolution) {
}; };
(function() { (function() {
var itemTitles = {}; var itemTitles = {}, documentTitles = {};
pandora.getDocumentTitle = function(itemData) { pandora.getWindowTitle = function(itemData) {
var parts = []; var parts = [];
if (itemData) { 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( itemTitles[pandora.user.ui.item] = Ox.decodeHTMLEntities(
pandora.getItemTitle(itemData) pandora.getItemTitle(itemData)
); );
} }
}
if (pandora.user.ui.section == 'items') { if (pandora.user.ui.section == 'items') {
if (!pandora.user.ui.item) { if (!pandora.user.ui.item) {
parts.push( parts.push(
@ -1094,16 +1268,33 @@ pandora.getClipVideos = function(clip, resolution) {
Ox._(Ox.toTitleCase(pandora.user.ui.itemView)) 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') { } else if (pandora.user.ui.section == 'edits') {
if (pandora.user.ui.edit) { if (pandora.user.ui.edit) {
parts.push(pandora.user.ui.edit.split(':').slice(1).join(':')); parts.push(pandora.user.ui.edit.split(':').slice(1).join(':'));
} }
parts.push(Ox._('Edits')); 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); parts.push(pandora.site.site.name);
return parts.join(' '); 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) { pandora.getFoldersHeight = function(section) {
section = section || pandora.user.ui.section; section = section || pandora.user.ui.section;
var height = 0; 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') { } else if (state.type == 'edits') {
pandora.api.getEdit({id: str, keys: ['id']}, function(result) { pandora.api.getEdit({id: str, keys: ['id']}, function(result) {
if (result.status.code == 200) { if (result.status.code == 200) {
@ -1310,21 +1517,23 @@ pandora.getItem = function(state, str, callback) {
} }
}); });
} else if (state.type == 'texts') { } else if (state.type == 'texts') {
pandora.api.getText({ pandora.api.findDocuments({
id: str, query: {
keys: ['id', 'names', 'pages', 'type'] conditions: [
{key: 'user', value: str.split(':')[0]},
{key: 'title', value: str.split(':').slice(1).join(':')}
],
operator: '&'},
keys: ['id', 'extension'],
range: [0, 2]
}, function(result) { }, function(result) {
if (result.status.code == 200) { state.type = 'documents';
state.item = result.data.id; if (result.data.items.length == 1) {
callback(); state.item = result.data.items[0].id;
} else { } 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 = ''; state.item = '';
callback();
} }
callback();
}); });
} else { } else {
callback(); callback();
@ -1471,12 +1680,19 @@ pandora.getLargeEditTimelineURL = function(edit, type, i, callback) {
}; };
pandora.getListData = function(list) { pandora.getListData = function(list) {
var data = {}, folder; var data = {}, folder, _list = pandora.user.ui._list;
if (Ox.isUndefined(list)) { if (Ox.isUndefined(list)) {
list = pandora.user.ui[ if (pandora.user.ui.section == 'items') {
pandora.user.ui.section == 'items' ? '_list' list = pandora.user.ui._list;
: pandora.user.ui.section.slice(0, -1) } 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) { if (list && pandora.$ui.folderList) {
Ox.forEach(pandora.$ui.folderList, function($list, id) { 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 // folder it is selected, since for example, a personal
// list may appear again in the featured lists browser // list may appear again in the featured lists browser
if ( if (
(list == pandora.user.ui._list && $list.options('selected').length) (list == _list && $list.options('selected').length)
|| !Ox.isEmpty($list.value(list)) || !Ox.isEmpty($list.value(list))
) { ) {
folder = id; folder = id;
@ -1506,6 +1722,16 @@ pandora.getListData = function(list) {
return data; 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) { pandora.getPageTitle = function(stateOrURL) {
var pages = [ var pages = [
{id: '', title: ''}, {id: '', title: ''},
@ -1529,6 +1755,7 @@ pandora.getPageTitle = function(stateOrURL) {
}; };
pandora.getPart = function(state, str, callback) { pandora.getPart = function(state, str, callback) {
Ox.Log('URL', 'getPart', state, str);
if (state.page == 'api') { if (state.page == 'api') {
pandora.api.api(function(result) { pandora.api.api(function(result) {
if (Ox.contains(Object.keys(result.data.actions), str)) { 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 // 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) // (but this can only be tested after find has been parsed)
callback(); callback();
} else if (state.type == 'documents') {
callback();
} else if (state.type == 'edits') { } else if (state.type == 'edits') {
if (val[0].key == 'index') { if (val[0].key == 'index') {
pandora.api.getEdit({id: state.item, keys: ['id', 'type']}, function(result) { pandora.api.getEdit({id: state.item, keys: ['id', 'type']}, function(result) {
@ -1675,8 +1904,6 @@ pandora.getSort = function(state, val, callback) {
} else { } else {
callback(); callback();
} }
} else if (state.type == 'texts') {
callback();
} }
}; };
@ -1705,6 +1932,29 @@ pandora.getSortOperator = function(key) {
) > -1 ? '+' : '-'; ) > -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) { pandora.getSpan = function(state, val, callback) {
// For a given item, or none (state.item), and a given view, or any // 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 // (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 // event/place name (string), and in that case sets state.span, and may
// modify state.view. // modify state.view.
// fixme: "subtitles:23" is still missing // fixme: "subtitles:23" is still missing
if (state.page == 'documents') { Ox.Log('URL', 'getSpan', state, val);
if (state.type == 'documents') {
pandora.api.getDocument({ pandora.api.getDocument({
id: state.part, id: state.item,
keys: ['dimensions', 'extension'] keys: ['dimensions', 'extension']
}, function(result) { }, function(result) {
var dimensions = result.data.dimensions, var dimensions = result.data.dimensions,
@ -1722,6 +1973,8 @@ pandora.getSpan = function(state, val, callback) {
values; values;
if (Ox.contains(['epub', 'pdf', 'txt'], extension)) { if (Ox.contains(['epub', 'pdf', 'txt'], extension)) {
state.span = Ox.limit(parseInt(val), 1, dimensions); 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)) { } else if (Ox.contains(['gif', 'jpg', 'png'], extension)) {
values = val.split(','); values = val.split(',');
if (values.length == 4) { if (values.length == 4) {
@ -1738,6 +1991,7 @@ pandora.getSpan = function(state, val, callback) {
state.span = ''; state.span = '';
} }
} }
Ox.Log('URL', 'getSpan result', state);
callback(); callback();
}); });
} else if (state.type == pandora.site.itemName.plural.toLowerCase()) { } else if (state.type == pandora.site.itemName.plural.toLowerCase()) {
@ -1817,24 +2071,6 @@ pandora.getSpan = function(state, val, callback) {
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) { function getId(type, callback) {
@ -1898,6 +2134,9 @@ pandora.getStatusText = function(data) {
data.items == 1 ? 'singular' : 'plural' data.items == 1 ? 'singular' : 'plural'
]), ]),
parts = []; 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); parts.push(Ox.formatNumber(data.items) + ' '+ itemName);
if (data.runtime) { if (data.runtime) {
parts.push(Ox.formatDuration(data.runtime, 'short')); 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._list = pandora.getListState(pandora.user.ui.find);
pandora.user.ui._filterState = pandora.getFilterState(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._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.site.sortKeys = pandora.getSortKeys();
pandora.URL.init(); pandora.URL.init();
pandora.URL.update(); pandora.URL.update();
@ -2277,6 +2518,8 @@ pandora.signout = function(data) {
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find); pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
pandora.user.ui._filterState = pandora.getFilterState(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._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.site.sortKeys = pandora.getSortKeys();
pandora.URL.init(); pandora.URL.init();
pandora.URL.update(); pandora.URL.update();
@ -2288,9 +2531,11 @@ pandora.reloadList = function() {
Ox.Log('', 'reloadList') Ox.Log('', 'reloadList')
var listData = pandora.getListData(); var listData = pandora.getListData();
Ox.Request.clearCache(); // fixme: remove Ox.Request.clearCache(); // fixme: remove
if (pandora.$ui.filters) {
pandora.$ui.filters.forEach(function($filter) { pandora.$ui.filters.forEach(function($filter) {
$filter.reloadList(); $filter.reloadList();
}); });
}
pandora.$ui.list pandora.$ui.list
.bindEvent({ .bindEvent({
init: function(data) { init: function(data) {
@ -2336,6 +2581,20 @@ pandora.renameList = function(oldId, newId, newName, folder) {
} }
}, false); }, false);
pandora.UI.set('lists.' + pandora.UI.encode(oldId), null, 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 { } else {
pandora.replaceURL = true; pandora.replaceURL = true;
pandora.UI.set( pandora.UI.set(
@ -2380,7 +2639,7 @@ pandora.resizeFolders = function(section) {
userColumnWidth = Math.round(columnWidth * 0.4), userColumnWidth = Math.round(columnWidth * 0.4),
nameColumnWidth = columnWidth - userColumnWidth; nameColumnWidth = columnWidth - userColumnWidth;
pandora.$ui.allItems && pandora.$ui.allItems.resizeElement(( pandora.$ui.allItems && pandora.$ui.allItems.resizeElement((
section == 'items' ? columnWidth Ox.contains(pandora.site.listSections, section) ? columnWidth
: section == 'edits' ? width - 16 : section == 'edits' ? width - 16
: width - 48 : width - 48
) - 8); ) - 8);
@ -2489,6 +2748,12 @@ pandora.resizeWindow = function() {
pandora.$ui.calendar.resizeCalendar(); 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') { } else if (pandora.user.ui.section == 'edits') {
if (!pandora.user.ui.edit) { 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 { } else {
var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)], var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)],
section = Ox.toTitleCase(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; return state;
}; };
function getState(find, key) {
pandora.getListState = function(find) {
// A list is selected if exactly one condition in an & query has "list"
// as key and "==" as operator
var index, state = ''; var index, state = '';
if (find.operator == '&') { if (find.operator == '&') {
index = oneCondition(find.conditions, 'list', '=='); index = oneCondition(find.conditions, key, '==');
if (index > -1) { if (index > -1) {
state = find.conditions[index].value; state = find.conditions[index].value;
} }
} }
return state; 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -216,6 +216,8 @@ if __name__ == "__main__":
run('./bin/pip', 'install', '-r', 'requirements.txt') run('./bin/pip', 'install', '-r', 'requirements.txt')
update_service('pandora-encoding') update_service('pandora-encoding')
update_service('pandora-tasks') update_service('pandora-tasks')
if old <= 5673:
run('./pandora/manage.py', 'rebuild_documentfind')
else: else:
if len(sys.argv) == 1: if len(sys.argv) == 1:
release = get_release() release = get_release()