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']:
config['keys'][key['id']] = key
# add entities if needed
if len(config.get('entities', [])) and not [k for k in config['documentKeys'] if k['id'] == 'entites']:
config['documentKeys'].append({
'id': 'entity',
'title': 'Entity',
'type': 'string',
'find': True
})
# add missing defaults
for section in sorted((
'capabilities', 'cantPlay', 'entities', 'itemName', 'itemTitleKeys', 'media', 'posters',
'capabilities', 'cantPlay',
'documentKeys',
'entities', 'itemName', 'itemTitleKeys', 'itemKeys', 'media', 'posters',
'site', 'tv', 'user.ui', 'user.ui.part', 'user.ui.showFolder',
'menuExtras', 'languages'
)):

View file

@ -35,11 +35,13 @@
*/
"capabilities": {
"canAddItems": {"staff": true, "admin": true},
"canAddDocuments": {"staff": true, "admin": true},
"canDownloadVideo": {"guest": -1, "member": -1, "friend": -1, "staff": -1, "admin": -1},
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
@ -61,11 +63,13 @@
"canPlayVideo": {"guest": 1, "member": 1, "friend": 4, "staff": 4, "admin": 4},
"canReadText": {"guest": 0, "member": 0, "friend": 1, "staff": 1, "admin": 1},
"canRemoveItems": {"admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"staff": true, "admin": true},
"canSeeDocument": {"guest": 1, "member": 1, "firend": 4, "staff": 4, "admin": 4},
"canSeeItem": {"guest": 3, "member": 3, "friend": 4, "staff": 4, "admin": 4},
"canSeeSize": {"friend": true, "staff": true, "admin": true},
"canSeeSoftwareVersion": {"staff": true, "admin": true},
@ -91,6 +95,212 @@
list means it will not be included in find annotations.
*/
"clipLayers": ["subtitles"],
"documentKeys": [
{
"id": "*",
"title": "All",
"type": "text",
"find": true
},
{
"id": "title",
"operator": "+",
"title": "Title",
"type": "string",
"find": true,
"sort": true,
"sortType": "title",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "type",
"operator": "+",
"title": "Type",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "author",
"operator": "+",
"title": "Author",
"type": ["string"],
"filter": true,
"find": true,
"sort": true,
"sortType": "person",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "publisher",
"operator": "+",
"title": "Publisher",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 256
},
{
"id": "place",
"title": "Place",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "date",
"title": "Date",
"type": "string",
"columnWidth": 120,
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
"sort": true
},
{
"id": "series",
"title": "Series",
"type": "string",
"columnWidth": 128,
"find": true,
"sort": true
},
{
"id": "edition",
"title": "Edition",
"type": "string",
"columnWidth": 128,
"find": true
},
{
"id": "language",
"title": "Language",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "id",
"operator": "+",
"title": "ID",
"type": "string",
"sort": true,
"columnWidth": 64
},
{
"id": "extension",
"operator": "+",
"title": "Extension",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 64
},
{
"id": "dimensions",
"operator": "-",
"title": "Dimensions",
"type": "integer",
"sort": true,
"columnWidth": 128
},
{
"id": "size",
"operator": "-",
"title": "Size",
"type": "integer",
"sort": true,
"format": {"type": "value", "args": ["B"]},
"columnWidth": 64
},
{
"id": "description",
"operator": "+",
"title": "Description",
"type": "text",
"find": true,
"sort": true,
"columnWidth": 256
},
{
"id": "matches",
"operator": "-",
"title": "Matches",
"type": "integer",
"sort": true,
"columnWidth": 64
},
{
"id": "user",
"operator": "+",
"title": "User",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "created",
"operator": "-",
"title": "Created",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "modified",
"operator": "-",
"title": "Modified",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "accessed",
"title": "Last Accessed",
"type": "date",
"capability": "canSeeAccessed",
"columnWidth": 150,
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"sort": true
},
{
"id": "timesaccessed",
"title": "Times Accessed",
"type": "integer",
"capability": "canSeeAccessed",
"columnWidth": 60,
"format": {"type": "number", "args": []},
"sort": true
},
{
"id": "rightslevel",
"title": "Rights Level",
"type": "enum",
"columnWidth": 90,
"format": {"type": "ColorLevel", "args": [
["Public", "Out of Copyright", "Under Copyright", "Private"]
]},
"sort": true,
"sortOperator": "+",
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
}
],
/*
"entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class
@ -976,13 +1186,24 @@
"calendarFind": "",
"calendarSelection": "",
"clipColumns": 2,
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
"collectionColumnWidth": {},
"collectionSelection": [],
"collectionSort": [
{"key": "title", "operator": "+"},
{"key": "extension", "operator": "+"}
],
"collectionView": "grid",
"collections": {},
"columns": {
"Colors": {
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
"columnWidth": {}
}
},
"document": "",
"documents": {},
"documentView": "view",
"documentSize": 256,
"documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}],
@ -1011,6 +1232,7 @@
],
"filtersSize": 176,
"find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true,
"help": "",
"icons": "posters",
@ -1076,7 +1298,7 @@
"featured": true,
"volumes": true
},
"texts": {
"documents": {
"personal": true,
"favorite": true,
"featured": true

View file

@ -36,11 +36,13 @@
*/
"capabilities": {
"canAddItems": {"researcher": true, "staff": true, "admin": true},
"canAddDocuments": {"researcher": true, "staff": true, "admin": true},
"canDownloadVideo": {"guest": -1, "member": -1, "researcher": 3, "staff": 3, "admin": 3},
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"researcher": true, "staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditEvents": {"researcher": true, "staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
@ -63,11 +65,13 @@
"canPlayVideo": {"guest": 1, "member": 1, "researcher": 3, "staff": 3, "admin": 3},
"canReadText": {"guest": 0, "member": 0, "researcher": 1, "staff": 1, "admin": 1},
"canRemoveItems": {"staff": true, "admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"researcher": true, "staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"researcher": true, "staff": true, "admin": true},
"canSeeExtraItemViews": {"researcher": true, "staff": true, "admin": true},
"canSeeMedia": {"researcher": true, "staff": true, "admin": true},
"canSeeDocument": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
"canSeeItem": {"guest": 3, "member": 3, "researcher": 3, "staff": 3, "admin": 3},
"canSeeSize": {"researcher": true, "staff": true, "admin": true},
"canSeeSoftwareVersion": {"researcher": true, "staff": true, "admin": true},
@ -94,6 +98,257 @@
*/
"clipLayers": ["subtitles", "keywords", "notes"],
/*
"documentKeys" defines the metadata associated with each document. Required keys
are "*", "id" and "title".
A documentKey must have the following properties:
"id": The unique id of the key (as used by the server)
"title": The title of the key (as displayed by the client)
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
"layer", "string", "text", "time" or ["..."] (list of values of
this type). If type is "layer", this is a reference to the
annotations layer with the same id.
and can have any of the following properties:
"additionalSort": Ordered list of {key, operator} objects, where key is
another itemKey and operator is "+" or "-". This can be used to
override user.ui.listSort when results are sorted by this key.
"autocomplete": If true, the find element will provide autocomplete
"autocompleteSort": Sort order of autocomplete suggestions
"capability": A capability required to see data for this key
"columnRequired": If true, the column can't be removed from list view
"columnWidth": Default column width in px. If absent, no column for
this key can be added in list view.
"filter": If true, one can filter results by this key
"find": If true, this key will appear as an option in the find element
"flag": Can be "country" or "language". If set (and filter is true), a
flag icon corresponding to the field's value will be displayed.
"format": {type: string, args: [value, value, ...]}, used for special
formatting. This will invoke Ox.formatType(args). For details, see
https://oxjs.org/#doc/Ox.formatArea etc.
"secondaryId": If true, loading the URL "/value" will redirect to the
corresponding item, in case there is an exact match for this key
"sort": If true, one can sort results by this key
"sortOperator": Sort order ("+" or "-"), in case it differs from the
default for the key's type ("+" for strings, "-" for numbers)
"sortType": Special sort type ("person" or "title") which can be
further configured in "Manage Names" or "Manage Titles"
"value": {key: string, type: string} or {layer: string, type: string},
for keys whose value is derived from other keys or layers (like
"number of actors" or "words per minute"). Possible values for type
are "length", "lengthperminute", "words", and "wordsperminute".
Alternatively, "value" can be set to the string "capability", which
results in an itemKey whose boolean value indicates the presence or
absence of a userLevel-dependent capability. This can be used to
create queries and lists like "all items this user can play" etc.
"values": [value, value, ...] Ordered list of values, in case "type" is
"enum"
*/
"documentKeys": [
{
"id": "*",
"title": "All",
"type": "text",
"find": true
},
{
"id": "title",
"operator": "+",
"title": "Title",
"type": "string",
"find": true,
"sort": true,
"sortType": "title",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "type",
"operator": "+",
"title": "Type",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "author",
"operator": "+",
"title": "Author",
"type": ["string"],
"filter": true,
"find": true,
"sort": true,
"sortType": "person",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "publisher",
"operator": "+",
"title": "Publisher",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 256
},
{
"id": "place",
"title": "Place",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "date",
"title": "Date",
"type": "string",
"columnWidth": 120,
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
"sort": true
},
{
"id": "series",
"title": "Series",
"type": "string",
"columnWidth": 128,
"find": true,
"sort": true
},
{
"id": "edition",
"title": "Edition",
"type": "string",
"columnWidth": 128,
"find": true
},
{
"id": "language",
"title": "Language",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "id",
"operator": "+",
"title": "ID",
"type": "string",
"sort": true,
"columnWidth": 64
},
{
"id": "extension",
"operator": "+",
"title": "Extension",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 64
},
{
"id": "dimensions",
"operator": "-",
"title": "Dimensions",
"type": "integer",
"sort": true,
"columnWidth": 128
},
{
"id": "size",
"operator": "-",
"title": "Size",
"type": "integer",
"sort": true,
"format": {"type": "value", "args": ["B"]},
"columnWidth": 64
},
{
"id": "description",
"operator": "+",
"title": "Description",
"type": "text",
"find": true,
"sort": true,
"columnWidth": 256
},
{
"id": "matches",
"operator": "-",
"title": "Matches",
"type": "integer",
"sort": true,
"columnWidth": 64
},
{
"id": "user",
"operator": "+",
"title": "User",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "created",
"operator": "-",
"title": "Created",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "modified",
"operator": "-",
"title": "Modified",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "accessed",
"title": "Last Accessed",
"type": "date",
"capability": "canSeeAccessed",
"columnWidth": 150,
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"sort": true
},
{
"id": "timesaccessed",
"title": "Times Accessed",
"type": "integer",
"capability": "canSeeAccessed",
"columnWidth": 60,
"format": {"type": "number", "args": []},
"sort": true
},
{
"id": "rightslevel",
"title": "Rights Level",
"type": "enum",
"columnWidth": 90,
"format": {"type": "ColorLevel", "args": [
["Public", "Out of Copyright", "Under Copyright", "Private"]
]},
"sort": true,
"sortOperator": "+",
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
}
],
/*
"entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor"
@ -1004,16 +1259,26 @@
"calendarFind": "",
"calendarSelection": "",
"clipColumns": 2,
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
"collectionColumnWidth": {},
"collectionSelection": [],
"collectionSort": [
{"key": "title", "operator": "+"},
{"key": "extension", "operator": "+"}
],
"collectionView": "grid",
"collections": {},
"columns": {
"Colors": {
"columns": ["title", "director", "country", "year", "hue", "saturation", "brightness"],
"columnWidth": {}
}
},
"documentView": "view",
"documents": {},
"documentSize": 256,
"documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}],
"documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid",
"edit": "",
"edits": {},
@ -1039,6 +1304,7 @@
],
"filtersSize": 176,
"find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true,
"help": "",
"icons": "posters",
@ -1063,7 +1329,6 @@
"page": "",
"part": {
"api": "",
"documents": "",
"entities": "",
"faq": "",
"help": "",
@ -1104,6 +1369,11 @@
"featured": true,
"volumes": true
},
"documents": {
"personal": true,
"favorite": true,
"featured": true
},
"texts": {
"personal": true,
"favorite": true,
@ -1117,6 +1387,8 @@
"sidebarSize": 256,
"text": "",
"texts": {},
"documents": {},
"document": "",
"theme": "oxmedium",
"updateAdvancedFindResults": false,
"videoLoop": false,

View file

@ -35,11 +35,13 @@
*/
"capabilities": {
"canAddItems": {"member": true, "staff": true, "admin": true},
"canAddDocuments": {"member": true, "staff": true, "admin": true},
"canDownloadVideo": {"guest": 0, "member": 0, "staff": 4, "admin": 4},
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
@ -61,11 +63,13 @@
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1},
"canRemoveItems": {"admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"staff": true, "admin": true},
"canSeeDocument": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeSize": {"staff": true, "admin": true},
"canSeeSoftwareVersion": {"staff": true, "admin": true},
@ -92,6 +96,257 @@
*/
"clipLayers": ["transcripts", "keywords", "places", "events", "descriptions"],
/*
"documentKeys" defines the metadata associated with each document. Required keys
are "*", "id" and "title".
A documentKey must have the following properties:
"id": The unique id of the key (as used by the server)
"title": The title of the key (as displayed by the client)
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
"layer", "string", "text", "time" or ["..."] (list of values of
this type). If type is "layer", this is a reference to the
annotations layer with the same id.
and can have any of the following properties:
"additionalSort": Ordered list of {key, operator} objects, where key is
another itemKey and operator is "+" or "-". This can be used to
override user.ui.listSort when results are sorted by this key.
"autocomplete": If true, the find element will provide autocomplete
"autocompleteSort": Sort order of autocomplete suggestions
"capability": A capability required to see data for this key
"columnRequired": If true, the column can't be removed from list view
"columnWidth": Default column width in px. If absent, no column for
this key can be added in list view.
"filter": If true, one can filter results by this key
"find": If true, this key will appear as an option in the find element
"flag": Can be "country" or "language". If set (and filter is true), a
flag icon corresponding to the field's value will be displayed.
"format": {type: string, args: [value, value, ...]}, used for special
formatting. This will invoke Ox.formatType(args). For details, see
https://oxjs.org/#doc/Ox.formatArea etc.
"secondaryId": If true, loading the URL "/value" will redirect to the
corresponding item, in case there is an exact match for this key
"sort": If true, one can sort results by this key
"sortOperator": Sort order ("+" or "-"), in case it differs from the
default for the key's type ("+" for strings, "-" for numbers)
"sortType": Special sort type ("person" or "title") which can be
further configured in "Manage Names" or "Manage Titles"
"value": {key: string, type: string} or {layer: string, type: string},
for keys whose value is derived from other keys or layers (like
"number of actors" or "words per minute"). Possible values for type
are "length", "lengthperminute", "words", and "wordsperminute".
Alternatively, "value" can be set to the string "capability", which
results in an itemKey whose boolean value indicates the presence or
absence of a userLevel-dependent capability. This can be used to
create queries and lists like "all items this user can play" etc.
"values": [value, value, ...] Ordered list of values, in case "type" is
"enum"
*/
"documentKeys": [
{
"id": "*",
"title": "All",
"type": "text",
"find": true
},
{
"id": "title",
"operator": "+",
"title": "Title",
"type": "string",
"find": true,
"sort": true,
"sortType": "title",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "type",
"operator": "+",
"title": "Type",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "author",
"operator": "+",
"title": "Author",
"type": ["string"],
"filter": true,
"find": true,
"sort": true,
"sortType": "person",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "publisher",
"operator": "+",
"title": "Publisher",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 256
},
{
"id": "place",
"title": "Place",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "date",
"title": "Date",
"type": "string",
"columnWidth": 120,
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
"sort": true
},
{
"id": "series",
"title": "Series",
"type": "string",
"columnWidth": 128,
"find": true,
"sort": true
},
{
"id": "edition",
"title": "Edition",
"type": "string",
"columnWidth": 128,
"find": true
},
{
"id": "language",
"title": "Language",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "id",
"operator": "+",
"title": "ID",
"type": "string",
"sort": true,
"columnWidth": 64
},
{
"id": "extension",
"operator": "+",
"title": "Extension",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 64
},
{
"id": "dimensions",
"operator": "-",
"title": "Dimensions",
"type": "integer",
"sort": true,
"columnWidth": 128
},
{
"id": "size",
"operator": "-",
"title": "Size",
"type": "integer",
"sort": true,
"format": {"type": "value", "args": ["B"]},
"columnWidth": 64
},
{
"id": "description",
"operator": "+",
"title": "Description",
"type": "text",
"find": true,
"sort": true,
"columnWidth": 256
},
{
"id": "matches",
"operator": "-",
"title": "Matches",
"type": "integer",
"sort": true,
"columnWidth": 64
},
{
"id": "user",
"operator": "+",
"title": "User",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "created",
"operator": "-",
"title": "Created",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "modified",
"operator": "-",
"title": "Modified",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "accessed",
"title": "Last Accessed",
"type": "date",
"capability": "canSeeAccessed",
"columnWidth": 150,
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"sort": true
},
{
"id": "timesaccessed",
"title": "Times Accessed",
"type": "integer",
"capability": "canSeeAccessed",
"columnWidth": 60,
"format": {"type": "number", "args": []},
"sort": true
},
{
"id": "rightslevel",
"title": "Rights Level",
"type": "enum",
"columnWidth": 90,
"format": {"type": "ColorLevel", "args": [
["Public", "Out of Copyright", "Under Copyright", "Private"]
]},
"sort": true,
"sortOperator": "+",
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
}
],
/*
"entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor"
@ -882,16 +1137,27 @@
"calendarFind": "",
"calendarSelection": "",
"clipColumns": 2,
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
"collectionColumnWidth": {},
"collectionSelection": [],
"collectionSort": [
{"key": "title", "operator": "+"},
{"key": "extension", "operator": "+"}
],
"collectionView": "grid",
"collections": {},
"columns": {
"Colors": {
"columns": ["title", "source", "project", "language", "hue", "saturation", "brightness"],
"columns": ["title", "director", "language", "hue", "saturation", "brightness"],
"columnWidth": {}
}
},
"document": "",
"documents": {},
"documentSize": 256,
"documentView": "view",
"documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}],
"documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid",
"edit": "",
"edits": {},
@ -917,6 +1183,7 @@
],
"filtersSize": 176,
"find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true,
"help": "",
"icons": "frames",
@ -937,7 +1204,6 @@
"page": "",
"part": {
"api": "",
"documents": "",
"entities": "",
"faq": "",
"help": "",
@ -981,7 +1247,7 @@
"featured": true,
"volumes": true
},
"texts": {
"documents": {
"personal": true,
"favorite": true,
"featured": true

View file

@ -39,11 +39,13 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
*/
"capabilities": {
"canAddItems": {"member": true, "staff": true, "admin": true},
"canAddDocuments": {"member": true, "staff": true, "admin": true},
"canDownloadVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canEditAnnotations": {"staff": true, "admin": true},
"canEditDocuments": {"staff": true, "admin": true},
"canEditEntities": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedCollections": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
@ -64,12 +66,14 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1},
"canRemoveItems": {"admin": true},
"canRemoveItems": {"staff": true, "admin": true},
"canRemoveDocuments": {"staff": true, "admin": true},
"canSeeAccessed": {"staff": true, "admin": true},
"canSeeAllTasks": {"staff": true, "admin": true},
"canSeeDebugMenu": {"staff": true, "admin": true},
"canSeeExtraItemViews": {"staff": true, "admin": true},
"canSeeMedia": {"staff": true, "admin": true},
"canSeeDocument": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4},
"canSeeSize": {"staff": true, "admin": true},
"canSeeSoftwareVersion": {"staff": true, "admin": true},
@ -96,6 +100,257 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
*/
"clipLayers": ["publicnotes", "keywords", "subtitles"],
/*
"documentKeys" defines the metadata associated with each document. Required keys
are "*", "id" and "title".
A documentKey must have the following properties:
"id": The unique id of the key (as used by the server)
"title": The title of the key (as displayed by the client)
"type": Can be "boolean", "date", "enum", "float", "hue", "integer",
"layer", "string", "text", "time" or ["..."] (list of values of
this type). If type is "layer", this is a reference to the
annotations layer with the same id.
and can have any of the following properties:
"additionalSort": Ordered list of {key, operator} objects, where key is
another itemKey and operator is "+" or "-". This can be used to
override user.ui.listSort when results are sorted by this key.
"autocomplete": If true, the find element will provide autocomplete
"autocompleteSort": Sort order of autocomplete suggestions
"capability": A capability required to see data for this key
"columnRequired": If true, the column can't be removed from list view
"columnWidth": Default column width in px. If absent, no column for
this key can be added in list view.
"filter": If true, one can filter results by this key
"find": If true, this key will appear as an option in the find element
"flag": Can be "country" or "language". If set (and filter is true), a
flag icon corresponding to the field's value will be displayed.
"format": {type: string, args: [value, value, ...]}, used for special
formatting. This will invoke Ox.formatType(args). For details, see
https://oxjs.org/#doc/Ox.formatArea etc.
"secondaryId": If true, loading the URL "/value" will redirect to the
corresponding item, in case there is an exact match for this key
"sort": If true, one can sort results by this key
"sortOperator": Sort order ("+" or "-"), in case it differs from the
default for the key's type ("+" for strings, "-" for numbers)
"sortType": Special sort type ("person" or "title") which can be
further configured in "Manage Names" or "Manage Titles"
"value": {key: string, type: string} or {layer: string, type: string},
for keys whose value is derived from other keys or layers (like
"number of actors" or "words per minute"). Possible values for type
are "length", "lengthperminute", "words", and "wordsperminute".
Alternatively, "value" can be set to the string "capability", which
results in an itemKey whose boolean value indicates the presence or
absence of a userLevel-dependent capability. This can be used to
create queries and lists like "all items this user can play" etc.
"values": [value, value, ...] Ordered list of values, in case "type" is
"enum"
*/
"documentKeys": [
{
"id": "*",
"title": "All",
"type": "text",
"find": true
},
{
"id": "title",
"operator": "+",
"title": "Title",
"type": "string",
"find": true,
"sort": true,
"sortType": "title",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "type",
"operator": "+",
"title": "Type",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "author",
"operator": "+",
"title": "Author",
"type": ["string"],
"filter": true,
"find": true,
"sort": true,
"sortType": "person",
"autocomplete": true,
"columnWidth": 256
},
{
"id": "publisher",
"operator": "+",
"title": "Publisher",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 256
},
{
"id": "place",
"title": "Place",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "date",
"title": "Date",
"type": "string",
"columnWidth": 120,
//"format": {"type": "date", "args": ["%a, %b %e, %Y"]},
"sort": true
},
{
"id": "series",
"title": "Series",
"type": "string",
"columnWidth": 128,
"find": true,
"sort": true
},
{
"id": "edition",
"title": "Edition",
"type": "string",
"columnWidth": 128,
"find": true
},
{
"id": "language",
"title": "Language",
"type": ["string"],
"columnWidth": 128,
"filter": true,
"find": true,
"sort": true
},
{
"id": "id",
"operator": "+",
"title": "ID",
"type": "string",
"sort": true,
"columnWidth": 64
},
{
"id": "extension",
"operator": "+",
"title": "Extension",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 64
},
{
"id": "dimensions",
"operator": "-",
"title": "Dimensions",
"type": "integer",
"sort": true,
"columnWidth": 128
},
{
"id": "size",
"operator": "-",
"title": "Size",
"type": "integer",
"sort": true,
"format": {"type": "value", "args": ["B"]},
"columnWidth": 64
},
{
"id": "description",
"operator": "+",
"title": "Description",
"type": "text",
"find": true,
"sort": true,
"columnWidth": 256
},
{
"id": "matches",
"operator": "-",
"title": "Matches",
"type": "integer",
"sort": true,
"columnWidth": 64
},
{
"id": "user",
"operator": "+",
"title": "User",
"type": "string",
"filter": true,
"find": true,
"sort": true,
"autocomplete": true,
"columnWidth": 128
},
{
"id": "created",
"operator": "-",
"title": "Created",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "modified",
"operator": "-",
"title": "Modified",
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"type": "date",
"sort": true,
"columnWidth": 144
},
{
"id": "accessed",
"title": "Last Accessed",
"type": "date",
"capability": "canSeeAccessed",
"columnWidth": 150,
"format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]},
"sort": true
},
{
"id": "timesaccessed",
"title": "Times Accessed",
"type": "integer",
"capability": "canSeeAccessed",
"columnWidth": 60,
"format": {"type": "number", "args": []},
"sort": true
},
{
"id": "rightslevel",
"title": "Rights Level",
"type": "enum",
"columnWidth": 90,
"format": {"type": "ColorLevel", "args": [
["Public", "Out of Copyright", "Under Copyright", "Private"]
]},
"sort": true,
"sortOperator": "+",
"values": ["Public", "Out of Copyright", "Under Copyright", "Private", "Unknown"]
}
],
/*
"entities" can be used to store arbitrary data. They can be referenced in
annotations, info view, or elsewhere. Each entry defines a specific class
of entity object, its properties and their types (for example an "actor"
@ -822,16 +1077,27 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"calendarFind": "",
"calendarSelection": "",
"clipColumns": 2,
"collectionColumns": ["title", "id", "extension", "dimensions", "size", "description", "matches", "user", "created", "modified"],
"collectionColumnWidth": {},
"collectionSelection": [],
"collectionSort": [
{"key": "title", "operator": "+"},
{"key": "extension", "operator": "+"}
],
"collectionView": "grid",
"collections": {},
"columns": {
"Colors": {
"columns": ["title", "director", "language", "hue", "saturation", "brightness"],
"columnWidth": {}
}
},
"document": "",
"documents": {},
"documentSize": 256,
"documentView": "view",
"documentsSelection": {},
"documentsSort": [{"key": "name", "operator": "+"}],
"documentsSort": [{"key": "title", "operator": "+"}],
"documentsView": "grid",
"edit": "",
"edits": {},
@ -857,6 +1123,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
],
"filtersSize": 176,
"find": {"conditions": [], "operator": "&"},
"findDocuments": {"conditions": [], "operator": "&"},
"followPlayer": true,
"help": "",
"icons": "posters",
@ -877,7 +1144,6 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"page": "",
"part": {
"api": "",
"documents": "",
"entities": "",
"faq": "",
"help": "",
@ -920,7 +1186,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"featured": true,
"volumes": true
},
"texts": {
"documents": {
"personal": true,
"favorite": true,
"featured": true

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 -*-
# vi:si:et:sw=4:sts=4:ts=4
import unicodedata
from six import string_types
from django.db.models import Q, Manager
from django.conf import settings
import ox
from oxdjango.query import QuerySet
@ -8,14 +12,31 @@ from oxdjango.query import QuerySet
import entity.managers
from oxdjango.managers import get_operator
from documentcollection.models import Collection
from item import utils
keymap = {
'user': 'user__username',
'item': 'items__public_id',
}
default_key = 'name'
default_key = 'title'
def parseCondition(condition, user, item=None):
def get_key_type(k):
key_type = (utils.get_by_id(settings.CONFIG['documentKeys'], k) or {'type': 'string'}).get('type')
if isinstance(key_type, list):
key_type = key_type[0]
key_type = {
'title': 'string',
'person': 'string',
'text': 'string',
'year': 'string',
'length': 'string',
'layer': 'string',
'list': 'list',
}.get(key_type, key_type)
return key_type
def parseCondition(condition, user, item=None, owner=None):
'''
'''
k = condition.get('key', default_key)
@ -33,17 +54,47 @@ def parseCondition(condition, user, item=None):
op = '='
if op.startswith('!'):
return ~buildCondition(k, op[1:], v)
return buildCondition(k, op[1:], v, user, True, owner=owner)
else:
return buildCondition(k, op, v)
return buildCondition(k, op, v, user, owner=owner)
def buildCondition(k, op, v):
def buildCondition(k, op, v, user, exclude=False, owner=None):
import entity.models
from . import models
# fixme: frontend should never call with list
if k == 'list':
print('fixme: frontend should never call with list', k, op, v)
k = 'collection'
key_type = get_key_type(k)
facet_keys = models.Document.facet_keys
if k == 'id':
v = ox.fromAZ(v)
return Q(**{k: v})
if isinstance(v, bool):
q = Q(**{k: v})
if exclude:
q = ~Q(id__in=models.Document.objects.filter(q))
return q
elif k == 'groups':
if op == '==' and v == '$my':
if not owner:
owner = user
groups = owner.groups.all()
else:
key = 'name' + get_operator(op)
groups = Group.objects.filter(**{key: v})
if not groups.count():
return Q(id=0)
q = Q(groups__in=groups)
if exclude:
q = ~q
return q
elif k in ('oshash', 'items__public_id'):
q = Q(**{k: v})
if exclude:
q = ~Q(id__in=models.Document.objects.filter(q))
return q
elif isinstance(v, bool):
key = k
elif k == 'entity':
entity_key, entity_v = entity.managers.namePredicate(op, v)
@ -51,13 +102,87 @@ def buildCondition(k, op, v):
v = entity.models.DocumentProperties.objects.filter(**{
'entity__' + entity_key: entity_v
}).values_list('document_id', flat=True)
elif k == 'collection':
q = Q(id=0)
l = v.split(":", 1)
if len(l) >= 2:
lqs = list(Collection.objects.filter(name=l[1], user__username=l[0]))
if len(lqs) == 1 and lqs[0].accessible(user):
l = lqs[0]
if l.query.get('static', False) is False:
data = l.query
q = parseConditions(data.get('conditions', []),
data.get('operator', '&'),
user, owner=l.user)
else:
key = k + get_operator(op, 'istr')
q = Q(id__in=l.documents.all())
else:
q = Q(id=0)
return q
elif key_type == 'boolean':
q = Q(**{'find__key': k, 'find__value': v})
if exclude:
q = ~Q(id__in=models.Document.objects.filter(q))
return q
elif key_type == "string":
in_find = True
if in_find:
value_key = 'find__value'
else:
value_key = k
if isinstance(v, string_types):
v = unicodedata.normalize('NFKD', v).lower()
if k in facet_keys:
in_find = False
facet_value = 'facets__value' + get_operator(op, 'istr')
v = models.Document.objects.filter(**{'facets__key': k, facet_value: v})
value_key = 'id__in'
else:
value_key = value_key + get_operator(op)
k = str(k)
value_key = str(value_key)
if k == '*':
q = Q(**{value_key: v})
elif in_find:
q = Q(**{'find__key': k, value_key: v})
else:
q = Q(**{value_key: v})
if exclude:
q = ~Q(id__in=models.Document.objects.filter(q))
return q
elif key_type == 'date':
def parse_date(d):
while len(d) < 3:
d.append(1)
return datetime(*[int(i) for i in d])
#using sort here since find only contains strings
v = parse_date(v.split('-'))
vk = 'sort__%s%s' % (k, get_operator(op, 'int'))
vk = str(vk)
q = Q(**{vk: v})
if exclude:
q = ~q
return q
else: # integer, float, list, time
#use sort table here
if key_type == 'time':
v = int(utils.parse_time(v))
vk = 'sort__%s%s' % (k, get_operator(op, 'int'))
vk = str(vk)
q = Q(**{vk: v})
if exclude:
q = ~q
return q
key = str(key)
return Q(**{key: v})
q = Q(**{key: v})
if exclude:
q = ~q
return q
def parseConditions(conditions, operator, user, item=None):
def parseConditions(conditions, operator, user, item=None, owner=None):
'''
conditions: [
{
@ -80,12 +205,12 @@ def parseConditions(conditions, operator, user, item=None):
for condition in conditions:
if 'conditions' in condition:
q = parseConditions(condition['conditions'],
condition.get('operator', '&'), user, item)
condition.get('operator', '&'), user, item, owner=owner)
if q:
conn.append(q)
pass
else:
conn.append(parseCondition(condition, user, item))
conn.append(parseCondition(condition, user, item, owner=owner))
if conn:
q = conn[0]
for c in conn[1:]:
@ -133,4 +258,21 @@ class DocumentManager(Manager):
if conditions:
qs = qs.filter(conditions)
#anonymous can only see public items
if not user or user.is_anonymous():
level = 'guest'
allowed_level = settings.CONFIG['capabilities']['canSeeDocument'][level]
qs = qs.filter(rightslevel__lte=allowed_level)
rendered_q = Q(rendered=True)
#users can see public items, there own items and items of there groups
else:
level = user.profile.get_level()
allowed_level = settings.CONFIG['capabilities']['canSeeDocument'][level]
q = Q(rightslevel__lte=allowed_level) | Q(user=user)
rendered_q = Q(rendered=True) | Q(user=user)
if user.groups.count():
q |= Q(groups__in=user.groups.all())
rendered_q |= Q(groups__in=user.groups.all())
qs = qs.filter(q)
return qs

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 re
from glob import glob
import unicodedata
from six import string_types
from six.moves.urllib.parse import quote, unquote
from django.db import models
from django.db.models import Max
from django.contrib.auth.models import User
from django.db import models, transaction
from django.db.models import Q, Sum, Max
from django.contrib.auth.models import User, Group
from django.db.models.signals import pre_delete
from django.conf import settings
from PIL import Image
import ox
from oxdjango import fields
from oxdjango.sortmodel import get_sort_field
from person.models import get_name_sort
from item.models import Item
from archive.extract import resize_image
from archive.chunk import save_chunk
@ -23,57 +29,249 @@ from archive.chunk import save_chunk
from . import managers
from . import utils
def get_path(f, x): return f.path(x)
def get_path(f, x):
return f.path(x)
class Document(models.Model):
class Meta:
unique_together = ("user", "name", "extension")
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, related_name='files')
name = models.CharField(max_length=255)
user = models.ForeignKey(User, related_name='documents')
groups = models.ManyToManyField(Group, blank=True, related_name='documents')
extension = models.CharField(max_length=255)
size = models.IntegerField(default=0)
matches = models.IntegerField(default=0)
ratio = models.FloatField(default=1)
ratio = models.FloatField(default=640/1024)
pages = models.IntegerField(default=-1)
width = models.IntegerField(default=-1)
height = models.IntegerField(default=-1)
description = models.TextField(default="")
oshash = models.CharField(max_length=16, unique=True, null=True)
file = models.FileField(default=None, blank=True,null=True, upload_to=get_path)
file = models.FileField(default=None, blank=True, null=True, upload_to=get_path)
objects = managers.DocumentManager()
uploading = models.BooleanField(default = False)
name_sort = models.CharField(max_length=255, null=True)
description_sort = models.CharField(max_length=512, null=True)
dimensions_sort = models.CharField(max_length=512)
uploading = models.BooleanField(default=False)
items = models.ManyToManyField(Item, through='ItemProperties', related_name='documents')
rightslevel = models.IntegerField(db_index=True, default=0)
data = fields.DictField(default={})
def update_access(self, user):
if not user.is_authenticated():
user = None
access, created = Access.objects.get_or_create(document=self, user=user)
if not created:
access.save()
def update_facet(self, key):
current_values = self.get_value(key, [])
if key == 'name':
current_values = []
for k in settings.CONFIG['documentKeys']:
if k.get('sortType') == 'person':
current_values += self.get(k['id'], [])
if not isinstance(current_values, list):
if not current_values:
current_values = []
else:
current_values = [unicode(current_values)]
filter_map = utils.get_by_id(settings.CONFIG['documentKeys'], key).get('filterMap')
if filter_map:
filter_map = re.compile(filter_map)
_current_values = []
for value in current_values:
value = filter_map.findall(value)
if value:
_current_values.append(value[0])
current_values = _current_values
current_values = list(set(current_values))
current_values = [ox.decode_html(ox.strip_tags(v)) for v in current_values]
current_values = [unicodedata.normalize('NFKD', v) for v in current_values]
self.update_facet_values(key, current_values)
def update_facet_values(self, key, current_values):
current_sortvalues = set([value.lower() for value in current_values])
saved_values = [i.value.lower() for i in Facet.objects.filter(document=self, key=key)]
removed_values = filter(lambda i: i not in current_sortvalues, saved_values)
if removed_values:
q = Q()
for v in removed_values:
q |= Q(value__iexact=v)
Facet.objects.filter(document=self, key=key).filter(q).delete()
for value in current_values:
if value.lower() not in saved_values:
sortvalue = value
if key in self.person_keys + ['name']:
sortvalue = get_name_sort(value)
sortvalue = utils.sort_string(sortvalue).lower()[:900]
f, created = Facet.objects.get_or_create(document=self, key=key, value=value, sortvalue=sortvalue)
if created:
Facet.objects.filter(document=self, key=key, value__iexact=value).exclude(value=value).delete()
Facet.objects.filter(key=key, value__iexact=value).exclude(value=value).update(value=value)
saved_values.append(value.lower())
def update_facets(self):
for key in set(self.facet_keys + ['title']):
self.update_facet(key)
def update_find(self):
def save(key, value):
if value not in ('', None):
f, created = Find.objects.get_or_create(document=self, key=key)
if isinstance(value, bool):
value = value and 'true' or 'false'
if isinstance(value, string_types):
value = ox.decode_html(ox.strip_tags(value.strip()))
value = unicodedata.normalize('NFKD', value).lower()
f.value = value
f.save()
else:
Find.objects.filter(document=self, key=key).delete()
with transaction.atomic():
data = self.json()
for key in settings.CONFIG['documentKeys']:
i = key['id']
if i == 'rightslevel':
save(i, self.rightslevel)
elif i not in ('*', 'dimensions') and i not in self.facet_keys:
value = data.get(i)
if isinstance(value, list):
value = u'\n'.join(value)
save(i, value)
base_keys = ('id', 'size', 'dimensions', 'extension', 'matches')
def update_sort(self):
try:
s = self.sort
except Sort.DoesNotExist:
s = Sort(document=self)
s.id = self.id
s.extension = self.extension
s.size = self.size
s.matches = self.matches
if self.extension == 'pdf':
s.dimensions = ox.sort_string('2') + ox.sort_string('%d' % self.pages)
else:
if self.extension == 'html':
resolution_sort = self.dimensions
s.dimensions = ox.sort_string('1') + ox.sort_string('%d' % resolution_sort)
else:
resolution_sort = self.width * self.height
s.dimensions = ox.sort_string('0') + ox.sort_string('%d' % resolution_sort)
def sortNames(values):
sort_value = u''
if values:
sort_value = u'; '.join([get_name_sort(name) for name in values])
if not sort_value:
sort_value = u''
return sort_value
def set_value(s, name, value):
if isinstance(value, string_types):
value = ox.decode_html(value.lower())
if not value:
value = None
setattr(s, name, value)
def get_value(source, key):
if 'value' in key and 'layer' in key['value']:
value = [a.value for a in self.annotations.filter(layer=key['value']['layer']).exclude(value='')]
else:
value = self.get_value(source)
return value
def get_words(source, key):
value = get_value(source, key)
if isinstance(value, list):
value = '\n'.join(value)
value = len(value.split(' ')) if value else 0
return value
for key in filter(lambda k: k.get('sort', False), settings.CONFIG['documentKeys']):
name = key['id']
if name not in self.base_keys:
source = name
sort_type = key.get('sortType', key['type'])
if 'value' in key:
if 'key' in key['value']:
source = key['value']['key']
sort_type = key['value'].get('type', sort_type)
if isinstance(sort_type, list):
sort_type = sort_type[0]
if sort_type == 'title':
value = self.get_value(source, u'Untitled')
value = utils.sort_title(value)[:955]
set_value(s, name, value)
elif sort_type == 'person':
value = sortNames(self.get_value(source, []))
value = utils.sort_string(value)[:955]
set_value(s, name, value)
elif sort_type == 'string':
value = self.get_value(source, u'')
if isinstance(value, list):
value = u','.join(value)
value = utils.sort_string(value)[:955]
set_value(s, name, value)
elif sort_type == 'words':
value = get_words(source, key) if s.duration else None
set_value(s, name, value)
elif sort_type == 'wordsperminute':
value = get_words(source, key)
value = value / (s.duration / 60) if value and s.duration else None
set_value(s, name, value)
elif sort_type in ('length', 'integer', 'time', 'float'):
# can be length of strings or length of arrays, i.e. keywords
if 'layer' in key.get('value', []):
value = self.annotations.filter(layer=key['value']['layer']).count()
else:
value = self.get_value(source)
if isinstance(value, list):
value = len(value)
set_value(s, name, value)
elif sort_type == 'year':
value = self.get_value(source)
set_value(s, name, value)
elif sort_type == 'date':
value = self.get_value(source)
if isinstance(value, string_types):
value = datetime_safe.datetime.strptime(value, '%Y-%m-%d')
set_value(s, name, value)
s.save()
def save(self, *args, **kwargs):
if not self.uploading:
if self.file:
self.size = self.file.size
self.get_info()
if self.extension == 'html':
self.size = len(self.data.get('text', ''))
self.name_sort = ox.sort_string(self.name or u'')[:255].lower()
if self.description:
self.description_sort = ox.sort_string(self.description)[:512].lower()
if self.id:
self.update_sort()
self.update_find()
self.update_facets()
new = False
else:
self.description_sort = None
if self.extension == 'pdf':
self.dimensions_sort = ox.sort_string('1') + ox.sort_string('%d' % self.pages)
else:
resolution_sort = self.width * self.height
self.dimensions_sort = ox.sort_string('0') + ox.sort_string('%d' % resolution_sort)
new = True
super(Document, self).save(*args, **kwargs)
if new:
self.update_sort()
self.update_find()
self.update_facets()
self.update_matches()
def __unicode__(self):
@ -100,40 +298,61 @@ class Document(models.Model):
def get_id(self):
return ox.toAZ(self.id)
def accessible(self, user):
return self.user == user or self.status in ('public', 'featured')
def editable(self, user, item=None):
if not user or user.is_anonymous():
return False
if self.user == user or \
user.is_staff or \
user.profile.capability('canEditDocuments') == True or \
user.profile.capability('canEditDocuments') is True or \
(item and item.editable(user)):
return True
return False
def edit(self, data, user, item=None):
for key in data:
if key == 'name':
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
if not data['name']:
data['name'] = "Untitled"
name = data['name']
num = 1
while Document.objects.filter(name=name, user=self.user, extension=self.extension).exclude(id=self.id).count()>0:
num += 1
name = data['name'] + ' [%d]' % num
self.name = name
elif key == 'description' and not item:
self.description = ox.sanitize_html(data['description'])
if item:
p, created = ItemProperties.objects.get_or_create(item=item, document=self)
if 'description' in data:
p.description = ox.sanitize_html(data['description'])
p.save()
else:
for key in data:
k = list(filter(lambda i: i['id'] == key, settings.CONFIG['documentKeys']))
ktype = k and k[0].get('type') or ''
if key == 'text' and self.extension == 'html':
self.data['text'] = ox.sanitize_html(data['text'], global_attributes=[
'data-name',
'data-type',
'data-value',
'lang'
])
elif ktype == 'text':
self.data[key] = ox.sanitize_html(data[key])
elif ktype == '[text]':
self.data[key] = [ox.sanitize_html(t) for t in data[key]]
elif ktype == '[string]':
self.data[key] = [ox.escape_html(t) for t in data[key]]
elif isinstance(data[key], string_types):
self.data[key] = ox.escape_html(data[key])
elif isinstance(data[key], list):
def cleanup(i):
if isinstance(i, string_types):
i = ox.escape_html(i)
return i
self.data[key] = [cleanup(i) for i in data[key]]
elif isinstance(data[key], int) or isinstance(data[key], float):
self.data[key] = data[key]
else:
self.data[key] = ox.escape_html(data[key])
@property
def dimensions(self):
if self.extension == 'pdf':
return self.pages
elif self.extension == 'html':
return len(self.data.get('text', '').split(' '))
else:
return self.resolution
@ -141,21 +360,43 @@ class Document(models.Model):
def resolution(self):
return [self.width, self.height]
def get_value(self, key, default=None):
if key in (
'extension',
'id',
'matches',
'ratio',
'size',
):
return getattr(self, key)
elif key == 'user':
return self.user.username
else:
return self.data.get(key, default)
def json(self, keys=None, user=None, item=None):
if not keys:
keys=[
keys = [
'description',
'dimensions',
'editable',
'entities',
'extension',
'id',
'name',
'oshash',
'title',
'ratio',
'matches',
'size',
'user',
]
if self.extension in ('html', 'txt'):
keys.append('text')
for key in settings.CONFIG['documentKeys']:
if key['id'] in ('*', ):
continue
if key['id'] not in keys:
keys.append(key['id'])
response = {}
_map = {
}
@ -166,6 +407,10 @@ class Document(models.Model):
response[key] = self.editable(user)
elif key == 'user':
response[key] = self.user.username
elif key == 'accessed':
response[key] = self.accessed.aggregate(Max('access'))['access__max']
elif key == 'timesaccessed':
response[key] = self.accessed.aggregate(Sum('accessed'))['accessed__sum']
elif key == 'entities':
dps = self.documentproperties.select_related('entity').order_by('index')
response[key] = entity_jsons = []
@ -175,8 +420,12 @@ class Document(models.Model):
entity_jsons.append(entity_json)
elif key == 'items':
response[key] = [i['public_id'] for i in self.items.all().values('public_id')]
elif key in self.data:
response[key] = self.data[key]
elif hasattr(self, _map.get(key, key)):
response[key] = getattr(self, _map.get(key,key)) or ''
response[key] = getattr(self, _map.get(key, key)) or ''
if self.extension == 'html':
response['text'] = self.data.get('text', '')
if item:
if isinstance(item, string_types):
item = Item.objects.get(public_id=item)
@ -185,6 +434,10 @@ class Document(models.Model):
if 'description' in keys and d[0].description:
response['description'] = d[0].description
response['index'] = d[0].index
if keys:
for key in list(response):
if key not in keys:
del response[key]
return response
def path(self, name=''):
@ -211,6 +464,9 @@ class Document(models.Model):
return False, 0
def thumbnail(self, size=None, page=None):
if not self.file:
return os.path.join(settings.STATIC_ROOT, 'png/cover.png')
return os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
src = self.file.path
folder = os.path.dirname(src)
if size:
@ -278,12 +534,12 @@ class Document(models.Model):
try:
size = Image.open(image).size
except:
size = [1,1]
size = [1, 1]
else:
if self.width > 0:
size = self.resolution
else:
size = [1,1]
size = [640, 1024]
self.ratio = size[0] / size[1]
return self.ratio
@ -337,6 +593,97 @@ class ItemProperties(models.Model):
if self.description:
self.description_sort = ox.sort_string(self.description)[:512].lower()
else:
self.description_sort = self.document.description_sort
self.description_sort = self.document.sort.description
super(ItemProperties, self).save(*args, **kwargs)
class Access(models.Model):
class Meta:
unique_together = ("document", "user")
access = models.DateTimeField(auto_now=True)
document = models.ForeignKey(Document, related_name='accessed')
user = models.ForeignKey(User, null=True, related_name='accessed_documents')
accessed = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if not self.accessed:
self.accessed = 0
self.accessed += 1
super(Access, self).save(*args, **kwargs)
timesaccessed = Access.objects.filter(document=self.document).aggregate(Sum('accessed'))['accessed__sum']
Sort.objects.filter(document=self.document).update(timesaccessed=timesaccessed, accessed=self.access)
def __unicode__(self):
if self.user:
return u"%s/%s/%s" % (self.user, self.document, self.access)
return u"%s/%s" % (self.item, self.access)
class Facet(models.Model):
'''
used for keys that can have multiple values like people, languages etc.
does not perform to well if total number of items goes above 10k
this happens for keywords in 0xdb right now
'''
class Meta:
unique_together = ("document", "key", "value")
document = models.ForeignKey('Document', related_name='facets')
key = models.CharField(max_length=200, db_index=True)
value = models.CharField(max_length=1000, db_index=True)
sortvalue = models.CharField(max_length=1000, db_index=True)
def __unicode__(self):
return u"%s=%s" % (self.key, self.value)
def save(self, *args, **kwargs):
if not self.sortvalue:
self.sortvalue = utils.sort_string(self.value).lower()[:900]
self.sotvalue = self.sortvalue.lower()
super(Facet, self).save(*args, **kwargs)
Document.facet_keys = []
for key in settings.CONFIG['documentKeys']:
if 'autocomplete' in key and 'autocompleteSortKey' not in key or \
key.get('filter'):
Document.facet_keys.append(key['id'])
Document.person_keys = []
for key in settings.CONFIG['itemKeys']:
if key.get('sortType') == 'person':
Document.person_keys.append(key['id'])
class Find(models.Model):
class Meta:
unique_together = ('document', 'key')
document = models.ForeignKey('Document', related_name='find', db_index=True)
key = models.CharField(max_length=200, db_index=True)
value = models.TextField(blank=True, db_index=settings.DB_GIN_TRGM)
def __unicode__(self):
return u'%s=%s' % (self.key, self.value)
'''
Sort
table constructed based on info in settings.CONFIG['documentKeys']
'''
attrs = {
'__module__': 'document.models',
'document': models.OneToOneField('Document', related_name='sort', primary_key=True),
'created': models.DateTimeField(null=True, blank=True, db_index=True),
}
for key in filter(lambda k: k.get('sort', False) or k['type'] in ('integer', 'time', 'float', 'date', 'enum'), settings.CONFIG['documentKeys']):
name = key['id']
sort_type = key.get('sortType', key['type'])
if isinstance(sort_type, list):
sort_type = sort_type[0]
field = get_sort_field(sort_type)
if name not in attrs:
attrs[name] = field[0](**field[1])
Sort = type('Sort', (models.Model,), attrs)
Sort.fields = [f.name for f in Sort._meta.fields]

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 -*-
# vi:si:et:sw=4:sts=4:ts=4
import subprocess
from item.utils import sort_title, sort_string, get_by_id
def pdfpages(pdf):
return int(pdfinfo(pdf).get('pages', '0'))

View file

@ -3,7 +3,9 @@
from __future__ import division, print_function, absolute_import
import os
import re
from glob import glob
import unicodedata
from six import string_types
import ox
@ -13,7 +15,8 @@ from oxdjango.decorators import login_required_json
from oxdjango.http import HttpFileResponse
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response, HttpErrorJson
from django import forms
from django.db.models import Sum
from django.db.models import Count, Sum
from django.conf import settings
from item import utils
from item.models import Item
@ -35,6 +38,13 @@ def get_document_or_404_json(id):
@login_required_json
def addDocument(request, data):
'''
Create new html document
takes {
title: string
}
or
Adds one or more documents to one or more items
takes {
item: string or [string], // one or more item ids (optional)
@ -46,6 +56,14 @@ def addDocument(request, data):
see: editDocument, findDocuments, getDocument, removeDocument, sortDocuments
'''
response = json_response()
if 'title' in data:
doc = models.Document(user=request.user, extension='html')
doc.data['title'] = data['title']
doc.save()
response = json_response(status=200, text='created')
response['data'] = doc.json(user=request.user)
add_changelog(request, data, doc.get_id())
else:
if 'ids' in data:
ids = data['ids']
else:
@ -95,7 +113,8 @@ def editDocument(request, data):
Edits data for a document
takes {
id: string, // document id
name: string, // new document name
key: value, // set new data
description: string // new document description
item: string // item id (optional)
}
@ -126,22 +145,26 @@ actions.register(editDocument, cache=False)
def _order_query(qs, sort, item=None):
prefix = 'sort__'
order_by = []
for e in sort:
operator = e['operator']
if operator != '-':
operator = ''
key = {
'name': 'name_sort',
'description': 'descriptions__description_sort'
if item else 'description_sort',
'dimensions': 'dimensions_sort',
if item else 'description',
'index': 'items__itemproperties__index',
#fixme:
'position': 'id',
'name': 'title',
}.get(e['key'], e['key'])
if key == 'resolution':
order_by.append('%swidth'%operator)
order_by.append('%sheight'%operator)
else:
if '__' not in key:
key = "%s%s" % (prefix, key)
order = '%s%s' % (operator, key)
order_by.append(order)
if order_by:
@ -149,6 +172,24 @@ def _order_query(qs, sort, item=None):
qs = qs.distinct()
return qs
def _order_by_group(query):
if 'sort' in query:
if len(query['sort']) == 1 and query['sort'][0]['key'] == 'items':
order_by = query['sort'][0]['operator'] == '-' and '-items' or 'items'
if query['group'] == "year":
secondary = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue'
order_by = (order_by, secondary)
elif query['group'] != "keyword":
order_by = (order_by, 'sortvalue')
else:
order_by = (order_by, 'value')
else:
order_by = query['sort'][0]['operator'] == '-' and '-sortvalue' or 'sortvalue'
order_by = (order_by, 'items')
else:
order_by = ('-sortvalue', 'items')
return order_by
def get_item(query):
for c in query.get('conditions', []):
if c.get('key') == 'item':
@ -162,7 +203,7 @@ def parse_query(data, user):
for key in ('keys', 'group', 'file', 'range', 'position', 'positions', 'sort'):
if key in data:
query[key] = data[key]
query['qs'] = models.Document.objects.find(data, user).exclude(name='')
query['qs'] = models.Document.objects.find(data, user)
query['item'] = get_item(data.get('query', {}))
return query
@ -192,7 +233,24 @@ def findDocuments(request, data):
#order
qs = _order_query(query['qs'], query['sort'], query['item'])
response = json_response()
if 'keys' in data:
if 'group' in query:
response['data']['items'] = []
items = 'items'
document_qs = query['qs']
order_by = _order_by_group(query)
qs = models.Facet.objects.filter(key=query['group']).filter(document__id__in=document_qs)
qs = qs.values('value').annotate(items=Count('id')).order_by(*order_by)
if 'positions' in query:
response['data']['positions'] = {}
ids = [j['value'] for j in qs]
response['data']['positions'] = utils.get_positions(ids, query['positions'])
elif 'range' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [{'name': i['value'], 'items': i[items]} for i in qs]
else:
response['data']['items'] = qs.count()
elif 'keys' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [l.json(data['keys'], request.user, query['item']) for l in qs]
@ -330,23 +388,15 @@ def upload(request):
if 'chunk' in request.FILES:
if file.editable(request.user):
response = process_chunk(request, file.save_chunk)
response['resultUrl'] = request.build_absolute_uri(file.get_absolute_url())
response['resultUrl'] = file.get_absolute_url()
# id is used to select document in dialog after upload
response['id'] = file.get_id()
return render_to_json_response(response)
#init upload
else:
if not file:
created = False
num = 1
_name = name
while not created:
file, created = models.Document.objects.get_or_create(
user=request.user, name=name, extension=extension)
if not created:
num += 1
name = _name + ' [%d]' % num
file.name = name
file = models.Document(user=request.user, extension=extension)
file.data['title'] = name
file.extension = extension
file.uploading = True
file.save()
@ -361,10 +411,81 @@ def upload(request):
file.width = -1
file.pages = -1
file.save()
upload_url = request.build_absolute_uri('/api/upload/document?id=%s' % file.get_id())
upload_url = '/api/upload/document?id=%s' % file.get_id()
return render_to_json_response({
'uploadUrl': upload_url,
'url': request.build_absolute_uri(file.get_absolute_url()),
'url': file.get_absolute_url(),
'result': 1
})
return render_to_json_response(response)
def autocompleteDocuments(request, data):
'''
Returns autocomplete strings for a given documeny key and search string
takes {
key: string, // document key
value: string, // search string
operator: string, // '=', '==', '^', '$'
query: object, // document query to limit results, see `find`
range: [int, int] // range of results to return
}
returns {
items: [string, ...] // list of matching strings
}
see: autocomplete, autocompleteEntities
'''
if 'range' not in data:
data['range'] = [0, 10]
op = data.get('operator', '=')
key = utils.get_by_id(settings.CONFIG['documentKeys'], data['key'])
order_by = key.get('autocompleteSort', False)
if order_by:
for o in order_by:
if o['operator'] != '-':
o['operator'] = ''
order_by = ['%(operator)ssort__%(key)s' % o for o in order_by]
else:
order_by = ['-items']
qs = parse_query({'query': data.get('query', {})}, request.user)['qs']
response = json_response({})
response['data']['items'] = []
'''
for d in qs:
value = d.json().get(data['key'])
add = False
if value:
if op == '=' and data['value'] in value:
add = True
elif op == '==' and data['value'].lower() == value.lower():
add = True
elif op == '^' and value.lower().startswith(data['value'].lower()):
add = True
if add and value not in response['data']['items']:
response['data']['items'].append(value)
'''
sort_type = key.get('sortType', key.get('type', 'string'))
qs = models.Facet.objects.filter(key=data['key'])
if data['value']:
value = unicodedata.normalize('NFKD', data['value']).lower()
if op == '=':
qs = qs.filter(value__icontains=value)
elif op == '==':
qs = qs.filter(value__iexact=value)
elif op == '^':
qs = qs.filter(value__istartswith=value)
elif op == '$':
qs = qs.filter(value__iendswith=value)
if 'query' in data:
document_query = parse_query({'query': data.get('query', {})}, request.user)['qs']
qs = qs.filter(document__in=document_query)
qs = qs.values('value').annotate(items=Count('id'))
qs = qs.order_by(*order_by)
qs = qs[data['range'][0]:data['range'][1]]
response = json_response({})
response['data']['items'] = [i['value'] for i in qs]
return render_to_json_response(response)
actions.register(autocompleteDocuments)

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:
import entity.models
import document.models
for table, column in (
(models.ItemFind._meta.db_table, 'value'), # Item Find
(models.Clip._meta.db_table, 'findvalue'), # Clip Find
(models.Annotation._meta.db_table, 'findvalue'), # Annotation Find
(entity.models.Find._meta.db_table, 'value'), # Entity Find
(document.models.Find._meta.db_table, 'value'), # Document Find
):
cursor = connection.cursor()
indexes = connection.introspection.get_indexes(cursor, table)

View file

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

View file

@ -24,6 +24,7 @@ from django.utils import datetime_safe
import ox
from oxdjango import fields
from oxdjango.sortmodel import get_sort_field
import ox.web.imdb
import ox.image
@ -1741,27 +1742,9 @@ for key in filter(lambda k: k.get('sort', False) or k['type'] in ('integer', 'ti
sort_type = key.get('sortType', key['type'])
if isinstance(sort_type, list):
sort_type = sort_type[0]
model = {
'char': (models.CharField, dict(null=True, max_length=1000, db_index=True)),
'year': (models.CharField, dict(null=True, max_length=4, db_index=True)),
'integer': (models.BigIntegerField, dict(null=True, blank=True, db_index=True)),
'float': (models.FloatField, dict(null=True, blank=True, db_index=True)),
'date': (models.DateTimeField, dict(null=True, blank=True, db_index=True))
}[{
'layer': 'char',
'string': 'char',
'title': 'char',
'person': 'char',
'year': 'year',
'words': 'integer',
'length': 'integer',
'date': 'date',
'hue': 'float',
'time': 'integer',
'enum': 'integer',
}.get(sort_type, sort_type)]
field = get_sort_field(sort_type)
if name not in attrs:
attrs[name] = model[0](**model[1])
attrs[name] = field[0](**field[1])
ItemSort = type('ItemSort', (models.Model,), attrs)
ItemSort.fields = [f.name for f in ItemSort._meta.fields]

View file

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

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',
'urlalias',
'tv',
'documentcollection',
'document',
'entity',
'websocket',

View file

@ -19,6 +19,7 @@ import oxdjango.api.urls
import app.views
import archive.views
import document.views
import documentcollection.views
import text.views
import user.views
import edit.views
@ -42,6 +43,7 @@ urlpatterns = [
url(r'^file/(?P<oshash>.*)$', archive.views.lookup_file),
url(r'^api/?', include(oxdjango.api.urls)),
url(r'^resetUI$', user.views.reset_ui),
url(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
url(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
url(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3})$', document.views.file),
url(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),

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

View file

@ -46,3 +46,13 @@ def update_numberoflists(username):
).update(
numberoflists=user.lists.count()
)
@task(ignore_results=True, queue='default')
def update_numberofcollections(username):
from . import models
user = models.User.objects.get(username=username)
models.SessionData.objects.filter(
user=user
).update(
numberofcollections=user.collections.count()
)

View file

@ -800,7 +800,10 @@ def setUI(request, data):
access, created = Access.objects.get_or_create(item=item, user=None)
if not created:
access.save()
if data.get('document'):
import document.models
doc = get_object_or_404_json(document.models.Document, id=ox.fromAZ(data['document']))
doc.update_access(request.user)
response = json_response()
return render_to_json_response(response)
actions.register(setUI, cache=False)

View file

@ -28,6 +28,12 @@ pandora.UI = (function() {
pandora.user.ui._findState = pandora.getFindState(
pandora.user.ui.find
);
pandora.user.ui._collection = pandora.getCollectionState(
pandora.user.ui.findDocuments
);
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(
pandora.user.ui.findDocuments
);
Ox.Theme(pandora.user.ui.theme);
pandora.$ui.appPanel.reload();
});
@ -40,6 +46,9 @@ pandora.UI = (function() {
var add = {},
args,
collection,
collectionView,
collectionSettings = pandora.site.collectionSettings,
editSettings = pandora.site.editSettings,
item,
list,
@ -69,6 +78,76 @@ pandora.UI = (function() {
} else if (args.section == 'edits') {
trigger.section = args.section;
trigger.edit = args.edit;
} else if (pandora.user.ui.section == 'documents' || args.section == 'documents') {
if ('findDocuments' in args) {
// the challenge here is that find may change list,
// and list may then change listSort and listView,
// which we don't want to trigger, since find triggers
// (values we put in add will be changed, but won't trigger)
collection = pandora.getCollectionState(args.findDocuments);
pandora.user.ui._collection = collection;
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(args.findDocuments);
if (pandora.$ui.appPanel && !pandora.stayInItemView) {
// if we're not on page load, and if find isn't a context change
// caused by an edit, then switch from item view to list view
args['document'] = '';
}
if (collection != self.previousUI._collection) {
Ox.Log('UI', 'FIND HAS CHANGED COLLECTION', self.previousUI._collection, '>', collection);
// if find has changed collection
Ox.forEach(collectionSettings, function(collectionSetting, setting) {
// then for each setting that corresponds to a collection setting
if (!Ox.isUndefined(args[setting])) {
add[setting] = args[setting];
} else if (
!pandora.user.ui.collections[collection]
|| Ox.isUndefined(pandora.user.ui.collections[collection][collectionSetting])
) {
// either add the default setting
add[setting] = pandora.site.user.ui[setting];
} else {
// or the existing collection setting
add[setting] = pandora.user.ui.collections[collection][collectionSetting];
}
});
}
} else {
collection = self.previousUI._collection;
}
if (!pandora.user.ui.collections[collection]) {
add['collections.' + that.encode(collection)] = {};
}
Ox.forEach(collectionSettings, function(collectionSetting, setting) {
// for each setting that corresponds to a collection setting
// set that collection setting to
var key = 'collections.' + that.encode(collection) + '.' + collectionSetting;
if (setting in args) {
// the setting passed to UI.set
add[key] = args[setting];
} else if (setting in add) {
// or the setting changed via find
add[key] = add[setting];
} else if (!pandora.user.ui.collections[collection]) {
// or the default setting
add[key] = pandora.site.user.ui[setting];
}
});
// set nested lisColumnWidth updates
Ox.forEach(args, function(value, key) {
if (Ox.startsWith(key, 'collectionColumnWidth.')) {
key = 'collections.' + that.encode(collection) + '.columnWidth.'
+ key.slice('collectionColumnWidth.'.length);
if (!(key in add)) {
add[key] = value;
}
}
});
if (args.document) {
// when switching to an item, update list selection
add['collectionSelection'] = [args.document];
add['collections.' + that.encode(collection) + '.selection'] = [args.document];
}
} else {
if ('find' in args) {
// the challenge here is that find may change list,

View file

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

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],
canUploadVideo = pandora.site.capabilities.canAddItems[pandora.user.level],
canAddDocuments = pandora.site.capabilities.canAddDocuments[pandora.user.level],
isSelected = pandora.user.ui._list || pandora.user.ui._collection,
that = Ox.Element()
.addClass('OxSelectableElement' + (pandora.user.ui._list ? '' : ' OxSelected'))
.addClass('OxSelectableElement' + (isSelected ? '' : ' OxSelected'))
.css({
height: '16px',
cursor: 'default',
@ -19,13 +21,21 @@ pandora.ui.allItems = function(section) {
click: function() {
that.gainFocus();
if (section == 'items') {
pandora.user.ui._list && pandora.UI.set({find: {conditions: [], operator: '&'}});
pandora.user.ui._list && pandora.UI.set({
find: {conditions: [], operator: '&'}
});
} else if (section == 'documents') {
pandora.user.ui._collection && pandora.UI.set({
findDocuments: {conditions: [], operator: '&'}
});
} else {
pandora.UI.set(section.slice(0, -1), '');
}
}
})
.bindEvent({
pandora_document: updateSelected,
pandora_finddocuments: updateSelected,
pandora_edit: updateSelected,
pandora_find: updateSelected,
pandora_section: updateSelected,
@ -95,6 +105,56 @@ pandora.ui.allItems = function(section) {
}, function(result) {
that.update(result.data.items);
});
} else if (section == 'documents') {
$items = $('<div>')
.css({
float: 'left',
width: '42px',
margin: '1px 4px 1px 3px',
textAlign: 'right'
})
.appendTo(that);
$buttons[0] = Ox.Button({
style: 'symbol',
title: 'add',
tooltip: canAddDocuments ? Ox._('Add {0}', [Ox._('Document')]) : '',
type: 'image'
})
.css({opacity: canAddDocuments ? 1 : 0.25})
.hide()
.bindEvent({
click: function(data) {
if (canAddDocuments) {
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog({
selected: 'add'
}).open();
}
}
})
.appendTo(that);
$buttons[1] = Ox.Button({
style: 'symbol',
title: 'upload',
tooltip: canAddDocuments ? Ox._('Upload {0}', [Ox._('Document')]) : '',
type: 'image'
})
.css({opacity: canAddDocuments ? 1 : 0.25})
.hide()
.bindEvent({
click: function() {
if (canAddDocuments) {
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog({
selected: 'upload'
}).open();
}
}
})
.appendTo(that);
pandora.api.findDocuments({
query: {conditions: [], operator: '&'}
}, function(result) {
that.update(result.data.items);
});
} else if (section == 'texts') {
$buttons[0] = Ox.Button({
style: 'symbol',
@ -124,6 +184,7 @@ pandora.ui.allItems = function(section) {
function updateSelected() {
that[
(section == 'items' && pandora.user.ui._list)
|| (section == 'documents' && pandora.user.ui._collection)
|| (section == 'edits' && pandora.user.ui.edit)
|| (section == 'texts' && pandora.user.ui.text)
? 'removeClass' : 'addClass'

View file

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

289
static/js/collection.js Normal file
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
? Ox._('Are you sure you want to delete the document "{0}"?', [files[0].name + '.' + files[0].extension])
? Ox._('Are you sure you want to delete the document "{0}"?', [files[0].title + '.' + files[0].extension])
: Ox._('Are you sure you want to delete {0} documents?', [files.length]),
keys: {enter: 'delete', escape: 'keep'},
title: files.length == 1

View file

@ -5,7 +5,7 @@
pandora.ui.deleteListDialog = function(list) {
var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1),
listData = pandora.getListData(list),
$folderList = pandora.$ui.folderList[listData.folder],
@ -42,6 +42,14 @@ pandora.ui.deleteListDialog = function(list) {
pandora.UI.set({
find: pandora.site.user.ui.find
});
} else if (ui.section == 'documents') {
pandora.UI.set(
'collections.' + listData.id, null
);
pandora.UI.set({
findDocuments: pandora.site.user.ui.findDocuments
});
} else {
pandora.UI.set(
folderItem.toLowerCase(), ''

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

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

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) {
section = section || pandora.user.ui.section;
var ui = pandora.user.ui,
folderItems = section == 'items' ? 'Lists' : Ox.toTitleCase(section),
folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1),
that = Ox.Bar({
size: 24

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -253,6 +253,14 @@ pandora.ui.mainMenu = function() {
} else {
that.checkItem('allitems');
}
} else if (ui.section == 'documents') {
if (data.checked) {
pandora.UI.set({
findDocuments: {conditions: [], operator: '&'}
});
} else {
that.checkItem('allitems');
}
} else {
pandora.UI.set(ui.section.slice(0, -1), '');
}
@ -268,6 +276,10 @@ pandora.ui.mainMenu = function() {
} else {
pandora.UI.set({itemSort: [{key: value, operator: pandora.getSortOperator(value)}]});
}
} else if (data.id == 'documentorder') {
pandora.UI.set({collectionSort: [{key: ui.collectionSort[0].key, operator: value == 'ascending' ? '+' : '-'}]});
} else if (data.id == 'documentsort') {
pandora.UI.set({collectionSort: [{key: value, operator: pandora.getDocumentSortOperator(value)}]});
} else if (data.id == 'find') {
if (value) {
pandora.$ui.findSelect.value(value);
@ -351,6 +363,15 @@ pandora.ui.mainMenu = function() {
operator: '&'
}
});
} else if (ui.section == 'documents') {
pandora.UI.set({
findDocuments: {
conditions: data.checked ? [
{key: 'collection', value: data.id.slice(8).replace(/\t/g, '_'), operator: '=='}
] : [],
operator: '&'
}
});
} else {
pandora.UI.set(ui.section.slice(0, -1), data.id.slice(8).replace(/\t/g, '_'));
}
@ -389,7 +410,11 @@ pandora.ui.mainMenu = function() {
} else if (data.id == 'editlist') {
pandora.ui.listDialog().open();
} else if (data.id == 'add') {
if (ui.section == 'documents') {
pandora.$ui.addDocumentDialog = pandora.ui.addDocumentDialog().open();
} else {
pandora.$ui.addItemDialog = pandora.ui.addItemDialog().open();
}
} else if (data.id == 'edit') {
pandora.ui.editItemDialog().open();
} else if (data.id == 'deletelist') {
@ -454,6 +479,13 @@ pandora.ui.mainMenu = function() {
pandora.UI.set({listSelection: items});
pandora.reloadList();
});
} else if (ui.section == 'documents') {
var items = pandora.clipboard.paste('document');
items.length && pandora.doHistory('paste', items, ui._collection, function() {
//fixme:
//pandora.UI.set({listSelection: items});
//pandora.reloadList();
});
} else if (ui.section == 'edits') {
var clips = pandora.clipboard.paste('clip');
clips.length && pandora.doHistory('paste', clips, ui.edit, function(result) {
@ -489,6 +521,26 @@ pandora.ui.mainMenu = function() {
}).open();
});
}
} else if (ui.section == 'documents') {
var files;
if (ui.document) {
files = [pandora.$ui.document.info()];
} else {
files = pandora.$ui.list.options('selected').map(function(id) {
return pandora.$ui.list.value(id);
});
}
pandora.ui.deleteDocumentDialog(
files,
function() {
Ox.Request.clearCache();
if (ui.document) {
pandora.UI.set({document: ''});
} else {
pandora.$ui.list.reloadList()
}
}
).open();
} else if (ui.section == 'edits') {
var clips = pandora.$ui.editPanel.getSelectedClips();
pandora.doHistory('delete', clips, ui.edit, function(result) {
@ -594,6 +646,30 @@ pandora.ui.mainMenu = function() {
pandora.$ui.errorlogsDialog = pandora.ui.errorlogsDialog().open();
}
},
pandora_collectionsort: function(data) {
that.checkItem('sortMenu_sortitems_' + data.value[0].key);
that.checkItem('sortMenu_orderitems_' + (
data.value[0].operator == '+' ? 'ascending' : 'descending')
);
},
pandora_finddocuments: function() {
var action = pandora.getListData().editable ? 'enableItem' : 'disableItem',
list = ui._collection,
previousList = pandora.UI.getPrevious()._collection;
if (list != previousList) {
that.uncheckItem(previousList == '' ? 'allitems' : 'viewlist' + previousList.replace(/_/g, Ox.char(9)));
that.checkItem(list == '' ? 'allitems' : 'viewlist' + list.replace(/_/g, '\t'));
}
that[ui._list ? 'enableItem' : 'disableItem']('duplicatelist');
that[action]('editlist');
that[action]('deletelist');
that[ui.listSelection.length ? 'enableItem' : 'disableItem']('newlistfromselection');
that.replaceMenu('itemMenu', getItemMenu());
that[ui.find.conditions.length ? 'enableItem' : 'disableItem']('clearquery');
that[Ox.sum(ui._filterState.map(function(filterState) {
return filterState.selected.length;
})) > 0 ? 'enableItem' : 'disableItem']('clearfilters');
},
pandora_edit: function() {
var action = pandora.getListData().editable ? 'enableItem' : 'disableItem',
edit = ui.edit,
@ -690,6 +766,14 @@ pandora.ui.mainMenu = function() {
pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem'
]('findsimilar');
},
pandora_collectionselection: function(data) {
var action = data.value.length ? 'enableItem' : 'disableItem';
that[action]('newlistfromselection');
that.replaceMenu('itemMenu', getItemMenu());
that[
pandora.getItemIdAndPosition() ? 'enableItem' : 'disableItem'
]('findsimilar');
},
pandora_listselection: function(data) {
var action = data.value.length ? 'enableItem' : 'disableItem';
that[action]('newlistfromselection');
@ -983,6 +1067,156 @@ pandora.ui.mainMenu = function() {
elements[Ox.mod((index + direction), elements.length)].gainFocus();
}
function getDocumentMenu() {
var listData = pandora.getListData(),
deleteVerb = ui._collection ? Ox._('Remove') : Ox._('Delete'),
isEditable = listData.editable && listData.type == 'static',
isListView = !ui.document,
listName = ui._collection ? Ox._('from List') : Ox._('from Archive'),
listItemsName = 'Documents',
selectionItems = ui.collectionSelection.length,
selectionItemName = (
selectionItems > 1 ? Ox.formatNumber(selectionItems) + ' ' : ''
) + Ox._(selectionItems == 1 ? 'Document' : 'Documents'),
clipboardItems = pandora.clipboard.items('document'),
clipboardItemName = clipboardItems == 0 ? ''
: (
clipboardItems > 1 ? Ox.formatNumber(clipboardItems) + ' ' : ''
) + Ox._(clipboardItems == 1 ? 'Document' : 'Documents'),
canEdit = false, //fixme
canDelete = (
ui.document || ui.collectionSelection.length
) && (
pandora.site.capabilities.canRemoveDocuments[pandora.user.level] ||
ui.collectionSelection.every(function(item) {
return pandora.$ui.list.value(item, 'editable');
})
),
canSelect = isListView,
canCopy = ui.collectionSelection.length,
canCut = canCopy && isEditable,
canPaste = isListView && isEditable,
canAdd = canCopy && clipboardItems > 0,
historyItems = pandora.history.items(),
undoText = pandora.history.undoText(),
redoText = pandora.history.redoText();
return { id: 'itemMenu', title: Ox._('Item'), items: [
{ id: 'add', title: Ox._('Add {0}...', [Ox._('Document')]), disabled: !pandora.site.capabilities.canAddItems[pandora.user.level] },
{ id: 'edit', title: Ox._('Edit {0}...', [Ox._('Document')]), disabled: true /*fixme: !canEdit */ },
{},
{ id: 'selectall', title: Ox._('Select All {0}', [listItemsName]), disabled: !canSelect, keyboard: 'control a' },
{ id: 'selectnone', title: Ox._('Select None'), disabled: !canSelect, keyboard: 'shift control a' },
{ id: 'invertselection', title: Ox._('Invert Selection'), disabled: !canSelect, keyboard: 'alt control a' },
{},
{ id: 'cut', title: Ox._('Cut {0}', [selectionItemName]), disabled: !canCut, keyboard: 'control x' },
{ id: 'cutadd', title: Ox._('Cut and Add to Clipboard'), disabled: !canCut || !canAdd, keyboard: 'shift control x' },
{ id: 'copy', title: Ox._('Copy {0}', [selectionItemName]), disabled: !canCopy, keyboard: 'control c' },
{ id: 'copyadd', title: Ox._('Copy and Add to Clipboard'), disabled: !canCopy || !canAdd, keyboard: 'shift control c' },
{ id: 'paste', title: clipboardItems == 0 ? Ox._('Paste') : Ox._('Paste {0}', [clipboardItemName]), disabled: !canPaste, keyboard: 'control v' },
{ id: 'clearclipboard', title: Ox._('Clear Clipboard'), disabled: !clipboardItems},
{},
{ id: 'delete', title: Ox._('{0} {1} {2}', [deleteVerb, selectionItemName, listName]), disabled: !canDelete, keyboard: 'delete' },
{},
{ id: 'undo', title: undoText ? Ox._('Undo {0}', [undoText]) : Ox._('Undo'), disabled: !undoText, keyboard: 'control z' },
{ id: 'redo', title: redoText ? Ox._('Redo {0}', [redoText]) : Ox._('Redo'), disabled: !redoText, keyboard: 'shift control z' },
{ id: 'clearhistory', title: Ox._('Clear History'), disabled: !historyItems }
] };
}
function getCollectionMenu() {
var itemNamePlural = pandora.getFolderItems(ui.section),
itemNameSingular = itemNamePlural.slice(0, -1),
disableEdit = isGuest || !ui._collection,
disableFromSelection = isGuest || ui.collectionSelection.length == 0;
return { id: 'listMenu', title: Ox._(itemNameSingular == 'Collection' ? 'File' : itemNameSingular), items: [].concat(
{
id: 'allitems',
title: pandora.getAllItemsTitle(),
checked: !ui._collection,
keyboard: 'shift control w'
},
['personal', 'favorite', 'featured'].map(function(folder) {
return {
id: folder + 'lists',
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
items: Ox.isUndefined(lists[folder])
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
: lists[folder].length == 0
? [{id: 'nolists', title: Ox._('No {0} {1}',
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
: lists[folder].map(function(list) {
return {
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
title: Ox.encodeHTMLEntities((
folder == 'favorite' ? list.user + ': ' : ''
) + list.name),
checked: list.id == ui._collection
};
})
};
}),
[
{},
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableFromSelection, keyboard: 'shift control n' },
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
{},
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' },
{},
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' }
]
)};
};
function getEditMenu() {
var itemNameSingular = 'Edit',
itemNamePlural = 'Edits',
disableEdit = isGuest || !ui.edit;
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
{
id: 'allitems',
title: pandora.getAllItemsTitle(),
checked: !ui.edit,
keyboard: 'shift control w'
},
['personal', 'favorite', 'featured'].map(function(folder) {
return {
id: folder + 'lists',
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
items: Ox.isUndefined(lists[folder])
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
: lists[folder].length == 0
? [{id: 'nolists', title: Ox._('No {0} {1}',
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
: lists[folder].map(function(list) {
return {
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
title: Ox.encodeHTMLEntities((
folder == 'favorite' ? list.user + ': ' : ''
) + list.name),
checked: list.id == ui.edit
};
})
};
}),
[
{},
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableEdit, keyboard: 'shift control n' },
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
{},
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' }
]
)};
}
function getFindMenu() {
return { id: 'findMenu', title: Ox._('Find'), items: [
{ id: 'find', title: Ox._('Find'), items: [
@ -1005,7 +1239,62 @@ pandora.ui.mainMenu = function() {
] };
}
function getItemListMenu() {
var itemNameSingular = 'List',
itemNamePlural = 'Lists',
disableEdit = isGuest || !ui._list,
disableFromSelection = isGuest || ui.listSelection.length == 0;
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
{
id: 'allitems',
title: pandora.getAllItemsTitle(),
checked: !ui._list,
keyboard: 'shift control w'
},
['personal', 'favorite', 'featured'].map(function(folder) {
return {
id: folder + 'lists',
title: Ox._(Ox.toTitleCase(folder) + ' ' + itemNamePlural),
items: Ox.isUndefined(lists[folder])
? [{id: 'loading', title: Ox._('Loading...'), disabled: true}]
: lists[folder].length == 0
? [{id: 'nolists', title: Ox._('No {0} {1}',
[Ox._(Ox.toTitleCase(folder)), Ox._(itemNamePlural)]), disabled: true}]
: lists[folder].map(function(list) {
return {
id: 'viewlist' + list.id.replace(/_/g, Ox.char(9)),
title: Ox.encodeHTMLEntities((
folder == 'favorite' ? list.user + ': ' : ''
) + list.name),
checked: list.id == ui._list
};
})
};
}),
[
{},
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: disableFromSelection, keyboard: 'shift control n' },
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
{ id: 'neweditfromselection', title: Ox._('New Edit from Selection'), disabled: disableFromSelection },
{ id: 'newsmarteditfromresults', title: Ox._('New Smart Edit from Results'), disabled: isGuest },
{},
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: disableEdit, keyboard: 'control d' },
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'control e' },
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: disableEdit, keyboard: 'delete' },
{},
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' },
{ id: 'tv', title: Ox._('TV'), keyboard: 'control space' }
]
)};
};
function getItemMenu() {
if (ui.section == 'documents') {
return getDocumentMenu();
}
var listData = pandora.getListData(),
deleteVerb = ui._list ? Ox._('Remove') : Ox._('Delete'),
isEditable = listData.editable && listData.type == 'static',
@ -1021,7 +1310,7 @@ pandora.ui.mainMenu = function() {
&& ui.editView != 'annotations', // FIXME: focus
listName = isVideoView || isClipView ? ''
: ui.section == 'items' ? (
ui._list ? Ox._('from List') : Ox._('from Archive')
ui._? Ox._('from List') : Ox._('from Archive')
)
: Ox._('from Edit'),
listItemsName = Ox._(
@ -1057,15 +1346,18 @@ pandora.ui.mainMenu = function() {
)
) && pandora.$ui.list.value(ui.listSelection[0], 'editable')
),
canDelete = pandora.site.capabilities.canRemoveItems[pandora.user.level] || (
canDelete = (
ui.section == 'items' && (
ui.item || (
Ox.contains(['list', 'grid', 'clips', 'timelines'], ui.listView)
&& ui.listSelection.length
)
) && ui.listSelection.every(function(item) {
) && (
pandora.site.capabilities.canRemoveItems[pandora.user.level] ||
ui.listSelection.every(function(item) {
return pandora.$ui.list.value(item, 'editable');
})
)
),
canSelect = isListView || isClipView || isEditView,
canCopy = isListView ? ui.listSelection.length
@ -1106,15 +1398,22 @@ pandora.ui.mainMenu = function() {
}
function getListMenu() {
var itemNameSingular = ui.section == 'items' ? 'List' : ui.section == 'edits' ? 'Edit' : 'Text',
itemNamePlural = ui.section == 'items' ? 'Lists' : ui.section == 'edits' ? 'Edits' : 'Texts';
return ({
items: getItemListMenu,
documents: getCollectionMenu,
edits: getEditMenu,
texts: getTextMenu
}[ui.section])();
}
function getTextMenu() {
var itemNameSingular = 'Text',
itemNamePlural = 'Texts';
return { id: 'listMenu', title: Ox._(itemNameSingular), items: [].concat(
{
id: 'allitems',
title: pandora.getAllItemsTitle(),
checked: ui.section == 'items' ? !ui.item && !ui._list
: ui.section == 'edits' ? !ui.edit
: !ui.text,
checked: !ui.text,
keyboard: 'shift control w'
},
['personal', 'favorite', 'featured'].map(function(folder) {
@ -1132,9 +1431,7 @@ pandora.ui.mainMenu = function() {
title: Ox.encodeHTMLEntities((
folder == 'favorite' ? list.user + ': ' : ''
) + list.name),
checked: ui.section == 'items' ? list.id == ui._list
: ui.section == 'edits' ? list.id == ui.edit
: list.id == ui.text
checked: list.id == ui.text
};
})
};
@ -1142,38 +1439,47 @@ pandora.ui.mainMenu = function() {
[
{},
{ id: 'newlist', title: Ox._('New ' + itemNameSingular), disabled: isGuest, keyboard: 'control n' },
],
ui.section == 'items' ? [
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: isGuest || ui.listSelection.length == 0, keyboard: 'shift control n' },
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' },
{ id: 'newsmartlistfromresults', title: Ox._('New Smart ' + itemNameSingular + ' from Results'), disabled: isGuest, keyboard: 'shift alt control n' },
{ id: 'neweditfromselection', title: Ox._('New Edit from Selection'), disabled: isGuest || ui.listSelection.length == 0 },
{ id: 'newsmarteditfromresults', title: Ox._('New Smart Edit from Results'), disabled: isGuest }
] : ui.section == 'edits' ? [
{ id: 'newlistfromselection', title: Ox._('New ' + itemNameSingular + ' from Selection'), disabled: isGuest || !ui.edit, keyboard: 'shift control n' },
{ id: 'newsmartlist', title: Ox._('New Smart ' + itemNameSingular), disabled: isGuest, keyboard: 'alt control n' }
] : [
{ id: 'newpdf', title: Ox._('New PDF'), disabled: isGuest, keyboard: 'alt control n' },
],
[
{}
],
ui.section != 'texts' ? [
{ id: 'duplicatelist', title: Ox._('Duplicate Selected ' + itemNameSingular), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'control d' }
] : [],
[
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'control e' },
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: isGuest || (ui.section == 'items' && !ui._list) || (ui.section == 'edits' && !ui.edit), keyboard: 'delete' }
],
ui.section == 'items' ? [
{},
{ id: 'print', title: Ox._('Print'), keyboard: 'control p' },
{ id: 'tv', title: Ox._('TV'), keyboard: 'control space' }
] : []
{ id: 'editlist', title: Ox._('Edit Selected ' + itemNameSingular + '...'), disabled: isGuest, keyboard: 'control e' },
{ id: 'deletelist', title: Ox._('Delete Selected ' + itemNameSingular + '...'), disabled: isGuest, keyboard: 'delete' }
]
)};
};
}
function getCollectionSortMenu() {
var isClipView = false,
clipItems = [].concat(!ui.document ? pandora.site.documentSortKeys.map(function(key) {
return Ox.extend({
checked: ui.collectionSort[0].key == key.id
}, key);
}) : []);
return { id: 'sortMenu', title: Ox._('Sort'), items: [
{ id: 'sortitems', title: Ox._('Sort {0} by', [Ox._('Documents')]), disabled: ui.document, items: [
{ group: 'documentsort', min: 1, max: 1, items: pandora.site.documentSortKeys.map(function(key) {
return Ox.extend({
checked: ui.collectionSort[0].key == key.id
}, key, {
title: Ox._(key.title)
});
}) }
] },
{ id: 'orderitems', title: Ox._('Order {0}', [Ox._('Documents')]), disabled: ui.document, items: [
{ group: 'documentorder', min: 1, max: 1, items: [
{ id: 'ascending', title: Ox._('Ascending'), checked: (ui.collectionSort[0].operator || pandora.getSortOperator(ui.collectionSort[0].key)) == '+' },
{ id: 'descending', title: Ox._('Descending'), checked: (ui.collectionSort[0].operator || pandora.getSortOperator(ui.collectionSort[0].key)) == '-' }
]}
] },
{ id: 'advancedsort', title: Ox._('Advanced Sort...'), keyboard: 'shift control s', disabled: true },
] };
}
function getSortMenu() {
if (ui.section == 'documents') {
return getCollectionSortMenu();
}
//fixme split items/clips menu
var isClipView = pandora.isClipView(),
clipItems = (isClipView ? pandora.site.clipKeys.map(function(key) {
return Ox.extend(Ox.clone(key), {

View file

@ -21,6 +21,17 @@ pandora.ui.mainPanel = function() {
orientation: 'horizontal'
})
.bindEvent({
pandora_finddocuments: function() {
var previousUI = pandora.UI.getPrevious();
if (!previousUI.document && ui._list == previousUI._list) {
that.replaceElement(1, pandora.$ui.documentPanel = pandora.ui.documentPanel());
}
},
pandora_document: function(data) {
if (!data.value || !data.previousValue) {
that.replaceElement(1, pandora.$ui.documentPanel = pandora.ui.documentPanel());
}
},
pandora_edit: function(data) {
that.replaceElement(1, pandora.$ui.editPanel = pandora.ui.editPanel());
},
@ -92,6 +103,7 @@ pandora.ui.mainPanel = function() {
function getRightPanel() {
return ui.section == 'items' ? pandora.$ui.rightPanel = pandora.ui.rightPanel()
: ui.section == 'edits' ? pandora.$ui.editPanel = pandora.ui.editPanel()
: ui.section == 'documents' ? pandora.$ui.documentPanel = pandora.ui.documentPanel()
: pandora.$ui.textPanel = pandora.ui.textPanel();
}
return that;

View file

@ -351,7 +351,11 @@ appPanel
findKeys: data.site.itemKeys.filter(function(key) {
return key.find;
}),
documentFindKeys: data.site.documentKeys.filter(function(key) {
return key.find;
}),
itemsSection: pandora.site.itemName.plural.toLowerCase(),
listSections: ['items', 'documents'],
map: data.site.layers.some(function(layer) {
return layer.type == 'place'
}) ? 'manual' : data.site.layers.some(function(layer) {
@ -364,6 +368,11 @@ appPanel
{id: 'featured', title: 'Featured Lists', showBrowser: false},
{id: 'volumes', title: 'Local Volumes'}
],
documents: [
{id: 'personal', title: 'Personal Collections'},
{id: 'favorite', title: 'Favorite Collections', showBrowser: false},
{id: 'featured', title: 'Featured Collections', showBrowser: false}
],
edits: [
{id: 'personal', title: 'Personal Edits'},
{id: 'favorite', title: 'Favorite Edits', showBrowser: false},
@ -375,7 +384,12 @@ appPanel
{id: 'featured', title: 'Featured Texts', showBrowser: false}
]
},
sortKeys: pandora.getSortKeys()
sortKeys: pandora.getSortKeys(),
documentSortKeys: pandora.getDocumentSortKeys(),
collectionViews: [
{id: 'list', title: Ox._('View as List')},
{id: 'grid', title: Ox._('View as Grid')}
]
});
pandora.site.listSettings = {};
Ox.forEach(pandora.site.user.ui, function(val, key) {
@ -383,6 +397,12 @@ appPanel
pandora.site.listSettings[key] = key[4].toLowerCase()+ key.slice(5);
}
});
pandora.site.collectionSettings = {};
Ox.forEach(pandora.site.user.ui, function(val, key) {
if (/^collection[A-Z]/.test(key)) {
pandora.site.collectionSettings[key] = key[10].toLowerCase()+ key.slice(11);
}
});
pandora.site.editSettings = {
clip: '',
'in': 0,

View file

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

View file

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

View file

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

View file

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

View file

@ -40,6 +40,9 @@ pandora.addFolderItem = function(section) {
if (!isSmart) {
if (isItems) {
data.items = ui.listSelection;
} else if (section == 'documents') {
//fixme
data.items = ui.collectionSelection;
} else {
data.clips = pandora.getClipData(
ui.section == 'items'
@ -66,7 +69,11 @@ pandora.addFolderItem = function(section) {
if (data.type == 'smart') {
data.query = listData.query;
}
pandora.api[isItems ? 'findLists' : 'findEdits']({
pandora.api[{
items: 'findLists',
documents: 'findCollections',
edits: 'findEdits',
}[section]]({
query: {conditions: [{
key: 'id',
operator: '==',
@ -103,6 +110,9 @@ pandora.addFolderItem = function(section) {
addList();
}
});
} else if(section == 'documents') {
//fixme
addList();
} else {
pandora.api.getEdit({
id: list,
@ -127,7 +137,11 @@ pandora.addFolderItem = function(section) {
});
}
function addList() {
pandora.api[isItems ? 'addList' : 'addEdit'](data, function(result) {
pandora.api[{
items: 'addList',
documents: 'addCollection',
edits: 'addEdit'
}[section]](data, function(result) {
getPosterFrames(result.data.id);
});
}
@ -136,23 +150,41 @@ pandora.addFolderItem = function(section) {
sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes')
? 'votes' : 'timesaccessed';
if (!isDuplicate) {
(isItems ? Ox.noop : pandora.api.getEdit)({
({
items: Ox.noop,
documents: Ox.noop,
edits: pandora.api.getEdit
}[section])({
id: newList,
keys: ['clips']
}, function(result) {
query = isItems ? {
conditions: [{key: 'list', value: newList, operator: '=='}],
if (Ox.contains(pandora.site.listSections, section)) {
query = {
conditions: [{
key: section == 'documents' ? 'collection' : 'list',
value: newList, operator: '=='
}],
operator: '&'
} : {
};
} else{
query = {
conditions: Ox.unique(result.data.clips.map(function(clip) {
return {key: 'id', value: clip.item, operator: '=='};
})),
operator: '|'
};
(isItems ? pandora.api.find : Ox.noop)({
}
({
items: pandora.api.find,
documents: pandora.api.findDocuments,
edits: Ox.noop
}[section])({
query: {
conditions: [
{key: 'list', value: newList, operator: '=='}
{
key: section == 'documents' ? 'collection' : 'list',
value: newList, operator: '=='
}
],
operator: '&'
},
@ -181,7 +213,11 @@ pandora.addFolderItem = function(section) {
});
});
} else {
pandora.api[isItems ? 'findLists' : 'findEdits']({
pandora.api[{
items: 'findLists',
documents: 'findCollections',
edits: 'findEdits'
}[section]]({
query: {
conditions: [{key: 'id', value: list, operator: '=='}],
operator: '&'
@ -193,7 +229,11 @@ pandora.addFolderItem = function(section) {
}
}
function setPosterFrames(newList, posterFrames) {
pandora.api[isItems ? 'editList' : 'editEdit']({
pandora.api[{
items: 'editList',
documents: 'editCollection',
edits: 'editEdit'
}[section]]({
id: newList,
posterFrames: posterFrames
}, function() {
@ -206,22 +246,37 @@ pandora.addFolderItem = function(section) {
// (same applies to addText, below)
$folderList = pandora.$ui.folderList.personal;
pandora.$ui.folder[0].options({collapsed: false});
Ox.Request.clearCache(isItems ? 'findLists' : 'findEdits');
Ox.Request.clearCache({
items: 'findLists',
documents: 'findCollections',
edits: 'findEdits'
}[section]);
$folderList.bindEventOnce({
load: function() {
$folderList.gainFocus()
.options({selected: [newList]})
.editCell(newList, 'name', true);
pandora.UI.set(isItems ? {
pandora.UI.set({
items: {
find: {
conditions: [
{key: 'list', value: newList, operator: '=='}
],
operator: '&'
}
} : {
},
documents: {
findDocuments: {
conditions: [
{key: 'collection', value: newList, operator: '=='}
],
operator: '&'
}
},
edits: {
edit: newList
});
}
}[section]);
}
}).reloadList();
}
@ -229,7 +284,7 @@ pandora.addFolderItem = function(section) {
pandora.addList = function() {
// addList(isSmart, isFrom) or addList(list) [=duplicate]
pandora.addFolderItem.apply(null, ['items'].concat(Ox.slice(arguments)));
pandora.addFolderItem.apply(null, [pandora.user.ui.section].concat(Ox.slice(arguments)));
};
pandora.addText = function(options) {
@ -271,8 +326,7 @@ pandora.beforeUnloadWindow = function() {
pandora.changeFolderItemStatus = function(id, status, callback) {
var ui = pandora.user.ui,
folderItems = ui.section == 'items'
? 'Lists' : Ox.toTitleCase(ui.section),
folderItems = pandora.getFolderItems(ui.section),
folderItem = folderItems.slice(0, -1);
if (status == 'private') {
pandora.api['find' + folderItems]({
@ -496,6 +550,30 @@ pandora.createLinks = function($element) {
callback(null, []);
}
});
} else if (type == 'documents') {
//fixme
pandora.api.findDocuments({
query: {
conditions: [{key: 'collection', operator: '==', value: target}],
operator: '&'
},
positions: items
}, function(result) {
var existingItems = Object.keys(result.data.positions),
addedItems = items.filter(function(item) {
return !Ox.contains(existingItems, item);
});
if (addedItems.length) {
pandora.api.addCollectionItems({
items: addedItems,
collection: target
}, function(result) {
callback(result, addedItems);
});
} else {
callback(null, []);
}
});
} else {
pandora.api.addClips({
clips: pandora.getClipData(items),
@ -540,6 +618,31 @@ pandora.createLinks = function($element) {
// FIXME: Why is this timeout needed?
setTimeout(pandora.reloadList, 250);
}
} else if (type == 'document' && ui.section == 'documents') {
Ox.Request.clearCache('findDocuments');
object.targets.filter(function(list) {
return list != ui._list;
}).forEach(function(list) {
listData = pandora.getListData(list);
pandora.api.findDocuments({
query: {
conditions: [{
key: 'collection',
operator: '==',
value: list
}],
operator: '&'
}
}, function(result) {
pandora.$ui.folderList[listData.folder].value(
list, 'items', result.data.items
);
});
});
if (Ox.contains(object.targets, ui._list)) {
// FIXME: Why is this timeout needed?
setTimeout(pandora.reloadList, 250);
}
} else if (type == 'clip' && ui.section == 'edits') {
// FIXME: update edit list (once it has item count)
if (Ox.contains(object.targets, ui.edit)) {
@ -743,6 +846,36 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
});
drag.action == 'move' && pandora.reloadList();
});
} else if (section == 'documents') {
var targets = drag.action == 'copy' ? drag.target.id
: [pandora.user.ui._collection, drag.target.id];
//fixme use history
//pandora.doHistory(drag.action, drag.ids, targets, function() {
pandora.api.addCollectionItems({
collection: drag.target.id,
items: drag.ids
}, function() {
Ox.Request.clearCache('find');
pandora.api.findDocuments({
query: {
conditions: [{
key: 'collection',
operator: '==',
value: drag.target.id
}],
operator: '&'
}
}, function(result) {
var folder = drag.target.status != 'featured'
? 'personal' : 'featured';
pandora.$ui.folderList[folder].value(
drag.target.id, 'items', result.data.items
);
cleanup(250);
});
drag.action == 'move' && pandora.reloadList();
});
} else if (section == 'edits') {
var targets = drag.action == 'copy' ? drag.target.id
: [pandora.user.ui.edit, drag.target.id];
@ -751,6 +884,9 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
pandora.$ui.editPanel && pandora.$ui.editPanel.updatePanel();
cleanup(250);
});
} else {
Ox.print('no drop support for', section);
cleanup(250);
}
}
} else {
@ -781,13 +917,20 @@ pandora.enableDragAndDrop = function($list, canMove, section, getItems) {
itemName = section == 'items' ? {
plural: Ox._(pandora.site.itemName.plural.toLowerCase()),
singular: Ox._(pandora.site.itemName.singular.toLowerCase())
} : {
} : section == 'documents' ? {
plural: Ox._('Documents'),
singular: Ox._('Document')
} :{
plural: Ox._('clips'),
singular: Ox._('clip')
},
targetName = section == 'items' ? {
plural: Ox._('lists'),
singular: Ox._('list')
} : section == 'documents' ? {
plural: Ox._('collections'),
singular: Ox._('collection')
} : {
plural: Ox._('edits'),
singular: Ox._('edit')
@ -946,11 +1089,36 @@ pandora.exitFullscreen = function() {
}
};
pandora.formatDocumentKey = function(key, data) {
var value;
if (key.format) {
value = (
/^color/.test(key.format.type.toLowerCase()) ? Ox.Theme : Ox
)['format' + Ox.toTitleCase(key.format.type)].apply(
this, [data[key.id]].concat(key.format.args || [])
);
if (key.id == 'rightslevel') {
value.css({width: size * 0.75 + 'px'});
}
} else {
value = data[key.id];
if (key.id == 'extension') {
value = value.toUpperCase();
} else if (key.id == 'dimensions') {
value = Ox.isArray(value)
? Ox.formatDimensions(value, 'px')
: Ox.formatCount(value, data.extension == 'html' ? 'word' : 'page');
}
}
return value;
}
pandora.getAllItemsTitle = function(section) {
section = section || pandora.user.ui.section;
return section == 'items'
? Ox._('All {0}', [Ox._(pandora.site.itemName.plural)])
: Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]);
return {
items: Ox._('All {0}', [Ox._(pandora.site.itemName.plural)]),
documents: Ox._('All {0}', [Ox._('Documents')])
}[section] || Ox._('{0} ' + Ox.toTitleCase(section), [pandora.site.site.name]);
};
pandora.getClipData = function(items) {
@ -1068,14 +1236,20 @@ pandora.getClipVideos = function(clip, resolution) {
};
(function() {
var itemTitles = {};
pandora.getDocumentTitle = function(itemData) {
var itemTitles = {}, documentTitles = {};
pandora.getWindowTitle = function(itemData) {
var parts = [];
if (itemData) {
if (pandora.user.ui.section == 'documents') {
documentTitles[pandora.user.ui.document] = Ox.decodeHTMLEntities(
pandora.getDocumentTitle(itemData)
);
} else {
itemTitles[pandora.user.ui.item] = Ox.decodeHTMLEntities(
pandora.getItemTitle(itemData)
);
}
}
if (pandora.user.ui.section == 'items') {
if (!pandora.user.ui.item) {
parts.push(
@ -1094,16 +1268,33 @@ pandora.getClipVideos = function(clip, resolution) {
Ox._(Ox.toTitleCase(pandora.user.ui.itemView))
]));
}
} else if (pandora.user.ui.section == 'documents') {
if (!pandora.user.ui.document) {
parts.push(
pandora.user.ui._collection
? pandora.user.ui._collection.split(':').slice(1).join(':')
: pandora.getAllItemsTitle('documents')
);
parts.push(Ox._('{0} View', [
Ox._(Ox.toTitleCase(pandora.user.ui.collectionView))
]));
parts.push(Ox._('Documents'));
} else {
parts.push(
documentTitles[pandora.user.ui.document] || pandora.user.ui.document
);
/*
parts.push(Ox._('{0} View', [
Ox._(Ox.toTitleCase(pandora.user.ui.documentView))
]));
*/
parts.push(Ox._('Document'));
}
} else if (pandora.user.ui.section == 'edits') {
if (pandora.user.ui.edit) {
parts.push(pandora.user.ui.edit.split(':').slice(1).join(':'));
}
parts.push(Ox._('Edits'));
} else if (pandora.user.ui.section == 'texts') {
if (pandora.user.ui.text) {
parts.push(pandora.user.ui.text.split(':').slice(1).join(':'));
}
parts.push(Ox._('Texts'));
}
parts.push(pandora.site.site.name);
return parts.join(' ');
@ -1138,6 +1329,12 @@ pandora.getFilterSizes = function() {
);
};
pandora.getFolderItems = function(section) {
return section == 'items' ? 'Lists'
: section == 'documents' ? 'Collections'
: Ox.toTitleCase(section);
}
pandora.getFoldersHeight = function(section) {
section = section || pandora.user.ui.section;
var height = 0;
@ -1299,6 +1496,16 @@ pandora.getItem = function(state, str, callback) {
});
}
});
} else if (state.type == 'documents') {
pandora.api.getDocument({id: str, keys: ['id']}, function(result) {
if (result.status.code == 200) {
state.item = result.data.id;
callback();
} else {
state.item = '';
callback();
}
});
} else if (state.type == 'edits') {
pandora.api.getEdit({id: str, keys: ['id']}, function(result) {
if (result.status.code == 200) {
@ -1310,21 +1517,23 @@ pandora.getItem = function(state, str, callback) {
}
});
} else if (state.type == 'texts') {
pandora.api.getText({
id: str,
keys: ['id', 'names', 'pages', 'type']
pandora.api.findDocuments({
query: {
conditions: [
{key: 'user', value: str.split(':')[0]},
{key: 'title', value: str.split(':').slice(1).join(':')}
],
operator: '&'},
keys: ['id', 'extension'],
range: [0, 2]
}, function(result) {
if (result.status.code == 200) {
state.item = result.data.id;
callback();
state.type = 'documents';
if (result.data.items.length == 1) {
state.item = result.data.items[0].id;
} else {
// FIXME: add findText call here?
// FIXME: it's obscure that in the texts case,
// we have to set item to '', while for videos,
// it remains undefined
state.item = '';
callback();
}
callback();
});
} else {
callback();
@ -1471,12 +1680,19 @@ pandora.getLargeEditTimelineURL = function(edit, type, i, callback) {
};
pandora.getListData = function(list) {
var data = {}, folder;
var data = {}, folder, _list = pandora.user.ui._list;
if (Ox.isUndefined(list)) {
list = pandora.user.ui[
pandora.user.ui.section == 'items' ? '_list'
: pandora.user.ui.section.slice(0, -1)
];
if (pandora.user.ui.section == 'items') {
list = pandora.user.ui._list;
} else if (pandora.user.ui.section == 'documents') {
list = pandora.user.ui._collection;
_list = pandora.user.ui._collection;
} else {
list = pandora.user.ui.section.slice(0, -1)
}
}
if (pandora.user.ui.section == 'documents') {
_list = pandora.user.ui._collection;
}
if (list && pandora.$ui.folderList) {
Ox.forEach(pandora.$ui.folderList, function($list, id) {
@ -1485,7 +1701,7 @@ pandora.getListData = function(list) {
// folder it is selected, since for example, a personal
// list may appear again in the featured lists browser
if (
(list == pandora.user.ui._list && $list.options('selected').length)
(list == _list && $list.options('selected').length)
|| !Ox.isEmpty($list.value(list))
) {
folder = id;
@ -1506,6 +1722,16 @@ pandora.getListData = function(list) {
return data;
};
pandora.getListIcon = function(section, id, size, modified) {
var folderItems = pandora.getFolderItems(section),
folderItem = folderItems.slice(0, -1);
size = size || '';
modified = modified || Ox.uid();
return pandora.getMediaURL('/'
+ folderItem.toLowerCase() + '/'
+ encodeURIComponent(id) + '/icon' + size + '.jpg?' + modified);
};
pandora.getPageTitle = function(stateOrURL) {
var pages = [
{id: '', title: ''},
@ -1529,6 +1755,7 @@ pandora.getPageTitle = function(stateOrURL) {
};
pandora.getPart = function(state, str, callback) {
Ox.Log('URL', 'getPart', state, str);
if (state.page == 'api') {
pandora.api.api(function(result) {
if (Ox.contains(Object.keys(result.data.actions), str)) {
@ -1656,6 +1883,8 @@ pandora.getSort = function(state, val, callback) {
// TODO in the future: If str is index, fall back if list is smart
// (but this can only be tested after find has been parsed)
callback();
} else if (state.type == 'documents') {
callback();
} else if (state.type == 'edits') {
if (val[0].key == 'index') {
pandora.api.getEdit({id: state.item, keys: ['id', 'type']}, function(result) {
@ -1675,8 +1904,6 @@ pandora.getSort = function(state, val, callback) {
} else {
callback();
}
} else if (state.type == 'texts') {
callback();
}
};
@ -1705,6 +1932,29 @@ pandora.getSortOperator = function(key) {
) > -1 ? '+' : '-';
};
pandora.getDocumentSortKeys = function() {
return pandora.site.documentKeys.filter(function(key) {
return key.sort && (
!key.capability
|| pandora.site.capabilities[key.capability][pandora.user.level]
);
}).map(function(key) {
return Ox.extend(key, {
operator: pandora.getDocumentSortOperator(key.id)
});
});
};
pandora.getDocumentSortOperator = function(key) {
var data = Ox.getObjectById(pandora.site.documentKeys, key);
return data.sortOperator || ['string', 'text'].indexOf(
Ox.isArray(data.type) ? data.type[0] : data.type
) > -1 ? '+' : '-';
};
pandora.getDocumentTitle = function(data) {
return data.title || Ox._('Untitled');
};
pandora.getSpan = function(state, val, callback) {
// For a given item, or none (state.item), and a given view, or any
// (state.view), this takes a value (array of numbers or string) and checks
@ -1712,9 +1962,10 @@ pandora.getSpan = function(state, val, callback) {
// event/place name (string), and in that case sets state.span, and may
// modify state.view.
// fixme: "subtitles:23" is still missing
if (state.page == 'documents') {
Ox.Log('URL', 'getSpan', state, val);
if (state.type == 'documents') {
pandora.api.getDocument({
id: state.part,
id: state.item,
keys: ['dimensions', 'extension']
}, function(result) {
var dimensions = result.data.dimensions,
@ -1722,6 +1973,8 @@ pandora.getSpan = function(state, val, callback) {
values;
if (Ox.contains(['epub', 'pdf', 'txt'], extension)) {
state.span = Ox.limit(parseInt(val), 1, dimensions);
} else if (Ox.contains(['html'], extension)) {
state.span = Ox.limit(parseInt(val), 0, 100);
} else if (Ox.contains(['gif', 'jpg', 'png'], extension)) {
values = val.split(',');
if (values.length == 4) {
@ -1738,6 +1991,7 @@ pandora.getSpan = function(state, val, callback) {
state.span = '';
}
}
Ox.Log('URL', 'getSpan result', state);
callback();
});
} else if (state.type == pandora.site.itemName.plural.toLowerCase()) {
@ -1817,24 +2071,6 @@ pandora.getSpan = function(state, val, callback) {
callback();
});
}
} else if (state.type == 'texts') {
pandora.api.getText({id: state.item}, function(result) {
if (isArray) {
if (result.data.type == 'html') {
state.span = Ox.limit(val[0], 0, 100);
} else {
state.span = Math.floor(
Ox.limit(val[0], 1, result.data.pages)
);
}
} else if (
result.data.type == 'html'
&& Ox.contains(result.data.names, val)
) {
state.span = val;
}
callback();
});
}
function getId(type, callback) {
@ -1898,6 +2134,9 @@ pandora.getStatusText = function(data) {
data.items == 1 ? 'singular' : 'plural'
]),
parts = [];
if (ui.section == 'documents') {
itemName = Ox._(Ox.toTitleCase(data.items == 1 ? ui.section.slice(0, -1) : ui.section));
}
parts.push(Ox.formatNumber(data.items) + ' '+ itemName);
if (data.runtime) {
parts.push(Ox.formatDuration(data.runtime, 'short'));
@ -2263,6 +2502,8 @@ pandora.signin = function(data) {
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
pandora.user.ui._filterState = pandora.getFilterState(pandora.user.ui.find);
pandora.user.ui._findState = pandora.getFindState(pandora.user.ui.find);
pandora.user.ui._collection = pandora.getCollectionState(pandora.user.ui.findDocuments);
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(pandora.user.ui.findDocuments);
pandora.site.sortKeys = pandora.getSortKeys();
pandora.URL.init();
pandora.URL.update();
@ -2277,6 +2518,8 @@ pandora.signout = function(data) {
pandora.user.ui._list = pandora.getListState(pandora.user.ui.find);
pandora.user.ui._filterState = pandora.getFilterState(pandora.user.ui.find);
pandora.user.ui._findState = pandora.getFindState(pandora.user.ui.find);
pandora.user.ui._collection = pandora.getCollectionState(pandora.user.ui.findDocuments);
pandora.user.ui._findDocumentsState = pandora.getFindDocumentsState(pandora.user.ui.findDocuments);
pandora.site.sortKeys = pandora.getSortKeys();
pandora.URL.init();
pandora.URL.update();
@ -2288,9 +2531,11 @@ pandora.reloadList = function() {
Ox.Log('', 'reloadList')
var listData = pandora.getListData();
Ox.Request.clearCache(); // fixme: remove
if (pandora.$ui.filters) {
pandora.$ui.filters.forEach(function($filter) {
$filter.reloadList();
});
}
pandora.$ui.list
.bindEvent({
init: function(data) {
@ -2336,6 +2581,20 @@ pandora.renameList = function(oldId, newId, newName, folder) {
}
}, false);
pandora.UI.set('lists.' + pandora.UI.encode(oldId), null, false);
} else if (pandora.user.ui.section == 'documents') {
pandora.replaceURL = true;
pandora.UI.set(
'collections.' + pandora.UI.encode(newId),
pandora.user.ui.lists[oldId],
false
);
pandora.UI.set({
findDocuments: {
conditions: [{key: 'collection', value: newId, operator: '=='}],
operator: '&'
}
}, false);
pandora.UI.set('collections.' + pandora.UI.encode(oldId), null, false);
} else {
pandora.replaceURL = true;
pandora.UI.set(
@ -2380,7 +2639,7 @@ pandora.resizeFolders = function(section) {
userColumnWidth = Math.round(columnWidth * 0.4),
nameColumnWidth = columnWidth - userColumnWidth;
pandora.$ui.allItems && pandora.$ui.allItems.resizeElement((
section == 'items' ? columnWidth
Ox.contains(pandora.site.listSections, section) ? columnWidth
: section == 'edits' ? width - 16
: width - 48
) - 8);
@ -2489,6 +2748,12 @@ pandora.resizeWindow = function() {
pandora.$ui.calendar.resizeCalendar();
}
}
} else if (pandora.user.ui.section == 'documents') {
if (pandora.user.ui.document) {
pandora.$ui.document && pandora.$ui.document.update();
} else {
pandora.$ui.list && pandora.$ui.list.size();
}
} else if (pandora.user.ui.section == 'edits') {
if (!pandora.user.ui.edit) {
// ...
@ -2537,6 +2802,38 @@ pandora.selectList = function() {
}
});
}
} else if (pandora.user.ui.section == 'documents') {
if (pandora.user.ui._collection) {
pandora.api.findCollections({
keys: ['status', 'user'],
query: {
conditions: [{
key: 'id',
operator: '==',
value: pandora.user.ui._collection
}],
operator: ''
},
range: [0, 1]
}, function(result) {
var folder, list;
if (result.data.items.length) {
list = result.data.items[0];
folder = list.status == 'featured' ? 'featured' : (
list.user == pandora.user.username
? 'personal' : 'favorite'
);
pandora.$ui.folderList[folder]
.options({selected: [pandora.user.ui._collection]});
if (
!pandora.hasDialogOrScreen()
&& !Ox.Focus.focusedElementIsInput()
) {
pandora.$ui.folderList[folder].gainFocus();
}
}
});
}
} else {
var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)],
section = Ox.toTitleCase(pandora.user.ui.section.slice(0, -1));
@ -2891,18 +3188,76 @@ pandora.wait = function(id, callback, timeout) {
}
return state;
};
pandora.getListState = function(find) {
// A list is selected if exactly one condition in an & query has "list"
// as key and "==" as operator
function getState(find, key) {
var index, state = '';
if (find.operator == '&') {
index = oneCondition(find.conditions, 'list', '==');
index = oneCondition(find.conditions, key, '==');
if (index > -1) {
state = find.conditions[index].value;
}
}
return state;
}
pandora.getCollectionState = function(find) {
// A collection is selected if exactly one condition in an & query has "collection"
// as key and "==" as operator
return getState(find, 'collection');
};
pandora.getListState = function(find) {
// A list is selected if exactly one condition in an & query has "list"
// as key and "==" as operator
return getState(find, 'list');
};
pandora.getFindDocumentsState = function(find) {
// The find element is populated if exactly one condition in an & query
// has a findKey as key and "=" as operator (and all other conditions
// are either list or filters), or if all conditions in an | query have
// the same filter id as key and "==" as operator
Ox.Log('Find', 'getFindDocumentsState', find)
var conditions, indices, state = {index: -1, key: '*', value: ''};
if (find.operator == '&') {
// number of conditions that are not list or filters
conditions = find.conditions.length
- !!pandora.user.ui._collection;
/*
- pandora.user.ui._filterState.filter(function(filter) {
return filter.index > -1;
}).length;
*/
// indices of non-advanced find queries
indices = pandora.site.documentKeys.map(function(findKey) {
return oneCondition(find.conditions, findKey.id, '=');
}).filter(function(index) {
return index > -1;
});
state = conditions == 1 && indices.length == 1 ? {
index: indices[0],
key: find.conditions[indices[0]].key,
value: Ox.decodeURIComponent(find.conditions[indices[0]].value)
} : {
index: -1,
key: conditions == 0 && indices.length == 0 ? '*' : 'advanced',
value: ''
};
} else {
state = {
index: -1,
key: 'advanced',
value: ''
};
/*
Ox.forEach(pandora.user.ui.documentFilters, function(key) {
if (everyCondition(find.conditions, key, '==')) {
state.key = '*';
return false;
}
});
*/
}
return state;
};
}());

BIN
static/png/cover.png Normal file

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')
update_service('pandora-encoding')
update_service('pandora-tasks')
if old <= 5673:
run('./pandora/manage.py', 'rebuild_documentfind')
else:
if len(sys.argv) == 1:
release = get_release()