initial commit v1.3
This commit is contained in:
commit
0da1b046f0
6
config
Normal file
6
config
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = false
|
||||||
|
bare = true
|
||||||
|
symlinks = false
|
||||||
|
ignorecase = true
|
||||||
1
description
Normal file
1
description
Normal file
@ -0,0 +1 @@
|
|||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
||||||
15
hooks/applypatch-msg.sample
Normal file
15
hooks/applypatch-msg.sample
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message taken by
|
||||||
|
# applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit. The hook is
|
||||||
|
# allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "applypatch-msg".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||||
|
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||||
|
:
|
||||||
24
hooks/commit-msg.sample
Normal file
24
hooks/commit-msg.sample
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message.
|
||||||
|
# Called by "git commit" with one argument, the name of the file
|
||||||
|
# that has the commit message. The hook should exit with non-zero
|
||||||
|
# status after issuing an appropriate message if it wants to stop the
|
||||||
|
# commit. The hook is allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "commit-msg".
|
||||||
|
|
||||||
|
# Uncomment the below to add a Signed-off-by line to the message.
|
||||||
|
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||||
|
# hook is more suited to it.
|
||||||
|
#
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||||
|
|
||||||
|
# This example catches duplicate Signed-off-by lines.
|
||||||
|
|
||||||
|
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||||
|
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||||
|
echo >&2 Duplicate Signed-off-by lines.
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
173
hooks/fsmonitor-watchman.sample
Normal file
173
hooks/fsmonitor-watchman.sample
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Open2;
|
||||||
|
|
||||||
|
# An example hook script to integrate Watchman
|
||||||
|
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||||
|
# new and modified files.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 2) and last update token
|
||||||
|
# formatted as a string and outputs to stdout a new update token and
|
||||||
|
# all files that have been modified since the update token. Paths must
|
||||||
|
# be relative to the root of the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "query-watchman" and set
|
||||||
|
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||||
|
#
|
||||||
|
my ($version, $last_update_token) = @ARGV;
|
||||||
|
|
||||||
|
# Uncomment for debugging
|
||||||
|
# print STDERR "$0 $version $last_update_token\n";
|
||||||
|
|
||||||
|
# Check the hook interface version
|
||||||
|
if ($version ne 2) {
|
||||||
|
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $git_work_tree = get_working_dir();
|
||||||
|
|
||||||
|
my $retry = 1;
|
||||||
|
|
||||||
|
my $json_pkg;
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
$json_pkg = "JSON::XS";
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
require JSON::PP;
|
||||||
|
$json_pkg = "JSON::PP";
|
||||||
|
};
|
||||||
|
|
||||||
|
launch_watchman();
|
||||||
|
|
||||||
|
sub launch_watchman {
|
||||||
|
my $o = watchman_query();
|
||||||
|
if (is_work_tree_watched($o)) {
|
||||||
|
output_result($o->{clock}, @{$o->{files}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub output_result {
|
||||||
|
my ($clockid, @files) = @_;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# binmode $fh, ":utf8";
|
||||||
|
# print $fh "$clockid\n@files\n";
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
binmode STDOUT, ":utf8";
|
||||||
|
print $clockid;
|
||||||
|
print "\0";
|
||||||
|
local $, = "\0";
|
||||||
|
print @files;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_clock {
|
||||||
|
my $response = qx/watchman clock "$git_work_tree"/;
|
||||||
|
die "Failed to get clock id on '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_query {
|
||||||
|
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||||
|
or die "open2() failed: $!\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
|
||||||
|
# In the query expression below we're asking for names of files that
|
||||||
|
# changed since $last_update_token but not from the .git folder.
|
||||||
|
#
|
||||||
|
# To accomplish this, we're using the "since" generator to use the
|
||||||
|
# recency index to select candidate nodes and "fields" to limit the
|
||||||
|
# output to file names only. Then we're using the "expression" term to
|
||||||
|
# further constrain the results.
|
||||||
|
if (substr($last_update_token, 0, 1) eq "c") {
|
||||||
|
$last_update_token = "\"$last_update_token\"";
|
||||||
|
}
|
||||||
|
my $query = <<" END";
|
||||||
|
["query", "$git_work_tree", {
|
||||||
|
"since": $last_update_token,
|
||||||
|
"fields": ["name"],
|
||||||
|
"expression": ["not", ["dirname", ".git"]]
|
||||||
|
}]
|
||||||
|
END
|
||||||
|
|
||||||
|
# Uncomment for debugging the watchman query
|
||||||
|
# open (my $fh, ">", ".git/watchman-query.json");
|
||||||
|
# print $fh $query;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
print CHLD_IN $query;
|
||||||
|
close CHLD_IN;
|
||||||
|
my $response = do {local $/; <CHLD_OUT>};
|
||||||
|
|
||||||
|
# Uncomment for debugging the watch response
|
||||||
|
# open ($fh, ">", ".git/watchman-response.json");
|
||||||
|
# print $fh $response;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
die "Watchman: command returned no output.\n" .
|
||||||
|
"Falling back to scanning...\n" if $response eq "";
|
||||||
|
die "Watchman: command returned invalid output: $response\n" .
|
||||||
|
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_work_tree_watched {
|
||||||
|
my ($output) = @_;
|
||||||
|
my $error = $output->{error};
|
||||||
|
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||||
|
$retry--;
|
||||||
|
my $response = qx/watchman watch "$git_work_tree"/;
|
||||||
|
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
$output = $json_pkg->new->utf8->decode($response);
|
||||||
|
$error = $output->{error};
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
# Watchman will always return all files on the first query so
|
||||||
|
# return the fast "everything is dirty" flag to git and do the
|
||||||
|
# Watchman query just to get it over with now so we won't pay
|
||||||
|
# the cost in git to look up each individual file.
|
||||||
|
my $o = watchman_clock();
|
||||||
|
$error = $output->{error};
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
output_result($o->{clock}, ("/"));
|
||||||
|
$last_update_token = $o->{clock};
|
||||||
|
|
||||||
|
eval { launch_watchman() };
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_working_dir {
|
||||||
|
my $working_dir;
|
||||||
|
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||||
|
$working_dir = Win32::GetCwd();
|
||||||
|
$working_dir =~ tr/\\/\//;
|
||||||
|
} else {
|
||||||
|
require Cwd;
|
||||||
|
$working_dir = Cwd::cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $working_dir;
|
||||||
|
}
|
||||||
8
hooks/post-update.sample
Normal file
8
hooks/post-update.sample
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare a packed repository for use over
|
||||||
|
# dumb transports.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "post-update".
|
||||||
|
|
||||||
|
exec git update-server-info
|
||||||
14
hooks/pre-applypatch.sample
Normal file
14
hooks/pre-applypatch.sample
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed
|
||||||
|
# by applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-applypatch".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||||
|
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||||
|
:
|
||||||
49
hooks/pre-commit.sample
Normal file
49
hooks/pre-commit.sample
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed.
|
||||||
|
# Called by "git commit" with no arguments. The hook should
|
||||||
|
# exit with non-zero status after issuing an appropriate message if
|
||||||
|
# it wants to stop the commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-commit".
|
||||||
|
|
||||||
|
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
against=HEAD
|
||||||
|
else
|
||||||
|
# Initial commit: diff against an empty tree object
|
||||||
|
against=$(git hash-object -t tree /dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If you want to allow non-ASCII filenames set this variable to true.
|
||||||
|
allownonascii=$(git config --type=bool hooks.allownonascii)
|
||||||
|
|
||||||
|
# Redirect output to stderr.
|
||||||
|
exec 1>&2
|
||||||
|
|
||||||
|
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||||
|
# them from being added to the repository. We exploit the fact that the
|
||||||
|
# printable range starts at the space character and ends with tilde.
|
||||||
|
if [ "$allownonascii" != "true" ] &&
|
||||||
|
# Note that the use of brackets around a tr range is ok here, (it's
|
||||||
|
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||||
|
# the square bracket bytes happen to fall in the designated range.
|
||||||
|
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||||
|
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||||
|
then
|
||||||
|
cat <<\EOF
|
||||||
|
Error: Attempt to add a non-ASCII file name.
|
||||||
|
|
||||||
|
This can cause problems if you want to work with people on other platforms.
|
||||||
|
|
||||||
|
To be portable it is advisable to rename the file.
|
||||||
|
|
||||||
|
If you know what you are doing you can disable this check using:
|
||||||
|
|
||||||
|
git config hooks.allownonascii true
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If there are whitespace errors, print the offending file names and fail.
|
||||||
|
exec git diff-index --check --cached $against --
|
||||||
13
hooks/pre-merge-commit.sample
Normal file
13
hooks/pre-merge-commit.sample
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to verify what is about to be committed.
|
||||||
|
# Called by "git merge" with no arguments. The hook should
|
||||||
|
# exit with non-zero status after issuing an appropriate message to
|
||||||
|
# stderr if it wants to stop the merge commit.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-merge-commit".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||||
|
exec "$GIT_DIR/hooks/pre-commit"
|
||||||
|
:
|
||||||
53
hooks/pre-push.sample
Normal file
53
hooks/pre-push.sample
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# An example hook script to verify what is about to be pushed. Called by "git
|
||||||
|
# push" after it has checked the remote status, but before anything has been
|
||||||
|
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||||
|
#
|
||||||
|
# This hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- Name of the remote to which the push is being done
|
||||||
|
# $2 -- URL to which the push is being done
|
||||||
|
#
|
||||||
|
# If pushing without using a named remote those arguments will be equal.
|
||||||
|
#
|
||||||
|
# Information about the commits which are being pushed is supplied as lines to
|
||||||
|
# the standard input in the form:
|
||||||
|
#
|
||||||
|
# <local ref> <local oid> <remote ref> <remote oid>
|
||||||
|
#
|
||||||
|
# This sample shows how to prevent push of commits where the log message starts
|
||||||
|
# with "WIP" (work in progress).
|
||||||
|
|
||||||
|
remote="$1"
|
||||||
|
url="$2"
|
||||||
|
|
||||||
|
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
|
||||||
|
|
||||||
|
while read local_ref local_oid remote_ref remote_oid
|
||||||
|
do
|
||||||
|
if test "$local_oid" = "$zero"
|
||||||
|
then
|
||||||
|
# Handle delete
|
||||||
|
:
|
||||||
|
else
|
||||||
|
if test "$remote_oid" = "$zero"
|
||||||
|
then
|
||||||
|
# New branch, examine all commits
|
||||||
|
range="$local_oid"
|
||||||
|
else
|
||||||
|
# Update to existing branch, examine new commits
|
||||||
|
range="$remote_oid..$local_oid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for WIP commit
|
||||||
|
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
|
||||||
|
if test -n "$commit"
|
||||||
|
then
|
||||||
|
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
169
hooks/pre-rebase.sample
Normal file
169
hooks/pre-rebase.sample
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||||
|
#
|
||||||
|
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||||
|
# its job, and can prevent the command from running by exiting with
|
||||||
|
# non-zero status.
|
||||||
|
#
|
||||||
|
# The hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- the upstream the series was forked from.
|
||||||
|
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||||
|
#
|
||||||
|
# This sample shows how to prevent topic branches that are already
|
||||||
|
# merged to 'next' branch from getting rebased, because allowing it
|
||||||
|
# would result in rebasing already published history.
|
||||||
|
|
||||||
|
publish=next
|
||||||
|
basebranch="$1"
|
||||||
|
if test "$#" = 2
|
||||||
|
then
|
||||||
|
topic="refs/heads/$2"
|
||||||
|
else
|
||||||
|
topic=`git symbolic-ref HEAD` ||
|
||||||
|
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$topic" in
|
||||||
|
refs/heads/??/*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0 ;# we do not interrupt others.
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Now we are dealing with a topic branch being rebased
|
||||||
|
# on top of master. Is it OK to rebase it?
|
||||||
|
|
||||||
|
# Does the topic really exist?
|
||||||
|
git show-ref -q "$topic" || {
|
||||||
|
echo >&2 "No such branch $topic"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is topic fully merged to master?
|
||||||
|
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||||
|
if test -z "$not_in_master"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is fully merged to master; better remove it."
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||||
|
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||||
|
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||||
|
if test "$only_next_1" = "$only_next_2"
|
||||||
|
then
|
||||||
|
not_in_topic=`git rev-list "^$topic" master`
|
||||||
|
if test -z "$not_in_topic"
|
||||||
|
then
|
||||||
|
echo >&2 "$topic is already up to date with master"
|
||||||
|
exit 1 ;# we could allow it, but there is no point.
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||||
|
/usr/bin/perl -e '
|
||||||
|
my $topic = $ARGV[0];
|
||||||
|
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||||
|
my (%not_in_next) = map {
|
||||||
|
/^([0-9a-f]+) /;
|
||||||
|
($1 => 1);
|
||||||
|
} split(/\n/, $ARGV[1]);
|
||||||
|
for my $elem (map {
|
||||||
|
/^([0-9a-f]+) (.*)$/;
|
||||||
|
[$1 => $2];
|
||||||
|
} split(/\n/, $ARGV[2])) {
|
||||||
|
if (!exists $not_in_next{$elem->[0]}) {
|
||||||
|
if ($msg) {
|
||||||
|
print STDERR $msg;
|
||||||
|
undef $msg;
|
||||||
|
}
|
||||||
|
print STDERR " $elem->[1]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$topic" "$not_in_next" "$not_in_master"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
<<\DOC_END
|
||||||
|
|
||||||
|
This sample hook safeguards topic branches that have been
|
||||||
|
published from being rewound.
|
||||||
|
|
||||||
|
The workflow assumed here is:
|
||||||
|
|
||||||
|
* Once a topic branch forks from "master", "master" is never
|
||||||
|
merged into it again (either directly or indirectly).
|
||||||
|
|
||||||
|
* Once a topic branch is fully cooked and merged into "master",
|
||||||
|
it is deleted. If you need to build on top of it to correct
|
||||||
|
earlier mistakes, a new topic branch is created by forking at
|
||||||
|
the tip of the "master". This is not strictly necessary, but
|
||||||
|
it makes it easier to keep your history simple.
|
||||||
|
|
||||||
|
* Whenever you need to test or publish your changes to topic
|
||||||
|
branches, merge them into "next" branch.
|
||||||
|
|
||||||
|
The script, being an example, hardcodes the publish branch name
|
||||||
|
to be "next", but it is trivial to make it configurable via
|
||||||
|
$GIT_DIR/config mechanism.
|
||||||
|
|
||||||
|
With this workflow, you would want to know:
|
||||||
|
|
||||||
|
(1) ... if a topic branch has ever been merged to "next". Young
|
||||||
|
topic branches can have stupid mistakes you would rather
|
||||||
|
clean up before publishing, and things that have not been
|
||||||
|
merged into other branches can be easily rebased without
|
||||||
|
affecting other people. But once it is published, you would
|
||||||
|
not want to rewind it.
|
||||||
|
|
||||||
|
(2) ... if a topic branch has been fully merged to "master".
|
||||||
|
Then you can delete it. More importantly, you should not
|
||||||
|
build on top of it -- other people may already want to
|
||||||
|
change things related to the topic as patches against your
|
||||||
|
"master", so if you need further changes, it is better to
|
||||||
|
fork the topic (perhaps with the same name) afresh from the
|
||||||
|
tip of "master".
|
||||||
|
|
||||||
|
Let's look at this example:
|
||||||
|
|
||||||
|
o---o---o---o---o---o---o---o---o---o "next"
|
||||||
|
/ / / /
|
||||||
|
/ a---a---b A / /
|
||||||
|
/ / / /
|
||||||
|
/ / c---c---c---c B /
|
||||||
|
/ / / \ /
|
||||||
|
/ / / b---b C \ /
|
||||||
|
/ / / / \ /
|
||||||
|
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||||
|
|
||||||
|
|
||||||
|
A, B and C are topic branches.
|
||||||
|
|
||||||
|
* A has one fix since it was merged up to "next".
|
||||||
|
|
||||||
|
* B has finished. It has been fully merged up to "master" and "next",
|
||||||
|
and is ready to be deleted.
|
||||||
|
|
||||||
|
* C has not merged to "next" at all.
|
||||||
|
|
||||||
|
We would want to allow C to be rebased, refuse A, and encourage
|
||||||
|
B to be deleted.
|
||||||
|
|
||||||
|
To compute (1):
|
||||||
|
|
||||||
|
git rev-list ^master ^topic next
|
||||||
|
git rev-list ^master next
|
||||||
|
|
||||||
|
if these match, topic has not merged in next at all.
|
||||||
|
|
||||||
|
To compute (2):
|
||||||
|
|
||||||
|
git rev-list master..topic
|
||||||
|
|
||||||
|
if this is empty, it is fully merged to "master".
|
||||||
|
|
||||||
|
DOC_END
|
||||||
24
hooks/pre-receive.sample
Normal file
24
hooks/pre-receive.sample
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to make use of push options.
|
||||||
|
# The example simply echoes all push options that start with 'echoback='
|
||||||
|
# and rejects all pushes when the "reject" push option is used.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "pre-receive".
|
||||||
|
|
||||||
|
if test -n "$GIT_PUSH_OPTION_COUNT"
|
||||||
|
then
|
||||||
|
i=0
|
||||||
|
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
|
||||||
|
do
|
||||||
|
eval "value=\$GIT_PUSH_OPTION_$i"
|
||||||
|
case "$value" in
|
||||||
|
echoback=*)
|
||||||
|
echo "echo from the pre-receive-hook: ${value#*=}" >&2
|
||||||
|
;;
|
||||||
|
reject)
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
42
hooks/prepare-commit-msg.sample
Normal file
42
hooks/prepare-commit-msg.sample
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to prepare the commit log message.
|
||||||
|
# Called by "git commit" with the name of the file that has the
|
||||||
|
# commit message, followed by the description of the commit
|
||||||
|
# message's source. The hook's purpose is to edit the commit
|
||||||
|
# message file. If the hook fails with a non-zero status,
|
||||||
|
# the commit is aborted.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||||
|
|
||||||
|
# This hook includes three examples. The first one removes the
|
||||||
|
# "# Please enter the commit message..." help message.
|
||||||
|
#
|
||||||
|
# The second includes the output of "git diff --name-status -r"
|
||||||
|
# into the message, just before the "git status" output. It is
|
||||||
|
# commented because it doesn't cope with --amend or with squashed
|
||||||
|
# commits.
|
||||||
|
#
|
||||||
|
# The third example adds a Signed-off-by line to the message, that can
|
||||||
|
# still be edited. This is rarely a good idea.
|
||||||
|
|
||||||
|
COMMIT_MSG_FILE=$1
|
||||||
|
COMMIT_SOURCE=$2
|
||||||
|
SHA1=$3
|
||||||
|
|
||||||
|
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
|
||||||
|
|
||||||
|
# case "$COMMIT_SOURCE,$SHA1" in
|
||||||
|
# ,|template,)
|
||||||
|
# /usr/bin/perl -i.bak -pe '
|
||||||
|
# print "\n" . `git diff --cached --name-status -r`
|
||||||
|
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
|
||||||
|
# *) ;;
|
||||||
|
# esac
|
||||||
|
|
||||||
|
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
|
||||||
|
# if test -z "$COMMIT_SOURCE"
|
||||||
|
# then
|
||||||
|
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
|
||||||
|
# fi
|
||||||
78
hooks/push-to-checkout.sample
Normal file
78
hooks/push-to-checkout.sample
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# An example hook script to update a checked-out tree on a git push.
|
||||||
|
#
|
||||||
|
# This hook is invoked by git-receive-pack(1) when it reacts to git
|
||||||
|
# push and updates reference(s) in its repository, and when the push
|
||||||
|
# tries to update the branch that is currently checked out and the
|
||||||
|
# receive.denyCurrentBranch configuration variable is set to
|
||||||
|
# updateInstead.
|
||||||
|
#
|
||||||
|
# By default, such a push is refused if the working tree and the index
|
||||||
|
# of the remote repository has any difference from the currently
|
||||||
|
# checked out commit; when both the working tree and the index match
|
||||||
|
# the current commit, they are updated to match the newly pushed tip
|
||||||
|
# of the branch. This hook is to be used to override the default
|
||||||
|
# behaviour; however the code below reimplements the default behaviour
|
||||||
|
# as a starting point for convenient modification.
|
||||||
|
#
|
||||||
|
# The hook receives the commit with which the tip of the current
|
||||||
|
# branch is going to be updated:
|
||||||
|
commit=$1
|
||||||
|
|
||||||
|
# It can exit with a non-zero status to refuse the push (when it does
|
||||||
|
# so, it must not modify the index or the working tree).
|
||||||
|
die () {
|
||||||
|
echo >&2 "$*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Or it can make any necessary changes to the working tree and to the
|
||||||
|
# index to bring them to the desired state when the tip of the current
|
||||||
|
# branch is updated to the new commit, and exit with a zero status.
|
||||||
|
#
|
||||||
|
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
|
||||||
|
# in order to emulate git fetch that is run in the reverse direction
|
||||||
|
# with git push, as the two-tree form of git read-tree -u -m is
|
||||||
|
# essentially the same as git switch or git checkout that switches
|
||||||
|
# branches while keeping the local changes in the working tree that do
|
||||||
|
# not interfere with the difference between the branches.
|
||||||
|
|
||||||
|
# The below is a more-or-less exact translation to shell of the C code
|
||||||
|
# for the default behaviour for git's push-to-checkout hook defined in
|
||||||
|
# the push_to_deploy() function in builtin/receive-pack.c.
|
||||||
|
#
|
||||||
|
# Note that the hook will be executed from the repository directory,
|
||||||
|
# not from the working tree, so if you want to perform operations on
|
||||||
|
# the working tree, you will have to adapt your code accordingly, e.g.
|
||||||
|
# by adding "cd .." or using relative paths.
|
||||||
|
|
||||||
|
if ! git update-index -q --ignore-submodules --refresh
|
||||||
|
then
|
||||||
|
die "Up-to-date check failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git diff-files --quiet --ignore-submodules --
|
||||||
|
then
|
||||||
|
die "Working directory has unstaged changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This is a rough translation of:
|
||||||
|
#
|
||||||
|
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
|
||||||
|
if git cat-file -e HEAD 2>/dev/null
|
||||||
|
then
|
||||||
|
head=HEAD
|
||||||
|
else
|
||||||
|
head=$(git hash-object -t tree --stdin </dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git diff-index --quiet --cached --ignore-submodules $head --
|
||||||
|
then
|
||||||
|
die "Working directory has staged changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git read-tree -u -m "$commit"
|
||||||
|
then
|
||||||
|
die "Could not update working tree to new HEAD"
|
||||||
|
fi
|
||||||
128
hooks/update.sample
Normal file
128
hooks/update.sample
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to block unannotated tags from entering.
|
||||||
|
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "update".
|
||||||
|
#
|
||||||
|
# Config
|
||||||
|
# ------
|
||||||
|
# hooks.allowunannotated
|
||||||
|
# This boolean sets whether unannotated tags will be allowed into the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowdeletetag
|
||||||
|
# This boolean sets whether deleting tags will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.allowmodifytag
|
||||||
|
# This boolean sets whether a tag may be modified after creation. By default
|
||||||
|
# it won't be.
|
||||||
|
# hooks.allowdeletebranch
|
||||||
|
# This boolean sets whether deleting branches will be allowed in the
|
||||||
|
# repository. By default they won't be.
|
||||||
|
# hooks.denycreatebranch
|
||||||
|
# This boolean sets whether remotely creating branches will be denied
|
||||||
|
# in the repository. By default this is allowed.
|
||||||
|
#
|
||||||
|
|
||||||
|
# --- Command line
|
||||||
|
refname="$1"
|
||||||
|
oldrev="$2"
|
||||||
|
newrev="$3"
|
||||||
|
|
||||||
|
# --- Safety check
|
||||||
|
if [ -z "$GIT_DIR" ]; then
|
||||||
|
echo "Don't run this script from the command line." >&2
|
||||||
|
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||||
|
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||||
|
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Config
|
||||||
|
allowunannotated=$(git config --type=bool hooks.allowunannotated)
|
||||||
|
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
|
||||||
|
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
|
||||||
|
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
|
||||||
|
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
|
||||||
|
|
||||||
|
# check for no description
|
||||||
|
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||||
|
case "$projectdesc" in
|
||||||
|
"Unnamed repository"* | "")
|
||||||
|
echo "*** Project description file hasn't been set" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Check types
|
||||||
|
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||||
|
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
|
||||||
|
if [ "$newrev" = "$zero" ]; then
|
||||||
|
newrev_type=delete
|
||||||
|
else
|
||||||
|
newrev_type=$(git cat-file -t $newrev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$refname","$newrev_type" in
|
||||||
|
refs/tags/*,commit)
|
||||||
|
# un-annotated tag
|
||||||
|
short_refname=${refname##refs/tags/}
|
||||||
|
if [ "$allowunannotated" != "true" ]; then
|
||||||
|
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||||
|
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,delete)
|
||||||
|
# delete tag
|
||||||
|
if [ "$allowdeletetag" != "true" ]; then
|
||||||
|
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/tags/*,tag)
|
||||||
|
# annotated tag
|
||||||
|
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "*** Tag '$refname' already exists." >&2
|
||||||
|
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,commit)
|
||||||
|
# branch
|
||||||
|
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||||
|
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/heads/*,delete)
|
||||||
|
# delete branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
refs/remotes/*,commit)
|
||||||
|
# tracking branch
|
||||||
|
;;
|
||||||
|
refs/remotes/*,delete)
|
||||||
|
# delete tracking branch
|
||||||
|
if [ "$allowdeletebranch" != "true" ]; then
|
||||||
|
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Anything else (is there anything else?)
|
||||||
|
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Finished
|
||||||
|
exit 0
|
||||||
BIN
icon128.png
Normal file
BIN
icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
BIN
icon16.png
Normal file
BIN
icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
icon48.png
Normal file
BIN
icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
6
info/exclude
Normal file
6
info/exclude
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
||||||
18
logo.svg
Normal file
18
logo.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,1.06336,2.75971)">
|
||||||
|
<path d="M29.084,77.983C31.181,58.967 29.062,50.529 36.754,44.744C49.64,35.054 102.599,34.51 136.469,35.795L206.142,42.187C217.006,41.529 224.264,49.431 221.483,81.179C256.838,86.568 265.736,95.737 369.139,91.406C405.197,86.319 441.618,78.333 475.885,87.571C487.301,84.124 486.858,172.918 482.916,288.919C485.788,339.392 486.242,393.318 477.164,460.864C418.805,476.382 386.339,470.972 354.437,466.617L250.247,464.06C172.39,467.443 122.39,464.961 77.024,461.503C49.237,458.528 30.449,451.695 26.527,438.492C20.656,339.325 28.5,304.166 33.558,256C34.293,198.175 36.5,170.61 33.558,164.275C31.604,121.252 32.745,90.269 29.084,77.983Z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,-6.06367,-10.8664)">
|
||||||
|
<g transform="matrix(1,0,0,1,-5.11361,8.94881)">
|
||||||
|
<path d="M73.189,192.4L75.745,218.607L144.779,205.823L120.489,270.702L93.004,335.581L205.503,336.859L201.029,302.981L164.594,307.456L131.356,310.652L146.697,256L182.492,178.976L73.189,192.4Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.883041,0,0,1.19429,27.9716,-69.6576)">
|
||||||
|
<path d="M228.514,253.24L267.506,256L315.446,256L266.227,319.6L309.693,318.322L332.065,313.848L325.034,338.777L227.236,335.581L276.454,274.217L222.762,279.331L228.514,253.24Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.01749,0,0,1.11861,-6.04128,-40.2004)">
|
||||||
|
<path d="M345.488,224.999C345.488,224.36 345.488,247.487 345.488,247.487L377.448,242.257L400.459,241.618L381.923,283.864L349.963,338.941L420.914,334.941L440.729,334.941L454.152,309.373L390.232,314.487L432.419,220.524C432.419,220.524 345.488,225.638 345.488,224.999Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
24
manifest.json
Normal file
24
manifest.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "SleepyTabs",
|
||||||
|
"description": "Suspend tabs when not in use",
|
||||||
|
"homepage_url": "https://sleepytabs.app",
|
||||||
|
"version": "1.3",
|
||||||
|
"manifest_version": 3,
|
||||||
|
"icons": {
|
||||||
|
"16": "icon16.png",
|
||||||
|
"48": "icon48.png",
|
||||||
|
"128": "icon128.png"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "service_worker.js"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"alarms",
|
||||||
|
"storage",
|
||||||
|
"tabs"
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
},
|
||||||
|
"options_page": "options.html"
|
||||||
|
}
|
||||||
70
options.css
Normal file
70
options.css
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
html {
|
||||||
|
padding: 50px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 130%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1024px;
|
||||||
|
margin-left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
table tr > td:nth-child(1) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
table tr > td:nth-child(2) {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
table tr:first-child > td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
table td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
outline: none;
|
||||||
|
border: 2px solid #eee;
|
||||||
|
background-color: #eee;
|
||||||
|
color: #222;
|
||||||
|
height: 45px;
|
||||||
|
padding: 0 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 45px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 150px;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #ccc;
|
||||||
|
background-color: #ccc;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
button#save {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #4287f5;
|
||||||
|
border-color: #4287f5;
|
||||||
|
}
|
||||||
|
button#save:hover {
|
||||||
|
background-color: #4287f5;
|
||||||
|
border-color: #4287f5;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
input[type=text], select, textarea {
|
||||||
|
min-width: 250px;
|
||||||
|
width: 100%;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
height: 125px;
|
||||||
|
}
|
||||||
57
options.html
Normal file
57
options.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="options.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<img src="logo.svg" width="128" />
|
||||||
|
<h1>SleepyTabs</h1>
|
||||||
|
<h5>Version 1.3</h5>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="delay">Automatically Suspend</label><br>(in minutes)</td>
|
||||||
|
<td>
|
||||||
|
<select id="delay">
|
||||||
|
<option>5</option>
|
||||||
|
<option>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
<option>30</option>
|
||||||
|
<option>45</option>
|
||||||
|
<option>60</option>
|
||||||
|
<option value="-1">never</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="incognito">Automatically Suspend incognito tabs?</label></td>
|
||||||
|
<td><input id="incognito" type="checkbox" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="audible">Automatically Suspend tabs playing audio?</label></td>
|
||||||
|
<td><input id="audible" type="checkbox" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="whitelistPages">Whitelisted Pages</label><br>(one per line)</td>
|
||||||
|
<td><textarea id="whitelistPages"></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="whitelistDomains">Whitelisted Domains</label><br>(one per line)</td>
|
||||||
|
<td><textarea id="whitelistDomains"></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<button type="button" id="cancel">Cancel</button>
|
||||||
|
<button type="button" id="save">Save</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
options.js
Normal file
34
options.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
let delay = document.querySelector('#delay');
|
||||||
|
let incognito = document.querySelector('#incognito');
|
||||||
|
let audible = document.querySelector('#audible');
|
||||||
|
let whitelistPages = document.querySelector('#whitelistPages');
|
||||||
|
let whitelistDomains = document.querySelector('#whitelistDomains');
|
||||||
|
let btnCancel = document.querySelector('#cancel');
|
||||||
|
let btnSave = document.querySelector('#save');
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage({type: 'GET_OPTIONS'}, null, (options) => {
|
||||||
|
delay.value = options.suspendAfterMins;
|
||||||
|
incognito.checked = options.suspendIncognito;
|
||||||
|
audible.checked = options.suspendAudible;
|
||||||
|
whitelistPages.value = options.whitelistPages.join("\n");
|
||||||
|
whitelistDomains.value = options.whitelistDomains.join("\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCancel.onclick = () => {
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
btnSave.onclick = () => {
|
||||||
|
let whitelistPagesValue = whitelistPages.value.trim();
|
||||||
|
let whitelistDomainsValue = whitelistDomains.value.trim();
|
||||||
|
let options = {
|
||||||
|
suspendAfterMins: delay.value,
|
||||||
|
suspendIncognito: incognito.value,
|
||||||
|
suspendAudible: audible.value,
|
||||||
|
whitelistPages: whitelistPagesValue ? whitelistPagesValue.split("\n") : [],
|
||||||
|
whitelistDomains: whitelistDomainsValue ? whitelistDomainsValue.split("\n") : [],
|
||||||
|
};
|
||||||
|
chrome.runtime.sendMessage({type: 'SET_OPTIONS', options: options}, () => {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
18
popup.css
Normal file
18
popup.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
html, body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000;
|
||||||
|
display: block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
background-color: #ccc;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
background-color: #999;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
18
popup.html
Normal file
18
popup.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#" class="suspend-tab">Suspend this tab now</a>
|
||||||
|
<a href="#" class="un-suspend-tab">Un-suspend this tab now</a>
|
||||||
|
<a href="#" class="suspend-other">Suspend other tabs</a>
|
||||||
|
<a href="#" class="un-suspend-all-tabs">Un-suspend all tabs</a>
|
||||||
|
<a href="#" class="never-suspend-page">Never suspend page</a>
|
||||||
|
<a href="#" class="remove-never-suspend-page">Remove never suspend page</a>
|
||||||
|
<a href="#" class="never-suspend-site">Never suspend site</a>
|
||||||
|
<a href="#" class="remove-never-suspend-site">Remove never suspend site</a>
|
||||||
|
<a href="options.html" target="_new">Options...</a>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
109
popup.js
Normal file
109
popup.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
function getCurrentTab(callback) {
|
||||||
|
chrome.tabs.query(
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
currentWindow: true
|
||||||
|
},
|
||||||
|
(_tabs) => {
|
||||||
|
callback(_tabs[0]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(callback) {
|
||||||
|
chrome.storage.local.get(['sleepyTabsOptions'], (result) => {
|
||||||
|
callback(result.sleepyTabsOptions || {
|
||||||
|
suspendAfterMins: 30,
|
||||||
|
suspendIncognito: false,
|
||||||
|
whitelistPages: [],
|
||||||
|
whitelistDomains: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
let _element = this;
|
||||||
|
getCurrentTab((_tab) => {
|
||||||
|
switch(_element.className) {
|
||||||
|
case 'suspend-tab':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'SUSPEND_TAB',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'un-suspend-tab':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'UNSUSPEND_TAB',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'suspend-other':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'SUSPEND_OTHER_TABS',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'un-suspend-all-tabs':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'UNSUSPEND_ALL_TABS',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'never-suspend-page':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'NEVER_SUSPEND_PAGE',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'remove-never-suspend-page':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'REMOVE_NEVER_SUSPEND_PAGE',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'never-suspend-site':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'NEVER_SUSPEND_DOMAIN',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
case 'remove-never-suspend-site':
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'REMOVE_NEVER_SUSPEND_DOMAIN',
|
||||||
|
tab: _tab
|
||||||
|
}, window.close);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('a[href="#"]').forEach((_element, _index) => {
|
||||||
|
_element.onclick = onClick;
|
||||||
|
});
|
||||||
|
|
||||||
|
let linkSuspendTab = document.querySelector('a.suspend-tab');
|
||||||
|
let linkUnSuspendTab = document.querySelector('a.un-suspend-tab');
|
||||||
|
let linkSuspendOther = document.querySelector('a.suspend-other');
|
||||||
|
let linkUnSuspendAll = document.querySelector('a.un-suspend-all');
|
||||||
|
let linkNeverSuspendPage = document.querySelector('a.never-suspend-page');
|
||||||
|
let linkRemoveNeverSuspendPage = document.querySelector('a.remove-never-suspend-page');
|
||||||
|
let linkNeverSuspendSite = document.querySelector('a.never-suspend-site');
|
||||||
|
let linkRemoveNeverSuspendSite = document.querySelector('a.remove-never-suspend-site');
|
||||||
|
|
||||||
|
getCurrentTab((_tab) => {
|
||||||
|
getOptions((options) => {
|
||||||
|
linkSuspendTab.style.display = _tab.url.match(/^chrome-extension:\S+suspended.html/) ? 'none' : 'block';
|
||||||
|
linkUnSuspendTab.style.display = linkSuspendTab.style.display == 'none' ? 'block' : 'none';
|
||||||
|
if(_tab.url.match(/^chrome/)) {
|
||||||
|
linkNeverSuspendPage.style.display = 'none';
|
||||||
|
linkRemoveNeverSuspendPage.style.display = 'none';
|
||||||
|
linkNeverSuspendSite.style.display = 'none';
|
||||||
|
linkRemoveNeverSuspendSite.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
linkNeverSuspendPage.style.display = options.whitelistPages.indexOf(_tab.url.replace(/\?.*$/, '')) > -1 ? 'none' : 'block';
|
||||||
|
linkRemoveNeverSuspendPage.style.display = linkNeverSuspendPage.style.display == 'none' ? 'block' : 'none';
|
||||||
|
linkNeverSuspendSite.style.display = options.whitelistDomains.indexOf(_tab.url.replace(/(^https?:\/\/[^/]+).*$/, '$1')) > -1 ? 'none' : 'block';
|
||||||
|
linkRemoveNeverSuspendSite.style.display = linkNeverSuspendSite.style.display == 'none' ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
223
service_worker.js
Normal file
223
service_worker.js
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
//-----------
|
||||||
|
// FUNCTIONS
|
||||||
|
//-----------
|
||||||
|
function getInactiveTabs(callback) {
|
||||||
|
chrome.storage.local.get(['sleepyTabsInactiveTabs'], (result) => {
|
||||||
|
callback(result.sleepyTabsInactiveTabs || {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(callback) {
|
||||||
|
chrome.storage.local.get(['sleepyTabsOptions'], (result) => {
|
||||||
|
callback(result.sleepyTabsOptions || {
|
||||||
|
suspendAfterMins: 30,
|
||||||
|
suspendIncognito: false,
|
||||||
|
suspendAudible: false,
|
||||||
|
whitelistPages: [],
|
||||||
|
whitelistDomains: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function inWhitelist(url, options) {
|
||||||
|
let inWhiteList = false;
|
||||||
|
options.whitelistPages.every((whitelistUrl, index) => {
|
||||||
|
if(url.replace(/\?.*$/, '') == whitelistUrl) {
|
||||||
|
inWhiteList = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if(!inWhiteList) {
|
||||||
|
options.whitelistDomains.every((whitelistUrl, index) => {
|
||||||
|
if(url.match(whitelistUrl)) {
|
||||||
|
inWhiteList = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return inWhiteList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInactiveTabs(inactiveTabs, callback) {
|
||||||
|
chrome.storage.local.set({
|
||||||
|
sleepyTabsInactiveTabs: inactiveTabs
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOptions(options, callback) {
|
||||||
|
chrome.storage.local.set({
|
||||||
|
sleepyTabsOptions: options
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function suspendTab(_tab) {
|
||||||
|
getInactiveTabs((inactiveTabs) => {
|
||||||
|
let _params = new URLSearchParams();
|
||||||
|
_params.append('url', _tab.url);
|
||||||
|
_params.append('title', _tab.title);
|
||||||
|
_params.append('favIconUrl', _tab.favIconUrl);
|
||||||
|
chrome.tabs.update(_tab.id, {
|
||||||
|
url: 'suspended.html?' + _params
|
||||||
|
});
|
||||||
|
delete inactiveTabs[_tab.id];
|
||||||
|
setInactiveTabs(inactiveTabs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function unSuspendTab(_tab, _updateInactiveTabList = true) {
|
||||||
|
getInactiveTabs((inactiveTabs) => {
|
||||||
|
if(_tab.url.match('chrome-extension://' + chrome.runtime.id + '/suspended.html')) { // check if currently suspended
|
||||||
|
chrome.tabs.sendMessage(_tab.id, {type: 'UNSUSPEND'}, () => {
|
||||||
|
if(_updateInactiveTabList) {
|
||||||
|
inactiveTabs[_tab.id] = Date.now();
|
||||||
|
setInactiveTabs(inactiveTabs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAlarm() {
|
||||||
|
stopAlarm(); // just to make sure it's not already running
|
||||||
|
setInactiveTabs({}); // re-start timer for inactive tabs
|
||||||
|
chrome.alarms.create('sleepyTabsAlarm', {
|
||||||
|
when: Date.now(),
|
||||||
|
periodInMinutes: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAlarm() {
|
||||||
|
chrome.alarms.clear('sleepyTabsAlarm');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
// LISTENERS
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
// check to suspend tabs (once per minute)
|
||||||
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||||
|
if(alarm.name == 'sleepyTabsAlarm') {
|
||||||
|
const _currentTime = Date.now();
|
||||||
|
getOptions((options) => {
|
||||||
|
getInactiveTabs((inactiveTabs) => {
|
||||||
|
chrome.windows.getAll({populate: true}, (_windows) => {
|
||||||
|
_windows.forEach((_window, _windowIndex) => {
|
||||||
|
_window.tabs.forEach((_tab, _tabIndex) => {
|
||||||
|
if(!_tab.active &&
|
||||||
|
!_tab.url.match(/^chrome/) &&
|
||||||
|
!inWhitelist(_tab.url, options) &&
|
||||||
|
!(options.suspendIncognito && _tab.incognito) &&
|
||||||
|
!(options.suspendAudible && _tab.audible)) {
|
||||||
|
if(!inactiveTabs[_tab.id]) {
|
||||||
|
inactiveTabs[_tab.id] = _currentTime;
|
||||||
|
setInactiveTabs(inactiveTabs);
|
||||||
|
} else if(_currentTime - inactiveTabs[_tab.id] >= options.suspendAfterMins * 60000) {
|
||||||
|
suspendTab(_tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove tab from inactive list when activated
|
||||||
|
chrome.tabs.onActivated.addListener((activeInfo) => {
|
||||||
|
getInactiveTabs((inactiveTabs) => {
|
||||||
|
delete inactiveTabs[activeInfo.tabId];
|
||||||
|
setInactiveTabs(inactiveTabs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove tab from inactive list when removed
|
||||||
|
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
|
||||||
|
getInactiveTabs((inactiveTabs) => {
|
||||||
|
delete inactiveTabs[tabId];
|
||||||
|
setInactiveTabs(inactiveTabs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// listen for messages
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
getOptions((options) => {
|
||||||
|
switch(request.type) {
|
||||||
|
case 'GET_OPTIONS':
|
||||||
|
sendResponse(options);
|
||||||
|
break;
|
||||||
|
case 'SET_OPTIONS':
|
||||||
|
setOptions(request.options);
|
||||||
|
if(options.suspendAfterMins > 0 && request.options.suspendAfterMins == 0) {
|
||||||
|
stopAlarm();
|
||||||
|
} else if(options.suspendAfterMins == 0 && request.options.suspendAfterMins > 0) {
|
||||||
|
startAlarm();
|
||||||
|
}
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'SUSPEND_TAB':
|
||||||
|
suspendTab(request.tab);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'UNSUSPEND_TAB':
|
||||||
|
unSuspendTab(request.tab);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'SUSPEND_OTHER_TABS':
|
||||||
|
chrome.tabs.query({windowId: request.tab.windowId}, (_tabs) => {
|
||||||
|
_tabs.forEach((_tab, _tabIndex) => {
|
||||||
|
if(_tab.id != request.tab.id && !_tab.url.match(/^chrome/)) {
|
||||||
|
suspendTab(_tab);
|
||||||
|
sendResponse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'UNSUSPEND_ALL_TABS':
|
||||||
|
setInactiveTabs({});
|
||||||
|
chrome.tabs.query({windowId: request.tab.windowId}, (_tabs) => {
|
||||||
|
_tabs.forEach((_tab, _tabIndex) => {
|
||||||
|
unSuspendTab(_tab, false);
|
||||||
|
sendResponse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'NEVER_SUSPEND_PAGE':
|
||||||
|
options.whitelistPages.push(request.tab.url.replace(/(^.*)\?.*$/, '$1'));
|
||||||
|
setOptions(options);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'REMOVE_NEVER_SUSPEND_PAGE':
|
||||||
|
options.whitelistPages.splice(options.whitelistPages.indexOf(request.tab.url.replace(/(^.*)\?.*$/, '$1')));
|
||||||
|
setOptions(options);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'NEVER_SUSPEND_DOMAIN':
|
||||||
|
options.whitelistDomains.push(request.tab.url.replace(/(^https?:\/\/[^/]+).*$/, '$1'));
|
||||||
|
setOptions(options);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
case 'REMOVE_NEVER_SUSPEND_DOMAIN':
|
||||||
|
options.whitelistDomains.splice(options.whitelistDomains.indexOf(request.tab.url.replace(/(^https?:\/\/[^/]+).*$/, '$1')));
|
||||||
|
setOptions(options);
|
||||||
|
sendResponse();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//------
|
||||||
|
// INIT
|
||||||
|
//------
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
getOptions((options) => {
|
||||||
|
if(options.suspendAfterMins > 0) {
|
||||||
|
startAlarm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
54
suspended.css
Normal file
54
suspended.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
html {
|
||||||
|
background-color: #222;
|
||||||
|
color: #eee;
|
||||||
|
font-size: 130%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html > img {
|
||||||
|
margin-bottom: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.un-suspend {
|
||||||
|
border: 2px solid #eee;
|
||||||
|
outline: none;
|
||||||
|
background-color: #eee;
|
||||||
|
color: #222;
|
||||||
|
height: 60px;
|
||||||
|
padding: 0 30px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 45px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button.un-suspend:hover {
|
||||||
|
background-color: #222;
|
||||||
|
border-color: #eee;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
#url {
|
||||||
|
color: #eee !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #fcba03;
|
||||||
|
}
|
||||||
|
#donate-area {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100px;
|
||||||
|
font-weight: 200;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 150px);
|
||||||
|
}
|
||||||
|
#donate-area > h2 {
|
||||||
|
font-size: 250%;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
#donate-area > h3 {
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
#donate-area > h3 > a {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
22
suspended.html
Normal file
22
suspended.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="suspended.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="//:0" width="64" />
|
||||||
|
<h1></h1>
|
||||||
|
<a id="url"></a>
|
||||||
|
<br>
|
||||||
|
<button type="button" class="un-suspend">Un-Suspend Tab</button>
|
||||||
|
<div id="donate-area">
|
||||||
|
<h2>Like this extension?</h2>
|
||||||
|
<h3>
|
||||||
|
<a href="https://www.paypal.com/donate?hosted_button_id=C342NKTTB2U8S" target="_new">BUY ME A BEER</a> 🍺
|
||||||
|
AND/OR DONATE TO
|
||||||
|
<a href="https://www.doctorswithoutborders.ca/donate-now" target="_blank">DOCTORS WITHOUT BORDERS</a> 💊
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<script src="suspended.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
suspended.js
Normal file
36
suspended.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
let _params = new URLSearchParams(window.location.search);
|
||||||
|
let img = document.querySelector('img');
|
||||||
|
let h1 = document.querySelector('h1');
|
||||||
|
let url = document.querySelector('#url');
|
||||||
|
let btnUnSuspend = document.querySelector('button.un-suspend');
|
||||||
|
|
||||||
|
function setFavicon(favImg){
|
||||||
|
let headTitle = document.querySelector('head');
|
||||||
|
let setFavicon = document.createElement('link');
|
||||||
|
setFavicon.setAttribute('rel','shortcut icon');
|
||||||
|
setFavicon.setAttribute('href',favImg);
|
||||||
|
headTitle.appendChild(setFavicon);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unSuspend() {
|
||||||
|
if(history.length > 1) {
|
||||||
|
history.back();
|
||||||
|
} else {
|
||||||
|
location.href = _params.get('url');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnUnSuspend.onclick = unSuspend;
|
||||||
|
|
||||||
|
document.title = _params.get('title') + ' [zzZ]';
|
||||||
|
img.src = _params.get('favIconUrl') || 'img/logo.svg';
|
||||||
|
setFavicon(_params.get('favIconUrl') || 'img/logo.svg');
|
||||||
|
h1.innerText = _params.get('title');
|
||||||
|
url.innerText = _params.get('url');
|
||||||
|
url.href = _params.get('url');
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
if(message.type == 'UNSUSPEND') {
|
||||||
|
sendResponse(); // need to send before un-suspending
|
||||||
|
unSuspend();
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user