9 Commits
0.4 ... 0.5

Author SHA1 Message Date
Luc Didry
23c130750e Improve Twitter animated card (gif) (#45) 2014-09-25 01:41:42 +02:00
Luc Didry
a0b507e48e Fixes #45 + Update README.md and Changelog 2014-09-24 01:41:36 +02:00
Luc Didry
9692504096 Fix broken icon class 2014-09-12 14:29:42 +02:00
Luc Didry
8386a2f4d2 Add license mention in manifest.webapp + manifest.webapp version 2014-09-12 14:28:56 +02:00
Luc Didry
bef9dfaca3 Update description in manifest.webapp 2014-08-05 23:42:08 +02:00
Luc Didry
b71c25bf54 Close #42 2014-08-05 23:00:50 +02:00
Luc Didry
b0e5f771f2 Add Kharec as contributor 2014-07-21 16:48:12 +02:00
Sandro CAZZANIGA
2b361fce26 Fix pod description 2014-07-21 16:47:31 +02:00
Luc Didry
64e0db48f2 Change URL of official instance to the secured URL 2014-07-19 11:19:53 +02:00
13 changed files with 485 additions and 23 deletions

View File

@@ -1,5 +1,10 @@
Revision history for Lutim
0.5 2014-09-24
- Add support for animated gif in Twitter cards (#45)
- Update README.md with Twitter integration informations
- bugfixes
0.4 2014-07-12
- Webapp ! Downloadable directly from the Lutim instance
- Configure expiration delay after uploading (#12)

View File

@@ -4,19 +4,20 @@
It means Let's Upload That Image.
## What does it do?
It stores images and allows you to see them, download them or use them in Twitter.
It stores images and allows you to see them, download them or use them in Twitter. From version 0.5, the gif images can be displayed as animated gifs in Twitter, but you need a HTTPS server (Twitter requires that. Lutim detects if you have a HTTPS server and displays an static image twitter card if you don't);
Images are indefinitly stored unless you request that they will be deleted at first view or after 24 hours / one week / one month / one year.
## License
Lutim is licensed under the terms of the AGPL. See the LICENSE file.
## Official instance
You can see it working at http://lut.im.
You can see it working at https://lut.im.
## Logo
Lutim's logo is an adaptation of [Lutin](http://commons.wikimedia.org/wiki/File:Lutin_by_godo.jpg) by [Godo](http://godoillustrateur.wordpress.com/), licensed under the terms of the CC-BY-SA 3.0 license.
![Lutim's logo](http://lut.im/img/Lutim_small.png)
![Lutim's logo](https://lut.im/img/Lutim_small.png)
## Dependencies
* Carton : Perl dependencies manager, it will get what you need, so don't bother for Perl modules dependencies (but you can read the file `cpanfile` if you want).
@@ -34,17 +35,21 @@ sudo apt-get install carton
* But, on another hand, some modules that Carton will install need to be compiled. So you will need some tools:
```shell
sudo apt-get install build-essential libssl-dev
sudo apt-get install build-essential
```
### Thumbnails dependancy
If you want to provide thumbnails of uploaded images, you have to install the *ImageMagick* image manipulation software (<http://www.imagemagick.org/>) and the Image::Magick CPAN module.
### Thumbnails and animated gifs in Twitter dependancy
If you want to provide thumbnails of uploaded images or have animated gifs in Twitter, you have to install the *ImageMagick* image manipulation software (<http://www.imagemagick.org/>) and the Image::Magick CPAN module.
On Debian, you can do:
```shell
sudo apt-get install perlmagick
```
## Twitter integration
If you want to share images of your Lutim instance on Twitter, you have to register your site at <https://cards-dev.twitter.com/validator>.
## Installation
After installing Carton :
```shell
@@ -119,6 +124,17 @@ UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE lutim ( short TEXT PRIMARY KEY, pat
PRAGMA writable_schema = 0;
```
***Warning!!!***
If you want to update to Lutim **0.5**, from a previous version, you'll have to modify the database.
```
sqlite3 lutim.db
PRAGMA writable_schema = 1;
UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE lutim ( short TEXT PRIMARY KEY, path TEXT, footprint TEXT, enabled INTEGER, mediatype TEXT, filename TEXT, counter INTEGER, delete_at_first_view INTEGER, delete_at_day INTEGER, created_at INTEGER, created_by TEXT, last_access_at INTEGER, mod_token TEXT, width INTEGER, height INTEGER)' WHERE NAME = 'lutim';
PRAGMA writable_schema = 0;
```
## Reverse proxy
You can use a reverse proxy like Nginx or Varnish (or Apache with the mod_proxy module). The web is full of tutos.
@@ -272,7 +288,7 @@ vim de.pm
There's just a few sentences, so it will be quick to translate. Please consider to send me you language file in order to help the other users :smile:.
## Others projects dependancies
Lutim is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [morris.js](http://www.oesmith.co.uk/morris.js/) for stats graphs.
Lutim is written in Perl with the [Mojolicious](http://mojolicio.us) framework, uses the [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly, [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity, [Raphaël](http://raphaeljs.com/) and [morris.js](http://www.oesmith.co.uk/morris.js/) for stats graphs and [freezeframe.js](http://freezeframe.chrisantonellis.com/) (slightly modified) to be able to freeze animated gifs in twitter card.
## Main developers
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, [@framasky](https://twitter.com/framasky)
@@ -282,3 +298,4 @@ Lutim is written in Perl with the [Mojolicious](http://mojolicio.us) framework,
* Jean-Bernard Marcon, aka Goofy (<https://github.com/goofy-bz>)
* Jean-Christophe Bach (<https://github.com/jcb>)
* Florian Bigard, aka Chocobozzz (<https://github.com/Chocobozzz>)
* Sandro CAZZANIGA, aka Kharec (<http://sandrocazzaniga.fr>), [@Kharec](https://twitter.com/Kharec)

View File

@@ -293,8 +293,9 @@ sub startup {
$self->asset('stats.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/morris-0.4.3.min.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('about.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('index.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/dmuploader.min.js');
$self->asset('stats.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/raphael-min.js', 'js/morris-0.4.3.min.js', 'js/stats.js');
$self->asset('index.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/dmuploader.min.js');
$self->asset('stats.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/raphael-min.js', 'js/morris-0.4.3.min.js', 'js/stats.js');
$self->asset('freeze.js' => 'js/jquery-2.1.0.min.js', 'js/freezeframe.min.js');
$self->defaults(layout => 'default');

View File

@@ -56,7 +56,7 @@ sub run {
=head1 NAME
Lutim::Command::cron::watch - Delete IP addresses from database after configured delay
Lutim::Command::cron::watch - Watch the files directory and take action when over quota
=head1 SYNOPSIS

View File

@@ -279,9 +279,13 @@ sub add {
my $filename = unidecode($upload->filename);
my $ext = ($filename =~ m/([^.]+)$/)[0];
my $path = 'files/'.$records[0]->short.'.'.$ext;
my ($width, $height);
if ($im_loaded) {
my $im = Image::Magick->new;
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$im->Resize(geometry=>'x85');
$thumb = 'data:'.$mediatype.';base64,';
@@ -301,7 +305,9 @@ sub add {
delete_at_day => ($c->param('delete-day') && $c->param('delete-day') <= $c->max_delay) ? $c->param('delete-day') : $c->max_delay,
delete_at_first_view => ($c->param('first-view')) ? 1 : 0,
created_at => time(),
created_by => $ip
created_by => $ip,
width => $width,
height => $height
);
# Log image creation
@@ -413,11 +419,31 @@ sub short {
$test = 1;
my $short = $images[0]->short;
$short .= '/'.$key if (defined($key));
my ($width, $height) = (340,340);
if ($images[0]->mediatype eq 'image/gif') {
if (defined($images[0]->width) && defined($images[0]->height)) {
($width, $height) = ($images[0]->width, $images[0]->height);
} elsif ($im_loaded) {
my $upload = $c->decrypt($key, $images[0]->path);
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$images[0]->update(
width => $width,
height => $height
);
}
}
return $c->render(
template => 'twitter',
layout => undef,
short => $short,
filename => $images[0]->filename
filename => $images[0]->filename,
mimetype => ($c->req->url->to_abs()->scheme eq 'https') ? $images[0]->mediatype : '',
width => $width,
height => $height
);
} else {
# Delete image if needed

View File

@@ -31,6 +31,7 @@ my $inf_body = <<EOF;
<li>Jean-Bernard Marcon, aka Goofy (<a href="https://github.com/goofy-bz">https://github.com/goofy-bz</a>)</li>
<li>Jean-Christophe Bach (<a href="https://github.com/jcb">https://github.com/jcb</a>)</li>
<li>Florian Bigard, aka Chocobozzz (<a href="https://github.com/Chocobozzz">https://github.com/Chocobozzz</a>)</li>
<li>Sandro CAZZANIGA, aka Kharec (<a href="http://sandrocazzaniga.fr">http://sandrocazzaniga.fr</a>), <a href="https://twitter.com/Kharec">\@Kharec</a></li>
</ul>
EOF

View File

@@ -31,6 +31,7 @@ my $inf_body = <<EOF;
<li>Jean-Bernard Marcon, aka Goofy (<a href="https://github.com/goofy-bz">https://github.com/goofy-bz</a>)</li>
<li>Jean-Christophe Bach (<a href="https://github.com/jcb">https://github.com/jcb</a>)</li>
<li>Florian Bigard, aka Chocobozzz (<a href="https://github.com/Chocobozzz">https://github.com/Chocobozzz</a>)</li>
<li>Sandro CAZZANIGA, aka Kharec (<a href="http://sandrocazzaniga.fr">http://sandrocazzaniga.fr</a>), <a href="https://twitter.com/Kharec">\@Kharec</a></li>
</ul>
EOF

View File

@@ -20,7 +20,9 @@ use ORLite {
created_at INTEGER,
created_by TEXT,
last_access_at INTEGER,
mod_token TEXT)'
mod_token TEXT,
width INTEGER,
height INTEGER)'
);
return 1;
}

390
public/js/freezeframe.js Normal file
View File

@@ -0,0 +1,390 @@
/* -----------------------------------------------------------------------------
* Freezeframe
* freezeframe.js v2.0.2
* 2014 Chris Antonellis
*
* Freezeframe.js is a script that automatically pauses animated GIFs and
* enables them to start animating on mouse hover.
*
* Website: http://freezeframe.chrisantonellis.com/
* Documentation: http://freezeframe.chrisantonellis.com/documentation/
*
* Licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
* http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
* -------------------------------------------------------------------------- */
FreezeFrame = (function($) {
var _ff;
var canvas;
var context;
var class_name;
var trigger_event;
var support_touch_devices;
var animation_play_duration;
var loading_background_color;
var loading_background_image;
var loading_background_position;
var loading_fade_in_speed;
var animation_icon_image;
var animation_icon_position;
var animation_fade_out_speed;
var is_touch_device;
var freezeframe_count;
function FreezeFrame( _options ) {
_ff = this;
_options.class_name == null ?
this.class_name = "freezeframe" :
this.class_name = _options.class_name;
_options.trigger_event == null ?
this.trigger_event = "hover" :
this.trigger_event = _options.trigger_event.toLowerCase();
_options.support_touch_devices == null ?
this.support_touch_devices = true :
this.support_touch_devuces = _options.support_touch_devices.toLowerCase();
_options.animation_play_duration == null ?
this.animation_play_duration = 10000 :
this.animation_play_duration = parseInt(_options.animation_play_duration);
_options.loading_background_color == null ?
this.loading_background_color = "#666" :
this.loading_background_color = _options.loading_background_color.toLowerCase();
_options.loading_background_image == null ?
this.loading_background_image = "data:image/gif;base64,R0lGODlhEAAQAPIAAGZmZv///4mJidbW1v///8PDw7CwsKampiH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" :
this.loading_background_image = _options.loading_background_image;
_options.loading_background_position == null ?
this.loading_background_position = "center center" :
this.loading_background_position = _options.loading_background_position.toLowerCase();
_options.animation_icon_image == null ?
this.animation_icon_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAl9JREFUeNrs2s2K2lAUAGATb3TE2Cr+oaJQNFOh1MVsxK0OuJbSdX2V9gG67GqW3RbaB7B9gy7qgKJF8G/EarHVWGMyJj230EXLLITm6q05F44/IVw5X3JPjiGCZVkuJw/R5fCBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAjwjyMej79MpVKP/hcAwe6boul0+p3P5/vmdruv4evbVqvVdRRALpd7HQqFpn6/XxUEwdI07UZV1ffNZvMzjwDE7gk9Hs8mGAwuYCl8hTNB32630nw+f1IqlRY8QtgOQAjRZVleJxKJ77AcVLptOBwup9PpPR4hbAcQRdGUJOk2HA5riqKokUjEgKK4GY/HKo8QhMGcv4oKIJg0+Xw+r8H7bSaT0XiEICwnhyLoCgQCptfrNSgCjxDkED8ChdGiwSMEOaQ2jxDkGOtuH4jJZHJ/Nps9LRaLi+Vy2WDVUB0FYB+IXq+36ff7P0ajURj2eQbF9EO73W6cFMBdENA8mXQbNFDier0mhULhQaVSeZzNZj+d3Bnwe+i6LkDCAqx9QmMwGJwlk8nzarV6Cf3EDHZ5DvHx5ADuSjwWiz0sl8uX8H9izjLxowLskfgL1okfBYCnxA8KwGPiBwGg9xpWq5XIY+IsAQT6stvt6BGX4Nrt4jFxZgCmaYqGYRDo4nzdbncny3KGx8SZAcCRl6CH9yuKcl6r1S6i0egXHhP/Y53aGfV6/U2n02nA5yuIC7vntzsEBo/KXkG84vaI/12w8Flhhw8EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEcNr4KcAABsB8S2vxAV8AAAAASUVORK5CYII=" :
this.animation_icon_image = _options.animation_icon_image;
_options.animation_icon_position == null ?
this.animation_icon_position = "top left" :
this.animation_icon_position = _options.animation_icon_position.toLowerCase();
_options.loading_fade_in_speed == null ?
this.loading_fade_in_speed = 500 :
this.loading_fade_in_speed = parseInt(_options.loading_fade_in_speed);
_options.animation_fade_out_speed == null ?
this.animation_fade_out_speed = 250 :
this.animation_fade_out_speed = parseInt(_options.animation_fade_out_speed);
this.is_touch_device = isMouseEventSupported("ontouchstart");
this.freezeframe_count = 0;
};
/** --------------------------------------------------------------------------
* @function .setup()
* Sets up the freezeframe instance by adding a canvas element to the
* document and capturing references to the canvas and its 2D context
* ------------------------------------------------------------------------ */
FreezeFrame.prototype.setup = function() {
$("<canvas>",{id:"freezeframe-canvas"})
.css("display", "none")
.prependTo($("body"));
this.canvas = $("canvas#freezeframe-canvas");
this.context = this.canvas[0].getContext('2d');
};
/** --------------------------------------------------------------------------
* .run()
* Starts freezeframe processing for each image. Copies the image to
* the freezeframe canvas and converts it to a data url
* ------------------------------------------------------------------------ */
FreezeFrame.prototype.run = function() {
var images = [];
var ext;
var figure_background = _ff.loading_background_color;
if( _ff.loading_background_image !== false ) {
figure_background += " url('" + _ff.loading_background_image + "') " +
_ff.loading_background_position + " no-repeat";
}
// Select all images with a class matching the option class_name
images = $('img[class*="' + _ff.class_name + '"]')
.not('[class*="' + _ff.class_name + '_done"]');
// Process each image by resetting the animation sequence, copying to the
// canvas, converting to a data url, and attaching that data url to the
// image itself as an attribute
$(images).each(function(index) {
// Change image class so it won't be reprocessed if .run() is run again
$(this).removeClass(_ff.class_name).addClass(_ff.class_name + "_done");
// Set cross-origin to anon to load images from remote services that send
// the correct header. Not working correctly, needs more testing
$(this).crossOrigin = "anonymous";
// Determine file extension
ext = $(this)[0].src.split(".");
ext = ext[ext.length - 1].toLowerCase();
// Remove non GIF files
if(ext !== "gif") {
images.splice(index, 1);
} else {
var freezeframe_figure = $("<figure />")
.attr("class", "freezeframe-container " + _ff.freezeframe_count)
.css({"display": "inline-block",
"overflow": "hidden",
"margin": 0,
"background-size": "100% auto",
"width": "100%",
"max-height": "100%",
"background": figure_background});
$(this).css({"opacity": 0,
"display": "block"})
.wrap(freezeframe_figure);
freezeframe_figure = $(this).parent();
// If an animation icon image is available, attach it
if(_ff.animation_icon_image !== false) {
var animation_icon = $("<div />")
.attr("class", "freezeframe-animation-icon")
.css({"display": "block",
"position": "absolute",
"background": "transparent " + "url('" + _ff.animation_icon_image + "') " +
_ff.animation_icon_position + " no-repeat",
"pointer-events": "none",
"z-index": 100,
"opacity": 0});
$(freezeframe_figure).prepend(animation_icon);
animation_icon = $(this).siblings($(".freezeframe-animation-icon"));
}
// Increment counter so each image gets a unique number
_ff.freezeframe_count++;
// Create a temporary refernce to the image as non-object to pass to .drawImage
var _self = this;
// Using imagesLoaded by Desandro because .load doesn't work on cached images
$(this).imagesLoaded(function() {
$(this).off("imagesLoaded");
_ff.canvas[0].width = $(this)[0].clientWidth;
_ff.canvas[0].height = $(this)[0].clientHeight;
$(this).attr("animated", $(this).attr("src"))
.attr("src", $(this).attr("src"));
_ff.context.drawImage(_self, 0, 0, $(this)[0].clientWidth, $(this)[0].clientHeight);
$(this).attr("src", _ff.canvas[0].toDataURL());
// If an animation icon image is available, show it
if(_ff.animation_icon_image !== false) {
$(animation_icon).css({
"width": parseInt($(this)[0].width),
"height": parseInt($(this)[0].height)})
.animate({"opacity": 1}, _ff.loading_fade_in_speed);
}
// When fade in sequence is complete, enable interaction
$(this).animate({"opacity": 1}, _ff.loading_fade_in_speed, function() {
$(freezeframe_figure).css("background", "url('" + $(this).attr("src") + "')");
$(this).css("opacity", 0)
.attr("src", $(this).attr("animated"));
// Touch Device or Click Event
if((_ff.support_touch_devices && _ff.is_touch_device) || _ff.trigger_event.toLowerCase() == "click") {
var stop_animation;
var animating = false;
$(this).click(function() {
// If not currently animating, start animating
if(!animating) {
$(this).attr("src", $(this).attr("src"))
.css("opacity", 1);
if(_ff.animation_icon_image !== false) {
$(animation_icon).css("opacity", 0);
}
stop_animation = setInterval(function() {
clearInterval(stop_animation);
this.animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
animating = false;
}, _ff.animation_play_duration);
animating = true;
// If currently animating, stop animating
} else {
clearInterval(stop_animation);
$(this).animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
animating = false;
}
});
} else {
// Hover Event
$(this).mouseenter(function() {
$(this).attr("src", $(this).attr("src"))
.css("opacity", 1);
if(_ff.animation_icon_image !== false) {
$(animation_icon).css("opacity", 0);
}
});
$(this).mouseleave(function() {
$(this).animate({"opacity": 0}, _ff.animation_fade_out_speed);
if(_ff.animation_icon_image !== false) {
$(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed);
}
});
}
});
});
}
});
};
return FreezeFrame;
})(jQuery);
/** ----------------------------------------------------------------------------
* Setup & Run Freezeframe
* -------------------------------------------------------------------------- */
jQuery(document).ready(function($) {
typeof(freezeframe_options) == 'undefined' ? freezeframe_options = {} : null;
freezeframe = new FreezeFrame(freezeframe_options);
freezeframe.setup();
freezeframe.run();
});
/** ----------------------------------------------------------------------------
* jQuery imagesLoaded plugin v2.1.1
* http://github.com/desandro/imagesloaded
*
* MIT License. by Paul Irish et al.
* -------------------------------------------------------------------------- */
;(function($, undefined) {
'use strict';
var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
$.fn.imagesLoaded = function( callback ) {
var $this = this,
deferred = $.isFunction($.Deferred) ? $.Deferred() : 0,
hasNotify = $.isFunction(deferred.notify),
$images = $this.find('img').add( $this.filter('img') ),
loaded = [],
proper = [],
broken = [];
if ($.isPlainObject(callback)) {
$.each(callback, function (key, value) {
if (key === 'callback') {
callback = value;
} else if (deferred) {
deferred[key](value);
}
});
}
function doneLoading() {
var $proper = $(proper),
$broken = $(broken);
if ( deferred ) {
if ( broken.length ) {
deferred.reject( $images, $proper, $broken );
} else {
deferred.resolve( $images );
}
}
if ( $.isFunction( callback ) ) {
callback.call( $this, $images, $proper, $broken );
}
}
function imgLoadedHandler( event ) {
imgLoaded( event.target, event.type === 'error' );
}
function imgLoaded( img, isBroken ) {
if ( img.src === BLANK || $.inArray( img, loaded ) !== -1 ) {
return;
}
loaded.push( img );
if ( isBroken ) {
broken.push( img );
} else {
proper.push( img );
}
$.data( img, 'imagesLoaded', { isBroken: isBroken, src: img.src } );
if ( hasNotify ) {
deferred.notifyWith( $(img), [ isBroken, $images, $(proper), $(broken) ] );
}
if ( $images.length === loaded.length ) {
setTimeout( doneLoading );
$images.unbind( '.imagesLoaded', imgLoadedHandler );
}
}
if ( !$images.length ) {
doneLoading();
} else {
$images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoadedHandler )
.each( function( i, el ) {
var src = el.src;
var cached = $.data( el, 'imagesLoaded' );
if ( cached && cached.src === src ) {
imgLoaded( el, cached.isBroken );
return;
}
if ( el.complete && el.naturalWidth !== undefined ) {
imgLoaded( el, el.naturalWidth === 0 || el.naturalHeight === 0 );
return;
}
if ( el.readyState || el.complete ) {
el.src = BLANK;
el.src = src;
}
});
}
return deferred ? deferred.promise( $this ) : $this;
};
})(jQuery);
/** ----------------------------------------------------------------------------
* isMouseEventSupported by kangax
* http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
* -------------------------------------------------------------------------- */
function isMouseEventSupported(eventName) {
var el = document.createElement('div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if (!isSupported) {
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}

1
public/js/freezeframe.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,7 @@
<%=l 'license' %> <%= link_to 'https://www.gnu.org/licenses/agpl-3.0.html' => begin %>AGPL<% end %> — 
<%= link_to url_for('about') => begin %><%=l 'informations' %><% end %> — 
<%= link_to 'https://github.com/ldidry/lutim' => (title => l 'fork-me') => begin %><i class="lead icon icon-github-circled"></i><% end %> 
<%= link_to $twitter_url => (title => l 'share-twitter') => begin %><i class="lead icon icon-touiteur"></i><% end %> 
<%= link_to $twitter_url => (title => l 'share-twitter') => begin %><i class="lead icon icon-twitter"></i><% end %> 
<%= link_to 'https://flattr.com/submit/auto?user_id=_SKy_&url='.$url.'&title=Lutim&category=software' => (title => 'Flattr this') => begin %><i class="lead icon icon-flattr"></i><% end %> 
<%= link_to 'bitcoin:1K3n4MXNRSMHk28oTfXEvDunWFthePvd8v?label=lutim' => (title => 'Give Bitcoins') => begin %><i class="lead icon icon-bitcoin"></i><% end %> 
<a class="btn btn-default btn-xs" href="#" id="install-app"><img src="<%= url_for('/') %>img/rocket.png" alt="mozilla rocket logo"> <%=l 'install_as_webapp' %></a>

View File

@@ -1,6 +1,6 @@
{
"name": "Lutim",
"description": "Lets Upload That Image!",
"description": "Lets Upload That Image!\n\nThis is a simple image sharing app which use <%= url_for('/')->to_abs %> for storing the images. Once you have uploaded an image, you'll be provided differents links:\n a view link, which points directly to the image\n a download link, which force the download of the image\n a twitter link, which you can share on twitter : the image will natively appeared in twitter\n a delete link, which allows you to delete the image anytime you want\n\nThe image is stored on <%= url_for('/')->to_abs %> for a delay which you can define.\n\nThe particularity of Lutim is that the image is encrypted and its usage is as most anonymous as possible. See the information page (<%= url_for('about')->to_abs %>) for more details.\nLicense: AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html)",
"launch_path": "<%= url_for('/') %>",
"icons": {
"32": "<%= url_for('/img/lutim32.png') %>",
@@ -17,7 +17,7 @@
"default_locale": "en",
"locales": {
"fr": {
"description": "Envoyons cette image !"
"description": "Envoyons cette image !\n\nCeci est une application de partage simple d'images qui utilise <%= url_for('/')->to_abs %> pour enregistrer les images. Une fois que vous avez envoyé une image, vous obtiendrez différents liens :\n un lien de visualisation, qui pointe directement sur l'image\n un lien de téléchargement, qui force le téléchargement de l'image\n un lien twitter, que vous pouvez partager sur twitter : l'image apparaîtra nativement dans twitter\n un lien de suppression, qui vous permet de supprimer l'image quand vous le souhaitez\n\nL'image est conservée sur <%= url_for('/')->to_abs %> pour un délai que vous pouvez définir.\n\nLa particularité de Lutim est que l'image est chiffrée et que son usage est aussi anonyme que possible. Voir la page d'information (<%= url_for('about')->to_abs %>) pour plus de détails.\nLicence : AGPLv3 (https://www.gnu.org/licenses/agpl-3.0.html)"
}
},
"activities": {
@@ -29,5 +29,6 @@
"disposition": "window"
}
},
"version": "0.1",
"chrome": { "navigation": true }
}

View File

@@ -1,17 +1,34 @@
% # vim:set sw=4 ts=4 sts=4 ft=html.epl expandtab:
% my $g = ($mimetype eq 'image/gif') ? 1 : 0;
<!DOCTYPE html>
<html style="height:100%;">
<html style="max-height:100%;">
<head>
<title>Lutim</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="<%= url_for('/')->to_abs() %>img/favicon.png">
<meta name="twitter:card" content="photo">
<link rel="icon" type="image/png" href="<%= url_for('index')->to_abs() %>img/favicon.png">
<meta name="twitter:site" content="<%= config('tweet_card_via') %>">
<meta name="twitter:image:src" content="<%= url_for('/')->to_abs().$short %>">
<meta name="twitter:image:src" content="<%= url_for('index')->to_abs().$short %>">
% if ($g) {
<meta name="twitter:card" content="player">
<meta name="twitter:image" content="<%= url_for('index')->to_abs().$short %>">
<meta name="twitter:player" content="<%= url_for('index')->to_abs().$short.'?t' %>">
<meta name="twitter:title" content="<%= $filename %>">
<meta name="twitter:player:width" content="<%= $width %>">
<meta name="twitter:player:height" content="<%= $height %>">
%= asset 'freeze.js'
%= javascript begin
freezeframe_options = {
trigger_event: "click",
animation_play_duration: 60000
}
% end
% } else {
<meta name="twitter:card" content="photo">
% }
</head>
<body style="height: 97%;">
<img style="max-width:100%; max-height:100%;" src="<%= url_for('/').$short %>" alt="<%= $filename %>">
<body<%= ($g) ? '' : ' style="height: 97%;"' %>>
<img<%= ' class="freezeframe"' if ($g) %> style="<%= 'max-' unless ($g) %>width:100%; max-height:100%;" src="<%= url_for('index')->to_abs().$short %><%= '.gif' if ($g) %>" alt="<%= $filename %>">
</body>
</html>