Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add Store Object & Improve redirect service

* Add Store Object to prepare migration to File mode and MongoDB
* Improve redirect to service by factorizing code (facebook, twitter)
* Add Google+ service
  • Loading branch information...
commit 8e961144247b89e85677520dc9eb7d6462250a47 1 parent 6f264d9
geistteufel authored
View
3  lib/App/Main.pm
@@ -20,6 +20,7 @@ use Dancer::Plugin::Redis 0.03;
#Load SCK module
use Celogeek::SCK;
+use Celogeek::SCK::Store;
#Initialize variable before any root
hook before => sub {
@@ -30,7 +31,7 @@ hook before => sub {
#sck url tools to reduce link
#set default settings, max_letters could be set later
var sck => Celogeek::SCK->new(
- 'redis' => redis,
+ 'store' => Celogeek::SCK::Store->new(engine => 'redis', connection => redis),
'max_generated_times' => 5,
);
View
23 lib/App/Redirect.pm
@@ -37,32 +37,17 @@ get qr{^/(.+)$}x => sub {
return $longurl;
};
-#Twitter redirect
+#Twitter / Facebook / Google+ / no url
get qr{^/(.+)$}x => sub {
- return pass() unless defined params->{t};
+ my ($service) = grep { defined params->{$_} } qw/t f g/;
+ return pass() unless defined $service;
my ($key) = splat();
#try enlarge key if exist
my $longurl;
try {
my $url = vars->{sck}->enlarge($key);
- $longurl = vars->{base} . "?t=1&url=" . uri_escape_utf8($url);
- };
- $longurl = vars->{base} unless defined $longurl;
-
- return redirect($longurl);
-};
-
-#Facebook redirect
-get qr{^/(.+)$}x => sub {
- return pass() unless defined params->{f};
- my ($key) = splat();
-
- #try enlarge key if exist
- my $longurl;
- try {
- my $url = vars->{sck}->enlarge($key);
- $longurl = vars->{base} . "?f=1&url=" . uri_escape_utf8($url);
+ $longurl = vars->{base} . "?" . $service . "=1&url=" . uri_escape_utf8($url);
};
$longurl = vars->{base} unless defined $longurl;
View
41 lib/App/Root.pm
@@ -38,30 +38,10 @@ any [ 'get', 'post' ] => '/' => sub {
};
};
-#twitter, call with t=1
+#twitter / facebook / google+ / with url
any [ 'get', 'post' ] => '/' => sub {
- return pass() unless defined params->{t} && defined params->{url};
-
- my $fetch_title = params->{title} // vars->{sck}->title( params->{url} )
- // "";
- my @title = ($fetch_title);
-
- try {
- push @title, vars->{base} . vars->{sck}->shorten( params->{url} );
- }
- catch {
-
- #push long title if any error occur
- push @title, params->{url};
- };
-
- return redirect( "http://twitter.com/?status="
- . uri_escape_utf8( join( ' - ', @title ) ) );
-};
-
-#facebook, call with f=1
-any [ 'get', 'post' ] => '/' => sub {
- return pass() unless defined params->{f} && defined params->{url};
+ my ($service) = grep { defined params->{$_} } qw/t f g/;
+ return pass() unless defined $service && defined params->{url};
my $title = params->{title} // vars->{sck}->title( params->{url} ) // "";
my $url;
@@ -70,14 +50,21 @@ any [ 'get', 'post' ] => '/' => sub {
$url = vars->{base} . vars->{sck}->shorten( params->{url} );
}
catch {
-
#push long title if any error occur
$url = params->{url};
};
- return redirect( "http://www.facebook.com/share.php" . "?u="
- . uri_escape_utf8($url) . "&t="
- . uri_escape_utf8($title) );
+ if ($service eq 't') { #twitter
+ return redirect( "http://twitter.com/?status="
+ . uri_escape_utf8( join( ' - ', $title, $url ) ) );
+ } elsif ( $service eq 'f' ) { #facebook
+ return redirect( "http://www.facebook.com/share.php"
+ . "?u=" . uri_escape_utf8($url)
+ . "&t=" . uri_escape_utf8($title) );
+ } elsif ( $service eq 'g' ) { #google +
+ return redirect( "https://plus.google.com/share?url="
+ . uri_escape_utf8($url) );
+ }
};
#normal call with url
View
199 lib/Celogeek/SCK.pm
@@ -6,7 +6,7 @@ package Celogeek::SCK;
use Celogeek::SCK;
- my $sck = Celogeek::SCK->new(redis => redis, max_generated_times => 5, max_letters => 10);
+ my $sck = Celogeek::SCK->new(store => Celogeek::SCK::Store->new(...), max_generated_times => 5, max_letters => 10);
my $short_url = $sck->shorten('http://www.montest.com');
my $long_url = $sck->enlarge($short_url);
@@ -37,10 +37,10 @@ use Encode;
use Regexp::Common qw /number/;
-has 'redis' => (
+has 'store' => (
'is' => 'rw',
'isa' => sub {
- die "$_[0] is not a Redis object" unless ref $_[0] eq 'Redis';
+ die "$_[0] is not a Celogeek::SCK::Store object" unless $_[0]->isa('Celogeek::SCK::Store');
},
'required' => 1,
);
@@ -69,6 +69,11 @@ has 'max_letters' => (
'default' => sub {1},
);
+has 'min_letters' => (
+ 'is' => 'rw',
+ 'default' => sub {1},
+);
+
has 'status' => (
'is' => 'rw',
);
@@ -78,19 +83,6 @@ has 'check_method' => (
'default' => sub {'header'},
);
-=method BUILD
-
-Initialize the SCK core.
-
-=cut
-
-sub BUILD {
- my ($self) = @_;
- $self->redis->incr('c:min_letters')
- unless $self->redis->exists('c:min_letters');
- return;
-}
-
=method generate
Generate a short link. It will start with min_letters, and try to find a random short link smaller than max_metter.
@@ -103,34 +95,33 @@ It throw "SCK:[NO WAY TO SHORTEN]" if the generator couldn't find anything
sub generate {
my ($self) = @_;
- my $key_size = $self->redis->get('c:min_letters');
- croak 'SCK:[NO WAY TO SHORTEN]' if $self->max_letters < $key_size;
+ croak 'SCK:[NO WAY TO SHORTEN]' if $self->max_letters < $self->min_letters;
my @letters_to_use = ( 'a' .. 'z', 'A' .. 'Z', 0 .. 9, '/' );
while ( $self->max_generated_times > $self->generated_times ) {
- #get random data to build a key with the size of key_size
- my $key = rand_data_string( $key_size, \@letters_to_use );
+ #get random data to build a key with the size of min_letters
+ my $shorturl = rand_data_string( $self->min_letters, \@letters_to_use );
#cleanup (remove double /, / on start and at the end)
- $key =~ s!/+!/!xg;
- $key =~ s!/$!!x;
- $key =~ s!^/!!x;
- next if $key eq ''; #one letter, only a /
+ $shorturl =~ s!/+!/!xg;
+ $shorturl =~ s!/$!!x;
+ $shorturl =~ s!^/!!x;
+ next if $shorturl eq ''; #one letter, only a /
$self->generated_times( $self->generated_times + 1 );
#we got a new key !
- unless ( $self->redis->exists( $self->_path_key($key) ) ) {
- return $key;
+ unless ( $self->store->exists_by_shorturl( $shorturl ) ) {
+ return $shorturl;
}
}
#try again with a min_letters + 1
$self->max_generated_times(
$self->max_generated_times + $self->generated_times );
- $self->redis->incr('c:min_letters');
+ $self->min_letters($self->min_letters + 1);
return $self->generate();
}
@@ -149,13 +140,11 @@ sub shorten {
croak 'SCK:[BAD URL]';
};
- #look in redis db
- my $hash_key = $self->_hash_key($url);
- if ( $self->redis->exists($hash_key) ) {
- return $self->redis->hget( $hash_key, 'path' );
+ #check if we have already shortenize the url
+ if ( $self->store->exists_by_url($url) ) {
+ return $self->store->get_by_url($url, 'path' );
}
else {
-
#check url
my $analyzer = Celogeek::SCK::Analyzer->new(
uri => $url,
@@ -177,17 +166,14 @@ sub shorten {
#generate a new one
$self->generated_times(0);
my $short = $self->generate();
- $self->_save(
- $hash_key,
- { url => $url,
- path => $short,
- clicks => 0,
- clicks_uniq => 0,
- created_at => DateTime->now,
- last_accessed_at => DateTime->now
- }
+ my $now = _datetime_str(DateTime->now);
+ $self->store->set_by_url($url,
+ path => $short,
+ clicks => 0,
+ clicks_uniq => 0,
+ created_at => $now,
+ last_accessed_at => $now,
);
- $self->redis->set( $self->_path_key($short), $hash_key );
return $short;
}
}
@@ -199,38 +185,36 @@ Try to get the long url from a key
=cut
sub enlarge {
- my ( $self, $key, %opts ) = @_;
+ my ( $self, $shorturl, %opts ) = @_;
my $clicks = $opts{clicks} // 0;
my $clicks_uniq = $opts{clicks_uniq} // 0;
croak 'SCK:[THIS KEY DOESNT EXIST]'
- unless ( $self->redis->exists( $self->_path_key($key) ) );
-
- my $hash_key = $self->redis->get( $self->_path_key($key) );
+ unless $self->store->exists_by_shorturl($shorturl);
#we have a clicks
if ($clicks) {
my $today = DateTime->now;
#incr click part
- $self->redis->hincrby( $hash_key, 'clicks', 1 );
- $self->redis->hincrby( 's:traffic', $today->ymd, 1 );
+ $self->store->increment_by_shorturl($shorturl, 'clicks', 1);
+ $self->store->increment_stat('traffic', $today->ymd, 1);
#we have a clicks_uniq
if ($clicks_uniq) {
#add score to element
- $self->redis->hincrby( $hash_key, 'clicks_uniq', 1 );
+ $self->store->increment_by_shorturl($shorturl, 'clicks_uniq', 1);
#add a score to top10
- $self->redis->zincrby( 's:top10', 1, $hash_key );
+ $self->store->increment_top10_by_shorturl($shorturl, 1);
#add a score to traffic
- $self->redis->hincrby( 's:traffic:uniq', $today->ymd, 1 );
+ $self->store->increment_stat('traffic:uniq', $today->ymd, 1);
}
- $self->_save( $hash_key, { last_accessed_at => $today } );
+ $self->store->set_by_shorturl( $shorturl, last_accessed_at => _datetime_str($today) );
}
- return $self->redis->hget( $hash_key, 'url' );
+ return $self->store->url_from_shorturl($shorturl);
}
=method stats
@@ -240,18 +224,18 @@ Return stats for a specific key
=cut
sub stats {
- my ( $self, $key, %opts ) = @_;
+ my ( $self, $shorturl, %opts ) = @_;
$opts{date_format} //= '%c UTC';
- if ( $self->redis->exists( $self->_path_key($key) ) ) {
- my $data = $self->_get( $self->redis->get( $self->_path_key($key) ) );
+ if ( $self->store->exists_by_shorturl( $shorturl ) ) {
+ my %data = $self->store->get_by_shorturl($shorturl);
foreach my $d (qw/created_at last_accessed_at/) {
- if ( $data->{$d} ) {
- $data->{$d} = $self->_datetime( $data->{$d} )
+ if ( $data{$d} ) {
+ $data{$d} = _datetime( $data{$d} )
->strftime( $opts{date_format} );
}
}
- return $data;
+ return \%data;
}
else {
croak 'SCK:[THIS KEY DOESNT EXIST]';
@@ -266,11 +250,7 @@ Return the title fetch from url
sub title {
my ($self, $url) = @_;
- my $title;
- if ( $self->redis->exists( $self->_hash_key($url) ) ) {
- $title = $self->redis->hget($self->_hash_key($url), "title");
- }
- return $title;
+ $self->store->get_by_url($url, 'title');
}
=method top10
@@ -282,45 +262,13 @@ Return the top10 of most clicks links of the week. Only one click per day per us
sub top10 {
my ($self) = @_;
- my @members_with_score
- = $self->redis->zrevrange( 's:top10', 0, 9, 'WITHSCORES' );
- my @top10_data = ();
-
- for ( my $i = 0; $i < @members_with_score; $i += 2 ) {
- my ( $member_key, $member_score )
- = @members_with_score[ $i .. $i + 1 ];
-
- #fetch data
- my $data = { score => $member_score };
- $data->{$_} = $self->redis->hget( $member_key, $_ )
- for qw/url path title/;
-
- $data->{alt} = '';
-
- # too many try, never try again
- if ( $data->{title} ) {
- Encode::_utf8_on( $data->{title} );
-
- #if title exist, use it
- if ( $data->{title} ne $data->{url} ) {
- $data->{alt} = $data->{title} . ' - ';
- }
- }
- else {
- $data->{title} = $data->{url};
- }
-
- #set alt
- $data->{alt} .= $data->{url} . ' - Score ' . $data->{score};
-
- push @top10_data, $data;
- }
- return \@top10_data;
+ my @top10 = $self->store->top10(title => qr/^[a-zA-Z0-9\.\s\'\"\(\)\r\n\[\]\{\}\|\-\,\;\&\:\!\?\/\#\@\<\>\+\*\™\’\»\_\$\£\=]+$/);
+ return \@top10;
}
#return a formated DateTime from DateTime or String
sub _datetime {
- my ( $self, $date ) = @_;
+ my ( $date ) = @_;
if ( ref $date && $date->isa('DateTime') ) {
return $date;
}
@@ -331,60 +279,9 @@ sub _datetime {
#return a str of DateTime
sub _datetime_str {
- my $self = shift;
- my $date = shift;
+ my ($date) = @_;
return $date->ymd . ' ' . $date->hms;
}
-#save a hash to redis
-sub _save {
- my $self = shift;
- my $hash_key = shift;
- my $data = shift;
-
- foreach my $key ( keys %$data ) {
- my $val = $data->{$key};
- if ( ref $val eq 'DateTime' ) {
- $data->{$key} = $self->_datetime_str($val);
- }
- }
- $self->redis->hmset( $hash_key, %$data );
-
- return;
-}
-
-#get an hash from redis
-sub _get {
- my $self = shift;
- my $hash_key = shift;
- my %data
- = $self->redis->exists($hash_key)
- ? $self->redis->hgetall($hash_key)
- : ();
- return \%data;
-}
-
-#common part of redis key
-sub _redis_key {
- my $self = shift;
- my $prefix = shift;
- my $key = shift;
- return $prefix . ':' . sha1_hex($key);
-}
-
-#key for path (short link), start with p:
-sub _path_key {
- my $self = shift;
- my $path = shift;
- return $self->_redis_key( 'p', $path );
-}
-
-#key for hash (url information), start with h:
-sub _hash_key {
- my $self = shift;
- my $url = shift;
- return $self->_redis_key( 'h', $url );
-}
-
1;
View
3  lib/Celogeek/SCK/Store.pm
@@ -9,9 +9,6 @@ use Moo;
has 'engine' => (
is => 'ro',
- isa => sub {
- die "not a valid engine" unless $_[0] =~ /^redis$/;
- },
required => 1,
);
View
4 lib/Celogeek/SCK/Store/Base.pm
@@ -16,7 +16,9 @@ sub _die {
}
#method need to be implemented
-for my $meth (qw/validate_connection set_config get_config/) {
+for my $meth (qw/
+ validate_connection
+ /) {
eval <<EOF
sub $meth { shift->_die }
EOF
View
212 lib/Celogeek/SCK/Store/Redis.pm
@@ -8,11 +8,223 @@ use Carp;
# VERSION
use 5.012;
+use Digest::SHA1 qw/sha1_hex/;
+
use Moo::Role;
+=method validate_connection
+
+Check if connection is a Redis connection
+
+=cut
sub validate_connection {
my $self = shift;
croak "not a Redis connection" unless ref $self->connection && $self->connection->isa('Redis');
}
+=method get_by_url
+
+Get information using URL as a key
+
+=cut
+sub get_by_url {
+ my $self = shift;
+ my ($url, $key) = @_;
+ if (defined $key) {
+ return $self->connection->hget('h:'.sha1_hex($url), $key),
+ } else {
+ return $self->connection->hgetall('h:'.sha1_hex($url)),
+ }
+}
+
+=method set_by_url
+
+Set information using URL as a key
+
+=cut
+sub set_by_url {
+ my $self = shift;
+ my ($url, %info) = @_;
+
+
+ if (defined $info{path}) {
+ #keep index
+ $self->connection->set('p:'.sha1_hex($info{path}), 'h:'.sha1_hex($url));
+ #add url to info
+ $info{url} = $url;
+ }
+ $self->connection->hmset('h:'.sha1_hex($url), %info);
+
+ return;
+}
+
+=method exists_by_url
+
+Check if url is already stored
+
+=cut
+sub exists_by_url {
+ my $self = shift;
+ my ($url) = @_;
+
+ $self->connection->exists('h:'.sha1_hex($url));
+}
+
+=method increment_by_url
+
+Increment a counter by url
+
+=cut
+sub increment_by_url {
+ my $self = shift;
+ my ($url, $counter, $number) = @_;
+
+ $self->connection->hincrby('h:'.sha1_hex($url), $counter, $number // 1);
+}
+
+
+=method get_by_shorturl
+
+Get information using the short key
+
+=cut
+sub get_by_shorturl {
+ my $self = shift;
+ my ($shorturl, $key) = @_;
+
+ $self->get_by_url($self->url_from_shorturl($shorturl), $key);
+}
+
+=method set_by_shorturl
+
+Set information using the short key
+
+=cut
+sub set_by_shorturl {
+ my $self = shift;
+ my ($shorturl, %info) = @_;
+
+ $self->set_by_url($self->url_from_shorturl($shorturl), %info);
+}
+
+=method exists_by_shorturl
+
+Check if shorturl is already stored
+
+=cut
+sub exists_by_shorturl {
+ my $self = shift;
+ my ($shorturl) = @_;
+
+ $self->connection->exists('p:'.sha1_hex($shorturl));
+}
+
+=method increment_by_shorturl
+
+Increment a counter by shorturl
+
+=cut
+sub increment_by_shorturl {
+ my $self = shift;
+ my ($shorturl, $counter, $number) = @_;
+
+ $self->increment_by_url($self->url_from_shorturl($shorturl), $counter, $number);
+}
+
+=method increment_top10_by_url
+
+Add score to a url by its url
+
+=cut
+sub increment_top10_by_url {
+ my $self = shift;
+ my ($url, $number) = @_;
+ $self->connection->zincrby( 's:top10', $number // 1, 'h:'.sha1_hex($url) );
+}
+
+=method increment_stat
+
+Increment stat counter
+
+=cut
+sub increment_stat {
+ my $self = shift;
+ my ($stat, $key, $val) = @_;
+ $self->connection->hincrby('s:'.$stat, $key, $val // 1);
+}
+
+=method increment_top10_by_shorturl
+
+Add score to a url by its shorturl
+
+=cut
+sub increment_top10_by_shorturl {
+ my $self = shift;
+ my ($shorturl, $number) = @_;
+ $self->increment_top10_by_url($self->url_from_shorturl($shorturl), $number);
+}
+
+=method url_from_shorturl
+
+Return url from shorturl
+
+=cut
+sub url_from_shorturl {
+ my $self = shift;
+ my ($shorturl) = @_;
+
+ my $h_url = $self->connection->get('p:'.sha1_hex($shorturl));
+ $self->connection->hget($h_url, 'url');
+}
+
+=method top10
+
+Return top10 using filter
+
+=cut
+sub top10 {
+ my $self = shift;
+ my (%filters) = @_;
+
+ my @members_with_score
+ = $self->connection->zrevrange( 's:top10', 0, -1, 'WITHSCORES' );
+
+ my @top10_data = ();
+
+ MEMBER:
+ for ( my $i = 0; $i < @members_with_score; $i += 2 ) {
+ my ( $member_key, $member_score ) = @members_with_score[ $i .. $i + 1 ];
+
+ my $url = $self->connection->hget($member_key, 'url');
+ my %data = $self->get_by_url($url);
+ for my $key(keys %filters) {
+ my $filter = $filters{$key};
+ unless(defined $data{$key} && $data{$key} =~ $filter) {
+ next MEMBER;
+ }
+ }
+ $data{score} = $member_score;
+
+ $data{alt} = '';
+ if ( $data{title} ) {
+ Encode::_utf8_on( $data{title} );
+
+ #if title exist, use it
+ if ( $data{title} ne $data{url} ) {
+ $data{alt} = $data{title} . ' - ';
+ }
+ }
+ else {
+ $data{title} = $data{url};
+ }
+
+ #set alt
+ $data{alt} .= $data{url} . ' - Score ' . $data{score};
+
+ push @top10_data, \%data;
+ last MEMBER unless @top10_data < 10;
+ }
+ return @top10_data;
+}
+
1;
View
BIN  public/GooglePlus.ico
Binary file not shown
View
12 views/_bookmarklettab.tt
@@ -5,6 +5,7 @@
<li><a class="quiet" href="javascript:window.location.href.indexOf('<% request.base %>') == 0 ? '<!DOCTYPE html><html><head><link rel=\'shortcut icon\' href=\'<% request.base %>favicon.ico\'/><meta http-equiv=\'refresh\' content=\'0;url=<% request.base %>?ib=1\'/></head></html>' : (function(){var sck='<% request.base %>?b=1&url='; var url=encodeURIComponent(location.href); var w=650; var h=420; var l=(screen.width-w)/2; var t=(screen.height-h)/2; window.open(sck+url,'sck','location=no,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,toolbar=no,width='+w+',height='+h+',top='+t+',left='+l);})();">Shorten with <% request.host %></a></li>
<li><a class="quiet" href="javascript:window.location.href.indexOf('<% request.base %>') == 0 ? '<!DOCTYPE html><html><head><link rel=\'shortcut icon\' href=\'<% request.base %>Twitterrific.ico\'/><meta http-equiv=\'refresh\' content=\'0;url=<% request.base %>?ib=1\'/></head></html>' : (function(){var sck='<% request.base %>?t=1&url='; var url=encodeURIComponent(location.href); var title=encodeURIComponent(document.title); window.open(sck+url+'&title='+title);})();">Post to twitter with <% request.host %></a></li>
<li><a class="quiet" href="javascript:window.location.href.indexOf('<% request.base %>') == 0 ? '<!DOCTYPE html><html><head><link rel=\'shortcut icon\' href=\'<% request.base %>Facebook.ico\'/><meta http-equiv=\'refresh\' content=\'0;url=<% request.base %>?ib=1\'/></head></html>' : (function(){var sck='<% request.base %>?f=1&url='; var url=encodeURIComponent(location.href); var title=encodeURIComponent(document.title); var w=626; var h=436; var l=(screen.width-w)/2; var t=(screen.height-h)/2; window.open(sck+url+'&title='+title,'sck','location=no,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,toolbar=no,width='+w+',height='+h+',top='+t+',left='+l);})();">Post to facebook with <% request.host %></a></li>
+ <li><a class="quiet" href="javascript:window.location.href.indexOf('<% request.base %>') == 0 ? '<!DOCTYPE html><html><head><link rel=\'shortcut icon\' href=\'<% request.base %>GooglePlus.ico\'/><meta http-equiv=\'refresh\' content=\'0;url=<% request.base %>?ib=1\'/></head></html>' : (function(){var sck='<% request.base %>?g=1&url='; var url=encodeURIComponent(location.href); var title=encodeURIComponent(document.title); var w=626; var h=436; var l=(screen.width-w)/2; var t=(screen.height-h)/2; window.open(sck+url+'&title='+title,'sck','location=no,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,toolbar=no,width='+w+',height='+h+',top='+t+',left='+l);})();">Post to google+ with <% request.host %></a></li>
</ul>
</div>
<hr />
@@ -48,4 +49,15 @@
<input type="text" name="bookmarklet_tweetthis_url" value="javascript:(function(){var sck='<% request.base %>?f=1&url='; var url=encodeURIComponent(location.href); var title=encodeURIComponent(document.title); window.open(sck+url+'&title='+title);})();" class="text readonly"/>
</div>
</fieldset>
+ <label>Post to google+ with <% request.host %></label>
+ <fieldset>
+ <div class="span-13 last">
+ <label for="bookmarklet_tweetthis_title" class="span-2">Title</label>
+ <input type="text" name="bookmarklet_tweetthis_title" value="Post to google+ with <% request.host %>" class="text readonly"/>
+ </div>
+ <div class="span-13 last">
+ <label for="bookmarklet_tweetthis_url" class="span-2">URL</label>
+ <input type="text" name="bookmarklet_tweetthis_url" value="javascript:(function(){var sck='<% request.base %>?g=1&url='; var url=encodeURIComponent(location.href); var title=encodeURIComponent(document.title); window.open(sck+url+'&title='+title);})();" class="text readonly"/>
+ </div>
+ </fieldset>
</form>
View
5 views/_statstab.tt
@@ -26,6 +26,11 @@
<button href="<% stats.short_url %>?f=1" target="_blank" class="last">Go &gt;&gt;</button>
</div>
<div class="span-13 last">
+ <label for="googleplus_r" class="span-2">Google+</label>
+ <input type="text" name="googleplus_r" class="text readonly" value="<% stats.short_url %>?g=1"/>
+ <button href="<% stats.short_url %>?g=1" target="_blank" class="last">Go &gt;&gt;</button>
+ </div>
+ <div class="span-13 last">
<label for="url_r_created_at" class="span-2">Created at</label>
<input type="text" name="url_r_created_at" class="text readonly" value="<% stats.created_at %>"/>
</div>
Please sign in to comment.
Something went wrong with that request. Please try again.