Merge pull request #273 from electerious/v2.7.0

v2.7.0
pull/275/head v2.7.0
Tobias Reich 10 years ago
commit a4c09288dc

16
.gitignore vendored

@ -1,16 +1,18 @@
build/node_modules/
build/bower_components/
src/node_modules/
src/bower_components/
php/config.php
data/config.php
uploads/import/*
uploads/big/*
uploads/import/*
uploads/medium/*
uploads/thumb/*
plugins/*
etc/*
!uploads/import/index.html
!uploads/big/index.html
!uploads/import/index.html
!uploads/medium/index.html
!uploads/thumb/index.html
!plugins/check/
!plugins/check/
!plugins/displaylog/

@ -13,7 +13,7 @@ When reporting a bug on GitHub, make sure you include the following information:
- How to reproduce the issue (step-by-step)
- What you have already tried
- Output of the diagnostics (`plugins/check/index.php`)
- Browser and Browser version
- Browser and system version
- Attach files when you have problems which specific photos
## Coding Guidelines

@ -4,27 +4,31 @@ FROM ubuntu:14.04
RUN apt-get update
RUN apt-get -y install git curl nano wget build-essential
# Install apache and PHP
RUN apt-get -y install apache2 mysql-server libapache2-mod-php5
RUN apt-get -y install php5-mysql php5-gd php5-curl
#RUN sed -i "s/variables_order.*/variables_order = \"EGPCS\"/g" /etc/php5/apache2/php.ini
# Decouple database from container
VOLUME ["/database"]
# Configure the database to use our data dir
RUN sed -i -e "s/^datadir\s*=.*/datadir = \/database/" /etc/mysql/my.cnf
# Install Apache, PHP, MySQL, and ImageMagick
RUN apt-get -y install apache2 mysql-server libapache2-mod-php5 imagemagick
RUN apt-get -y install php5-mysql php5-gd php5-curl php5-imagick
# Modify php.ini to contain the following settings:
# max_execution_time = 200
# post_max_size = 100M
# upload_max_size = 100M
# upload_max_filesize = 20M
# memory_limit = 256M
RUN sed -i -e "s/^max_execution_time\s*=.*/max_execution_time = 200/" \
-e "s/^post_max_size\s*=.*/post_max_size = 100M/" \
-e "s/^upload_max_filesize\s*=.*/upload_max_filesize = 20M\nupload_max_size = 100M/" \
-e "s/^memory_limit\s*=.*/memory_limit = 256M/" /etc/php5/apache2/php.ini
# Link /var/www to /app directory
RUN mkdir -p /app && rm -fr /var/www/html && ln -s /app /var/www/html
WORKDIR /app
# Clone lychee
RUN git clone https://github.com/renfredxh/Lychee.git .
RUN git clone https://github.com/electerious/Lychee.git .
# Set file permissions
RUN chown www-data:www-data /app -R
RUN chown -R www-data:www-data /app
RUN chmod -R 777 uploads/ data/
EXPOSE 80
CMD scripts/start
CMD src/commands/start

@ -2,8 +2,8 @@
#### A great looking and easy-to-use Photo-Management-System.
![Lychee](http://l.electerious.com/uploads/big/136b4779d133a94666d5f0d151b8ea2f.png)
![Lychee](http://l.electerious.com/uploads/big/580f1300f884c330fa34b652decb0571.png)
![Lychee](http://l.electerious.com/uploads/big/68375288e048658e253a7c0c964f19b9.jpg)
![Lychee](http://l.electerious.com/uploads/big/198f237580ea004529ab7b6faad90274.jpg)
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://electerious.com/lychee_demo/) or read more on our [Website](http://lychee.electerious.com).
@ -39,6 +39,10 @@ In order to use the Dropbox import from your server, you need a valid drop-ins a
Lychee supports [Twitter Cards](https://dev.twitter.com/docs/cards) and [Open Graph](http://opengraphprotocol.org) for shared images (not albums). In order to use Twitter Cards you need to request an approval for your domain. Simply share an image with Lychee, copy its link and paste it in [Twitters Card Validator](https://dev.twitter.com/docs/cards/validation/validator).
### Imagick
Lychee uses [Imagick](http://www.imagemagick.org) when installed on your server. In this case you will benefit from a faster processing of your uploads, better looking thumbnails and intermediate sized images for small screen devices. You can disable the usage of [Imagick](http://www.imagemagick.org) in [the settings](docs/Settings.md).
### 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)

@ -1 +0,0 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>no_images</title><g sketch:type="MSLayerGroup" fill="none"><rect fill="#222" sketch:type="MSShapeGroup" width="200" height="200"/><g transform="translate(68 52)" sketch:type="MSShapeGroup"><rect stroke="#B4B4B4" stroke-width="4" width="50" height="42" rx="3"/><rect fill="#222" x="11" y="11" width="44" height="36"/><rect stroke="#B4B4B4" stroke-width="4" x="20" y="20" width="50" height="42" rx="3"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 542 B

@ -1 +0,0 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>password</title><g sketch:type="MSLayerGroup" fill="none"><rect fill="#222" sketch:type="MSShapeGroup" width="200" height="200"/><path d="M118 73h2.999c4.413 0 8.001 3.579 8.001 7.994v28.013c0 4.412-3.582 7.994-8.001 7.994h-40.999c-4.413 0-8.001-3.579-8.001-7.994v-28.013c0-4.412 3.582-7.994 8.001-7.994h2.999v-9.495c0-9.665 7.835-17.505 17.5-17.505 9.667 0 17.5 7.837 17.5 17.505v9.495zm-17.5-20c-5.799 0-10.5 4.702-10.5 10.498v9.502h21v-9.502c0-5.798-4.7-10.498-10.5-10.498zm0 0" fill="#B4B4B4" sketch:type="MSShapeGroup"/></g></svg>

Before

Width:  |  Height:  |  Size: 662 B

@ -1,17 +0,0 @@
/* ToolTips */
(function(c){function b(e,d){return(typeof e=="function")?(e.call(d)):e}function a(e,d){this.$element=c(e);this.options=d;this.enabled=true;this.fixTitle()}a.prototype={show:function(){var g=this.getTitle();if(g&&this.enabled){var f=this.tip();f.find(".tipsy-inner")[this.options.html?"html":"text"](g);f[0].className="tipsy";f.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).prependTo(document.body);var j=c.extend({},this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight});
var d=f[0].offsetWidth,i=f[0].offsetHeight,h=b(this.options.gravity,this.$element[0]);var e;switch(h.charAt(0)){case"n":e={top:j.top+j.height+this.options.offset,left:j.left+j.width/2-d/2};break;case"s":e={top:j.top-i-this.options.offset,left:j.left+j.width/2-d/2};break;case"e":e={top:j.top+j.height/2-i/2,left:j.left-d-this.options.offset};break;case"w":e={top:j.top+j.height/2-i/2,left:j.left+j.width+this.options.offset};break}if(h.length==2){if(h.charAt(1)=="w"){e.left=j.left+j.width/2-15}else{e.left=j.left+j.width/2-d+15
}}f.css(e).addClass("tipsy-"+h);f.find(".tipsy-arrow")[0].className="tipsy-arrow tipsy-arrow-"+h.charAt(0);if(this.options.className){f.addClass(b(this.options.className,this.$element[0]))}if(this.options.fade){f.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity})}else{f.css({visibility:"visible",opacity:this.options.opacity})}}},hide:function(){if(this.options.fade){this.tip().stop().fadeOut(function(){c(this).remove()})}else{this.tip().remove()}},fixTitle:function(){var d=this.$element;
if(d.attr("title")||typeof(d.attr("original-title"))!="string"){d.attr("original-title",d.attr("title")||"").removeAttr("title")}},getTitle:function(){var f,d=this.$element,e=this.options;this.fixTitle();var f,e=this.options;if(typeof e.title=="string"){f=d.attr(e.title=="title"?"original-title":e.title)}else{if(typeof e.title=="function"){f=e.title.call(d[0])}}f=(""+f).replace(/(^\s*|\s*$)/,"");return f||e.fallback},tip:function(){if(!this.$tip){this.$tip=c('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>')
}return this.$tip},validate:function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}},enable:function(){this.enabled=true},disable:function(){this.enabled=false},toggleEnabled:function(){this.enabled=!this.enabled}};c.fn.tipsy=function(h){if(h===true){return this.data("tipsy")}else{if(typeof h=="string"){var j=this.data("tipsy");if(j){j[h]()}return this}}h=c.extend({},c.fn.tipsy.defaults,h);function g(l){var m=c.data(l,"tipsy");if(!m){m=new a(l,c.fn.tipsy.elementOptions(l,h));
c.data(l,"tipsy",m)}return m}function k(){var l=g(this);l.hoverState="in";if(h.delayIn==0){l.show()}else{l.fixTitle();setTimeout(function(){if(l.hoverState=="in"){l.show()}},h.delayIn)}}function f(){var l=g(this);l.hoverState="out";if(h.delayOut==0){l.hide()}else{setTimeout(function(){if(l.hoverState=="out"){l.hide()}},h.delayOut)}}if(!h.live){this.each(function(){g(this)})}if(h.trigger!="manual"){var d=h.live?"live":"bind",i=h.trigger=="hover"?"mouseenter":"focus",e=h.trigger=="hover"?"mouseleave":"blur";
this[d](i,k)[d](e,f)}return this};c.fn.tipsy.defaults={className:null,delayIn:0,delayOut:0,fade:false,fallback:"",gravity:"n",html:false,live:false,offset:0,opacity:0.8,title:"title",trigger:"hover"};c.fn.tipsy.elementOptions=function(e,d){return c.metadata?c.extend({},d,c(e).metadata()):d};c.fn.tipsy.autoNS=function(){return c(this).offset().top>(c(document).scrollTop()+c(window).height()/2)?"s":"n"};c.fn.tipsy.autoWE=function(){return c(this).offset().left>(c(document).scrollLeft()+c(window).width()/2)?"e":"w"
};c.fn.tipsy.autoBounds=function(e,d){return function(){var f={ns:d[0],ew:(d.length>1?d[1]:false)},i=c(document).scrollTop()+e,g=c(document).scrollLeft()+e,h=c(this);if(h.offset().top<i){f.ns="n"}if(h.offset().left<g){f.ew="w"}if(c(window).width()+c(document).scrollLeft()-h.offset().left<e){f.ew="e"}if(c(window).height()+c(document).scrollTop()-h.offset().top<e){f.ns="s"}return f.ns+(f.ew?f.ew:"")}}})(jQuery);
/* Browser Detection */
var BrowserDetect={init:function(){this.browser=this.searchString(this.dataBrowser)||"An unknown browser";this.version=this.searchVersion(navigator.userAgent)||this.searchVersion(navigator.appVersion)||"an unknown version";this.OS=this.searchString(this.dataOS)||"an unknown OS"},searchString:function(d){for(var a=0;a<d.length;a++){var b=d[a].string;var c=d[a].prop;this.versionSearchString=d[a].versionSearch||d[a].identity;if(b){if(b.indexOf(d[a].subString)!=-1){return d[a].identity}}else{if(c){return d[a].identity}}}},searchVersion:function(b){var a=b.indexOf(this.versionSearchString);if(a==-1){return}return parseFloat(b.substring(a+this.versionSearchString.length+1))},dataBrowser:[{string:navigator.userAgent,subString:"Chrome",identity:"Chrome"},{string:navigator.userAgent,subString:"OmniWeb",versionSearch:"OmniWeb/",identity:"OmniWeb"},{string:navigator.vendor,subString:"Apple",identity:"Safari",versionSearch:"Version"},{prop:window.opera,identity:"Opera"},{string:navigator.vendor,subString:"iCab",identity:"iCab"},{string:navigator.vendor,subString:"KDE",identity:"Konqueror"},{string:navigator.userAgent,subString:"Firefox",identity:"Firefox"},{string:navigator.vendor,subString:"Camino",identity:"Camino"},{string:navigator.userAgent,subString:"Netscape",identity:"Netscape"},{string:navigator.userAgent,subString:"MSIE",identity:"Explorer",versionSearch:"MSIE"},{string:navigator.userAgent,subString:"Gecko",identity:"Mozilla",versionSearch:"rv"},{string:navigator.userAgent,subString:"Mozilla",identity:"Netscape",versionSearch:"Mozilla"}],dataOS:[{string:navigator.platform,subString:"Win",identity:"Windows"},{string:navigator.platform,subString:"Mac",identity:"Mac"},{string:navigator.userAgent,subString:"iPhone",identity:"iPhone/iPod"},{string:navigator.platform,subString:"Linux",identity:"Linux"}]};BrowserDetect.init();
function mobileBrowser() { if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) return true; else return false; }
/* GET */
function gup(b){b=b.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var a="[\\?&]"+b+"=([^&#]*)",d=new RegExp(a),c=d.exec(window.location.href);if(c===null){return""}else{return c[1]}};
/*! jQuery Retina Plugin */
(function(a){a.fn.retina=function(c){var d={"retina-background":false,"retina-suffix":"@2x"};if(c){a.extend(d,c)}var b=function(f,g){var e=new Image();e.onload=function(){g(e)};e.src=f};if(window.devicePixelRatio>1){this.each(function(){var e=a(this);if(this.tagName.toLowerCase()=="img"&&e.attr("src")){var g=e.attr("src").replace(/\.(?!.*\.)/,d["retina-suffix"]+".");b(g,function(h){e.attr("src",h.src);var i=a("<div>").append(e.clone()).remove().html();if(!(/(width|height)=["']\d+["']/.test(i))){e.attr("width",h.width/2)
}})}if(d["retina-background"]){var f=e.css("background-image");if(/^url\(.*\)$/.test(f)){var g=f.substring(4,f.length-1).replace(/\.(?!.*\.)/,d["retina-suffix"]+".");b(g,function(h){e.css("background-image","url("+h.src+")");if(e.css("background-size")=="auto auto"){e.css("background-size",(h.width/2)+"px auto")}})}}})}}})(jQuery);

@ -1,400 +0,0 @@
/**
* @name Album Module
* @description Takes care of every action an album can handle and execute.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
album = {
json: null,
getID: function() {
var id;
if (photo.json) id = photo.json.album;
else if (album.json) id = album.json.id;
else id = $(".album:hover, .album.active").attr("data-id");
// Search
if (!id) id = $(".album:hover, .album.active").attr("data-id");
if (!id) id = $(".photo:hover, .photo.active").attr("data-album-id");
if (id) return id;
else return false;
},
load: function(albumID, refresh) {
var startTime,
params,
durationTime,
waitTime;
password.get(albumID, function() {
if (!refresh) {
loadingBar.show();
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomOut");
lychee.animate(".divider", "fadeOut");
}
startTime = new Date().getTime();
params = "getAlbum&albumID=" + albumID + "&password=" + password.value;
lychee.api(params, function(data) {
if (data==="Warning: Album private!") {
if (document.location.hash.replace("#", "").split("/")[1]!=undefined) {
// Display photo only
lychee.setMode("view");
} else {
// Album not public
lychee.content.show();
lychee.goto("");
}
return false;
}
if (data==="Warning: Wrong password!") {
album.load(albumID, refresh);
return false;
}
album.json = data;
durationTime = (new Date().getTime() - startTime);
if (durationTime>300) waitTime = 0; else if (refresh) waitTime = 0; else waitTime = 300 - durationTime;
if (!visible.albums()&&!visible.photo()&&!visible.album()) waitTime = 0;
setTimeout(function() {
view.album.init();
if (!refresh) {
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomIn");
view.header.mode("album");
}
}, waitTime);
});
});
},
parse: function() {
if (!album.json.title) album.json.title = "Untitled";
},
add: function() {
var title,
params,
buttons,
isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n) };
buttons = [
["Create Album", function() {
title = $(".message input.text").val();
if (title.length===0) title = "Untitled";
modal.close();
params = "addAlbum&title=" + escape(encodeURI(title));
lychee.api(params, function(data) {
if (data===true) data = 1; // Avoid first album to be true
if (data!==false&&isNumber(data)) {
albums.refresh();
lychee.goto(data);
} else {
lychee.error(null, params, data);
}
});
}],
["Cancel", function() {}]
];
modal.show("New Album", "Enter a title for this album: <input class='text' type='text' maxlength='30' placeholder='Title' value='Untitled'>", buttons);
},
delete: function(albumIDs) {
var params,
buttons,
albumTitle;
if (!albumIDs) return false;
if (albumIDs instanceof Array===false) albumIDs = [albumIDs];
buttons = [
["", function() {
params = "deleteAlbum&albumIDs=" + albumIDs;
lychee.api(params, function(data) {
if (visible.albums()) {
albumIDs.forEach(function(id) {
albums.json.num--;
view.albums.content.delete(id);
delete albums.json.content[id];
});
} else {
albums.refresh();
lychee.goto("");
}
if (data!==true) lychee.error(null, params, data);
});
}],
["", function() {}]
];
if (albumIDs.toString()==="0") {
buttons[0][0] = "Clear Unsorted";
buttons[1][0] = "Keep Unsorted";
modal.show("Clear Unsorted", "Are you sure you want to delete all photos from 'Unsorted'?<br>This action can't be undone!", buttons);
} else if (albumIDs.length===1) {
buttons[0][0] = "Delete Album and Photos";
buttons[1][0] = "Keep Album";
// Get title
if (album.json) albumTitle = album.json.title;
else if (albums.json) albumTitle = albums.json.content[albumIDs].title;
modal.show("Delete Album", "Are you sure you want to delete the album '" + albumTitle + "' and all of the photos it contains? This action can't be undone!", buttons);
} else {
buttons[0][0] = "Delete Albums and Photos";
buttons[1][0] = "Keep Albums";
modal.show("Delete Albums", "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!", buttons);
}
},
setTitle: function(albumIDs) {
var oldTitle = "",
newTitle,
params,
buttons;
if (!albumIDs) return false;
if (albumIDs instanceof Array===false) albumIDs = [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.json.content[albumIDs].title;
if (!oldTitle) oldTitle = "";
oldTitle = oldTitle.replace("'", "&apos;");
}
buttons = [
["Set Title", function() {
// Get input
newTitle = $(".message input.text").val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
// Set to Untitled when empty
newTitle = (newTitle==="") ? "Untitled" : newTitle;
if (visible.album()) {
album.json.title = newTitle;
view.album.title();
if (albums.json) {
var id = albumIDs[0];
albums.json.content[id].title = newTitle;
}
} else if (visible.albums()) {
albumIDs.forEach(function(id) {
albums.json.content[id].title = newTitle;
view.albums.content.title(id);
});
}
params = "setAlbumTitle&albumIDs=" + albumIDs + "&title=" + escape(encodeURI(newTitle));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
if (albumIDs.length===1) modal.show("Set Title", "Enter a new title for this album: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
else modal.show("Set Titles", "Enter a title for all " + albumIDs.length + " selected album: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
},
setDescription: function(photoID) {
var oldDescription = album.json.description.replace("'", "&apos;"),
description,
params,
buttons;
buttons = [
["Set Description", function() {
// Get input
description = $(".message input.text").val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.album()) {
album.json.description = description;
view.album.description();
}
params = "setAlbumDescription&albumID=" + photoID + "&description=" + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Set Description", "Please enter a description for this album: <input class='text' type='text' maxlength='800' placeholder='Description' value='" + oldDescription + "'>", buttons);
},
setPublic: function(albumID, e) {
var params,
password = "",
listed = false,
downloadable = false;
albums.refresh();
if (!visible.message()&&album.json.public==0) {
modal.show("Share Album", "This album will be shared with the following properties:</p><form><div class='choice'><input type='checkbox' name='listed' value='listed' checked><h2>Visible</h2><p>Listed to visitors of your Lychee.</p></div><div class='choice'><input type='checkbox' name='downloadable' value='downloadable'><h2>Downloadable</h2><p>Visitors of your Lychee can download this album.</p></div><div class='choice'><input type='checkbox' name='password' value='password'><h2>Password protected</h2><p>Only accessible with a valid password.<input class='text' type='password' placeholder='password' value='' style='display: none;'></p></div></form><p style='display: none;'>", [["Share Album", function() { album.setPublic(album.getID(), e) }], ["Cancel", function() {}]], -170);
$(".message .choice input[name='password']").on("change", function() {
if ($(this).prop('checked')===true) $(".message .choice input.text").show();
else $(".message .choice input.text").hide();
});
return true;
}
if (visible.message()) {
if ($(".message .choice input[name='password']:checked").val()==="password") {
password = md5($(".message input.text").val());
album.json.password = 1;
} else {
password = "";
album.json.password = 0;
}
if ($(".message .choice input[name='listed']:checked").val()==="listed") listed = true;
if ($(".message .choice input[name='downloadable']:checked").val()==="downloadable") downloadable = true;
}
params = "setAlbumPublic&albumID=" + albumID + "&password=" + password + "&visible=" + listed + "&downloadable=" + downloadable;
if (visible.album()) {
album.json.public = (album.json.public==0) ? 1 : 0;
album.json.password = (album.json.public==0) ? 0 : album.json.password;
view.album.public();
view.album.password();
if (album.json.public==1) contextMenu.shareAlbum(albumID, e);
}
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
},
share: function(service) {
var link = "",
url = location.href;
switch (service) {
case 0:
link = "https://twitter.com/share?url=" + encodeURI(url);
break;
case 1:
link = "http://www.facebook.com/sharer.php?u=" + encodeURI(url) + "&t=" + encodeURI(album.json.title);
break;
case 2:
link = "mailto:?subject=" + encodeURI(album.json.title) + "&body=" + encodeURI(url);
break;
default:
link = "";
break;
}
if (link.length>5) location.href = link;
},
getArchive: function(albumID) {
var link,
url = "php/api.php?function=getAlbumArchive&albumID=" + albumID;
if (location.href.indexOf("index.html")>0) link = location.href.replace(location.hash, "").replace("index.html", url);
else link = location.href.replace(location.hash, "") + url;
if (lychee.publicMode) link += "&password=" + password.value;
location.href = link;
}
};

@ -1,113 +0,0 @@
/**
* @name Albums Module
* @description Takes care of every action albums can handle and execute.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
albums = {
json: null,
load: function() {
var startTime,
durationTime,
waitTime;
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomOut");
lychee.animate(".divider", "fadeOut");
startTime = new Date().getTime();
if(albums.json===null) {
lychee.api("getAlbums", function(data) {
/* Smart Albums */
data.unsortedAlbum = {
id: 0,
title: "Unsorted",
sysdate: data.unsortedNum + " photos",
unsorted: 1,
thumb0: data.unsortedThumb0,
thumb1: data.unsortedThumb1,
thumb2: data.unsortedThumb2
};
data.starredAlbum = {
id: "f",
title: "Starred",
sysdate: data.starredNum + " photos",
star: 1,
thumb0: data.starredThumb0,
thumb1: data.starredThumb1,
thumb2: data.starredThumb2
};
data.publicAlbum = {
id: "s",
title: "Public",
sysdate: data.publicNum + " photos",
public: 1,
thumb0: data.publicThumb0,
thumb1: data.publicThumb1,
thumb2: data.publicThumb2
};
data.recentAlbum = {
id: "r",
title: "Recent",
sysdate: data.recentNum + " photos",
recent: 1,
thumb0: data.recentThumb0,
thumb1: data.recentThumb1,
thumb2: data.recentThumb2
};
albums.json = data;
durationTime = (new Date().getTime() - startTime);
if (durationTime>300) waitTime = 0; else waitTime = 300 - durationTime;
if (!visible.albums()&&!visible.photo()&&!visible.album()) waitTime = 0;
if (visible.album()&&lychee.content.html()==="") waitTime = 0;
setTimeout(function() {
view.header.mode("albums");
view.albums.init();
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomIn");
}, waitTime);
});
} else {
setTimeout(function() {
view.header.mode("albums");
view.albums.init();
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomIn");
}, 300);
}
},
parse: function(album) {
if (album.password&&lychee.publicMode) {
album.thumb0 = "assets/img/password.svg";
album.thumb1 = "assets/img/password.svg";
album.thumb2 = "assets/img/password.svg";
} else {
if (!album.thumb0) album.thumb0 = "assets/img/no_images.svg";
if (!album.thumb1) album.thumb1 = "assets/img/no_images.svg";
if (!album.thumb2) album.thumb2 = "assets/img/no_images.svg";
}
},
refresh: function() {
albums.json = null;
}
};

@ -1,489 +0,0 @@
/**
* @name Build Module
* @description This module is used to generate HTML-Code.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
build = {
divider: function(title) {
return "<div class='divider fadeIn'><h1>" + title + "</h1></div>";
},
editIcon: function(id) {
return "<div id='" + id + "' class='edit'><a class='icon-pencil'></a></div>";
},
multiselect: function(top, left) {
return "<div id='multiselect' style='top: " + top + "px; left: " + left + "px;'></div>";
},
album: function(albumJSON) {
if (!albumJSON) return "";
var album = "",
longTitle = "",
title = albumJSON.title,
typeThumb = "";
if (title!==null&&title.length>18) {
title = albumJSON.title.substr(0, 18) + "...";
longTitle = albumJSON.title;
}
if (albumJSON.thumb0.split('.').pop()==="svg") typeThumb = "nonretina";
album += "<div class='album' data-id='" + albumJSON.id + "' data-password='" + albumJSON.password + "'>";
album += "<img src='" + albumJSON.thumb2 + "' width='200' height='200' alt='thumb' data-type='nonretina'>";
album += "<img src='" + albumJSON.thumb1 + "' width='200' height='200' alt='thumb' data-type='nonretina'>";
album += "<img src='" + albumJSON.thumb0 + "' width='200' height='200' alt='thumb' data-type='" + typeThumb + "'>";
album += "<div class='overlay'>";
if (albumJSON.password&&!lychee.publicMode) album += "<h1><span class='icon-lock'></span> " + title + "</h1>";
else album += "<h1 title='" + longTitle + "'>" + title + "</h1>";
album += "<a>" + albumJSON.sysdate + "</a>";
album += "</div>";
if (!lychee.publicMode) {
if(albumJSON.star==1) album += "<a class='badge red icon-star'></a>";
if(albumJSON.public==1) album += "<a class='badge red icon-share'></a>";
if(albumJSON.unsorted==1) album += "<a class='badge red icon-reorder'></a>";
if(albumJSON.recent==1) album += "<a class='badge red icon-time'></a>";
}
album += "</div>";
return album;
},
photo: function(photoJSON) {
if (!photoJSON) return "";
var photo = "",
longTitle = "",
title = photoJSON.title;
if (title!==null&&title.length>18) {
title = photoJSON.title.substr(0, 18) + "...";
longTitle = photoJSON.title;
}
photo += "<div class='photo' data-album-id='" + photoJSON.album + "' data-id='" + photoJSON.id + "'>";
photo += "<img src='" + photoJSON.thumbUrl + "' width='200' height='200' alt='thumb'>";
photo += "<div class='overlay'>";
photo += "<h1 title='" + longTitle + "'>" + title + "</h1>";
if (photoJSON.cameraDate==1) {
photo += "<a><span class='icon-camera' title='Photo Date'></span>" + photoJSON.sysdate + "</a>";
} else {
photo += "<a>" + photoJSON.sysdate + "</a>";
}
photo += "</div>";
if (photoJSON.star==1) photo += "<a class='badge red icon-star'></a>";
if (!lychee.publicMode&&photoJSON.public==1&&album.json.public!=1) photo += "<a class='badge red icon-share'></a>";
photo += "</div>";
return photo;
},
imageview: function(photoJSON, isSmall, visibleControls) {
if (!photoJSON) return "";
var view = "";
view += "<div class='arrow_wrapper previous'><a id='previous' class='icon-caret-left'></a></div>";
view += "<div class='arrow_wrapper next'><a id='next' class='icon-caret-right'></a></div>";
if (isSmall) {
if (visibleControls)
view += "<div id='image' class='small' style='background-image: url(" + photoJSON.url + "); width: " + photoJSON.width + "px; height: " + photoJSON.height + "px; margin-top: -" + parseInt(photoJSON.height/2-20) + "px; margin-left: -" + photoJSON.width/2 + "px;'></div>";
else
view += "<div id='image' class='small' style='background-image: url(" + photoJSON.url + "); width: " + photoJSON.width + "px; height: " + photoJSON.height + "px; margin-top: -" + parseInt(photoJSON.height/2) + "px; margin-left: -" + photoJSON.width/2 + "px;'></div>";
} else {
if (visibleControls)
view += "<div id='image' style='background-image: url(" + photoJSON.url + ")'></div>";
else
view += "<div id='image' style='background-image: url(" + photoJSON.url + ");' class='full'></div>";
}
return view;
},
no_content: function(typ) {
var no_content = "";
no_content += "<div class='no_content fadeIn'>";
no_content += "<a class='icon icon-" + typ + "'></a>";
if (typ==="search") no_content += "<p>No results</p>";
else if (typ==="share") no_content += "<p>No public albums</p>";
else if (typ==="cog") no_content += "<p>No configuration</p>";
no_content += "</div>";
return no_content;
},
modal: function(title, text, button, marginTop, closeButton) {
var modal = "",
custom_style = "";
if (marginTop) custom_style = "style='margin-top: " + marginTop + "px;'";
modal += "<div class='message_overlay fadeIn'>";
modal += "<div class='message center'" + custom_style + ">";
modal += "<h1>" + title + "</h1>";
if (closeButton!==false) {
modal += "<a class='close icon-remove-sign'></a>";
}
modal += "<p>" + text + "</p>";
$.each(button, function(index) {
if (this[0]!=="") {
if (index===0) modal += "<a class='button active'>" + this[0] + "</a>";
else modal += "<a class='button'>" + this[0] + "</a>";
}
});
modal += "</div>";
modal += "</div>";
return modal;
},
signInModal: function() {
var modal = "";
modal += "<div class='message_overlay'>";
modal += "<div class='message center'>";
modal += "<h1><a class='icon-lock'></a> Sign In</h1>";
modal += "<a class='close icon-remove-sign'></a>";
modal += "<div class='sign_in'>";
modal += "<input id='username' type='text' value='' placeholder='username' autocapitalize='off' autocorrect='off'>";
modal += "<input id='password' type='password' value='' placeholder='password'>";
modal += "</div>";
modal += "<div id='version'>Version " + lychee.version + "<span> &#8211; <a target='_blank' href='" + lychee.updateURL + "'>Update available!</a><span></div>";
modal += "<a onclick='lychee.login()' class='button active'>Sign in</a>";
modal += "</div>";
modal += "</div>";
return modal;
},
uploadModal: function(title, files) {
var modal = "";
modal += "<div class='upload_overlay fadeIn'>";
modal += "<div class='upload_message center'>";
modal += "<h1>" + title + "</h1>";
modal += "<a class='close icon-remove-sign'></a>";
modal += "<div class='rows'>";
for (var i = 0; i < files.length; i++) {
if (files[i].name.length>40) files[i].name = files[i].name.substr(0, 17) + "..." + files[i].name.substr(files[i].name.length-20, 20);
modal += "<div class='row'>";
modal += "<a class='name'>" + lychee.escapeHTML(files[i].name) + "</a>";
if (files[i].supported===true) modal += "<a class='status'></a>";
else modal += "<a class='status error'>Not supported</a>";
modal += "<p class='notice'></p>";
modal += "</div>";
}
modal += "</div>";
modal += "</div>";
modal += "</div>";
return modal;
},
contextMenu: function(items) {
var menu = "";
menu += "<div class='contextmenu_bg'></div>";
menu += "<div class='contextmenu'>";
menu += "<table>";
menu += "<tbody>";
$.each(items, function(index) {
if (items[index][0]==="separator"&&items[index][1]===-1) menu += "<tr class='separator'></tr>";
else if (items[index][1]===-1) menu += "<tr class='no_hover'><td>" + items[index][0] + "</td></tr>";
else if (items[index][2]!=undefined) menu += "<tr><td onclick='" + items[index][2] + "; window.contextMenu.close();'>" + items[index][0] + "</td></tr>";
else menu += "<tr><td onclick='window.contextMenu.fns[" + items[index][1] + "](); window.contextMenu.close();'>" + items[index][0] + "</td></tr>";
});
menu += "</tbody>";
menu += "</table>";
menu += "</div>";
return menu;
},
tags: function(tags, forView) {
var html = "",
editTagsHTML = (forView===true||lychee.publicMode) ? "" : " " + build.editIcon("edit_tags");
if (tags!=="") {
tags = tags.split(",");
tags.forEach(function(tag, index, array) {
html += "<a class='tag'>" + tag + "<span class='icon-remove' data-index='" + index + "'></span></a>";
});
html += editTagsHTML;
} else {
html = "<div class='empty'>No Tags" + editTagsHTML + "</div>";
}
return html;
},
infoboxPhoto: function(photoJSON, forView) {
if (!photoJSON) return "";
var infobox = "",
public,
editTitleHTML,
editDescriptionHTML,
infos,
exifHash = "";
infobox += "<div class='header'><h1>About</h1><a class='icon-remove-sign'></a></div>";
infobox += "<div class='wrapper'>";
switch (photoJSON.public) {
case "0":
public = "No";
break;
case "1":
public = "Yes";
break;
case "2":
public = "Yes (Album)";
break;
default:
public = "-";
break;
}
editTitleHTML = (forView===true||lychee.publicMode) ? "" : " " + build.editIcon("edit_title");
editDescriptionHTML = (forView===true||lychee.publicMode) ? "" : " " + build.editIcon("edit_description");
infos = [
["", "Basics"],
["Title", photoJSON.title + editTitleHTML],
["Uploaded", photoJSON.sysdate],
["Description", photoJSON.description + editDescriptionHTML],
["", "Image"],
["Size", photoJSON.size],
["Format", photoJSON.type],
["Resolution", photoJSON.width + " x " + photoJSON.height],
["Tags", build.tags(photoJSON.tags, forView)]
];
exifHash = photoJSON.takestamp+photoJSON.make+photoJSON.model+photoJSON.shutter+photoJSON.aperture+photoJSON.focal+photoJSON.iso;
if (exifHash!="0"&&exifHash!=="null") {
infos = infos.concat([
["", "Camera"],
["Captured", photoJSON.takedate],
["Make", photoJSON.make],
["Type/Model", photoJSON.model],
["Shutter Speed", photoJSON.shutter],
["Aperture", photoJSON.aperture],
["Focal Length", photoJSON.focal],
["ISO", photoJSON.iso]
]);
}
infos = infos.concat([
["", "Share"],
["Public", public]
]);
$.each(infos, function(index) {
if (infos[index][1]===""||infos[index][1]===undefined||infos[index][1]===null) infos[index][1] = "-";
switch (infos[index][0]) {
case "": // Separator
infobox += "</table>";
infobox += "<div class='separator'><h1>" + infos[index][1] + "</h1></div>";
infobox += "<table>";
break;
case "Tags": // Tags
if (forView!==true&&!lychee.publicMode) {
infobox += "</table>";
infobox += "<div class='separator'><h1>" + infos[index][0] + "</h1></div>";
infobox += "<div id='tags'>" + infos[index][1] + "</div>";
}
break;
default: // Item
infobox += "<tr>";
infobox += "<td>" + infos[index][0] + "</td>";
infobox += "<td class='attr_" + infos[index][0].toLowerCase() + "'>" + infos[index][1] + "</td>";
infobox += "</tr>";
break;
}
});
infobox += "</table>";
infobox += "<div class='bumper'></div>";
infobox += "</div>";
return infobox;
},
infoboxAlbum: function(albumJSON, forView) {
if (!albumJSON) return "";
var infobox = "",
public = "-",
password = "-",
downloadable = "-",
editTitleHTML,
editDescriptionHTML,
infos;
infobox += "<div class='header'><h1>About</h1><a class='icon-remove-sign'></a></div>";
infobox += "<div class='wrapper'>";
switch (albumJSON.public) {
case "0":
public = "No";
break;
case "1":
public = "Yes";
break;
}
switch (albumJSON.password) {
case false:
password = "No";
break;
case true:
password = "Yes";
break;
}
switch (albumJSON.downloadable) {
case "0":
downloadable = "No";
break;
case "1":
downloadable = "Yes";
break;
}
editTitleHTML = (forView===true||lychee.publicMode) ? "" : " " + build.editIcon("edit_title_album");
editDescriptionHTML = (forView===true||lychee.publicMode) ? "" : " " + build.editIcon("edit_description_album");
infos = [
["", "Basics"],
["Title", albumJSON.title + editTitleHTML],
["Description", albumJSON.description + editDescriptionHTML],
["", "Album"],
["Created", albumJSON.sysdate],
["Images", albumJSON.num],
["", "Share"],
["Public", public],
["Downloadable", downloadable],
["Password", password]
];
$.each(infos, function(index) {
if (infos[index][1]===""||infos[index][1]===undefined||infos[index][1]===null) infos[index][1] = "-";
if (infos[index][0]==="") {
infobox += "</table>";
infobox += "<div class='separator'><h1>" + infos[index][1] + "</h1></div>";
infobox += "<table id='infos'>";
} else {
infobox += "<tr>";
infobox += "<td>" + infos[index][0] + "</td>";
infobox += "<td class='attr_" + infos[index][0].toLowerCase() + "'>" + infos[index][1] + "</td>";
infobox += "</tr>";
}
});
infobox += "</table>";
infobox += "<div class='bumper'></div>";
infobox += "</div>";
return infobox;
}
};

@ -1,354 +0,0 @@
/**
* @name ContextMenu Module
* @description This module is used for the context menu.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
contextMenu = {
fns: null,
show: function(items, mouse_x, mouse_y, orientation) {
contextMenu.close();
$("body")
.css("overflow", "hidden")
.append(build.contextMenu(items));
// Do not leave the screen
if ((mouse_x+$(".contextmenu").outerWidth(true))>$("html").width()) orientation = "left";
if ((mouse_y+$(".contextmenu").outerHeight(true))>$("html").height()) mouse_y -= (mouse_y+$(".contextmenu").outerHeight(true)-$("html").height());
if (mouse_x>$(document).width()) mouse_x = $(document).width();
if (mouse_x<0) mouse_x = 0;
if (mouse_y>$(document).height()) mouse_y = $(document).height();
if (mouse_y<0) mouse_y = 0;
if (orientation==="left") mouse_x -= $(".contextmenu").outerWidth(true);
if (mouse_x===null||
mouse_x===undefined||
isNaN(mouse_x)||
mouse_y===null||
mouse_y===undefined||
isNaN(mouse_y)) {
mouse_x = "10px";
mouse_y = "10px";
}
$(".contextmenu").css({
"top": mouse_y,
"left": mouse_x,
"opacity": 0.98
});
},
add: function(e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
upload.notify();
contextMenu.fns = [
function() { $("#upload_files").click() },
function() { upload.start.url() },
function() { upload.start.dropbox() },
function() { upload.start.server() },
function() { album.add() }
];
items = [
["<a class='icon-picture'></a> Upload Photo", 0],
["separator", -1],
["<a class='icon-link'></a> Import from Link", 1],
["<a class='icon-folder-open'></a> Import from Dropbox", 2],
["<a class='icon-hdd'></a> Import from Server", 3],
["separator", -1],
["<a class='icon-folder-close'></a> New Album", 4]
];
contextMenu.show(items, mouse_x, mouse_y, "left");
},
settings: function(e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
contextMenu.fns = [
function() { settings.setLogin() },
function() { settings.setSorting() },
function() { settings.setDropboxKey() },
function() { window.open(lychee.website); },
function() { window.open("plugins/check/"); },
function() { window.open("plugins/displaylog/"); },
function() { lychee.logout() }
];
items = [
["<a class='icon-user'></a> Change Login", 0],
["<a class='icon-sort'></a> Change Sorting", 1],
["<a class='icon-folder-open'></a> Set Dropbox", 2],
["separator", -1],
["<a class='icon-info-sign'></a> About Lychee", 3],
["<a class='icon-dashboard'></a> Diagnostics", 4],
["<a class='icon-list'></a> Show Log", 5],
["separator", -1],
["<a class='icon-signout'></a> Sign Out", 6]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
},
album: function(albumID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
if (albumID==="0"||albumID==="f"||albumID==="s"||albumID==="r") return false;
contextMenu.fns = [
function() { album.setTitle([albumID]) },
function() { album.delete([albumID]) }
];
items = [
["<a class='icon-edit'></a> Rename", 0],
["<a class='icon-trash'></a> Delete", 1]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
$(".album[data-id='" + albumID + "']").addClass("active");
},
albumMulti: function(albumIDs, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
multiselect.stopResize();
contextMenu.fns = [
function() { album.setTitle(albumIDs) },
function() { album.delete(albumIDs) },
];
items = [
["<a class='icon-edit'></a> Rename All", 0],
["<a class='icon-trash'></a> Delete All", 1]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
},
photo: function(photoID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
contextMenu.fns = [
function() { photo.setStar([photoID]) },
function() { photo.editTags([photoID]) },
function() { photo.setTitle([photoID]) },
function() { photo.duplicate([photoID]) },
function() { contextMenu.move([photoID], e, "right") },
function() { photo.delete([photoID]) }
];
items = [
["<a class='icon-star'></a> Star", 0],
["<a class='icon-tags'></a> Tags", 1],
["separator", -1],
["<a class='icon-edit'></a> Rename", 2],
["<a class='icon-copy'></a> Duplicate", 3],
["<a class='icon-folder-open'></a> Move", 4],
["<a class='icon-trash'></a> Delete", 5]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
$(".photo[data-id='" + photoID + "']").addClass("active");
},
photoMulti: function(photoIDs, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
multiselect.stopResize();
contextMenu.fns = [
function() { photo.setStar(photoIDs) },
function() { photo.editTags(photoIDs) },
function() { photo.setTitle(photoIDs) },
function() { photo.duplicate(photoIDs) },
function() { contextMenu.move(photoIDs, e, "right") },
function() { photo.delete(photoIDs) }
];
items = [
["<a class='icon-star'></a> Star All", 0],
["<a class='icon-tags'></a> Tag All", 1],
["separator", -1],
["<a class='icon-edit'></a> Rename All", 2],
["<a class='icon-copy'></a> Duplicate All", 3],
["<a class='icon-folder-open'></a> Move All", 4],
["<a class='icon-trash'></a> Delete All", 5]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
},
photoMore: function(photoID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
contextMenu.fns = [
function() { window.open(photo.getDirectLink()) },
function() { photo.getArchive(photoID) }
];
items = [["<a class='icon-resize-full'></a> Full Photo", 0]];
if ((album.json&&album.json.downloadable&&album.json.downloadable==="1"&&lychee.publicMode)||!lychee.publicMode) items.push(["<a class='icon-circle-arrow-down'></a> Download", 1]);
contextMenu.show(items, mouse_x, mouse_y, "right");
},
move: function(photoIDs, e, orientation) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items = [];
contextMenu.close(true);
if (album.getID()!=="0") {
items = [
["Unsorted", 0, "photo.setAlbum([" + photoIDs + "], 0)"],
["separator", -1]
];
}
lychee.api("getAlbums", function(data) {
if (data.num===0) {
items = [["New Album", 0, "album.add()"]];
} else {
$.each(data.content, function(index) {
if (this.id!=album.getID()) items.push([this.title, 0, "photo.setAlbum([" + photoIDs + "], " + this.id + ")"]);
});
}
if (!visible.photo()) contextMenu.show(items, mouse_x, mouse_y, "right");
else contextMenu.show(items, mouse_x, mouse_y, "left");
});
},
sharePhoto: function(photoID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY,
items,
link = "";
mouse_y -= $(document).scrollTop();
contextMenu.fns = [
function() { photo.setPublic(photoID) },
function() { photo.share(photoID, 0) },
function() { photo.share(photoID, 1) },
function() { photo.share(photoID, 2) },
function() { photo.share(photoID, 3) },
function() { window.open(photo.getDirectLink()) }
];
link = photo.getViewLink(photoID);
if (photo.json.public==="2") link = location.href;
items = [
["<input readonly id='link' value='" + link + "'>", -1],
["separator", -1],
["<a class='icon-eye-close'></a> Make Private", 0],
["separator", -1],
["<a class='icon-twitter'></a> Twitter", 1],
["<a class='icon-facebook'></a> Facebook", 2],
["<a class='icon-envelope'></a> Mail", 3],
["<a class='icon-hdd'></a> Dropbox", 4],
["<a class='icon-link'></a> Direct Link", 5]
];
contextMenu.show(items, mouse_x, mouse_y, "left");
$(".contextmenu input").focus().select();
},
shareAlbum: function(albumID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY,
items;
mouse_y -= $(document).scrollTop();
contextMenu.fns = [
function() { album.setPublic(albumID) },
function() { album.share(0) },
function() { album.share(1) },
function() { album.share(2) },
function() { password.remove(albumID) }
];
items = [
["<input readonly id='link' value='" + location.href + "'>", -1],
["separator", -1],
["<a class='icon-eye-close'></a> Make Private", 0],
["separator", -1],
["<a class='icon-twitter'></a> Twitter", 1],
["<a class='icon-facebook'></a> Facebook", 2],
["<a class='icon-envelope'></a> Mail", 3],
];
contextMenu.show(items, mouse_x, mouse_y, "left");
$(".contextmenu input").focus().select();
},
close: function(leaveSelection) {
if (!visible.contextMenu()) return false;
contextMenu.fns = [];
$(".contextmenu_bg, .contextmenu").remove();
$("body").css("overflow", "auto");
if (leaveSelection!==true) {
$(".photo.active, .album.active").removeClass("active");
if (visible.multiselect()) multiselect.close();
}
}
};

@ -1,205 +0,0 @@
/**
* @name Init Module
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
$(document).ready(function(){
/* Event Name */
var event_name = (mobileBrowser()) ? "touchend" : "click";
/* Disable ContextMenu */
$(document).bind("contextmenu", function(e) { e.preventDefault() });
/* Tooltips */
if (!mobileBrowser()) $(".tools").tipsy({gravity: 'n', fade: false, delayIn: 0, opacity: 1});
/* Multiselect */
$("#content").on("mousedown", multiselect.show);
$(document).on("mouseup", multiselect.getSelection);
/* Header */
$("#hostedwith").on(event_name, function() { window.open(lychee.website) });
$("#button_signin").on(event_name, lychee.loginDialog);
$("#button_settings").on("click", contextMenu.settings);
$("#button_share").on(event_name, function(e) {
if (photo.json.public==1||photo.json.public==2) contextMenu.sharePhoto(photo.getID(), e);
else photo.setPublic(photo.getID(), e);
});
$("#button_share_album").on(event_name, function(e) {
if (album.json.public==1) contextMenu.shareAlbum(album.getID(), e);
else album.setPublic(album.getID(), e);
});
$("#button_more").on(event_name, function(e) { contextMenu.photoMore(photo.getID(), e) });
$("#button_trash_album").on(event_name, function() { album.delete([album.getID()]) });
$("#button_move").on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
$("#button_trash").on(event_name, function() { photo.delete([photo.getID()]) });
$("#button_info_album").on(event_name, function() { view.infobox.show() });
$("#button_info").on(event_name, function() { view.infobox.show() });
$("#button_archive").on(event_name, function() { album.getArchive(album.getID()) });
$("#button_star").on(event_name, function() { photo.setStar([photo.getID()]) });
/* Search */
$("#search").on("keyup click", function() { search.find($(this).val()) });
/* Clear Search */
$("#clearSearch").on(event_name, function () {
$("#search").focus();
search.reset();
});
/* Back Buttons */
$("#button_back_home").on(event_name, function() { lychee.goto("") });
$("#button_back").on(event_name, function() { lychee.goto(album.getID()) });
/* Image View */
lychee.imageview
.on(event_name, ".arrow_wrapper.previous", photo.previous)
.on(event_name, ".arrow_wrapper.next", photo.next);
/* Infobox */
$("#infobox")
.on(event_name, ".header a", function() { view.infobox.hide() })
.on(event_name, "#edit_title_album", function() { album.setTitle([album.getID()]) })
.on(event_name, "#edit_description_album", function() { album.setDescription(album.getID()) })
.on(event_name, "#edit_title", function() { photo.setTitle([photo.getID()]) })
.on(event_name, "#edit_description", function() { photo.setDescription(photo.getID()) })
.on(event_name, "#edit_tags", function() { photo.editTags([photo.getID()]) })
.on(event_name, "#tags .tag span", function() { photo.deleteTag(photo.getID(), $(this).data('index')) });
/* Keyboard */
Mousetrap
.bind('left', function() { if (visible.photo()) $("#imageview a#previous").click() })
.bind('right', function() { if (visible.photo()) $("#imageview a#next").click() })
.bind(['u', 'ctrl+u'], function() { $("#upload_files").click() })
.bind(['s', 'ctrl+s', 'f', 'ctrl+f'], function(e) {
if (visible.photo()) {
$("#button_star").click();
} else if (visible.albums()) {
e.preventDefault();
$("#search").focus();
}
})
.bind(['r', 'ctrl+r'], function(e) {
e.preventDefault();
if (visible.album()) album.setTitle(album.getID());
else if (visible.photo()) photo.setTitle([photo.getID()]);
})
.bind(['d', 'ctrl+d'], function(e) {
e.preventDefault();
if (visible.photo()) photo.setDescription(photo.getID());
else if (visible.album()) album.setDescription(album.getID());
})
.bind(['t', 'ctrl+t'], function(e) {
if (visible.photo()) {
e.preventDefault();
photo.editTags([photo.getID()]);
}
})
.bind(['i', 'ctrl+i'], function() {
if (visible.infobox()) view.infobox.hide();
else if (visible.multiselect()) return false;
else if (visible.infoboxbutton()) view.infobox.show();
})
.bind(['command+backspace', 'ctrl+backspace'], function() {
if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]);
else if (visible.album()&&!visible.message()) album.delete([album.getID()]);
})
.bind(['command+a', 'ctrl+a'], function() {
if (visible.album()&&!visible.message()) multiselect.selectAll();
else if (visible.albums()&&!visible.message()) multiselect.selectAll();
});
Mousetrap.bindGlobal('enter', function() {
if ($(".message .button.active").length) $(".message .button.active").addClass("pressed").click()
});
Mousetrap.bindGlobal(['esc', 'command+up'], function(e) {
e.preventDefault();
if (visible.message()&&$(".message .close").length>0) modal.close();
else if (visible.contextMenu()) contextMenu.close();
else if (visible.infobox()) view.infobox.hide();
else if (visible.photo()) lychee.goto(album.getID());
else if (visible.album()) lychee.goto("");
else if (visible.albums()&&$("#search").val().length!==0) search.reset();
});
if (mobileBrowser()) {
$(document)
/* Fullscreen on mobile */
.on('touchend', '#image', function(e) {
if (swipe.obj===null||(swipe.offset>=-5&&swipe.offset<=5)) {
if (visible.controls()) view.header.hide(e, 0);
else view.header.show();
}
})
/* Swipe on mobile */
.swipe().on('swipeStart', function() { if (visible.photo()) swipe.start($("#image")) })
.swipe().on('swipeMove', function(e) { if (visible.photo()) swipe.move(e.swipe) })
.swipe().on('swipeEnd', function(e) { if (visible.photo()) swipe.stop(e.swipe, photo.previous, photo.next) });
}
/* Document */
$(document)
/* Login */
.on("keyup", "#password", function() { if ($(this).val().length>0) $(this).removeClass("error") })
/* Header */
.on(event_name, "#title.editable", function() {
if (visible.photo()) photo.setTitle([photo.getID()]);
else album.setTitle([album.getID()]);
})
/* Navigation */
.on("click", ".album", function() { lychee.goto($(this).attr("data-id")) })
.on("click", ".photo", function() { lychee.goto(album.getID() + "/" + $(this).attr("data-id")) })
/* Modal */
.on(event_name, ".message .close", modal.close)
.on(event_name, ".message .button:first", function() { if (modal.fns!==null) modal.fns[0](); if (!visible.signin()) modal.close() })
.on(event_name, ".message .button:last", function() { if (modal.fns!==null) modal.fns[1](); if (!visible.signin()) modal.close() })
/* Add Dialog */
.on(event_name, ".button_add", function(e) { contextMenu.add(e) })
/* Context Menu */
.on("contextmenu", ".photo", function(e) { contextMenu.photo(photo.getID(), e) })
.on("contextmenu", ".album", function(e) { contextMenu.album(album.getID(), e) })
.on(event_name, ".contextmenu_bg", contextMenu.close)
.on("contextmenu", ".contextmenu_bg", contextMenu.close)
/* Infobox */
.on(event_name, "#infobox_overlay", view.infobox.hide)
/* Upload */
.on("change", "#upload_files", function() { modal.close(); upload.start.local(this.files) })
.on(event_name, ".upload_message a.close", upload.close)
.on("dragover", function(e) { e.preventDefault(); }, false)
.on("drop", function(e) {
e.stopPropagation();
e.preventDefault();
// Close open overlays or views which are correlating with the upload
if (visible.photo()) lychee.goto(album.getID());
if (visible.contextMenu()) contextMenu.close();
// Detect if dropped item is a file or a link
if (e.originalEvent.dataTransfer.files.length>0) upload.start.local(e.originalEvent.dataTransfer.files);
else if (e.originalEvent.dataTransfer.getData('Text').length>3) upload.start.url(e.originalEvent.dataTransfer.getData('Text'));
return true;
});
/* Init */
lychee.init();
});

@ -1,91 +0,0 @@
/**
* @name LoadingBar Module
* @description This module is used to show and hide the loading bar.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
loadingBar = {
status: null,
show: function(status, errorText) {
if (status==='error') {
// Set status
loadingBar.status = 'error';
// Parse text
if (errorText) errorText = errorText.replace('<br>', '');
if (!errorText) errorText = 'Whoops, it looks like something went wrong. Please reload the site and try again!';
// Move header down
if (visible.controls()) lychee.header.addClass('error');
// Modify loading
lychee.loadingBar
.removeClass('loading uploading error')
.addClass(status)
.html('<h1>Error: <span>' + errorText + '</span></h1>')
.show()
.css('height', '40px');
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() { loadingBar.hide(true) }, 3000));
return true;
}
if (loadingBar.status===null) {
// Set status
loadingBar.status = 'loading';
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() {
// Move header down
if (visible.controls()) lychee.header.addClass('loading');
// Modify loading
lychee.loadingBar
.removeClass('loading uploading error')
.addClass('loading')
.show();
}, 1000));
return true;
}
},
hide: function(force) {
if ((loadingBar.status!=='error'&&loadingBar.status!==null)||force) {
// Remove status
loadingBar.status = null;
// Move header up
if (visible.controls()) lychee.header.removeClass('error loading');
// Modify loading
lychee.loadingBar
.html('')
.css('height', '3px');
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
setTimeout(function() { lychee.loadingBar.hide() }, 300);
}
}
};

@ -1,377 +0,0 @@
/**
* @name Lychee Module
* @description This module provides the basic functions of Lychee.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
var lychee = {
title: "",
version: "2.6.3",
version_code: "020603",
api_path: "php/api.php",
update_path: "http://lychee.electerious.com/version/index.php",
updateURL: "https://github.com/electerious/Lychee",
website: "http://lychee.electerious.com",
publicMode: false,
viewMode: false,
debugMode: false,
username: "",
checkForUpdates: false,
sorting: "",
location: "",
dropbox: false,
dropboxKey: '',
loadingBar: $("#loading"),
header: $("header"),
content: $("#content"),
imageview: $("#imageview"),
infobox: $("#infobox"),
init: function() {
var params;
params = "init&version=" + lychee.version_code;
lychee.api(params, function(data) {
if (data.loggedIn!==true) {
lychee.setMode("public");
} else {
lychee.username = data.config.username || '';
lychee.sorting = data.config.sorting || '';
lychee.dropboxKey = data.config.dropboxKey || '';
lychee.location = data.config.location || '';
}
// No configuration
if (data==="Warning: No configuration!") {
lychee.header.hide();
lychee.content.hide();
$("body").append(build.no_content("cog"));
settings.createConfig();
return true;
}
// No login
if (data.config.login===false) {
settings.createLogin();
}
lychee.checkForUpdates = data.config.checkForUpdates;
$(window).bind("popstate", lychee.load);
lychee.load();
});
},
api: function(params, callback, loading) {
if (loading===undefined) loadingBar.show();
$.ajax({
type: "POST",
url: lychee.api_path,
data: "function=" + params,
dataType: "text",
success: function(data) {
setTimeout(function() { loadingBar.hide() }, 100);
if (typeof data==="string"&&data.substring(0, 7)==="Error: ") {
lychee.error(data.substring(7, data.length), params, data);
upload.close(true);
return false;
}
if (data==="1") data = true;
else if (data==="") data = false;
if (typeof data==="string"&&data.substring(0, 1)==="{"&&data.substring(data.length-1, data.length)==="}") data = $.parseJSON(data);
if (lychee.debugMode) console.log(data);
callback(data);
},
error: function(jqXHR, textStatus, errorThrown) {
lychee.error("Server error or API not found.", params, errorThrown);
upload.close(true);
}
});
},
login: function() {
var user = $("input#username").val(),
password = md5($("input#password").val()),
params;
params = "login&user=" + user + "&password=" + password;
lychee.api(params, function(data) {
if (data===true) {
// Use 'try' to catch a thrown error when Safari is in private mode
try { localStorage.setItem("lychee_username", user); }
catch (err) {}
window.location.reload();
} else {
// Show error and reactive button
$("#password").val("").addClass("error").focus();
$(".message .button.active").removeClass("pressed");
}
});
},
loginDialog: function() {
var local_username;
$("body").append(build.signInModal());
$("#username").focus();
if (localStorage) {
local_username = localStorage.getItem("lychee_username");
if (local_username!==null) {
if (local_username.length>0) $("#username").val(local_username);
$("#password").focus();
}
}
if (lychee.checkForUpdates==="1") lychee.getUpdate();
},
logout: function() {
lychee.api("logout", function() {
window.location.reload();
});
},
goto: function(url) {
if (url===undefined) url = "#";
else url = "#" + url;
history.pushState(null, null, url);
lychee.load();
},
load: function() {
var albumID = "",
photoID = "",
hash = document.location.hash.replace("#", "").split("/");
$(".no_content").remove();
contextMenu.close();
multiselect.close();
if (hash[0]!==undefined) albumID = hash[0];
if (hash[1]!==undefined) photoID = hash[1];
if (albumID&&photoID) {
// Trash data
photo.json = null;
// Show Photo
if (lychee.content.html()===""||($("#search").length&&$("#search").val().length!==0)) {
lychee.content.hide();
album.load(albumID, true);
}
photo.load(photoID, albumID);
} else if (albumID) {
// Trash data
photo.json = null;
// Show Album
if (visible.photo()) view.photo.hide();
if (album.json&&albumID==album.json.id) view.album.title();
else album.load(albumID);
} else {
// Trash data
album.json = null;
photo.json = null;
search.code = "";
// Show Albums
if (visible.album()) view.album.hide();
if (visible.photo()) view.photo.hide();
albums.load();
}
},
getUpdate: function() {
$.ajax({
url: lychee.update_path,
success: function(data) { if (parseInt(data)>parseInt(lychee.version_code)) $("#version span").show(); }
});
},
setTitle: function(title, editable) {
if (lychee.title==="") lychee.title = document.title;
if (title==="Albums") document.title = lychee.title;
else document.title = lychee.title + " - " + title;
if (editable) $("#title").addClass("editable");
else $("#title").removeClass("editable");
$("#title").html(title);
},
setMode: function(mode) {
$("#button_settings, #button_settings, #button_search, #search, #button_trash_album, #button_share_album, .button_add, .button_divider").remove();
$("#button_trash, #button_move, #button_share, #button_star").remove();
$(document)
.on("mouseenter", "#title.editable", function() { $(this).removeClass("editable") })
.off("click", "#title.editable")
.off("touchend", "#title.editable")
.off("contextmenu", ".photo")
.off("contextmenu", ".album")
.off("drop");
Mousetrap
.unbind(['u', 'ctrl+u'])
.unbind(['s', 'ctrl+s'])
.unbind(['r', 'ctrl+r'])
.unbind(['d', 'ctrl+d'])
.unbind(['t', 'ctrl+t'])
.unbind(['command+backspace', 'ctrl+backspace']);
if (mode==="public") {
$("header #button_signin, header #hostedwith").show();
lychee.publicMode = true;
} else if (mode==="view") {
Mousetrap.unbind('esc');
$("#button_back, a#next, a#previous").remove();
$(".no_content").remove();
lychee.publicMode = true;
lychee.viewMode = true;
}
},
animate: function(obj, animation) {
var animations = [
["fadeIn", "fadeOut"],
["contentZoomIn", "contentZoomOut"]
];
if (!obj.jQuery) obj = $(obj);
for (var i = 0; i < animations.length; i++) {
for (var x = 0; x < animations[i].length; x++) {
if (animations[i][x]==animation) {
obj.removeClass(animations[i][0] + " " + animations[i][1]).addClass(animation);
return true;
}
}
}
return false;
},
escapeHTML: function(s) {
return s.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
},
loadDropbox: function(callback) {
if (!lychee.dropbox&&lychee.dropboxKey) {
loadingBar.show();
var g = document.createElement("script"),
s = document.getElementsByTagName("script")[0];
g.src = "https://www.dropbox.com/static/api/1/dropins.js";
g.id = "dropboxjs";
g.type = "text/javascript";
g.async = "true";
g.setAttribute("data-app-key", lychee.dropboxKey);
g.onload = g.onreadystatechange = function() {
var rs = this.readyState;
if (rs&&rs!=="complete"&&rs!=="loaded") return;
lychee.dropbox = true;
loadingBar.hide();
callback();
};
s.parentNode.insertBefore(g, s);
} else if (lychee.dropbox&&lychee.dropboxKey) {
callback();
} else {
settings.setDropboxKey(callback);
}
},
removeHTML: function(html) {
var tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText;
},
error: function(errorThrown, params, data) {
console.error({
description: errorThrown,
params: params,
response: data
});
loadingBar.show("error", errorThrown);
}
};

@ -1,35 +0,0 @@
/**
* @name Modal Module
* @description Build, show and hide a modal.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
modal = {
fns: null,
show: function(title, text, buttons, marginTop, closeButton) {
if (!buttons) {
buttons = [
["", function() {}],
["", function() {}]
];
}
modal.fns = [buttons[0][1], buttons[1][1]];
$("body").append(build.modal(title, text, buttons, marginTop, closeButton));
$(".message input:first-child").focus().select();
},
close: function() {
modal.fns = null;
$(".message_overlay").removeClass("fadeIn").css("opacity", 0);
setTimeout(function() { $(".message_overlay").remove() }, 300);
}
};

@ -1,210 +0,0 @@
/**
* @name Multiselect Module
* @description Select multiple albums or photos.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
multiselect = {
position: {
top: null,
right: null,
bottom: null,
left: null
},
show: function(e) {
if (mobileBrowser()) return false;
if (lychee.publicMode) return false;
if (visible.search()) return false;
if (visible.infobox()) return false;
if (!visible.albums()&&!visible.album) return false;
if ($('.album:hover, .photo:hover').length!==0) return false;
if (visible.multiselect()) $('#multiselect').remove();
multiselect.position.top = e.pageY;
multiselect.position.right = -1 * (e.pageX - $(document).width());
multiselect.position.bottom = -1 * (multiselect.position.top - $(window).height());
multiselect.position.left = e.pageX;
$('body').append(build.multiselect(multiselect.position.top, multiselect.position.left));
$(document).on('mousemove', multiselect.resize);
},
selectAll: function() {
var e,
newWidth,
newHeight;
if (mobileBrowser()) return false;
if (lychee.publicMode) return false;
if (visible.search()) return false;
if (visible.infobox()) return false;
if (!visible.albums()&&!visible.album) return false;
if (visible.multiselect()) $('#multiselect').remove();
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));
newWidth = $(document).width() - multiselect.position.right + 2;
newHeight = $(document).height() - multiselect.position.bottom;
$('#multiselect').css({
width: newWidth,
height: newHeight
});
e = {
pageX: $(document).width() - (multiselect.position.right / 2),
pageY: $(document).height() - multiselect.position.bottom
};
multiselect.getSelection(e);
},
resize: function(e) {
var mouse_x = e.pageX,
mouse_y = e.pageY,
newHeight,
newWidth;
if (multiselect.position.top===null||
multiselect.position.right===null||
multiselect.position.bottom===null||
multiselect.position.left===null) return false;
if (mouse_y>=multiselect.position.top) {
// Do not leave the screen
newHeight = mouse_y - multiselect.position.top;
if ((multiselect.position.top+newHeight)>=$(document).height())
newHeight -= (multiselect.position.top + newHeight) - $(document).height() + 2;
$('#multiselect').css({
top: multiselect.position.top,
bottom: 'inherit',
height: newHeight
});
} else {
$('#multiselect').css({
top: 'inherit',
bottom: multiselect.position.bottom,
height: multiselect.position.top - e.pageY
});
}
if (mouse_x>=multiselect.position.left) {
// Do not leave the screen
newWidth = mouse_x - multiselect.position.left;
if ((multiselect.position.left+newWidth)>=$(document).width())
newWidth -= (multiselect.position.left + newWidth) - $(document).width() + 2;
$('#multiselect').css({
right: 'inherit',
left: multiselect.position.left,
width: newWidth
});
} else {
$('#multiselect').css({
right: multiselect.position.right,
left: 'inherit',
width: multiselect.position.left - e.pageX
});
}
},
stopResize: function() {
$(document).off('mousemove');
},
getSize: function() {
if (!visible.multiselect()) return false;
return {
top: $('#multiselect').offset().top,
left: $('#multiselect').offset().left,
width: parseInt($('#multiselect').css('width').replace('px', '')),
height: parseInt($('#multiselect').css('height').replace('px', ''))
};
},
getSelection: function(e) {
var tolerance = 150,
id,
ids = [],
offset,
size = multiselect.getSize();
if (visible.contextMenu()) return false;
if (!visible.multiselect()) return false;
$('.photo, .album').each(function() {
offset = $(this).offset();
if (offset.top>=(size.top-tolerance)&&
offset.left>=(size.left-tolerance)&&
(offset.top+206)<=(size.top+size.height+tolerance)&&
(offset.left+206)<=(size.left+size.width+tolerance)) {
id = $(this).data('id');
if (id!=='0'&&id!==0&&id!=='f'&&id!=='s'&&id!=='r'&&id!==null&&id!==undefined) {
ids.push(id);
$(this).addClass('active');
}
}
});
if (ids.length!==0&&visible.album()) contextMenu.photoMulti(ids, e);
else if (ids.length!==0&&visible.albums()) contextMenu.albumMulti(ids, e);
else multiselect.close();
},
close: function() {
multiselect.stopResize();
multiselect.position.top = null;
multiselect.position.right = null;
multiselect.position.bottom = null;
multiselect.position.left = null;
lychee.animate('#multiselect', 'fadeOut');
setTimeout(function() {
$('#multiselect').remove();
}, 300);
}
};

@ -1,63 +0,0 @@
/**
* @name Password Module
* @description Controls the access to password-protected albums and photos.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
password = {
value: "",
get: function(albumID, callback) {
var passwd = $(".message input.text").val(),
params;
if (!lychee.publicMode) callback();
else if (album.json&&album.json.password==false) callback();
else if (albums.json&&albums.json.content[albumID].password==false) callback();
else if (!albums.json&&!album.json) {
// Continue without password
album.json = {password: true};
callback("");
} else if (passwd==undefined) {
// Request password
password.getDialog(albumID, callback);
} else {
// Check password
params = "checkAlbumAccess&albumID=" + albumID + "&password=" + md5(passwd);
lychee.api(params, function(data) {
if (data===true) {
password.value = md5(passwd);
callback();
} else {
lychee.goto("");
loadingBar.show("error", "Access denied. Wrong password!");
}
});
}
},
getDialog: function(albumID, callback) {
var buttons;
buttons = [
["Enter", function() { password.get(albumID, callback) }],
["Cancel", lychee.goto]
];
modal.show("<a class='icon-lock'></a> Enter Password", "This album is protected by a password. Enter the password below to view the photos of this album: <input class='text' type='password' placeholder='password' value=''>", buttons, -110, false);
}
};

@ -1,590 +0,0 @@
/**
* @name Photo Module
* @description Takes care of every action a photo can handle and execute.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
photo = {
json: null,
cache: null,
getID: function() {
var id;
if (photo.json) id = photo.json.id;
else id = $(".photo:hover, .photo.active").attr("data-id");
if (id) return id;
else return false;
},
load: function(photoID, albumID) {
var params,
checkPasswd;
params = "getPhoto&photoID=" + photoID + "&albumID=" + albumID + "&password=" + password.value;
lychee.api(params, function(data) {
if (data==="Warning: Wrong password!") {
checkPasswd = function() {
if (password.value!=="") photo.load(photoID, albumID);
else setTimeout(checkPasswd, 250);
};
checkPasswd();
return false;
}
photo.json = data;
if (!visible.photo()) view.photo.show();
view.photo.init();
lychee.imageview.show();
setTimeout(function() {
lychee.content.show();
//photo.preloadNext(photoID, albumID);
}, 300);
});
},
//preload the next photo for better response time
preloadNext: function(photoID) {
var nextPhoto,
url;
// Never preload on mobile devices with bare RAM and
// mostly mobile internet
if (mobileBrowser()) return false;
if (album.json &&
album.json.content &&
album.json.content[photoID] &&
album.json.content[photoID].nextPhoto!="") {
nextPhoto = album.json.content[photoID].nextPhoto;
url = album.json.content[nextPhoto].url;
photo.cache = new Image();
photo.cache.src = url;
photo.cache.onload = function() { photo.cache = null };
}
},
parse: function() {
if (!photo.json.title) photo.json.title = "Untitled";
},
previous: function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].previousPhoto!=="") {
if (animate===true) {
delay = 200;
$("#image").css({
WebkitTransform: 'translateX(100%)',
MozTransform: 'translateX(100%)',
transform: 'translateX(100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].previousPhoto)
}, delay);
}
},
next: function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].nextPhoto!=="") {
if (animate===true) {
delay = 200;
$("#image").css({
WebkitTransform: 'translateX(-100%)',
MozTransform: 'translateX(-100%)',
transform: 'translateX(-100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].nextPhoto);
}, delay);
}
},
duplicate: function(photoIDs) {
var params;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
albums.refresh();
params = "duplicatePhoto&photoIDs=" + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
else album.load(album.getID(), false);
});
},
delete: function(photoIDs) {
var params,
buttons,
photoTitle;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
if (photoIDs.length===1) {
// Get title if only one photo is selected
if (visible.photo()) photoTitle = photo.json.title;
else photoTitle = album.json.content[photoIDs].title;
if (photoTitle==="") photoTitle = "Untitled";
}
buttons = [
["", function() {
var nextPhoto,
previousPhoto;
photoIDs.forEach(function(id, index, array) {
// Change reference for the next and previous photo
if (album.json.content[id].nextPhoto!==""||album.json.content[id].previousPhoto!=="") {
nextPhoto = album.json.content[id].nextPhoto;
previousPhoto = album.json.content[id].previousPhoto;
album.json.content[previousPhoto].nextPhoto = nextPhoto;
album.json.content[nextPhoto].previousPhoto = previousPhoto;
}
album.json.content[id] = null;
view.album.content.delete(id);
});
// Only when search is not active
if (!visible.albums()) lychee.goto(album.getID());
albums.refresh();
params = "deletePhoto&photoIDs=" + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["", function() {}]
];
if (photoIDs.length===1) {
buttons[0][0] = "Delete Photo";
buttons[1][0] = "Keep Photo";
modal.show("Delete Photo", "Are you sure you want to delete the photo '" + photoTitle + "'?<br>This action can't be undone!", buttons);
} else {
buttons[0][0] = "Delete Photos";
buttons[1][0] = "Keep Photos";
modal.show("Delete Photos", "Are you sure you want to delete all " + photoIDs.length + " selected photo?<br>This action can't be undone!", buttons);
}
},
setTitle: function(photoIDs) {
var oldTitle = "",
newTitle,
params,
buttons;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
if (photoIDs.length===1) {
// Get old title if only one photo is selected
if (photo.json) oldTitle = photo.json.title;
else if (album.json) oldTitle = album.json.content[photoIDs].title;
oldTitle = oldTitle.replace("'", "&apos;");
}
buttons = [
["Set Title", function() {
// Get input
newTitle = $(".message input.text").val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
if (visible.photo()) {
photo.json.title = (newTitle==="") ? "Untitled" : newTitle;
view.photo.title();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].title = newTitle;
view.album.content.title(id);
});
params = "setPhotoTitle&photoIDs=" + photoIDs + "&title=" + escape(encodeURI(newTitle));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
if (photoIDs.length===1) modal.show("Set Title", "Enter a new title for this photo: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
else modal.show("Set Titles", "Enter a title for all " + photoIDs.length + " selected photos: <input class='text' type='text' maxlength='30' placeholder='Title' value=''>", buttons);
},
setAlbum: function(photoIDs, albumID) {
var params,
nextPhoto,
previousPhoto;
if (!photoIDs) return false;
if (visible.photo) lychee.goto(album.getID());
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
photoIDs.forEach(function(id, index, array) {
// Change reference for the next and previous photo
if (album.json.content[id].nextPhoto!==""||album.json.content[id].previousPhoto!=="") {
nextPhoto = album.json.content[id].nextPhoto;
previousPhoto = album.json.content[id].previousPhoto;
album.json.content[previousPhoto].nextPhoto = nextPhoto;
album.json.content[nextPhoto].previousPhoto = previousPhoto;
}
album.json.content[id] = null;
view.album.content.delete(id);
});
albums.refresh();
params = "setPhotoAlbum&photoIDs=" + photoIDs + "&albumID=" + albumID;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
},
setStar: function(photoIDs) {
var params;
if (!photoIDs) return false;
if (visible.photo()) {
photo.json.star = (photo.json.star==0) ? 1 : 0;
view.photo.star();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].star = (album.json.content[id].star==0) ? 1 : 0;
view.album.content.star(id);
});
albums.refresh();
params = "setPhotoStar&photoIDs=" + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
},
setPublic: function(photoID, e) {
var params;
if (photo.json.public==2) {
modal.show("Public Album", "This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.", [["Show Album", function() { lychee.goto(photo.json.original_album) }], ["Close", function() {}]]);
return false;
}
if (visible.photo()) {
photo.json.public = (photo.json.public==0) ? 1 : 0;
view.photo.public();
if (photo.json.public==1) contextMenu.sharePhoto(photoID, e);
}
album.json.content[photoID].public = (album.json.content[photoID].public==0) ? 1 : 0;
view.album.content.public(photoID);
albums.refresh();
params = "setPhotoPublic&photoID=" + photoID;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
},
setDescription: function(photoID) {
var oldDescription = photo.json.description.replace("'", "&apos;"),
description,
params,
buttons;
buttons = [
["Set Description", function() {
// Get input
description = $(".message input.text").val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.photo()) {
photo.json.description = description;
view.photo.description();
}
params = "setPhotoDescription&photoID=" + photoID + "&description=" + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Set Description", "Enter a description for this photo: <input class='text' type='text' maxlength='800' placeholder='Description' value='" + oldDescription + "'>", buttons);
},
editTags: function(photoIDs) {
var oldTags = "",
tags = "",
buttons;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
// Get tags
if (visible.photo()) oldTags = photo.json.tags;
if (visible.album()&&photoIDs.length===1) oldTags = album.json.content[photoIDs].tags;
if (visible.album()&&photoIDs.length>1) {
var same = true;
photoIDs.forEach(function(id, index, array) {
if(album.json.content[id].tags===album.json.content[photoIDs[0]].tags&&same===true) same = true;
else same = false;
});
if (same) oldTags = album.json.content[photoIDs[0]].tags;
}
// Improve tags
oldTags = oldTags.replace(/,/g, ', ');
buttons = [
["Set Tags", function() {
tags = $(".message input.text").val();
photo.setTags(photoIDs, tags);
}],
["Cancel", function() {}]
];
if (photoIDs.length===1) modal.show("Set Tags", "Enter your tags for this photo. You can add multiple tags by separating them with a comma: <input class='text' type='text' maxlength='800' placeholder='Tags' value='" + oldTags + "'>", buttons);
else modal.show("Set Tags", "Enter your tags for all " + photoIDs.length + " selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: <input class='text' type='text' maxlength='800' placeholder='Tags' value='" + oldTags + "'>", buttons);
},
setTags: function(photoIDs, tags) {
var params;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
// Parse tags
tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',');
tags = tags.replace(/,$|^,|(\ ){0,}$/g, '');
// Remove html from input
tags = lychee.removeHTML(tags);
if (visible.photo()) {
photo.json.tags = tags;
view.photo.tags();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].tags = tags;
});
params = "setPhotoTags&photoIDs=" + photoIDs + "&tags=" + tags;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
},
deleteTag: function(photoID, index) {
var tags;
// Remove
tags = photo.json.tags.split(',');
tags.splice(index, 1);
// Save
photo.json.tags = tags.toString();
photo.setTags([photoID], photo.json.tags);
},
share: function(photoID, service) {
var link = "",
url = photo.getViewLink(photoID),
filename = "unknown";
switch (service) {
case 0:
link = "https://twitter.com/share?url=" + encodeURI(url);
break;
case 1:
link = "http://www.facebook.com/sharer.php?u=" + encodeURI(url) + "&t=" + encodeURI(photo.json.title);
break;
case 2:
link = "mailto:?subject=" + encodeURI(photo.json.title) + "&body=" + encodeURI(url);
break;
case 3:
lychee.loadDropbox(function() {
filename = photo.json.title + "." + photo.getDirectLink().split('.').pop();
Dropbox.save(photo.getDirectLink(), filename);
});
break;
default:
link = "";
break;
}
if (link.length>5) location.href = link;
},
isSmall: function() {
var size = {
width: false,
height: false
};
if (photo.json.width<$(window).width()-60) size.width = true;
if (photo.json.height<$(window).height()-100) size.height = true;
if (size.width&&size.height) return true;
else return false;
},
getArchive: function(photoID) {
var link,
url = "php/api.php?function=getPhotoArchive&photoID=" + photoID;
if (location.href.indexOf("index.html")>0) link = location.href.replace(location.hash, "").replace("index.html", url);
else link = location.href.replace(location.hash, "") + url;
if (lychee.publicMode) link += "&password=" + password.value;
location.href = link;
},
getDirectLink: function() {
return $("#imageview #image").css("background-image").replace(/"/g,"").replace(/url\(|\)$/ig, "");
},
getViewLink: function(photoID) {
var url = "view.php?p=" + photoID;
if (location.href.indexOf("index.html")>0) return location.href.replace("index.html" + location.hash, url);
else return location.href.replace(location.hash, url);
}
};

@ -1,97 +0,0 @@
/**
* @name Search Module
* @description Searches through your photos and albums.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
search = {
code: null,
find: function(term) {
var params,
albumsData = "",
photosData = "",
code;
clearTimeout($(window).data("timeout"));
$(window).data("timeout", setTimeout(function() {
if ($("#search").val().length!==0) {
params = "search&term=" + term;
lychee.api(params, function(data) {
if (data&&data.albums) {
albums.json = { content: data.albums };
$.each(albums.json.content, function() {
albums.parse(this);
albumsData += build.album(this);
});
}
if (data&&data.photos) {
album.json = { content: data.photos };
$.each(album.json.content, function() {
photosData += build.photo(this);
});
}
if (albumsData===""&&photosData==="") code = "error";
else if (albumsData==="") code = build.divider("Photos")+photosData;
else if (photosData==="") code = build.divider("Albums")+albumsData;
else code = build.divider("Photos")+photosData+build.divider("Albums")+albumsData;
if (search.code!==md5(code)) {
$(".no_content").remove();
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomOut");
lychee.animate(".divider", "fadeOut");
search.code = md5(code);
setTimeout(function() {
if (code==="error") $("body").append(build.no_content("search"));
else {
lychee.content.html(code);
lychee.animate(".album:nth-child(-n+50), .photo:nth-child(-n+50)", "contentZoomIn");
$("img[data-type!='svg']").retina();
}
}, 300);
}
});
} else search.reset();
}, 250));
},
reset: function() {
$("#search").val("");
$(".no_content").remove();
if (search.code!=="") {
// Trash data
albums.json = null;
album.json = null;
photo.json = null;
search.code = "";
lychee.animate(".divider", "fadeOut");
albums.load();
}
}
};

@ -1,292 +0,0 @@
/**
* @name Settings Module
* @description Lets you change settings.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
var settings = {
createConfig: function() {
var dbName,
dbUser,
dbPassword,
dbHost,
dbTablePrefix,
buttons,
params;
buttons = [
["Connect", function() {
dbHost = $(".message input.text#dbHost").val();
dbUser = $(".message input.text#dbUser").val();
dbPassword = $(".message input.text#dbPassword").val();
dbName = $(".message input.text#dbName").val();
dbTablePrefix = $(".message input.text#dbTablePrefix").val();
if (dbHost.length<1) dbHost = "localhost";
if (dbName.length<1) dbName = "lychee";
params = "dbCreateConfig&dbName=" + escape(dbName) + "&dbUser=" + escape(dbUser) + "&dbPassword=" + escape(dbPassword) + "&dbHost=" + escape(dbHost) + "&dbTablePrefix=" + escape(dbTablePrefix);
lychee.api(params, function(data) {
if (data!==true) {
// Configuration failed
setTimeout(function() {
// Connection failed
if (data.indexOf("Warning: Connection failed!")!==-1) {
buttons = [
["Retry", function() { setTimeout(settings.createConfig, 400) }],
["", function() {}]
];
modal.show("Connection Failed", "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.", buttons, null, false);
return false;
}
// Creation failed
if (data.indexOf("Warning: Creation failed!")!==-1) {
buttons = [
["Retry", function() { setTimeout(settings.createConfig, 400) }],
["", function() {}]
];
modal.show("Creation Failed", "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.", buttons, null, false);
return false;
}
// Could not create file
if (data.indexOf("Warning: Could not create file!")!==-1) {
buttons = [
["Retry", function() { setTimeout(settings.createConfig, 400) }],
["", function() {}]
];
modal.show("Saving Failed", "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 the readme for more information.", buttons, null, false);
return false;
}
// Something went wrong
buttons = [
["Retry", function() { setTimeout(settings.createConfig, 400) }],
["", function() {}]
];
modal.show("Configuration Failed", "Something unexpected happened. Please try again and check your installation and server. Take a look the readme for more information.", buttons, null, false);
return false;
}, 400);
} else {
// Configuration successful
window.location.reload();
}
});
}],
["", function() {}]
];
modal.show("Configuration", "Enter your database connection details below: <input id='dbHost' class='text less' type='text' placeholder='Database Host (optional)' value=''><input id='dbUser' class='text less' type='text' placeholder='Database Username' value=''><input id='dbPassword' class='text more' type='password' placeholder='Database Password' value=''><br>Lychee will create its own database. If required, you can enter the name of an existing database instead:<input id='dbName' class='text less' type='text' placeholder='Database Name (optional)' value=''><input id='dbTablePrefix' class='text more' type='text' placeholder='Table prefix (optional)' value=''>", buttons, -235, false);
},
createLogin: function() {
var username,
password,
params,
buttons;
buttons = [
["Create Login", function() {
username = $(".message input.text#username").val();
password = $(".message input.text#password").val();
if (username.length<1||password.length<1) {
setTimeout(function() {
buttons = [
["Retry", function() { setTimeout(settings.createLogin, 400) }],
["", function() {}]
];
modal.show("Wrong Input", "The username or password you entered is not long enough. Please try again with another username and password!", buttons, null, false);
return false;
}, 400);
} else {
params = "setLogin&username=" + escape(username) + "&password=" + md5(password);
lychee.api(params, function(data) {
if (data!==true) {
setTimeout(function() {
buttons = [
["Retry", function() { setTimeout(settings.createLogin, 400) }],
["", function() {}]
];
modal.show("Creation Failed", "Unable to save login. Please try again with another username and password!", buttons, null, false);
return false;
}, 400);
}
});
}
}],
["", function() {}]
];
modal.show("Create Login", "Enter a username and password for your installation: <input id='username' class='text less' type='text' placeholder='New Username' value=''><input id='password' class='text' type='password' placeholder='New Password' value=''>", buttons, -122, false);
},
setLogin: function() {
var old_password,
username,
password,
params,
buttons;
buttons = [
["Change Login", function() {
old_password = $(".message input.text#old_password").val();
username = $(".message input.text#username").val();
password = $(".message input.text#password").val();
if (old_password.length<1) {
loadingBar.show("error", "Your old password was entered incorrectly. Please try again!");
return false;
}
if (username.length<1) {
loadingBar.show("error", "Your new username was entered incorrectly. Please try again!");
return false;
}
if (password.length<1) {
loadingBar.show("error", "Your new password was entered incorrectly. Please try again!");
return false;
}
params = "setLogin&oldPassword=" + md5(old_password) + "&username=" + escape(username) + "&password=" + md5(password);
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Change Login", "Enter your current password: <input id='old_password' class='text more' type='password' placeholder='Current Password' value=''><br>Your username and password will be changed to the following: <input id='username' class='text less' type='text' placeholder='New Username' value=''><input id='password' class='text' type='password' placeholder='New Password' value=''>", buttons, -171);
},
setSorting: function() {
var buttons,
sorting,
params;
buttons = [
["Change Sorting", function() {
sorting[0] = $("select#settings_type").val();
sorting[1] = $("select#settings_order").val();
albums.refresh();
params = "setSorting&type=" + sorting[0] + "&order=" + sorting[1];
lychee.api(params, function(data) {
if (data===true) {
lychee.sorting = "ORDER BY " + sorting[0] + " " + sorting[1];
lychee.load();
} else lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Change Sorting",
"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> \
in an \
<select id='settings_order'> \
<option value='ASC'>Ascending</option> \
<option value='DESC'>Descending</option> \
</select> \
order.\
", buttons);
if (lychee.sorting!=="") {
sorting = lychee.sorting.replace("ORDER BY ", "").split(" ");
$("select#settings_type").val(sorting[0]);
$("select#settings_order").val(sorting[1]);
}
},
setDropboxKey: function(callback) {
var buttons,
params,
key;
buttons = [
["Set Key", function() {
key = $(".message input.text#key").val();
params = "setDropboxKey&key=" + key;
lychee.api(params, function(data) {
if (data===true) {
lychee.dropboxKey = key;
if (callback) lychee.loadDropbox(callback);
} else lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Set Dropbox Key", "In order to import photos from your Dropbox, you need a valid drop-ins app key from <a href='https://www.dropbox.com/developers/apps/create'>their website</a>. Generate yourself a personal key and enter it below: <input id='key' class='text' type='text' placeholder='Dropbox API Key' value='" + lychee.dropboxKey + "'>", buttons);
}
};

@ -1,54 +0,0 @@
/**
* @name Swipe Module
* @description Swipes and moves an object.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
swipe = {
obj: null,
tolerance: 150,
offset: 0,
start: function(obj, tolerance) {
if (obj) swipe.obj = obj;
if (tolerance) swipe.tolerance = tolerance;
return true;
},
move: function(e) {
if (swipe.obj===null) return false;
swipe.offset = -1 * e.x;
swipe.obj.css({
WebkitTransform: 'translateX(' + swipe.offset + 'px)',
MozTransform: 'translateX(' + swipe.offset + 'px)',
transform: 'translateX(' + swipe.offset + 'px)'
});
},
stop: function(e, left, right) {
if (e.x<=-swipe.tolerance) left(true);
else if (e.x>=swipe.tolerance) right(true);
else if (swipe.obj!==null) {
swipe.obj.css({
WebkitTransform: 'translateX(0px)',
MozTransform: 'translateX(0px)',
transform: 'translateX(0px)'
});
}
swipe.obj = null;
swipe.offset = 0;
}
};

@ -1,402 +0,0 @@
/**
* @name Album Module
* @description Takes care of every action an album can handle and execute.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
upload = {
show: function(title, files, callback) {
upload.close(true);
$("body").append(build.uploadModal(title, files));
if (callback!==null&&callback!==undefined) callback();
},
notify: function(title, text) {
var popup;
if (!text||text==="") text = "You can now manage your new photo(s).";
if (!window.webkitNotifications) return false;
if (window.webkitNotifications.checkPermission()!==0) window.webkitNotifications.requestPermission();
if (window.webkitNotifications.checkPermission()===0&&title) {
popup = window.webkitNotifications.createNotification("", title, text);
popup.show();
}
},
start: {
local: function(files) {
var albumID = album.getID(),
error = false,
process = function(files, file) {
var formData = new FormData(),
xhr = new XMLHttpRequest(),
pre_progress = 0,
progress,
finish = function() {
window.onbeforeunload = null;
$("#upload_files").val("");
if (error===false) {
// Success
upload.close();
upload.notify("Upload complete");
} else {
// Error
$(".upload_message a.close").show();
upload.notify("Upload complete", "Failed to upload one or more photos.");
}
albums.refresh();
if (album.getID()===false) lychee.goto("0");
else album.load(albumID);
};
// Check if file is supported
if (file.supported===false) {
// Skip file
if (file.next!==null) process(files, file.next);
else {
// Look for supported files
// If zero files are supported, hide the upload after a delay
var hasSupportedFiles = false;
for (var i = 0; i < files.length; i++) {
if (files[i].supported===true) {
hasSupportedFiles = true;
break;
}
}
if (hasSupportedFiles===false) setTimeout(finish, 2000);
}
return false;
}
formData.append("function", "upload");
formData.append("albumID", albumID);
formData.append("tags", "");
formData.append(0, file);
xhr.open("POST", lychee.api_path);
xhr.onload = function() {
var wait = false;
file.ready = true;
// Set status
if (xhr.status===200&&xhr.responseText==="1") {
// Success
$(".upload_message .rows .row:nth-child(" + (file.num+1) + ") .status")
.html("Finished")
.addClass("success");
} else {
// Error
$(".upload_message .rows .row:nth-child(" + (file.num+1) + ") .status")
.html("Error")
.addClass("error");
$(".upload_message .rows .row:nth-child(" + (file.num+1) + ") p.notice")
.html("Server returned an unknown response. Please take a look at the console of your browser for further details.")
.show();
// Set global error
error = true;
// Throw error
lychee.error("Upload failed. Server returned the status code " + xhr.status + "!", xhr, xhr.responseText);
}
// Check if there are file which are not finished
for (var i = 0; i < files.length; i++) {
if (files[i].ready===false) {
wait = true;
break;
}
}
// Finish upload when all files are finished
if (wait===false) finish();
};
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
// Calculate progress
progress = (e.loaded / e.total * 100 | 0);
// Set progress when progress has changed
if (progress>pre_progress) {
$(".upload_message .rows .row:nth-child(" + (file.num+1) + ") .status").html(progress + "%");
pre_progress = progress;
}
if (progress>=100) {
// Scroll to the uploading file
var scrollPos = 0;
if ((file.num+1)>4) scrollPos = (file.num + 1 - 4) * 40
$(".upload_message .rows").scrollTop(scrollPos);
// Set status to processing
$(".upload_message .rows .row:nth-child(" + (file.num+1) + ") .status").html("Processing");
// Upload next file
if (file.next!==null) process(files, file.next);
}
}
};
xhr.send(formData);
};
if (files.length<=0) return false;
if (albumID===false||visible.albums()===true) albumID = 0;
for (var i = 0; i < files.length; i++) {
files[i].num = i;
files[i].ready = false;
files[i].supported = true;
if (i < files.length-1) files[i].next = files[i+1];
else files[i].next = null;
// Check if file is supported
if (files[i].type!=="image/jpeg"&&files[i].type!=="image/jpg"&&files[i].type!=="image/png"&&files[i].type!=="image/gif") {
files[i].ready = true;
files[i].supported = false;
}
}
window.onbeforeunload = function() { return "Lychee is currently uploading!"; };
upload.show("Uploading", files);
// Upload first file
process(files, files[0]);
},
url: function() {
var albumID = album.getID(),
params,
extension,
buttons,
link,
files = [];
if (albumID===false) albumID = 0;
buttons = [
["Import", function() {
link = $(".message input.text").val();
if (link&&link.length>3) {
extension = link.split('.').pop();
if (extension!=="jpeg"&&extension!=="jpg"&&extension!=="png"&&extension!=="gif"&&extension!=="webp") {
loadingBar.show("error", "The file format of this link is not supported.");
return false;
}
files[0] = {
name: link,
supported: true
}
upload.show("Importing URL", files, function() {
$(".upload_message .rows .row .status").html("Importing");
});
params = "importUrl&url=" + escape(encodeURI(link)) + "&albumID=" + albumID;
lychee.api(params, function(data) {
upload.close();
upload.notify("Import complete");
albums.refresh();
if (album.getID()===false) lychee.goto("0");
else album.load(albumID);
if (data!==true) lychee.error(null, params, data);
});
} else loadingBar.show("error", "Link to short or too long. Please try another one!");
}],
["Cancel", function() {}]
];
modal.show("Import from Link", "Please enter the direct link to a photo to import it: <input class='text' type='text' placeholder='http://' value='http://'>", buttons);
},
server: function() {
var albumID = album.getID(),
params,
buttons,
files = [],
path;
if (albumID===false) albumID = 0;
buttons = [
["Import", function() {
path = $(".message input.text").val();
files[0] = {
name: path,
supported: true
};
upload.show("Importing from server", files, function() {
$(".upload_message .rows .row .status").html("Importing");
});
params = "importServer&albumID=" + albumID + "&path=" + escape(encodeURI(path));
lychee.api(params, function(data) {
upload.close();
upload.notify("Import complete");
albums.refresh();
if (data==="Notice: Import only contains albums!") {
if (visible.albums()) lychee.load();
else lychee.goto("");
}
else if (album.getID()===false) lychee.goto("0");
else album.load(albumID);
if (data==="Notice: Import only contains albums!") return true;
else if (data==="Warning: Folder empty!") lychee.error("Folder empty. No photos imported!", params, data);
else if (data!==true) lychee.error(null, params, data);
});
}],
["Cancel", function() {}]
];
modal.show("Import from Server", "This action will import all photos, folders and sub-folders which are located in the following directory. The <b>original files will be deleted</b> after the import when possible. <input class='text' type='text' maxlength='100' placeholder='Absolute path to directory' value='" + lychee.location + "uploads/import/'>", buttons);
},
dropbox: function() {
var albumID = album.getID(),
params,
links = "";
if (albumID===false) albumID = 0;
lychee.loadDropbox(function() {
Dropbox.choose({
linkType: "direct",
multiselect: true,
success: function(files) {
for (var i = 0; i < files.length; i++) {
links += files[i].link + ",";
files[i] = {
name: files[i].link,
supported: true
};
}
// Remove last comma
links = links.substr(0, links.length-1);
upload.show("Importing from Dropbox", files, function() {
$(".upload_message .rows .row .status").html("Importing");
});
params = "importUrl&url=" + escape(links) + "&albumID=" + albumID;
lychee.api(params, function(data) {
upload.close();
upload.notify("Import complete");
albums.refresh();
if (album.getID()===false) lychee.goto("0");
else album.load(albumID);
if (data!==true) lychee.error(null, params, data);
});
}
});
});
}
},
close: function(force) {
if (force===true) {
$(".upload_overlay").remove();
} else {
$(".upload_overlay").removeClass("fadeIn").css("opacity", 0);
setTimeout(function() { $(".upload_overlay").remove() }, 300);
}
}
};

@ -1,497 +0,0 @@
/**
* @name UI View
* @description Responsible to reflect data changes to the UI.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
view = {
header: {
show: function() {
var newMargin = -1*($("#imageview #image").height()/2)+20;
clearTimeout($(window).data("timeout"));
lychee.imageview.removeClass("full");
lychee.header.removeClass("hidden");
lychee.loadingBar.css("opacity", 1);
if ($("#imageview #image.small").length>0) $("#imageview #image").css('margin-top', newMargin);
else $("#imageview #image").removeClass('full');
},
hide: function(e, delay) {
var newMargin = -1*($("#imageview #image").height()/2);
if (delay===undefined) delay = 500;
if (visible.photo()&&!visible.infobox()&&!visible.contextMenu()&&!visible.message()) {
clearTimeout($(window).data("timeout"));
$(window).data("timeout", setTimeout(function() {
lychee.imageview.addClass("full");
lychee.header.addClass("hidden");
lychee.loadingBar.css("opacity", 0);
if ($("#imageview #image.small").length>0) $("#imageview #image").css('margin-top', newMargin);
else $("#imageview #image").addClass('full');
}, delay));
}
},
mode: function(mode) {
var albumID = album.getID();
switch (mode) {
case "albums":
lychee.header.removeClass("view");
$("#tools_album, #tools_photo").hide();
$("#tools_albums").show();
break;
case "album":
lychee.header.removeClass("view");
$("#tools_albums, #tools_photo").hide();
$("#tools_album").show();
album.json.content === false ? $("#button_archive").hide() : $("#button_archive").show();
if (lychee.publicMode&&album.json.downloadable==="0") $("#button_archive").hide();
if (albumID==="s"||albumID==="f"||albumID==="r") {
$("#button_info_album, #button_trash_album, #button_share_album").hide();
} else if (albumID==="0") {
$("#button_info_album, #button_share_album").hide();
$("#button_trash_album").show();
} else {
$("#button_info_album, #button_trash_album, #button_share_album").show();
}
break;
case "photo":
lychee.header.addClass("view");
$("#tools_albums, #tools_album").hide();
$("#tools_photo").show();
break;
}
}
},
infobox: {
show: function() {
if (!visible.infobox()) $("body").append("<div id='infobox_overlay' class='fadeIn'></div>");
lychee.infobox.addClass("active");
},
hide: function() {
lychee.animate("#infobox_overlay", "fadeOut");
setTimeout(function() { $("#infobox_overlay").remove() }, 300);
lychee.infobox.removeClass("active");
}
},
albums: {
init: function() {
view.albums.title();
view.albums.content.init();
},
title: function() {
lychee.setTitle("Albums", false);
},
content: {
scrollPosition: 0,
init: function() {
var smartData = "",
albumsData = "";
/* Smart Albums */
albums.parse(albums.json.unsortedAlbum);
albums.parse(albums.json.publicAlbum);
albums.parse(albums.json.starredAlbum);
albums.parse(albums.json.recentAlbum);
if (!lychee.publicMode) smartData = build.divider("Smart Albums") + build.album(albums.json.unsortedAlbum) + build.album(albums.json.starredAlbum) + build.album(albums.json.publicAlbum) + build.album(albums.json.recentAlbum);
/* Albums */
if (albums.json.content) {
$.each(albums.json.content, function() {
albums.parse(this);
//display albums in reverse order
albumsData = build.album(this) + albumsData;
});
if (!lychee.publicMode) albumsData = build.divider("Albums") + albumsData;
}
if (smartData===""&&albumsData==="") {
lychee.content.html('');
$("body").append(build.no_content("share"));
} else {
lychee.content.html(smartData + albumsData);
}
$("img[data-type!='nonretina']").retina();
/* Restore scroll position */
if (view.albums.content.scrollPosition!==null) {
$("html, body").scrollTop(view.albums.content.scrollPosition);
}
},
title: function(albumID) {
var prefix = "",
longTitle = "",
title = albums.json.content[albumID].title;
if (albums.json.content[albumID].password) prefix = "<span class='icon-lock'></span> ";
if (title!==null&&title.length>18) {
longTitle = title;
title = title.substr(0, 18) + "...";
}
$(".album[data-id='" + albumID + "'] .overlay h1")
.html(prefix + title)
.attr("title", longTitle);
},
delete: function(albumID) {
$(".album[data-id='" + albumID + "']").css("opacity", 0).animate({
width: 0,
marginLeft: 0
}, 300, function() {
$(this).remove();
if (albums.json.num<=0) lychee.animate(".divider:last-of-type", "fadeOut");
});
}
}
},
album: {
init: function() {
album.parse();
view.album.infobox();
view.album.title();
view.album.public();
view.album.content.init();
album.json.init = 1;
},
hide: function() {
view.infobox.hide();
},
title: function() {
if ((visible.album()||!album.json.init)&&!visible.photo()) {
switch (album.getID()) {
case "f":
lychee.setTitle("Starred", false);
break;
case "s":
lychee.setTitle("Public", false);
break;
case "r":
lychee.setTitle("Recent", false);
break;
case "0":
lychee.setTitle("Unsorted", false);
break;
default:
if (album.json.init) $("#infobox .attr_title").html(album.json.title + " " + build.editIcon("edit_title_album"));
lychee.setTitle(album.json.title, true);
break;
}
}
},
content: {
init: function() {
var photosData = "";
$.each(album.json.content, function() {
photosData += build.photo(this);
});
lychee.content.html(photosData);
$("img[data-type!='svg']").retina();
/* Save and reset scroll position */
view.albums.content.scrollPosition = $(document).scrollTop();
$("html, body").scrollTop(0);
},
title: function(photoID) {
var longTitle = "",
title = album.json.content[photoID].title;
if (title!==null&&title.length>18) {
longTitle = title;
title = title.substr(0, 18) + "...";
}
$(".photo[data-id='" + photoID + "'] .overlay h1")
.html(title)
.attr("title", longTitle);
},
star: function(photoID) {
$(".photo[data-id='" + photoID + "'] .icon-star").remove();
if (album.json.content[photoID].star==1) $(".photo[data-id='" + photoID + "']").append("<a class='badge red icon-star'></a>");
},
public: function(photoID) {
$(".photo[data-id='" + photoID + "'] .icon-share").remove();
if (album.json.content[photoID].public==1) $(".photo[data-id='" + photoID + "']").append("<a class='badge red icon-share'></a>");
},
delete: function(photoID) {
$(".photo[data-id='" + photoID + "']").css("opacity", 0).animate({
width: 0,
marginLeft: 0
}, 300, function() {
$(this).remove();
// Only when search is not active
if (!visible.albums()) {
album.json.num--;
view.album.num();
view.album.title();
}
});
}
},
description: function() {
$("#infobox .attr_description").html(album.json.description + " " + build.editIcon("edit_description_album"));
},
num: function() {
$("#infobox .attr_images").html(album.json.num);
},
public: function() {
if (album.json.public==1) {
$("#button_share_album a").addClass("active");
$("#button_share_album").attr("title", "Share Album");
$(".photo .icon-share").remove();
if (album.json.init) $("#infobox .attr_visibility").html("Public");
} else {
$("#button_share_album a").removeClass("active");
$("#button_share_album").attr("title", "Make Public");
if (album.json.init) $("#infobox .attr_visibility").html("Private");
}
},
password: function() {
if (album.json.password==1) $("#infobox .attr_password").html("Yes");
else $("#infobox .attr_password").html("No");
},
infobox: function() {
if ((visible.album()||!album.json.init)&&!visible.photo()) lychee.infobox.html(build.infoboxAlbum(album.json)).show();
}
},
photo: {
init: function() {
photo.parse();
view.photo.infobox();
view.photo.title();
view.photo.star();
view.photo.public();
view.photo.photo();
photo.json.init = 1;
},
show: function() {
// Change header
lychee.content.addClass("view");
view.header.mode("photo");
// Make body not scrollable
$("body").css("overflow", "hidden");
// Fullscreen
$(document)
.bind("mouseenter", view.header.show)
.bind("mouseleave", view.header.hide);
lychee.animate(lychee.imageview, "fadeIn");
},
hide: function() {
view.header.show();
if (visible.infobox) view.infobox.hide();
lychee.content.removeClass("view");
view.header.mode("album");
// Make body scrollable
$("body").css("overflow", "auto");
// Disable Fullscreen
$(document)
.unbind("mouseenter")
.unbind("mouseleave");
// Hide Photo
lychee.animate(lychee.imageview, "fadeOut");
setTimeout(function() {
lychee.imageview.hide();
view.album.infobox();
}, 300);
},
title: function() {
if (photo.json.init) $("#infobox .attr_title").html(photo.json.title + " " + build.editIcon("edit_title"));
lychee.setTitle(photo.json.title, true);
},
description: function() {
if (photo.json.init) $("#infobox .attr_description").html(photo.json.description + " " + build.editIcon("edit_description"));
},
star: function() {
$("#button_star a").removeClass("icon-star-empty icon-star");
if (photo.json.star==1) {
// Starred
$("#button_star a").addClass("icon-star");
$("#button_star").attr("title", "Unstar Photo");
} else {
// Unstarred
$("#button_star a").addClass("icon-star-empty");
$("#button_star").attr("title", "Star Photo");
}
},
public: function() {
if (photo.json.public==1||photo.json.public==2) {
// Photo public
$("#button_share a").addClass("active");
$("#button_share").attr("title", "Share Photo");
if (photo.json.init) $("#infobox .attr_visibility").html("Public");
} else {
// Photo private
$("#button_share a").removeClass("active");
$("#button_share").attr("title", "Make Public");
if (photo.json.init) $("#infobox .attr_visibility").html("Private");
}
},
tags: function() {
$("#infobox #tags").html(build.tags(photo.json.tags));
},
photo: function() {
lychee.imageview.html(build.imageview(photo.json, photo.isSmall(), visible.controls()));
if ((album.json&&album.json.content&&album.json.content[photo.getID()]&&album.json.content[photo.getID()].nextPhoto==="")||lychee.viewMode) $("a#next").hide();
if ((album.json&&album.json.content&&album.json.content[photo.getID()]&&album.json.content[photo.getID()].previousPhoto==="")||lychee.viewMode) $("a#previous").hide();
},
infobox: function() {
lychee.infobox.html(build.infoboxPhoto(photo.json)).show();
}
}
};

@ -1,110 +0,0 @@
/**
* @name Main
* @description Used to view single photos with view.php
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
var header = $("header"),
headerTitle = $("#title"),
imageview = $("#imageview"),
api_path = "php/api.php",
infobox = $("#infobox");
$(document).ready(function(){
/* Event Name */
if (mobileBrowser()) event_name = "touchend";
else event_name = "click";
/* Window */
$(window).keydown(key);
/* Infobox */
$(document).on(event_name, "#infobox .header a", function() { hideInfobox() });
$(document).on(event_name, "#infobox_overlay", function() { hideInfobox() });
$("#button_info").on(event_name, function() { showInfobox() });
/* Direct Link */
$("#button_direct").on(event_name, function() {
link = $("#imageview #image").css("background-image").replace(/"/g,"").replace(/url\(|\)$/ig, "");
window.open(link,"_newtab");
});
loadPhotoInfo(gup("p"));
});
function key(e) {
code = (e.keyCode ? e.keyCode : e.which);
if (code===27&&visibleInfobox()) { hideInfobox(); e.preventDefault(); }
}
function visibleInfobox() {
if (parseInt(infobox.css("right").replace("px", ""))<0) return false;
else return true;
}
function isPhotoSmall(photo) {
size = [
["width", false],
["height", false]
];
if (photo.width<$(window).width()-60) size["width"] = true;
if (photo.height<$(window).height()-100) size["height"] = true;
if (size["width"]&&size["height"]) return true;
else return false;
}
function showInfobox() {
$("body").append("<div id='infobox_overlay' class='fadeIn'></div>");
infobox.addClass("active");
}
function hideInfobox() {
$("#infobox_overlay").removeClass("fadeIn").addClass("fadeOut");
setTimeout(function() { $("#infobox_overlay").remove() }, 300);
infobox.removeClass("active");
}
function loadPhotoInfo(photoID) {
params = "function=getPhoto&photoID=" + photoID + "&albumID=0&password=''";
$.ajax({type: "POST", url: api_path, data: params, dataType: "json", success: function(data) {
if (!data.title) data.title = "Untitled";
document.title = "Lychee - " + data.title;
headerTitle.html(data.title);
imageview.attr("data-id", photoID);
if (isPhotoSmall(data)) imageview.html("<div id='image' class='small' style='background-image: url(" + data.url + "); width: " + data.width + "px; height: " + data.height + "px; margin-top: -" + parseInt((data.height/2)-20) + "px; margin-left: -" + data.width/2 + "px;'></div>");
else imageview.html("<div id='image' style='background-image: url(" + data.url + ");'></div>");
imageview.removeClass("fadeOut").addClass("fadeIn").show();
infobox.html(build.infoboxPhoto(data, true)).show();
}, error: ajaxError });
}
function ajaxError(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
}

@ -1,67 +0,0 @@
/**
* @name Visible Module
* @description This module is used to check if elements are visible or not.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
visible = {
albums: function() {
if ($('#tools_albums').css('display')==='block') return true;
else return false;
},
album: function() {
if ($('#tools_album').css('display')==='block') return true;
else return false;
},
photo: function() {
if ($('#imageview.fadeIn').length>0) return true;
else return false;
},
search: function() {
if (search.code!==null&&search.code!=='') return true;
else return false;
},
infobox: function() {
if ($('#infobox.active').length>0) return true;
else return false;
},
infoboxbutton: function() {
if (visible.albums()) return false;
if (visible.photo()) return true;
if (visible.album()&&$('#button_info_album:visible').length>0) return true;
else return false;
},
controls: function() {
if (lychee.loadingBar.css('opacity')<1) return false;
else return true;
},
message: function() {
if ($('.message').length>0) return true;
else return false;
},
signin: function() {
if ($('.message .sign_in').length>0) return true;
else return false;
},
contextMenu: function() {
if ($('.contextmenu').length>0) return true;
else return false;
},
multiselect: function() {
if ($('#multiselect').length>0) return true;
else return false;
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,245 +0,0 @@
/**
* @name content.css
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
/* Gradient ------------------------------------------------*/
#content::before {
content: "";
position: absolute;
left: 0px;
width: 100%;
height: 20px;
background: linear-gradient(to bottom, #262626, #222);
border-top: 1px solid #333;
}
/* Modes ------------------------------------------------*/
#content.view::before {
display: none;
}
#content {
position: absolute;
padding: 50px 0px 33px 0px;
width: 100%;
min-height: calc(100% - 90px);
-webkit-overflow-scrolling: touch;
}
/* Photo ------------------------------------------------*/
.photo {
float: left;
display: inline-block;
width: 206px;
height: 206px;
margin: 30px 0px 0px 30px;
cursor: pointer;
}
.photo img {
position: absolute;
width: 200px;
height: 200px;
background-color: #222;
border-radius: 2px;
border: 2px solid #ccc;
}
.photo:hover img,
.photo.active img {
box-shadow: 0px 0px 5px #005ecc;
}
.photo:active {
transition-duration: .1s;
transform: scale(.98);
}
/* Album ------------------------------------------------*/
.album {
float: left;
display: inline-block;
width: 204px;
height: 204px;
margin: 30px 0px 0px 30px;
cursor: pointer;
}
.album img:first-child,
.album img:nth-child(2) {
transform: rotate(0deg) translateY(0px) translateX(0px);
opacity: 0;
}
.album:hover img:first-child {
transform: rotate(-2deg) translateY(10px) translateX(-12px);
opacity: 1;
}
.album:hover img:nth-child(2) {
transform: rotate(5deg) translateY(-8px) translateX(12px);
opacity: 1;
}
.album img {
position: absolute;
width: 200px;
height: 200px;
background-color: #222;
border-radius: 2px;
border: 2px solid #ccc;
}
.album:hover img,
.album.active img {
box-shadow: 0px 0px 5px #005ecc;
}
/* Album/Photo Overlay ------------------------------------------------*/
.album .overlay,
.photo .overlay {
position: absolute;
width: 200px;
height: 200px;
margin: 2px;
}
.album .overlay {
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 40%,rgba(0,0,0,0.85) 100%);
}
.photo .overlay {
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.5) 80%, rgba(0,0,0,0.9) 100%);
opacity: 0;
}
.photo:hover .overlay,
.photo.active .overlay {
opacity: 1;
}
.album .overlay h1,
.photo .overlay h1 {
min-height: 19px;
width: 185px;
margin: 153px 0px 3px 15px;
color: #fff;
font-size: 16px;
font-weight: bold;
overflow: hidden;
}
.album .overlay a,
.photo .overlay a {
font-size: 11px;
color: #aaa;
}
.album .overlay a {
margin-left: 15px;
}
.photo .overlay a {
margin: 155px 0px 5px 15px;
}
.photo .overlay a span {
margin: 0px 5px 0px 0px;
}
/* Badges ------------------------------------------------*/
.album .badge,
.photo .badge {
position: absolute;
margin-top: -1px;
margin-left: 12px;
padding: 12px 7px 3px 7px;
box-shadow: 0px 0px 3px rgba(0, 0, 0, .8);
border-radius: 0px 0px 3px 3px;
border: 1px solid #fff;
border-top: none;
color: #fff;
font-size: 24px;
text-shadow: 0px 1px 0px rgba(0, 0, 0, .4);
opacity: .9;
}
.album .badge.icon-star,
.photo .badge.icon-star {
padding: 12px 8px 3px 8px;
}
.album .badge.icon-share,
.photo .badge.icon-share {
padding: 12px 6px 3px 8px;
}
.album .badge.icon-time,
.photo .badge.icon-time {
padding: 12px 8px 3px 9px;
}
.album .badge::after,
.photo .badge::after {
content: "";
position: absolute;
margin-top: -12px;
margin-left: -26px;
width: 38px;
height: 5px;
background: linear-gradient(to bottom, rgba(0,0,0,1) 0%,rgba(0,0,0,0) 100%);
opacity: .4;
}
.album .badge.icon-star::after,
.photo .badge.icon-star::after {
margin-left: -29px;
}
.album .badge.icon-share::after,
.photo .badge.icon-share::after {
margin-left: -31px;
}
.album .badge.icon-time::after,
.photo .badge.icon-time::after {
margin-left: -29px;
}
.album .badge.icon-reorder::after {
margin-left: -30px;
}
.album .badge:nth-child(2n),
.photo .badge:nth-child(2n) {
margin-left: 57px;
}
.album .badge.red,
.photo .badge.red {
background: linear-gradient(to bottom, #d64b4b 0%, #ab2c2c 100%);
}
.album .badge.blue,
.photo .badge.blue {
background: linear-gradient(to bottom, #347cd6 0%, #2945ab 100%);
}
/* Divider ------------------------------------------------*/
.divider {
float: left;
width: 100%;
margin-top: 50px;
opacity: 0;
border-top: 1px solid #2E2E2E;
box-shadow: 0px -1px 0px #151515;
}
.divider:first-child {
margin-top: 0px;
border-top: none;
}
.divider h1 {
float: left;
margin: 20px 0px 0px 30px;
color: #fff;
font-size: 14px;
font-weight: bold;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .8);
}
/* No Content ------------------------------------------------*/
.no_content {
position: absolute;
top: 50%;
left: 50%;
height: 160px;
width: 180px;
margin-top: -60px;
margin-left: -90px;
padding-top: 20px;
color: rgba(20, 20, 20, 1);
text-shadow: 0px 1px 0px rgba(255, 255, 255, .05);
text-align: center;
}
.no_content .icon {
font-size: 80px;
}
.no_content p {
font-size: 18px;
font-weight: bold;
}

@ -1,93 +0,0 @@
/**
* @name contextmenu.css
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
.contextmenu_bg {
position: fixed;
height: 100%;
width: 100%;
z-index: 1000;
}
.contextmenu {
position: fixed;
top: 0px;
left: 0px;
padding: 5px 0px 6px 0px;
background: linear-gradient(to bottom, #444 0%, #2f2f2f 100%);
border: 1px solid rgba(0,0,0,0.5);
border-bottom: 1px solid rgba(0,0,0,.7);
border-radius: 5px;
box-shadow: 0px 3px 4px rgba(0,0,0,0.25), inset 0px 1px 0px rgba(255,255,255, .1);
opacity: 0;
z-index: 1001;
transition: none;
}
/* Items ------------------------------------------------*/
.contextmenu tr {
font-size: 14px;
color: #eee;
text-shadow: 0px -1px 0px rgba(0,0,0,.2);
cursor: pointer;
}
.contextmenu tr:hover {
background: linear-gradient(to bottom, #6a84f2, #4967F0);
}
.contextmenu tr.no_hover:hover {
cursor: inherit;
background-color: inherit;
background-image: none;
}
.contextmenu tr.separator {
float: left;
height: 1px;
width: 100%;
background-color: #1f1f1f;
border-bottom: 1px solid #4c4c4c;
margin: 5px 0px;
cursor: inherit;
}
.contextmenu tr.separator:hover {
background-color: #222;
background-image: none;
}
.contextmenu tr td {
padding: 7px 30px 6px 12px;
white-space: nowrap;
transition: none;
}
.contextmenu tr:hover td {
color: #fff;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.05);
text-shadow: 0px -1px 0px rgba(0,0,0,.2);
}
.contextmenu tr.no_hover:hover td {
box-shadow: none;
}
.contextmenu tr a {
float: left;
width: 10px;
margin-right: 10px;
text-align: center;
}
/* Direct Link Input ------------------------------------------------*/
.contextmenu #link {
float: right;
width: 140px;
margin: -1px -18px -2px -1px;
padding: 5px 7px 6px 7px;
background-color: #444;
color: #fff;
border: none;
border: 1px solid rgba(0, 0, 0, .5);
box-shadow: 0px 1px 0px rgba(255,255,255,.08);
outline: none;
border-radius: 5px;
}
.contextmenu tr a#link_icon {
padding-top: 4px;
}

@ -1,86 +0,0 @@
/**
* @name imageview.css
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
#imageview {
position: fixed;
display: none;
width: 100%;
min-height: 100%;
background-color: rgba(10,10,10,.98);
transition: background-color .3s;
}
/* Modes ------------------------------------------------*/
#imageview.view {
background-color: inherit;
}
#imageview.full {
background-color: rgba(0,0,0,1);
}
/* ImageView ------------------------------------------------*/
#imageview #image {
position: absolute;
top: 60px;
right: 30px;
bottom: 30px;
left: 30px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
transition: top .3s, right .3s, bottom .3s, left .3s, margin-top .3s, opacity .2s, transform .3s cubic-bezier(0.51,.92,.24,1.15);
animation-name: zoomIn;
animation-duration: .3s;
animation-timing-function: cubic-bezier(0.51,.92,.24,1.15);
}
#imageview #image.small {
top: 50%;
right: auto;
bottom: auto;
left: 50%;
}
#imageview #image.full {
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
/* Previous/Next Buttons ------------------------------------------------*/
#imageview .arrow_wrapper {
position: fixed;
width: 20%;
height: calc(100% - 60px);
top: 60px;
z-index: 1;
}
#imageview .arrow_wrapper.previous {
left: 0;
}
#imageview .arrow_wrapper.next {
right: 0;
}
#imageview .arrow_wrapper a {
position: fixed;
top: 50%;
margin-top: -10px;
color: #fff;
font-size: 50px;
text-shadow: 0px 1px 2px #000;
cursor: pointer;
opacity: 0;
z-index: 2;
transition: opacity .2s;
}
#imageview .arrow_wrapper:hover a {
opacity: .2;
}
#imageview .arrow_wrapper a#previous {
left: 20px;
}
#imageview .arrow_wrapper a#next {
right: 20px;
}

@ -1,54 +0,0 @@
/**
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
@import "reset";
html,
body {
min-height: 100%;
}
body {
background-color: #222;
font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12px;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-smoothing: antialiased;
}
body.view {
background-color: #0f0f0f;
}
.center {
position: absolute;
left: 50%;
top:50%;
}
* {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
transition: color .3s, opacity .3s ease-out, transform .3s ease-out, box-shadow .3s;
}
input {
-webkit-user-select: text !important;
-moz-user-select: text !important;
user-select: text !important;
}
@import "animations";
@import "content";
@import "contextmenu";
@import "font";
@import "header";
@import "imageview";
@import "infobox";
@import "loading";
@import "message";
@import "misc";
@import "multiselect";
@import "tooltip";
@import "upload";
@import "mediaquery";

@ -1,210 +0,0 @@
/**
* @name message.css
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
.message_overlay {
position: fixed;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
background-color: rgba(0,0,0,.85);
z-index: 1000;
}
.message {
position: absolute;
display: inline-block;
width: 500px;
margin-left: -250px;
margin-top: -95px;
background-color: #444;
background-image: linear-gradient(to bottom, rgb(75, 75, 75), rgb(45, 45, 45));
border-radius: 5px;
box-shadow: 0px 0px 5px #000, inset 0px 1px 0px rgba(255,255,255,.08);
/* Animation */
animation-name: moveUp;
animation-duration: .3s;
animation-timing-function: cubic-bezier(0.51,.92,.24,1.15);
}
/* Header ------------------------------------------------*/
.message h1 {
float: left;
width: 100%;
padding: 12px 0px;
color: #fff;
font-size: 16px;
font-weight: bold;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
text-align: center;
}
.message .close {
position: absolute;
top: 0px;
right: 0px;
padding: 12px 14px 6px 7px;
color: #aaa;
font-size: 20px;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
cursor: pointer;
}
.message .close:hover {
color: #fff;
}
/* Text ------------------------------------------------*/
.message p {
float: left;
width: 90%;
margin-top: 1px;
padding: 12px 5% 15px 5%;
color: #eee;
font-size: 14px;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
line-height: 20px;
}
.message p b {
font-weight: bold;
color: #fff;
}
.message p a {
color: #eee;
text-decoration: none;
border-bottom: 1px dashed #888;
}
/* Button ------------------------------------------------*/
.message .button {
float: right;
margin: 15px 15px 15px 0px;
padding: 7px 10px 8px 10px;
color: #ccc;
font-size: 14px;
font-weight: bold;
text-align: center;
text-shadow: 0px -1px 0px #222;
border-radius: 5px;
border: 1px solid rgba(0,0,0,.4);
box-shadow: inset 0px 1px 0px rgba(255,255,255,.08), 0px 1px 0px rgba(255,255,255,.05);
cursor: pointer;
}
.message .button:first-of-type {
margin: 15px 5% 18px 0px !important;
}
.message .button.active {
color: #fff;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.08), 0px 1px 0px rgba(255,255,255,.1), 0px 0px 4px #005ecc;
}
.message .button:hover {
background-image: linear-gradient(to bottom, rgb(60, 60, 60), rgb(57, 57, 57));
}
.message .button:active,
.message .button.pressed {
background-color: #393939;
background-image: linear-gradient(to bottom, rgb(57, 57, 57), rgb(60, 60, 60));
}
/* Sign in ------------------------------------------------*/
.sign_in {
float: left;
width: 100%;
margin-top: 1px;
padding: 5px 0px;
color: #eee;
font-size: 14px;
text-shadow: 0px -1px 0px #222;
line-height: 20px;
}
.sign_in input {
float: left;
width: 88%;
padding: 7px 1% 9px 1%;
margin: 0px 5%;
background-color: transparent;
color: #fff;
text-shadow: 0px -1px 0px #222;
border: none;
border-bottom: 1px solid #222;
box-shadow: 0px 1px 0px rgba(255,255,255,.1);
border-radius: 0px;
outline: none;
}
.sign_in input:first-of-type {
margin-bottom: 10px;
}
.sign_in input.error:focus {
box-shadow: 0px 1px 0px rgba(204, 0, 7, 0.6);
}
.message #version {
display: inline-block;
margin-top: 23px;
margin-left: 5%;
color: #888;
text-shadow: 0px -1px 0px #111;
}
.message #version span {
display: none;
}
.message #version span a {
color: #888;
}
/* Input Misc ------------------------------------------------*/
.message input.text {
float: left;
width: calc(100% - 10px);
padding: 17px 5px 9px 5px;
margin-top: 10px;
background-color: transparent;
color: #fff;
text-shadow: 0px -1px 0px #222;
border: none;
box-shadow: 0px 1px 0px rgba(255,255,255,.1);
border-bottom: 1px solid #222;
border-radius: 0px;
outline: none;
}
.message input.less {
margin-bottom: -10px;
}
.message input.more {
margin-bottom: 30px;
}
.message .copylink {
margin-bottom: 20px;
}
/* Radio Buttons ------------------------------------------------*/
.message .choice {
float: left;
margin: 12px 5%;
width: 90%;
color: #fff;
}
.message .choice input {
float: left;
}
.message .choice h2 {
float: left;
margin: 1px 0px 0px 8px;
color: #fff;
font-size: 14px;
font-weight: 700;
text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
}
.message .choice p {
margin-top: 2px;
padding: 0px 5% 0px 25px;
color: #aaa;
font-size: 13px;
}
.message .choice p input {
width: 100%;
padding: 10px 1px 9px;
margin-top: 10px;
}

@ -1,33 +0,0 @@
/**
* @name tooltip.css
*/
.tipsy {
padding: 4px;
font-size: 12px;
position: absolute;
z-index: 100000;
animation-name: fadeIn;
animation-duration: .3s;
}
.tipsy-inner {
padding: 8px 10px 7px;
color: #fff;
max-width: 200px;
text-align: center;
background: rgba(0, 0, 0, .8);
border-radius: 25px;
}
.tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed rgba(0, 0, 0, .8); }
.tipsy-arrow-n { border-bottom-color: rgba(0, 0, 0, .8); }
.tipsy-arrow-s { border-top-color: rgba(0, 0, 0, .8); }
.tipsy-arrow-e { border-left-color: rgba(0, 0, 0, .8); }
.tipsy-arrow-w { border-right-color: rgba(0, 0, 0, .8); }
.tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
.tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }

@ -1,70 +0,0 @@
var gulp = require('gulp'),
plugins = require("gulp-load-plugins")();
var paths = {
view: [
'bower_components/jQuery/dist/jquery.min.js',
'bower_components/js-md5/js/md5.min.js',
'bower_components/mousetrap/mousetrap.min.js',
'bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js',
'../assets/js/_frameworks.js',
'../assets/js/build.js',
'../assets/js/view/main.js'
],
js: [
'bower_components/jQuery/dist/jquery.min.js',
'bower_components/js-md5/js/md5.min.js',
'bower_components/mousetrap/mousetrap.min.js',
'bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js',
'../assets/js/*.js'
],
css: [
'../assets/scss/main.scss'
]
}
var catchError = function(err) {
console.log(err.toString());
this.emit('end');
}
gulp.task('view', function() {
gulp.src(paths.view)
.pipe(plugins.concat('view.js', {newLine: "\n"}))
.pipe(plugins.uglify())
.pipe(gulp.dest('../assets/min/'));
});
gulp.task('js', function() {
gulp.src(paths.js)
.pipe(plugins.concat('main.js', {newLine: "\n"}))
.pipe(plugins.uglify())
.on('error', catchError)
.pipe(gulp.dest('../assets/min/'));
});
gulp.task('css', function() {
gulp.src(paths.css)
.pipe(plugins.sass())
.on('error', catchError)
.pipe(plugins.concat('main.css', {newLine: "\n"}))
.pipe(plugins.autoprefixer('last 4 versions', '> 5%'))
.pipe(plugins.minifyCss())
.pipe(gulp.dest('../assets/min/'));
});
gulp.task('default', ['view', 'js', 'css']);
gulp.task('watch', ['default'], function() {
gulp.watch(paths.view, ['view']);
gulp.watch(paths.js, ['js']);
gulp.watch(paths.css, ['css']);
});

1
dist/main.css vendored

File diff suppressed because one or more lines are too long

6
dist/main.js vendored

File diff suppressed because one or more lines are too long

4
dist/view.js vendored

File diff suppressed because one or more lines are too long

@ -15,12 +15,12 @@ After [installing Node.js](http://nodejs.org) you can use the included `npm` pac
### Build
The Gruntfile is located in `build/` and can be easily executed using the `gulp` command.
The Gruntfile is located in `src/` and can be easily executed using the `gulp` command.
### Watch for changes
While developing, you might want to use the following command to watch for changes in `assets/js/` and `assets/css/`:
While developing, you might want to use the following command to watch for changes:
gulp watch
`gulp` will automatically build Lychee everytime you save a file.

@ -1,3 +1,22 @@
## v2.7
Released December 6, 2014
- `New` Intermediate sized images for small screen devices #67
- `New` Added Docker help (@renfredxh, #252)
- `New` Move-Photo context shows album previews
- `Improved` Upload shows server-errors
- `Improved` Improved thumb creation
- `Improved` Docker (@renfredxh, #252)
- `Improved` CSS has been rewritten partly
- `Improved` Front-end has been rewritten partly #245
- `Improved` Folder- and code-structure has been updated
- `Improved` Context-menu now based on [basicContext](https://github.com/electerious/basicContext) #245
- `Fixed` OpenGraph image too big for some sites #69
- `Fixed` Wrong sizes after EXIF rotation
- `Fixed` Returning to 'Albums' after searching failed
- `Fixed` Move-Photo not scrollable #215
## v2.6.3
Released October 10, 2014

@ -0,0 +1,41 @@
### Installation using Docker
*Note: pre-installation of the latest version of [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/installation/) is required to deploy Lychee using Docker.*
First, clone the latest version of Lychee and build it using the Dockerfile included in the repository.
```bash
git clone https://github.com/electerious/Lychee.git
cd Lychee
docker build -t lychee .
```
Once this is finished, remember to set the proper permissions on the `uploads` and `data` directories, so the container can mount these directories as volumes.
```
chmod -R 777 uploads/ data/
```
Now you can use the `docker run` command to run your Lychee container.
```bash
docker run -v /var/lib/mysql --name lychee_data \
-v $(pwd)/data:/app/data \
-v $(pwd)/uploads:/app/uploads \
-i -t -d -p 8000:80 lychee
```
Browse to [localhost:8000](http://localhost:8000/) (the port can be specified via the `-p` flag) and you will see Lychee's configuration page. The default database username is `root` with no password (you can manage MySQL users by running `docker exec -i -t <container_id> mysql`). After submitting your database configuration, you can sign in and create a new username and password and start using Lychee.
*Note: if you are deploying on a server, you might want to forward your container to port `80` instead of `8000` so it'll be publicly accessible.*
### Managing Data
Running the container with the options above mounts three Docker [data volumes](https://docs.docker.com/userguide/dockervolumes/). The first is a named "data" volume used to store the MySQL database. The last two will mount the `/data` and `/uploads` from the container to your host `Lychee` directory. If you would like to upgrade or redeploy Lychee while preserving your data, you can kill the container and the volumes will persist. Just rebuild your new container and run it using a similar command:
```bash
sudo docker run --volumes-from lychee_data \
-v $(pwd)/data:/app/data \
-v $(pwd)/uploads:/app/uploads \
-i -t -d -p 8000:80 lychee
```

@ -18,6 +18,9 @@ If possible, change these settings directly in your `php.ini`. We recommend to i
#### Which browsers are supported?
Lychee supports the latest versions of Google Chrome, Apple Safari, Mozilla Firefox, Opera and Microsoft Internet Explorer. Make sure you are always running the newest version.
#### Which image file formats are supported?
You can upload `*.png`, `*.jpeg` and `*.gif` photos.
#### What is new?
Take a look at the [Changelog](Changelog.md) to see what's new.

@ -50,4 +50,10 @@ This key is required to use the Dropbox import feature from your server. Lychee
imagick = [0|1]
If `1`, Lychee will use Imagick when available. Disable [Imagick](http://www.imagemagick.org) if you have problems or if you are using an outdated version. Lychee will use [GD](http://php.net/manual/en/book.image.php) when Imagick is disabled or not available.
If `1`, Lychee will use Imagick when available. Disable [Imagick](http://www.imagemagick.org) if you have problems or if you are using an outdated version. Lychee will use [GD](http://php.net/manual/en/book.image.php) when Imagick is disabled or not available.
#### Medium
medium = [0|1]
If `1`, Lychee will create a second, smaller version of your photo. This feature requires [Imagick](http://www.imagemagick.org) on your server and an activated `imagick` option the the settings table.

@ -10,11 +10,11 @@
<meta name="description" content="">
<!-- CSS -->
<link type="text/css" rel="stylesheet" href="assets/min/main.css">
<link type="text/css" rel="stylesheet" href="dist/main.css">
<link rel="shortcut icon" href="favicon.ico">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-iphone.png" sizes="120x120">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-ipad.png" sizes="152x152">
<link rel="apple-touch-icon" href="src/images/apple-touch-icon-iphone.png" sizes="120x120">
<link rel="apple-touch-icon" href="src/images/apple-touch-icon-ipad.png" sizes="152x152">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
@ -31,17 +31,17 @@
<!-- Buttons -->
<div id="tools_albums">
<a class="button left icon-cog" id="button_settings"></a>
<a class="button left icon-signout" id="button_signin"></a>
<a class="button left icon-cog" id="button_settings" title="Settings"></a>
<a class="button left icon-signout" id="button_signin" title="Sign In"></a>
<a id="hostedwith">Hosted with Lychee</a>
<a class="button right icon icon-plus button_add"></a>
<a class="button right icon icon-plus button_add" title="Add"></a>
<a class="button_divider"></a>
<input id="search" type="text" name="search" placeholder="Search …">
<a id="clearSearch" class="button right">&times;</a>
</div>
<div id="tools_album">
<a class="button left icon-arrow-left" id="button_back_home"></a>
<a class="button right icon icon-plus button_add"></a>
<a class="button left icon-arrow-left" id="button_back_home" title="Close Album"></a>
<a class="button right icon icon-plus button_add" title="Add"></a>
<a class="button_divider"></a>
<div class="tools" id="button_trash_album" title="Delete Album"><a class="icon-trash"></a></div>
<div class="tools" id="button_info_album" title="Show Info"><a class="icon-info-sign"></a></div>
@ -49,7 +49,7 @@
<div class="tools" id="button_share_album" title="Share Album"><a class="icon-share"></a></div>
</div>
<div id="tools_photo">
<a class="button left icon-arrow-left" id="button_back"></a>
<a class="button left icon-arrow-left" id="button_back" title="Close Photo"></a>
<div class="tools" id="button_more" title="More"><a class="icon-caret-down"></a></div>
<a class="button_divider"></a>
<div class="tools" id="button_trash" title="Delete"><a class="icon-trash"></a></div>
@ -79,7 +79,7 @@
</div>
<!-- JS -->
<script async type="text/javascript" src="assets/min/main.js"></script>
<script async type="text/javascript" src="dist/main.js"></script>
</body>
</html>

@ -2,7 +2,6 @@
###
# @name Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Admin Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Guest Access (Public Mode)
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Installation Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name API
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -1,8 +1,7 @@
<?php
###
# @name Autoload
# @author Tobias Reich
# @name Autoload
# @copyright 2014 by Tobias Reich
###

@ -24,5 +24,6 @@ CREATE TABLE IF NOT EXISTS `?` (
`thumbUrl` varchar(50) NOT NULL,
`album` varchar(30) NOT NULL DEFAULT '0',
`checksum` VARCHAR(100) DEFAULT NULL,
`medium` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

@ -2,7 +2,6 @@
###
# @name Update to version 2.1
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.1.1
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.2
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.5
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.5.5
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.6.1
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Update to version 2.6.2
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
@ -35,7 +34,7 @@ if ($result->num_rows===0) {
$query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('imagick', '1')", array(LYCHEE_TABLE_SETTINGS));
$result = $database->query($query);
if (!$result) {
Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')');
Log::error($database, 'update_020602', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}

@ -0,0 +1,41 @@
<?php
###
# @name Update to version 2.6.2
# @copyright 2014 by Tobias Reich
###
# Add medium to photos
$query = Database::prepare($database, "SELECT `medium` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
if (!$database->query($query)) {
$query = Database::prepare($database, "ALTER TABLE `?` ADD `medium` TINYINT(1) NOT NULL DEFAULT 0", array(LYCHEE_TABLE_PHOTOS));
$result = $database->query($query);
if (!$result) {
Log::error($database, 'update_020700', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
# Create medium folder
if (is_dir(LYCHEE_UPLOADS_MEDIUM)===false) {
# Only create the folder when it is missing
if (@mkdir(LYCHEE_UPLOADS_MEDIUM)===false)
Log::error($database, 'update_020700', __LINE__, 'Could not create medium-folder');
}
# Add medium to settings
$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'medium' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
$result = $database->query($query);
if ($result->num_rows===0) {
$query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('medium', '1')", array(LYCHEE_TABLE_SETTINGS));
$result = $database->query($query);
if (!$result) {
Log::error($database, 'update_020700', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
# Set version
if (Database::setVersion($database, '020700')===false) return false;
?>

@ -2,7 +2,6 @@
###
# @name Define
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
@ -11,7 +10,7 @@ define('LYCHEE', substr(__DIR__, 0, -3));
# Define dirs
define('LYCHEE_DATA', LYCHEE . 'data/');
define('LYCHEE_BUILD', LYCHEE . 'build/');
define('LYCHEE_SRC', LYCHEE . 'src/');
define('LYCHEE_UPLOADS', LYCHEE . 'uploads/');
define('LYCHEE_UPLOADS_BIG', LYCHEE_UPLOADS . 'big/');
define('LYCHEE_UPLOADS_MEDIUM', LYCHEE_UPLOADS . 'medium/');
@ -23,8 +22,9 @@ define('LYCHEE_PLUGINS', LYCHEE . 'plugins/');
define('LYCHEE_CONFIG_FILE', LYCHEE_DATA . 'config.php');
# Define urls
define('LYCHEE_URL_UPLOADS_THUMB', 'uploads/thumb/');
define('LYCHEE_URL_UPLOADS_BIG', 'uploads/big/');
define('LYCHEE_URL_UPLOADS_MEDIUM', 'uploads/medium/');
define('LYCHEE_URL_UPLOADS_THUMB', 'uploads/thumb/');
function defineTablePrefix($dbTablePrefix) {

@ -2,7 +2,6 @@
###
# @name Album Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -1,8 +1,7 @@
<?php
###
# @name Database Module
# @author Tobias Reich
# @name Database Module
# @copyright 2014 by Tobias Reich
###
@ -22,7 +21,7 @@ class Database extends Module {
# Avoid sql injection on older MySQL versions by using GBK
if ($database->server_version<50500) $database->set_charset('GBK');
else $database->set_charset("utf8");
else $database->set_charset('utf8');
# Set unicode
$database->query('SET NAMES utf8;');
@ -54,7 +53,8 @@ class Database extends Module {
'020500', #2.5
'020505', #2.5.5
'020601', #2.6.1
'020602' #2.6.2
'020602', #2.6.2
'020700' #2.7.0
);
# For each update

@ -2,7 +2,6 @@
###
# @name Upload Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Log Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -2,7 +2,6 @@
###
# @name Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###

@ -1,8 +1,7 @@
<?php
###
# @name Photo Module
# @author Tobias Reich
# @name Photo Module
# @copyright 2014 by Tobias Reich
###
@ -44,9 +43,12 @@ class Photo extends Module {
self::dependencies(isset($this->database));
# Check permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false||hasPermissions(LYCHEE_UPLOADS_THUMB)===false) {
Log::error($this->database, __METHOD__, __LINE__, 'Wrong permissions in uploads/');
exit('Error: Wrong permissions in uploads-folder!');
if (hasPermissions(LYCHEE_UPLOADS)===false||
hasPermissions(LYCHEE_UPLOADS_BIG)===false||
hasPermissions(LYCHEE_UPLOADS_THUMB)===false||
hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) {
Log::error($this->database, __METHOD__, __LINE__, 'An upload-folder is missing or not readable and writable');
exit('Error: An upload-folder is missing or not readable and writable!');
}
# Call plugins
@ -122,6 +124,7 @@ class Photo extends Module {
$photo_name = $exists['photo_name'];
$path = $exists['path'];
$path_thumb = $exists['path_thumb'];
$medium = ($exists['medium']==='1' ? true : false);
$exists = true;
}
@ -156,27 +159,33 @@ class Photo extends Module {
if ($exists===false) {
# Set orientation based on EXIF data
if ($file['type']==='image/jpeg'&&isset($info['orientation'], $info['width'], $info['height'])&&$info['orientation']!=='') {
if (!$this->adjustFile($path, $info)) Log::notice($this->database, __METHOD__, __LINE__, 'Could not adjust photo (' . $info['title'] . ')');
if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&$info['orientation']!=='') {
$adjustFile = $this->adjustFile($path, $info);
if ($adjustFile!==false) $info = $adjustFile;
else Log::notice($this->database, __METHOD__, __LINE__, 'Skipped adjustment of photo (' . $info['title'] . ')');
}
# Set original date
if ($info['takestamp']!==''&&$info['takestamp']!==0) @touch($path, $info['takestamp']);
# Create Thumb
if (!$this->createThumb($path, $photo_name)) {
if (!$this->createThumb($path, $photo_name, $info['type'], $info['width'], $info['height'])) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not create thumbnail for photo');
exit('Error: Could not create thumbnail for photo!');
}
# Create Medium
if ($this->createMedium($path, $photo_name, $info['width'], $info['height'])) $medium = true;
else $medium = false;
# Set thumb url
$path_thumb = md5($id) . '.jpeg';
}
# Save to DB
$values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $description, $tags, $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum);
$query = Database::prepare($this->database, "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum) VALUES ('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?')", $values);
$values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $description, $tags, $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum, $medium);
$query = Database::prepare($this->database, "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum, medium) VALUES ('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?')", $values);
$result = $this->database->query($query);
if (!$result) {
@ -199,8 +208,8 @@ class Photo extends Module {
self::dependencies(isset($this->database, $checksum));
# Exclude $photoID from select when $photoID is set
if (isset($photoID)) $query = Database::prepare($this->database, "SELECT id, url, thumbUrl FROM ? WHERE checksum = '?' AND id <> '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum, $photoID));
else $query = Database::prepare($this->database, "SELECT id, url, thumbUrl FROM ? WHERE checksum = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum));
if (isset($photoID)) $query = Database::prepare($this->database, "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' AND id <> '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum, $photoID));
else $query = Database::prepare($this->database, "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum));
$result = $this->database->query($query);
@ -216,7 +225,8 @@ class Photo extends Module {
$return = array(
'photo_name' => $result->url,
'path' => LYCHEE_UPLOADS_BIG . $result->url,
'path_thumb' => $result->thumbUrl
'path_thumb' => $result->thumbUrl,
'medium' => $result->medium
);
return $return;
@ -227,20 +237,23 @@ class Photo extends Module {
}
private function createThumb($url, $filename, $width = 200, $height = 200) {
private function createThumb($url, $filename, $type, $width, $height) {
# Check dependencies
self::dependencies(isset($this->database, $this->settings, $url, $filename));
self::dependencies(isset($this->database, $this->settings, $url, $filename, $type, $width, $height));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
$info = getimagesize($url);
$photoName = explode(".", $filename);
# Size of the thumbnail
$newWidth = 200;
$newHeight = 200;
$photoName = explode('.', $filename);
$newUrl = LYCHEE_UPLOADS_THUMB . $photoName[0] . '.jpeg';
$newUrl2x = LYCHEE_UPLOADS_THUMB . $photoName[0] . '@2x.jpeg';
# create thumbnails with Imagick
# Create thumbnails with Imagick
if(extension_loaded('imagick')&&$this->settings['imagick']==='1') {
# Read image
@ -253,34 +266,36 @@ class Photo extends Module {
$thumb2x = clone $thumb;
# Create 1st version
$thumb->cropThumbnailImage($width, $height);
$thumb->cropThumbnailImage($newWidth, $newHeight);
$thumb->writeImage($newUrl);
$thumb->clear();
$thumb->destroy();
# Create 2nd version
$thumb2x->cropThumbnailImage($width*2, $height*2);
$thumb2x->cropThumbnailImage($newWidth*2, $newHeight*2);
$thumb2x->writeImage($newUrl2x);
$thumb2x->clear();
$thumb2x->destroy();
} else {
# Set position and size
$thumb = imagecreatetruecolor($width, $height);
$thumb2x = imagecreatetruecolor($width*2, $height*2);
if ($info[0]<$info[1]) {
$newSize = $info[0];
# Create image
$thumb = imagecreatetruecolor($newWidth, $newHeight);
$thumb2x = imagecreatetruecolor($newWidth*2, $newHeight*2);
# Set position
if ($width<$height) {
$newSize = $width;
$startWidth = 0;
$startHeight = $info[1]/2 - $info[0]/2;
$startHeight = $height/2 - $width/2;
} else {
$newSize = $info[1];
$startWidth = $info[0]/2 - $info[1]/2;
$newSize = $height;
$startWidth = $width/2 - $height/2;
$startHeight = 0;
}
# Create new image
switch($info['mime']) {
switch($type) {
case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break;
case 'image/png': $sourceImg = imagecreatefrompng($url); break;
case 'image/gif': $sourceImg = imagecreatefromgif($url); break;
@ -290,12 +305,12 @@ class Photo extends Module {
}
# Create thumb
fastimagecopyresampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $width, $height, $newSize, $newSize);
fastimagecopyresampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth, $newHeight, $newSize, $newSize);
imagejpeg($thumb, $newUrl, $this->settings['thumbQuality']);
imagedestroy($thumb);
# Create retina thumb
fastimagecopyresampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $width*2, $height*2, $newSize, $newSize);
fastimagecopyresampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth*2, $newHeight*2, $newSize, $newSize);
imagejpeg($thumb2x, $newUrl2x, $this->settings['thumbQuality']);
imagedestroy($thumb2x);
@ -311,6 +326,57 @@ class Photo extends Module {
}
private function createMedium($url, $filename, $width, $height) {
# Check dependencies
self::dependencies(isset($this->database, $this->settings, $url, $filename, $width, $height));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Size of the medium-photo
# When changing these values,
# also change the size detection in the front-end
$newWidth = 1920;
$newHeight = 1080;
# Is photo big enough?
# Is medium activated?
# Is Imagick installed and activated?
if (($width>$newWidth||$height>$newHeight)&&
($this->settings['medium']==='1')&&
(extension_loaded('imagick')&&$this->settings['imagick']==='1')) {
# $info = getimagesize($url);
$newUrl = LYCHEE_UPLOADS_MEDIUM . $filename;
# Read image
$medium = new Imagick();
$medium->readImage($url);
$medium->scaleImage($newWidth, $newHeight, true);
$medium->writeImage($newUrl);
$medium->clear();
$medium->destroy();
$error = false;
} else {
# Photo too small or
# Medium is deactivated or
# Imagick not installed
$error = true;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if ($error===true) return false;
return true;
}
public function adjustFile($path, $info) {
# Check dependencies
@ -319,6 +385,8 @@ class Photo extends Module {
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
$swapSize = false;
if (extension_loaded('imagick')&&$this->settings['imagick']==='1') {
$rotateImage = 0;
@ -327,17 +395,20 @@ class Photo extends Module {
case 3:
$rotateImage = 180;
$imageOrientation = 1;
break;
case 6:
$rotateImage = 90;
$imageOrientation = 1;
$rotateImage = 90;
$swapSize = true;
break;
case 8:
$rotateImage = 270;
$imageOrientation = 1;
$rotateImage = 270;
$swapSize = true;
break;
default:
return false;
break;
}
@ -346,7 +417,7 @@ class Photo extends Module {
$image = new Imagick();
$image->readImage($path);
$image->rotateImage(new ImagickPixel(), $rotateImage);
$image->setImageOrientation($imageOrientation);
$image->setImageOrientation(1);
$image->writeImage($path);
$image->clear();
$image->destroy();
@ -356,7 +427,6 @@ class Photo extends Module {
$newWidth = $info['width'];
$newHeight = $info['height'];
$process = false;
$sourceImg = imagecreatefromjpeg($path);
switch ($info['orientation']) {
@ -364,6 +434,7 @@ class Photo extends Module {
case 2:
# mirror
# not yet implemented
return false;
break;
case 3:
@ -374,11 +445,13 @@ class Photo extends Module {
case 4:
# rotate 180 and mirror
# not yet implemented
return false;
break;
case 5:
# rotate 90 and mirror
# not yet implemented
return false;
break;
case 6:
@ -386,11 +459,13 @@ class Photo extends Module {
$sourceImg = imagerotate($sourceImg, -90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
$swapSize = true;
break;
case 7:
# rotate -90 and mirror
# not yet implemented
return false;
break;
case 8:
@ -398,30 +473,38 @@ class Photo extends Module {
$sourceImg = imagerotate($sourceImg, 90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
$swapSize = true;
break;
}
# Need to adjust photo?
if ($process===true) {
default:
return false;
break;
# Recreate photo
$newSourceImg = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight);
imagejpeg($newSourceImg, $path, 100);
}
# Free memory
imagedestroy($sourceImg);
imagedestroy($newSourceImg);
# Recreate photo
$newSourceImg = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight);
imagejpeg($newSourceImg, $path, 100);
}
# Free memory
imagedestroy($sourceImg);
imagedestroy($newSourceImg);
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
# SwapSize should be true when the image has been rotated
# Return new dimensions in this case
if ($swapSize===true) {
$swapSize = $info['width'];
$info['width'] = $info['height'];
$info['height'] = $swapSize;
}
return $info;
}
@ -442,7 +525,11 @@ class Photo extends Module {
$photo['sysdate'] = date('d M. Y', substr($photo['id'], 0, -4));
if (strlen($photo['takestamp'])>1) $photo['takedate'] = date('d M. Y', $photo['takestamp']);
# Parse url
# Parse medium
if ($photo['medium']==='1') $photo['medium'] = LYCHEE_URL_UPLOADS_MEDIUM . $photo['url'];
else $photo['medium'] = '';
# Parse paths
$photo['url'] = LYCHEE_URL_UPLOADS_BIG . $photo['url'];
$photo['thumbUrl'] = LYCHEE_URL_UPLOADS_THUMB . $photo['thumbUrl'];
@ -892,6 +979,12 @@ class Photo extends Module {
return false;
}
# Delete medium
if (file_exists(LYCHEE_UPLOADS_MEDIUM . $photo->url)&&!unlink(LYCHEE_UPLOADS_MEDIUM . $photo->url)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/medium/');
return false;
}
# Delete thumb
if (file_exists(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)&&!unlink(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/thumb/');

@ -1,8 +1,7 @@
<?php
###
# @name Plugins Module
# @author Tobias Reich
# @name Plugins Module
# @copyright 2014 by Tobias Reich
###

@ -1,8 +1,7 @@
<?php
###
# @name Session Module
# @author Tobias Reich
# @name Session Module
# @copyright 2014 by Tobias Reich
###

@ -1,8 +1,7 @@
<?php
###
# @name Settings Module
# @author Tobias Reich
# @name Settings Module
# @copyright 2014 by Tobias Reich
###

@ -1,11 +1,9 @@
<?php
/**
* @name Misc Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
###
# @name Misc Module
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
@ -15,7 +13,7 @@ function search($database, $settings, $term) {
$return['albums'] = '';
// Photos
# Photos
$query = Database::prepare($database, "SELECT id, title, tags, public, star, album, thumbUrl FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%' OR tags LIKE '%?%'", array(LYCHEE_TABLE_PHOTOS, $term, $term, $term));
$result = $database->query($query);
while($row = $result->fetch_assoc()) {
@ -24,20 +22,20 @@ function search($database, $settings, $term) {
$return['photos'][$row['id']]['sysdate'] = date('d M. Y', substr($row['id'], 0, -4));
}
// Albums
# Albums
$query = Database::prepare($database, "SELECT id, title, public, sysstamp, password FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%'", array(LYCHEE_TABLE_ALBUMS, $term, $term));
$result = $database->query($query);
$i = 0;
while($row = $result->fetch_object()) {
// Info
# Info
$return['albums'][$row->id]['id'] = $row->id;
$return['albums'][$row->id]['title'] = $row->title;
$return['albums'][$row->id]['public'] = $row->public;
$return['albums'][$row->id]['sysdate'] = date('F Y', $row->sysstamp);
$return['albums'][$row->id]['password'] = ($row->password=='' ? false : true);
// Thumbs
# Thumbs
$query = Database::prepare($database, "SELECT thumbUrl FROM ? WHERE album = '?' " . $settings['sorting'] . " LIMIT 0, 3", array(LYCHEE_TABLE_PHOTOS, $row->id));
$result2 = $database->query($query);
$k = 0;
@ -58,12 +56,15 @@ function getGraphHeader($database, $photoID) {
if (!isset($database, $photoID)) return false;
$query = Database::prepare($database, "SELECT title, description, url FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photoID));
$query = Database::prepare($database, "SELECT title, description, url, medium FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photoID));
$result = $database->query($query);
$row = $result->fetch_object();
$parseUrl = parse_url("http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
$picture = $parseUrl['scheme']."://".$parseUrl['host'].$parseUrl['path']."/../uploads/big/".$row->url;
if ($row->medium==='1') $dir = 'medium';
else $dir = 'big';
$parseUrl = parse_url('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
$picture = $parseUrl['scheme'] . '://' . $parseUrl['host'] . $parseUrl['path'] . '/../uploads/' . $dir . '/' . $row->url;
$return = '<!-- General Meta Data -->';
$return .= '<meta name="title" content="'.$row->title.'" />';
@ -121,13 +122,14 @@ function get_hashed_password($password) {
}
function hasPermissions($path, $permissions = '0777') {
function hasPermissions($path) {
// Check if the given path is readable and writable
// Both functions are also verifying that the path exists
if (is_readable($path)===true&&
is_writeable($path)===true) return true;
/* assume that if running with the same uid as the owner of the directory it's ok */
$stat = @stat($path);
if ($stat && ($stat['uid'] == getmyuid())) return true;
if (substr(sprintf('%o', @fileperms($path)), -4)!=$permissions) return false;
else return true;
return false;
}

@ -68,11 +68,12 @@ if (!isset($settings['imagick'])||$settings['imagick']=='') $error .= ('Error
if (!isset($settings['checkForUpdates'])||($settings['checkForUpdates']!='0'&&$settings['checkForUpdates']!='1')) $error .= ('Error 410: No or wrong property for checkForUpdates in database' . PHP_EOL);
# Permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false) $error .= ('Error 500: Wrong permissions for \'uploads/big\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_THUMB)===false) $error .= ('Error 501: Wrong permissions for \'uploads/thumb\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_IMPORT)===false) $error .= ('Error 502: Wrong permissions for \'uploads/import\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS)===false) $error .= ('Error 503: Wrong permissions for \'uploads/\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_DATA)===false) $error .= ('Error 504: Wrong permissions for \'data/\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false) $error .= ('Error 500: \'uploads/big\' missing or not readable and writable (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) $error .= ('Error 500: \'uploads/medium\' missing or not readable and writable (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_THUMB)===false) $error .= ('Error 501: \'uploads/thumb\' missing or not readable and writable (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_IMPORT)===false) $error .= ('Error 502: \'uploads/import\' missing or not readable and writable (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS)===false) $error .= ('Error 503: \'uploads/\' missing or not readable and writable (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_DATA)===false) $error .= ('Error 504: \'data/\' missing or not readable and writable (777 required)' . PHP_EOL);
# Check dropboxKey
if (!$settings['dropboxKey']) echo('Warning: Dropbox import not working. No property for dropboxKey.' . PHP_EOL);
@ -92,7 +93,7 @@ echo(PHP_EOL . PHP_EOL . 'System Information' . PHP_EOL);
echo('------------------' . PHP_EOL);
# Load json
$json = file_get_contents(LYCHEE_BUILD . 'package.json');
$json = file_get_contents(LYCHEE_SRC . 'package.json');
$json = json_decode($json, true);
$imagick = extension_loaded('imagick');

@ -0,0 +1,8 @@
User-agent: *
Disallow: /data/
Disallow: /dist/
Disallow: /docs/
Disallow: /php/
Disallow: /plugins/
Disallow: /src/
Disallow: /uploads/

@ -1,8 +1,10 @@
{
"name": "Lychee",
"private": true,
"dependencies": {
"jQuery": "~2.1.1",
"js-md5": "~1.1.0",
"mousetrap": "~1.4.6"
"mousetrap": "~1.4.6",
"basicContext": "~2.0.2"
}
}
}

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

@ -0,0 +1,177 @@
var gulp = require('gulp'),
plugins = require("gulp-load-plugins")(),
paths = {}
/* Error Handler -------------------------------- */
var catchError = function(err) {
console.log(err.toString());
this.emit('end');
}
/* View ----------------------------------------- */
paths.view = {
js: [
'bower_components/jQuery/dist/jquery.min.js',
'../src/scripts/_frameworks.js',
'../src/scripts/view/main.js'
],
coffee: [
'../src/scripts/build.coffee'
],
scripts: [
'../dist/_view--javascript.js',
'../dist/_view--coffee.js'
]
}
gulp.task('view--js', function() {
var stream =
gulp.src(paths.view.js)
.pipe(plugins.concat('_view--javascript.js', {newLine: "\n"}))
.pipe(gulp.dest('../dist/'));
return stream;
});
gulp.task('view--coffee', function() {
var stream =
gulp.src(paths.view.coffee)
.pipe(plugins.coffee({bare: true}))
.on('error', catchError)
.pipe(plugins.concat('_view--coffee.js', {newLine: "\n"}))
.pipe(gulp.dest('../dist/'));
return stream;
});
gulp.task('view--scripts', ['view--js', 'view--coffee'], function() {
var stream =
gulp.src(paths.view.scripts)
.pipe(plugins.concat('view.js', {newLine: "\n"}))
.pipe(plugins.uglify())
.on('error', catchError)
.pipe(gulp.dest('../dist/'));
return stream;
});
/* Main ----------------------------------------- */
paths.main = {
js: [
'bower_components/jQuery/dist/jquery.min.js',
'bower_components/js-md5/js/md5.min.js',
'bower_components/mousetrap/mousetrap.min.js',
'bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js',
'bower_components/basicContext/dist/basicContext.min.js',
'../src/scripts/*.js'
],
coffee: [
'../src/scripts/*.coffee'
],
scripts: [
'../dist/_main--javascript.js',
'../dist/_main--coffee.js'
],
scss: [
'../src/styles/*.scss'
],
styles: [
'bower_components/basicContext/src/styles/main.scss',
'../src/styles/main.scss'
]
}
gulp.task('main--js', function() {
var stream =
gulp.src(paths.main.js)
.pipe(plugins.concat('_main--javascript.js', {newLine: "\n"}))
.pipe(gulp.dest('../dist/'));
return stream;
});
gulp.task('main--coffee', function() {
var stream =
gulp.src(paths.main.coffee)
.pipe(plugins.coffee({bare: true}))
.on('error', catchError)
.pipe(plugins.concat('_main--coffee.js', {newLine: "\n"}))
.pipe(gulp.dest('../dist/'));
return stream;
});
gulp.task('main--scripts', ['main--js', 'main--coffee'], function() {
var stream =
gulp.src(paths.main.scripts)
.pipe(plugins.concat('main.js', {newLine: "\n"}))
.pipe(plugins.uglify())
.on('error', catchError)
.pipe(gulp.dest('../dist/'));
return stream;
});
gulp.task('main--styles', function() {
var stream =
gulp.src(paths.main.styles)
.pipe(plugins.sass())
.on('error', catchError)
.pipe(plugins.concat('main.css', {newLine: "\n"}))
.pipe(plugins.autoprefixer('last 4 versions', '> 5%'))
.pipe(plugins.minifyCss())
.pipe(gulp.dest('../dist/'));
return stream;
});
/* Clean ----------------------------------------- */
gulp.task('clean', function() {
var stream =
gulp.src('../dist/_*.*', { read: false })
.pipe(plugins.rimraf({ force: true }))
.on('error', catchError);
return stream;
});
/* Tasks ----------------------------------------- */
gulp.task('default', ['view--scripts', 'main--scripts', 'main--styles'], function() {
gulp.start('clean');
});
gulp.task('watch', ['default'], function() {
gulp.watch(paths.view.js, ['view--scripts']);
gulp.watch(paths.view.coffee, ['view--scripts']);
gulp.watch(paths.main.js, ['main--scripts']);
gulp.watch(paths.main.coffee, ['main--scripts']);
gulp.watch(paths.main.scss, ['main--styles']);
});

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g><path d="M0 0h16v16H0V0z" fill="#444"/><rect stroke="#aaa" x="4" y="4" width="5" height="5" rx="1"/><path fill="#444" d="M6 6h8v7H6z"/><rect stroke="#aaa" x="7" y="7" width="5" height="5" rx="1"/></g></g></svg>

After

Width:  |  Height:  |  Size: 331 B

@ -0,0 +1 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path fill="#222" d="M0 0h200v200H0z"/><g transform="translate(68 52)"><rect stroke="#B4B4B4" stroke-width="4" width="50" height="42" rx="3"/><path fill="#222" d="M11 11h44v36H11z"/><rect stroke="#B4B4B4" stroke-width="4" x="20" y="20" width="50" height="42" rx="3"/></g></g></svg>

After

Width:  |  Height:  |  Size: 361 B

@ -0,0 +1 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path fill="#222" d="M0 0h200v200H0z"/><path d="M118 73h3c4.412 0 8 3.58 8 7.994v28.013c0 4.412-3.582 7.994-8 7.994H80c-4.413 0-8-3.578-8-7.993V80.994C72 76.582 75.58 73 80 73h3v-9.495C83 53.84 90.833 46 100.5 46S118 53.837 118 63.505V73zm-17.5-20C94.7 53 90 57.702 90 63.498V73h21v-9.502C111 57.7 106.3 53 100.5 53zm0 0" fill="#B4B4B4"/></g></svg>

After

Width:  |  Height:  |  Size: 428 B

@ -1,20 +1,23 @@
{
"name": "Lychee",
"version": "2.6.3",
"version": "2.7.0",
"description": "Self-hosted photo-management done right.",
"authors": "Tobias Reich <tobias.reich.ich@gmail.com>",
"license": "MIT",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/electerious/Lychee.git"
},
"devDependencies": {
"gulp": "^3.8.8",
"gulp-autoprefixer": "1.0.1",
"gulp-concat": "^2.4.1",
"gulp-load-plugins": "^0.7.0",
"gulp-minify-css": "^0.3.10",
"gulp-sass": "^1.0.0",
"gulp": "^3.8.10",
"gulp-autoprefixer": "2.0.0",
"gulp-coffee": "^2.2.0",
"gulp-concat": "^2.4.2",
"gulp-load-plugins": "^0.7.1",
"gulp-minify-css": "^0.3.11",
"gulp-rimraf": "^0.1.1",
"gulp-sass": "^1.2.4",
"gulp-uglify": "^1.0.1"
}
}

@ -0,0 +1,8 @@
/* Browser Detection */
var BrowserDetect={init:function(){this.browser=this.searchString(this.dataBrowser)||"An unknown browser";this.version=this.searchVersion(navigator.userAgent)||this.searchVersion(navigator.appVersion)||"an unknown version";this.OS=this.searchString(this.dataOS)||"an unknown OS"},searchString:function(d){for(var a=0;a<d.length;a++){var b=d[a].string;var c=d[a].prop;this.versionSearchString=d[a].versionSearch||d[a].identity;if(b){if(b.indexOf(d[a].subString)!=-1){return d[a].identity}}else{if(c){return d[a].identity}}}},searchVersion:function(b){var a=b.indexOf(this.versionSearchString);if(a==-1){return}return parseFloat(b.substring(a+this.versionSearchString.length+1))},dataBrowser:[{string:navigator.userAgent,subString:"Chrome",identity:"Chrome"},{string:navigator.userAgent,subString:"OmniWeb",versionSearch:"OmniWeb/",identity:"OmniWeb"},{string:navigator.vendor,subString:"Apple",identity:"Safari",versionSearch:"Version"},{prop:window.opera,identity:"Opera"},{string:navigator.vendor,subString:"iCab",identity:"iCab"},{string:navigator.vendor,subString:"KDE",identity:"Konqueror"},{string:navigator.userAgent,subString:"Firefox",identity:"Firefox"},{string:navigator.vendor,subString:"Camino",identity:"Camino"},{string:navigator.userAgent,subString:"Netscape",identity:"Netscape"},{string:navigator.userAgent,subString:"MSIE",identity:"Explorer",versionSearch:"MSIE"},{string:navigator.userAgent,subString:"Gecko",identity:"Mozilla",versionSearch:"rv"},{string:navigator.userAgent,subString:"Mozilla",identity:"Netscape",versionSearch:"Mozilla"}],dataOS:[{string:navigator.platform,subString:"Win",identity:"Windows"},{string:navigator.platform,subString:"Mac",identity:"Mac"},{string:navigator.userAgent,subString:"iPhone",identity:"iPhone/iPod"},{string:navigator.platform,subString:"Linux",identity:"Linux"}]};BrowserDetect.init();
function mobileBrowser() { if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) return true; else return false; }
/* GET */
function gup(b){b=b.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var a="[\\?&]"+b+"=([^&#]*)",d=new RegExp(a),c=d.exec(window.location.href);if(c===null){return""}else{return c[1]}};
/*! jQuery Retina Plugin */
(function(a){a.fn.retina=function(c){var d={"retina-background":false,"retina-suffix":"@2x"};if(c){a.extend(d,c)}var b=function(f,g){var e=new Image();e.onload=function(){g(e)};e.src=f};if(window.devicePixelRatio>1){this.each(function(){var e=a(this);if(this.tagName.toLowerCase()=="img"&&e.attr("src")){var g=e.attr("src").replace(/\.(?!.*\.)/,d["retina-suffix"]+".");b(g,function(h){e.attr("src",h.src);var i=a("<div>").append(e.clone()).remove().html();if(!(/(width|height)=["']\d+["']/.test(i))){e.attr("width",h.width/2)
}})}if(d["retina-background"]){var f=e.css("background-image");if(/^url\(.*\)$/.test(f)){var g=f.substring(4,f.length-1).replace(/\.(?!.*\.)/,d["retina-suffix"]+".");b(g,function(h){e.css("background-image","url("+h.src+")");if(e.css("background-size")=="auto auto"){e.css("background-size",(h.width/2)+"px auto")}})}}})}}})(jQuery);

@ -0,0 +1,407 @@
/**
* @description Takes care of every action an album can handle and execute.
* @copyright 2014 by Tobias Reich
*/
album = {
json: null
}
album.getID = function() {
var id;
if (photo.json) id = photo.json.album;
else if (album.json) id = album.json.id;
else id = $('.album:hover, .album.active').attr('data-id');
// Search
if (!id) id = $('.album:hover, .album.active').attr('data-id');
if (!id) id = $('.photo:hover, .photo.active').attr('data-album-id');
if (id) return id;
else return false;
}
album.load = function(albumID, refresh) {
var startTime,
params,
durationTime,
waitTime;
password.get(albumID, function() {
if (!refresh) {
lychee.animate('.album, .photo', 'contentZoomOut');
lychee.animate('.divider', 'fadeOut');
}
startTime = new Date().getTime();
params = 'getAlbum&albumID=' + albumID + '&password=' + password.value;
lychee.api(params, function(data) {
if (data==='Warning: Album private!') {
if (document.location.hash.replace('#', '').split('/')[1]!=undefined) {
// Display photo only
lychee.setMode('view');
} else {
// Album not public
lychee.content.show();
lychee.goto('');
}
return false;
}
if (data==='Warning: Wrong password!') {
album.load(albumID, refresh);
return false;
}
album.json = data;
// Calculate delay
durationTime = (new Date().getTime() - startTime);
if (durationTime>300) waitTime = 0;
else waitTime = 300 - durationTime;
// Skip delay when refresh is true
// Skip delay when opening a blank Lychee
if (refresh===true) waitTime = 0;
if (!visible.albums()&&!visible.photo()&&!visible.album()) waitTime = 0;
setTimeout(function() {
view.album.init();
if (!refresh) {
lychee.animate('.album, .photo', 'contentZoomIn');
view.header.mode('album');
}
}, waitTime);
});
});
}
album.parse = function() {
if (!album.json.title) album.json.title = 'Untitled';
}
album.add = function() {
var title,
params,
buttons,
isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n) };
buttons = [
['Create Album', function() {
title = $('.message input.text').val();
if (title.length===0) title = 'Untitled';
modal.close();
params = 'addAlbum&title=' + escape(encodeURI(title));
lychee.api(params, function(data) {
// Avoid first album to be true
if (data===true) data = 1;
if (data!==false&&isNumber(data)) {
albums.refresh();
lychee.goto(data);
} else {
lychee.error(null, params, data);
}
});
}],
['Cancel', function() {}]
];
modal.show('New Album', "Enter a title for this album: <input class='text' type='text' maxlength='30' placeholder='Title' value='Untitled'>", buttons);
}
album.delete = function(albumIDs) {
var params,
buttons,
albumTitle;
if (!albumIDs) return false;
if (albumIDs instanceof Array===false) albumIDs = [albumIDs];
buttons = [
['', function() {
params = 'deleteAlbum&albumIDs=' + albumIDs;
lychee.api(params, function(data) {
if (visible.albums()) {
albumIDs.forEach(function(id) {
albums.json.num--;
view.albums.content.delete(id);
delete albums.json.content[id];
});
} else {
albums.refresh();
lychee.goto('');
}
if (data!==true) lychee.error(null, params, data);
});
}],
['', function() {}]
];
if (albumIDs.toString()==='0') {
buttons[0][0] = 'Clear Unsorted';
buttons[1][0] = 'Keep Unsorted';
modal.show('Clear Unsorted', "Are you sure you want to delete all photos from 'Unsorted'?<br>This action can't be undone!", buttons);
} else if (albumIDs.length===1) {
buttons[0][0] = 'Delete Album and Photos';
buttons[1][0] = 'Keep Album';
// Get title
if (album.json) albumTitle = album.json.title;
else if (albums.json) albumTitle = albums.json.content[albumIDs].title;
modal.show('Delete Album', "Are you sure you want to delete the album '" + albumTitle + "' and all of the photos it contains? This action can't be undone!", buttons);
} else {
buttons[0][0] = 'Delete Albums and Photos';
buttons[1][0] = 'Keep Albums';
modal.show('Delete Albums', "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!", buttons);
}
}
album.setTitle = function(albumIDs) {
var oldTitle = '',
newTitle,
params,
buttons;
if (!albumIDs) return false;
if (albumIDs instanceof Array===false) albumIDs = [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.json.content[albumIDs].title;
if (!oldTitle) oldTitle = '';
oldTitle = oldTitle.replace("'", '&apos;');
}
buttons = [
['Set Title', function() {
// Get input
newTitle = $('.message input.text').val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
// Set to Untitled when empty
newTitle = (newTitle==='') ? 'Untitled' : newTitle;
if (visible.album()) {
album.json.title = newTitle;
view.album.title();
if (albums.json) {
var id = albumIDs[0];
albums.json.content[id].title = newTitle;
}
} else if (visible.albums()) {
albumIDs.forEach(function(id) {
albums.json.content[id].title = newTitle;
view.albums.content.title(id);
});
}
params = 'setAlbumTitle&albumIDs=' + albumIDs + '&title=' + escape(encodeURI(newTitle));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
if (albumIDs.length===1) modal.show('Set Title', "Enter a new title for this album: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
else modal.show('Set Titles', "Enter a title for all " + albumIDs.length + " selected album: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
}
album.setDescription = function(photoID) {
var oldDescription = album.json.description.replace("'", '&apos;'),
description,
params,
buttons;
buttons = [
['Set Description', function() {
// Get input
description = $('.message input.text').val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.album()) {
album.json.description = description;
view.album.description();
}
params = 'setAlbumDescription&albumID=' + photoID + '&description=' + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
modal.show('Set Description', "Please enter a description for this album: <input class='text' type='text' maxlength='800' placeholder='Description' value='" + oldDescription + "'>", buttons);
}
album.setPublic = function(albumID, e) {
var params,
password = '',
listed = false,
downloadable = false;
albums.refresh();
if (!visible.message()&&album.json.public==0) {
modal.show('Share Album', "This album will be shared with the following properties:</p><form><div class='choice'><input type='checkbox' name='listed' value='listed' checked><h2>Visible</h2><p>Listed to visitors of your Lychee.</p></div><div class='choice'><input type='checkbox' name='downloadable' value='downloadable'><h2>Downloadable</h2><p>Visitors of your Lychee can download this album.</p></div><div class='choice'><input type='checkbox' name='password' value='password'><h2>Password protected</h2><p>Only accessible with a valid password.<input class='text' type='password' placeholder='password' value='' style='display: none;'></p></div></form><p style='display: none;'>", [['Share Album', function() { album.setPublic(album.getID(), e) }], ['Cancel', function() {}]], -170);
$('.message .choice input[name="password"]').on('change', function() {
if ($(this).prop('checked')===true) $('.message .choice input.text').show();
else $('.message .choice input.text').hide();
});
return true;
}
if (visible.message()) {
if ($('.message .choice input[name="password"]:checked').val()==='password') {
password = md5($('.message input.text').val());
album.json.password = 1;
} else {
password = '';
album.json.password = 0;
}
if ($('.message .choice input[name="listed"]:checked').val()==='listed') listed = true;
if ($('.message .choice input[name="downloadable"]:checked').val()==='downloadable') downloadable = true;
}
params = 'setAlbumPublic&albumID=' + albumID + '&password=' + password + '&visible=' + listed + '&downloadable=' + downloadable;
if (visible.album()) {
album.json.public = (album.json.public==0) ? 1 : 0;
album.json.password = (album.json.public==0) ? 0 : album.json.password;
view.album.public();
view.album.password();
if (album.json.public==1) contextMenu.shareAlbum(albumID, e);
}
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}
album.share = function(service) {
var link = '',
url = location.href;
switch (service) {
case 0:
link = 'https://twitter.com/share?url=' + encodeURI(url);
break;
case 1:
link = 'http://www.facebook.com/sharer.php?u=' + encodeURI(url) + '&t=' + encodeURI(album.json.title);
break;
case 2:
link = 'mailto:?subject=' + encodeURI(album.json.title) + '&body=' + encodeURI(url);
break;
default:
link = '';
break;
}
if (link.length>5) location.href = link;
}
album.getArchive = function(albumID) {
var link,
url = 'php/api.php?function=getAlbumArchive&albumID=' + albumID;
if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url);
else link = location.href.replace(location.hash, '') + url;
if (lychee.publicMode) link += '&password=' + password.value;
location.href = link;
}

@ -0,0 +1,115 @@
/**
* @description Takes care of every action albums can handle and execute.
* @copyright 2014 by Tobias Reich
*/
albums = {
json: null
}
albums.load = function() {
var startTime,
durationTime,
waitTime;
lychee.animate('.album, .photo', 'contentZoomOut');
lychee.animate('.divider', 'fadeOut');
startTime = new Date().getTime();
if (albums.json===null) {
lychee.api('getAlbums', function(data) {
/* Smart Albums */
data.unsortedAlbum = {
id: 0,
title: 'Unsorted',
sysdate: data.unsortedNum + ' photos',
unsorted: '1',
thumb0: data.unsortedThumb0,
thumb1: data.unsortedThumb1,
thumb2: data.unsortedThumb2
};
data.starredAlbum = {
id: 'f',
title: 'Starred',
sysdate: data.starredNum + ' photos',
star: '1',
thumb0: data.starredThumb0,
thumb1: data.starredThumb1,
thumb2: data.starredThumb2
};
data.publicAlbum = {
id: 's',
title: 'Public',
sysdate: data.publicNum + ' photos',
public: '1',
thumb0: data.publicThumb0,
thumb1: data.publicThumb1,
thumb2: data.publicThumb2
};
data.recentAlbum = {
id: 'r',
title: 'Recent',
sysdate: data.recentNum + ' photos',
recent: '1',
thumb0: data.recentThumb0,
thumb1: data.recentThumb1,
thumb2: data.recentThumb2
};
albums.json = data;
// Calculate delay
durationTime = (new Date().getTime() - startTime);
if (durationTime>300) waitTime = 0;
else waitTime = 300 - durationTime;
// Skip delay when opening a blank Lychee
if (!visible.albums()&&!visible.photo()&&!visible.album()) waitTime = 0;
if (visible.album()&&lychee.content.html()==='') waitTime = 0;
setTimeout(function() {
view.header.mode('albums');
view.albums.init();
lychee.animate('.album, .photo', 'contentZoomIn');
}, waitTime);
});
} else {
setTimeout(function() {
view.header.mode('albums');
view.albums.init();
lychee.animate('.album, .photo', 'contentZoomIn');
}, 300);
}
}
albums.parse = function(album) {
if (album.password&&lychee.publicMode) {
album.thumb0 = 'src/images/password.svg';
album.thumb1 = 'src/images/password.svg';
album.thumb2 = 'src/images/password.svg';
} else {
if (!album.thumb0) album.thumb0 = 'src/images/no_images.svg';
if (!album.thumb1) album.thumb1 = 'src/images/no_images.svg';
if (!album.thumb2) album.thumb2 = 'src/images/no_images.svg';
}
}
albums.refresh = function() {
albums.json = null;
}

@ -0,0 +1,424 @@
###
# @description This module is used to generate HTML-Code.
# @copyright 2014 by Tobias Reich
###
window.build = {}
build.divider = (title) ->
"<div class='divider fadeIn'><h1>#{ title }</h1></div>"
build.editIcon = (id) ->
"<div id='#{ id }' class='edit'><a class='icon-pencil'></a></div>"
build.multiselect = (top, left) ->
"<div id='multiselect' style='top: #{ top }px; left: #{ left }px;'></div>"
build.album = (data) ->
return '' if not data?
title = data.title
longTitle = ''
typeThumb = ''
if title? and title.length > 18
title = data.title.substr(0, 18) + '...'
longTitle = data.title
if data.thumb0.split('.').pop() is 'svg' then typeThumb = 'nonretina'
html = """
<div class='album' data-id='#{ data.id }' data-password='#{ data.password }'>
<img src='#{ data.thumb2 }' width='200' height='200' alt='thumb' data-type='nonretina'>
<img src='#{ data.thumb1 }' width='200' height='200' alt='thumb' data-type='nonretina'>
<img src='#{ data.thumb0 }' width='200' height='200' alt='thumb' data-type='#{ typeThumb }'>
<div class='overlay'>
"""
if data.password and lychee.publicMode is false
html += "<h1 title='#{ longTitle }'><span class='icon-lock'></span> #{ title }</h1>";
else
html += "<h1 title='#{ longTitle }'>#{ title }</h1>"
html += """
<a>#{ data.sysdate }</a>
</div>
"""
if lychee.publicMode is false
if data.star is '1' then html += "<a class='badge icon-star'></a>"
if data.public is '1' then html += "<a class='badge icon-share'></a>"
if data.unsorted is '1' then html += "<a class='badge icon-reorder'></a>"
if data.recent is '1' then html += "<a class='badge icon-time'></a>"
html += "</div>"
return html
build.photo = (data) ->
return '' if not data?
title = data.title
longTitle = ''
if title? and title.length > 18
title = data.title.substr(0, 18) + '...'
longTitle = data.title
html = """
<div class='photo' data-album-id='#{ data.album }' data-id='#{ data.id }'>
<img src='#{ data.thumbUrl }' width='200' height='200' alt='thumb'>
<div class='overlay'>
<h1 title='#{ longTitle }'>#{ title }</h1>
"""
if data.cameraDate is '1'
html += "<a><span class='icon-camera' title='Photo Date'></span>#{ data.sysdate }</a>"
else
html += "<a>#{ data.sysdate }</a>"
html += "</div>"
if data.star is '1' then html += "<a class='badge icon-star'></a>"
if lychee.publicMode is false and data.public is '1' and album.json.public isnt '1' then html += "<a class='badge icon-share'></a>"
html += "</div>"
return html
build.imageview = (data, size, visibleControls) ->
return '' if not data?
html = """
<div class='arrow_wrapper previous'><a id='previous' class='icon-caret-left'></a></div>
<div class='arrow_wrapper next'><a id='next' class='icon-caret-right'></a></div>
"""
if size is 'big'
if visibleControls is true
html += "<div id='image' style='background-image: url(#{ data.url })'></div>"
else
html += "<div id='image' style='background-image: url(#{ data.url });' class='full'></div>"
else if size is 'medium'
if visibleControls is true
html += "<div id='image' style='background-image: url(#{ data.medium })'></div>"
else
html += "<div id='image' style='background-image: url(#{ data.medium });' class='full'></div>"
else if size is 'small'
if visibleControls is true
html += "<div id='image' class='small' style='background-image: url(#{ data.url }); width: #{ data.width }px; height: #{ data.height }px; margin-top: -#{ parseInt(data.height/2-20) }px; margin-left: -#{ data.width/2 }px;'></div>"
else
html += "<div id='image' class='small' style='background-image: url(#{ data.url }); width: #{ data.width }px; height: #{ data.height }px; margin-top: -#{ parseInt(data.height/2) }px; margin-left: -#{ data.width/2 }px;'></div>"
return html
build.no_content = (typ) ->
html = """
<div class='no_content fadeIn'>
<a class='icon icon-#{ typ }'></a>
"""
switch typ
when 'search' then html += "<p>No results</p>"
when 'share' then html += "<p>No public albums</p>"
when 'cog' then html += "<p>No configuration</p>"
html += "</div>"
return html
build.modal = (title, text, button, marginTop, closeButton) ->
if marginTop? then custom_style = "style='margin-top: #{ marginTop }px;'"
else custom_style = ''
html = """
<div class='message_overlay fadeIn'>
<div class='message center' #{ custom_style }>
<h1>#{ title }</h1>
"""
if closeButton isnt false then html += "<a class='close icon-remove-sign'></a>"
html += "<p>#{ text }</p>"
$.each button, (index) ->
if this[0] isnt ''
if index is 0 then html += "<a class='button active'>#{ this[0] }</a>"
else html += "<a class='button'>#{ this[0] }</a>"
html += """
</div>
</div>
"""
return html
build.signInModal = ->
html = """
<div class='message_overlay'>
<div class='message center'>
<h1><a class='icon-lock'></a> Sign In</h1>
<a class='close icon-remove-sign'></a>
<div class='sign_in'>
<input id='username' type='text' value='' placeholder='username' autocapitalize='off' autocorrect='off'>
<input id='password' type='password' value='' placeholder='password'>
</div>
<div id='version'>Version #{ lychee.version }<span> &#8211; <a target='_blank' href='#{ lychee.updateURL }'>Update available!</a><span></div>
<a onclick='lychee.login()' class='button active'>Sign in</a>
</div>
</div>
"""
return html
build.uploadModal = (title, files) ->
html = """
<div class='upload_overlay fadeIn'>
<div class='upload_message center'>
<h1>#{ title }</h1>
<a class='close icon-remove-sign'></a>
<div class='rows'>
"""
i = 0
file = null
while i < files.length
file = files[i]
if file.name.length > 40
file.name = file.name.substr(0, 17) + '...' + file.name.substr(file.name.length-20, 20)
html += """
<div class='row'>
<a class='name'>#{ lychee.escapeHTML(file.name) }</a>
"""
if file.supported is true then html += "<a class='status'></a>"
else html += "<a class='status error'>Not supported</a>"
html += """
<p class='notice'></p>
</div>
"""
i++
html += """
</div>
</div>
</div>
"""
return html
build.tags = (tags, forView) ->
html = ''
if forView is true or lychee.publicMode is true then editTagsHTML = ''
else editTagsHTML = ' ' + build.editIcon('edit_tags')
if tags isnt ''
tags = tags.split ','
tags.forEach (tag, index, array) ->
html += "<a class='tag'>#{ tag }<span class='icon-remove' data-index='#{ index }'></span></a>"
html += editTagsHTML
else
html = "<div class='empty'>No Tags#{ editTagsHTML }</div>"
return html
build.infoboxPhoto = (data, forView) ->
html = """
<div class='header'><h1>About</h1><a class='icon-remove-sign'></a></div>
<div class='wrapper'>
"""
switch data.public
when '0' then visible = 'No'
when '1' then visible = 'Yes'
when '2' then visible = 'Yes (Album)'
else visible = '-'
if forView is true or lychee.publicMode is true then editTitleHTML = ''
else editTitleHTML = ' ' + build.editIcon('edit_title')
if forView is true or lychee.publicMode is true then editDescriptionHTML = ''
else editDescriptionHTML = ' ' + build.editIcon('edit_description')
infos = [
['', 'Basics']
['Title', data.title + editTitleHTML]
['Uploaded', data.sysdate]
['Description', data.description + editDescriptionHTML]
['', 'Image']
['Size', data.size]
['Format', data.type]
['Resolution', data.width + ' x ' + data.height]
['Tags', build.tags(data.tags, forView)]
]
exifHash = data.takestamp+data.make+data.model+data.shutter+data.aperture+data.focal+data.iso
if exifHash isnt '0' or exifHash isnt '0'
infos = infos.concat [
['', 'Camera']
['Captured', data.takedate]
['Make', data.make]
['Type/Model', data.model]
['Shutter Speed', data.shutter]
['Aperture', data.aperture]
['Focal Length', data.focal]
['ISO', data.iso]
]
infos = infos.concat [
['', 'Share']
['Public', visible]
]
infos.forEach (info, i, items) ->
if info[1] is '' or
not info[1]?
info[1] = '-'
switch info[0]
when ''
# Separator
html += """
</table>
<div class='separator'><h1>#{ info[1] }</h1></div>
<table>
"""
when 'Tags'
# Tags
if forView isnt true and lychee.publicMode is false
html += """
</table>
<div class='separator'><h1>#{ info[0] }</h1></div>
<div id='tags'>#{ info[1] }</div>
"""
else
# Item
html += """
<tr>
<td>#{ info[0] }</td>
<td class='attr_#{ info[0].toLowerCase() }'>#{ info[1] }</td>
</tr>
"""
html += """
</table>
<div class='bumper'></div>
</div>
"""
return html
build.infoboxAlbum = (data, forView) ->
html = """
<div class='header'><h1>About</h1><a class='icon-remove-sign'></a></div>
<div class='wrapper'>
"""
switch data.public
when '0' then visible = 'No'
when '1' then visible = 'Yes'
else visible = '-'
switch data.password
when false then password = 'No'
when true then password = 'Yes'
else password = '-'
switch data.downloadable
when '0' then downloadable = 'No'
when '1' then downloadable = 'Yes'
else downloadable = '-'
if forView is true or lychee.publicMode is true then editTitleHTML = ''
else editTitleHTML = ' ' + build.editIcon('edit_title_album')
if forView is true or lychee.publicMode is true then editDescriptionHTML = ''
else editDescriptionHTML = ' ' + build.editIcon('edit_description_album')
infos = [
['', 'Basics']
['Title', data.title + editTitleHTML]
['Description', data.description + editDescriptionHTML]
['', 'Album']
['Created', data.sysdate]
['Images', data.num]
['', 'Share']
['Public', visible]
['Downloadable', downloadable]
['Password', password]
]
infos.forEach (info, i, items) ->
if info[0] is ''
# Separator
html += """
</table>
<div class='separator'><h1>#{ info[1] }</h1></div>
<table id='infos'>
"""
else
# Item
html += """
<tr>
<td>#{ info[0] }</td>
<td class='attr_#{ info[0].toLowerCase() }'>#{ info[1] }</td>
</tr>
"""
html += """
</table>
<div class='bumper'></div>
</div>
"""
return html

@ -0,0 +1,226 @@
/**
* @description This module is used for the context menu.
* @copyright 2014 by Tobias Reich
*/
contextMenu = {}
contextMenu.add = function(e) {
var items = [
{ type: 'item', title: 'Upload Photo', icon: 'icon-picture', fn: function() { $('#upload_files').click() } },
{ type: 'separator' },
{ type: 'item', title: 'Import from Link', icon: 'icon-link', fn: upload.start.url },
{ type: 'item', title: 'Import from Dropbox', icon: 'icon-folder-open', fn: upload.start.dropbox },
{ type: 'item', title: 'Import from Server', icon: 'icon-hdd', fn: upload.start.server },
{ type: 'separator' },
{ type: 'item', title: 'New Album', icon: 'icon-folder-close', fn: album.add }
];
basicContext.show(items, e);
upload.notify();
}
contextMenu.settings = function(e) {
var items = [
{ type: 'item', title: 'Change Login', icon: 'icon-user', fn: settings.setLogin },
{ type: 'item', title: 'Change Sorting', icon: 'icon-sort', fn: settings.setSorting },
{ type: 'item', title: 'Set Dropbox', icon: 'icon-folder-open', fn: settings.setDropboxKey },
{ type: 'separator' },
{ type: 'item', title: 'About Lychee', icon: 'icon-info-sign', fn: function() { window.open(lychee.website) } },
{ type: 'item', title: 'Diagnostics', icon: 'icon-dashboard', fn: function() { window.open('plugins/check/') } },
{ type: 'item', title: 'Show Log', icon: 'icon-list', fn: function() { window.open('plugins/displaylog/') } },
{ type: 'separator' },
{ type: 'item', title: 'Sign Out', icon: 'icon-signout', fn: lychee.logout }
];
basicContext.show(items, e);
}
contextMenu.album = function(albumID, e) {
if (albumID==='0'||albumID==='f'||albumID==='s'||albumID==='r') return false;
var items = [
{ type: 'item', title: 'Rename', icon: 'icon-edit', fn: function() { album.setTitle([albumID]) } },
{ type: 'item', title: 'Delete', icon: 'icon-trash', fn: function() { album.delete([albumID]) } }
];
$('.album[data-id="' + albumID + '"]').addClass('active');
basicContext.show(items, e, contextMenu.close);
}
contextMenu.albumMulti = function(albumIDs, e) {
multiselect.stopResize();
var items = [
{ type: 'item', title: 'Rename All', icon: 'icon-edit', fn: function() { album.setTitle(albumIDs) } },
{ type: 'item', title: 'Delete All', icon: 'icon-trash', fn: function() { album.delete(albumIDs) } }
];
basicContext.show(items, e, contextMenu.close);
}
contextMenu.photo = function(photoID, e) {
// Notice for 'Move':
// fn must call basicContext.close() first,
// in order to keep the selection
var items = [
{ type: 'item', title: 'Star', icon: 'icon-star', fn: function() { photo.setStar([photoID]) } },
{ type: 'item', title: 'Tags', icon: 'icon-tags', fn: function() { photo.editTags([photoID]) } },
{ type: 'separator' },
{ type: 'item', title: 'Rename', icon: 'icon-edit', fn: function() { photo.setTitle([photoID]) } },
{ type: 'item', title: 'Duplicate', icon: 'icon-copy', fn: function() { photo.duplicate([photoID]) } },
{ type: 'item', title: 'Move', icon: 'icon-folder-open', fn: function() { basicContext.close(); contextMenu.move([photoID], e); } },
{ type: 'item', title: 'Delete', icon: 'icon-trash', fn: function() { photo.delete([photoID]) } }
];
$('.photo[data-id="' + photoID + '"]').addClass('active');
basicContext.show(items, e, contextMenu.close);
}
contextMenu.photoMulti = function(photoIDs, e) {
// Notice for 'Move All':
// fn must call basicContext.close() first,
// in order to keep the selection and multiselect
multiselect.stopResize();
var items = [
{ type: 'item', title: 'Star All', icon: 'icon-star', fn: function() { photo.setStar(photoIDs) } },
{ type: 'item', title: 'Tag All', icon: 'icon-tags', fn: function() { photo.editTags(photoIDs) } },
{ type: 'separator' },
{ type: 'item', title: 'Rename All', icon: 'icon-edit', fn: function() { photo.setTitle(photoIDs) } },
{ type: 'item', title: 'Duplicate All', icon: 'icon-copy', fn: function() { photo.duplicate(photoIDs) } },
{ type: 'item', title: 'Move All', icon: 'icon-folder-open', fn: function() { basicContext.close(); contextMenu.move(photoIDs, e); } },
{ type: 'item', title: 'Delete All', icon: 'icon-trash', fn: function() { photo.delete(photoIDs) } }
];
basicContext.show(items, e, contextMenu.close);
}
contextMenu.photoMore = function(photoID, e) {
var items = [
{ type: 'item', title: 'Full Photo', icon: 'icon-resize-full', fn: function() { window.open(photo.getDirectLink()) } },
{ type: 'item', title: 'Download', icon: 'icon-circle-arrow-down', fn: function() { photo.getArchive(photoID) } }
];
// Remove download-item when
// 1) In public mode
// 2) Downloadable not 1 or not found
if (!(album.json&&album.json.downloadable&&album.json.downloadable==='1')&&
lychee.publicMode===true) items.splice(1, 1);
basicContext.show(items, e);
}
contextMenu.move = function(photoIDs, e) {
var items = [];
// Show Unsorted when unsorted is not the current album
if (album.getID()!=='0') {
items = [
{ type: 'item', title: 'Unsorted', fn: function() { photo.setAlbum(photoIDs, 0) } },
{ type: 'separator' }
];
}
lychee.api('getAlbums', function(data) {
if (data.num===0) {
// Show only 'Add album' when no album available
items = [
{ type: 'item', title: 'New Album', fn: album.add }
];
} else {
// Generate list of albums
$.each(data.content, function(index) {
var that = this;
if (!that.thumb0) that.thumb0 = 'src/images/no_cover.svg';
that.title = "<img class='albumCover' width='16' height='16' src='" + that.thumb0 + "'><div class='albumTitle'>" + that.title + "</div>";
if (that.id!=album.getID()) items.push({ type: 'item', title: that.title, fn: function() { photo.setAlbum(photoIDs, that.id) } });
});
}
basicContext.show(items, e, contextMenu.close);
});
}
contextMenu.sharePhoto = function(photoID, e) {
var link = photo.getViewLink(photoID);
if (photo.json.public==='2') link = location.href;
var items = [
{ type: 'item', title: '<input readonly id="link" value="' + link + '">', fn: function() {}, class: 'noHover' },
{ type: 'separator' },
{ type: 'item', title: 'Make Private', icon: 'icon-eye-close', fn: function() { photo.setPublic(photoID) } },
{ type: 'separator' },
{ type: 'item', title: 'Twitter', icon: 'icon-twitter', fn: function() { photo.share(photoID, 0) } },
{ type: 'item', title: 'Facebook', icon: 'icon-facebook', fn: function() { photo.share(photoID, 1) } },
{ type: 'item', title: 'Mail', icon: 'icon-envelope', fn: function() { photo.share(photoID, 2) } },
{ type: 'item', title: 'Dropbox', icon: 'icon-hdd', fn: function() { photo.share(photoID, 3) } },
{ type: 'item', title: 'Direct Link', icon: 'icon-link', fn: function() { window.open(photo.getDirectLink()) } }
];
basicContext.show(items, e);
$('.basicContext input#link').focus().select();
}
contextMenu.shareAlbum = function(albumID, e) {
var items = [
{ type: 'item', title: '<input readonly id="link" value="' + location.href + '">', fn: function() {}, class: 'noHover' },
{ type: 'separator' },
{ type: 'item', title: 'Make Private', icon: 'icon-eye-close', fn: function() { album.setPublic(albumID) } },
{ type: 'separator' },
{ type: 'item', title: 'Twitter', icon: 'icon-twitter', fn: function() { album.share(0) } },
{ type: 'item', title: 'Facebook', icon: 'icon-facebook', fn: function() { album.share(1) } },
{ type: 'item', title: 'Mail', icon: 'icon-envelope', fn: function() { album.share(2) } }
];
basicContext.show(items, e);
$('.basicContext input#link').focus().select();
}
contextMenu.close = function() {
if (!visible.contextMenu()) return false;
basicContext.close();
$('.photo.active, .album.active').removeClass('active');
if (visible.multiselect()) multiselect.close();
}

@ -0,0 +1,198 @@
/**
* @description This module is used for bindings.
* @copyright 2014 by Tobias Reich
*/
$(document).ready(function() {
/* Event Name */
var event_name = (mobileBrowser()) ? 'touchend' : 'click';
/* Disable ContextMenu */
$(document).bind('contextmenu', function(e) { e.preventDefault() });
/* Multiselect */
$('#content') .on('mousedown', multiselect.show);
$(document) .on('mouseup', multiselect.getSelection);
/* Header */
$('#button_share').on(event_name, function(e) {
if (photo.json.public==1||photo.json.public==2) contextMenu.sharePhoto(photo.getID(), e);
else photo.setPublic(photo.getID(), e);
});
$('#button_share_album').on(event_name, function(e) {
if (album.json.public==1) contextMenu.shareAlbum(album.getID(), e);
else album.setPublic(album.getID(), e);
});
$('#button_signin') .on(event_name, lychee.loginDialog);
$('#button_settings') .on(event_name, contextMenu.settings);
$('#button_info_album') .on(event_name, view.infobox.show);
$('#button_info') .on(event_name, view.infobox.show);
$('#button_more') .on(event_name, function(e) { contextMenu.photoMore(photo.getID(), e) });
$('#button_move') .on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
$('#hostedwith') .on(event_name, function() { window.open(lychee.website) });
$('#button_trash_album') .on(event_name, function() { album.delete([album.getID()]) });
$('#button_trash') .on(event_name, function() { photo.delete([photo.getID()]) });
$('#button_archive') .on(event_name, function() { album.getArchive(album.getID()) });
$('#button_star') .on(event_name, function() { photo.setStar([photo.getID()]) });
$('#button_back_home') .on(event_name, function() { lychee.goto('') });
$('#button_back') .on(event_name, function() { lychee.goto(album.getID()) });
/* Search */
$('#search').on('keyup click', function() { search.find($(this).val()) });
/* Clear Search */
$('#clearSearch').on(event_name, function () {
$('#search').focus();
search.reset();
});
/* Image View */
lychee.imageview
.on(event_name, '.arrow_wrapper.previous', photo.previous)
.on(event_name, '.arrow_wrapper.next', photo.next);
/* Infobox */
$('#infobox')
.on(event_name, '.header a', view.infobox.hide)
.on(event_name, '#edit_title_album', function() { album.setTitle([album.getID()]) })
.on(event_name, '#edit_description_album', function() { album.setDescription(album.getID()) })
.on(event_name, '#edit_title', function() { photo.setTitle([photo.getID()]) })
.on(event_name, '#edit_description', function() { photo.setDescription(photo.getID()) })
.on(event_name, '#edit_tags', function() { photo.editTags([photo.getID()]) })
.on(event_name, '#tags .tag span', function() { photo.deleteTag(photo.getID(), $(this).data('index')) });
/* Keyboard */
Mousetrap
.bind('left', function() { if (visible.photo()) $('#imageview a#previous').click() })
.bind('right', function() { if (visible.photo()) $('#imageview a#next').click() })
.bind(['u', 'ctrl+u'], function() { $('#upload_files').click() })
.bind(['s', 'ctrl+s', 'f', 'ctrl+f'], function(e) {
if (visible.photo()) {
$('#button_star').click();
} else if (visible.albums()) {
e.preventDefault();
$('#search').focus();
}
})
.bind(['r', 'ctrl+r'], function(e) {
e.preventDefault();
if (visible.album()) album.setTitle(album.getID());
else if (visible.photo()) photo.setTitle([photo.getID()]);
})
.bind(['d', 'ctrl+d'], function(e) {
e.preventDefault();
if (visible.photo()) photo.setDescription(photo.getID());
else if (visible.album()) album.setDescription(album.getID());
})
.bind(['t', 'ctrl+t'], function(e) {
if (visible.photo()) {
e.preventDefault();
photo.editTags([photo.getID()]);
}
})
.bind(['i', 'ctrl+i'], function() {
if (visible.infobox()) view.infobox.hide();
else if (visible.multiselect()) return false;
else if (visible.infoboxbutton()) view.infobox.show();
})
.bind(['command+backspace', 'ctrl+backspace'], function() {
if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]);
else if (visible.album()&&!visible.message()) album.delete([album.getID()]);
})
.bind(['command+a', 'ctrl+a'], function() {
if (visible.album()&&!visible.message()) multiselect.selectAll();
else if (visible.albums()&&!visible.message()) multiselect.selectAll();
});
Mousetrap.bindGlobal('enter', function() {
if ($('.message .button.active').length) $('.message .button.active').addClass('pressed').click()
});
Mousetrap.bindGlobal(['esc', 'command+up'], function(e) {
e.preventDefault();
if (visible.message()&&$('.message .close').length>0) modal.close();
else if (visible.contextMenu()) contextMenu.close();
else if (visible.infobox()) view.infobox.hide();
else if (visible.photo()) lychee.goto(album.getID());
else if (visible.album()) lychee.goto('');
else if (visible.albums()&&$('#search').val().length!==0) search.reset();
});
if (mobileBrowser()) {
$(document)
/* Fullscreen on mobile */
.on('touchend', '#image', function(e) {
if (swipe.obj===null||(swipe.offset>=-5&&swipe.offset<=5)) {
if (visible.controls()) view.header.hide(e, 0);
else view.header.show();
}
})
/* Swipe on mobile */
.swipe().on('swipeStart', function() { if (visible.photo()) swipe.start($('#image')) })
.swipe().on('swipeMove', function(e) { if (visible.photo()) swipe.move(e.swipe) })
.swipe().on('swipeEnd', function(e) { if (visible.photo()) swipe.stop(e.swipe, photo.previous, photo.next) });
}
/* Document */
$(document)
/* Login */
.on('keyup', '#password', function() { if ($(this).val().length>0) $(this).removeClass('error') })
/* Header */
.on(event_name, '#title.editable', function() {
if (visible.photo()) photo.setTitle([photo.getID()]);
else album.setTitle([album.getID()]);
})
/* Navigation */
.on('click', '.album', function() { lychee.goto($(this).attr('data-id')) })
.on('click', '.photo', function() { lychee.goto(album.getID() + '/' + $(this).attr('data-id')) })
/* Modal */
.on(event_name, '.message .close', modal.close)
.on(event_name, '.message .button:first', function() { if (modal.fns!==null) modal.fns[0](); if (!visible.signin()) modal.close() })
.on(event_name, '.message .button:last', function() { if (modal.fns!==null) modal.fns[1](); if (!visible.signin()) modal.close() })
/* Add Dialog */
.on(event_name, '.button_add', contextMenu.add)
/* Context Menu */
.on('contextmenu', '.photo', function(e) { contextMenu.photo(photo.getID(), e) })
.on('contextmenu', '.album', function(e) { contextMenu.album(album.getID(), e) })
.on(event_name, '.contextmenu_bg', contextMenu.close)
/* Infobox */
.on(event_name, '#infobox_overlay', view.infobox.hide)
/* Upload */
.on('change', '#upload_files', function() { modal.close(); upload.start.local(this.files) })
.on(event_name, '.upload_message a.close', upload.close)
.on('dragover', function(e) { e.preventDefault(); }, false)
.on('drop', function(e) {
e.stopPropagation();
e.preventDefault();
// Close open overlays or views which are correlating with the upload
if (visible.photo()) lychee.goto(album.getID());
if (visible.contextMenu()) contextMenu.close();
// Detect if dropped item is a file or a link
if (e.originalEvent.dataTransfer.files.length>0) upload.start.local(e.originalEvent.dataTransfer.files);
else if (e.originalEvent.dataTransfer.getData('Text').length>3) upload.start.url(e.originalEvent.dataTransfer.getData('Text'));
return true;
});
/* Init */
lychee.init();
});

@ -0,0 +1,89 @@
/**
* @description This module is used to show and hide the loading bar.
* @copyright 2014 by Tobias Reich
*/
loadingBar = {
status: null
}
loadingBar.show = function(status, errorText) {
if (status==='error') {
// Set status
loadingBar.status = 'error';
// Parse text
if (errorText) errorText = errorText.replace('<br>', '');
if (!errorText) errorText = 'Whoops, it looks like something went wrong. Please reload the site and try again!';
// Move header down
if (visible.controls()) lychee.header.addClass('error');
// Modify loading
lychee.loadingBar
.removeClass('loading uploading error')
.addClass(status)
.html('<h1>Error: <span>' + errorText + '</span></h1>')
.show()
.css('height', '40px');
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() { loadingBar.hide(true) }, 3000));
return true;
}
if (loadingBar.status===null) {
// Set status
loadingBar.status = 'loading';
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() {
// Move header down
if (visible.controls()) lychee.header.addClass('loading');
// Modify loading
lychee.loadingBar
.removeClass('loading uploading error')
.addClass('loading')
.show();
}, 1000));
return true;
}
}
loadingBar.hide = function(force) {
if ((loadingBar.status!=='error'&&loadingBar.status!==null)||force) {
// Remove status
loadingBar.status = null;
// Move header up
if (visible.controls()) lychee.header.removeClass('error loading');
// Modify loading
lychee.loadingBar
.html('')
.css('height', '3px');
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
setTimeout(function() { lychee.loadingBar.hide() }, 300);
}
}

@ -0,0 +1,392 @@
/**
* @description This module provides the basic functions of Lychee.
* @copyright 2014 by Tobias Reich
*/
lychee = {
title: '',
version: '2.7.0',
version_code: '020700',
api_path: 'php/api.php',
update_path: 'http://lychee.electerious.com/version/index.php',
updateURL: 'https://github.com/electerious/Lychee',
website: 'http://lychee.electerious.com',
publicMode: false,
viewMode: false,
debugMode: false,
checkForUpdates:false,
username: '',
sorting: '',
location: '',
dropbox: false,
dropboxKey: '',
loadingBar: $('#loading'),
header: $('header'),
content: $('#content'),
imageview: $('#imageview'),
infobox: $('#infobox')
}
lychee.init = function() {
var params;
params = 'init&version=' + lychee.version_code;
lychee.api(params, function(data) {
if (data.loggedIn!==true) {
lychee.setMode('public');
} else {
lychee.username = data.config.username || '';
lychee.sorting = data.config.sorting || '';
lychee.dropboxKey = data.config.dropboxKey || '';
lychee.location = data.config.location || '';
}
// No configuration
if (data==='Warning: No configuration!') {
lychee.header.hide();
lychee.content.hide();
$('body').append(build.no_content('cog'));
settings.createConfig();
return true;
}
// No login
if (data.config.login===false) {
settings.createLogin();
}
lychee.checkForUpdates = data.config.checkForUpdates;
$(window).bind('popstate', lychee.load);
lychee.load();
});
}
lychee.api = function(params, callback) {
loadingBar.show();
$.ajax({
type: 'POST',
url: lychee.api_path,
data: 'function=' + params,
dataType: 'text',
success: function(data) {
setTimeout(function() { loadingBar.hide() }, 100);
// Catch errors
if (typeof data==='string'&&
data.substring(0, 7)==='Error: ') {
lychee.error(data.substring(7, data.length), params, data);
upload.close(true);
return false;
}
// Convert 1 to true and an empty string to false
if (data==='1') data = true;
else if (data==='') 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);
// Output response when debug mode is enabled
if (lychee.debugMode) console.log(data);
callback(data);
},
error: function(jqXHR, textStatus, errorThrown) {
lychee.error('Server error or API not found.', params, errorThrown);
upload.close(true);
}
});
}
lychee.login = function() {
var user = $('input#username').val(),
password = md5($('input#password').val()),
params;
params = 'login&user=' + user + '&password=' + password;
lychee.api(params, function(data) {
if (data===true) {
// Use 'try' to catch a thrown error when Safari is in private mode
try { localStorage.setItem('lychee_username', user); }
catch (err) {}
window.location.reload();
} else {
// Show error and reactive button
$('#password').val('').addClass('error').focus();
$('.message .button.active').removeClass('pressed');
}
});
}
lychee.loginDialog = function() {
var local_username;
$('body').append(build.signInModal());
$('#username').focus();
if (localStorage) {
local_username = localStorage.getItem('lychee_username');
if (local_username!==null) {
if (local_username.length>0) $('#username').val(local_username);
$('#password').focus();
}
}
if (lychee.checkForUpdates==='1') lychee.getUpdate();
}
lychee.logout = function() {
lychee.api('logout', function() {
window.location.reload();
});
}
lychee.goto = function(url) {
if (url===undefined) url = '#';
else url = '#' + url;
history.pushState(null, null, url);
lychee.load();
}
lychee.load = function() {
var albumID = '',
photoID = '',
hash = document.location.hash.replace('#', '').split('/');
$('.no_content').remove();
contextMenu.close();
multiselect.close();
if (hash[0]!==undefined) albumID = hash[0];
if (hash[1]!==undefined) photoID = hash[1];
if (albumID&&photoID) {
// Trash data
photo.json = null;
// Show Photo
if (lychee.content.html()===''||
($('#search').length&&$('#search').val().length!==0)) {
lychee.content.hide();
album.load(albumID, true);
}
photo.load(photoID, albumID);
} else if (albumID) {
// Trash data
photo.json = null;
// Show Album
if (visible.photo()) view.photo.hide();
if (album.json&&albumID==album.json.id) view.album.title();
else album.load(albumID);
} else {
// Trash albums.json when filled with search results
if (search.code!=='') {
albums.json = null;
search.code = '';
}
// Trash data
album.json = null;
photo.json = null;
// Show Albums
if (visible.album()) view.album.hide();
if (visible.photo()) view.photo.hide();
albums.load();
}
}
lychee.getUpdate = function() {
$.ajax({
url: lychee.update_path,
success: function(data) { if (parseInt(data)>parseInt(lychee.version_code)) $('#version span').show(); }
});
}
lychee.setTitle = function(title, editable) {
if (lychee.title==='') lychee.title = document.title;
if (title==='Albums') document.title = lychee.title;
else document.title = lychee.title + ' - ' + title;
if (editable) $('#title').addClass('editable');
else $('#title').removeClass('editable');
$('#title').html(title);
}
lychee.setMode = function(mode) {
$('#button_settings, #button_settings, #button_search, #search, #button_trash_album, #button_share_album, .button_add, .button_divider').remove();
$('#button_trash, #button_move, #button_share, #button_star').remove();
$(document)
.on('mouseenter', '#title.editable', function() { $(this).removeClass('editable') })
.off('click', '#title.editable')
.off('touchend', '#title.editable')
.off('contextmenu', '.photo')
.off('contextmenu', '.album')
.off('drop');
Mousetrap
.unbind(['u', 'ctrl+u'])
.unbind(['s', 'ctrl+s'])
.unbind(['f', 'ctrl+f'])
.unbind(['r', 'ctrl+r'])
.unbind(['d', 'ctrl+d'])
.unbind(['t', 'ctrl+t'])
.unbind(['command+backspace', 'ctrl+backspace'])
.unbind(['command+a', 'ctrl+a']);
if (mode==='public') {
$('header #button_signin, header #hostedwith').show();
lychee.publicMode = true;
} else if (mode==='view') {
Mousetrap.unbind('esc');
$('#button_back, a#next, a#previous').remove();
$('.no_content').remove();
lychee.publicMode = true;
lychee.viewMode = true;
}
}
lychee.animate = function(obj, animation) {
var animations = [
['fadeIn', 'fadeOut'],
['contentZoomIn', 'contentZoomOut']
];
if (!obj.jQuery) obj = $(obj);
for (var i = 0; i < animations.length; i++) {
for (var x = 0; x < animations[i].length; x++) {
if (animations[i][x]==animation) {
obj.removeClass(animations[i][0] + ' ' + animations[i][1]).addClass(animation);
return true;
}
}
}
return false;
}
lychee.escapeHTML = function(s) {
return s.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
lychee.loadDropbox = function(callback) {
if (!lychee.dropbox&&lychee.dropboxKey) {
loadingBar.show();
var g = document.createElement('script'),
s = document.getElementsByTagName('script')[0];
g.src = 'https://www.dropbox.com/static/api/1/dropins.js';
g.id = 'dropboxjs';
g.type = 'text/javascript';
g.async = 'true';
g.setAttribute('data-app-key', lychee.dropboxKey);
g.onload = g.onreadystatechange = function() {
var rs = this.readyState;
if (rs&&rs!=='complete'&&rs!=='loaded') return;
lychee.dropbox = true;
loadingBar.hide();
callback();
};
s.parentNode.insertBefore(g, s);
} else if (lychee.dropbox&&lychee.dropboxKey) {
callback();
} else {
settings.setDropboxKey(callback);
}
}
lychee.removeHTML = function(html) {
var tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText;
}
lychee.error = function(errorThrown, params, data) {
console.error({
description: errorThrown,
params: params,
response: data
});
loadingBar.show('error', errorThrown);
}

@ -0,0 +1,34 @@
/**
* @description Build, show and hide a modal.
* @copyright 2014 by Tobias Reich
*/
modal = {
fns: null
}
modal.show = function(title, text, buttons, marginTop, closeButton) {
if (!buttons) {
buttons = [
['', function() {}],
['', function() {}]
];
}
modal.fns = [buttons[0][1], buttons[1][1]];
$('body').append(build.modal(title, text, buttons, marginTop, closeButton));
$('.message input:first-child').focus().select();
}
modal.close = function() {
modal.fns = null;
$('.message_overlay').removeClass('fadeIn').css('opacity', 0);
setTimeout(function() { $('.message_overlay').remove() }, 300);
}

@ -0,0 +1,206 @@
/**
* @description Select multiple albums or photos.
* @copyright 2014 by Tobias Reich
*/
multiselect = {}
multiselect.position = {
top: null,
right: null,
bottom: null,
left: null
}
multiselect.show = function(e) {
if (mobileBrowser()) return false;
if (lychee.publicMode) return false;
if (visible.search()) return false;
if (visible.infobox()) return false;
if (!visible.albums()&&!visible.album) return false;
if ($('.album:hover, .photo:hover').length!==0) return false;
if (visible.multiselect()) $('#multiselect').remove();
multiselect.position.top = e.pageY;
multiselect.position.right = -1 * (e.pageX - $(document).width());
multiselect.position.bottom = -1 * (multiselect.position.top - $(window).height());
multiselect.position.left = e.pageX;
$('body').append(build.multiselect(multiselect.position.top, multiselect.position.left));
$(document).on('mousemove', multiselect.resize);
}
multiselect.selectAll = function() {
var e,
newWidth,
newHeight;
if (mobileBrowser()) return false;
if (lychee.publicMode) return false;
if (visible.search()) return false;
if (visible.infobox()) return false;
if (!visible.albums()&&!visible.album) return false;
if (visible.multiselect()) $('#multiselect').remove();
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));
newWidth = $(document).width() - multiselect.position.right + 2;
newHeight = $(document).height() - multiselect.position.bottom;
$('#multiselect').css({
width: newWidth,
height: newHeight
});
e = {
pageX: $(document).width() - (multiselect.position.right / 2),
pageY: $(document).height() - multiselect.position.bottom
};
multiselect.getSelection(e);
}
multiselect.resize = function(e) {
var mouse_x = e.pageX,
mouse_y = e.pageY,
newHeight,
newWidth;
if (multiselect.position.top===null||
multiselect.position.right===null||
multiselect.position.bottom===null||
multiselect.position.left===null) return false;
if (mouse_y>=multiselect.position.top) {
// Do not leave the screen
newHeight = mouse_y - multiselect.position.top;
if ((multiselect.position.top+newHeight)>=$(document).height())
newHeight -= (multiselect.position.top + newHeight) - $(document).height() + 2;
$('#multiselect').css({
top: multiselect.position.top,
bottom: 'inherit',
height: newHeight
});
} else {
$('#multiselect').css({
top: 'inherit',
bottom: multiselect.position.bottom,
height: multiselect.position.top - e.pageY
});
}
if (mouse_x>=multiselect.position.left) {
// Do not leave the screen
newWidth = mouse_x - multiselect.position.left;
if ((multiselect.position.left+newWidth)>=$(document).width())
newWidth -= (multiselect.position.left + newWidth) - $(document).width() + 2;
$('#multiselect').css({
right: 'inherit',
left: multiselect.position.left,
width: newWidth
});
} else {
$('#multiselect').css({
right: multiselect.position.right,
left: 'inherit',
width: multiselect.position.left - e.pageX
});
}
}
multiselect.stopResize = function() {
$(document).off('mousemove');
}
multiselect.getSize = function() {
if (!visible.multiselect()) return false;
return {
top: $('#multiselect').offset().top,
left: $('#multiselect').offset().left,
width: parseInt($('#multiselect').css('width').replace('px', '')),
height: parseInt($('#multiselect').css('height').replace('px', ''))
};
}
multiselect.getSelection = function(e) {
var tolerance = 150,
id,
ids = [],
offset,
size = multiselect.getSize();
if (visible.contextMenu()) return false;
if (!visible.multiselect()) return false;
$('.photo, .album').each(function() {
offset = $(this).offset();
if (offset.top>=(size.top-tolerance)&&
offset.left>=(size.left-tolerance)&&
(offset.top+206)<=(size.top+size.height+tolerance)&&
(offset.left+206)<=(size.left+size.width+tolerance)) {
id = $(this).data('id');
if (id!=='0'&&id!==0&&id!=='f'&&id!=='s'&&id!=='r'&&id!==null&&id!==undefined) {
ids.push(id);
$(this).addClass('active');
}
}
});
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.close = function() {
multiselect.stopResize();
multiselect.position.top = null;
multiselect.position.right = null;
multiselect.position.bottom = null;
multiselect.position.left = null;
lychee.animate('#multiselect', 'fadeOut');
setTimeout(function() {
$('#multiselect').remove();
}, 300);
}

@ -0,0 +1,61 @@
/**
* @description Controls the access to password-protected albums and photos.
* @copyright 2014 by Tobias Reich
*/
password = {
value: ''
}
password.get = function(albumID, callback) {
var passwd = $('.message input.text').val(),
params;
if (!lychee.publicMode) callback();
else if (album.json&&album.json.password==false) callback();
else if (albums.json&&albums.json.content[albumID].password==false) callback();
else if (!albums.json&&!album.json) {
// Continue without password
album.json = {password: true};
callback('');
} else if (passwd==undefined) {
// Request password
password.getDialog(albumID, callback);
} else {
// Check password
params = 'checkAlbumAccess&albumID=' + albumID + '&password=' + md5(passwd);
lychee.api(params, function(data) {
if (data===true) {
password.value = md5(passwd);
callback();
} else {
lychee.goto('');
loadingBar.show('error', 'Access denied. Wrong password!');
}
});
}
}
password.getDialog = function(albumID, callback) {
var buttons;
buttons = [
['Enter', function() { password.get(albumID, callback) }],
['Cancel', lychee.goto]
];
modal.show("<a class='icon-lock'></a> Enter Password", "This album is protected by a password. Enter the password below to view the photos of this album: <input class='text' type='password' placeholder='password' value=''>", buttons, -110, false);
}

@ -0,0 +1,625 @@
/**
* @description Takes care of every action a photo can handle and execute.
* @copyright 2014 by Tobias Reich
*/
photo = {
json: null,
cache: null
}
photo.getID = function() {
var id;
if (photo.json) id = photo.json.id;
else id = $('.photo:hover, .photo.active').attr('data-id');
if (id) return id;
else return false;
}
photo.load = function(photoID, albumID) {
var params,
checkPasswd;
params = 'getPhoto&photoID=' + photoID + '&albumID=' + albumID + '&password=' + password.value;
lychee.api(params, function(data) {
if (data==='Warning: Wrong password!') {
checkPasswd = function() {
if (password.value!=='') photo.load(photoID, albumID);
else setTimeout(checkPasswd, 250);
};
checkPasswd();
return false;
}
photo.json = data;
if (!visible.photo()) view.photo.show();
view.photo.init();
lychee.imageview.show();
setTimeout(function() {
lychee.content.show();
//photo.preloadNext(photoID, albumID);
}, 300);
});
}
// Preload the next photo for better response time
photo.preloadNext = function(photoID) {
var nextPhoto,
url;
// Never preload on mobile devices with bare RAM and
// mostly mobile internet
if (mobileBrowser()) return false;
if (album.json &&
album.json.content &&
album.json.content[photoID] &&
album.json.content[photoID].nextPhoto!='') {
nextPhoto = album.json.content[photoID].nextPhoto;
url = album.json.content[nextPhoto].url;
photo.cache = new Image();
photo.cache.src = url;
photo.cache.onload = function() { photo.cache = null };
}
}
photo.parse = function() {
if (!photo.json.title) photo.json.title = 'Untitled';
}
photo.previous = function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].previousPhoto!=='') {
if (animate===true) {
delay = 200;
$('#image').css({
WebkitTransform: 'translateX(100%)',
MozTransform: 'translateX(100%)',
transform: 'translateX(100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + '/' + album.json.content[photo.getID()].previousPhoto)
}, delay);
}
}
photo.next = function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].nextPhoto!=='') {
if (animate===true) {
delay = 200;
$('#image').css({
WebkitTransform: 'translateX(-100%)',
MozTransform: 'translateX(-100%)',
transform: 'translateX(-100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + '/' + album.json.content[photo.getID()].nextPhoto);
}, delay);
}
}
photo.duplicate = function(photoIDs) {
var params;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
albums.refresh();
params = 'duplicatePhoto&photoIDs=' + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
else album.load(album.getID());
});
}
photo.delete = function(photoIDs) {
var params,
buttons,
photoTitle;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
if (photoIDs.length===1) {
// Get title if only one photo is selected
if (visible.photo()) photoTitle = photo.json.title;
else photoTitle = album.json.content[photoIDs].title;
// Fallback for photos without a title
if (photoTitle==='') photoTitle = 'Untitled';
}
buttons = [
['', function() {
var nextPhoto = '',
previousPhoto = '';
photoIDs.forEach(function(id, index, array) {
// Change reference for the next and previous photo
if (album.json.content[id].nextPhoto!==''||album.json.content[id].previousPhoto!=='') {
nextPhoto = album.json.content[id].nextPhoto;
previousPhoto = album.json.content[id].previousPhoto;
album.json.content[previousPhoto].nextPhoto = nextPhoto;
album.json.content[nextPhoto].previousPhoto = previousPhoto;
}
album.json.content[id] = null;
view.album.content.delete(id);
});
albums.refresh();
// Go to next photo if there is a next photo and
// next photo is not the current one. Show album otherwise.
if (visible.photo()&&nextPhoto!==''&&nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto);
else if (!visible.albums()) lychee.goto(album.getID());
params = 'deletePhoto&photoIDs=' + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['', function() {}]
];
if (photoIDs.length===1) {
buttons[0][0] = 'Delete Photo';
buttons[1][0] = 'Keep Photo';
modal.show('Delete Photo', "Are you sure you want to delete the photo '" + photoTitle + "'?<br>This action can't be undone!", buttons);
} else {
buttons[0][0] = 'Delete Photos';
buttons[1][0] = 'Keep Photos';
modal.show('Delete Photos', "Are you sure you want to delete all " + photoIDs.length + " selected photo?<br>This action can't be undone!", buttons);
}
}
photo.setTitle = function(photoIDs) {
var oldTitle = '',
newTitle,
params,
buttons;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
if (photoIDs.length===1) {
// Get old title if only one photo is selected
if (photo.json) oldTitle = photo.json.title;
else if (album.json) oldTitle = album.json.content[photoIDs].title;
oldTitle = oldTitle.replace("'", '&apos;');
}
buttons = [
['Set Title', function() {
// Get input
newTitle = $('.message input.text').val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
if (visible.photo()) {
photo.json.title = (newTitle==='') ? 'Untitled' : newTitle;
view.photo.title();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].title = newTitle;
view.album.content.title(id);
});
params = 'setPhotoTitle&photoIDs=' + photoIDs + '&title=' + escape(encodeURI(newTitle));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
if (photoIDs.length===1) modal.show('Set Title', "Enter a new title for this photo: <input class='text' type='text' maxlength='30' placeholder='Title' value='" + oldTitle + "'>", buttons);
else modal.show('Set Titles', "Enter a title for all " + photoIDs.length + " selected photos: <input class='text' type='text' maxlength='30' placeholder='Title' value=''>", buttons);
}
photo.setAlbum = function(photoIDs, albumID) {
var params,
nextPhoto,
previousPhoto;
if (!photoIDs) return false;
if (visible.photo) lychee.goto(album.getID());
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
photoIDs.forEach(function(id, index, array) {
// Change reference for the next and previous photo
if (album.json.content[id].nextPhoto!==''||album.json.content[id].previousPhoto!=='') {
nextPhoto = album.json.content[id].nextPhoto;
previousPhoto = album.json.content[id].previousPhoto;
album.json.content[previousPhoto].nextPhoto = nextPhoto;
album.json.content[nextPhoto].previousPhoto = previousPhoto;
}
album.json.content[id] = null;
view.album.content.delete(id);
});
albums.refresh();
params = 'setPhotoAlbum&photoIDs=' + photoIDs + '&albumID=' + albumID;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}
photo.setStar = function(photoIDs) {
var params;
if (!photoIDs) return false;
if (visible.photo()) {
photo.json.star = (photo.json.star==0) ? 1 : 0;
view.photo.star();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].star = (album.json.content[id].star==0) ? 1 : 0;
view.album.content.star(id);
});
albums.refresh();
params = 'setPhotoStar&photoIDs=' + photoIDs;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}
photo.setPublic = function(photoID, e) {
var params;
if (photo.json.public==2) {
modal.show('Public Album', "This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.", [['Show Album', function() { lychee.goto(photo.json.original_album) }], ['Close', function() {}]]);
return false;
}
if (visible.photo()) {
photo.json.public = (photo.json.public==0) ? 1 : 0;
view.photo.public();
if (photo.json.public==1) contextMenu.sharePhoto(photoID, e);
}
album.json.content[photoID].public = (album.json.content[photoID].public==0) ? 1 : 0;
view.album.content.public(photoID);
albums.refresh();
params = 'setPhotoPublic&photoID=' + photoID;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}
photo.setDescription = function(photoID) {
var oldDescription = photo.json.description.replace("'", '&apos;'),
description,
params,
buttons;
buttons = [
['Set Description', function() {
// Get input
description = $('.message input.text').val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.photo()) {
photo.json.description = description;
view.photo.description();
}
params = 'setPhotoDescription&photoID=' + photoID + '&description=' + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
modal.show('Set Description', "Enter a description for this photo: <input class='text' type='text' maxlength='800' placeholder='Description' value='" + oldDescription + "'>", buttons);
}
photo.editTags = function(photoIDs) {
var oldTags = '',
tags = '',
buttons;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
// Get tags
if (visible.photo()) oldTags = photo.json.tags;
if (visible.album()&&photoIDs.length===1) oldTags = album.json.content[photoIDs].tags;
if (visible.album()&&photoIDs.length>1) {
var same = true;
photoIDs.forEach(function(id, index, array) {
if(album.json.content[id].tags===album.json.content[photoIDs[0]].tags&&same===true) same = true;
else same = false;
});
if (same) oldTags = album.json.content[photoIDs[0]].tags;
}
// Improve tags
oldTags = oldTags.replace(/,/g, ', ');
buttons = [
['Set Tags', function() {
tags = $('.message input.text').val();
photo.setTags(photoIDs, tags);
}],
['Cancel', function() {}]
];
if (photoIDs.length===1) modal.show('Set Tags', "Enter your tags for this photo. You can add multiple tags by separating them with a comma: <input class='text' type='text' maxlength='800' placeholder='Tags' value='" + oldTags + "'>", buttons);
else modal.show('Set Tags', "Enter your tags for all " + photoIDs.length + " selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: <input class='text' type='text' maxlength='800' placeholder='Tags' value='" + oldTags + "'>", buttons);
}
photo.setTags = function(photoIDs, tags) {
var params;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
// Parse tags
tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',');
tags = tags.replace(/,$|^,|(\ ){0,}$/g, '');
// Remove html from input
tags = lychee.removeHTML(tags);
if (visible.photo()) {
photo.json.tags = tags;
view.photo.tags();
}
photoIDs.forEach(function(id, index, array) {
album.json.content[id].tags = tags;
});
params = 'setPhotoTags&photoIDs=' + photoIDs + '&tags=' + tags;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}
photo.deleteTag = function(photoID, index) {
var tags;
// Remove
tags = photo.json.tags.split(',');
tags.splice(index, 1);
// Save
photo.json.tags = tags.toString();
photo.setTags([photoID], photo.json.tags);
}
photo.share = function(photoID, service) {
var link = '',
url = photo.getViewLink(photoID),
filename = 'unknown';
switch (service) {
case 0:
link = 'https://twitter.com/share?url=' + encodeURI(url);
break;
case 1:
link = 'http://www.facebook.com/sharer.php?u=' + encodeURI(url) + '&t=' + encodeURI(photo.json.title);
break;
case 2:
link = 'mailto:?subject=' + encodeURI(photo.json.title) + '&body=' + encodeURI(url);
break;
case 3:
lychee.loadDropbox(function() {
filename = photo.json.title + '.' + photo.getDirectLink().split('.').pop();
Dropbox.save(photo.getDirectLink(), filename);
});
break;
default:
link = '';
break;
}
if (link.length>5) location.href = link;
}
photo.getSize = function() {
// Size can be 'big', 'medium' or 'small'
// Default is big
// Small is centered in the middle of the screen
var size = 'big',
scaled = false,
hasMedium = photo.json.medium!=='',
pixelRatio = window.devicePixelRatio,
view = {
width: $(window).width()-60,
height: $(window).height()-100
};
// Detect if the photo will be shown scaled,
// because the screen size is smaller than the photo
if (photo.json.width>view.width||
photo.json.width>view.height) scaled = true;
// Calculate pixel ratio of screen
if (pixelRatio!==undefined&&pixelRatio>1) {
view.width = view.width * pixelRatio;
view.height = view.height * pixelRatio;
}
// Medium available and
// Medium still bigger than screen
if (hasMedium===true&&
(1920>view.width&&1080>view.height)) size = 'medium';
// Photo not scaled
// Photo smaller then screen
if (scaled===false&&
(photo.json.width<view.width&&
photo.json.width<view.height)) size = 'small';
return size;
}
photo.getArchive = function(photoID) {
var link,
url = 'php/api.php?function=getPhotoArchive&photoID=' + photoID;
if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url);
else link = location.href.replace(location.hash, '') + url;
if (lychee.publicMode) link += '&password=' + password.value;
location.href = link;
}
photo.getDirectLink = function() {
var url = '';
if (photo.json&&
photo.json.url&&
photo.json.url!=='') url = photo.json.url;
return url;
}
photo.getViewLink = function(photoID) {
var url = 'view.php?p=' + photoID;
if (location.href.indexOf('index.html')>0) return location.href.replace('index.html' + location.hash, url);
else return location.href.replace(location.hash, url);
}

@ -0,0 +1,104 @@
/**
* @description Searches through your photos and albums.
* @copyright 2014 by Tobias Reich
*/
search = {
code: null
}
search.find = function(term) {
var params,
albumsData = '',
photosData = '',
code;
clearTimeout($(window).data('timeout'));
$(window).data('timeout', setTimeout(function() {
if ($('#search').val().length!==0) {
params = 'search&term=' + term;
lychee.api(params, function(data) {
// Build albums
if (data&&data.albums) {
albums.json = { content: data.albums };
$.each(albums.json.content, function() {
albums.parse(this);
albumsData += build.album(this);
});
}
// Build photos
if (data&&data.photos) {
album.json = { content: data.photos };
$.each(album.json.content, function() {
photosData += build.photo(this);
});
}
// 1. No albums and photos found
// 2. Only photos found
// 3. Only albums found
// 4. Albums and photos found
if (albumsData===''&&photosData==='') code = 'error';
else if (albumsData==='') code = build.divider('Photos') + photosData;
else if (photosData==='') code = build.divider('Albums') + albumsData;
else code = build.divider('Photos') + photosData + build.divider('Albums') + albumsData;
// Only refresh view when search results are different
if (search.code!==md5(code)) {
$('.no_content').remove();
lychee.animate('.album, .photo', 'contentZoomOut');
lychee.animate('.divider', 'fadeOut');
search.code = md5(code);
setTimeout(function() {
if (code==='error') {
lychee.content.html('');
$('body').append(build.no_content('search'));
} else {
lychee.content.html(code);
lychee.animate('.album, .photo', 'contentZoomIn');
$('img[data-type!="svg"]').retina();
}
}, 300);
}
});
} else search.reset();
}, 250));
}
search.reset = function() {
$('#search').val('');
$('.no_content').remove();
if (search.code!=='') {
// Trash data
albums.json = null;
album.json = null;
photo.json = null;
search.code = '';
lychee.animate('.divider', 'fadeOut');
albums.load();
}
}

@ -0,0 +1,289 @@
/**
* @description Lets you change settings.
* @copyright 2014 by Tobias Reich
*/
settings = {}
settings.createConfig = function() {
var dbName,
dbUser,
dbPassword,
dbHost,
dbTablePrefix,
buttons,
params;
buttons = [
['Connect', function() {
dbHost = $('.message input.text#dbHost').val();
dbUser = $('.message input.text#dbUser').val();
dbPassword = $('.message input.text#dbPassword').val();
dbName = $('.message input.text#dbName').val();
dbTablePrefix = $('.message input.text#dbTablePrefix').val();
if (dbHost.length<1) dbHost = 'localhost';
if (dbName.length<1) dbName = 'lychee';
params = 'dbCreateConfig&dbName=' + escape(dbName) + '&dbUser=' + escape(dbUser) + '&dbPassword=' + escape(dbPassword) + '&dbHost=' + escape(dbHost) + '&dbTablePrefix=' + escape(dbTablePrefix);
lychee.api(params, function(data) {
if (data!==true) {
// Configuration failed
setTimeout(function() {
// Connection failed
if (data.indexOf('Warning: Connection failed!')!==-1) {
buttons = [
['Retry', function() { setTimeout(settings.createConfig, 400) }],
['', function() {}]
];
modal.show('Connection Failed', '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.', buttons, null, false);
return false;
}
// Creation failed
if (data.indexOf('Warning: Creation failed!')!==-1) {
buttons = [
['Retry', function() { setTimeout(settings.createConfig, 400) }],
['', function() {}]
];
modal.show('Creation Failed', '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.', buttons, null, false);
return false;
}
// Could not create file
if (data.indexOf('Warning: Could not create file!')!==-1) {
buttons = [
['Retry', function() { setTimeout(settings.createConfig, 400) }],
['', function() {}]
];
modal.show('Saving Failed', "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 the readme for more information.", buttons, null, false);
return false;
}
// Something went wrong
buttons = [
['Retry', function() { setTimeout(settings.createConfig, 400) }],
['', function() {}]
];
modal.show('Configuration Failed', 'Something unexpected happened. Please try again and check your installation and server. Take a look the readme for more information.', buttons, null, false);
return false;
}, 400);
} else {
// Configuration successful
window.location.reload();
}
});
}],
['', function() {}]
];
modal.show('Configuration', "Enter your database connection details below: <input id='dbHost' class='text less' type='text' placeholder='Database Host (optional)' value=''><input id='dbUser' class='text less' type='text' placeholder='Database Username' value=''><input id='dbPassword' class='text more' type='password' placeholder='Database Password' value=''><br>Lychee will create its own database. If required, you can enter the name of an existing database instead:<input id='dbName' class='text less' type='text' placeholder='Database Name (optional)' value=''><input id='dbTablePrefix' class='text more' type='text' placeholder='Table prefix (optional)' value=''>", buttons, -235, false);
}
settings.createLogin = function() {
var username,
password,
params,
buttons;
buttons = [
['Create Login', function() {
username = $('.message input.text#username').val();
password = $('.message input.text#password').val();
if (username.length<1||password.length<1) {
setTimeout(function() {
buttons = [
['Retry', function() { setTimeout(settings.createLogin, 400) }],
['', function() {}]
];
modal.show('Wrong Input', 'The username or password you entered is not long enough. Please try again with another username and password!', buttons, null, false);
return false;
}, 400);
} else {
params = 'setLogin&username=' + escape(username) + '&password=' + md5(password);
lychee.api(params, function(data) {
if (data!==true) {
setTimeout(function() {
buttons = [
['Retry', function() { setTimeout(settings.createLogin, 400) }],
['', function() {}]
];
modal.show('Creation Failed', 'Unable to save login. Please try again with another username and password!', buttons, null, false);
return false;
}, 400);
}
});
}
}],
['', function() {}]
];
modal.show('Create Login', "Enter a username and password for your installation: <input id='username' class='text less' type='text' placeholder='New Username' value=''><input id='password' class='text' type='password' placeholder='New Password' value=''>", buttons, -122, false);
}
settings.setLogin = function() {
var old_password,
username,
password,
params,
buttons;
buttons = [
['Change Login', function() {
old_password = $('.message input.text#old_password').val();
username = $('.message input.text#username').val();
password = $('.message input.text#password').val();
if (old_password.length<1) {
loadingBar.show('error', 'Your old password was entered incorrectly. Please try again!');
return false;
}
if (username.length<1) {
loadingBar.show('error', 'Your new username was entered incorrectly. Please try again!');
return false;
}
if (password.length<1) {
loadingBar.show('error', 'Your new password was entered incorrectly. Please try again!');
return false;
}
params = 'setLogin&oldPassword=' + md5(old_password) + '&username=' + escape(username) + '&password=' + md5(password);
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
modal.show('Change Login', "Enter your current password: <input id='old_password' class='text more' type='password' placeholder='Current Password' value=''><br>Your username and password will be changed to the following: <input id='username' class='text less' type='text' placeholder='New Username' value=''><input id='password' class='text' type='password' placeholder='New Password' value=''>", buttons, -171);
}
settings.setSorting = function() {
var buttons,
sorting,
params;
buttons = [
['Change Sorting', function() {
sorting[0] = $('select#settings_type').val();
sorting[1] = $('select#settings_order').val();
albums.refresh();
params = 'setSorting&type=' + sorting[0] + '&order=' + sorting[1];
lychee.api(params, function(data) {
if (data===true) {
lychee.sorting = 'ORDER BY ' + sorting[0] + ' ' + sorting[1];
lychee.load();
} else lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
modal.show('Change Sorting',
"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> \
in an \
<select id='settings_order'> \
<option value='ASC'>Ascending</option> \
<option value='DESC'>Descending</option> \
</select> \
order.\
", buttons);
if (lychee.sorting!=='') {
sorting = lychee.sorting.replace('ORDER BY ', '').split(' ');
$('select#settings_type').val(sorting[0]);
$('select#settings_order').val(sorting[1]);
}
}
settings.setDropboxKey = function(callback) {
var buttons,
params,
key;
buttons = [
['Set Key', function() {
key = $('.message input.text#key').val();
params = 'setDropboxKey&key=' + key;
lychee.api(params, function(data) {
if (data===true) {
lychee.dropboxKey = key;
if (callback) lychee.loadDropbox(callback);
} else lychee.error(null, params, data);
});
}],
['Cancel', function() {}]
];
modal.show('Set Dropbox Key', "In order to import photos from your Dropbox, you need a valid drop-ins app key from <a href='https://www.dropbox.com/developers/apps/create'>their website</a>. Generate yourself a personal key and enter it below: <input id='key' class='text' type='text' placeholder='Dropbox API Key' value='" + lychee.dropboxKey + "'>", buttons);
}

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

Loading…
Cancel
Save