Browse Source

Merge pull request #354 from electerious/develop

v3.0.1
Tobias Reich 9 years ago
parent
commit
d4c724b02c

File diff suppressed because it is too large
+ 0 - 0
dist/main.css


File diff suppressed because it is too large
+ 0 - 0
dist/main.js


File diff suppressed because it is too large
+ 0 - 0
dist/view.js


+ 10 - 1
docs/Changelog.md

@@ -1,6 +1,15 @@
+## v3.0.1
+
+Released May 24, 2015
+
+- `New` Album Sorting (Thanks @ophian, #98)
+- `New` Identifier to prevent login of multiple instances of lychee (#344)
+- `Improved` Albums and photos now can have a title with up to 50 chars (#332)
+- `Fixed` Removing last Tag from photo not possible in Firefox (#269)
+
 ## v3.0.0
 
-Released April 6, 2015
+Released May 6, 2015
 
 **Warning**: You need to enter a new username and password when upgrading from a previous version. Your installation is accessible for everyone till you enter a new login by visiting your Lychee. Both fields are now stored in a secure way. Legacy md5 code has been removed.
 

+ 7 - 2
php/access/Admin.php

@@ -279,9 +279,14 @@ class Admin extends Access {
 
 	private function setSorting() {
 
-		Module::dependencies(isset($_POST['type'], $_POST['order']));
+		Module::dependencies(isset($_POST['typeAlbums'], $_POST['orderAlbums'], $_POST['typePhotos'], $_POST['orderPhotos']));
 		$this->settings = new Settings($this->database);
-		echo $this->settings->setSorting($_POST['type'], $_POST['order']);
+
+		$sA = $this->settings->setSortingAlbums($_POST['typeAlbums'], $_POST['orderAlbums']);
+		$sP = $this->settings->setSortingPhotos($_POST['typePhotos'], $_POST['orderPhotos']);
+
+		if ($sA===true&&$sP===true)	echo true;
+		else						echo false;
 
 	}
 

+ 2 - 1
php/api.php

@@ -63,7 +63,8 @@ if (!empty($_POST['function'])||!empty($_GET['function'])) {
 	if (isset($_POST['function']))	$fn = $_POST['function'];
 	else							$fn = $_GET['function'];
 
-	if (isset($_SESSION['login'])&&$_SESSION['login']==true) {
+	if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&&
+		(isset($_SESSION['identifier'])&&$_SESSION['identifier']===$settings['identifier'])) {
 
 		###
 		# Admin Access

+ 2 - 2
php/database/albums_table.sql

@@ -4,12 +4,12 @@
 
 CREATE TABLE IF NOT EXISTS `?` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
-  `title` varchar(50) NOT NULL,
+  `title` varchar(100) NOT NULL DEFAULT '',
   `description` varchar(1000) DEFAULT '',
   `sysstamp` int(11) NOT NULL,
   `public` tinyint(1) NOT NULL DEFAULT '0',
   `visible` tinyint(1) NOT NULL DEFAULT '1',
   `downloadable` tinyint(1) NOT NULL DEFAULT '0',
-  `password` varchar(100) DEFAULT '',
+  `password` varchar(100) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

+ 1 - 1
php/database/photos_table.sql

@@ -4,7 +4,7 @@
 
 CREATE TABLE IF NOT EXISTS `?` (
   `id` bigint(14) NOT NULL,
-  `title` varchar(50) NOT NULL,
+  `title` varchar(100) NOT NULL,
   `description` varchar(1000) DEFAULT '',
   `url` varchar(100) NOT NULL,
   `tags` varchar(1000) NOT NULL DEFAULT '',

+ 5 - 1
php/database/settings_content.sql

@@ -9,6 +9,10 @@ VALUES
   ('password',''),
   ('thumbQuality','90'),
   ('checkForUpdates','1'),
-  ('sorting','ORDER BY id DESC'),
+  ('sortingPhotos','ORDER BY id DESC'),
+  ('sortingAlbums','ORDER BY id DESC'),
+  ('medium','1'),
+  ('imagick','1'),
   ('dropboxKey',''),
+  ('identifier',''),
   ('plugins','');

+ 70 - 0
php/database/update_030001.php

@@ -0,0 +1,70 @@
+<?php
+
+###
+# @name			Update to version 3.0.1
+# @copyright	2015 by Tobias Reich
+###
+
+if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
+
+# Change length of photo title
+$query	= Database::prepare($database, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_PHOTOS));
+$result	= $database->query($query);
+if (!$result) {
+	Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')');
+	return false;
+}
+
+# Change length of album title
+$query	= Database::prepare($database, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_ALBUMS));
+$result	= $database->query($query);
+if (!$result) {
+	Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')');
+	return false;
+}
+
+# Add album sorting to settings
+$query	= Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'sortingAlbums' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
+$result	= $database->query($query);
+if ($result->num_rows===0) {
+	$query	= Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('sortingAlbums', 'ORDER BY id DESC')", array(LYCHEE_TABLE_SETTINGS));
+	$result	= $database->query($query);
+	if (!$result) {
+		Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')');
+		return false;
+	}
+}
+
+# Rename sorting to sortingPhotos
+$query	= Database::prepare($database, "UPDATE ? SET `key` = 'sortingPhotos' WHERE `key` = 'sorting' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
+$result	= $database->query($query);
+if (!$result) {
+	Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')');
+	return false;
+}
+
+# Add identifier to settings
+$query	= Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
+$result	= $database->query($query);
+if ($result->num_rows===0) {
+	$identifier	= md5(microtime(true));
+	$query		= Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('identifier', '?')", array(LYCHEE_TABLE_SETTINGS, $identifier));
+	$result		= $database->query($query);
+	if (!$result) {
+		Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')');
+		return false;
+	}
+} else {
+	$identifier		= md5(microtime(true));
+	$query			= Database::prepare($database, "UPDATE `?` SET `value` = '?' WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS, $identifier));
+	$result	= $database->query($query);
+	if (!$result) {
+		Log::error($database, 'update_030001', __LINE__, 'Could not reset public albums (' . $database->error . ')');
+		return false;
+	}
+}
+
+# Set version
+if (Database::setVersion($database, '030001')===false) return false;
+
+?>

+ 14 - 14
php/modules/Album.php

@@ -94,19 +94,19 @@ class Album extends Module {
 		switch ($this->albumIDs) {
 
 			case 'f':	$return['public'] = '0';
-						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE star = 1 " . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE star = 1 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 						break;
 
 			case 's':	$return['public'] = '0';
-						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE public = 1 " . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE public = 1 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 						break;
 
 			case 'r':	$return['public'] = '0';
-						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) " . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 						break;
 
 			case '0':	$return['public'] = '0';
-						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = 0 " . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+						$query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = 0 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 						break;
 
 			default:	$query	= Database::prepare($this->database, "SELECT * FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs));
@@ -114,7 +114,7 @@ class Album extends Module {
 						$return = $albums->fetch_assoc();
 						$return['sysdate']	= date('d M. Y', $return['sysstamp']);
 						$return['password']	= ($return['password']=='' ? '0' : '1');
-						$query	= Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = '?' " . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
+						$query	= Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = '?' " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
 						break;
 
 		}
@@ -189,8 +189,8 @@ class Album extends Module {
 		if ($public===false) $return['smartalbums'] = $this->getSmartInfo();
 
 		# Albums query
-		$query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0', array(LYCHEE_TABLE_ALBUMS));
-		if ($public===false) $query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ?', array(LYCHEE_TABLE_ALBUMS));
+		if ($public===false)	$query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ? ' . $this->settings['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS));
+		else					$query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0 ' . $this->settings['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS));
 
 		# Execute query
 		$albums = $this->database->query($query);
@@ -210,7 +210,7 @@ class Album extends Module {
 				($public===false)) {
 
 					# Execute query
-					$query	= Database::prepare($this->database, "SELECT thumbUrl FROM ? WHERE album = '?' ORDER BY star DESC, " . substr($this->settings['sorting'], 9) . " LIMIT 3", array(LYCHEE_TABLE_PHOTOS, $album['id']));
+					$query	= Database::prepare($this->database, "SELECT thumbUrl FROM ? WHERE album = '?' ORDER BY star DESC, " . substr($this->settings['sortingPhotos'], 9) . " LIMIT 3", array(LYCHEE_TABLE_PHOTOS, $album['id']));
 					$thumbs	= $this->database->query($query);
 
 					# For each thumb
@@ -223,7 +223,7 @@ class Album extends Module {
 			}
 
 			# Add to return
-			$return['albums'][$album['id']] = $album;
+			$return['albums'][] = $album;
 
 		}
 
@@ -254,7 +254,7 @@ class Album extends Module {
 		# Unsorted
 		###
 
-		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE album = 0 ' . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE album = 0 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 		$unsorted	= $this->database->query($query);
 		$i			= 0;
 
@@ -274,7 +274,7 @@ class Album extends Module {
 		# Starred
 		###
 
-		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE star = 1 ' . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE star = 1 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 		$starred	= $this->database->query($query);
 		$i			= 0;
 
@@ -294,7 +294,7 @@ class Album extends Module {
 		# Public
 		###
 
-		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE public = 1 ' . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE public = 1 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 		$public		= $this->database->query($query);
 		$i			= 0;
 
@@ -314,7 +314,7 @@ class Album extends Module {
 		# Recent
 		###
 
-		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) ' . $this->settings['sorting'], array(LYCHEE_TABLE_PHOTOS));
+		$query		= Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS));
 		$recent		= $this->database->query($query);
 		$i			= 0;
 
@@ -484,7 +484,7 @@ class Album extends Module {
 		$this->plugins(__METHOD__, 0, func_get_args());
 
 		# Parse
-		if (strlen($title)>50) $title = substr($title, 0, 50);
+		if (strlen($title)>100) $title = substr($title, 0, 100);
 
 		# Execute query
 		$query	= Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $title, $this->albumIDs));

+ 2 - 1
php/modules/Database.php

@@ -55,7 +55,8 @@ class Database extends Module {
 			'020601', #2.6.1
 			'020602', #2.6.2
 			'020700', #2.7.0
-			'030000' #3.0.0
+			'030000', #3.0.0
+			'030001' #3.0.1
 		);
 
 		# For each update

+ 1 - 1
php/modules/Photo.php

@@ -842,7 +842,7 @@ class Photo extends Module {
 		$this->plugins(__METHOD__, 0, func_get_args());
 
 		# Parse
-		if (strlen($title)>50) $title = substr($title, 0, 50);
+		if (strlen($title)>100) $title = substr($title, 0, 100);
 
 		# Set title
 		$query	= Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $title, $this->photoIDs));

+ 15 - 7
php/modules/Session.php

@@ -44,6 +44,9 @@ class Session extends Module {
 		unset($return['config']['username']);
 		unset($return['config']['password']);
 
+		# Remove identifier from response
+		unset($return['config']['identifier']);
+
 		# Path to Lychee for the server-import dialog
 		$return['config']['location'] = LYCHEE;
 
@@ -67,10 +70,13 @@ class Session extends Module {
 
 			# Unset unused vars
 			unset($return['config']['thumbQuality']);
-			unset($return['config']['sorting']);
+			unset($return['config']['sortingAlbums']);
+			unset($return['config']['sortingPhotos']);
 			unset($return['config']['dropboxKey']);
 			unset($return['config']['login']);
 			unset($return['config']['location']);
+			unset($return['config']['imagick']);
+			unset($return['config']['medium']);
 			unset($return['config']['plugins']);
 
 		}
@@ -96,15 +102,13 @@ class Session extends Module {
 		# Check login with crypted hash
 		if ($this->settings['username']===$username&&
 			$this->settings['password']===$password) {
-				$_SESSION['login'] = true;
+				$_SESSION['login']		= true;
+				$_SESSION['identifier']	= $this->settings['identifier'];
 				return true;
 		}
 
 		# No login
-		if ($this->noLogin()===true) {
-			$_SESSION['login'] = true;
-			return true;
-		}
+		if ($this->noLogin()===true) return true;
 
 		# Call plugins
 		$this->plugins(__METHOD__, 1, func_get_args());
@@ -121,7 +125,8 @@ class Session extends Module {
 		# Check if login credentials exist and login if they don't
 		if ($this->settings['username']===''&&
 			$this->settings['password']==='') {
-				$_SESSION['login'] = true;
+				$_SESSION['login']		= true;
+				$_SESSION['identifier']	= $this->settings['identifier'];
 				return true;
 		}
 
@@ -134,6 +139,9 @@ class Session extends Module {
 		# Call plugins
 		$this->plugins(__METHOD__, 0, func_get_args());
 
+		$_SESSION['login']		= null;
+		$_SESSION['identifier']	= null;
+
 		session_destroy();
 
 		# Call plugins

+ 59 - 2
php/modules/Settings.php

@@ -129,7 +129,7 @@ class Settings extends Module {
 
 	}
 
-	public function setSorting($type, $order) {
+	public function setSortingPhotos($type, $order) {
 
 		# Check dependencies
 		self::dependencies(isset($this->database, $type, $order));
@@ -182,7 +182,64 @@ class Settings extends Module {
 		# Execute query
 		# Do not prepare $sorting because it is a true statement
 		# Preparing (escaping) the sorting would destroy it
-		$query	= Database::prepare($this->database, "UPDATE ? SET value = '$sorting' WHERE `key` = 'sorting'", array(LYCHEE_TABLE_SETTINGS));
+		# $sorting is save and can't contain user-input
+		$query	= Database::prepare($this->database, "UPDATE ? SET value = '$sorting' WHERE `key` = 'sortingPhotos'", array(LYCHEE_TABLE_SETTINGS));
+		$result	= $this->database->query($query);
+
+		if (!$result) {
+			Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
+			return false;
+		}
+		return true;
+
+	}
+
+	public function setSortingAlbums($type, $order) {
+
+		# Check dependencies
+		self::dependencies(isset($this->database, $type, $order));
+
+		$sorting = 'ORDER BY ';
+
+		# Set row
+		switch ($type) {
+
+			case 'id':			$sorting .= 'id';
+								break;
+
+			case 'title':		$sorting .= 'title';
+								break;
+
+			case 'description':	$sorting .= 'description';
+								break;
+
+			case 'public':		$sorting .= 'public';
+								break;
+
+			default:			exit('Error: Unknown type for sorting!');
+
+		}
+
+		$sorting .= ' ';
+
+		# Set order
+		switch ($order) {
+
+			case 'ASC':		$sorting .= 'ASC';
+							break;
+
+			case 'DESC':	$sorting .= 'DESC';
+							break;
+
+			default:		exit('Error: Unknown order for sorting!');
+
+		}
+
+		# Execute query
+		# Do not prepare $sorting because it is a true statement
+		# Preparing (escaping) the sorting would destroy it
+		# $sorting is save and can't contain user-input
+		$query	= Database::prepare($this->database, "UPDATE ? SET value = '$sorting' WHERE `key` = 'sortingAlbums'", array(LYCHEE_TABLE_SETTINGS));
 		$result	= $this->database->query($query);
 
 		if (!$result) {

+ 1 - 1
php/modules/misc.php

@@ -47,7 +47,7 @@ function search($database, $settings, $term) {
 		$album = Album::prepareData($album);
 
 		# Thumbs
-		$query	= Database::prepare($database, "SELECT thumbUrl FROM ? WHERE album = '?' " . $settings['sorting'] . " LIMIT 0, 3", array(LYCHEE_TABLE_PHOTOS, $album['id']));
+		$query	= Database::prepare($database, "SELECT thumbUrl FROM ? WHERE album = '?' " . $settings['sortingPhotos'] . " LIMIT 0, 3", array(LYCHEE_TABLE_PHOTOS, $album['id']));
 		$thumbs	= $database->query($query);
 
 		# For each thumb

+ 1 - 1
src/bower.json

@@ -2,7 +2,7 @@
   "name": "Lychee",
   "private": true,
   "dependencies": {
-    "jQuery": "~2.1.3",
+    "jQuery": "~2.1.4",
     "mousetrap": "~1.5.2",
     "basicContext": "~2.0.9",
     "basicModal": "~2.0.8"

+ 2 - 2
src/package.json

@@ -1,6 +1,6 @@
 {
   "name": "Lychee",
-  "version": "3.0.0",
+  "version": "3.0.1",
   "description": "Self-hosted photo-management done right.",
   "authors": "Tobias Reich <tobias@electerious.com>",
   "license": "MIT",
@@ -18,7 +18,7 @@
     "gulp-load-plugins": "^0.10.0",
     "gulp-minify-css": "^1.1.1",
     "gulp-rimraf": "^0.1.1",
-    "gulp-sass": "^1.3.3",
+    "gulp-sass": "^2.0.1",
     "gulp-uglify": "^1.2.0"
   }
 }

+ 9 - 9
src/scripts/album.js

@@ -138,7 +138,7 @@ album.add = function() {
 	}
 
 	basicModal.show({
-		body: "<p>Enter a title for the new album: <input class='text' data-name='title' type='text' maxlength='30' placeholder='Title' value='Untitled'></p>",
+		body: "<p>Enter a title for the new album: <input class='text' data-name='title' type='text' maxlength='50' placeholder='Title' value='Untitled'></p>",
 		buttons: {
 			action: {
 				title: 'Create Album',
@@ -180,7 +180,7 @@ album.delete = function(albumIDs) {
 				albumIDs.forEach(function(id) {
 					albums.json.num--;
 					view.albums.content.delete(id);
-					delete albums.json.albums[id];
+					albums.deleteByID(id);
 				});
 
 			} else {
@@ -210,7 +210,7 @@ album.delete = function(albumIDs) {
 
 		// Get title
 		if (album.json)			albumTitle = album.json.title;
-		else if (albums.json)	albumTitle = albums.json.albums[albumIDs].title;
+		else if (albums.json)	albumTitle = albums.getByID(albumIDs).title;
 
 		msg = "<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>";
 
@@ -254,7 +254,7 @@ album.setTitle = function(albumIDs) {
 
 		// Get old title if only one album is selected
 		if (album.json)			oldTitle = album.json.title;
-		else if (albums.json)	oldTitle = albums.json.albums[albumIDs].title;
+		else if (albums.json)	oldTitle = albums.getByID(albumIDs).title;
 
 		if (!oldTitle) oldTitle = '';
 		oldTitle = oldTitle.replace(/'/g, '&apos;');
@@ -281,13 +281,13 @@ album.setTitle = function(albumIDs) {
 
 			if (albums.json) {
 				var id = albumIDs[0];
-				albums.json.albums[id].title = newTitle;
+				albums.getByID(id).title = newTitle;
 			}
 
 		} else if (visible.albums()) {
 
 			albumIDs.forEach(function(id) {
-				albums.json.albums[id].title = newTitle;
+				albums.getByID(id).title = newTitle;
 				view.albums.content.title(id);
 			});
 
@@ -306,7 +306,7 @@ album.setTitle = function(albumIDs) {
 
 	}
 
-	input = "<input class='text' data-name='title' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>";
+	input = "<input class='text' data-name='title' type='text' maxlength='50' placeholder='Title' value='" + oldTitle + "'>";
 
 	if (albumIDs.length===1)	msg = "<p>Enter a new title for this album: " + input + "</p>";
 	else						msg = "<p>Enter a title for all " + albumIDs.length + " selected albums: " + input +"</p>";
@@ -580,7 +580,7 @@ album.merge = function(albumIDs) {
 	if (albumIDs instanceof Array===false) albumIDs = [albumIDs];
 
 	// Get title of first album
-	if (albums.json) title = albums.json.albums[albumIDs[0]].title;
+	if (albums.json) title = albums.getByID(albumIDs[0]).title;
 
 	if (!title) title = '';
 	title = title.replace(/'/g, '&apos;');
@@ -588,7 +588,7 @@ album.merge = function(albumIDs) {
 	if (albumIDs.length===2) {
 
 		// Get title of second album
-		if (albums.json) sTitle = albums.json.albums[albumIDs[1]].title;
+		if (albums.json) sTitle = albums.getByID(albumIDs[1]).title;
 
 		if (!sTitle) sTitle = '';
 		sTitle = sTitle.replace(/'/g, '&apos;');

+ 46 - 0
src/scripts/albums.js

@@ -105,6 +105,52 @@ albums._createSmartAlbums = function(data) {
 
 }
 
+albums.getByID = function(albumID) {
+
+	// Function returns the JSON of an album
+
+	if (albumID===undefined||albumID===null)	return undefined;
+	if (!albums.json)							return undefined;
+	if (!albums.json.albums)					return undefined;
+
+	var json = undefined;
+
+	$.each(albums.json.albums, function(i) {
+
+		let elem = albums.json.albums[i];
+
+		if (elem.id==albumID) json = elem;
+
+	});
+
+	return json;
+
+}
+
+albums.deleteByID = function(albumID) {
+
+	// Function returns the JSON of an album
+
+	if (albumID===undefined||albumID===null)	return false;
+	if (!albums.json)							return false;
+	if (!albums.json.albums)					return false;
+
+	var deleted = false;
+
+	$.each(albums.json.albums, function(i) {
+
+		if (albums.json.albums[i].id==albumID) {
+			albums.json.albums.splice(i, 1);
+			deleted = true;
+			return false;
+		}
+
+	});
+
+	return deleted;
+
+}
+
 albums.refresh = function() {
 
 	albums.json = null;

+ 4 - 22
src/scripts/build.js

@@ -59,16 +59,7 @@ build.album = function(data) {
 
 	if (data===null||data===undefined) return '';
 
-	var html		= '',
-		title		= data.title,
-		longTitle	= '';
-
-	if (title!==null&&title.length>18) {
-
-		title		= data.title.substr(0, 18) + '...';
-		longTitle	= data.title;
-
-	}
+	var html = '';
 
 	var {path: thumbPath, hasRetina: thumbRetina} = lychee.retinize(data.thumbs[0]);
 
@@ -78,7 +69,7 @@ build.album = function(data) {
 				<img src='${ data.thumbs[1] }' width='200' height='200' alt='thumb' data-retina='false'>
 				<img src='${ thumbPath }' width='200' height='200' alt='thumb' data-retina='${ thumbRetina }'>
 				<div class='overlay'>
-					<h1 title='${ longTitle }'>${ title }</h1>
+					<h1 title='${ data.title }'>${ data.title }</h1>
 					<a>${ data.sysdate }</a>
 				</div>
 			`
@@ -103,16 +94,7 @@ build.photo = function(data) {
 
 	if (data===null||data===undefined) return '';
 
-	var html		= '',
-		title		= data.title,
-		longTitle	= '';
-
-	if (title!==null&&title.length>18) {
-
-		title		= data.title.substr(0, 18) + '...';
-		longTitle	= data.title;
-
-	}
+	var html = '';
 
 	var {path: thumbPath, hasRetina: thumbRetina} = lychee.retinize(data.thumbUrl);
 
@@ -120,7 +102,7 @@ build.photo = function(data) {
 			<div class='photo' data-album-id='${ data.album }' data-id='${ data.id }'>
 				<img src='${ thumbPath }' width='200' height='200' alt='thumb'>
 				<div class='overlay'>
-					<h1 title='${ longTitle }'>${ title }</h1>
+					<h1 title='${ data.title }'>${ data.title }</h1>
 			`
 
 	if (data.cameraDate==='1')	html += `<a><span title='Camera Date'>${ build.iconic('camera-slr') }</span>${ data.sysdate }</a>`;

+ 19 - 17
src/scripts/contextMenu.js

@@ -84,7 +84,7 @@ contextMenu.albumTitle = function(albumID, e) {
 
 	api.post('Album::getAll', {}, function(data) {
 
-		if (data.num>1) {
+		if (data.albums&&data.num>1) {
 
 			// Generate list of albums
 			$.each(data.albums, function(index) {
@@ -96,7 +96,7 @@ contextMenu.albumTitle = function(albumID, e) {
 
 				title = "<img class='cover' width='16' height='16' src='" + that.thumbs[0] + "'><div class='title'>" + that.title + "</div>";
 
-				if (that.id!=albumID) items.unshift({ type: 'item', title, fn: function() { lychee.goto(that.id) } });
+				if (that.id!=albumID) items.push({ type: 'item', title, fn: function() { lychee.goto(that.id) } });
 
 			});
 
@@ -114,28 +114,30 @@ contextMenu.albumTitle = function(albumID, e) {
 
 contextMenu.mergeAlbum = function(albumID, e) {
 
-    var items = [];
+	var items = [];
+
+	api.post('Album::getAll', {}, function(data) {
 
-    api.post('Album::getAll', {}, function(data) {
+		if (data.albums&&data.num>1) {
 
-        $.each(data.albums, function(){
+			$.each(data.albums, function(){
+
+				var that = this;
 
-            var that = this;
+				if (!that.thumbs[0]) that.thumbs[0] = 'src/images/no_cover.svg';
+				that.contextTitle = "<img class='cover' width='16' height='16' src='" + that.thumbs[0] + "'><div class='title'>" + that.title + "</div>";
 
-            if (!that.thumbs[0]) that.thumbs[0] = 'src/images/no_cover.svg';
-            that.contextTitle = "<img class='cover' width='16' height='16' src='" + that.thumbs[0] + "'><div class='title'>" + that.title + "</div>";
+				if (that.id!=albumID) items.push({ type: 'item', title: that.contextTitle, fn: function() { album.merge([albumID, that.id]) } });
 
-            if (that.id!=album.getID()) {
-                items.unshift({ type: 'item', title: that.contextTitle, fn: function() { album.merge([albumID, that.id]) } });
-            }
+			});
 
-        });
+		}
 
-        if (items.length===0) return false;
+		if (items.length===0) return false;
 
-        basicContext.show(items, e, contextMenu.close);
+		basicContext.show(items, e, contextMenu.close);
 
-    })
+	});
 
 }
 
@@ -191,7 +193,7 @@ contextMenu.photoTitle = function(albumID, photoID, e) {
 
 	var data = album.json;
 
-	if (data.num>1) {
+	if (data.content!==false&&data.num>1) {
 
 		items.push({ type: 'separator' });
 
@@ -253,7 +255,7 @@ contextMenu.move = function(photoIDs, e) {
 				if (!that.thumbs[0]) that.thumbs[0] = 'src/images/no_cover.svg';
 				that.title = "<img class='cover' width='16' height='16' src='" + that.thumbs[0] + "'><div class='title'>" + that.title + "</div>";
 
-				if (that.id!=album.getID()) items.unshift({ type: 'item', title: that.title, fn: function() { photo.setAlbum(photoIDs, that.id) } });
+				if (that.id!=album.getID()) items.push({ type: 'item', title: that.title, fn: function() { photo.setAlbum(photoIDs, that.id) } });
 
 			});
 

+ 1 - 1
src/scripts/loadingBar.js

@@ -85,7 +85,7 @@ loadingBar.hide = function(force) {
 		loadingBar.status = null;
 
 		// Move header up
-		if (visible.header()) header.dom().removeClass('error loading');
+		header.dom().removeClass('error loading');
 
 		// Set timeout
 		clearTimeout(loadingBar._timeout);

+ 10 - 5
src/scripts/lychee.js

@@ -6,8 +6,8 @@
 lychee = {
 
 	title:			document.title,
-	version:		'3.0.0',
-	version_code:	'030000',
+	version:		'3.0.1',
+	version_code:	'030001',
 
 	update_path:	'http://lychee.electerious.com/version/index.php',
 	updateURL:		'https://github.com/electerious/Lychee',
@@ -18,7 +18,8 @@ lychee = {
 	debugMode:		false,
 
 	checkForUpdates:'1',
-	sorting:		'',
+	sortingPhotos:	'',
+	sortingAlbums:	'',
 	location:		'',
 
 	dropbox:		false,
@@ -48,7 +49,8 @@ lychee.init = function() {
 
 			// Logged in
 
-			lychee.sorting			= data.config.sorting			|| '';
+			lychee.sortingPhotos	= data.config.sortingPhotos		|| '';
+			lychee.sortingAlbums	= data.config.sortingAlbums		|| '';
 			lychee.dropboxKey		= data.config.dropboxKey		|| '';
 			lychee.location			= data.config.location			|| '';
 			lychee.checkForUpdates	= data.config.checkForUpdates	|| '1';
@@ -382,10 +384,13 @@ lychee.loadDropbox = function(callback) {
 
 }
 
-lychee.removeHTML = function(html) {
+lychee.removeHTML = function(html = '') {
+
+	if (html==='') return html;
 
 	var tmp = document.createElement('DIV');
 	tmp.innerHTML = html;
+
 	return tmp.textContent || tmp.innerText;
 
 }

+ 3 - 3
src/scripts/password.js

@@ -14,9 +14,9 @@ password.get = function(albumID, callback) {
 	var passwd = $('.basicModal input.text').val(),
 		params;
 
-	if (lychee.publicMode===false)										callback();
-	else if (album.json&&album.json.password==='0')						callback();
-	else if (albums.json&&albums.json.albums[albumID].password==='0')	callback();
+	if (lychee.publicMode===false)									callback();
+	else if (album.json&&album.json.password==='0')					callback();
+	else if (albums.json&&albums.getByID(albumID).password==='0')	callback();
 	else if (!albums.json&&!album.json) {
 
 		// Continue without password

+ 2 - 2
src/scripts/photo.js

@@ -223,7 +223,7 @@ photo.delete = function(photoIDs) {
 
 			}
 
-			album.json.content[id] = null;
+			delete album.json.content[id];
 			view.album.content.delete(id);
 
 		});
@@ -330,7 +330,7 @@ photo.setTitle = function(photoIDs) {
 
 	}
 
-	input = "<input class='text' data-name='title' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>";
+	input = "<input class='text' data-name='title' type='text' maxlength='50' placeholder='Title' value='" + oldTitle + "'>";
 
 	if (photoIDs.length===1)	msg = "<p>Enter a new title for this photo: " + input + "</p>";
 	else						msg = "<p>Enter a title for all " + photoIDs.length + " selected photos: " + input + "</p>";

+ 62 - 23
src/scripts/settings.js

@@ -281,7 +281,8 @@ settings.setLogin = function() {
 
 settings.setSorting = function() {
 
-	var sorting = [],
+	var sortingPhotos = [],
+		sortingAlbums = [],
 		action,
 		msg = '';
 
@@ -289,21 +290,27 @@ settings.setSorting = function() {
 
 		var params;
 
-		sorting[0] = $('.basicModal select#settings_type').val();
-		sorting[1] = $('.basicModal select#settings_order').val();
+		sortingAlbums[0] = $('.basicModal select#settings_albums_type').val();
+		sortingAlbums[1] = $('.basicModal select#settings_albums_order').val();
+
+		sortingPhotos[0] = $('.basicModal select#settings_photos_type').val();
+		sortingPhotos[1] = $('.basicModal select#settings_photos_order').val();
 
 		basicModal.close();
 		albums.refresh();
 
 		params = {
-			type: sorting[0],
-			order: sorting[1]
+			typeAlbums:		sortingAlbums[0],
+			orderAlbums:	sortingAlbums[1],
+			typePhotos:		sortingPhotos[0],
+			orderPhotos:	sortingPhotos[1]
 		}
 
 		api.post('Settings::setSorting', params, function(data) {
 
 			if (data===true) {
-				lychee.sorting = 'ORDER BY ' + sorting[0] + ' ' + sorting[1];
+				lychee.sortingAlbums	= 'ORDER BY ' + sortingAlbums[0] + ' ' + sortingAlbums[1];
+				lychee.sortingPhotos	= 'ORDER BY ' + sortingPhotos[0] + ' ' + sortingPhotos[1];
 				lychee.load();
 			} else lychee.error(null, params, data);
 
@@ -312,22 +319,45 @@ settings.setSorting = function() {
 	}
 
 	msg =	`
+			<p>
+				Sort albums by
+				<span class="select">
+					<select id='settings_albums_type'>
+						<option value='id'>Creation Time</option>
+						<option value='title'>Title</option>
+						<option value='description'>Description</option>
+						<option value='public'>Public</option>
+					</select>
+				</span>
+				in an
+				<span class="select">
+					<select id='settings_albums_order'>
+						<option value='ASC'>Ascending</option>
+						<option value='DESC'>Descending</option>
+					</select>
+				</span>
+				order.
+			</p>
 			<p>
 				Sort photos by
-				<select id='settings_type'>
-					<option value='id'>Upload Time</option>
-					<option value='takestamp'>Take Date</option>
-					<option value='title'>Title</option>
-					<option value='description'>Description</option>
-					<option value='public'>Public</option>
-					<option value='star'>Star</option>
-					<option value='type'>Photo Format</option>
-				</select>
+				<span class="select">
+					<select id='settings_photos_type'>
+						<option value='id'>Upload Time</option>
+						<option value='takestamp'>Take Date</option>
+						<option value='title'>Title</option>
+						<option value='description'>Description</option>
+						<option value='public'>Public</option>
+						<option value='star'>Star</option>
+						<option value='type'>Photo Format</option>
+					</select>
+				</span>
 				in an
-				<select id='settings_order'>
-					<option value='ASC'>Ascending</option>
-					<option value='DESC'>Descending</option>
-				</select>
+				<span class="select">
+					<select id='settings_photos_order'>
+						<option value='ASC'>Ascending</option>
+						<option value='DESC'>Descending</option>
+					</select>
+				</span>
 				order.
 			</p>
 			`
@@ -346,12 +376,21 @@ settings.setSorting = function() {
 		}
 	});
 
-	if (lychee.sorting!=='') {
+	if (lychee.sortingAlbums!=='') {
+
+		sortingAlbums = lychee.sortingAlbums.replace('ORDER BY ', '').split(' ');
+
+		$('.basicModal select#settings_albums_type').val(sortingAlbums[0]);
+		$('.basicModal select#settings_albums_order').val(sortingAlbums[1]);
+
+	}
+
+	if (lychee.sortingPhotos!=='') {
 
-		sorting = lychee.sorting.replace('ORDER BY ', '').split(' ');
+		sortingPhotos = lychee.sortingPhotos.replace('ORDER BY ', '').split(' ');
 
-		$('.basicModal select#settings_type').val(sorting[0]);
-		$('.basicModal select#settings_order').val(sorting[1]);
+		$('.basicModal select#settings_photos_type').val(sortingPhotos[0]);
+		$('.basicModal select#settings_photos_order').val(sortingPhotos[1]);
 
 	}
 

+ 15 - 23
src/scripts/view.js

@@ -46,9 +46,7 @@ view.albums = {
 
 				$.each(albums.json.albums, function() {
 					albums.parse(this);
-
-					// Display albums in reverse order
-					albumsData = build.album(this) + albumsData;
+					albumsData += build.album(this);
 				});
 
 				// Add divider
@@ -73,17 +71,11 @@ view.albums = {
 
 		title: function(albumID) {
 
-			var longTitle	= '',
-				title		= albums.json.albums[albumID].title;
-
-			if (title!==null&&title.length>18) {
-				longTitle	= title;
-				title		= title.substr(0, 18) + '...';
-			}
+			var title = albums.getByID(albumID).title;
 
 			$('.album[data-id="' + albumID + '"] .overlay h1')
 				.html(title)
-				.attr('title', longTitle);
+				.attr('title', title);
 
 		},
 
@@ -94,7 +86,7 @@ view.albums = {
 				marginLeft:	0
 			}, 300, function() {
 				$(this).remove();
-				if (albums.json.num<=0) lychee.animate('#content .divider:last-of-type', 'fadeOut');
+				if (albums.json.num<=0) lychee.content.find('.divider:last-child').remove();
 			});
 
 		}
@@ -155,27 +147,27 @@ view.album = {
 			view.albums.content.scrollPosition = $(document).scrollTop();
 			$('html, body').scrollTop(0);
 
-			$.each(album.json.content, function() {
-				photosData += build.photo(this);
-			});
+			if (album.json.content&&album.json.content!==false) {
+
+				// Build photos
+				$.each(album.json.content, function() {
+					photosData += build.photo(this);
+				});
+
+			}
 
+			// Add photos to view
 			lychee.content.html(photosData);
 
 		},
 
 		title: function(photoID) {
 
-			var longTitle	= '',
-				title		= album.json.content[photoID].title;
-
-			if (title!==null&&title.length>18) {
-				longTitle	= title;
-				title		= title.substr(0, 18) + '...';
-			}
+			var title = album.json.content[photoID].title;
 
 			$('.photo[data-id="' + photoID + '"] .overlay h1')
 				.html(title)
-				.attr('title', longTitle);
+				.attr('title', title);
 
 		},
 

+ 6 - 4
src/styles/_content.scss

@@ -125,13 +125,15 @@
 	.album .overlay h1,
 	.photo .overlay h1 {
 		min-height: 19px;
-		width: 185px;
+		width: 180px;
 		margin: 12px 0 5px 15px;
 		color: #fff;
 		text-shadow: 0 1px 3px black(.4);
 		font-size: 16px;
 		font-weight: bold;
 		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
 	}
 
 	.album .overlay a,
@@ -218,13 +220,13 @@
 	.iconic {
 		fill: white(.3);
 		margin: 0 0 10px;
-		width: 60px;
-		height: 60px;
+		width: 50px;
+		height: 50px;
 		filter: drop-shadow(0 -1px 0 black(.4));
 	}
 
 	p {
-		font-size: 18px;
+		font-size: 16px;
 		font-weight: bold;
 	}
 }

+ 3 - 0
src/styles/_header.scss

@@ -38,6 +38,9 @@ header {
 		text-align: center;
 		z-index: 1;
 		cursor: default;
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
 
 		.iconic {
 			display: none;

+ 59 - 0
src/styles/_message.scss

@@ -172,6 +172,65 @@
 		}
 	}
 
+	/* Select ------------------------------------------------*/
+	.select {
+		display: inline-block;
+		position: relative;
+		margin: 1px 5px;
+		padding: 0;
+		width: 110px;
+		background: black(.3);
+		color: #fff;
+		border-radius: 3px;
+		border: 1px solid black(.2);
+		box-shadow: 0 1px 0 white(.02);
+		font-size: 11px;
+		line-height: 16px;
+		overflow: hidden;
+		outline: 0;
+		vertical-align: middle;
+
+		&::after {
+			position: absolute;
+			content: '≡';
+			right: 8px;
+			top: 4px;
+			color: $colorBlue;
+			font-size: 16px;
+			line-height: 16px;
+			font-weight: bold;
+			pointer-events: none;
+		}
+
+		select {
+			margin: 0;
+			padding: 4px 8px;
+			width: 120%;
+			color: #fff;
+			font-size: 11px;
+			line-height: 16px;
+			border: 0;
+			outline: 0;
+			box-shadow: none;
+			border-radius: 0;
+			background-color: transparent;
+			background-image: none;
+			-moz-appearance: none;
+			-webkit-appearance: none;
+			appearance: none;
+
+			&:focus { outline: none; }
+		}
+
+		select option {
+			margin: 0;
+			padding: 0;
+			background: #fff;
+			color: #333;
+			transition: none;
+		}
+	}
+
 	/* Version ------------------------------------------------*/
 	.version {
 		margin: -5px 0 0;

Some files were not shown because too many files changed in this diff