Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23c130750e | ||
|
|
a0b507e48e | ||
|
|
9692504096 | ||
|
|
8386a2f4d2 | ||
|
|
bef9dfaca3 | ||
|
|
b71c25bf54 | ||
|
|
b0e5f771f2 | ||
|
|
2b361fce26 | ||
|
|
64e0db48f2 | ||
|
|
241e8b8ea1 | ||
|
|
e0f13489a4 | ||
|
|
1ca3137dd1 | ||
|
|
b791c609c2 | ||
|
|
388feba7c0 | ||
|
|
542d3efdf5 | ||
|
|
a57815a2ea | ||
|
|
44c0d5a9c9 | ||
|
|
ebb5282579 | ||
|
|
d03ce97c8b | ||
|
|
a4ca866790 | ||
|
|
73b09d28c6 | ||
|
|
5c45be0099 | ||
|
|
150f27bf9b | ||
|
|
d0b388834f | ||
|
|
e951a44324 | ||
|
|
9fcefcdbd4 | ||
|
|
05892366d8 | ||
|
|
989412046b | ||
|
|
f2baf273ca | ||
|
|
420a37ee75 | ||
|
|
940faed363 | ||
|
|
f86b0e71d1 | ||
|
|
7f99d73691 | ||
|
|
4633fdacef | ||
|
|
bf0e2d47b9 | ||
|
|
f044c1e953 | ||
|
|
277053d2a8 | ||
|
|
b450ea7607 | ||
|
|
510bfb3eed |
10
Changes
@@ -1,5 +1,15 @@
|
||||
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)
|
||||
- Twitter share button in the "upload success" message (#35)
|
||||
|
||||
0.3 2014-06-01
|
||||
- Add a delete link to images (#28)
|
||||
- Concatenated css and js with Mojolicious::Plugin::AssetPack
|
||||
|
||||
78
README.md
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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
|
||||
@@ -58,26 +63,26 @@ vi lutim.conf
|
||||
## Configuration
|
||||
The `lutim.conf.template` is self-documented but here is the options that you can set:
|
||||
|
||||
* hypnotoad: address and port to listen to, user and group which runs hypnotoad (if you run Lutim with a different user from what is defined here, be sure that the user which launchs hypnotoad is able to setuid/setgid to the defined user/group, otherwise it will not work and you'll have 100% CPU consumption. Launch hypnotoad with the root user or with the user which is defined here);
|
||||
* contact: write something which make people able to contact you (contact form URL, email address, whatever);
|
||||
* secrets: an array of random string. Used by Mojolicious for encrypting session cookies.
|
||||
* piwik_img: the Piwik image provides you records of visits without javascript (better privacy than js and cookies);
|
||||
* length: length of the random string part of image's URL (default is 8);
|
||||
* provis_step: Lutim provisions random strings for image's URL per pack of `provis_step` (default is 5);
|
||||
* provisioning: number of random strings to provision (default is 100);
|
||||
* hosted_by: if someone hosts your Lutim instance, you can add some HTML (a logo for example) to make it appear on index page;
|
||||
* tweet_card_via: a Twitter account which will appear on Twitter cards;
|
||||
* max_file_size: well, this is explicit (default is 10Mio = 10485760 octets);
|
||||
* https: 1 if you want to provide secure images URLs (default is 0) DEPRECATED, PASS A `X-Forwarded-Proto` HEADER TO LUTIM FROM YOUR REVERSE PROXY INSTEAD;
|
||||
* token_length: length of the secret token used to allow people to delete their images when they want;
|
||||
* stats_day_num: when you generate statistics with `script/lutim cron stats`, you will have stats for the last `stats_day_num` days (default is 365);
|
||||
* keep_ip_during: when you delete IP addresses of image's senders with `script/lutim cron cleanbdd`, the IP addresses of images older than `keep_ip_during` days will be deleted (default is 365);
|
||||
* broadcast_message: put some string (not HTML) here and this message will be displayed on all Lutim pages (not in JSON responses);
|
||||
* allowed_domains: array of authorized domains for API calls. Example: `['http://1.example.com', 'http://2.example.com']`. If you want to authorize everyone to use the API: `['\*']`.
|
||||
* default_delay: what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365;
|
||||
* max_delay: if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max_delay) option and a warning message will be displayed on homepage;
|
||||
* always_encrypt: if set to 1, all images will be encrypted.
|
||||
* delete_no_longer_viewed_files: if set, the images which have not been viewed since `delete_no_longer_viewed_files` days will be deleted by the `script/lutim cron cleanfiles` command
|
||||
* **hypnotoad :** address and port to listen to, user and group which runs hypnotoad (if you run Lutim with a different user from what is defined here, be sure that the user which launchs hypnotoad is able to setuid/setgid to the defined user/group, otherwise it will not work and you'll have 100% CPU consumption. Launch hypnotoad with the root user or with the user which is defined here);
|
||||
* **contact :** write something which make people able to contact you (contact form URL, email address, whatever);
|
||||
* **secrets :** an array of random string. Used by Mojolicious for encrypting session cookies.
|
||||
* **piwik_img :** the Piwik image provides you records of visits without javascript (better privacy than js and cookies);
|
||||
* **length :** length of the random string part of image's URL (default is 8);
|
||||
* **provis_step :** Lutim provisions random strings for image's URL per pack of `provis_step` (default is 5);
|
||||
* **provisioning :** number of random strings to provision (default is 100);
|
||||
* **hosted_by :** if someone hosts your Lutim instance, you can add some HTML (a logo for example) to make it appear on index page;
|
||||
* **tweet_card_via :** a Twitter account which will appear on Twitter cards;
|
||||
* **max_file_size :** well, this is explicit (default is 10Mio = 10485760 octets);
|
||||
* **https :** 1 if you want to provide secure images URLs (default is 0) DEPRECATED, PASS A `X-Forwarded-Proto` HEADER TO LUTIM FROM YOUR REVERSE PROXY INSTEAD;
|
||||
* **token_length :** length of the secret token used to allow people to delete their images when they want;
|
||||
* **stats_day_num :** when you generate statistics with `script/lutim cron stats`, you will have stats for the last `stats_day_num` days (default is 365);
|
||||
* **keep_ip_during :** when you delete IP addresses of image's senders with `script/lutim cron cleanbdd`, the IP addresses of images older than `keep_ip_during` days will be deleted (default is 365);
|
||||
* **broadcast_message :** put some string (not HTML) here and this message will be displayed on all Lutim pages (not in JSON responses);
|
||||
* **allowed_domains :** array of authorized domains for API calls. Example: `['http://1.example.com', 'http://2.example.com']`. If you want to authorize everyone to use the API: `['\*']`.
|
||||
* **default_delay :** what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365;
|
||||
* **max_delay :** if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max_delay) option and a warning message will be displayed on homepage;
|
||||
* **always_encrypt :** if set to 1, all images will be encrypted.
|
||||
* **delete_no_longer_viewed_files :** if set, the images which have not been viewed since `delete_no_longer_viewed_files` days will be deleted by the `script/lutim cron cleanfiles` command
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -264,7 +280,7 @@ Lutim comes with English and French languages. It will choose the language to di
|
||||
|
||||
If you want to add more languages, for example German:
|
||||
```shell
|
||||
cd lib/I18N
|
||||
cd lib/Lutim/I18N
|
||||
cp en.pm de.pm
|
||||
vim de.pm
|
||||
```
|
||||
@@ -272,12 +288,14 @@ 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.
|
||||
|
||||
## Core developer
|
||||
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), main developer, [@framasky](https://twitter.com/framasky)
|
||||
## Main developers
|
||||
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, [@framasky](https://twitter.com/framasky)
|
||||
* Dattaz (<http://dattaz.fr>), webapp developer, [@dat_taz](https://twitter.com/dat_taz)
|
||||
|
||||
## Contributors
|
||||
* 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)
|
||||
|
||||
2
cpanfile
@@ -1,5 +1,5 @@
|
||||
requires 'Mojolicious';
|
||||
requires 'IO::Socket::SSL';
|
||||
requires 'EV';
|
||||
requires 'Data::Validate::URI';
|
||||
requires 'Mojolicious::Plugin::I18N';
|
||||
requires 'Mojolicious::Plugin::ConfigHashMerge';
|
||||
|
||||
@@ -8,6 +8,13 @@ DISTRIBUTIONS
|
||||
Algorithm::DiffOld 1.1
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
CSS-Minifier-XS-0.09
|
||||
pathname: G/GT/GTERMARS/CSS-Minifier-XS-0.09.tar.gz
|
||||
provides:
|
||||
CSS::Minifier::XS 0.09
|
||||
requirements:
|
||||
ExtUtils::CBuilder 0
|
||||
Test::More 0
|
||||
Carp-1.3301
|
||||
pathname: Z/ZE/ZEFRAM/Carp-1.3301.tar.gz
|
||||
provides:
|
||||
@@ -224,16 +231,16 @@ DISTRIBUTIONS
|
||||
Data::Validate::Domain 0
|
||||
Data::Validate::IP 0
|
||||
ExtUtils::MakeMaker 0
|
||||
DateTime-1.08
|
||||
pathname: D/DR/DROLSKY/DateTime-1.08.tar.gz
|
||||
DateTime-1.10
|
||||
pathname: D/DR/DROLSKY/DateTime-1.10.tar.gz
|
||||
provides:
|
||||
DateTime 1.08
|
||||
DateTime::Duration 1.08
|
||||
DateTime::Helpers 1.08
|
||||
DateTime::Infinite 1.08
|
||||
DateTime::Infinite::Future 1.08
|
||||
DateTime::Infinite::Past 1.08
|
||||
DateTime::LeapSecond 1.08
|
||||
DateTime 1.10
|
||||
DateTime::Duration 1.10
|
||||
DateTime::Helpers 1.10
|
||||
DateTime::Infinite 1.10
|
||||
DateTime::Infinite::Future 1.10
|
||||
DateTime::Infinite::Past 1.10
|
||||
DateTime::LeapSecond 1.10
|
||||
inc::MyModuleBuild undef
|
||||
requirements:
|
||||
Carp 0
|
||||
@@ -244,9 +251,6 @@ DISTRIBUTIONS
|
||||
POSIX 0
|
||||
Params::Validate 0.76
|
||||
Scalar::Util 0
|
||||
Storable 0
|
||||
Test::Fatal 0
|
||||
Test::More 0.88
|
||||
Try::Tiny 0
|
||||
XSLoader 0
|
||||
base 0
|
||||
@@ -255,9 +259,9 @@ DISTRIBUTIONS
|
||||
overload 0
|
||||
perl 5.008001
|
||||
strict 0
|
||||
utf8 0
|
||||
vars 0
|
||||
warnings 0
|
||||
warnings::register 0
|
||||
DateTime-Locale-0.45
|
||||
pathname: D/DR/DROLSKY/DateTime-Locale-0.45.tar.gz
|
||||
provides:
|
||||
@@ -1187,6 +1191,14 @@ DISTRIBUTIONS
|
||||
base 0
|
||||
strict 0
|
||||
warnings 0
|
||||
EV-4.17
|
||||
pathname: M/ML/MLEHMANN/EV-4.17.tar.gz
|
||||
provides:
|
||||
EV 4.17
|
||||
EV::MakeMaker undef
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
common::sense 0
|
||||
Exception-Class-1.37
|
||||
pathname: D/DR/DROLSKY/Exception-Class-1.37.tar.gz
|
||||
provides:
|
||||
@@ -1306,6 +1318,17 @@ DISTRIBUTIONS
|
||||
requirements:
|
||||
IO::File 0.01
|
||||
Test::More 0.01
|
||||
File-Which-1.09
|
||||
pathname: A/AD/ADAMK/File-Which-1.09.tar.gz
|
||||
provides:
|
||||
File::Which 1.09
|
||||
requirements:
|
||||
Exporter 0
|
||||
ExtUtils::MakeMaker 0
|
||||
File::Spec 0.60
|
||||
Getopt::Std 0
|
||||
Test::More 0.80
|
||||
Test::Script 1.05
|
||||
Filesys-DiskUsage-0.05
|
||||
pathname: C/CO/COG/Filesys-DiskUsage-0.05.tar.gz
|
||||
provides:
|
||||
@@ -1323,16 +1346,18 @@ DISTRIBUTIONS
|
||||
ExtUtils::MakeMaker 6.31
|
||||
Storable 0
|
||||
Test::Most 0
|
||||
IO-Socket-SSL-1.982
|
||||
pathname: S/SU/SULLR/IO-Socket-SSL-1.982.tar.gz
|
||||
IO-Socket-SSL-1.997
|
||||
pathname: S/SU/SULLR/IO-Socket-SSL-1.997.tar.gz
|
||||
provides:
|
||||
IO::Socket::SSL 1.982
|
||||
IO::Socket::SSL 1.997
|
||||
IO::Socket::SSL::Intercept 1.93
|
||||
IO::Socket::SSL::OCSP_Cache 1.997
|
||||
IO::Socket::SSL::OCSP_Resolver 1.997
|
||||
IO::Socket::SSL::PublicSuffix undef
|
||||
IO::Socket::SSL::SSL_Context 1.982
|
||||
IO::Socket::SSL::SSL_HANDLE 1.982
|
||||
IO::Socket::SSL::Session_Cache 1.982
|
||||
IO::Socket::SSL::Utils 0.02
|
||||
IO::Socket::SSL::SSL_Context 1.997
|
||||
IO::Socket::SSL::SSL_HANDLE 1.997
|
||||
IO::Socket::SSL::Session_Cache 1.997
|
||||
IO::Socket::SSL::Utils 0.03
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
Net::SSLeay 1.46
|
||||
@@ -1345,6 +1370,14 @@ DISTRIBUTIONS
|
||||
ExtUtils::MakeMaker 0
|
||||
Test::More 0.31
|
||||
Time::HiRes 0
|
||||
JavaScript-Minifier-XS-0.09
|
||||
pathname: G/GT/GTERMARS/JavaScript-Minifier-XS-0.09.tar.gz
|
||||
provides:
|
||||
JavaScript::Minifier::XS 0.09
|
||||
requirements:
|
||||
ExtUtils::CBuilder 0
|
||||
Test::More 0
|
||||
perl v5.8.8
|
||||
List-MoreUtils-0.33
|
||||
pathname: A/AD/ADAMK/List-MoreUtils-0.33.tar.gz
|
||||
provides:
|
||||
@@ -1459,8 +1492,8 @@ DISTRIBUTIONS
|
||||
perl 5.006
|
||||
strict 0
|
||||
warnings 0
|
||||
Mojolicious-4.97
|
||||
pathname: S/SR/SRI/Mojolicious-4.97.tar.gz
|
||||
Mojolicious-5.12
|
||||
pathname: S/SR/SRI/Mojolicious-5.12.tar.gz
|
||||
provides:
|
||||
Mojo undef
|
||||
Mojo::Asset undef
|
||||
@@ -1523,7 +1556,7 @@ DISTRIBUTIONS
|
||||
Mojo::UserAgent::Server undef
|
||||
Mojo::UserAgent::Transactor undef
|
||||
Mojo::Util undef
|
||||
Mojolicious 4.97
|
||||
Mojolicious 5.12
|
||||
Mojolicious::Command undef
|
||||
Mojolicious::Command::cgi undef
|
||||
Mojolicious::Command::cpanify undef
|
||||
@@ -1572,6 +1605,19 @@ DISTRIBUTIONS
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
perl 5.010001
|
||||
Mojolicious-Plugin-AssetPack-0.16
|
||||
pathname: J/JH/JHTHORSEN/Mojolicious-Plugin-AssetPack-0.16.tar.gz
|
||||
provides:
|
||||
Mojolicious::Plugin::AssetPack 0.16
|
||||
Mojolicious::Plugin::AssetPack::Preprocessors 0.01
|
||||
requirements:
|
||||
CSS::Minifier::XS 0.01
|
||||
ExtUtils::MakeMaker 0
|
||||
File::Which 1.00
|
||||
IPC::Run3 0.04
|
||||
JavaScript::Minifier::XS 0.01
|
||||
Mojolicious 4.30
|
||||
Test::More 0.88
|
||||
Mojolicious-Plugin-ConfigHashMerge-0.01
|
||||
pathname: D/DO/DOTAN/Mojolicious-Plugin-ConfigHashMerge-0.01.tar.gz
|
||||
provides:
|
||||
@@ -1580,14 +1626,14 @@ DISTRIBUTIONS
|
||||
ExtUtils::MakeMaker 0
|
||||
Hash::Merge::Simple 0.051
|
||||
Mojolicious 4.85
|
||||
Mojolicious-Plugin-I18N-1.21
|
||||
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.21.tar.gz
|
||||
Mojolicious-Plugin-I18N-1.3
|
||||
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.3.tar.gz
|
||||
provides:
|
||||
Mojolicious::Plugin::I18N 1.21
|
||||
Mojolicious::Plugin::I18N 1.3
|
||||
requirements:
|
||||
I18N::LangTags 0.35
|
||||
Module::Build 0.42
|
||||
Mojolicious 4
|
||||
Mojolicious 5
|
||||
Test::More 0
|
||||
perl 5.010001
|
||||
Net-Domain-TLD-1.70
|
||||
@@ -2004,12 +2050,13 @@ DISTRIBUTIONS
|
||||
Algorithm::Diff 1.19
|
||||
Exporter 0
|
||||
ExtUtils::MakeMaker 0
|
||||
Text-Unidecode-0.04
|
||||
pathname: S/SB/SBURKE/Text-Unidecode-0.04.tar.gz
|
||||
Text-Unidecode-1.01
|
||||
pathname: S/SB/SBURKE/Text-Unidecode-1.01.tar.gz
|
||||
provides:
|
||||
Text::Unidecode 0.04
|
||||
Text::Unidecode 1.01
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
perl 5.008
|
||||
Try-Tiny-0.19
|
||||
pathname: D/DO/DOY/Try-Tiny-0.19.tar.gz
|
||||
provides:
|
||||
@@ -2021,3 +2068,9 @@ DISTRIBUTIONS
|
||||
constant 0
|
||||
strict 0
|
||||
warnings 0
|
||||
common-sense-3.73
|
||||
pathname: M/ML/MLEHMANN/common-sense-3.73.tar.gz
|
||||
provides:
|
||||
common::sense 3.73
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
|
||||
27
lib/Lutim.pm
@@ -10,6 +10,8 @@ mkdir($ENV{MOJO_TMPDIR}, 0700) unless (-d $ENV{MOJO_TMPDIR});
|
||||
sub startup {
|
||||
my $self = shift;
|
||||
|
||||
push @{$self->commands->namespaces}, 'Lutim::Command';
|
||||
|
||||
$self->{wait_for_it} = {};
|
||||
|
||||
$self->plugin('I18N');
|
||||
@@ -86,11 +88,7 @@ sub startup {
|
||||
my $c = shift;
|
||||
my $ip_only = shift || 0;
|
||||
|
||||
my $proxy = '';
|
||||
my @x_forward = $c->req->headers->header('X-Forwarded-For');
|
||||
for my $x (@x_forward) {
|
||||
$proxy .= join(', ', @$x);
|
||||
}
|
||||
my $proxy = $c->req->headers->header('X-Forwarded-For');
|
||||
|
||||
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
|
||||
|
||||
@@ -111,7 +109,7 @@ sub startup {
|
||||
my $short;
|
||||
do {
|
||||
$short= $c->shortener($c->config->{length});
|
||||
} while (LutimModel::Lutim->count('WHERE short = ?', $short) || $short eq 'about' || $short eq 'stats');
|
||||
} while (LutimModel::Lutim->count('WHERE short = ?', $short) || $short eq 'about' || $short eq 'stats' || $short eq 'd' || $short eq 'm');
|
||||
|
||||
LutimModel::Lutim->create(
|
||||
short => $short,
|
||||
@@ -292,11 +290,12 @@ sub startup {
|
||||
);
|
||||
|
||||
$self->asset('index.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/animation.css', 'css/uploader.css', 'css/hennypenny.css', 'css/lutim.css');
|
||||
$self->asset('stats.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/animation.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/animation.css', 'css/hennypenny.css', 'css/lutim.css');
|
||||
$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');
|
||||
|
||||
@@ -323,6 +322,10 @@ sub startup {
|
||||
to('Controller#stats')->
|
||||
name('stats');
|
||||
|
||||
$r->get('/manifest.webapp')->
|
||||
to('Controller#webapp')->
|
||||
name('manifest.webapp');
|
||||
|
||||
$r->post('/')->
|
||||
to('Controller#add')->
|
||||
name('add');
|
||||
@@ -331,6 +334,10 @@ sub startup {
|
||||
to('Controller#delete')->
|
||||
name('delete');
|
||||
|
||||
$r->post('/m/:short/:token')->
|
||||
to('Controller#modify')->
|
||||
name('modify');
|
||||
|
||||
$r->get('/:short')->
|
||||
to('Controller#short')->
|
||||
name('short');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package Mojolicious::Command::cron;
|
||||
package Lutim::Command::cron;
|
||||
use Mojo::Base 'Mojolicious::Commands';
|
||||
|
||||
has description => 'Execute tasks.';
|
||||
@@ -7,7 +7,7 @@ has hint => <<EOF;
|
||||
See 'script/lutim cron help TASK' for more information on a specific task.
|
||||
EOF
|
||||
has message => sub { shift->extract_usage . "\nCron tasks:\n" };
|
||||
has namespaces => sub { ['Mojolicious::Command::cron'] };
|
||||
has namespaces => sub { ['Lutim::Command::cron'] };
|
||||
|
||||
sub help { shift->run(@_) }
|
||||
|
||||
@@ -17,7 +17,7 @@ sub help { shift->run(@_) }
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron - Cron commands
|
||||
Lutim::Command::cron - Cron commands
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package Mojolicious::Command::cron::cleanbdd;
|
||||
package Lutim::Command::cron::cleanbdd;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
use LutimModel;
|
||||
use Mojo::Util qw(slurp decode);
|
||||
@@ -28,7 +28,7 @@ sub run {
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron::cleanbdd - Delete IP addresses from database after configured delay
|
||||
Lutim::Command::cron::cleanbdd - Delete IP addresses from database after configured delay
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package Mojolicious::Command::cron::cleanfiles;
|
||||
package Lutim::Command::cron::cleanfiles;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
use LutimModel;
|
||||
use Mojo::Util qw(slurp decode);
|
||||
@@ -32,7 +32,7 @@ sub run {
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron::cleanfiles - Delete expired files
|
||||
Lutim::Command::cron::cleanfiles - Delete expired files
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package Mojolicious::Command::cron::stats;
|
||||
package Lutim::Command::cron::stats;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
use LutimModel;
|
||||
use Mojo::DOM;
|
||||
@@ -55,7 +55,7 @@ sub run {
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Command::cron::stats - Stats generator
|
||||
Lutim::Command::cron::stats - Stats generator
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package Mojolicious::Command::cron::watch;
|
||||
package Lutim::Command::cron::watch;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
use Mojo::Util qw(slurp decode);
|
||||
use Filesys::DiskUsage qw/du/;
|
||||
@@ -56,7 +56,7 @@ sub run {
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::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
|
||||
|
||||
@@ -46,6 +46,89 @@ sub stats {
|
||||
);
|
||||
}
|
||||
|
||||
sub webapp {
|
||||
my $c = shift;
|
||||
|
||||
my $headers = Mojo::Headers->new();
|
||||
$headers->add('Content-Type' => 'application/x-web-app-manifest+json');
|
||||
$c->res->content->headers($headers);
|
||||
|
||||
$c->render(
|
||||
template => 'manifest',
|
||||
format => 'webapp'
|
||||
);
|
||||
}
|
||||
|
||||
sub modify {
|
||||
my $c = shift;
|
||||
my $short = $c->param('short');
|
||||
my $token = $c->param('token');
|
||||
my $url = $c->param('url');
|
||||
|
||||
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL', $short);
|
||||
if (scalar(@images)) {
|
||||
my $image = $images[0];
|
||||
my $msg;
|
||||
if ($image->mod_token() ne $token || $token eq '') {
|
||||
$msg = $c->l('invalid_token');
|
||||
} else {
|
||||
$c->app->log->info('[MODIFICATION] someone modify '.$image->filename.' with token method (path: '.$image->path.')');
|
||||
|
||||
$image->update(
|
||||
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,
|
||||
);
|
||||
$msg = $c->l('image_delay_modified');
|
||||
if (defined($c->param('format')) && $c->param('format') eq 'json') {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
msg => $msg
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$msg .= ' (<a href="'.$url.'">'.$url.'</a>)' unless (!defined($url));
|
||||
$c->flash(
|
||||
success => $msg
|
||||
);
|
||||
return $c->redirect_to('/');
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($c->param('format')) && $c->param('format') eq 'json') {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
msg => $msg
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$c->flash(
|
||||
msg => $msg
|
||||
);
|
||||
return $c->redirect_to('/');
|
||||
}
|
||||
} else {
|
||||
$c->app->log->info('[UNSUCCESSFUL] someone tried to modify '.$short.' but it does\'nt exist.');
|
||||
|
||||
# Image never existed
|
||||
my $msg = $c->l('image_mod_not_found', $short);
|
||||
if (defined($c->param('format')) && $c->param('format') eq 'json') {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
msg => $msg
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$c->flash(
|
||||
msg => $msg
|
||||
);
|
||||
return $c->redirect_to('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub delete {
|
||||
my $c = shift;
|
||||
my $short = $c->param('short');
|
||||
@@ -55,7 +138,7 @@ sub delete {
|
||||
if (scalar(@images)) {
|
||||
my $image = $images[0];
|
||||
my $msg;
|
||||
if ($image->mod_token() ne $token) {
|
||||
if ($image->mod_token() ne $token || $token eq '') {
|
||||
$msg = $c->l('invalid_token');
|
||||
} elsif ($image->enabled() == 0) {
|
||||
$msg = $c->l('already_deleted', $image->filename);
|
||||
@@ -196,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,';
|
||||
@@ -218,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
|
||||
@@ -227,6 +316,11 @@ sub add {
|
||||
# Give url to user
|
||||
$short = $records[0]->short;
|
||||
$real_short = $short;
|
||||
if (!defined($records[0]->mod_token)) {
|
||||
$records[0]->update(
|
||||
mod_token => $c->shortener($c->config->{token_length})
|
||||
);
|
||||
}
|
||||
$token = $records[0]->mod_token;
|
||||
$short .= '/'.$key if (defined($key));
|
||||
} else {
|
||||
@@ -325,19 +419,53 @@ 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 {
|
||||
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
|
||||
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
|
||||
$dt->set_time_zone('GMT');
|
||||
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
|
||||
# Delete image if needed
|
||||
if ($images[0]->delete_at_first_view && $images[0]->counter >= 1) {
|
||||
# Log deletion
|
||||
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
|
||||
|
||||
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key);
|
||||
# Delete image
|
||||
$c->delete_image($images[0]);
|
||||
|
||||
$c->flash(
|
||||
msg => $c->l('image_not_found')
|
||||
);
|
||||
return $c->redirect_to('/');
|
||||
} else {
|
||||
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
|
||||
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
|
||||
$dt->set_time_zone('GMT');
|
||||
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
|
||||
|
||||
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key);
|
||||
}
|
||||
}
|
||||
|
||||
if ($test != 500) {
|
||||
@@ -367,7 +495,7 @@ sub short {
|
||||
|
||||
if (scalar(@images)) {
|
||||
# Log access try
|
||||
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist.');
|
||||
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist anymore.');
|
||||
|
||||
# Warn user
|
||||
$c->flash(
|
||||
|
||||
@@ -21,15 +21,17 @@ my $inf_body = <<EOF;
|
||||
<h4>What about the software which provides the service?</h4>
|
||||
<p>The Lutim software is a <a href="http://en.wikipedia.org/wiki/Free_software">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> to see what you can do.</p>
|
||||
<p>For more details, see the <a href="https://github.com/ldidry/lutim">Github</a> page of the project.</p>
|
||||
<h4>Core developer</h4>
|
||||
<h4>Main developers</h4>
|
||||
<ul>
|
||||
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), main developer</li>
|
||||
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), core developer, <a href="https://twitter.com/framasky">\@framasky</a></li>
|
||||
<li>Dattaz (<a href="http://dattaz.fr">http://dattaz.fr</a>), webapp developer, <a href="https://twitter.com/dat_taz">\@dat_taz</a></li>
|
||||
</ul>
|
||||
<h4>Contributors</h4>
|
||||
<ul>
|
||||
<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
|
||||
|
||||
@@ -43,6 +45,8 @@ our %Lexicon = (
|
||||
'view-link' => 'View link',
|
||||
'download-link' => 'Download link',
|
||||
'twitter-link' => 'Link for put in a tweet',
|
||||
'tweet_it' => 'Tweet it!',
|
||||
'share_it' => 'Share it!',
|
||||
'delete-link' => 'Deletion link',
|
||||
'some-bad' => 'Something bad happened',
|
||||
'delete-first' => 'Delete at first view?',
|
||||
@@ -81,6 +85,10 @@ our %Lexicon = (
|
||||
'image_deleted' => 'The image [_1] has been successfully deleted',
|
||||
'invalid_token' => 'The delete token is invalid.',
|
||||
'already_deleted' => 'The image [_1] has already been deleted.',
|
||||
'install_as_webapp' => 'Install webapp',
|
||||
'image_delay_modified' => 'The image\'s delay has been successfully modified',
|
||||
'image_mod_not_found' => 'Unable to find the image [_1].',
|
||||
'modify_image_error' => 'Error while trying to modify the image.',
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
@@ -21,15 +21,17 @@ my $inf_body = <<EOF;
|
||||
<h4>Et à propos du logiciel qui fournit le service ?</h4>
|
||||
<p>Le logiciel Lutim est un <a href="https://fr.wikipedia.org/wiki/Logiciel_libre">logiciel libre</a>, ce qui vous permet de le télécharger et de l’installer sur votre propre serveur. Jetez un coup d’œil à l’<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL</a> pour voir quels sont vos droits.</p>
|
||||
<p>Pour plus de détails, consultez la page <a href="https://github.com/ldidry/lutim">Github</a> du projet.</p>
|
||||
<h4>Développeur de l'application</h4>
|
||||
<h4>Développeurs de l'application</h4>
|
||||
<ul>
|
||||
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), développeur principal</li>
|
||||
<li>Luc Didry, aka Sky (<a href="http://www.fiat-tux.fr">http://www.fiat-tux.fr</a>), développeur principal, <a href="https://twitter.com/framasky">\@framasky</a></li>
|
||||
<li>Dattaz (<a href="http://dattaz.fr">http://dattaz.fr</a>), développeur de la webapp, <a href="https://twitter.com/dat_taz">\@dat_taz</a></li>
|
||||
</ul>
|
||||
<h4>Contributeurs</h4>
|
||||
<ul>
|
||||
<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
|
||||
|
||||
@@ -43,6 +45,8 @@ our %Lexicon = (
|
||||
'view-link' => 'Lien d\'affichage',
|
||||
'download-link' => 'Lien de téléchargement',
|
||||
'twitter-link' => 'Lien pour mettre dans un tweet',
|
||||
'tweet_it' => 'Tweetez !',
|
||||
'share_it' => 'Partagez !',
|
||||
'delete-link' => 'Lien de suppression',
|
||||
'some-bad' => 'Un problème est survenu',
|
||||
'delete-first' => 'Supprimer au premier accès ?',
|
||||
@@ -81,6 +85,10 @@ our %Lexicon = (
|
||||
'image_deleted' => 'L\'image [_1] a été supprimée avec succès.',
|
||||
'invalid_token' => 'Le jeton de suppression est invalide.',
|
||||
'already_deleted' => 'L\'image [_1] a déjà été supprimée.',
|
||||
'install_as_webapp' => 'Installer la webapp',
|
||||
'image_delay_modified' => 'Le délai de l\'image a été modifié avec succès.',
|
||||
'image_mod_not_found' => 'Impossible de trouver l\'image [_1].',
|
||||
'modify_image_error' => 'Une erreur est survenue lors de la tentative de modification de l\'image.',
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -32,3 +41,10 @@ label.always-encrypt {
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#install-app img {
|
||||
height: 22px;
|
||||
}
|
||||
#install-app {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@media (max-width:768px){
|
||||
.uploader div.browser label {
|
||||
max-width:95%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:768px){
|
||||
.uploader div.browser label {
|
||||
width:300px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploader div.browser label {
|
||||
background-color: #5a7bc2;
|
||||
padding: 5px 15px;
|
||||
@@ -38,7 +50,6 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
width: 300px;
|
||||
margin: 20px auto 0px auto;
|
||||
|
||||
box-shadow: 2px 2px 2px #888888;
|
||||
|
||||
BIN
public/img/lutim120.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/lutim128.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/img/lutim152.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/img/lutim196.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/img/lutim256.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/img/lutim32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/img/lutim60.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/img/lutim76.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
public/img/lutim90.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
public/img/rocket.png
Normal file
|
After Width: | Height: | Size: 509 B |
390
public/js/freezeframe.js
Normal 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
@@ -1,3 +1,26 @@
|
||||
$('document').ready(function() {
|
||||
$('.jsonly').show();
|
||||
// Are we in a mozilla navigator? (well, are we in a navigator which can handle webapps?)
|
||||
if (navigator.mozApps !== undefined) {
|
||||
var installCheck = navigator.mozApps.checkInstalled(manifestUrl);
|
||||
installCheck.onsuccess = function() {
|
||||
if(installCheck.result === null) {
|
||||
var button = $('#install-app');
|
||||
// Show app install button when app is not installed
|
||||
button.css('display','inline-block');
|
||||
button.click(function() {
|
||||
var request = window.navigator.mozApps.install(manifestUrl);
|
||||
request.onsuccess = function () {
|
||||
// Save the App object that is returned
|
||||
var appRecord = this.result;
|
||||
button.css('display','none');
|
||||
};
|
||||
request.onerror = function () {
|
||||
// Display the error information from the DOMError object
|
||||
alert('Install failed, error: ' + this.error.name);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
<img class="pull-left thumbnail" alt="<%= stash('filename') %> thumbnail" src="<%= stash('thumb') %>">
|
||||
% }
|
||||
<div>
|
||||
% # Display image informations
|
||||
% my $url = url_for('/')->to_abs().stash('short');
|
||||
<strong><%= stash('filename') %></strong>
|
||||
<a target="_blank" class="btn btn-default btn-primary btn-xs" href="https://twitter.com/share?url=<%= $url %>?t"><%=l 'tweet_it' %></a>
|
||||
<ul class="list-unstyled">
|
||||
% my $url = url_for('/')->to_abs().stash('short');
|
||||
% my $delete_url = url_for('delete', {short => stash('real_short'), token => stash('token')})->to_abs();
|
||||
<li><i class="icon icon-eye" title =" <%= l 'view-link' %>"></i> <%= link_to $url => begin %> <%= $url %> <%= end %></li>
|
||||
<li><i class="icon icon-download" title =" <%= l 'download-link' %>"></i> <%= link_to $url.'?dl' => begin %> <%= $url.'?dl' %> <%= end %></li>
|
||||
@@ -19,12 +21,49 @@
|
||||
<li><i class="icon icon-trash" title =" <%= l 'delete-link' %>"></i> <%= link_to $delete_url => begin %> <%= $delete_url %> <%= end %></li>
|
||||
</ul>
|
||||
</div>
|
||||
% # Delay modification form
|
||||
% my $modify_url = url_for('modify', {short => stash('real_short'), token => stash('token')})->to_abs();
|
||||
<form class="form" role="form" method="POST" action="<%== $modify_url %>">
|
||||
<div class="form-group form-inline">
|
||||
<input name="image_url" type="hidden" value="<%= $url %>">
|
||||
<select name="delete-day" class="form-control">
|
||||
% for my $delay (qw/0 1 7 30 365/) {
|
||||
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
|
||||
% if (config('max_delay')) {
|
||||
% if ($delay) {
|
||||
% if ($delay < config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% } elsif ($delay == config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% last;
|
||||
% } else {
|
||||
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
|
||||
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
% } else {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% }
|
||||
% }
|
||||
</select>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
|
||||
</label>
|
||||
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
|
||||
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
</div>
|
||||
<%= submit_button l('go'), class => 'btn btn-sm btn-default btn-primary', id => 'submitbutton' %>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
% }
|
||||
% if (defined(flash('success'))) {
|
||||
<div class="alert alert-success">
|
||||
<button type="button" class="close jsonly" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<%= flash('success') %>
|
||||
<p><%== flash('success') %></p>
|
||||
</div>
|
||||
% }
|
||||
% if (defined(flash('msg'))) {
|
||||
@@ -38,48 +77,48 @@
|
||||
|
||||
<noscript>
|
||||
<form class="form" role="form" method="POST" action="<%== url_for('add') %>" enctype="multipart/form-data">
|
||||
<div class="form-group form-inline">
|
||||
<select name="delete-day" class="form-control">
|
||||
% for my $delay (qw/0 1 7 30 365/) {
|
||||
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
|
||||
% if (config('max_delay')) {
|
||||
% if ($delay) {
|
||||
% if ($delay < config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% } elsif ($delay == config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% last;
|
||||
% } else {
|
||||
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
|
||||
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
% } else {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% }
|
||||
% }
|
||||
</select>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
|
||||
</label>
|
||||
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
|
||||
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
<div class="form-group form-inline">
|
||||
<select name="delete-day" class="form-control">
|
||||
% for my $delay (qw/0 1 7 30 365/) {
|
||||
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
|
||||
% if (config('max_delay')) {
|
||||
% if ($delay) {
|
||||
% if ($delay < config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% } elsif ($delay == config('max_delay')) {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% last;
|
||||
% } else {
|
||||
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
|
||||
<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
% } else {
|
||||
<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>
|
||||
% }
|
||||
% }
|
||||
</select>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
|
||||
</label>
|
||||
<label <%== (config('always_encrypt')) ? 'class="always-encrypt"' : '' %>>
|
||||
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lutim-file"><%=l 'upload_image' %></label>
|
||||
<input type="file" name="file" id="lutim-file" accept="image/*">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lutim-file-url"><%=l 'upload_image_url' %></label>
|
||||
<input type="url" name="lutim-file-url" placeholder="<%=l 'image_url' %>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lutim-file"><%=l 'upload_image' %></label>
|
||||
<input type="file" name="file" id="lutim-file">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lutim-file-url"><%=l 'upload_image_url' %></label>
|
||||
<input type="url" name="lutim-file-url" placeholder="<%=l 'image_url' %>">
|
||||
<p class="help-block"><%=l 'image-only' %></p>
|
||||
</div>
|
||||
<%= submit_button l('go'), class => 'btn btn-default btn-primary', id => 'submitbutton' %>
|
||||
</form>
|
||||
<%= submit_button l('go'), class => 'btn btn-default btn-primary', id => 'submitbutton' %>
|
||||
</form>
|
||||
</noscript>
|
||||
|
||||
<!-- D&D Zone-->
|
||||
@@ -121,15 +160,15 @@
|
||||
<div class="browser">
|
||||
<label>
|
||||
<span><%=l 'file-browser' %></span>
|
||||
<input type="file" name="files[]" multiple="multiple" title='<%=l 'file-browser' %>'>
|
||||
<input type="file" name="files[]" multiple="multiple" title='<%=l 'file-browser' %>' accept="image/*">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block"><%=l 'image-only' %></p>
|
||||
<form class="form-horizontal" role="form" method="POST" action="<%== url_for('add') %>">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-3"><span class="hidden-spin" style="font-size:200%; display:none;" > <i class="icon-spinner animate-spin pull-right"></i></span><label for="lutim-file-url" class="control-label pull-right"><%=l 'upload_image_url' %></label></span>
|
||||
<div class="col-sm-9">
|
||||
<span class="col-sm-3 col-xs-12"><span class="hidden-spin" style="font-size:200%; display:none;" > <i class="icon-spinner animate-spin pull-right"></i></span><label for="lutim-file-url" class="control-label pull-right"><%=l 'upload_image_url' %></label></span>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<input type="url" name="lutim-file-url" class="form-control" id="lutim-file-url" placeholder="<%=l 'image_url' %>">
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,15 +178,55 @@
|
||||
<!-- /D&D Zone -->
|
||||
|
||||
%= javascript begin
|
||||
function link(url, dl, token) {
|
||||
function link(url, dl, token, modify) {
|
||||
if (token !== undefined) {
|
||||
url = 'd/'+url+'/'+token;
|
||||
if (modify !== undefined && modify === true) {
|
||||
return '<%== url_for('index')->to_abs() %>m/'+url+'/'+token;
|
||||
} else {
|
||||
url = 'd/'+url+'/'+token;
|
||||
}
|
||||
} else if (dl !== '') {
|
||||
url = url+'?'+dl;
|
||||
}
|
||||
return '<a href="<%== url_for('index')->to_abs() %>'+url+'"><%== url_for('index')->to_abs() %>'+url+'</a>';
|
||||
}
|
||||
function message(success, msg) {
|
||||
function share(url) {
|
||||
console.log(url);
|
||||
new MozActivity({
|
||||
name: "share",
|
||||
data: {
|
||||
type: "url",
|
||||
number: 1,
|
||||
url: url
|
||||
}
|
||||
});
|
||||
}
|
||||
function tw_url(url) {
|
||||
var btn = ' <a target="_blank" class="btn btn-default btn-primary btn-xs" href="https://twitter.com/share?url=<%== url_for('index')->to_abs() %>'+url+'?t"><%=l 'tweet_it' %></a>';
|
||||
if (navigator.mozSetMessageHandler !== undefined) {
|
||||
btn = btn+' <a target="_blank" class="btn btn-default btn-primary btn-xs" href="" onclick="share(\'<%== url_for('index')->to_abs() %>'+url+'?t\');return false;"><%=l 'share_it' %></a>';
|
||||
}
|
||||
return btn
|
||||
}
|
||||
function modify(url, short) {
|
||||
$.ajax({
|
||||
url : url,
|
||||
type : "POST",
|
||||
data : {
|
||||
'image_url' : '<%== url_for('index')->to_abs() %>'+short,
|
||||
'format' : 'json',
|
||||
'first-view' : ($("#first-view-"+short).prop('checked')) ? 1 : 0,
|
||||
'delete-day' : $("#day-"+short).val()
|
||||
},
|
||||
success: function(data) {
|
||||
alert(data.msg);
|
||||
},
|
||||
error: function() {
|
||||
alert('<%=l 'modify_image_error' %>');
|
||||
}
|
||||
});
|
||||
}
|
||||
function build_message(success, msg) {
|
||||
if(success) {
|
||||
var thumb = (msg.thumb !== null) ? '<img class="pull-left thumbnail" alt="'+msg.filename+' thumbnail" src="'+msg.thumb+'">' : ''
|
||||
return '<div class="alert alert-success"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>'
|
||||
@@ -155,6 +234,7 @@
|
||||
+'<div><strong>'
|
||||
+msg.filename
|
||||
+'</strong>'
|
||||
+tw_url(msg.short)
|
||||
+'<ul class="list-unstyled"><li><i class="icon icon-eye" title="<%=l 'view-link' %>"></i> '
|
||||
+link(msg.short, '')
|
||||
+'</li><li><i class="icon icon-download" title="<%=l 'download-link' %>"></i> '
|
||||
@@ -163,7 +243,32 @@
|
||||
+link(msg.short, 't')
|
||||
+'</li><li><i class="icon icon-trash" title="<%=l 'delete-link' %>"></i> '
|
||||
+link(msg.real_short, '', msg.token)
|
||||
+'</li></ul></div>';
|
||||
+'</li></ul><form class="form" role="form" method="POST" action="'
|
||||
+link(msg.real_short, '', msg.token, true)
|
||||
+'"><div class="form-group form-inline"><select id="day-'+msg.real_short+'" name="delete-day" class="form-control">'
|
||||
% for my $delay (qw/0 1 7 30 365/) {
|
||||
% my $text = ($delay == 7 || $delay == 30) ? l('delay_days', $delay) : l("delay_$delay");
|
||||
% if (config('max_delay')) {
|
||||
% if ($delay) {
|
||||
% if ($delay < config('max_delay')) {
|
||||
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
|
||||
% } elsif ($delay == config('max_delay')) {
|
||||
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
|
||||
% last;
|
||||
% } else {
|
||||
% my $text = ($delay == 1) ? l('delay_1') : l('delay_days', $delay);
|
||||
+'<option value="<%= config('max_delay') %>" <%== is_selected(config('max_delay')) %>><%=l('delay_days', config('max_delay')) %></option>'
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
% } else {
|
||||
+'<option value="<%= $delay %>" <%== is_selected($delay) %>><%= $text %></option>'
|
||||
% }
|
||||
% }
|
||||
+'</select> <div class="checkbox"><label><input id="first-view-'+msg.real_short+'" type="checkbox" name="first-view"> <%=l 'delete-first' %></label>'
|
||||
+'</div> '
|
||||
+'<a href="#" onclick="modify(\''+link(msg.real_short, '', msg.token, true)+'\', \''+msg.real_short+'\');return false;" class="btn btn-sm btn-default btn-primary"><%=l 'go' %></a></div></form>'
|
||||
+'</div>';
|
||||
} else {
|
||||
return '<div class="alert alert-danger"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button><strong><%=l 'some-bad' %></strong><br>'
|
||||
+msg.filename
|
||||
@@ -186,17 +291,16 @@
|
||||
$('#'+id).prop('aria-valuenow', percent);
|
||||
$('#'+id).prop('style', 'width: '+percent+'%;');
|
||||
$('#'+id+'-text').html(percentStr);
|
||||
|
||||
},
|
||||
onUploadSuccess: function(id, data){
|
||||
$('#'+id+'-div').remove();
|
||||
$(".messages").append(message(data.success, data.msg));
|
||||
$(".messages").append(build_message(data.success, data.msg));
|
||||
},
|
||||
onUploadError: function(id, message){
|
||||
$(".messages").append(message(false, ''));
|
||||
$(".messages").append(build_message(false, ''));
|
||||
},
|
||||
onFileSizeError: function(file){
|
||||
$(".messages").append(message(false, { filename: file.name, msg: '<%= l('file_too_big', $max_file_size) %>'}));
|
||||
$(".messages").append(build_message(false, { filename: file.name, msg: '<%= l('file_too_big', $max_file_size) %>'}));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -218,13 +322,13 @@
|
||||
'delete-day' : $("#delete-day").val()
|
||||
},
|
||||
success: function(data) {
|
||||
$(".messages").append(message(data.success, data.msg));
|
||||
$(".messages").append(build_message(data.success, data.msg));
|
||||
if (data.success) {
|
||||
$("#lutim-file-url").val('');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$(".messages").append(message(false, ''));
|
||||
$(".messages").append(build_message(false, ''));
|
||||
},
|
||||
complete: function() {
|
||||
$("#lutim-file-url").prop('disabled', '');
|
||||
@@ -236,6 +340,67 @@
|
||||
}
|
||||
}
|
||||
|
||||
function fileUpload(file) {
|
||||
var fd = new FormData();
|
||||
fd.append('file', file);
|
||||
|
||||
fd.append('format', 'json');
|
||||
fd.append('first-view', ($("#first-view").prop('checked')) ? 1 : 0);
|
||||
fd.append('crypt', ($("#crypt").prop('checked')) ? 1 : 0);
|
||||
fd.append('delete-day', ($("#delete-day").val()));
|
||||
|
||||
$(".messages").append('<div id="1-div">'+file.name+'<br><div class="progress"><div id="1"class="progress-bar progress-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"><span id="1-text" class="pull-left" style="padding-left: 10px;"> 0%</span></div></div></div>');
|
||||
// Ajax Submit
|
||||
$.ajax({
|
||||
url: '<%== url_for('add') %>',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: fd,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
forceSync: false,
|
||||
xhr: function(){
|
||||
var xhrobj = $.ajaxSettings.xhr();
|
||||
if(xhrobj.upload){
|
||||
xhrobj.upload.addEventListener('progress', function(event) {
|
||||
var percent = 0;
|
||||
var position = event.loaded || event.position;
|
||||
var total = event.total || e.totalSize;
|
||||
if(event.lengthComputable){
|
||||
percent = Math.ceil(position / total * 100);
|
||||
}
|
||||
|
||||
var percentStr = ' '+percent+'%';
|
||||
$('#1').prop('aria-valuenow', percent);
|
||||
$('#1').prop('style', 'width: '+percent+'%;');
|
||||
$('#1-text').html(percentStr);
|
||||
}, false);
|
||||
}
|
||||
|
||||
return xhrobj;
|
||||
},
|
||||
success: function (data, message, xhr){
|
||||
$('#1-div').remove();
|
||||
$(".messages").append(build_message(data.success, data.msg));
|
||||
},
|
||||
error: function (xhr, status, errMsg){
|
||||
$(".messages").append(build_message(false, ''));
|
||||
},
|
||||
});
|
||||
}
|
||||
window.onload = function() {
|
||||
if (navigator.mozSetMessageHandler !== undefined) {
|
||||
navigator.mozSetMessageHandler('activity', function handler(activityRequest) {
|
||||
var activityName = activityRequest.source.name;
|
||||
if (activityName == 'share') {
|
||||
activity = activityRequest;
|
||||
blob = activity.source.data.blobs[0];
|
||||
fileUpload(blob);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
$('document').ready(function() {
|
||||
var firstview = ($("#first-view").prop('checked')) ? 1 : 0;
|
||||
var deleteday = ($("#delete-day").prop('checked')) ? 1 : 0;
|
||||
|
||||
@@ -11,7 +11,17 @@
|
||||
<title>Lutim</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link rel="icon" type="image/png" href="<%= url_for('/') %>img/favicon.png">
|
||||
<link rel="icon" sizes="128x128" href="<%= url_for('/') %>img/lutim128.png">
|
||||
<link rel="icon" sizes="196x196" href="<%= url_for('/') %>img/lutim196.png">
|
||||
<link rel="apple-touch-icon" href="<%= url_for('/') %>img/lutim60.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= url_for('/') %>img/lutim76.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= url_for('/') %>img/lutim120.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= url_for('/') %>img/lutim152.png">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="<%= url_for('/') %>img/lutim128.png">
|
||||
% if (current_route 'stats') {
|
||||
%= asset 'stats.css'
|
||||
% } elsif (current_route 'about') {
|
||||
@@ -38,9 +48,10 @@
|
||||
<%=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 %>
|
||||
<%= 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>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,6 +65,9 @@
|
||||
<strong><%= stash('stop_upload') %></strong>
|
||||
</div>
|
||||
% }
|
||||
%= javascript begin
|
||||
var manifestUrl = '<%== url_for('manifest.webapp')->to_abs() %>';
|
||||
% end
|
||||
% if (current_route 'stats') {
|
||||
%= asset 'stats.js'
|
||||
% } elsif (!(current_route 'about')) {
|
||||
|
||||
34
templates/manifest.webapp.ep
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "Lutim",
|
||||
"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') %>",
|
||||
"60": "<%= url_for('/img/lutim60.png') %>",
|
||||
"90": "<%= url_for('/img/lutim90.png') %>",
|
||||
"120": "<%= url_for('/img/lutim120.png') %>",
|
||||
"128": "<%= url_for('/img/lutim128.png') %>",
|
||||
"256": "<%= url_for('/img/lutim256.png') %>"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Lutim team !",
|
||||
"url": "https://github.com/ldidry/lutim"
|
||||
},
|
||||
"default_locale": "en",
|
||||
"locales": {
|
||||
"fr": {
|
||||
"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": {
|
||||
"share": {
|
||||
"filters": {
|
||||
"type": [ "image/*"]
|
||||
},
|
||||
"href": "<%= url_for('/') %>",
|
||||
"disposition": "window"
|
||||
}
|
||||
},
|
||||
"version": "0.1",
|
||||
"chrome": { "navigation": true }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ sub upload {
|
||||
$self->{_links}->{'view_image'} = $link;
|
||||
$self->{_links}->{'download_link'} = $link.'?dl';
|
||||
$self->{_links}->{'twitter_link'} = $link.'?t';
|
||||
$self->{_links}->{'delete_link'} = $url.'d/'.$hash->{msg}->{real_short}.'/'.$hash->{msg}->{token};
|
||||
|
||||
#set success code (200)
|
||||
$self->{_links}{'status'} = 200;
|
||||
|
||||