Compare commits

...

41 commits

Author SHA1 Message Date
j
7f31998254 add 0p for audio only profile 2021-04-01 11:30:28 +02:00
j
20b3831862 don't fail if getsize fails on tmp file 2021-03-29 11:03:12 +02:00
j
4689af2050 replace all not just first 2021-03-19 10:19:29 +01:00
j
7ea1f38a0f check body 2021-03-19 10:19:19 +01:00
j
7d8b38e627 cast list values to str, just in case 2021-02-05 10:07:14 +01:00
j
b377956a13 add option to add/remove values to listkeys in batchedit dialog 2020-11-26 20:23:10 +01:00
j
e7df18f727 typo 2020-11-08 23:26:16 +01:00
j
1bba157f7f pass referer to get_info 2020-11-08 22:54:49 +01:00
j
41fce072a3 pass referer in more places 2020-11-08 22:43:47 +01:00
j
4cf28434eb pass referer to video download 2020-11-08 22:29:24 +01:00
j
5d6e753b05 DEA.L. and DEAIL. work for aac 2020-10-17 09:47:07 +02:00
j
f157ebf2c4 honor _blank 2020-10-15 11:46:16 +02:00
j
ac8c01ee98 add menu item to toggle icon size 2020-09-29 11:03:39 +02:00
j
f13f7d238c still load config 2020-09-27 18:02:23 +02:00
j
63d82ad7e7 remove config reloader 2020-09-27 17:58:35 +02:00
j
7ab789f80a docker fixes 2020-09-27 17:53:37 +02:00
j
213adcaaaa update requirements 2020-09-27 17:19:02 +02:00
j
fdac35c00d update django, sync docker install 2020-09-27 13:03:45 +02:00
j
03e85c6eef double check 2020-09-25 12:49:26 +02:00
j
09f9580e1e use new id after create 2020-09-22 14:49:44 +02:00
j
2dfe0f3ff2 queue [add|edit]Annotation calls
addAnnotation was called multiple times creating multiple annotations
editAnnotation calls might overwrite later calls depending on response
time.
2020-09-22 12:49:23 +02:00
j
5bbb6ca195 support ipv6 2020-09-21 15:03:27 +02:00
j
2779d8d099 change password in case user already existed 2020-09-19 14:49:44 +02:00
j
d00cf08638 move buld item metadata edits to tasks too 2020-09-13 16:41:34 +02:00
j
945ac98dad fix info in item documents panel 2020-09-13 11:06:22 +02:00
j
5024a2ba0c display lists in collection list view 2020-09-11 14:31:50 +02:00
j
940632369a list view, load keys 2020-09-11 14:16:43 +02:00
j
1c0462393c add to current item 2020-09-11 14:07:08 +02:00
j
40edf9dd4a typo 2020-09-11 13:57:54 +02:00
j
ca7741f92c run bulk update as task 2020-09-11 13:55:01 +02:00
j
2d8a3f24dc can see > direct upload 2020-08-29 23:54:19 +02:00
j
d795e40344 fix username 2020-08-29 23:37:59 +02:00
j
ffb512a304 add oshash to changelog, keep upload filename, show direct upload instance 2020-08-29 23:32:57 +02:00
j
800725d54c add more collumn state 2020-08-24 01:05:42 +02:00
j
80597790f9 no title placeholder 2020-08-18 10:17:14 +02:00
j
638dfc8bb3 get all ids 2020-07-21 15:26:55 +02:00
j
e4815f091d move rights level code into infoViewUtils 2020-07-21 14:51:31 +02:00
j
4bde77abcc rebuild posters 2020-07-21 14:51:01 +02:00
j
953cb93745 load editable in batch dialog 2020-07-21 14:23:08 +02:00
j
19da7ca26d delete items from archive while in list 2020-07-21 13:12:31 +02:00
j
8788dd9fe8 pandoractl installs itself 2020-06-07 17:19:46 +02:00
51 changed files with 717 additions and 505 deletions

7
ctl
View file

@ -17,7 +17,7 @@ if [ "$action" = "init" ]; then
SUDO="" SUDO=""
PANDORA_USER=`ls -l update.py | cut -f3 -d" "` PANDORA_USER=`ls -l update.py | cut -f3 -d" "`
if [ `whoami` != $PANDORA_USER ]; then if [ `whoami` != $PANDORA_USER ]; then
SUDO="sudo -H -u $PANDORA_USER" SUDO="sudo -E -H -u $PANDORA_USER"
fi fi
$SUDO python3 -m venv --system-site-packages . $SUDO python3 -m venv --system-site-packages .
branch=`cat .git/HEAD | sed 's@/@\n@g' | tail -n1` branch=`cat .git/HEAD | sed 's@/@\n@g' | tail -n1`
@ -62,11 +62,10 @@ if [ ! -z $cmd ]; then
SUDO="" SUDO=""
PANDORA_USER=`ls -l update.py | cut -f3 -d" "` PANDORA_USER=`ls -l update.py | cut -f3 -d" "`
if [ `whoami` != $PANDORA_USER ]; then if [ `whoami` != $PANDORA_USER ]; then
SUDO="sudo -H -u $PANDORA_USER" SUDO="sudo -E -H -u $PANDORA_USER"
fi fi
shift shift
$SUDO "$BASE/$cmd" $@ exec $SUDO "$BASE/$cmd" $@
exit $?
fi fi
if [ `whoami` != 'root' ]; then if [ `whoami` != 'root' ]; then

View file

@ -30,6 +30,8 @@ apt-get update -qq
apt-get install -y \ apt-get install -y \
netcat-openbsd \ netcat-openbsd \
sudo \ sudo \
rsync \
iproute2 \
vim \ vim \
wget \ wget \
pwgen \ pwgen \
@ -47,17 +49,20 @@ apt-get install -y \
python3-cssselect \ python3-cssselect \
python3-html5lib \ python3-html5lib \
python3-ox \ python3-ox \
python3-elasticsearch \
oxframe \ oxframe \
ffmpeg \ ffmpeg \
mkvtoolnix \ mkvtoolnix \
gpac \ gpac \
imagemagick \ imagemagick \
poppler-utils \ poppler-utils \
youtube-dl \
ipython3 \ ipython3 \
tesseract-ocr \
tesseract-ocr-eng \
postfix \ postfix \
postgresql-client postgresql-client
apt-get install -y --no-install-recommends youtube-dl rtmpdump
apt-get clean apt-get clean
rm -f /install.sh rm -f /install.sh

View file

@ -8,6 +8,8 @@ export LANG=en_US.UTF-8
mkdir -p /run/pandora mkdir -p /run/pandora
chown -R ${user}.${user} /run/pandora chown -R ${user}.${user} /run/pandora
update="/usr/bin/sudo -u $user -E -H /srv/pandora/update.py"
# pan.do/ra services # pan.do/ra services
if [ "$action" = "pandora" ]; then if [ "$action" = "pandora" ]; then
if [ ! -e /srv/pandora/initialized ]; then if [ ! -e /srv/pandora/initialized ]; then
@ -26,12 +28,12 @@ if [ "$action" = "pandora" ]; then
/overlay/install.py /overlay/install.py
echo "Initializing database..." echo "Initializing database..."
echo "CREATE EXTENSION pg_trgm;" | /srv/pandora/pandora/manage.py dbshell echo "CREATE EXTENSION pg_trgm;" | /srv/pandora/pandora/manage.py dbshell || true
/srv/pandora/pandora/manage.py init_db /srv/pandora/pandora/manage.py init_db
/srv/pandora/update.py db $update db
echo "Generating static files..." echo "Generating static files..."
/srv/pandora/update.py static
chown -R ${user}.${user} /srv/pandora/ chown -R ${user}.${user} /srv/pandora/
$update static
touch /srv/pandora/initialized touch /srv/pandora/initialized
fi fi
/srv/pandora_base/docker/wait-for db 5432 /srv/pandora_base/docker/wait-for db 5432
@ -44,54 +46,53 @@ if [ "$action" = "encoding" ]; then
/srv/pandora_base/docker/wait-for-file /srv/pandora/initialized /srv/pandora_base/docker/wait-for-file /srv/pandora/initialized
/srv/pandora_base/docker/wait-for rabbitmq 5672 /srv/pandora_base/docker/wait-for rabbitmq 5672
name=pandora-encoding-$(hostname) name=pandora-encoding-$(hostname)
cd /srv/pandora/pandora
exec /usr/bin/sudo -u $user -E -H \ exec /usr/bin/sudo -u $user -E -H \
/srv/pandora/bin/python \ /srv/pandora/bin/celery \
/srv/pandora/pandora/manage.py \ -A app worker \
celery worker \ -Q encoding -n ${name} \
-c 1 \ --pidfile /run/pandora/encoding.pid \
-Q encoding -n $name \ --maxtasksperchild 500 \
-l INFO -c 1 \
-l INFO
fi fi
if [ "$action" = "tasks" ]; then if [ "$action" = "tasks" ]; then
/srv/pandora_base/docker/wait-for-file /srv/pandora/initialized /srv/pandora_base/docker/wait-for-file /srv/pandora/initialized
/srv/pandora_base/docker/wait-for rabbitmq 5672 /srv/pandora_base/docker/wait-for rabbitmq 5672
name=pandora-default-$(hostname) name=pandora-default-$(hostname)
cd /srv/pandora/pandora
exec /usr/bin/sudo -u $user -E -H \ exec /usr/bin/sudo -u $user -E -H \
/srv/pandora/bin/python \ /srv/pandora/bin/celery \
/srv/pandora/pandora/manage.py \ -A app worker \
celery worker \ -Q default,celery -n ${name} \
-Q default,celery -n $name \ --pidfile /run/pandora/tasks.pid \
--maxtasksperchild 1000 \ --maxtasksperchild 1000 \
-l INFO -l INFO
fi fi
if [ "$action" = "cron" ]; then if [ "$action" = "cron" ]; then
/srv/pandora_base/docker/wait-for-file /srv/pandora/initialized /srv/pandora_base/docker/wait-for-file /srv/pandora/initialized
/srv/pandora_base/docker/wait-for rabbitmq 5672 /srv/pandora_base/docker/wait-for rabbitmq 5672
cd /srv/pandora/pandora
exec /usr/bin/sudo -u $user -E -H \ exec /usr/bin/sudo -u $user -E -H \
/srv/pandora/bin/python \ /srv/pandora/bin/celery \
/srv/pandora/pandora/manage.py \ -A app beat \
celerybeat -s /run/pandora/celerybeat-schedule \ -s /run/pandora/celerybeat-schedule \
--pidfile /run/pandora/cron.pid \ --pidfile /run/pandora/cron.pid \
-l INFO -l INFO
fi fi
if [ "$action" = "websocketd" ]; then if [ "$action" = "websocketd" ]; then
/srv/pandora_base/docker/wait-for-file /srv/pandora/initialized /srv/pandora_base/docker/wait-for-file /srv/pandora/initialized
/srv/pandora_base/docker/wait-for rabbitmq 5672 /srv/pandora_base/docker/wait-for rabbitmq 5672
cd /srv/pandora/pandora
exec /usr/bin/sudo -u $user -E -H \ exec /usr/bin/sudo -u $user -E -H \
/srv/pandora/bin/python \ /srv/pandora/bin/python \
/srv/pandora/pandora/manage.py websocketd /srv/pandora/pandora/manage.py websocketd
fi fi
# pan.do/ra management and update # pan.do/ra management and update
if [ "$action" = "manage.py" ]; then if [ "$action" = "ctl" ]; then
shift shift
exec /usr/bin/sudo -u $user -E -H \ exec /srv/pandora/ctl "$@"
/srv/pandora/pandora/manage.py "$@"
fi
if [ "$action" = "update.py" ]; then
shift
exec /usr/bin/sudo -u $user -E -H \
/srv/pandora/update.py "$@"
fi fi
if [ "$action" = "bash" ]; then if [ "$action" = "bash" ]; then
shift shift

View file

@ -56,13 +56,9 @@ cp /srv/pandora/docker/entrypoint.sh /entrypoint.sh
mv /srv/pandora/ /srv/pandora_base/ mv /srv/pandora/ /srv/pandora_base/
mkdir /pandora mkdir /pandora
ln -s /pandora /srv/pandora ln -s /pandora /srv/pandora
cat > /usr/local/bin/update.py << EOF
#!/bin/sh
exec /srv/pandora/update.py \$@
EOF
cat > /usr/local/bin/manage.py << EOF cat > /usr/local/bin/pandoractl << EOF
#!/bin/sh #!/bin/sh
exec /srv/pandora/pandora/manage.py \$@ exec /srv/pandora/ctl \$@
EOF EOF
chmod +x /usr/local/bin/manage.py /usr/local/bin/update.py chmod +x /usr/local/bin/pandoractl

12
docker/publish.sh Normal file
View file

@ -0,0 +1,12 @@
#!/bin/bash
# push new version of pan.do/ra to docker hub
set -e
cd /tmp
git clone https://code.0x2620.org/0x2620/pandora
cd pandora
./docker/build.sh
docker push 0x2620/pandora-base:latest
docker push 0x2620/pandora-nginx:latest
docker push 0x2620/pandora:latest

View file

@ -26,7 +26,7 @@ and to get started run this:
To update pan.do/ra run: To update pan.do/ra run:
docker-compose run pandora update.py docker-compose run pandora ctl update
EOF EOF
touch __init__.py touch __init__.py

View file

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
TIMEOUT=60 TIMEOUT=180
TARGET="$1" TARGET="$1"
for i in `seq $TIMEOUT` ; do for i in `seq $TIMEOUT` ; do

View file

@ -24,9 +24,6 @@ User = get_user_model()
_win = (sys.platform == "win32") _win = (sys.platform == "win32")
RUN_RELOADER = True
NOTIFIER = None
def get_version(): def get_version():
git_dir = join(dirname(dirname(dirname(__file__))), '.git') git_dir = join(dirname(dirname(dirname(__file__))), '.git')
if exists(git_dir): if exists(git_dir):
@ -257,46 +254,6 @@ check the README for further details.
except: except:
pass pass
def reloader_thread():
global NOTIFIER
settings.RELOADER_RUNNING=True
_config_mtime = 0
try:
import pyinotify
INOTIFY = True
except:
INOTIFY = False
if INOTIFY:
def add_watch():
name = os.path.realpath(settings.SITE_CONFIG)
wm.add_watch(name, pyinotify.IN_CLOSE_WRITE, reload_config)
def reload_config(event):
load_config()
add_watch()
wm = pyinotify.WatchManager()
add_watch()
notifier = pyinotify.Notifier(wm)
NOTIFIER = notifier
notifier.loop()
else:
while RUN_RELOADER:
try:
stat = os.stat(settings.SITE_CONFIG)
mtime = stat.st_mtime
if _win:
mtime -= stat.st_ctime
if mtime > _config_mtime:
load_config()
_config_mtime = mtime
time.sleep(10)
except:
#sys.stderr.write("reloading config failed\n")
pass
def update_static(): def update_static():
oxjs_build = os.path.join(settings.STATIC_ROOT, 'oxjs/tools/build/build.py') oxjs_build = os.path.join(settings.STATIC_ROOT, 'oxjs/tools/build/build.py')
if os.path.exists(oxjs_build): if os.path.exists(oxjs_build):
@ -407,10 +364,7 @@ def update_geoip(force=False):
print('failed to download GeoLite2-City.mmdb') print('failed to download GeoLite2-City.mmdb')
def init(): def init():
if not settings.RELOADER_RUNNING: load_config(True)
load_config(True)
if settings.RELOAD_CONFIG:
thread.start_new_thread(reloader_thread, ())
def shutdown(): def shutdown():
if settings.RELOADER_RUNNING: if settings.RELOADER_RUNNING:

View file

@ -11,6 +11,8 @@ def run(cmd):
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
if p.returncode != 0: if p.returncode != 0:
print('failed to run:', cmd)
print(stdout)
print(stderr) print(stderr)
sys.exit(1) sys.exit(1)

View file

@ -36,8 +36,10 @@ info_key_map = {
'display_id': 'id', 'display_id': 'id',
} }
def get_info(url): def get_info(url, referer=None):
cmd = ['youtube-dl', '-j', '--all-subs', url] cmd = ['youtube-dl', '-j', '--all-subs', url]
if referer:
cmd += ['--referer', referer]
p = subprocess.Popen(cmd, p = subprocess.Popen(cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True) stderr=subprocess.PIPE, close_fds=True)
@ -57,6 +59,8 @@ def get_info(url):
info[-1]['tags'] = [] info[-1]['tags'] = []
if 'upload_date' in i and i['upload_date']: if 'upload_date' in i and i['upload_date']:
info[-1]['date'] = '-'.join([i['upload_date'][:4], i['upload_date'][4:6], i['upload_date'][6:]]) info[-1]['date'] = '-'.join([i['upload_date'][:4], i['upload_date'][4:6], i['upload_date'][6:]])
if 'referer' not in info[-1]:
info[-1]['referer'] = url
return info return info
def add_subtitles(item, media, tmp): def add_subtitles(item, media, tmp):
@ -84,9 +88,9 @@ def add_subtitles(item, media, tmp):
sub.selected = True sub.selected = True
sub.save() sub.save()
def download(item_id, url): def download(item_id, url, referer=None):
item = Item.objects.get(public_id=item_id) item = Item.objects.get(public_id=item_id)
info = get_info(url) info = get_info(url, referer)
if not len(info): if not len(info):
return '%s contains no videos' % url return '%s contains no videos' % url
media = info[0] media = info[0]
@ -96,7 +100,12 @@ def download(item_id, url):
tmp = tmp.decode('utf-8') tmp = tmp.decode('utf-8')
os.chdir(tmp) os.chdir(tmp)
cmd = ['youtube-dl', '-q', media['url']] cmd = ['youtube-dl', '-q', media['url']]
if settings.CONFIG['video'].get('reuseUload', False): if referer:
cmd += ['--referer', referer]
elif 'referer' in media:
cmd += ['--referer', media['referer']]
if settings.CONFIG['video'].get('reuseUpload', False):
max_resolution = max(settings.CONFIG['video']['resolutions']) max_resolution = max(settings.CONFIG['video']['resolutions'])
format = settings.CONFIG['video']['formats'][0] format = settings.CONFIG['video']['formats'][0]
if format == 'mp4': if format == 'mp4':

View file

@ -2,13 +2,13 @@
import os import os
from os.path import exists from os.path import exists
import fractions import fractions
import math
import re
import shutil
import subprocess import subprocess
import tempfile import tempfile
import time import time
import math
import shutil
from distutils.spawn import find_executable from distutils.spawn import find_executable
from glob import glob from glob import glob
@ -63,8 +63,8 @@ def supported_formats():
'webm': 'libvpx' in stdout and 'libvorbis' in stdout, 'webm': 'libvpx' in stdout and 'libvorbis' in stdout,
'vp8': 'libvpx' in stdout and 'libvorbis' in stdout, 'vp8': 'libvpx' in stdout and 'libvorbis' in stdout,
'vp9': 'libvpx-vp9' in stdout and 'libopus' in stdout, 'vp9': 'libvpx-vp9' in stdout and 'libopus' in stdout,
'mp4': 'libx264' in stdout and 'DEA.L. aac' in stdout, 'mp4': 'libx264' in stdout and bool(re.compile('DEA.L. aac').findall(stdout)),
'h264': 'libx264' in stdout and 'DEA.L. aac' in stdout, 'h264': 'libx264' in stdout and bool(re.compile('DEA.L. aac').findall(stdout)),
} }
def stream(video, target, profile, info, audio_track=0, flags={}): def stream(video, target, profile, info, audio_track=0, flags={}):
@ -145,6 +145,13 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
audioquality = -1 audioquality = -1
audiobitrate = '22k' audiobitrate = '22k'
audiochannels = 1 audiochannels = 1
elif profile == '0p':
info['video'] = []
audiorate = 48000
audioquality = 6
audiobitrate = None
audiochannels = None
audio_codec = 'libopus'
else: else:
height = 96 height = 96

View file

@ -336,7 +336,9 @@ class File(models.Model):
def done_cb(): def done_cb():
if done: if done:
self.info.update(ox.avinfo(self.data.path)) info = ox.avinfo(self.data.path)
del info['path']
self.info.update(info)
self.parse_info() self.parse_info()
# reject invalid uploads # reject invalid uploads
if self.info.get('oshash') != self.oshash: if self.info.get('oshash') != self.oshash:
@ -481,6 +483,13 @@ class File(models.Model):
user.is_staff or \ user.is_staff or \
self.item.user == user or \ self.item.user == user or \
self.item.groups.filter(id__in=user.groups.all()).count() > 0 self.item.groups.filter(id__in=user.groups.all()).count() > 0
if 'instances' in data and 'filename' in self.info and self.data:
data['instances'].append({
'ignore': False,
'path': self.info['filename'],
'user': self.item.user.username if self.item and self.item.user else 'system',
'volume': 'Direct Upload'
})
if not can_see_media: if not can_see_media:
if 'instances' in data: if 'instances' in data:
data['instances'] = [] data['instances'] = []

View file

@ -200,8 +200,8 @@ def update_stream(id):
c.save() c.save()
@task(queue="encoding") @task(queue="encoding")
def download_media(item_id, url): def download_media(item_id, url, referer=None):
return external.download(item_id, url) return external.download(item_id, url, referer)
@task(queue='default') @task(queue='default')
def move_media(data, user): def move_media(data, user):

View file

@ -195,7 +195,9 @@ def addMedia(request, data):
response['data']['item'] = f.item.public_id response['data']['item'] = f.item.public_id
response['data']['itemUrl'] = request.build_absolute_uri('/%s' % f.item.public_id) response['data']['itemUrl'] = request.build_absolute_uri('/%s' % f.item.public_id)
if not f.available: if not f.available:
add_changelog(request, data, f.item.public_id) changelog_data = data.copy()
changelog_data['oshash'] = oshash
add_changelog(request, changelog_data, f.item.public_id)
else: else:
if 'item' in data: if 'item' in data:
i = Item.objects.get(public_id=data['item']) i = Item.objects.get(public_id=data['item'])
@ -220,11 +222,15 @@ def addMedia(request, data):
if 'info' in data and data['info'] and isinstance(data['info'], dict): if 'info' in data and data['info'] and isinstance(data['info'], dict):
f.info = data['info'] f.info = data['info']
f.info['extension'] = extension f.info['extension'] = extension
if 'filename' in data:
f.info['filename'] = data['filename']
f.parse_info() f.parse_info()
f.save() f.save()
response['data']['item'] = i.public_id response['data']['item'] = i.public_id
response['data']['itemUrl'] = request.build_absolute_uri('/%s' % i.public_id) response['data']['itemUrl'] = request.build_absolute_uri('/%s' % i.public_id)
add_changelog(request, data, i.public_id) changelog_data = data.copy()
changelog_data['oshash'] = oshash
add_changelog(request, changelog_data, i.public_id)
return render_to_json_response(response) return render_to_json_response(response)
actions.register(addMedia, cache=False) actions.register(addMedia, cache=False)
@ -739,6 +745,7 @@ def addMediaUrl(request, data):
takes { takes {
url: string, // url url: string, // url
referer: string // optional referer url
item: string // item item: string // item
} }
returns { returns {
@ -751,7 +758,7 @@ def addMediaUrl(request, data):
response = json_response() response = json_response()
i = Item.objects.get(public_id=data['item']) i = Item.objects.get(public_id=data['item'])
Task.start(i, request.user) Task.start(i, request.user)
t = tasks.download_media.delay(data['item'], data['url']) t = tasks.download_media.delay(data['item'], data['url'], data.get('referer'))
response['data']['taskId'] = t.task_id response['data']['taskId'] = t.task_id
add_changelog(request, data, data['item']) add_changelog(request, data, data['item'])
return render_to_json_response(response) return render_to_json_response(response)

View file

@ -5,7 +5,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models

View file

@ -5,7 +5,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- import ox
from celery.task import task from celery.task import task
@task(queue="encoding") @task(queue="encoding")
@ -6,3 +6,20 @@ def extract_fulltext(id):
from . import models from . import models
d = models.Document.objects.get(id=id) d = models.Document.objects.get(id=id)
d.update_fulltext() d.update_fulltext()
@task(queue='default')
def bulk_edit(data, username):
from django.db import transaction
from . import models
from item.models import Item
user = models.User.objects.get(username=username)
item = 'item' in data and Item.objects.get(public_id=data['item']) or None
documents = models.Document.objects.filter(pk__in=map(ox.fromAZ, data['id']))
for document in documents:
if document.editable(user, item):
with transaction.atomic():
document.refresh_from_db()
document.edit(data, user, item)
document.save()
return {}

View file

@ -23,6 +23,7 @@ from archive.chunk import process_chunk
from changelog.models import add_changelog from changelog.models import add_changelog
from . import models from . import models
from . import tasks
def get_document_or_404_json(request, id): def get_document_or_404_json(request, id):
response = {'status': {'code': 404, response = {'status': {'code': 404,
@ -131,13 +132,13 @@ def editDocument(request, data):
item = 'item' in data and Item.objects.get(public_id=data['item']) or None item = 'item' in data and Item.objects.get(public_id=data['item']) or None
if data['id']: if data['id']:
if isinstance(data['id'], list): if isinstance(data['id'], list):
documents = models.Document.objects.filter(pk__in=map(ox.fromAZ, data['id'])) add_changelog(request, data)
t = tasks.bulk_edit.delay(data, request.user.username)
response['data']['taskId'] = t.task_id
else: else:
documents = [models.Document.get(data['id'])] document = models.Document.get(data['id'])
for document in documents:
if document.editable(request.user, item): if document.editable(request.user, item):
if document == documents[0]: add_changelog(request, data)
add_changelog(request, data)
document.edit(data, request.user, item) document.edit(data, request.user, item)
document.save() document.save()
response['data'] = document.json(user=request.user, item=item) response['data'] = document.json(user=request.user, item=item)

View file

@ -4,7 +4,6 @@ from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models

View file

@ -6,7 +6,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models
import clip.models import clip.models

View file

@ -5,7 +5,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models
import clip.models import clip.models

View file

@ -5,7 +5,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models
import clip.models import clip.models

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import os
from glob import glob
from django.core.management.base import BaseCommand
import app.monkey_patch
from ... import models
from ... import tasks
class Command(BaseCommand):
"""
rebuild posters for all items.
"""
help = 'rebuild all posters for all items.'
args = ''
def handle(self, **options):
offset = 0
chunk = 100
count = models.Item.objects.count()
while offset <= count:
for i in models.Item.objects.all().order_by('id')[offset:offset+chunk]:
print(i)
if i.poster:
i.poster.delete()
i.make_poster()
offset += chunk

View file

@ -6,7 +6,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models

View file

@ -5,7 +5,6 @@ from django.core.management.base import BaseCommand
from django.db import connection, transaction from django.db import connection, transaction
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models

View file

@ -5,7 +5,6 @@ from django.db import connection, transaction
from django.db.models import fields from django.db.models import fields
from django.conf import settings from django.conf import settings
settings.RELOAD_CONFIG = False
import app.monkey_patch import app.monkey_patch
from ... import models from ... import models
import clip.models import clip.models

View file

@ -1022,7 +1022,7 @@ class Item(models.Model):
elif sort_type == 'string': elif sort_type == 'string':
value = self.get(source, '') value = self.get(source, '')
if isinstance(value, list): if isinstance(value, list):
value = ','.join(value) value = ','.join([str(v) for v in value])
value = utils.sort_string(value)[:955] value = utils.sort_string(value)[:955]
set_value(s, name, value) set_value(s, name, value)
elif sort_type == 'words': elif sort_type == 'words':

View file

@ -5,6 +5,7 @@ from urllib.parse import quote
import gzip import gzip
import os import os
import random import random
import logging
from celery.task import task, periodic_task from celery.task import task, periodic_task
from django.conf import settings from django.conf import settings
@ -16,6 +17,9 @@ from app.utils import limit_rate
from taskqueue.models import Task from taskqueue.models import Task
logger = logging.getLogger(__name__)
@periodic_task(run_every=timedelta(days=1), queue='encoding') @periodic_task(run_every=timedelta(days=1), queue='encoding')
def cronjob(**kwargs): def cronjob(**kwargs):
if limit_rate('item.tasks.cronjob', 8 * 60 * 60): if limit_rate('item.tasks.cronjob', 8 * 60 * 60):
@ -350,3 +354,18 @@ def update_sitemap(base_url):
f.write(data) f.write(data)
with gzip.open(sitemap, 'wb') as f: with gzip.open(sitemap, 'wb') as f:
f.write(data) f.write(data)
@task(queue='default')
def bulk_edit(data, username):
from django.db import transaction
from . import models
from .views import edit_item
user = models.User.objects.get(username=username)
items = models.Item.objects.filter(public_id__in=data['id'])
for item in items:
if item.editable(user):
with transaction.atomic():
item.refresh_from_db()
response = edit_item(user, item, data)
return {}

View file

@ -533,17 +533,18 @@ def get(request, data):
return render_to_json_response(response) return render_to_json_response(response)
actions.register(get) actions.register(get)
def edit_item(request, item, data): def edit_item(user, item, data):
data = data.copy()
update_clips = False update_clips = False
response = json_response(status=200, text='ok') response = json_response(status=200, text='ok')
if 'rightslevel' in data: if 'rightslevel' in data:
if request.user.profile.capability('canEditRightsLevel'): if user.profile.capability('canEditRightsLevel'):
item.level = int(data['rightslevel']) item.level = int(data['rightslevel'])
else: else:
response = json_response(status=403, text='permission denied') response = json_response(status=403, text='permission denied')
del data['rightslevel'] del data['rightslevel']
if 'user' in data: if 'user' in data:
if request.user.profile.get_level() in ('admin', 'staff') and \ if user.profile.get_level() in ('admin', 'staff') and \
models.User.objects.filter(username=data['user']).exists(): models.User.objects.filter(username=data['user']).exists():
new_user = models.User.objects.get(username=data['user']) new_user = models.User.objects.get(username=data['user'])
if new_user != item.user: if new_user != item.user:
@ -551,10 +552,10 @@ def edit_item(request, item, data):
update_clips = True update_clips = True
del data['user'] del data['user']
if 'groups' in data: if 'groups' in data:
if not request.user.profile.capability('canManageUsers'): if not user.profile.capability('canManageUsers'):
# Users wihtout canManageUsers can only add/remove groups they are not in # Users wihtout canManageUsers can only add/remove groups they are not in
groups = set([g.name for g in item.groups.all()]) groups = set([g.name for g in item.groups.all()])
user_groups = set([g.name for g in request.user.groups.all()]) user_groups = set([g.name for g in user.groups.all()])
other_groups = list(groups - user_groups) other_groups = list(groups - user_groups)
data['groups'] = [g for g in data['groups'] if g in user_groups] + other_groups data['groups'] = [g for g in data['groups'] if g in user_groups] + other_groups
r = item.edit(data) r = item.edit(data)
@ -597,7 +598,7 @@ def add(request, data):
i.make_poster() i.make_poster()
del data['title'] del data['title']
if data: if data:
response = edit_item(request, item, data) response = edit_item(request.user, item, data)
response['data'] = item.json() response['data'] = item.json()
add_changelog(request, request_data, item.public_id) add_changelog(request, request_data, item.public_id)
return render_to_json_response(response) return render_to_json_response(response)
@ -619,16 +620,16 @@ def edit(request, data):
see: add, find, get, lookup, remove, upload see: add, find, get, lookup, remove, upload
''' '''
if isinstance(data['id'], list): if isinstance(data['id'], list):
items = models.Item.objects.filter(public_id__in=data['id']) add_changelog(request, data)
t = tasks.bulk_edit.delay(data, request.user.username)
response = json_response(status=200, text='ok')
response['data']['taskId'] = t.task_id
else: else:
items = [get_object_or_404_json(models.Item, public_id=data['id'])] item = get_object_or_404_json(models.Item, public_id=data['id'])
for item in items:
if item.editable(request.user): if item.editable(request.user):
request_data = data.copy() add_changelog(request, data)
response = edit_item(request, item, data) response = edit_item(request.user, item, data)
response['data'] = item.json() response['data'] = item.json()
if item == items[0]:
add_changelog(request, request_data)
else: else:
response = json_response(status=403, text='permission denied') response = json_response(status=403, text='permission denied')
return render_to_json_response(response) return render_to_json_response(response)
@ -1029,7 +1030,10 @@ def download(request, id, resolution=None, format='webm', part=None):
return HttpResponseForbidden() return HttpResponseForbidden()
elif r is True: elif r is True:
response = HttpResponse(FileWrapper(video), content_type=content_type) response = HttpResponse(FileWrapper(video), content_type=content_type)
response['Content-Length'] = os.path.getsize(video.name) try:
response['Content-Length'] = os.path.getsize(video.name)
except:
pass
else: else:
response = HttpFileResponse(r, content_type=content_type) response = HttpFileResponse(r, content_type=content_type)
else: else:

View file

@ -73,5 +73,3 @@ if __name__ == "__main__":
sys.stderr.write("Error: Can't find '%s'.\nBefore you run pan.do/ra you must create it\n" % settings.SITE_CONFIG) sys.stderr.write("Error: Can't find '%s'.\nBefore you run pan.do/ra you must create it\n" % settings.SITE_CONFIG)
sys.exit(1) sys.exit(1)
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)
import app.config
app.config.shutdown()

View file

@ -220,7 +220,6 @@ XACCELREDIRECT = False
SITE_CONFIG = join(PROJECT_ROOT, 'config.jsonc') SITE_CONFIG = join(PROJECT_ROOT, 'config.jsonc')
DEFAULT_CONFIG = join(PROJECT_ROOT, 'config.pandora.jsonc') DEFAULT_CONFIG = join(PROJECT_ROOT, 'config.pandora.jsonc')
RELOAD_CONFIG = False
#used if CONFIG['canDownloadVideo'] is set #used if CONFIG['canDownloadVideo'] is set
TRACKER_URL = "udp://tracker.openbittorrent.com:80" TRACKER_URL = "udp://tracker.openbittorrent.com:80"
@ -275,7 +274,6 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
DATA_UPLOAD_MAX_MEMORY_SIZE = 32 * 1024 * 1024 DATA_UPLOAD_MAX_MEMORY_SIZE = 32 * 1024 * 1024
RELOADER_RUNNING = False
#you can ignore things below this line #you can ignore things below this line
#========================================================================= #=========================================================================
LOCAL_APPS = [] LOCAL_APPS = []

View file

@ -22,7 +22,7 @@ def get_location(ip):
else: else:
try: try:
location = g.city(ip) location = g.city(ip)
except GeoIP2Exception: except:
try: try:
location = g.country(s.ip) location = g.country(s.ip)
except: except:

View file

@ -24,7 +24,7 @@ class Worker(ConsumerMixin):
def process_task(self, body, message): def process_task(self, body, message):
try: try:
if body['task'] == 'trigger_event': if isinstance(body, dict) and body.get('task') == 'trigger_event':
daemon.trigger_event(*body['args']) daemon.trigger_event(*body['args'])
except: except:
logger.error('faild to trigger event %s', body, exc_info=True) logger.error('faild to trigger event %s', body, exc_info=True)

View file

@ -1,14 +1,15 @@
Django==3.0.6 Django==3.0.10
simplejson simplejson
chardet chardet
celery>4.3.0 celery<5.0,>4.3
django-celery-results django-celery-results
django-extensions==2.2.9 django-extensions==2.2.9
gunicorn==20.0.4 gunicorn==20.0.4
html5lib html5lib
requests==2.23.0 requests<3.0.0,>=2.24.0
urllib3<2.0.0,>=1.25.2
tornado<5 tornado<5
geoip2==3.0.0 geoip2==4.1.0
youtube-dl>=2020.5.8 youtube-dl>=2020.5.8
python-memcached python-memcached
elasticsearch elasticsearch

View file

@ -106,7 +106,7 @@ pandora.ui.addFilesDialog = function(options) {
}); });
var selectItems = []; var selectItems = [];
if (!pandora.site.itemRequiresVideo && pandora.user.ui.item) { if (pandora.user.ui.item && options.editable) {
selectItems.push({ selectItems.push({
id: 'add', id: 'add',
title: Ox._( title: Ox._(
@ -114,31 +114,23 @@ pandora.ui.addFilesDialog = function(options) {
[pandora.site.itemName.singular.toLowerCase()] [pandora.site.itemName.singular.toLowerCase()]
) )
}); });
}
if (options.items.length > 1) {
selectItems.push({ selectItems.push({
id: 'one', id: 'multiple',
title: Ox._( title: Ox._(
options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}', 'Create multiple {0}',
[pandora.site.itemName.singular.toLowerCase()] [pandora.site.itemName.plural.toLowerCase()]
)
});
} else {
if (options.items.length > 1) {
selectItems.push({
id: 'multiple',
title: Ox._(
'Create multiple {0}',
[pandora.site.itemName.plural.toLowerCase()]
)
});
}
selectItems.push({
id: 'one',
title: Ox._(
'Create one {0} with multiple parts',
[pandora.site.itemName.singular.toLowerCase()]
) )
}); });
} }
selectItems.push({
id: 'one',
title: Ox._(
options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}',
[pandora.site.itemName.singular.toLowerCase()]
)
});
var $select = Ox.Select({ var $select = Ox.Select({
items: selectItems, items: selectItems,
width: 256 width: 256
@ -224,6 +216,7 @@ pandora.ui.addFilesDialog = function(options) {
), function(result) { ), function(result) {
pandora.api.addMediaUrl({ pandora.api.addMediaUrl({
url: item.url, url: item.url,
referer: item.referer,
item: id item: id
}, callback); }, callback);
}); });

View file

@ -222,7 +222,8 @@ pandora.ui.addItemDialog = function(options) {
} }
return value; return value;
}); });
values.url= info.url; values.url = info.url;
values.referer = info.referer;
return Ox.extend(info, values); return Ox.extend(info, values);
} }
@ -270,10 +271,16 @@ pandora.ui.addItemDialog = function(options) {
} else { } else {
$screen.stop(); $screen.stop();
that.close(); that.close();
pandora.ui.addFilesDialog({ (pandora.user.ui.item ? pandora.api.get : Ox.noop)({
action: selected, id: pandora.user.ui.item,
items: items keys: ['editable']
}).open(); }, function(result) {
pandora.ui.addFilesDialog({
action: selected,
items: items,
editable: pandora.user.ui.item && result.data.editable
}).open();
})
} }
}) })
} }

View file

@ -0,0 +1,124 @@
pandora.ui.addRemoveKeyDialog = function(options) {
var item = Ox.getObjectById(pandora.site.documentKeys, options.key),
itemTitle = Ox._(item ? item.title : options.key),
dialogTitle = Ox._('Add/Remove {0}', [itemTitle]);
return Ox.Element('<span>')
.addClass('OxLight')
.html(' [+]')
.css({
cursor: 'pointer',
})
.options({
tooltip: dialogTitle
})
.on('click', function(event) {
var add, remove,
dialog = Ox.Dialog({
buttons: [
Ox.Button({
title: Ox._('Cancel')
})
.css({margin: '4px 4px 4px 0'})
.bindEvent({
click: function() {
dialog.close()
}
}),
Ox.Button({
title: Ox._('Update'),
width: 52
}).bindEvent({
click: function() {
var addValue = add.value()
var removeValue = remove.value()
if (!addValue.length && !removeValue.length) {
dialog.close()
return
}
var $loading = Ox.LoadingScreen({
width: 256,
height: 64
})
dialog.options({
content: $loading.start()
})
var ids = options.ids;
pandora.api[{
'items': 'find',
'documents': 'findDocuments',
}[options.section]]({
query: {
conditions: [{key: 'id', operator: '&', value: ids}]
},
range: [0, ids.length],
keys: ['id', options.key]
}, function(result) {
Ox.serialForEach(result.data.items,
function(item, index, items, callback) {
var changed = false
var value = item[options.key] || []
if (addValue.length && !Ox.contains(value, addValue)) {
value.push(addValue)
changed = true
}
if (removeValue.length && Ox.contains(value, removeValue)) {
value = value.filter(function(v) { return v != removeValue; } )
changed = true
}
if (changed) {
var edit = {id: item.id}
edit[options.key] = value
pandora.api[{
'items': 'edit',
'documents': 'editDocument',
}[options.section]](edit, function(response) {
callback()
})
} else {
callback()
}
},
function() {
$loading.stop()
dialog.close()
}
)
})
}
})
],
closeButton: true,
content: Ox.Element()
.css({
padding: '8px'
})
.append(
add = Ox.Input({
width: 240,
type: 'input',
label: 'Add'
})
)
.append(
Ox.Element().html('').css({
height: '8px'
})
)
.append(
remove = Ox.Input({
width: 240,
type: 'input',
label: 'Remove'
})
),
height: 64,
removeOnClose: true,
title: dialogTitle,
width: 256
}).open();
})
}

View file

@ -13,20 +13,6 @@ pandora.ui.collection = function() {
if (view == 'list') { if (view == 'list') {
that = Ox.TableList({ that = Ox.TableList({
draggable: true,
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) { columns: pandora.site.documentSortKeys.filter(function(key) {
return !key.capability return !key.capability
|| pandora.hasCapability(key.capability); || pandora.hasCapability(key.capability);
@ -40,7 +26,11 @@ pandora.ui.collection = function() {
defaultWidth: key.columnWidth, defaultWidth: key.columnWidth,
format: (function() { format: (function() {
return function(value, data) { return function(value, data) {
return pandora.formatDocumentKey(key, data); var value = pandora.formatDocumentKey(key, data);
if (Ox.isArray(value)) {
value = value.join(', ');
}
return value;
} }
})(), })(),
id: key.id, id: key.id,
@ -54,7 +44,25 @@ pandora.ui.collection = function() {
}; };
}), }),
columnsVisible: true, columnsVisible: true,
columnsMovable: true,
columnsRemovable: true,
columnsResizable: true,
columnsVisible: true,
draggable: true,
items: function(data, callback) {
pandora.api.findDocuments(Ox.extend(data, {
query: ui.findDocuments
}), callback);
return Ox.clone(data, true);
},
keys: keys,
scrollbarVisible: true, scrollbarVisible: true,
selected: ui.collectionSelection,
sort: ui.collectionSort.concat([
{key: 'extension', operator: '+'},
{key: 'title', operator: '+'}
]),
unique: 'id',
}) })
.bindEvent({ .bindEvent({
columnchange: function(data) { columnchange: function(data) {

View file

@ -218,6 +218,7 @@ pandora.ui.documentInfoView = function(data, isMixed) {
.append( .append(
Ox.EditableContent({ Ox.EditableContent({
editable: canEdit, editable: canEdit,
placeholder: formatLight(Ox._('No Title')),
tooltip: canEdit ? pandora.getEditTooltip() : '', tooltip: canEdit ? pandora.getEditTooltip() : '',
value: data.title || '' value: data.title || ''
}) })
@ -571,6 +572,13 @@ pandora.ui.documentInfoView = function(data, isMixed) {
} }
}) })
.appendTo($element); .appendTo($element);
if (isMixed[key] && Ox.contains(listKeys, key)) {
pandora.ui.addRemoveKeyDialog({
ids: ui.collectionSelection,
key: key,
section: ui.section
}).appendTo($element)
}
} }
}); });
$element.appendTo($text); $element.appendTo($text);

View file

@ -810,9 +810,8 @@ pandora.ui.documentsPanel = function(options) {
item: function(data, sort, size) { item: function(data, sort, size) {
var sortKey = sort[0].key, var sortKey = sort[0].key,
infoKey = sortKey == 'title' ? 'extension' : sortKey, infoKey = sortKey == 'title' ? 'extension' : sortKey,
info = ( key = Ox.getObjectById(pandora.site.documentKeys, infoKey),
Ox.getObjectById(pandora.site.documentKeys, infoKey).format || Ox.identity info = pandora.formatDocumentKey(key, data, size),
)(data[infoKey]),
size = size || 128; size = size || 128;
return { return {
height: Math.round(data.ratio > 1 ? size / data.ratio : size), height: Math.round(data.ratio > 1 ? size / data.ratio : size),

View file

@ -4,14 +4,12 @@ pandora.ui.editDialog = function() {
var ui = pandora.user.ui, var ui = pandora.user.ui,
hasChanged = false, hasChanged = false,
ids = ui.listSelection.filter(function(id) { ids = ui.listSelection,
return pandora.$ui.list.value(id, 'editable'); keys = ['editable'].concat(pandora.site.itemKeys.filter(function(key) {
}),
keys = pandora.site.itemKeys.filter(function(key) {
return key.id != '*' return key.id != '*'
}).map(function(key) { }).map(function(key) {
return key.id return key.id
}), })),
listKeys = pandora.site.itemKeys.filter(function(key) { listKeys = pandora.site.itemKeys.filter(function(key) {
return Ox.isArray(key.type); return Ox.isArray(key.type);
}).map(function(key){ }).map(function(key){
@ -86,26 +84,51 @@ pandora.ui.editDialog = function() {
} }
], ],
operator: '&' operator: '&'
} },
range: [0, ids.length]
}, function(result) { }, function(result) {
var data = {}, var data = {},
isMixed = {}, isMixed = {},
items = result.data.items; updateTitle = false,
keys.forEach(function(key) { items = result.data.items.filter(function(item) {
if (!item.editable) {
updateTitle = true
}
return item.editable;
});
if (updateTitle) {
that.options({
title: Ox._('Edit Metadata for {0}', [
Ox.formatNumber(items.length) + ' ' + Ox._(
items.length == 1 ? pandora.site.itemName.singular : pandora.site.itemName.plural
)
])
})
// no editable items
if (!items.length) {
that.close()
return
}
}
keys.filter(function(key) {
return key != 'editable'
}).forEach(function(key) {
var isArray = Ox.contains(listKeys, key), var isArray = Ox.contains(listKeys, key),
values = items.map(function(item) { values = items.map(function(item) {
return item[key]; return item[key];
}); });
if (isArray) { if (isArray) {
values = values.map(function(value) { values = values.map(function(value) {
return (value || []).join(separator); value = value || []
return value.join ? value.join(separator) : value;
}); });
} }
if (Ox.unique(values).length > 1) { if (Ox.unique(values).length > 1) {
isMixed[key] = true; isMixed[key] = true;
} }
data[key] = isMixed[key] ? null data[key] = isMixed[key] ? null
: isArray ? values[0].split(separator) : isArray && values.length ? values[0].split(separator)
: values[0]; : values[0];
}); });
that.options({ that.options({

View file

@ -3,14 +3,12 @@
pandora.ui.editDocumentsDialog = function() { pandora.ui.editDocumentsDialog = function() {
var ui = pandora.user.ui, var ui = pandora.user.ui,
hasChanged = false, hasChanged = false,
ids = ui.collectionSelection.filter(function(id) { ids = ui.collectionSelection,
return pandora.$ui.list.value(id, 'editable'); keys = ['editable'].concat(pandora.site.documentKeys.filter(function(key) {
}),
keys = pandora.site.documentKeys.filter(function(key) {
return key.id != '*' return key.id != '*'
}).map(function(key) { }).map(function(key) {
return key.id return key.id
}), })),
listKeys = pandora.site.documentKeys.filter(function(key) { listKeys = pandora.site.documentKeys.filter(function(key) {
return Ox.isArray(key.type); return Ox.isArray(key.type);
}).map(function(key){ }).map(function(key){
@ -85,12 +83,36 @@ pandora.ui.editDocumentsDialog = function() {
} }
], ],
operator: '&' operator: '&'
} },
range: [0, ids.length]
}, function(result) { }, function(result) {
var data = {}, var data = {},
isMixed = {}, isMixed = {},
items = result.data.items; updateTitle = false,
keys.forEach(function(key) { items = result.data.items.filter(function(item) {
if (!item.editable) {
updateTitle = true
}
return item.editable;
});
if (updateTitle) {
that.options({
title: Ox._('Edit Metadata for {0}', [
Ox.formatNumber(items.length) + ' ' + Ox._(
items.length == 1 ? 'Document' : 'Documents'
)
])
})
// no editable items
if (!items.length) {
that.close()
return
}
}
keys.filter(function(key) {
return key != 'editable'
}).forEach(function(key) {
var isArray = Ox.contains(listKeys, key), var isArray = Ox.contains(listKeys, key),
values = items.map(function(item) { values = items.map(function(item) {
return item[key]; return item[key];

View file

@ -205,9 +205,9 @@ pandora.ui.editor = function(data) {
}).open(); }).open();
}, },
editannotation: function(data) { editannotation: function(data) {
Ox.Log('', 'editAnnotation', data); Ox.Log('', 'editAnnotation', data.id, data);
function callback(result) { function callback(result) {
Ox.Log('', 'editAnnotation result', result); Ox.Log('', 'editAnnotation result', result.data.id, result);
if (!Ox.isEmpty(result.data)) { if (!Ox.isEmpty(result.data)) {
result.data.date = Ox.formatDate( result.data.date = Ox.formatDate(
result.data.modified.slice(0, 10), '%B %e, %Y' result.data.modified.slice(0, 10), '%B %e, %Y'
@ -222,21 +222,56 @@ pandora.ui.editor = function(data) {
pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || ''); pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || '');
Ox.Request.clearCache(); Ox.Request.clearCache();
}; };
var edit = {
'in': data['in'],
out: data.out,
value: data.value
}
if (data.id[0] == '_') { if (data.id[0] == '_') {
pandora.api.addAnnotation({ edit.item = ui.item;
'in': data['in'], edit.layer = data.layer;
item: ui.item,
layer: data.layer, if (queue[data.id]) {
out: data.out, queue[data.id].push(edit);
value: data.value } else {
}, callback); queue[data.id] = [];
pandora.api.addAnnotation(edit, function(result) {
callback(result);
var id = result.data.id,
pending = queue[id];
delete queue[id];
pending && pending.length && Ox.serialForEach(pending, function(edit, index, array, callback) {
edit.id = id
Ox.Log('', 'process pending editAnnotation request', id, edit);
pandora.api.editAnnotation(edit, function(result) {
callback();
})
}, function() {
Ox.Request.clearCache();
});
});
}
} else { } else {
pandora.api.editAnnotation({ edit.id = data.id;
id: data.id, if (queue[data.id]) {
'in': data['in'], queue[data.id].push(edit);
out: data.out, } else {
value: data.value queue[data.id] = [];
}, callback); pandora.api.editAnnotation(edit, function(result) {
callback(result);
var pending = queue[edit.id];
delete queue[edit.id];
pending && pending.length && Ox.serialForEach(pending, function(edit, index, array, cb) {
Ox.Log('', 'process pending editAnnotation request', edit.id, edit);
pandora.api.editAnnotation(edit, function(result) {
callback(result);
cb();
})
}, function() {
Ox.Request.clearCache();
});
});
}
} }
}, },
embedselection: function() { embedselection: function() {
@ -387,7 +422,8 @@ pandora.ui.editor = function(data) {
pandora_videotimeline: function(data) { pandora_videotimeline: function(data) {
that.options({timeline: data.value}); that.options({timeline: data.value});
} }
}); }),
queue = [];
pandora._dontSelectResult = false; pandora._dontSelectResult = false;

View file

@ -132,6 +132,7 @@ pandora.ui.importMediaDialog = function(options) {
pandora.api.edit(edit, function(result) { pandora.api.edit(edit, function(result) {
pandora.api.addMediaUrl({ pandora.api.addMediaUrl({
url: info.url, url: info.url,
referer: info.referer,
item: edit.id item: edit.id
}, function(result) { }, function(result) {
if (result.data.taskId) { if (result.data.taskId) {

View file

@ -198,9 +198,7 @@ pandora.ui.infoView = function(data, isMixed) {
top: margin + 'px', top: margin + 'px',
right: margin + 'px' right: margin + 'px'
}) })
.appendTo($info), .appendTo($info);
$capabilities;
[$options, $edit].forEach(function($element) { [$options, $edit].forEach(function($element) {
$element.find('input').css({ $element.find('input').css({
@ -346,7 +344,7 @@ pandora.ui.infoView = function(data, isMixed) {
.append(formatKey('Rights Level', 'statistics')) .append(formatKey('Rights Level', 'statistics'))
.append($rightsLevel) .append($rightsLevel)
.appendTo($statistics); .appendTo($statistics);
renderRightsLevel(); pandora.renderRightsLevel(that, $rightsLevel, data, isMixed, isMultiple, canEdit);
// Notes -------------------------------------------------------------------- // Notes --------------------------------------------------------------------
@ -496,15 +494,6 @@ pandora.ui.infoView = function(data, isMixed) {
return ret; return ret;
} }
function getRightsLevelElement(rightsLevel) {
return Ox.Theme.formatColorLevel(
rightsLevel,
pandora.site.rightsLevels.map(function(rightsLevel) {
return rightsLevel.name;
})
);
}
function getValue(key, value) { function getValue(key, value) {
return !value ? '' return !value ? ''
: Ox.contains(specialListKeys, key) ? value.join('; ') : Ox.contains(specialListKeys, key) ? value.join('; ')
@ -512,81 +501,6 @@ pandora.ui.infoView = function(data, isMixed) {
: value; : 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.hasCapability(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) { function renderGroup(keys) {
var $element; var $element;
keys.forEach(function(key) { displayedKeys.push(key) }); keys.forEach(function(key) { displayedKeys.push(key) });
@ -615,6 +529,13 @@ pandora.ui.infoView = function(data, isMixed) {
} }
}) })
.appendTo($element); .appendTo($element);
if (isMixed[key] && Ox.contains(listKeys, key)) {
pandora.ui.addRemoveKeyDialog({
ids: ui.listSelection,
key: key,
section: ui.section
}).appendTo($element)
}
} }
}); });
$element.appendTo($text); $element.appendTo($text);
@ -632,53 +553,6 @@ pandora.ui.infoView = function(data, isMixed) {
} }
} }
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);
var edit = {
id: isMultiple ? ui.listSelection : data.id,
rightslevel: rightsLevel
};
pandora.api.edit(edit, function(result) {
that.triggerEvent('change', Ox.extend({}, 'rightslevel', rightsLevel));
});
}
})
.appendTo($rightsLevel);
} else {
$rightsLevelElement
.css({
marginBottom: '4px'
})
.appendTo($rightsLevel);
}
$capabilities = $('<div>').appendTo($rightsLevel);
renderCapabilities(data.rightslevel);
}
function toggleIconSize() { function toggleIconSize() {
iconSize = iconSize == 256 ? 512 : 256; iconSize = iconSize == 256 ? 512 : 256;
iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio); iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio);

View file

@ -44,6 +44,12 @@ pandora.ui.infoView = function(data, isMixed) {
$options = Ox.MenuButton({ $options = Ox.MenuButton({
items: [ items: [
{
id: 'toggle',
title: Ox._('Toggle {0} size...', [
Ox._(ui.icons == 'posters' ? 'poster' : 'icon')
]),
},
{ {
id: 'delete', id: 'delete',
title: Ox._('Delete {0}...', [pandora.site.itemName.singular]), title: Ox._('Delete {0}...', [pandora.site.itemName.singular]),
@ -62,7 +68,9 @@ pandora.ui.infoView = function(data, isMixed) {
}) })
.bindEvent({ .bindEvent({
click: function(data_) { click: function(data_) {
if (data_.id == 'delete') { if (data_.id == 'toggle') {
toggleIconSize()
} else if (data_.id == 'delete') {
pandora.$ui.deleteItemsDialog = pandora.ui.deleteItemsDialog({ pandora.$ui.deleteItemsDialog = pandora.ui.deleteItemsDialog({
items: [data] items: [data]
}).open(); }).open();
@ -204,9 +212,7 @@ pandora.ui.infoView = function(data, isMixed) {
top: margin + 'px', top: margin + 'px',
right: margin + 'px' right: margin + 'px'
}) })
.appendTo($info), .appendTo($info);
$capabilities;
[$options, $edit].forEach(function($element) { [$options, $edit].forEach(function($element) {
$element.find('input').css({ $element.find('input').css({
@ -437,7 +443,7 @@ pandora.ui.infoView = function(data, isMixed) {
.append(formatKey(Ox._('Rights Level'), 'statistics')) .append(formatKey(Ox._('Rights Level'), 'statistics'))
.append($rightsLevel) .append($rightsLevel)
.appendTo($statistics); .appendTo($statistics);
renderRightsLevel(); pandora.renderRightsLevel(that, $rightsLevel, data, isMixed, isMultiple, canEdit);
// User and Groups --------------------------------------------------------- // User and Groups ---------------------------------------------------------
if (!isMultiple) { if (!isMultiple) {
@ -628,100 +634,12 @@ pandora.ui.infoView = function(data, isMixed) {
return formatLink(key, ret, key == 'date' && value); return formatLink(key, ret, key == 'date' && value);
} }
function getRightsLevelElement(rightsLevel) {
return Ox.Theme.formatColorLevel(
rightsLevel,
pandora.site.rightsLevels.map(function(rightsLevel) {
return rightsLevel.name;
})
);
}
function getValue(key, value) { function getValue(key, value) {
return !value ? '' return !value ? ''
: Ox.contains(listKeys, key) ? value.join(', ') : Ox.contains(listKeys, key) ? value.join(', ')
: value; : 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._(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.hasCapability(capability.name, userLevel) >= rightsLevel,
$element = Ox.Theme.formatColorLevel(hasCapability, ['', '']);
Ox.Button({
tooltip: Ox._('{0} '
+ (hasCapability ? 'can' : 'can\'t') + ' '
+ Ox.toSlashes(capability.name)
.split('/').slice(1).join(' ')
.toLowerCase()
.replace('see item', 'see the item')
.replace('play video', 'play the full video')
.replace('download video', 'download the video'),
[canEdit ? Ox._(Ox.toTitleCase(userLevel)) : Ox._('You')]),
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) { function renderGroup(keys) {
var $element; var $element;
if (canEdit || keys.filter(function(key) { if (canEdit || keys.filter(function(key) {
@ -757,53 +675,6 @@ pandora.ui.infoView = function(data, isMixed) {
} }
} }
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: Ox._(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);
var edit = {
id: isMultiple ? ui.listSelection : data.id,
rightslevel: rightsLevel
};
pandora.api.edit(edit, function(result) {
that.triggerEvent('change', Ox.extend({}, 'rightslevel', rightsLevel));
});
}
})
.appendTo($rightsLevel);
} else {
$rightsLevelElement
.css({
marginBottom: '4px'
})
.appendTo($rightsLevel);
}
$capabilities = $('<div>').appendTo($rightsLevel);
renderCapabilities(data.rightslevel);
}
function toggleIconSize() { function toggleIconSize() {
iconSize = iconSize == 256 ? 512 : 256; iconSize = iconSize == 256 ? 512 : 256;
iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio); iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio);

View file

@ -14,3 +14,145 @@ pandora.cleanupDate = function(value) {
return value return value
}; };
pandora.renderRightsLevel = function(that, $rightsLevel, data, isMixed, isMultiple, canEdit) {
var ui = pandora.user.ui,
rightsLevels = pandora.site.rightsLevels.map(function(rightsLevel) {
return rightsLevel.name;
}).concat(isMultiple ? ['Mixed'] : []),
rightsLevel = isMixed.rightslevel ? rightsLevels.length - 1 : data.rightslevel,
$capabilities,
$rightsLevelElement = getRightsLevelElement(rightsLevel),
$rightsLevelSelect;
$rightsLevel.empty();
if (canEdit) {
$rightsLevelSelect = Ox.Select({
items: pandora.site.rightsLevels.map(function(rightsLevel, i) {
return {id: i, title: Ox._(rightsLevel.name)};
}).concat(isMultiple ? [
{id: rightsLevels.length - 1, title: Ox._('Mixed'), disabled: true}
] : []),
width: 128,
value: 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')})
if (rightsLevel < pandora.site.rightsLevels.length) {
renderCapabilities(rightsLevel);
var edit = {
id: isMultiple ? ui.listSelection : data.id,
rightslevel: rightsLevel
};
pandora.api.edit(edit, function(result) {
that.triggerEvent('change', Ox.extend({}, 'rightslevel', rightsLevel));
});
}
}
})
.appendTo($rightsLevel);
} else {
$rightsLevelElement
.css({
marginBottom: '4px'
})
.appendTo($rightsLevel);
}
$capabilities = $('<div>').appendTo($rightsLevel);
!isMixed.rightslevel && renderCapabilities(data.rightslevel);
function getRightsLevelElement(rightsLevel) {
return Ox.Theme.formatColorLevel(rightsLevel, rightsLevels)
}
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._(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.hasCapability(capability.name, userLevel) >= rightsLevel,
$element = Ox.Theme.formatColorLevel(hasCapability, ['', '']);
Ox.Button({
tooltip: Ox._('{0} '
+ (hasCapability ? 'can' : 'can\'t') + ' '
+ Ox.toSlashes(capability.name)
.split('/').slice(1).join(' ')
.toLowerCase()
.replace('see item', 'see the item')
.replace('play video', 'play the full video')
.replace('download video', 'download the video'),
[canEdit ? Ox._(Ox.toTitleCase(userLevel)) : Ox._('You')]),
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);
}
});
}
}

View file

@ -442,7 +442,37 @@ pandora.ui.mainMenu = function() {
}); });
} }
} else if (data.id == 'deletefromarchive') { } else if (data.id == 'deletefromarchive') {
if (ui.section == 'documents') { if (ui.section == 'items') {
var ids;
if (ui.item) {
ids = [ui.item]
} else {
ids = ui.listSelection
}
pandora.api.find({
query: {
conditions: [{
key: 'id',
operator: '&',
value: ids
}],
operator: '&'
},
keys: ['id', 'title'],
range: [0, ui.listSelection.length]
}, function(result) {
pandora.$ui.deleteItemsDialog = pandora.ui.deleteItemsDialog({
items: result.data.items
}, function() {
Ox.Request.clearCache();
if (ui.item) {
pandora.UI.set({item: ''});
} else {
pandora.$ui.list.reloadList()
}
}).open();
});
} else if (ui.section == 'documents') {
var files; var files;
if (ui.document) { if (ui.document) {
files = [pandora.$ui.document.info()]; files = [pandora.$ui.document.info()];
@ -1364,6 +1394,9 @@ pandora.ui.mainMenu = function() {
{ id: 'clearclipboard', title: Ox._('Clear Clipboard'), disabled: !clipboardItems}, { id: 'clearclipboard', title: Ox._('Clear Clipboard'), disabled: !clipboardItems},
{}, {},
{ id: 'delete', title: Ox._('{0} {1} {2}', [deleteVerb, selectionItemName, listName]), disabled: !canDelete, keyboard: 'delete' }, { id: 'delete', title: Ox._('{0} {1} {2}', [deleteVerb, selectionItemName, listName]), disabled: !canDelete, keyboard: 'delete' },
ui._list ? [
{ id: 'deletefromarchive', title: Ox._('{0} {1} {2}', [Ox._('Delete'), selectionItemName, Ox._('from Archive')]), disabled: !canDelete }
] : [],
{}, {},
{ id: 'undo', title: undoText ? Ox._('Undo {0}', [undoText]) : Ox._('Undo'), disabled: !undoText, keyboard: 'control z' }, { 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: 'redo', title: redoText ? Ox._('Redo {0}', [redoText]) : Ox._('Redo'), disabled: !redoText, keyboard: 'shift control z' },

View file

@ -58,10 +58,16 @@ pandora.ui.mediaExistsDialog = function(options) {
return existing.indexOf(item.oshash) == -1; return existing.indexOf(item.oshash) == -1;
}); });
that.close(); that.close();
pandora.ui.addFilesDialog({ (pandora.user.ui.item ? pandora.api.get : Ox.noop)({
action: options.action, id: pandora.user.ui.item,
items: items keys: ['editable']
}).open(); }, function(result) {
pandora.ui.addFilesDialog({
action: options.action,
items: items,
editable: pandora.user.ui.item && result.data.editable
}).open();
})
} }
}) })
]; ];

View file

@ -375,7 +375,11 @@ pandora.clickLink = function(e, selectEmbed) {
if (match) { if (match) {
(selectEmbed || pandora.$ui.textPanel.selectEmbed)(parseInt(match[1])); (selectEmbed || pandora.$ui.textPanel.selectEmbed)(parseInt(match[1]));
} else { } else {
pandora.openURL(e.target.href); if (e.target.target == '_blank') {
pandora.openLink(e.target.href);
} else {
pandora.openURL(e.target.href);
}
} }
}; };
@ -725,11 +729,11 @@ pandora.uploadDroppedFiles = function(files) {
pandora.enableBatchEdit = function(section) { pandora.enableBatchEdit = function(section) {
var ui = pandora.user.ui; var ui = pandora.user.ui;
if (section == 'documents') { if (section == 'documents') {
return !ui.document && ui.collectionSelection.length > 1 && ui.collectionSelection.every(function(item) { return !ui.document && ui.collectionSelection.length > 1 && ui.collectionSelection.some(function(item) {
return pandora.$ui.list && pandora.$ui.list.value(item, 'editable'); return pandora.$ui.list && pandora.$ui.list.value(item, 'editable');
}) })
} else { } else {
return !ui.item && ui.listSelection.length > 1 && ui.listSelection.every(function(item) { return !ui.item && ui.listSelection.length > 1 && ui.listSelection.some(function(item) {
return pandora.$ui.list && pandora.$ui.list.value(item, 'editable'); return pandora.$ui.list && pandora.$ui.list.value(item, 'editable');
}) })
} }
@ -2655,8 +2659,9 @@ pandora.openURL = function(url) {
}; };
pandora.safeDocumentName = function(name) { pandora.safeDocumentName = function(name) {
['?', '#', '%'].forEach(function(c) { ['\\?', '#', '%', '/'].forEach(function(c) {
name = name.replace(c, '_'); var r = new RegExp(c, 'g')
name = name.replace(r, '_');
}) })
return name; return name;
}; };

View file

@ -89,6 +89,7 @@ apt-get install -y \
python3-pyinotify \ python3-pyinotify \
python3-simplejson \ python3-simplejson \
python3-lxml \ python3-lxml \
python3-cssselect \
python3-html5lib \ python3-html5lib \
python3-ox \ python3-ox \
python3-elasticsearch \ python3-elasticsearch \
@ -118,6 +119,7 @@ fi
if [ "$RABBITMQ" == "local" ]; then if [ "$RABBITMQ" == "local" ]; then
RABBITPWD=$(pwgen -n 16 -1) RABBITPWD=$(pwgen -n 16 -1)
rabbitmqctl add_user pandora $RABBITPWD rabbitmqctl add_user pandora $RABBITPWD
rabbitmqctl change_password pandora $RABBITPWD
rabbitmqctl add_vhost /pandora rabbitmqctl add_vhost /pandora
rabbitmqctl set_permissions -p /pandora pandora ".*" ".*" ".*" rabbitmqctl set_permissions -p /pandora pandora ".*" ".*" ".*"
CELERY_BROKER_URL="amqp://pandora:$RABBITPWD@localhost:5672//pandora" CELERY_BROKER_URL="amqp://pandora:$RABBITPWD@localhost:5672//pandora"
@ -185,7 +187,7 @@ fi
# if pandora is running inside a container, expose backend at port 2620 # if pandora is running inside a container, expose backend at port 2620
if [ "$LXC" == "yes" ]; then if [ "$LXC" == "yes" ]; then
sed -i s/127.0.0.1/0.0.0.0/g /srv/pandora/pandora/gunicorn_config.py sed -i "s/127.0.0.1/[::]/g" /srv/pandora/pandora/gunicorn_config.py
echo "WEBSOCKET_ADDRESS = \"0.0.0.0\"" >> /srv/pandora/pandora/local_settings.py echo "WEBSOCKET_ADDRESS = \"0.0.0.0\"" >> /srv/pandora/pandora/local_settings.py
fi fi
/srv/pandora/ctl start /srv/pandora/ctl start
@ -199,7 +201,6 @@ if [ "$NGINX" == "local" ]; then
cp "/srv/pandora/etc/nginx/pandora" "/etc/nginx/sites-available/pandora" cp "/srv/pandora/etc/nginx/pandora" "/etc/nginx/sites-available/pandora"
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/pandora rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/pandora
ln -s ../sites-available/pandora /etc/nginx/sites-enabled/pandora ln -s ../sites-available/pandora /etc/nginx/sites-enabled/pandora
ln -s /srv/pandora/ctl /usr/local/bin/pandoractl
read -r -d '' GZIP <<EOI read -r -d '' GZIP <<EOI
gzip_static on;\\ gzip_static on;\\