Browse Source

Merge pull request #6 from electerious/develop

Develop
Quentin Ligier 8 years ago
parent
commit
55577e1c39

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


+ 7 - 2
docs/Changelog.md

@@ -1,6 +1,6 @@
 ## v3.1.0
 
-Released March ??, 2016
+Released March 29, 2016
 
 **Warning**: It's no longer possible to update from Lychee versions older than 2.7.
 
@@ -14,7 +14,8 @@ This updates includes a huge rewrite of the back-end. We are now using namespace
 
 - `New` Empty titles for albums
 - `New` Share albums as hidden so they are only viewable with a direct link (#27)
-- `Improved` Error messages and log output
+- `New` Log failed and successful login attempts (Thanks @qligier, #382 #246)
+- `Improved` error messages and log output
 - `Improved` The search shows albums above photos (#434)
 - `Improved` Album id now based on the current microtime (#27)
 - `Improved` Back-end modules and plugins
@@ -22,6 +23,10 @@ This updates includes a huge rewrite of the back-end. We are now using namespace
 - `Improved` Default photo title now "Untitled"
 - `Improved` Move to next photo after after moving a picture (#437)
 - `Improved` Return to album overview when canceling album password input
+- `Improved` URL import now accepts photo URLs containing "?" and ":" (Thanks @qligier, #482)
+- `Improved` Replaced date by strftime to simplify date translations (Thanks @qligier, #461)
+- `Fixed` Missing icons in Safari 9.1
+- `Fixed` duplicate uploads (Thanks @qligier, #433)
 - `Fixed` incorrect escaping when using backslashes
 - `Fixed` session_start() after sending headers (#433)
 - `Fixed` error when deleting last open photo in album

+ 4 - 1
docs/FAQ.md

@@ -55,4 +55,7 @@ Yes. Lychee uses ImageMagick when available.
 There's a problem with images compressed by ImageOptim. [Read more.](https://github.com/electerious/Lychee/issues/175#issuecomment-47403992)
 
 #### How to change the title of the site?
-[#455](https://github.com/electerious/Lychee/issues/455)
+[#455](https://github.com/electerious/Lychee/issues/455)
+
+#### How to reset username and password?
+Simply delete the whole `lychee_settings` table from the database. Lychee will regenerate it and ask you to enter a new username and password.

+ 3 - 3
php/Access/Admin.php

@@ -93,7 +93,7 @@ final class Admin extends Access {
 		Validator::required(isset($_POST['title']), __METHOD__);
 
 		$album = new Album(null);
-		Response::json($album->add($_POST['title']));
+		Response::json($album->add($_POST['title']), JSON_NUMERIC_CHECK);
 
 	}
 
@@ -231,7 +231,7 @@ final class Admin extends Access {
 		Validator::required(isset($_FILES, $_POST['albumID']), __METHOD__);
 
 		$photo = new Photo(null);
-		Response::json($photo->add($_FILES, $_POST['albumID']));
+		Response::json($photo->add($_FILES, $_POST['albumID']), JSON_NUMERIC_CHECK);
 
 	}
 
@@ -249,7 +249,7 @@ final class Admin extends Access {
 		Validator::required(isset($_POST['albumID'], $_POST['path']), __METHOD__);
 
 		$import = new Import();
-		echo $import->server($_POST['path'], $_POST['albumID']);
+		Response::json($import->server($_POST['path'], $_POST['albumID']));
 
 	}
 

+ 1 - 1
php/Access/Installation.php

@@ -26,7 +26,7 @@ final class Installation extends Access {
 
 		Validator::required(isset($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']), __METHOD__);
 
-		echo Config::create($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']);
+		Response::json(Config::create($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']));
 
 	}
 

+ 1 - 1
php/Modules/Album.php

@@ -21,7 +21,7 @@ final class Album {
 	}
 
 	/**
-	 * @return integer|false ID of the created album.
+	 * @return string|false ID of the created album.
 	 */
 	public function add($title = 'Untitled') {
 

+ 1 - 1
php/Modules/Config.php

@@ -42,7 +42,7 @@ $config = "<?php
 ?>";
 
 		// Save file
-		if (file_put_contents(LYCHEE_CONFIG_FILE, $config)===false) return 'Warning: Could not create file!';
+		if (@file_put_contents(LYCHEE_CONFIG_FILE, $config)===false) return 'Warning: Could not create file!';
 
 		return true;
 

+ 2 - 2
php/Modules/Database.php

@@ -56,7 +56,7 @@ final class Database {
 		$connection = self::connect($host, $user, $password);
 
 		// Check if the connection was successful
-		if ($connection===false) Response::error('' . $connection->connect_error);
+		if ($connection===false) Response::error($connection->connect_error);
 
 		if (self::setCharset($connection)===false) Response::error('Could not set database charset!');
 
@@ -81,7 +81,7 @@ final class Database {
 	public static function connect($host = 'localhost', $user, $password) {
 
 		// Open a new connection to the MySQL server
-		$connection = new Mysqli($host, $user, $password);
+		$connection = @new Mysqli($host, $user, $password);
 
 		// Check if the connection was successful
 		if ($connection->connect_errno) return false;

+ 2 - 2
php/Modules/Photo.php

@@ -39,7 +39,7 @@ final class Photo {
 	 * Creats new photo(s).
 	 * Exits on error.
 	 * Use $returnOnError if you want to handle errors by your own.
-	 * @return boolean Returns true when successful.
+	 * @return string|false ID of the added photo.
 	 */
 	public function add(array $files, $albumID = 0, $returnOnError = false) {
 
@@ -251,7 +251,7 @@ final class Photo {
 		// Call plugins
 		Plugins::get()->activate(__METHOD__, 1, func_get_args());
 
-		return true;
+		return $id;
 
 	}
 

+ 4 - 4
php/Modules/Response.php

@@ -6,19 +6,19 @@ final class Response {
 
 	public static function warning($msg) {
 
-		exit('Warning: ' . $msg);
+		exit(json_encode('Warning: ' . $msg));
 
 	}
 
 	public static function error($msg) {
 
-		exit('Error: ' . $msg);
+		exit(json_encode('Error: ' . $msg));
 
 	}
 
-	public static function json($str) {
+	public static function json($str, $options = 0) {
 
-		exit(json_encode($str));
+		exit(json_encode($str, $options));
 
 	}
 

+ 1 - 1
php/database/update_030100.php

@@ -14,6 +14,6 @@ $result = Database::execute($connection, $query, 'update_030100', __LINE__);
 if ($result===false) Response::error('Could not adjust the length of the album id field!');
 
 // Set version
-// if (Database::setVersion($connection, '030100')===false) Response::error('Could not update version of database!');
+if (Database::setVersion($connection, '030100')===false) Response::error('Could not update version of database!');
 
 ?>

+ 2 - 1
php/define.php

@@ -26,7 +26,7 @@ define('LYCHEE_URL_UPLOADS_BIG', 'uploads/big/');
 define('LYCHEE_URL_UPLOADS_MEDIUM', 'uploads/medium/');
 define('LYCHEE_URL_UPLOADS_THUMB', 'uploads/thumb/');
 
-function defineTablePrefix($dbTablePrefix = '') {
+function defineTablePrefix($dbTablePrefix) {
 
 	// This part is wrapped into a function, because it needs to be called
 	// after the config-file has been loaded. Other defines are available
@@ -34,6 +34,7 @@ function defineTablePrefix($dbTablePrefix = '') {
 
 	// Parse table prefix
 	// Old users do not have the table prefix stored in their config-file
+	if (isset($dbTablePrefix)===false) $dbTablePrefix = '';
 	if ($dbTablePrefix!=='') $dbTablePrefix .= '_';
 
 	// Define tables

+ 6 - 2
php/helpers/generateID.php

@@ -1,5 +1,8 @@
 <?php
 
+/**
+ * @return string Generated ID.
+ */
 function generateID() {
 
 	// Generate id based on the current microtime
@@ -8,8 +11,9 @@ function generateID() {
 	// Ensure that the id has a length of 14 chars
 	while(strlen($id)<14) $id .= 0;
 
-	// Return the integer value of the id
-	return intval($id);
+	// Return id as a string. Don't convert the id to an integer
+	// as 14 digits are too big for 32bit PHP versions.
+	return $id;
 
 }
 

+ 1 - 0
src/gulpfile.js

@@ -28,6 +28,7 @@ paths.view = {
 	],
 	scripts: [
 		'node_modules/jquery/dist/jquery.min.js',
+		'node_modules/basiccontext/dist/basicContext.min.js',
 		'../dist/_view--javascript.js'
 	],
 	svg: [

BIN
src/images/apple-touch-icon-ipad.png


BIN
src/images/apple-touch-icon-iphone-plus.png


BIN
src/images/apple-touch-icon-iphone.png


+ 1 - 1
src/images/no_cover.svg

@@ -1 +1 @@
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>Group</title><desc>Created with Sketch.</desc><g sketch:type="MSShapeGroup" fill="none"><path d="M0 0h16v16h-16v-16z" id="Shape" fill="#444"/><path id="Rectangle-path" stroke="#aaa" d="M4 4h5v5h-5z"/><path d="M6 6h8v7h-8v-7z" fill="#444"/><path stroke="#aaa" d="M7 7h5v5h-5z"/></g></svg>
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M0 0h16v16h-16v-16z" fill="#444"/><path stroke="#aaa" d="M4 4h5v5h-5z"/><path d="M6 6h8v7h-8v-7z" fill="#444"/><path stroke="#aaa" d="M7 7h5v5h-5z"/></g></svg>

+ 1 - 1
src/images/no_images.svg

@@ -1 +1 @@
-<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>Group</title><desc>Created with Sketch.</desc><g sketch:type="MSLayerGroup" fill="none"><path d="M0 0h200v200h-200v-200z" id="Shape" fill="#222" sketch:type="MSShapeGroup"/><g sketch:type="MSShapeGroup"><path id="Rectangle-path" stroke="#B4B4B4" stroke-width="4" d="M68 52h50v42h-50z"/><path d="M79 63h44v36h-44v-36z" fill="#222"/><path stroke="#B4B4B4" stroke-width="4" d="M88 72h50v42h-50z"/></g></g></svg>
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M0 0h200v200h-200v-200z" fill="#222"/><g><path stroke="#B4B4B4" stroke-width="4" d="M68 52h50v42h-50z"/><path d="M79 63h44v36h-44v-36z" fill="#222"/><path stroke="#B4B4B4" stroke-width="4" d="M88 72h50v42h-50z"/></g></g></svg>

+ 1 - 1
src/images/password.svg

@@ -1 +1 @@
-<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>Group</title><desc>Created with Sketch.</desc><g sketch:type="MSLayerGroup" fill="none"><path d="M0 0h200v200h-200v-200z" id="Shape" fill="#222" sketch:type="MSShapeGroup"/><path d="M100.5 49c-10.45 0-19 8.614-19 19.143v9.571h-9.5v38.286h57v-38.286h-9.5v-9.571c0-10.529-8.55-19.143-19-19.143zm0 9.571c5.32 0 9.5 4.211 9.5 9.571v9.571h-19v-9.571c0-5.36 4.18-9.571 9.5-9.571z" fill="#B4B4B4" sketch:type="MSShapeGroup"/></g></svg>
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M0 0h200v200h-200v-200z" id="Shape" fill="#222"/><path d="M100.5 49c-10.45 0-19 8.614-19 19.143v9.571h-9.5v38.286h57v-38.286h-9.5v-9.571c0-10.529-8.55-19.143-19-19.143zm0 9.571c5.32 0 9.5 4.211 9.5 9.571v9.571h-19v-9.571c0-5.36 4.18-9.571 9.5-9.571z" fill="#B4B4B4"/></g></svg>

+ 62 - 8
src/npm-shrinkwrap.json

@@ -32,6 +32,11 @@
       "from": "ansi-styles@>=2.1.0 <3.0.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.0.tgz"
     },
+    "any-promise": {
+      "version": "1.1.0",
+      "from": "any-promise@>=1.1.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.1.0.tgz"
+    },
     "archy": {
       "version": "1.0.0",
       "from": "archy@>=1.0.0 <2.0.0",
@@ -42,6 +47,11 @@
       "from": "are-we-there-yet@>=1.1.2 <1.2.0",
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz"
     },
+    "arr-flatten": {
+      "version": "1.0.1",
+      "from": "arr-flatten@>=1.0.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz"
+    },
     "array-differ": {
       "version": "1.0.0",
       "from": "array-differ@>=1.0.0 <2.0.0",
@@ -67,6 +77,11 @@
       "from": "array-uniq@>=1.0.2 <2.0.0",
       "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
     },
+    "arrify": {
+      "version": "1.0.1",
+      "from": "arrify@>=1.0.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz"
+    },
     "asn1": {
       "version": "0.2.3",
       "from": "asn1@>=0.2.3 <0.3.0",
@@ -341,8 +356,8 @@
       "from": "basiccontext@>=3.5.1 <4.0.0"
     },
     "basicmodal": {
-      "version": "3.3.3",
-      "from": "basicmodal@>=3.3.3 <4.0.0"
+      "version": "3.3.4",
+      "from": "basicmodal@>=3.3.4 <4.0.0"
     },
     "beeper": {
       "version": "1.1.0",
@@ -751,6 +766,18 @@
       "from": "get-stdin@>=4.0.1 <5.0.0",
       "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
     },
+    "get-value": {
+      "version": "1.3.1",
+      "from": "get-value@>=1.1.5 <2.0.0",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-1.3.1.tgz",
+      "dependencies": {
+        "lazy-cache": {
+          "version": "0.2.7",
+          "from": "lazy-cache@>=0.2.4 <0.3.0",
+          "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz"
+        }
+      }
+    },
     "glob": {
       "version": "5.0.15",
       "from": "glob@>=5.0.0 <5.1.0",
@@ -840,6 +867,18 @@
       "from": "graceful-readlink@>=1.0.0",
       "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
     },
+    "group-array": {
+      "version": "0.3.0",
+      "from": "group-array@>=0.3.0 <0.4.0",
+      "resolved": "https://registry.npmjs.org/group-array/-/group-array-0.3.0.tgz",
+      "dependencies": {
+        "kind-of": {
+          "version": "2.0.1",
+          "from": "kind-of@>=2.0.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz"
+        }
+      }
+    },
     "gulp": {
       "version": "3.9.1",
       "from": "gulp@>=3.9.1 <4.0.0",
@@ -880,9 +919,9 @@
       }
     },
     "gulp-inject": {
-      "version": "3.0.0",
-      "from": "gulp-inject@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/gulp-inject/-/gulp-inject-3.0.0.tgz"
+      "version": "4.0.0",
+      "from": "gulp-inject@>=4.0.0 <5.0.0",
+      "resolved": "https://registry.npmjs.org/gulp-inject/-/gulp-inject-4.0.0.tgz"
     },
     "gulp-load-plugins": {
       "version": "1.2.0",
@@ -1074,6 +1113,11 @@
       "from": "is-builtin-module@>=1.0.0 <2.0.0",
       "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz"
     },
+    "is-extendable": {
+      "version": "0.1.1",
+      "from": "is-extendable@>=0.1.1 <0.2.0",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz"
+    },
     "is-finite": {
       "version": "1.0.1",
       "from": "is-finite@>=1.0.0 <2.0.0",
@@ -1140,9 +1184,9 @@
       "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
     },
     "jquery": {
-      "version": "2.2.1",
-      "from": "jquery@>=2.2.1 <3.0.0",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.1.tgz"
+      "version": "2.2.3",
+      "from": "jquery@>=2.2.3 <3.0.0",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.3.tgz"
     },
     "js-base64": {
       "version": "2.1.9",
@@ -1466,6 +1510,11 @@
       "from": "node-uuid@>=1.4.7 <1.5.0",
       "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
     },
+    "noncharacters": {
+      "version": "1.1.0",
+      "from": "noncharacters@>=1.1.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/noncharacters/-/noncharacters-1.1.0.tgz"
+    },
     "nopt": {
       "version": "3.0.6",
       "from": "nopt@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
@@ -1856,6 +1905,11 @@
       "from": "stream-consume@>=0.1.0 <0.2.0",
       "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz"
     },
+    "stream-to-array": {
+      "version": "2.3.0",
+      "from": "stream-to-array@>=2.3.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz"
+    },
     "string_decoder": {
       "version": "0.10.31",
       "from": "string_decoder@>=0.10.0 <0.11.0",

+ 3 - 3
src/package.json

@@ -16,18 +16,18 @@
   "dependencies": {
     "babel-preset-es2015": "^6.6.0",
     "basiccontext": "^3.5.1",
-    "basicmodal": "^3.3.3",
+    "basicmodal": "^3.3.4",
     "gulp": "^3.9.1",
     "gulp-autoprefixer": "3.1.0",
     "gulp-babel": "^6.1.2",
     "gulp-concat": "^2.6.0",
-    "gulp-inject": "^3.0.0",
+    "gulp-inject": "^4.0.0",
     "gulp-load-plugins": "^1.2.0",
     "gulp-minify-css": "^1.2.4",
     "gulp-rimraf": "^0.2.0",
     "gulp-sass": "^2.2.0",
     "gulp-uglify": "^1.5.3",
-    "jquery": "^2.2.1",
+    "jquery": "^2.2.3",
     "mousetrap": "^1.5.3"
   }
 }

+ 3 - 12
src/scripts/album.js

@@ -116,9 +116,6 @@ album.add = function() {
 
 		api.post('Album::add', params, function(data) {
 
-			// Avoid first album to be true
-			if (data===true) data = 1
-
 			if (data!==false && isNumber(data)) {
 				albums.refresh()
 				lychee.goto(data)
@@ -512,26 +509,20 @@ album.setPublic = function(albumID, modal, e) {
 
 album.share = function(service) {
 
-	let link = ''
 	let url  = location.href
 
 	switch (service) {
 		case 'twitter':
-			link = `https://twitter.com/share?url=${ encodeURI(url) }`
+			window.open(`https://twitter.com/share?url=${ encodeURI(url) }`)
 			break
 		case 'facebook':
-			link = `http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(album.json.title) }`
+			window.open(`http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(album.json.title) }`)
 			break
 		case 'mail':
-			link = `mailto:?subject=${ encodeURI(album.json.title) }&body=${ encodeURI(url) }`
-			break
-		default:
-			link = ''
+			location.href = `mailto:?subject=${ encodeURI(album.json.title) }&body=${ encodeURI(url) }`
 			break
 	}
 
-	if (link!=='') location.href = link
-
 }
 
 album.getArchive = function(albumID) {

+ 1 - 10
src/scripts/api.js

@@ -26,15 +26,6 @@ api.post = function(fn, params, callback) {
 			return false
 		}
 
-		// Convert 1 to true and an empty string to false
-		if (data==='true')       data = true
-		else if (data==='false') data = false
-
-		// Convert to JSON if string start with '{' and ends with '}'
-		if (typeof data==='string' && data.substring(0, 1)==='{' && data.substring(data.length - 1, data.length)==='}') {
-			data = $.parseJSON(data)
-		}
-
 		callback(data)
 
 	}
@@ -49,7 +40,7 @@ api.post = function(fn, params, callback) {
 		type: 'POST',
 		url: api.path,
 		data: params,
-		dataType: 'text',
+		dataType: 'json',
 		success,
 		error
 	})

+ 6 - 6
src/scripts/contextMenu.js

@@ -304,10 +304,10 @@ contextMenu.sharePhoto = function(photoID, e) {
 		{ title: build.iconic('twitter', iconClass) + 'Twitter', fn: () => photo.share(photoID, 'twitter') },
 		{ title: build.iconic('facebook', iconClass) + 'Facebook', fn: () => photo.share(photoID, 'facebook') },
 		{ title: build.iconic('envelope-closed') + 'Mail', fn: () => photo.share(photoID, 'mail') },
-		{ title: build.iconic('dropbox', iconClass) + 'Dropbox', fn: () => photo.share(photoID, 'dropbox') },
+		{ title: build.iconic('dropbox', iconClass) + 'Dropbox', visible: lychee.publicMode===false, fn: () => photo.share(photoID, 'dropbox') },
 		{ title: build.iconic('link-intact') + 'Direct Link', fn: () => window.open(photo.getDirectLink()) },
-		{ },
-		{ title: build.iconic('ban') + 'Make Private', fn: () => photo.setPublic(photoID) }
+		{ visible: lychee.publicMode===false },
+		{ title: build.iconic('ban') + 'Make Private', visible: lychee.publicMode===false, fn: () => photo.setPublic(photoID) }
 	]
 
 	basicContext.show(items, e.originalEvent)
@@ -325,9 +325,9 @@ contextMenu.shareAlbum = function(albumID, e) {
 		{ title: build.iconic('twitter', iconClass) + 'Twitter', fn: () => album.share('twitter') },
 		{ title: build.iconic('facebook', iconClass) + 'Facebook', fn: () => album.share('facebook') },
 		{ title: build.iconic('envelope-closed') + 'Mail', fn: () => album.share('mail') },
-		{ },
-		{ title: build.iconic('pencil') + 'Edit Sharing', fn: () => album.setPublic(albumID, true, e) },
-		{ title: build.iconic('ban') + 'Make Private', fn: () => album.setPublic(albumID, false) }
+		{ visible: lychee.publicMode===false },
+		{ title: build.iconic('pencil') + 'Edit Sharing', visible: lychee.publicMode===false, fn: () => album.setPublic(albumID, true, e) },
+		{ title: build.iconic('ban') + 'Make Private', visible: lychee.publicMode===false, fn: () => album.setPublic(albumID, false) }
 	]
 
 	basicContext.show(items, e.originalEvent)

+ 8 - 2
src/scripts/lychee.js

@@ -236,8 +236,14 @@ lychee.setTitle = function(title, editable) {
 
 lychee.setMode = function(mode) {
 
-	$('#button_settings, #button_trash_album, #button_share_album, .button_add, .header__divider').remove()
-	$('#button_trash, #button_move, #button_share, #button_star').remove()
+	$('#button_settings, #button_trash_album, .button_add, .header__divider').remove()
+	$('#button_trash, #button_move, #button_star').remove()
+
+	$('#button_share, #button_share_album')
+		.removeClass('button--eye')
+		.addClass('button--share')
+		.find('use')
+		.attr('xlink:href', '#share')
 
 	$(document)
 		.off('click',       '.header__title--editable')

+ 3 - 9
src/scripts/photo.js

@@ -610,18 +610,17 @@ photo.deleteTag = function(photoID, index) {
 
 photo.share = function(photoID, service) {
 
-	let link = ''
 	let url  = photo.getViewLink(photoID)
 
 	switch (service) {
 		case 'twitter':
-			link = `https://twitter.com/share?url=${ encodeURI(url) }`
+			window.open(`https://twitter.com/share?url=${ encodeURI(url) }`)
 			break
 		case 'facebook':
-			link = `http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(photo.json.title) }`
+			window.open(`http://www.facebook.com/sharer.php?u=${ encodeURI(url) }&t=${ encodeURI(photo.json.title) }`)
 			break
 		case 'mail':
-			link = `mailto:?subject=${ encodeURI(photo.json.title) }&body=${ encodeURI(url) }`
+			location.href = `mailto:?subject=${ encodeURI(photo.json.title) }&body=${ encodeURI(url) }`
 			break
 		case 'dropbox':
 			lychee.loadDropbox(function() {
@@ -629,13 +628,8 @@ photo.share = function(photoID, service) {
 				Dropbox.save(photo.getDirectLink(), filename)
 			})
 			break
-		default:
-			link = ''
-			break
 	}
 
-	if (link!=='') location.href = link
-
 }
 
 photo.getArchive = function(photoID) {

+ 3 - 3
src/scripts/settings.js

@@ -36,7 +36,7 @@ settings.createConfig = function() {
 			if (data!==true) {
 
 				// Connection failed
-				if (data.indexOf('Warning: Connection failed!')!==-1) {
+				if (data==='Warning: Connection failed!') {
 
 					basicModal.show({
 						body: '<p>Unable to connect to host database because access was denied. Double-check your host, username and password and ensure that access from your current location is permitted.</p>',
@@ -53,7 +53,7 @@ settings.createConfig = function() {
 				}
 
 				// Creation failed
-				if (data.indexOf('Warning: Creation failed!')!==-1) {
+				if (data==='Warning: Creation failed!') {
 
 					basicModal.show({
 						body: '<p>Unable to create the database. Double-check your host, username and password and ensure that the specified user has the rights to modify and add content to the database.</p>',
@@ -70,7 +70,7 @@ settings.createConfig = function() {
 				}
 
 				// Could not create file
-				if (data.indexOf('Warning: Could not create file!')!==-1) {
+				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>",

+ 27 - 12
src/scripts/upload.js

@@ -93,13 +93,22 @@ upload.start = {
 
 			xhr.onload = function() {
 
+				let data      = null
 				let wait      = false
 				let errorText = ''
 
+				const isNumber = (n) => (!isNaN(parseFloat(n)) && isFinite(n))
+
+				try {
+					data = JSON.parse(xhr.responseText)
+				} catch(e) {
+					data = ''
+				}
+
 				file.ready = true
 
 				// Set status
-				if (xhr.status===200 && xhr.responseText==='true') {
+				if (xhr.status===200 && isNumber(data)) {
 
 					// Success
 					$('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status')
@@ -108,9 +117,9 @@ upload.start = {
 
 				} else {
 
-					if (xhr.responseText.substr(0, 6)==='Error:') {
+					if (data.substr(0, 6)==='Error:') {
 
-						errorText = xhr.responseText.substr(6) + ' Please take a look at the console of your browser for further details.'
+						errorText = data.substr(6) + ' Please take a look at the console of your browser for further details.'
 						error     = true
 
 						// Error Status
@@ -118,9 +127,12 @@ upload.start = {
 							.html('Failed')
 							.addClass('error')
 
-					} else if (xhr.responseText.substr(0, 8)==='Warning:') {
+						// Throw error
+						if (error===true) lychee.error('Upload failed. Server returned an error!', xhr, data)
+
+					} else if (data.substr(0, 8)==='Warning:') {
 
-						errorText = xhr.responseText.substr(8)
+						errorText = data.substr(8)
 						warning   = true
 
 						// Warning Status
@@ -128,6 +140,9 @@ upload.start = {
 							.html('Skipped')
 							.addClass('warning')
 
+						// Throw error
+						if (error===true) lychee.error('Upload failed. Server returned a warning!', xhr, data)
+
 					} else {
 
 						errorText = 'Server returned an unknown response. Please take a look at the console of your browser for further details.'
@@ -138,15 +153,15 @@ upload.start = {
 							.html('Failed')
 							.addClass('error')
 
+						// Throw error
+						if (error===true) lychee.error('Upload failed. Server returned an unkown error!', xhr, data)
+
 					}
 
 					$('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') p.notice')
 						.html(errorText)
 						.show()
 
-					// Throw error
-					if (error===true) lychee.error('Upload failed. Server returned the status code ' + xhr.status + '!', xhr, xhr.responseText)
-
 				}
 
 				// Check if there are file which are not finished
@@ -206,8 +221,8 @@ upload.start = {
 
 		for (let i = 0; i < files.length; i++) {
 
-			files[i].num       = i
-			files[i].ready     = false
+			files[i].num   = i
+			files[i].ready = false
 
 			if (i < files.length-1) files[i].next = files[i + 1]
 			else                    files[i].next = null
@@ -242,7 +257,7 @@ upload.start = {
 				basicModal.close()
 
 				files[0] = {
-					name      : data.link
+					name: data.link
 				}
 
 				upload.show('Importing URL', files, function() {
@@ -321,7 +336,7 @@ upload.start = {
 			let files = []
 
 			files[0] = {
-				name      : data.path
+				name: data.path
 			}
 
 			upload.show('Importing from server', files, function() {

+ 60 - 24
src/scripts/view/main.js

@@ -3,21 +3,12 @@
  * @copyright   2015 by Tobias Reich
  */
 
-// Sub-implementation of Lychee -------------------------------------------------------------- //
+// Sub-implementation of lychee -------------------------------------------------------------- //
 
 let lychee = {}
 
 lychee.content = $('.content')
 
-lychee.getEventName = function() {
-
-	let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement)
-	let eventName       = (touchendSupport===true ? 'touchend' : 'click')
-
-	return eventName
-
-}
-
 lychee.escapeHTML = function(html = '') {
 
 	// Ensure that html is a string
@@ -69,31 +60,76 @@ lychee.html = function(literalSections, ...substs) {
 
 }
 
+// Sub-implementation of photo -------------------------------------------------------------- //
+
+let photo = {}
+
+photo.share = function(photoID, service) {
+
+	let url  = location.toString()
+
+	switch (service) {
+		case 'twitter':
+			window.open(`https://twitter.com/share?url=${ encodeURI(url) }`)
+			break
+		case 'facebook':
+			window.open(`http://www.facebook.com/sharer.php?u=${ encodeURI(url) }`)
+			break
+		case 'mail':
+			location.href = `mailto:?subject=&body=${ encodeURI(url) }`
+			break
+	}
+
+}
+
+photo.getDirectLink = function() {
+
+	return $('#imageview img').attr('src').replace(/"/g,'').replace(/url\(|\)$/ig, '')
+
+}
+
+// Sub-implementation of contextMenu -------------------------------------------------------------- //
+
+let contextMenu = {}
+
+contextMenu.sharePhoto = function(photoID, e) {
+
+	let iconClass = 'ionicons'
+
+	let items = [
+		{ title: build.iconic('twitter', iconClass) + 'Twitter', fn: () => photo.share(photoID, 'twitter') },
+		{ title: build.iconic('facebook', iconClass) + 'Facebook', fn: () => photo.share(photoID, 'facebook') },
+		{ title: build.iconic('envelope-closed') + 'Mail', fn: () => photo.share(photoID, 'mail') },
+		{ title: build.iconic('link-intact') + 'Direct Link', fn: () => window.open(photo.getDirectLink(), '_newtab') }
+	]
+
+	basicContext.show(items, e.originalEvent)
+
+}
+
 // Main -------------------------------------------------------------- //
 
-let loadingBar = { show() {}, hide() {} },
-    imageview  = $('#imageview')
+let loadingBar = { show() {}, hide() {} }
+let imageview  = $('#imageview')
 
 $(document).ready(function() {
 
-	// Event Name
-	let eventName = lychee.getEventName()
+	// Save ID of photo
+	let photoID = gup('p')
 
 	// Set API error handler
 	api.onError = error
 
-	// Infobox
-	header.dom('#button_info').on(eventName, sidebar.toggle)
-
-	// Direct Link
-	header.dom('#button_direct').on(eventName, function() {
-
-		let link = $('#imageview img').attr('src').replace(/"/g,'').replace(/url\(|\)$/ig, '')
-		window.open(link, '_newtab')
-
+	// Share
+	header.dom('#button_share').on('click', function(e) {
+		contextMenu.sharePhoto(photoID, e)
 	})
 
-	loadPhotoInfo(gup('p'))
+	// Infobox
+	header.dom('#button_info').on('click', sidebar.toggle)
+
+	// Load photo
+	loadPhotoInfo(photoID)
 
 })
 

+ 0 - 1
src/styles/_basicContext.custom.scss

@@ -12,7 +12,6 @@
 	&__item {
 		margin-bottom: 2px;
 		font-size: 14px;
-		text-shadow: $shadowLight;
 
 		&--separator {
 			margin: 4px 0;

+ 1 - 5
src/styles/_content.scss

@@ -57,10 +57,10 @@
 			width: 200px;
 			height: 200px;
 			background: #222;
+			color: #222;
 			box-shadow: 0 2px 5px black(.5);
 			border: 1px solid white(.5);
 			transition: opacity .3s ease-out, transform .3s ease-out, border-color .3s ease-out;
-			will-change: transform;
 		}
 
 		&:hover img,
@@ -146,7 +146,6 @@
 		margin: 0 5px 0 0;
 		width: 8px;
 		height: 8px;
-		filter: drop-shadow(0 1px 3px black(.4));
 	}
 
 	.album img[data-overlay='false'] + .overlay h1,
@@ -185,7 +184,6 @@
 			fill: #fff;
 			width: 16px;
 			height: 16px;
-			filter: drop-shadow($shadowLight);
 		}
 	}
 
@@ -225,7 +223,6 @@
 	left: 50%;
 	padding-top: 20px;
 	color: white(.35);
-	text-shadow: 0 -1px 0 black(.4);
 	text-align: center;
 	transform: translateX(-50%) translateY(-50%);
 
@@ -234,7 +231,6 @@
 		margin: 0 0 10px;
 		width: 50px;
 		height: 50px;
-		filter: drop-shadow(0 -1px 0 black(.4));
 	}
 
 	p {

+ 2 - 0
src/styles/_header.scss

@@ -95,6 +95,8 @@
 
 		&--eye.active .iconic { fill: #ff9737; }
 
+		&--share .iconic { height: 18px; }
+
 		&--info.active .iconic { fill: $colorBlue; }
 	}
 

+ 0 - 2
src/styles/_imageview.scss

@@ -27,7 +27,6 @@
 		width: auto;
 		height: auto;
 		transition: top .3s, right .3s, bottom .3s, left .3s, max-width .3s, max-height .3s;
-		will-change: transform;
 
 		animation-name: zoomIn;
 		animation-duration: .3s;
@@ -105,7 +104,6 @@
 
 		.iconic {
 			fill: white(.8);
-			filter: drop-shadow(0 1px 0 black(.4));
 		}
 	}
 

+ 0 - 5
src/styles/_message.scss

@@ -26,7 +26,6 @@
 		color: white(.9);
 		font-size: 14px;
 		text-align: left;
-		text-shadow: $shadow;
 		line-height: 20px;
 
 		b {
@@ -56,7 +55,6 @@
 		padding: 13px 0 15px;
 		background: black(.02);
 		color: white(.5);
-		text-shadow: $shadow;
 		border-top: 1px solid black(.2);
 		box-shadow: inset 0 1px 0 white(.02);
 		cursor: default;
@@ -85,7 +83,6 @@
 		width: 100%;
 		background-color: transparent;
 		color: #fff;
-		text-shadow: $shadow;
 		border: none;
 		// Do not use rgba() for border-bottom
 		// to avoid a blurry line in Safari on non-retina screens
@@ -116,7 +113,6 @@
 			color: white(1);
 			font-size: 14px;
 			font-weight: 700;
-			text-shadow: $shadow;
 		}
 
 		label input {
@@ -258,7 +254,6 @@
 		color: #fff;
 		font-size: 16px;
 		font-weight: bold;
-		text-shadow: $shadow;
 		text-align: center;
 	}
 

+ 1 - 0
src/styles/_sidebar.scss

@@ -92,6 +92,7 @@
 	table {
 		float: left;
 		margin: 10px 0 15px 20px;
+		width: calc(100% - 20px);
 	}
 
 	table tr td {

+ 1 - 2
src/styles/main.scss

@@ -12,8 +12,7 @@
 }
 
 // Properties -------------------------------------------------------------- //
-$shadowLight : 0 -1px 0 black(.1);
-$shadow      : 0 -1px 0 black(.2);
+$shadow: 0 -1px 0 black(.2);
 
 // Colors ------------------------------------------------------------------ //
 $colorBlue : #2293EC;

+ 3 - 3
view.php

@@ -48,12 +48,12 @@
 
 			<a class="header__title"></a>
 
+			<a class="button button--share" id="button_share" title="Share Photo">
+				<svg class="iconic"><use xlink:href="#share"></use></svg>
+			</a>
 			<a class="button button--info" id="button_info" title="About Photo">
 				<svg class="iconic"><use xlink:href="#info"></use></svg>
 			</a>
-			<a class="button" id="button_direct" title="Direct Link">
-				<svg class="iconic"><use xlink:href="#link-intact"></use></svg>
-			</a>
 
 		</div>
 

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