Compare commits
58 Commits
master
...
feature/su
Author | SHA1 | Date | |
---|---|---|---|
![]() |
727c74c294 | ||
![]() |
635b31eb0b | ||
![]() |
a0abb1a460 | ||
![]() |
f460569bdc | ||
![]() |
a2def81b52 | ||
![]() |
ab3a5a4d63 | ||
![]() |
3f7f2ffcd1 | ||
![]() |
a765b5ee06 | ||
![]() |
425749eb85 | ||
![]() |
7002e039b3 | ||
![]() |
0cb50e5090 | ||
![]() |
1fc3ca05cc | ||
![]() |
bdd2aca6f2 | ||
![]() |
d2c7cab3e3 | ||
![]() |
ff5637a7da | ||
![]() |
4b08f7e6f0 | ||
![]() |
1bf8f8d2f8 | ||
![]() |
774fda3a7c | ||
![]() |
e64e8ae621 | ||
![]() |
9d5e9a4137 | ||
![]() |
5c18fbeed8 | ||
![]() |
4c58ae0733 | ||
![]() |
993738af49 | ||
![]() |
7ba384e70a | ||
![]() |
9548f43b06 | ||
![]() |
5bbac75481 | ||
![]() |
7ebe2a1aa0 | ||
![]() |
ea073b4324 | ||
![]() |
55ae8a4be2 | ||
![]() |
0746acda79 | ||
![]() |
3040ab1d62 | ||
![]() |
5ee34c50a4 | ||
![]() |
7126354d7a | ||
![]() |
58d3ad727d | ||
![]() |
20c194f00c | ||
![]() |
b91759e6c1 | ||
![]() |
9b5328d888 | ||
![]() |
adaa23d39f | ||
![]() |
96bd6b240b | ||
![]() |
61a62421f4 | ||
![]() |
066fc90056 | ||
![]() |
2b698ca024 | ||
![]() |
2da195f925 | ||
![]() |
0c7d95fb15 | ||
![]() |
1020b0d1dc | ||
![]() |
8b37d122b9 | ||
![]() |
f7e31c2be7 | ||
![]() |
ffef7e3d8f | ||
![]() |
daf7d37c84 | ||
![]() |
cdd130b243 | ||
![]() |
d121e2c4f6 | ||
![]() |
55247e4f56 | ||
![]() |
6feae99bf1 | ||
![]() |
1b6c74bcb6 | ||
![]() |
eddb666b5a | ||
![]() |
212b241d0e | ||
![]() |
ef9040870a | ||
![]() |
6d4df5f6b7 |
47
.drone.yml
47
.drone.yml
@ -1,47 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: publish
|
||||
pull: default
|
||||
image: plugins/docker:18.09
|
||||
settings:
|
||||
registry: https://registry.nixaid.com
|
||||
repo: "registry.nixaid.com/${DRONE_REPO_NAMESPACE}/${DRONE_REPO_NAME}"
|
||||
tags:
|
||||
- latest
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
# storage_path: /drone/docker
|
||||
# storage_driver: aufs
|
||||
# ipv6: false
|
||||
# debug: true
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: notify
|
||||
pull: default
|
||||
image: drillster/drone-email:latest
|
||||
settings:
|
||||
from: "Drone CI <noreply@nixaid.com>"
|
||||
host: mx.nixaid.com
|
||||
port: 587
|
||||
subject: "NIXAID Drone Pipeline {{#success build.status}}SUCCESS{{else}}FAILURE{{/success}} Notification"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
status:
|
||||
- success
|
||||
- failure
|
@ -10,13 +10,6 @@ Options -Indexes
|
||||
# php_value upload_max_filesize 20M
|
||||
# php_value max_file_uploads 100
|
||||
#</IfModule>
|
||||
#<IfModule mod_php7.c>
|
||||
# php_value max_execution_time 200
|
||||
# php_value post_max_size 200M
|
||||
# php_value upload_max_size 200M
|
||||
# php_value upload_max_filesize 20M
|
||||
# php_value max_file_uploads 100
|
||||
#</IfModule>
|
||||
|
||||
# ---
|
||||
# Uncomment these lines when you want to allow access to the Lychee API from different origins
|
||||
|
@ -1,46 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at lychee@electerious.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
58
Dockerfile
58
Dockerfile
@ -1,58 +0,0 @@
|
||||
FROM alpine
|
||||
|
||||
ARG LYCHEE_VERSION=3.1.6
|
||||
|
||||
ENV UID=991 GID=991
|
||||
|
||||
RUN BUILD_DEPS=" \
|
||||
imagemagick-dev \
|
||||
tar \
|
||||
libressl \
|
||||
ca-certificates \
|
||||
build-base \
|
||||
autoconf \
|
||||
pcre-dev \
|
||||
libtool" \
|
||||
&& apk -U upgrade && apk add \
|
||||
${BUILD_DEPS} \
|
||||
nginx \
|
||||
php7-mbstring \
|
||||
php7-fpm \
|
||||
php7-exif \
|
||||
php7-gd \
|
||||
php7-json \
|
||||
php7-mysqli \
|
||||
php7-zip \
|
||||
php7-session \
|
||||
php7-pear \
|
||||
php7-dev \
|
||||
s6 \
|
||||
su-exec \
|
||||
imagemagick \
|
||||
&& pecl install imagick \
|
||||
&& echo "extension=imagick.so" > /etc/php7/conf.d/imagick.ini \
|
||||
&& sed -i -e "s/max_execution_time = 30/max_execution_time = 200/g" \
|
||||
-e "s/post_max_size = 8M/post_max_size = 100M/g" \
|
||||
-e "s/upload_max_filesize = 2M/upload_max_filesize = 20M/g" \
|
||||
-e "s/memory_limit = 256M/memory_limit = 512M/g" \
|
||||
/etc/php7/php.ini \
|
||||
&& mkdir /lychee \
|
||||
&& apk del ${BUILD_DEPS} php7-pear php7-dev \
|
||||
&& rm -rf /var/cache/apk/* /tmp/*
|
||||
|
||||
COPY . /lychee/
|
||||
|
||||
RUN mv /lychee/docker/nginx.conf /etc/nginx/nginx.conf && \
|
||||
mv /lychee/docker/php-fpm.conf /etc/php7/php-fpm.conf && \
|
||||
mv /lychee/docker/s6.d /etc/ && \
|
||||
mv /lychee/docker/run.sh /usr/local/bin/run.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/run.sh /etc/s6.d/*/* /etc/s6.d/.s6-svscan/*
|
||||
|
||||
VOLUME /lychee/uploads /lychee/data
|
||||
|
||||
EXPOSE 8888
|
||||
|
||||
LABEL maintainer="Andrey Arapov <andrey.arapov@nixaid.com>"
|
||||
|
||||
CMD ["run.sh"]
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Tobias Reich
|
||||
Copyright (c) 2016 Tobias Reich (http://electerious.com/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
24
README.md
24
README.md
@ -1,15 +1,11 @@
|
||||
[](https://drone.nixaid.com/arno/lychee)
|
||||
|
||||
# Lychee
|
||||
|
||||
#### A great looking and easy-to-use photo-management-system.
|
||||
|
||||
*Since the 1st of April 2018 this project has moved to it's own Organisation (https://github.com/LycheeOrg) where people are able to submit their fixes to it. We, the Organisation owners, want to thank electerious (Tobias Reich) for the opportunity to make this project live on.*
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
Lychee is a free photo-management tool, which runs on your server or web-space. Installing is a matter of seconds. Upload, manage and share photos like from a native application. Lychee comes with everything you need and all your photos are stored securely. Read more on our [website](http://lychee.electerious.com).
|
||||
Lychee is a free photo-management tool, which runs on your server or web-space. Installing is a matter of seconds. Upload, manage and share photos like from a native application. Lychee comes with everything you need and all your photos are stored securely. Try the [Live Demo](http://ld.electerious.com) or read more on our [Website](http://lychee.electerious.com).
|
||||
|
||||
## Installation
|
||||
|
||||
@ -21,7 +17,7 @@ You can use Lychee right after the installation. Here are some advanced features
|
||||
|
||||
### Settings
|
||||
|
||||
Sign in and click the gear in the top left corner to change your settings. If you want to edit them manually: MySQL details are stored in `data/config.php`. Other options and hidden settings are stored directly in the database. [Settings »](docs/Settings.md)
|
||||
Sign in and click the gear on the top left corner to change your settings. If you want to edit them manually: MySQL details are stored in `data/config.php`. Other options and hidden settings are stored directly in the database. [Settings »](docs/Settings.md)
|
||||
|
||||
### Update
|
||||
|
||||
@ -33,7 +29,7 @@ Lychee is ready to use, right out of the box. If you want to contribute and edit
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
These shortcuts will help you to use Lychee even faster. [Keyboard Shortcuts »](docs/Keyboard%20Shortcuts.md)
|
||||
These shortcuts will help you to use Lychee even faster. [Keyboard Shortcuts »](docs/Keyboard Shortcuts.md)
|
||||
|
||||
### Dropbox import
|
||||
|
||||
@ -49,13 +45,13 @@ Lychee uses [Imagick](http://www.imagemagick.org) when installed on your server.
|
||||
|
||||
### Docker
|
||||
|
||||
Browse the [Docker Hub Registry](https://hub.docker.com/r/kdelfour/lychee-docker/) for various automated Lychee-Docker builds. We recommend using [lychee-docker](https://hub.docker.com/r/kdelfour/lychee-docker/) by [kdelfour](https://github.com/kdelfour).
|
||||
Browse the [Docker Hub Registry](https://hub.docker.com/r/kdelfour/lychee-docker/) for various automated Lychee-Docker builds. We recommed to use [lychee-docker](https://hub.docker.com/r/kdelfour/lychee-docker/) by [kdelfour](https://github.com/kdelfour).
|
||||
|
||||
### Plugins and Extensions
|
||||
|
||||
The plugin-system of Lychee allows you to execute scripts when a certain action fires. Plugins are hooks, which are injected directly into Lychee. [Plugin documentation »](docs/Plugins.md)
|
||||
The plugin-system of Lychee allows you to execute scripts, when a certain action fires. Plugins are hooks, which are injected directly into Lychee. [Plugin documentation »](docs/Plugins.md)
|
||||
|
||||
It's also possible to build extensions upon Lychee. The way to do so isn't documented and can change every time. We recommend using the plugin-system, when possible.
|
||||
It's also possible to build extensions upon Lychee. The way to do so isn't documented and can change every time. We recommend to use the plugin-system, when possible.
|
||||
|
||||
Here's a list of all available Plugins and Extensions:
|
||||
|
||||
@ -74,3 +70,7 @@ Here's a list of all available Plugins and Extensions:
|
||||
## Troubleshooting
|
||||
|
||||
Take a look at the [FAQ](docs/FAQ.md) if you have problems. Discovered a bug? Please create an issue here on GitHub!
|
||||
|
||||
## Donate
|
||||
|
||||
I am working hard on continuously developing and maintaining Lychee. Please consider making a donation via [Flattr](https://flattr.com/submit/auto?user_id=electerious&url=http%3A%2F%2Flychee.electerious.com&title=Lychee&category=software) or PayPal (from [our site](http://lychee.electerious.com/)) to keep the project going strong and me motivated.
|
||||
|
BIN
dist/main.css
vendored
BIN
dist/main.css
vendored
Binary file not shown.
BIN
dist/main.js
vendored
BIN
dist/main.js
vendored
Binary file not shown.
BIN
dist/view.js
vendored
BIN
dist/view.js
vendored
Binary file not shown.
@ -1,26 +0,0 @@
|
||||
## wonderfall/lychee
|
||||
|
||||

|
||||
|
||||
#### What is this?
|
||||
Lychee is a free photo-management tool, which runs on your server or web-space. Installing is a matter of seconds. Upload, manage and share photos like from a native application. Lychee comes with everything you need and all your photos are stored securely.
|
||||
|
||||
#### Features
|
||||
- Based on Alpine Linux.
|
||||
- nginx + PHP7.
|
||||
- Active Imagemagick + pecl ext.
|
||||
|
||||
#### Build-time variables
|
||||
- **LYCHEE_VERSION** : version of Lychee.
|
||||
- **IMAGICK_EXT_VERSION** : version of imagick pecl extension.
|
||||
|
||||
#### Environment variables
|
||||
- **UID** : lychee user id *(default : 991)*.
|
||||
- **GID** : lychee group id *(default : 991)*.
|
||||
|
||||
#### Volumes
|
||||
- **/lychee/uploads** : uploads.
|
||||
- **/lychee/data** : data files.
|
||||
|
||||
#### Ports
|
||||
- **8888** [(reverse proxy!)](https://github.com/hardware/mailserver/wiki/Reverse-proxy-configuration)
|
@ -1,88 +0,0 @@
|
||||
worker_processes auto;
|
||||
pid /tmp/nginx.pid;
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
access_log off;
|
||||
error_log /tmp/ngx_error.log error;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 15;
|
||||
keepalive_disable msie6;
|
||||
keepalive_requests 100;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
server_tokens off;
|
||||
|
||||
fastcgi_temp_path /tmp/fastcgi 1 2;
|
||||
client_body_temp_path /tmp/client_body 1 2;
|
||||
proxy_temp_path /tmp/proxy 1 2;
|
||||
uwsgi_temp_path /tmp/uwsgi 1 2;
|
||||
scgi_temp_path /tmp/scgi 1 2;
|
||||
|
||||
gzip on;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 512;
|
||||
gzip_buffers 4 8k;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_disable "msie6";
|
||||
gzip_types
|
||||
text/css
|
||||
text/javascript
|
||||
text/xml
|
||||
text/plain
|
||||
text/x-component
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/xml
|
||||
application/rss+xml
|
||||
application/vnd.ms-fontobject
|
||||
font/truetype
|
||||
font/opentype
|
||||
image/svg+xml;
|
||||
|
||||
server {
|
||||
listen 8888;
|
||||
root /lychee;
|
||||
index index.php index.html;
|
||||
client_max_body_size 100M;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(?:ico|css|js|gif|jpe?g|png|ttf|woff)\$ {
|
||||
access_log off;
|
||||
expires 30d;
|
||||
add_header Pragma public;
|
||||
add_header Cache-Control "public, mustrevalidate, proxy-revalidate";
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.*\.php)(/.*)?$;
|
||||
fastcgi_pass unix:/tmp/php-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_intercept_errors off;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 4 16k;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
[global]
|
||||
daemonize = no
|
||||
|
||||
[www]
|
||||
listen = /tmp/php-fpm.sock
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
chdir = /
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
mkdir /lychee/uploads/big /lychee/uploads/import /lychee/uploads/medium /lychee/uploads/thumb
|
||||
chown -R $UID:$GID /lychee /etc/nginx /etc/php7 /var/log /var/lib/nginx /tmp /etc/s6.d
|
||||
exec su-exec $UID:$GID /bin/s6-svscan /etc/s6.d
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
exit 0
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
exec nginx
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
exec php-fpm7
|
@ -1,10 +1,3 @@
|
||||
## v3.1.6
|
||||
|
||||
Released March 20, 2017
|
||||
|
||||
- `Fixed` Downloading a SmartAlbum results in crash (#652)
|
||||
- `Fixed` htaccess IfModule for PHP7 (#653)
|
||||
|
||||
## v3.1.5
|
||||
|
||||
Released October 25, 2016
|
||||
|
@ -27,7 +27,7 @@ You can also use the [direct download](https://github.com/electerious/Lychee/arc
|
||||
|
||||
Change the permissions of `uploads/`, `data/` and all their subfolders. Sufficient read/write privileges are required.
|
||||
|
||||
chmod -R 750 uploads/ data/
|
||||
chmod -R 777 uploads/ data/
|
||||
|
||||
### 4. Finish
|
||||
|
||||
|
@ -14,8 +14,8 @@ The following keys and shortcuts can be used in Lychee.
|
||||
| Key | Action |
|
||||
|:-----------|:------------|
|
||||
| `s` or `f` | Search |
|
||||
| `cmd`+`a` | Select all albums |
|
||||
| `ctrl`+`a` | Select all albums |
|
||||
| `meta-key`+`left-click` | Select album individually |
|
||||
| `right-click` | Open context-menu |
|
||||
|
||||
### Album
|
||||
| Key | Action |
|
||||
@ -25,8 +25,8 @@ The following keys and shortcuts can be used in Lychee.
|
||||
| `i` | Show information |
|
||||
| `cmd`+`backspace` | Delete album |
|
||||
| `ctrl`+`backspace` | Delete album |
|
||||
| `cmd`+`a` | Select all photos |
|
||||
| `ctrl`+`a` | Select all photos |
|
||||
| `meta-key`+`left-click` | Select album/photo individually |
|
||||
| `right-click` | Open context-menu |
|
||||
|
||||
### Photo
|
||||
| Key | Action |
|
||||
|
@ -26,9 +26,9 @@ final class Admin extends Access {
|
||||
case 'Album::setTitle': self::setAlbumTitleAction(); break;
|
||||
case 'Album::setDescription': self::setAlbumDescriptionAction(); break;
|
||||
case 'Album::setPublic': self::setAlbumPublicAction(); break;
|
||||
case 'Album::setPosition': self::setPositionAction(); break;
|
||||
case 'Album::delete': self::deleteAlbumAction(); break;
|
||||
case 'Album::merge': self::mergeAlbumsAction(); break;
|
||||
case 'Album::move': self::moveAlbumsAction(); break;
|
||||
|
||||
// Photo functions
|
||||
case 'Photo::get': self::getPhotoAction(); break;
|
||||
@ -73,8 +73,10 @@ final class Admin extends Access {
|
||||
|
||||
private static function getAlbumsAction() {
|
||||
|
||||
Validator::required(isset($_POST['parent']), __METHOD__);
|
||||
|
||||
$albums = new Albums();
|
||||
Response::json($albums->get(false));
|
||||
Response::json($albums->get(false, $_POST['parent']));
|
||||
|
||||
}
|
||||
|
||||
@ -91,10 +93,10 @@ final class Admin extends Access {
|
||||
|
||||
private static function addAlbumAction() {
|
||||
|
||||
Validator::required(isset($_POST['title']), __METHOD__);
|
||||
Validator::required(isset($_POST['title'], $_POST['parent']), __METHOD__);
|
||||
|
||||
$album = new Album(null);
|
||||
Response::json($album->add($_POST['title']), JSON_NUMERIC_CHECK);
|
||||
Response::json($album->add($_POST['title'], $_POST['parent']), JSON_NUMERIC_CHECK);
|
||||
|
||||
}
|
||||
|
||||
@ -116,15 +118,6 @@ final class Admin extends Access {
|
||||
|
||||
}
|
||||
|
||||
private static function setPositionAction() {
|
||||
|
||||
Validator::required(isset($_POST['albumID'],$_POST['photoOrder']), __METHOD__);
|
||||
|
||||
$album = new Album($_POST['albumID']);
|
||||
Response::json($album->setPosition());
|
||||
|
||||
}
|
||||
|
||||
private static function setAlbumPublicAction() {
|
||||
|
||||
Validator::required(isset($_POST['albumID'], $_POST['password'], $_POST['visible'], $_POST['downloadable']), __METHOD__);
|
||||
@ -151,6 +144,14 @@ final class Admin extends Access {
|
||||
|
||||
}
|
||||
|
||||
private static function moveAlbumsAction() {
|
||||
|
||||
Validator::required(isset($_POST['albumIDs']), __METHOD__);
|
||||
$album = new Album($_POST['albumIDs']);
|
||||
Response::json($album->move());
|
||||
|
||||
}
|
||||
|
||||
// Photo functions
|
||||
|
||||
private static function getPhotoAction() {
|
||||
|
@ -44,8 +44,10 @@ final class Guest extends Access {
|
||||
|
||||
private static function getAlbumsAction() {
|
||||
|
||||
Validator::required(isset($_POST['parent']), __METHOD__);
|
||||
|
||||
$albums = new Albums();
|
||||
Response::json($albums->get(true));
|
||||
Response::json($albums->get(true, $_POST['parent']));
|
||||
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ final class Album {
|
||||
/**
|
||||
* @return string|false ID of the created album.
|
||||
*/
|
||||
public function add($title = 'Untitled') {
|
||||
public function add($title = 'Untitled', $parent = 0) {
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
@ -35,7 +35,7 @@ final class Album {
|
||||
$visible = 1;
|
||||
|
||||
// Database
|
||||
$query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, sysstamp, public, visible) VALUES ('?', '?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $id, $title, $sysstamp, $public, $visible));
|
||||
$query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, sysstamp, public, visible, parent) VALUES ('?', '?', '?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $id, $title, $sysstamp, $public, $visible, $parent));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Call plugins
|
||||
@ -69,6 +69,7 @@ final class Album {
|
||||
if (isset($data['description'])) $album['description'] = $data['description'];
|
||||
if (isset($data['visible'])) $album['visible'] = $data['visible'];
|
||||
if (isset($data['downloadable'])) $album['downloadable'] = $data['downloadable'];
|
||||
if (isset($data['parent'])) $album['parent'] = $data['parent'];
|
||||
|
||||
// Parse date
|
||||
$album['sysdate'] = strftime('%B %Y', $data['sysstamp']);
|
||||
@ -79,6 +80,7 @@ final class Album {
|
||||
// Parse thumbs or set default value
|
||||
$album['thumbs'] = (isset($data['thumbs']) ? explode(',', $data['thumbs']) : array());
|
||||
|
||||
|
||||
return $album;
|
||||
|
||||
}
|
||||
@ -193,12 +195,6 @@ final class Album {
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
|
||||
// Illicit chars
|
||||
$badChars = array_merge(
|
||||
array_map('chr', range(0,31)),
|
||||
array("<", ">", ":", '"', "/", "\\", "|", "?", "*")
|
||||
);
|
||||
|
||||
// Photos query
|
||||
switch($this->albumIDs) {
|
||||
case 's':
|
||||
@ -214,34 +210,34 @@ final class Album {
|
||||
$zipTitle = 'Recent';
|
||||
break;
|
||||
default:
|
||||
$photos = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
|
||||
$zipTitle = 'Unsorted';
|
||||
}
|
||||
|
||||
// Get title from database when album is not a SmartAlbum
|
||||
if ($this->albumIDs!=0&&is_numeric($this->albumIDs)) {
|
||||
// Get title from database when album is not a SmartAlbum
|
||||
if ($this->albumIDs!=0 && is_numeric($this->albumIDs)) {
|
||||
|
||||
$query = Database::prepare(Database::get(), "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs));
|
||||
$album = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
$query = Database::prepare(Database::get(), "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs));
|
||||
$album = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
if ($album===false) return false;
|
||||
if ($album===false) return false;
|
||||
|
||||
// Get album object
|
||||
$album = $album->fetch_object();
|
||||
// Get album object
|
||||
$album = $album->fetch_object();
|
||||
|
||||
// Album not found?
|
||||
if ($album===null) {
|
||||
Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album');
|
||||
return false;
|
||||
}
|
||||
// Album not found?
|
||||
if ($album===null) {
|
||||
Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set title
|
||||
$zipTitle = $album->title;
|
||||
// Set title
|
||||
$zipTitle = $album->title;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Escape title
|
||||
$zipTitle = str_replace($badChars, '', $zipTitle);
|
||||
$zipTitle = $this->cleanZipName($zipTitle);
|
||||
|
||||
$filename = LYCHEE_DATA . $zipTitle . '.zip';
|
||||
|
||||
@ -252,54 +248,16 @@ final class Album {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute query
|
||||
$photos = Database::execute(Database::get(), $photos, __METHOD__, __LINE__);
|
||||
|
||||
// Check if album empty
|
||||
if ($photos->num_rows==0) {
|
||||
Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create ZipArchive without images');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse each path
|
||||
$files = array();
|
||||
while ($photo = $photos->fetch_object()) {
|
||||
|
||||
// Parse url
|
||||
$photo->url = LYCHEE_UPLOADS_BIG . $photo->url;
|
||||
|
||||
// Parse title
|
||||
$photo->title = str_replace($badChars, '', $photo->title);
|
||||
if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled';
|
||||
|
||||
// Check if readable
|
||||
if (!@is_readable($photo->url)) continue;
|
||||
|
||||
// Get extension of image
|
||||
$extension = getExtension($photo->url, false);
|
||||
|
||||
// Set title for photo
|
||||
$zipFileName = $zipTitle . '/' . $photo->title . $extension;
|
||||
|
||||
// Check for duplicates
|
||||
if (!empty($files)) {
|
||||
$i = 1;
|
||||
while (in_array($zipFileName, $files)) {
|
||||
|
||||
// Set new title for photo
|
||||
$zipFileName = $zipTitle . '/' . $photo->title . '-' . $i . $extension;
|
||||
|
||||
$i++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add to array
|
||||
$files[] = $zipFileName;
|
||||
|
||||
// Add photo to zip
|
||||
$zip->addFile($photo->url, $zipFileName);
|
||||
|
||||
// Add photos to zip
|
||||
switch($this->albumIDs) {
|
||||
case 's':
|
||||
case 'f':
|
||||
case 'r':
|
||||
$this->addPhotosToZip($zip, $zipTitle, $photos);
|
||||
break;
|
||||
default:
|
||||
$this->addAlbumToZip($zip, $zipTitle, $this->albumIDs);
|
||||
break;
|
||||
}
|
||||
|
||||
// Finish zip
|
||||
@ -321,6 +279,84 @@ final class Album {
|
||||
|
||||
}
|
||||
|
||||
private function cleanZipName($name) {
|
||||
|
||||
// Illicit chars
|
||||
$badChars = array_merge(
|
||||
array_map('chr', range(0,31)),
|
||||
array("<", ">", ":", '"', "/", "\\", "|", "?", "*")
|
||||
);
|
||||
|
||||
return str_replace($badChars, '', $name);
|
||||
|
||||
}
|
||||
|
||||
private function addAlbumToZip($zip, $path, $albumID) {
|
||||
|
||||
// Fetch album title
|
||||
$photos = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $albumID));
|
||||
|
||||
$this->addPhotosToZip($zip, $path, $photos);
|
||||
|
||||
// Fetch subalbums
|
||||
$query = Database::prepare(Database::get(), "SELECT id, title FROM ? WHERE parent = '?'", array(LYCHEE_TABLE_ALBUMS, $albumID));
|
||||
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Add them recursively
|
||||
while($album = $albums->fetch_assoc()) {
|
||||
$this->addAlbumToZip($zip, $path . '/' . $this->cleanZipName($album['title']), $album['id']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function addPhotosToZip($zip, $path, $query) {
|
||||
|
||||
// Execute query
|
||||
$photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Parse each path
|
||||
$files = array();
|
||||
while ($photo = $photos->fetch_object()) {
|
||||
|
||||
// Parse url
|
||||
$photo->url = LYCHEE_UPLOADS_BIG . $photo->url;
|
||||
|
||||
// Parse title
|
||||
$photo->title = $this->cleanZipName($photo->title);
|
||||
if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled';
|
||||
|
||||
// Check if readable
|
||||
if (!@is_readable($photo->url)) continue;
|
||||
|
||||
// Get extension of image
|
||||
$extension = getExtension($photo->url, false);
|
||||
|
||||
// Set title for photo
|
||||
$zipFileName = $path . '/' . $photo->title . $extension;
|
||||
|
||||
// Check for duplicates
|
||||
if (!empty($files)) {
|
||||
$i = 1;
|
||||
while (in_array($zipFileName, $files)) {
|
||||
|
||||
// Set new title for photo
|
||||
$zipFileName = $path . '/' . $photo->title . '-' . $i . $extension;
|
||||
|
||||
$i++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Add to array
|
||||
$files[] = $zipFileName;
|
||||
|
||||
// Add photo to zip
|
||||
$zip->addFile($photo->url, $zipFileName);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean Returns true when successful.
|
||||
*/
|
||||
@ -344,41 +380,6 @@ final class Album {
|
||||
|
||||
}
|
||||
|
||||
public function setPosition(){
|
||||
// Check dependencies
|
||||
Validator::required(isset($_POST['photoOrder']), __METHOD__);
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
|
||||
$id_list = implode(',', $_POST['photoOrder']);
|
||||
$indices = [];
|
||||
$size = count(explode(',',$id_list));
|
||||
for($i = 0; $i < $size; $i++){
|
||||
$indices[$i] = $i;
|
||||
}
|
||||
|
||||
$whens = implode(
|
||||
" ",
|
||||
array_map(
|
||||
function ($id, $value) {
|
||||
return "WHEN {$id} THEN {$value}";
|
||||
},
|
||||
explode(',',$id_list),
|
||||
$indices
|
||||
)
|
||||
);
|
||||
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET position = CASE id ? END WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $whens, $id_list));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 1, func_get_args());
|
||||
|
||||
if ($result===false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean Returns true when successful.
|
||||
*/
|
||||
@ -474,6 +475,31 @@ final class Album {
|
||||
|
||||
}
|
||||
|
||||
private function getSubAlbums($albumID) {
|
||||
|
||||
$query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE parent = '?'", array(LYCHEE_TABLE_ALBUMS, $albumID));
|
||||
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
$ids = array();
|
||||
while($album = $albums->fetch_assoc()) {
|
||||
$ids = array_merge($ids, array($album['id']), $this->getSubAlbums($album['id']));
|
||||
}
|
||||
|
||||
return $ids;
|
||||
|
||||
}
|
||||
|
||||
private function addSubAlbumIDs($ids) {
|
||||
|
||||
$res = array();
|
||||
|
||||
foreach(explode(',', $ids) as $id)
|
||||
$res = array_merge($res, array($id), $this->getSubAlbums($id));
|
||||
|
||||
return implode(',', $res);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean Returns true when successful.
|
||||
*/
|
||||
@ -490,8 +516,11 @@ final class Album {
|
||||
$visible = ($visible==='1' ? 1 : 0);
|
||||
$downloadable = ($downloadable==='1' ? 1 : 0);
|
||||
|
||||
// Get all album ids, including subalbums
|
||||
$ids = $this->addSubAlbumIDs($this->albumIDs);
|
||||
|
||||
// Set public
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $this->albumIDs));
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $ids));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
if ($result===false) return false;
|
||||
@ -499,7 +528,7 @@ final class Album {
|
||||
// Reset permissions for photos
|
||||
if ($public===1) {
|
||||
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $ids));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
if ($result===false) return false;
|
||||
@ -608,6 +637,7 @@ final class Album {
|
||||
$albumID = array_splice($albumIDs, 0, 1);
|
||||
$albumID = $albumID[0];
|
||||
|
||||
// Move photos
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET album = ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->albumIDs));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
@ -617,6 +647,13 @@ final class Album {
|
||||
// Convert to string
|
||||
$filteredIDs = implode(',', $albumIDs);
|
||||
|
||||
// Move subalbums
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET parent = ? WHERE parent IN (?)", array(LYCHEE_TABLE_ALBUMS, $albumID, $filteredIDs));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
if ($result===false) return false;
|
||||
|
||||
// Delete other albums
|
||||
$query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $filteredIDs));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
@ -628,6 +665,40 @@ final class Album {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean Returns true when successful.
|
||||
*/
|
||||
public function move() {
|
||||
|
||||
// Check dependencies
|
||||
Validator::required(isset($this->albumIDs), __METHOD__);
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
|
||||
// Convert to array
|
||||
$albumIDs = explode(',', $this->albumIDs);
|
||||
|
||||
// Get first albumID
|
||||
$albumID = array_splice($albumIDs, 0, 1);
|
||||
$albumID = $albumID[0];
|
||||
|
||||
// $albumIDs contains all IDs without the first albumID
|
||||
// Convert to string
|
||||
$filteredIDs = implode(',', $albumIDs);
|
||||
|
||||
// Move albums
|
||||
$query = Database::prepare(Database::get(), "UPDATE ? SET parent = ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $albumID, $filteredIDs));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 1, func_get_args());
|
||||
|
||||
if ($result===false) return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean Returns true when successful.
|
||||
*/
|
||||
@ -639,11 +710,14 @@ final class Album {
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
|
||||
// Get all album ids, including subalbums
|
||||
$ids = $this->addSubAlbumIDs($this->albumIDs);
|
||||
|
||||
// Init vars
|
||||
$photoIDs = array();
|
||||
|
||||
// Execute query
|
||||
$query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
|
||||
$query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $ids));
|
||||
$photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
if ($photos===false) return false;
|
||||
@ -664,7 +738,7 @@ final class Album {
|
||||
}
|
||||
|
||||
// Delete albums
|
||||
$query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs));
|
||||
$query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $ids));
|
||||
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
||||
// Call plugins
|
||||
|
@ -16,7 +16,7 @@ final class Albums {
|
||||
/**
|
||||
* @return array|false Returns an array of albums or false on failure.
|
||||
*/
|
||||
public function get($public = true) {
|
||||
public function get($public = true, $parent = 0) {
|
||||
|
||||
// Call plugins
|
||||
Plugins::get()->activate(__METHOD__, 0, func_get_args());
|
||||
@ -32,8 +32,8 @@ final class Albums {
|
||||
if ($public===false) $return['smartalbums'] = $this->getSmartAlbums();
|
||||
|
||||
// Albums query
|
||||
if ($public===false) $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS));
|
||||
else $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0 ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS));
|
||||
if ($public===false) $query = Database::prepare(Database::get(), "SELECT id, title, public, sysstamp, password, parent FROM ? " . ($parent != -1 ? "WHERE parent = '?' " : "") . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS, $parent));
|
||||
else $query = Database::prepare(Database::get(), "SELECT id, title, public, sysstamp, password, parent FROM ? " . ($parent != -1 ? "WHERE parent = '?' " : "") . " AND public = 1 AND visible <> 0 " . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS, $parent));
|
||||
|
||||
// Execute query
|
||||
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
|
||||
|
@ -15,7 +15,8 @@ final class Database {
|
||||
'030001', // 3.0.1
|
||||
'030003', // 3.0.3
|
||||
'030100', // 3.1.0
|
||||
'030102' // 3.1.2
|
||||
'030102', // 3.1.2
|
||||
'030200' // 3.2.0
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -208,7 +208,7 @@ final class Photo {
|
||||
$info = $this->getInfo($path);
|
||||
|
||||
// Use title of file if IPTC title missing
|
||||
if ($info['title']==='') $info['title'] = substr(basename($file['name'], $extension), 0, 100);
|
||||
if ($info['title']==='') $info['title'] = substr(basename($file['name'], $extension), 0, 30);
|
||||
|
||||
if ($exists===false) {
|
||||
|
||||
@ -709,7 +709,7 @@ final class Photo {
|
||||
|
||||
// Parse photo
|
||||
$photo['sysdate'] = strftime('%d %b. %Y', substr($photo['id'], 0, -4));
|
||||
if (strlen($photo['takestamp'])>1) $photo['takedate'] = strftime('%d %b. %Y %T', $photo['takestamp']);
|
||||
if (strlen($photo['takestamp'])>1) $photo['takedate'] = strftime('%d %b. %Y', $photo['takestamp']);
|
||||
|
||||
// Parse medium
|
||||
if ($photo['medium']==='1') $photo['medium'] = LYCHEE_URL_UPLOADS_MEDIUM . $photo['url'];
|
||||
@ -832,7 +832,7 @@ final class Photo {
|
||||
}
|
||||
|
||||
// Read EXIF
|
||||
if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF, IFD0', false, false);
|
||||
if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', false, false);
|
||||
else $exif = false;
|
||||
|
||||
// EXIF Metadata
|
||||
|
@ -24,8 +24,7 @@ CREATE TABLE IF NOT EXISTS `?` (
|
||||
`album` bigint(20) unsigned NOT NULL,
|
||||
`checksum` char(40) DEFAULT NULL,
|
||||
`medium` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`position` tinyint(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `Index_album` (`album`),
|
||||
KEY `Index_star` (`star`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Update to version 3.1.7
|
||||
*/
|
||||
|
||||
use Lychee\Modules\Database;
|
||||
use Lychee\Modules\Response;
|
||||
|
||||
// Add position to photos
|
||||
$query = Database::prepare($connection, "SELECT `position` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
|
||||
$result = Database::execute($connection, $query, 'update_030107', __LINE__);
|
||||
|
||||
if ($result===false) {
|
||||
|
||||
$query = Database::prepare($connection, "ALTER TABLE `?` ADD `position` TINYINT(1) NOT NULL", array(LYCHEE_TABLE_PHOTOS));
|
||||
$result = Database::execute($connection, $query, 'update_030107', __LINE__);
|
||||
|
||||
if ($result===false) Response::error('Could not add position to database!');
|
||||
|
||||
}
|
||||
// Set version
|
||||
if (Database::setVersion($connection, '030107')===false) Response::error('Could not update version of database!');
|
||||
|
||||
?>
|
29
php/database/update_030200.php
Normal file
29
php/database/update_030200.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Update to version 3.2.0
|
||||
*/
|
||||
|
||||
use Lychee\Modules\Database;
|
||||
use Lychee\Modules\Response;
|
||||
|
||||
// Add parent field to albums
|
||||
$query = Database::prepare($connection, "SELECT `parent` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS));
|
||||
$result = Database::execute($connection, $query, "update_030200", __LINE__);
|
||||
|
||||
if ($result===false) {
|
||||
|
||||
$query = Database::prepare($connection, "ALTER TABLE `?` ADD `parent` BIGINT(14) NOT NULL DEFAULT 0", array(LYCHEE_TABLE_ALBUMS));
|
||||
$result = Database::execute($connection, $query, "update_030200", __LINE__);
|
||||
|
||||
if ($result===false) {
|
||||
Log::error($database, 'update_030200', __LINE__, 'Could not update database (' . $database->error . ')');
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set version
|
||||
// if (Database::setVersion($connection, '030200')===false) Response::error('Could not update version of database!');
|
||||
|
||||
?>
|
@ -83,7 +83,6 @@ paths.main = {
|
||||
],
|
||||
scripts: [
|
||||
'node_modules/jquery/dist/jquery.min.js',
|
||||
'node_modules/jquery-ui-dist/jquery-ui.min.js',
|
||||
'node_modules/mousetrap/mousetrap.min.js',
|
||||
'node_modules/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js',
|
||||
'node_modules/basiccontext/dist/basicContext.min.js',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Lychee",
|
||||
"version": "3.1.6",
|
||||
"version": "3.2.0",
|
||||
"description": "Self-hosted photo-management done right.",
|
||||
"authors": "Tobias Reich <tobias@electerious.com>",
|
||||
"license": "MIT",
|
||||
@ -14,21 +14,20 @@
|
||||
"compile": "gulp"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-preset-es2015": "^6.24.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"basiccontext": "^3.5.1",
|
||||
"basicmodal": "^3.3.8",
|
||||
"basicmodal": "^3.3.7",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "3.1.1",
|
||||
"gulp-babel": "^6.1.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-inject": "^4.2.0",
|
||||
"gulp-load-plugins": "^1.5.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-inject": "^4.1.0",
|
||||
"gulp-load-plugins": "^1.3.0",
|
||||
"gulp-minify-css": "^1.2.4",
|
||||
"gulp-rimraf": "^0.2.1",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-uglify": "^2.1.1",
|
||||
"jquery": "^3.2.0",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"gulp-rimraf": "^0.2.0",
|
||||
"gulp-sass": "^2.3.2",
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"jquery": "^3.1.1",
|
||||
"mousetrap": "^1.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,36 @@
|
||||
* @description Takes care of every action an album can handle and execute.
|
||||
*/
|
||||
|
||||
const buildAlbumOptions = function(albums, select, parent = 0, layer = 0) {
|
||||
|
||||
var cmbxOptions = ''
|
||||
|
||||
for (i in albums) {
|
||||
if (albums[i].parent==parent) {
|
||||
|
||||
let title = (layer>0 ? ' '.repeat(layer - 1) + '└ ' : '') + albums[i].title
|
||||
let selected = select==albums[i].id ? ' selected="selected"' : ''
|
||||
|
||||
cmbxOptions += `<option${ selected } value='${ albums[i].id }'>${ title }</option>`
|
||||
cmbxOptions += buildAlbumOptions(albums, select, albums[i].id, layer + 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return cmbxOptions
|
||||
|
||||
}
|
||||
|
||||
album = {
|
||||
|
||||
json: null
|
||||
json: null,
|
||||
subjson: null
|
||||
|
||||
}
|
||||
|
||||
album.isSmartID = function(id) {
|
||||
|
||||
return (id==='0' || id==='f' || id==='s' || id==='r')
|
||||
|
||||
}
|
||||
|
||||
@ -13,38 +40,34 @@ album.getID = function() {
|
||||
let id = null
|
||||
|
||||
let isID = (id) => {
|
||||
if (id==='0' || id==='f' || id==='s' || id==='r') return true
|
||||
if (album.isSmartID(id)===true) return true
|
||||
return $.isNumeric(id)
|
||||
}
|
||||
|
||||
if (photo.json) id = photo.json.album
|
||||
else if (album.json) id = album.json.id
|
||||
|
||||
// Search
|
||||
if (isID(id)===false) id = $('.album:hover, .album.active').attr('data-id')
|
||||
if (isID(id)===false) id = $('.photo:hover, .photo.active').attr('data-album-id')
|
||||
|
||||
if (isID(id)===false) {
|
||||
if (photo.json) id = photo.json.album
|
||||
else if (album.json) id = album.json.id
|
||||
}
|
||||
|
||||
if (isID(id)===true) return id
|
||||
else return false
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
album.getParent = function() {
|
||||
|
||||
if (album.json==null || album.isSmartID(album.json.id)===true || album.json.parent==0) return ''
|
||||
|
||||
return album.json.parent
|
||||
|
||||
}
|
||||
|
||||
album.load = function(albumID, refresh = false) {
|
||||
$('.content').sortable({
|
||||
stop: function(event, ui){
|
||||
let albumID = ui.item.data('album-id');
|
||||
let photoOrder = $('.content').children().map(function(){
|
||||
return $(this).data('id');
|
||||
}).get();
|
||||
let params = {
|
||||
albumID: albumID,
|
||||
photoOrder: photoOrder
|
||||
};
|
||||
api.post('Album::setPosition', params, function(data) {
|
||||
if (data!==true) lychee.error(null, params, data)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
password.get(albumID, function() {
|
||||
|
||||
@ -93,17 +116,45 @@ album.load = function(albumID, refresh = false) {
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
view.album.init()
|
||||
let finish = function() {
|
||||
view.album.init()
|
||||
|
||||
if (refresh===false) {
|
||||
lychee.animate(lychee.content, 'contentZoomIn')
|
||||
header.setMode('album')
|
||||
}
|
||||
}
|
||||
|
||||
if (album.isSmartID(albumID)===false) {
|
||||
|
||||
params = {
|
||||
parent: albumID
|
||||
}
|
||||
|
||||
api.post('Albums::get', params, function(data) {
|
||||
|
||||
let waitTime = 0
|
||||
|
||||
album.subjson = data
|
||||
|
||||
// Calculate delay
|
||||
let durationTime = (new Date().getTime() - startTime)
|
||||
if (durationTime>300) waitTime = 0
|
||||
else waitTime = 300 - durationTime
|
||||
|
||||
setTimeout(finish, waitTime)
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
finish()
|
||||
|
||||
if (refresh===false) {
|
||||
lychee.animate(lychee.content, 'contentZoomIn')
|
||||
header.setMode('album')
|
||||
}
|
||||
|
||||
}, waitTime)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@ -114,18 +165,20 @@ album.parse = function() {
|
||||
|
||||
}
|
||||
|
||||
album.add = function() {
|
||||
album.add = function(albumID = 0) {
|
||||
|
||||
const action = function(data) {
|
||||
|
||||
let title = data.title
|
||||
let parent = albumID
|
||||
|
||||
const isNumber = (n) => (!isNaN(parseFloat(n)) && isFinite(n))
|
||||
|
||||
basicModal.close()
|
||||
|
||||
let params = {
|
||||
title
|
||||
title,
|
||||
parent
|
||||
}
|
||||
|
||||
api.post('Album::add', params, function(data) {
|
||||
@ -141,18 +194,26 @@ album.add = function() {
|
||||
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: `<p>Enter a title for the new album: <input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='Untitled'></p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Create Album',
|
||||
fn: action
|
||||
},
|
||||
cancel: {
|
||||
title: 'Cancel',
|
||||
fn: basicModal.close
|
||||
api.post('Albums::get', { parent: -1 }, function(data) {
|
||||
|
||||
let msg = `
|
||||
<p>Enter a title for the new album: <input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='Untitled'></p>
|
||||
`
|
||||
|
||||
basicModal.show({
|
||||
body: msg,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Create Album',
|
||||
fn: action
|
||||
},
|
||||
cancel: {
|
||||
title: 'Cancel',
|
||||
fn: basicModal.close
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@ -184,6 +245,21 @@ album.delete = function(albumIDs) {
|
||||
albums.deleteByID(id)
|
||||
})
|
||||
|
||||
} else if (visible.album()) {
|
||||
|
||||
// if we deleted the current album, go to its parent
|
||||
if (albumIDs.length==1 && album.json.id==albumIDs[0]) {
|
||||
|
||||
let id = album.getParent()
|
||||
album.refresh()
|
||||
lychee.goto(id)
|
||||
|
||||
}
|
||||
// otherwise, we deleted a subalbum
|
||||
else {
|
||||
album.reload()
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
albums.refresh()
|
||||
@ -212,20 +288,20 @@ album.delete = function(albumIDs) {
|
||||
cancel.title = 'Keep Album'
|
||||
|
||||
// Get title
|
||||
if (album.json) albumTitle = album.json.title
|
||||
else if (albums.json) albumTitle = albums.getByID(albumIDs).title
|
||||
if (album.json && album.json.id == albumIDs[0]) albumTitle = album.json.title
|
||||
else if (albums.json || album.subjson) albumTitle = albums.getByID(albumIDs).title
|
||||
|
||||
// Fallback for album without a title
|
||||
if (albumTitle==='') albumTitle = 'Untitled'
|
||||
|
||||
msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos and subalbums it contains? This action can't be undone!</p>`
|
||||
|
||||
} else {
|
||||
|
||||
action.title = 'Delete Albums and Photos'
|
||||
cancel.title = 'Keep Albums'
|
||||
|
||||
msg = lychee.html`<p>Are you sure you want to delete all $${ albumIDs.length } selected albums and all of the photos they contain? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete all $${ albumIDs.length } selected albums and all of the photos and subalbums they contain? This action can't be undone!</p>`
|
||||
|
||||
}
|
||||
|
||||
@ -257,8 +333,8 @@ album.setTitle = function(albumIDs) {
|
||||
if (albumIDs.length===1) {
|
||||
|
||||
// Get old title if only one album is selected
|
||||
if (album.json) oldTitle = album.json.title
|
||||
else if (albums.json) oldTitle = albums.getByID(albumIDs).title
|
||||
if (album.json && album.json.id == albumIDs[0]) oldTitle = album.json.title
|
||||
else if (albums.json || album.subjson) oldTitle = albums.getByID(albumIDs).title
|
||||
|
||||
}
|
||||
|
||||
@ -272,10 +348,20 @@ album.setTitle = function(albumIDs) {
|
||||
|
||||
// Rename only one album
|
||||
|
||||
album.json.title = newTitle
|
||||
view.album.title()
|
||||
if (album.json.id == albumIDs[0]) {
|
||||
album.json.title = newTitle
|
||||
view.album.title()
|
||||
}
|
||||
|
||||
if (albums.json) albums.getByID(albumIDs[0]).title = newTitle
|
||||
if (albums.json || album.subjson) {
|
||||
albumIDs.forEach(function(id) {
|
||||
let a = albums.getByID(id)
|
||||
if (a) {
|
||||
a.title = newTitle
|
||||
view.album.content.title(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} else if (visible.albums()) {
|
||||
|
||||
@ -553,7 +639,7 @@ album.getArchive = function(albumID) {
|
||||
|
||||
}
|
||||
|
||||
album.merge = function(albumIDs) {
|
||||
const getMessage = function(albumIDs, titles, operation) {
|
||||
|
||||
let title = ''
|
||||
let sTitle = ''
|
||||
@ -563,7 +649,8 @@ album.merge = function(albumIDs) {
|
||||
if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ]
|
||||
|
||||
// Get title of first album
|
||||
if (albums.json) title = albums.getByID(albumIDs[0]).title
|
||||
if (titles.length > 0) title = titles[0]
|
||||
else if (albums.json || album.subjson) title = albums.getByID(albumIDs[0]).title
|
||||
|
||||
// Fallback for first album without a title
|
||||
if (title==='') title = 'Untitled'
|
||||
@ -571,19 +658,26 @@ album.merge = function(albumIDs) {
|
||||
if (albumIDs.length===2) {
|
||||
|
||||
// Get title of second album
|
||||
if (albums.json) sTitle = albums.getByID(albumIDs[1]).title
|
||||
if (titles.length > 1) sTitle = titles[1]
|
||||
else if (albums.json || album.subjson) sTitle = albums.getByID(albumIDs[1]).title
|
||||
|
||||
// Fallback for second album without a title
|
||||
if (sTitle==='') sTitle = 'Untitled'
|
||||
|
||||
msg = lychee.html`<p>Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to ${ operation } the album '$${ sTitle }' into '$${ title }'?</p>`
|
||||
|
||||
} else {
|
||||
|
||||
msg = lychee.html`<p>Are you sure you want to merge all selected albums into the album '$${ title }'?</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to ${ operation } all selected albums into '$${ title }'?</p>`
|
||||
|
||||
}
|
||||
|
||||
return msg
|
||||
|
||||
}
|
||||
|
||||
album.merge = function(albumIDs, titles = []) {
|
||||
|
||||
const action = function() {
|
||||
|
||||
basicModal.close()
|
||||
@ -594,19 +688,15 @@ album.merge = function(albumIDs) {
|
||||
|
||||
api.post('Album::merge', params, function(data) {
|
||||
|
||||
if (data!==true) {
|
||||
lychee.error(null, params, data)
|
||||
} else {
|
||||
albums.refresh()
|
||||
lychee.goto()
|
||||
}
|
||||
if (data!==true) lychee.error(null, params, data)
|
||||
else album.reload()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: msg,
|
||||
body: getMessage(albumIDs, titles, 'merge'),
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Merge Albums',
|
||||
@ -620,4 +710,59 @@ album.merge = function(albumIDs) {
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
album.move = function(albumIDs, titles = []) {
|
||||
|
||||
const action = function() {
|
||||
|
||||
basicModal.close()
|
||||
|
||||
let params = {
|
||||
albumIDs: albumIDs.join()
|
||||
}
|
||||
|
||||
api.post('Album::move', params, function(data) {
|
||||
|
||||
if (data!==true) lychee.error(null, params, data)
|
||||
else album.reload()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: getMessage(albumIDs, titles, 'move'),
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Move Albums',
|
||||
fn: action,
|
||||
class: 'red'
|
||||
},
|
||||
cancel: {
|
||||
title: "Don't Move",
|
||||
fn: basicModal.close
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
album.reload = function() {
|
||||
|
||||
let albumID = album.getID()
|
||||
|
||||
album.refresh()
|
||||
albums.refresh()
|
||||
|
||||
if (visible.album()) lychee.goto(albumID)
|
||||
else lychee.goto()
|
||||
|
||||
}
|
||||
|
||||
album.refresh = function() {
|
||||
|
||||
album.json = null
|
||||
album.subjson = null
|
||||
|
||||
}
|
@ -16,7 +16,11 @@ albums.load = function() {
|
||||
|
||||
if (albums.json===null) {
|
||||
|
||||
api.post('Albums::get', {}, function(data) {
|
||||
let params = {
|
||||
parent: 0
|
||||
}
|
||||
|
||||
api.post('Albums::get', params, function(data) {
|
||||
|
||||
let waitTime = 0
|
||||
|
||||
@ -108,18 +112,21 @@ albums.getByID = function(albumID) {
|
||||
// Function returns the JSON of an album
|
||||
|
||||
if (albumID==null) return undefined
|
||||
if (!albums.json) return undefined
|
||||
if (!albums.json.albums) return undefined
|
||||
if (albumID instanceof Array)
|
||||
albumID = albumID[0]
|
||||
|
||||
let json = undefined
|
||||
|
||||
$.each(albums.json.albums, function(i) {
|
||||
let func = function() {
|
||||
if (this.id==albumID) json = this
|
||||
}
|
||||
|
||||
let elem = albums.json.albums[i]
|
||||
|
||||
if (elem.id==albumID) json = elem
|
||||
|
||||
})
|
||||
if (albums.json && albums.json.albums) {
|
||||
$.each(albums.json.albums, func)
|
||||
}
|
||||
else if (album.subjson && album.subjson.albums) {
|
||||
$.each(album.subjson.albums, func)
|
||||
}
|
||||
|
||||
return json
|
||||
|
||||
|
@ -14,11 +14,11 @@ build.iconic = function(icon, classes = '') {
|
||||
|
||||
}
|
||||
|
||||
build.divider = function(title) {
|
||||
build.divider = function(title, id = '') {
|
||||
|
||||
let html = ''
|
||||
|
||||
html += lychee.html`<div class='divider'><h1>$${ title }</h1></div>`
|
||||
html += lychee.html`<div class='divider' id='$${ id }'><h1>$${ title }</h1></div>`
|
||||
|
||||
return html
|
||||
|
||||
|
@ -2,9 +2,75 @@
|
||||
* @description This module is used for the context menu.
|
||||
*/
|
||||
|
||||
const buildAlbumList = function(albums, exclude, action, parent = 0, layer = 0) {
|
||||
|
||||
let items = []
|
||||
|
||||
for (i in albums) {
|
||||
if ((layer==0 && !albums[i].parent) || albums[i].parent==parent) {
|
||||
|
||||
let album = albums[i]
|
||||
|
||||
let thumb = 'src/images/no_cover.svg'
|
||||
if (album.thumbs && album.thumbs[0]) thumb = album.thumbs[0]
|
||||
else if (album.thumbUrl) thumb = album.thumbUrl
|
||||
|
||||
if (album.title==='') album.title = 'Untitled'
|
||||
|
||||
let prefix = (layer > 0 ? ' '.repeat(layer - 1) + '└ ' : '')
|
||||
|
||||
let html = lychee.html`
|
||||
${ prefix }
|
||||
<img class='cover' width='16' height='16' src='$${ thumb }'>
|
||||
<div class='title'>$${ album.title }</div>
|
||||
`
|
||||
|
||||
items.push({
|
||||
title: html,
|
||||
disabled: (exclude.indexOf(album.id)!==-1),
|
||||
fn: () => action(album)
|
||||
})
|
||||
|
||||
items = items.concat(buildAlbumList(albums, exclude, action, album.id, layer + 1))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
|
||||
}
|
||||
|
||||
const getAlbumFrom = function(albums, id) {
|
||||
|
||||
for (a in albums) {
|
||||
if (albums[a].id == id) return albums[a]
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
const getSubIDs = function(albums, albumID) {
|
||||
|
||||
let ids = [ albumID ]
|
||||
|
||||
for (a in albums) {
|
||||
if (albums[a].parent==albumID) {
|
||||
|
||||
let sub = getSubIDs(albums, albums[a].id)
|
||||
for (id in sub)
|
||||
ids.push(sub[id])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
|
||||
}
|
||||
|
||||
contextMenu = {}
|
||||
|
||||
contextMenu.add = function(e) {
|
||||
contextMenu.add = function(albumID, e) {
|
||||
|
||||
let items = [
|
||||
{ title: build.iconic('image') + 'Upload Photo', fn: () => $('#upload_files').click() },
|
||||
@ -13,7 +79,7 @@ contextMenu.add = function(e) {
|
||||
{ title: build.iconic('dropbox', 'ionicons') + 'Import from Dropbox', fn: upload.start.dropbox },
|
||||
{ title: build.iconic('terminal') + 'Import from Server', fn: upload.start.server },
|
||||
{ },
|
||||
{ title: build.iconic('folder') + 'New Album', fn: album.add }
|
||||
{ title: build.iconic('folder') + 'New Album', fn: () => album.add(albumID) }
|
||||
]
|
||||
|
||||
basicContext.show(items, e.originalEvent)
|
||||
@ -46,18 +112,16 @@ contextMenu.album = function(albumID, e) {
|
||||
// fn must call basicContext.close() first,
|
||||
// in order to keep the selection
|
||||
|
||||
if (albumID==='0' || albumID==='f' || albumID==='s' || albumID==='r') return false
|
||||
|
||||
// Show merge-item when there's more than one album
|
||||
let showMerge = (albums.json && albums.json.albums && Object.keys(albums.json.albums).length>1)
|
||||
if (album.isSmartID(albumID)) return false
|
||||
|
||||
let items = [
|
||||
{ title: build.iconic('pencil') + 'Rename', fn: () => album.setTitle([ albumID ]) },
|
||||
{ title: build.iconic('collapse-left') + 'Merge', visible: showMerge, fn: () => { basicContext.close(); contextMenu.mergeAlbum(albumID, e) } },
|
||||
{ title: build.iconic('collapse-left') + 'Merge', fn: () => { basicContext.close(); contextMenu.mergeAlbum(albumID, e) } },
|
||||
{ title: build.iconic('folder') + 'Move', fn: () => { basicContext.close(); contextMenu.moveAlbum([ albumID ], e) } },
|
||||
{ title: build.iconic('trash') + 'Delete', fn: () => album.delete([ albumID ]) }
|
||||
]
|
||||
|
||||
$('.album[data-id="' + albumID + '"]').addClass('active')
|
||||
multiselect.select('.album[data-id="' + albumID + '"]')
|
||||
|
||||
basicContext.show(items, e.originalEvent, contextMenu.close)
|
||||
|
||||
@ -71,13 +135,11 @@ contextMenu.albumMulti = function(albumIDs, e) {
|
||||
// Show list of albums otherwise
|
||||
let autoMerge = (albumIDs.length>1 ? true : false)
|
||||
|
||||
// Show merge-item when there's more than one album
|
||||
let showMerge = (albums.json && albums.json.albums && Object.keys(albums.json.albums).length>1)
|
||||
|
||||
let items = [
|
||||
{ title: build.iconic('pencil') + 'Rename All', fn: () => album.setTitle(albumIDs) },
|
||||
{ title: build.iconic('collapse-left') + 'Merge All', visible: showMerge && autoMerge, fn: () => album.merge(albumIDs) },
|
||||
{ title: build.iconic('collapse-left') + 'Merge', visible: showMerge && !autoMerge, fn: () => { basicContext.close(); contextMenu.mergeAlbum(albumIDs[0], e) } },
|
||||
{ title: build.iconic('collapse-left') + 'Merge All', visible: autoMerge, fn: () => album.merge(albumIDs) },
|
||||
{ title: build.iconic('collapse-left') + 'Merge', visible: !autoMerge, fn: () => { basicContext.close(); contextMenu.mergeAlbum(albumIDs[0], e) } },
|
||||
{ title: build.iconic('folder') + 'Move All', fn: () => { basicContext.close(); contextMenu.moveAlbum(albumIDs, e) } },
|
||||
{ title: build.iconic('trash') + 'Delete All', fn: () => album.delete(albumIDs) }
|
||||
]
|
||||
|
||||
@ -89,26 +151,13 @@ contextMenu.albumMulti = function(albumIDs, e) {
|
||||
|
||||
contextMenu.albumTitle = function(albumID, e) {
|
||||
|
||||
api.post('Albums::get', {}, function(data) {
|
||||
api.post('Albums::get', { parent: -1 }, function(data) {
|
||||
|
||||
let items = []
|
||||
|
||||
if (data.albums && data.num>1) {
|
||||
|
||||
// Generate list of albums
|
||||
$.each(data.albums, function() {
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
if (this.title==='') this.title = 'Untitled'
|
||||
|
||||
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=albumID) items.push({
|
||||
title: html,
|
||||
fn: () => lychee.goto(this.id)
|
||||
})
|
||||
|
||||
})
|
||||
items = buildAlbumList(data.albums, [ albumID ], (a) => lychee.goto(a.id))
|
||||
|
||||
items.unshift({ })
|
||||
|
||||
@ -124,25 +173,57 @@ contextMenu.albumTitle = function(albumID, e) {
|
||||
|
||||
contextMenu.mergeAlbum = function(albumID, e) {
|
||||
|
||||
api.post('Albums::get', {}, function(data) {
|
||||
api.post('Albums::get', { parent: -1 }, function(data) {
|
||||
|
||||
let items = []
|
||||
|
||||
if (data.albums && data.num>1) {
|
||||
|
||||
$.each(data.albums, function() {
|
||||
let selalbum = albums.getByID(albumID)
|
||||
let title = selalbum.title
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
if (this.title==='') this.title = 'Untitled'
|
||||
// Disable all parents
|
||||
// It's not possible to move them into us
|
||||
let exclude = [ albumID ]
|
||||
let a = getAlbumFrom(data.albums, selalbum.parent)
|
||||
while (a!=null) {
|
||||
exclude.push(a.id)
|
||||
a = getAlbumFrom(data.albums, a.parent)
|
||||
}
|
||||
|
||||
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
items = buildAlbumList(data.albums, exclude, (a) => album.merge([ albumID, a.id ], [ title, a.title ]))
|
||||
|
||||
if (this.id!=albumID) items.push({
|
||||
title: html,
|
||||
fn: () => album.merge([ albumID, this.id ])
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
if (items.length===0) return false
|
||||
|
||||
basicContext.show(items, e.originalEvent, contextMenu.close)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
contextMenu.moveAlbum = function(albumIDs, e) {
|
||||
|
||||
api.post('Albums::get', { parent: -1 }, function(data) {
|
||||
|
||||
let items = []
|
||||
|
||||
if (data.albums && data.num>1) {
|
||||
|
||||
let title = albums.getByID(albumIDs[0]).title
|
||||
// Disable all childs
|
||||
// It's not possible to move us into them
|
||||
let exclude = []
|
||||
for (i in albumIDs) {
|
||||
let sub = getSubIDs(data.albums, String(albumIDs[i]))
|
||||
for (s in sub)
|
||||
exclude.push(sub[s])
|
||||
}
|
||||
|
||||
items = buildAlbumList(data.albums, exclude, (a) => album.move([ a.id ].concat(albumIDs), [ a.title, title ]), 0, 1)
|
||||
|
||||
items.unshift({ title: 'Root', fn: () => album.move([ 0 ].concat(albumIDs), [ 'Root', title ]) })
|
||||
|
||||
}
|
||||
|
||||
@ -170,7 +251,7 @@ contextMenu.photo = function(photoID, e) {
|
||||
{ title: build.iconic('trash') + 'Delete', fn: () => photo.delete([ photoID ]) }
|
||||
]
|
||||
|
||||
$('.photo[data-id="' + photoID + '"]').addClass('active')
|
||||
multiselect.select('.photo[data-id="' + photoID + '"]')
|
||||
|
||||
basicContext.show(items, e.originalEvent, contextMenu.close)
|
||||
|
||||
@ -178,12 +259,12 @@ contextMenu.photo = function(photoID, e) {
|
||||
|
||||
contextMenu.photoMulti = function(photoIDs, e) {
|
||||
|
||||
multiselect.stopResize()
|
||||
|
||||
// Notice for 'Move All':
|
||||
// fn must call basicContext.close() first,
|
||||
// in order to keep the selection and multiselect
|
||||
|
||||
multiselect.stopResize()
|
||||
|
||||
let items = [
|
||||
{ title: build.iconic('star') + 'Star All', fn: () => photo.setStar(photoIDs) },
|
||||
{ title: build.iconic('tag') + 'Tag All', fn: () => photo.editTags(photoIDs) },
|
||||
@ -210,19 +291,7 @@ contextMenu.photoTitle = function(albumID, photoID, e) {
|
||||
|
||||
items.push({ })
|
||||
|
||||
// Generate list of albums
|
||||
$.each(data.content, function(index) {
|
||||
|
||||
if (this.title==='') this.title = 'Untitled'
|
||||
|
||||
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=photoID) items.push({
|
||||
title: html,
|
||||
fn: () => lychee.goto(albumID + '/' + this.id)
|
||||
})
|
||||
|
||||
})
|
||||
items = items.concat(buildAlbumList(data.content, [ photoID ], (a) => lychee.goto(albumID + '/' + a.id)))
|
||||
|
||||
}
|
||||
|
||||
@ -250,7 +319,7 @@ contextMenu.move = function(photoIDs, e) {
|
||||
|
||||
let items = []
|
||||
|
||||
api.post('Albums::get', {}, function(data) {
|
||||
api.post('Albums::get', { parent: -1 }, function(data) {
|
||||
|
||||
if (data.num===0) {
|
||||
|
||||
@ -261,20 +330,7 @@ contextMenu.move = function(photoIDs, e) {
|
||||
|
||||
} else {
|
||||
|
||||
// Generate list of albums
|
||||
$.each(data.albums, function() {
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
if (this.title==='') this.title = 'Untitled'
|
||||
|
||||
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=album.getID()) items.push({
|
||||
title: html,
|
||||
fn: () => photo.setAlbum(photoIDs, this.id)
|
||||
})
|
||||
|
||||
})
|
||||
items = buildAlbumList(data.albums, [ album.getID() ], (a) => photo.setAlbum(photoIDs, a.id))
|
||||
|
||||
// Show Unsorted when unsorted is not the current album
|
||||
if (album.getID()!=='0') {
|
||||
@ -344,7 +400,7 @@ contextMenu.close = function() {
|
||||
|
||||
basicContext.close()
|
||||
|
||||
$('.photo.active, .album.active').removeClass('active')
|
||||
multiselect.deselect('.photo.active, .album.active')
|
||||
if (visible.multiselect()) multiselect.close()
|
||||
|
||||
}
|
@ -43,7 +43,7 @@ header.bind = function() {
|
||||
header.dom('#button_settings') .on(eventName, contextMenu.settings)
|
||||
header.dom('#button_info_album') .on(eventName, sidebar.toggle)
|
||||
header.dom('#button_info') .on(eventName, sidebar.toggle)
|
||||
header.dom('.button_add') .on(eventName, contextMenu.add)
|
||||
header.dom('.button_add') .on(eventName, function(e) { contextMenu.add(album.getID(), e) })
|
||||
header.dom('#button_more') .on(eventName, function(e) { contextMenu.photoMore(photo.getID(), e) })
|
||||
header.dom('#button_move') .on(eventName, function(e) { contextMenu.move([ photo.getID() ], e) })
|
||||
header.dom('.header__hostedwith') .on(eventName, function() { window.open(lychee.website) })
|
||||
@ -51,7 +51,7 @@ header.bind = function() {
|
||||
header.dom('#button_trash') .on(eventName, function() { photo.delete([ photo.getID() ]) })
|
||||
header.dom('#button_archive') .on(eventName, function() { album.getArchive(album.getID()) })
|
||||
header.dom('#button_star') .on(eventName, function() { photo.setStar([ photo.getID() ]) })
|
||||
header.dom('#button_back_home') .on(eventName, function() { lychee.goto() })
|
||||
header.dom('#button_back_home') .on(eventName, function() { lychee.goto(album.getParent()) })
|
||||
header.dom('#button_back') .on(eventName, function() { lychee.goto(album.getID()) })
|
||||
|
||||
header.dom('.header__search').on('keyup click', function() { search.find($(this).val()) })
|
||||
@ -132,8 +132,8 @@ header.setMode = function(mode) {
|
||||
header.dom('.header__toolbar--album').addClass('header__toolbar--visible')
|
||||
|
||||
// Hide download button when album empty
|
||||
if (album.json.content===false) $('#button_archive').hide()
|
||||
else $('#button_archive').show()
|
||||
if (album.json.content===false && album.subjson.num==0) $('#button_archive').hide()
|
||||
else $('#button_archive').show()
|
||||
|
||||
// Hide download button when not logged in and album not downloadable
|
||||
if (lychee.publicMode===true && album.json.downloadable==='0') $('#button_archive').hide()
|
||||
|
@ -54,10 +54,6 @@ $(document).ready(function() {
|
||||
if (visible.photo() && basicModal.visible()===false) { photo.delete([photo.getID()]); return false }
|
||||
else if (visible.album() && basicModal.visible()===false) { album.delete([album.getID()]); return false }
|
||||
})
|
||||
.bind([ 'command+a', 'ctrl+a' ], function() {
|
||||
if (visible.album() && basicModal.visible()===false) { multiselect.selectAll(); return false }
|
||||
else if (visible.albums() && basicModal.visible()===false) { multiselect.selectAll(); return false }
|
||||
})
|
||||
|
||||
Mousetrap.bindGlobal('enter', function() {
|
||||
if (basicModal.visible()===true) basicModal.action()
|
||||
@ -67,7 +63,7 @@ $(document).ready(function() {
|
||||
if (basicModal.visible()===true) basicModal.cancel()
|
||||
else if (visible.contextMenu()) contextMenu.close()
|
||||
else if (visible.photo()) lychee.goto(album.getID())
|
||||
else if (visible.album()) lychee.goto()
|
||||
else if (visible.album()) lychee.goto(album.getParent())
|
||||
else if (visible.albums() && header.dom('.header__search').val().length!==0) search.reset()
|
||||
return false
|
||||
})
|
||||
@ -95,12 +91,12 @@ $(document).ready(function() {
|
||||
$(document)
|
||||
|
||||
// Navigation
|
||||
.on('click', '.album', function() { lychee.goto($(this).attr('data-id')) })
|
||||
.on('click', '.photo', function() { lychee.goto(album.getID() + '/' + $(this).attr('data-id')) })
|
||||
.on('click', '.album', function(e) { multiselect.albumClick(e, $(this)) })
|
||||
.on('click', '.photo', function(e) { multiselect.photoClick(e, $(this)) })
|
||||
|
||||
// Context Menu
|
||||
.on('contextmenu', '.photo', function(e) { contextMenu.photo(photo.getID(), e) })
|
||||
.on('contextmenu', '.album', function(e) { contextMenu.album(album.getID(), e) })
|
||||
.on('contextmenu', '.photo', function(e) { multiselect.photoContextMenu(e, $(this)) })
|
||||
.on('contextmenu', '.album', function(e) { multiselect.albumContextMenu(e, $(this)) })
|
||||
|
||||
// Upload
|
||||
.on('change', '#upload_files', function() { basicModal.close(); upload.start.local(this.files) })
|
||||
|
@ -5,8 +5,8 @@
|
||||
lychee = {
|
||||
|
||||
title : document.title,
|
||||
version : '3.1.7',
|
||||
versionCode : '030107',
|
||||
version : '3.2.0',
|
||||
versionCode : '030200',
|
||||
|
||||
updatePath : '//update.electerious.com/index.json',
|
||||
updateURL : 'https://github.com/electerious/Lychee',
|
||||
@ -169,6 +169,7 @@ lychee.load = function() {
|
||||
|
||||
// Trash data
|
||||
photo.json = null
|
||||
albums.json = null
|
||||
|
||||
// Show Photo
|
||||
if (lychee.content.html()==='' || (header.dom('.header__search').length && header.dom('.header__search').val().length!==0)) {
|
||||
@ -181,10 +182,11 @@ lychee.load = function() {
|
||||
|
||||
// Trash data
|
||||
photo.json = null
|
||||
albums.json = null
|
||||
|
||||
// Show Album
|
||||
if (visible.photo()) view.photo.hide()
|
||||
if (visible.sidebar() && (albumID==='0' || albumID==='f' || albumID==='s' || albumID==='r')) sidebar.toggle()
|
||||
if (visible.sidebar() && album.isSmartID(albumID)) sidebar.toggle()
|
||||
if (album.json && albumID==album.json.id) view.album.title()
|
||||
else album.load(albumID)
|
||||
|
||||
@ -197,6 +199,7 @@ lychee.load = function() {
|
||||
}
|
||||
|
||||
// Trash data
|
||||
album.subjson = null
|
||||
album.json = null
|
||||
photo.json = null
|
||||
|
||||
@ -426,4 +429,4 @@ lychee.error = function(errorThrown, params, data) {
|
||||
|
||||
loadingBar.show('error', errorThrown)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,19 @@
|
||||
* @description Select multiple albums or photos.
|
||||
*/
|
||||
|
||||
multiselect = {}
|
||||
const isSelectKeyPressed = function(e) {
|
||||
|
||||
return e.metaKey || e.ctrlKey
|
||||
|
||||
}
|
||||
|
||||
multiselect = {
|
||||
|
||||
ids : [],
|
||||
albumsSelected : 0,
|
||||
photosSelected : 0
|
||||
|
||||
}
|
||||
|
||||
multiselect.position = {
|
||||
|
||||
@ -21,6 +33,121 @@ multiselect.bind = function() {
|
||||
|
||||
}
|
||||
|
||||
multiselect.isSelected = function(id) {
|
||||
|
||||
let pos = $.inArray(id, multiselect.ids)
|
||||
|
||||
return {
|
||||
selected : (pos===-1 ? false : true),
|
||||
position : pos
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
multiselect.toggleItem = function(object, id) {
|
||||
|
||||
let selected = multiselect.isSelected(id).selected
|
||||
|
||||
if (selected===false) multiselect.addItem(object, id)
|
||||
else multiselect.removeItem(object, id)
|
||||
|
||||
}
|
||||
|
||||
multiselect.addItem = function(object, id) {
|
||||
|
||||
if (album.isSmartID(id)) return
|
||||
if (multiselect.isSelected(id).selected===true) return
|
||||
|
||||
let isAlbum = object.hasClass('album')
|
||||
|
||||
if ((isAlbum && multiselect.photosSelected > 0) ||
|
||||
(!isAlbum && multiselect.albumsSelected > 0)) {
|
||||
lychee.error('Please select either albums or photos!')
|
||||
return
|
||||
}
|
||||
|
||||
multiselect.ids.push(id)
|
||||
multiselect.select(object)
|
||||
|
||||
if (isAlbum) multiselect.albumsSelected++
|
||||
else multiselect.photosSelected++
|
||||
|
||||
}
|
||||
|
||||
multiselect.removeItem = function(object, id) {
|
||||
|
||||
let { selected, pos } = multiselect.isSelected(id)
|
||||
|
||||
if (selected===false) return
|
||||
|
||||
multiselect.ids.splice(pos, 1)
|
||||
multiselect.deselect(object)
|
||||
|
||||
let isAlbum = object.hasClass('album')
|
||||
|
||||
if (isAlbum) multiselect.albumsSelected--
|
||||
else multiselect.photosSelected--
|
||||
|
||||
}
|
||||
|
||||
multiselect.albumClick = function(e, albumObj) {
|
||||
|
||||
let id = albumObj.attr('data-id')
|
||||
|
||||
if (isSelectKeyPressed(e)) multiselect.toggleItem(albumObj, id)
|
||||
else lychee.goto(id)
|
||||
|
||||
}
|
||||
|
||||
multiselect.photoClick = function(e, photoObj) {
|
||||
|
||||
let id = photoObj.attr('data-id')
|
||||
|
||||
if (isSelectKeyPressed(e)) multiselect.toggleItem(photoObj, id)
|
||||
else lychee.goto(album.getID() + '/' + id)
|
||||
|
||||
}
|
||||
|
||||
multiselect.albumContextMenu = function(e, albumObj) {
|
||||
|
||||
let id = albumObj.attr('data-id')
|
||||
let selected = multiselect.isSelected(id).selected
|
||||
|
||||
if (selected!==false) {
|
||||
contextMenu.albumMulti(multiselect.ids, e)
|
||||
multiselect.clearSelection(false)
|
||||
} else {
|
||||
multiselect.clearSelection()
|
||||
contextMenu.album(album.getID(), e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
multiselect.photoContextMenu = function(e, photoObj) {
|
||||
|
||||
let id = photoObj.attr('data-id')
|
||||
let selected = multiselect.isSelected(id).selected
|
||||
|
||||
if (selected!==false) {
|
||||
contextMenu.photoMulti(multiselect.ids, e)
|
||||
multiselect.clearSelection(false)
|
||||
} else {
|
||||
multiselect.clearSelection()
|
||||
contextMenu.photo(photo.getID(), e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
multiselect.clearSelection = function(deselect = true) {
|
||||
|
||||
if (deselect) multiselect.deselect('.photo.active, .album.active')
|
||||
|
||||
multiselect.ids = []
|
||||
multiselect.albumsSelected = 0
|
||||
multiselect.photosSelected = 0
|
||||
|
||||
}
|
||||
|
||||
multiselect.show = function(e) {
|
||||
|
||||
if (lychee.publicMode) return false
|
||||
@ -44,43 +171,6 @@ multiselect.show = function(e) {
|
||||
|
||||
}
|
||||
|
||||
multiselect.selectAll = function() {
|
||||
|
||||
if (lychee.publicMode) return false
|
||||
if (visible.search()) return false
|
||||
if (!visible.albums() && !visible.album) return false
|
||||
if (visible.multiselect()) $('#multiselect').remove()
|
||||
|
||||
sidebar.setSelectable(false)
|
||||
|
||||
multiselect.position.top = 70
|
||||
multiselect.position.right = 40
|
||||
multiselect.position.bottom = 90
|
||||
multiselect.position.left = 20
|
||||
|
||||
$('body').append(build.multiselect(multiselect.position.top, multiselect.position.left))
|
||||
|
||||
let documentSize = {
|
||||
width : $(document).width(),
|
||||
height : $(document).height()
|
||||
}
|
||||
|
||||
let newSize = {
|
||||
width : documentSize.width - multiselect.position.right + 2,
|
||||
height : documentSize.height - multiselect.position.bottom
|
||||
}
|
||||
|
||||
let e = {
|
||||
pageX : documentSize.width - (multiselect.position.right / 2),
|
||||
pageY : documentSize.height - multiselect.position.bottom
|
||||
}
|
||||
|
||||
$('#multiselect').css(newSize)
|
||||
|
||||
multiselect.getSelection(e)
|
||||
|
||||
}
|
||||
|
||||
multiselect.resize = function(e) {
|
||||
|
||||
if (multiselect.position.top === null ||
|
||||
@ -187,6 +277,11 @@ multiselect.getSelection = function(e) {
|
||||
if (visible.contextMenu()) return false
|
||||
if (!visible.multiselect()) return false
|
||||
|
||||
if (!isSelectKeyPressed(e) && (size.width==0 || size.height==0)) {
|
||||
multiselect.close()
|
||||
return false
|
||||
}
|
||||
|
||||
$('.photo, .album').each(function() {
|
||||
|
||||
let offset = $(this).offset()
|
||||
@ -196,26 +291,37 @@ multiselect.getSelection = function(e) {
|
||||
(offset.top + 206)<=(size.top + size.height + tolerance) &&
|
||||
(offset.left + 206)<=(size.left + size.width + tolerance)) {
|
||||
|
||||
let id = $(this).data('id')
|
||||
let id = $(this).attr('data-id')
|
||||
|
||||
if (id!=='0' && id!==0 && id!=='f' && id!=='s' && id!=='r' && id!=null) {
|
||||
|
||||
ids.push(id)
|
||||
$(this).addClass('active')
|
||||
|
||||
}
|
||||
multiselect.addItem($(this), id)
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if (ids.length!==0 && visible.album()) contextMenu.photoMulti(ids, e)
|
||||
else if (ids.length!==0 && visible.albums()) contextMenu.albumMulti(ids, e)
|
||||
else multiselect.close()
|
||||
multiselect.hide()
|
||||
|
||||
}
|
||||
|
||||
multiselect.close = function() {
|
||||
multiselect.select = function(id) {
|
||||
|
||||
let el = $(id)
|
||||
|
||||
el.addClass('selected')
|
||||
el.addClass('active')
|
||||
|
||||
}
|
||||
|
||||
multiselect.deselect = function(id) {
|
||||
|
||||
let el = $(id)
|
||||
|
||||
el.removeClass('selected')
|
||||
el.removeClass('active')
|
||||
|
||||
}
|
||||
|
||||
multiselect.hide = function() {
|
||||
|
||||
sidebar.setSelectable(true)
|
||||
|
||||
@ -229,4 +335,12 @@ multiselect.close = function() {
|
||||
lychee.animate('#multiselect', 'fadeOut')
|
||||
setTimeout(() => $('#multiselect').remove(), 300)
|
||||
|
||||
}
|
||||
|
||||
multiselect.close = function() {
|
||||
|
||||
multiselect.clearSelection()
|
||||
|
||||
multiselect.hide()
|
||||
|
||||
}
|
@ -72,7 +72,7 @@ settings.createConfig = function() {
|
||||
if (data==='Warning: Could not create file!') {
|
||||
|
||||
basicModal.show({
|
||||
body: "<p>Unable to save this configuration. Permission denied in <b>'data/'</b>. Please set the read, write and execute rights for others in <b>'data/'</b> and <b>'uploads/'</b>. Take a look at the readme for more information.</p>",
|
||||
body: `<p>Unable to save this configuration. Permission denied in <b>'data/'</b>. Please set the read, write and execute rights for others in <b>'data/'</b> and <b>'uploads/'</b>. Take a look at the readme for more information.</p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Retry',
|
||||
|
@ -124,14 +124,18 @@ sidebar.createStructure.photo = function(data) {
|
||||
// Set value for public
|
||||
switch (data.public) {
|
||||
|
||||
case '0' : _public = 'No'
|
||||
break
|
||||
case '1' : _public = 'Yes'
|
||||
break
|
||||
case '2' : _public = 'Yes (Album)'
|
||||
break
|
||||
default : _public = '-'
|
||||
break
|
||||
case '0':
|
||||
_public = 'No'
|
||||
break
|
||||
case '1':
|
||||
_public = 'Yes'
|
||||
break
|
||||
case '2':
|
||||
_public = 'Yes (Album)'
|
||||
break
|
||||
default:
|
||||
_public = '-'
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
|
@ -143,8 +143,22 @@ view.album = {
|
||||
|
||||
let photosData = ''
|
||||
|
||||
// Sub albums
|
||||
if (album.subjson && album.subjson.albums && album.subjson.num!==0) {
|
||||
|
||||
photosData = build.divider('Albums')
|
||||
|
||||
$.each(album.subjson.albums, function() {
|
||||
albums.parse(this)
|
||||
photosData += build.album(this)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if (album.json.content && album.json.content!==false) {
|
||||
|
||||
photosData += build.divider('Photos', 'divider-photos')
|
||||
|
||||
// Build photos
|
||||
$.each(album.json.content, function() {
|
||||
photosData += build.photo(this)
|
||||
@ -163,13 +177,32 @@ view.album = {
|
||||
|
||||
title: function(photoID) {
|
||||
|
||||
let title = album.json.content[photoID].title
|
||||
let ntitle = ''
|
||||
let prefix = ''
|
||||
|
||||
title = lychee.escapeHTML(title)
|
||||
if (album.json.content[photoID]) {
|
||||
|
||||
$('.photo[data-id="' + photoID + '"] .overlay h1')
|
||||
.html(title)
|
||||
.attr('title', title)
|
||||
prefix = '.photo'
|
||||
ntitle = album.json.content[photoID].title
|
||||
|
||||
} else {
|
||||
|
||||
prefix = '.album'
|
||||
|
||||
for (i in album.subjson.albums) {
|
||||
if (album.subjson.albums[i].id==photoID) {
|
||||
ntitle = album.subjson.albums[i].title
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ntitle = lychee.escapeHTML(ntitle)
|
||||
|
||||
$(prefix + '[data-id="' + photoID + '"] .overlay h1')
|
||||
.html(ntitle)
|
||||
.attr('title', ntitle)
|
||||
|
||||
},
|
||||
|
||||
@ -202,6 +235,7 @@ view.album = {
|
||||
if (!visible.albums()) {
|
||||
album.json.num--
|
||||
view.album.num()
|
||||
if (album.json.num == 0) $('#divider-photos').hide()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -72,6 +72,10 @@
|
||||
transition: none;
|
||||
border-color: darken($colorBlue, 15%);
|
||||
}
|
||||
|
||||
&.selected img {
|
||||
outline: 1px solid $colorBlue;
|
||||
}
|
||||
}
|
||||
|
||||
// Album -------------------------------------------------------------- //
|
||||
|
Loading…
Reference in New Issue
Block a user