Merge pull request #1 from janeczku/master

update from janeczku/calibre-web
pull/92/head
Ethan Lin 8 years ago committed by GitHub
commit 6d99b9bc25

@ -56,7 +56,6 @@ LOG_DIR = check_setting_str(CFG, 'General', 'LOG_DIR', os.getcwd())
PORT = check_setting_int(CFG, 'General', 'PORT', 8083) PORT = check_setting_int(CFG, 'General', 'PORT', 8083)
NEWEST_BOOKS = check_setting_str(CFG, 'General', 'NEWEST_BOOKS', 60) NEWEST_BOOKS = check_setting_str(CFG, 'General', 'NEWEST_BOOKS', 60)
RANDOM_BOOKS = check_setting_int(CFG, 'General', 'RANDOM_BOOKS', 4) RANDOM_BOOKS = check_setting_int(CFG, 'General', 'RANDOM_BOOKS', 4)
DEFAULT_LANG = check_setting_str(CFG, 'General', 'DEFAULT_LANG', "")
CheckSection('Advanced') CheckSection('Advanced')
TITLE_REGEX = check_setting_str(CFG, 'Advanced', 'TITLE_REGEX', '^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+') TITLE_REGEX = check_setting_str(CFG, 'Advanced', 'TITLE_REGEX', '^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
@ -73,7 +72,7 @@ if DB_ROOT == "":
configval = {"DB_ROOT": DB_ROOT, "APP_DB_ROOT": APP_DB_ROOT, "MAIN_DIR": MAIN_DIR, "LOG_DIR": LOG_DIR, "PORT": PORT, configval = {"DB_ROOT": DB_ROOT, "APP_DB_ROOT": APP_DB_ROOT, "MAIN_DIR": MAIN_DIR, "LOG_DIR": LOG_DIR, "PORT": PORT,
"NEWEST_BOOKS": NEWEST_BOOKS, "DEVELOPMENT": DEVELOPMENT, "TITLE_REGEX": TITLE_REGEX, "NEWEST_BOOKS": NEWEST_BOOKS, "DEVELOPMENT": DEVELOPMENT, "TITLE_REGEX": TITLE_REGEX,
"PUBLIC_REG": PUBLIC_REG, "UPLOADING": UPLOADING, "ANON_BROWSE": ANON_BROWSE, "DEFAULT_LANG": DEFAULT_LANG} "PUBLIC_REG": PUBLIC_REG, "UPLOADING": UPLOADING, "ANON_BROWSE": ANON_BROWSE}
def save_config(configval): def save_config(configval):
@ -86,7 +85,6 @@ def save_config(configval):
new_config['General']['LOG_DIR'] = configval["LOG_DIR"] new_config['General']['LOG_DIR'] = configval["LOG_DIR"]
new_config['General']['PORT'] = configval["PORT"] new_config['General']['PORT'] = configval["PORT"]
new_config['General']['NEWEST_BOOKS'] = configval["NEWEST_BOOKS"] new_config['General']['NEWEST_BOOKS'] = configval["NEWEST_BOOKS"]
new_config['General']['DEFAULT_LANG'] = configval["DEFAULT_LANG"]
new_config['Advanced'] = {} new_config['Advanced'] = {}
new_config['Advanced']['TITLE_REGEX'] = configval["TITLE_REGEX"] new_config['Advanced']['TITLE_REGEX'] = configval["TITLE_REGEX"]
new_config['Advanced']['DEVELOPMENT'] = int(configval["DEVELOPMENT"]) new_config['Advanced']['DEVELOPMENT'] = int(configval["DEVELOPMENT"])

@ -1,3 +0,0 @@
.annotator-adder {
width: 80px;
}

@ -1,384 +0,0 @@
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%);
background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%);
background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%);
background-repeat: repeat-x;
border-color: #e0e0e0;
border-color: #ccc;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
}
.btn-default:active,
.btn-default.active {
background-color: #e6e6e6;
border-color: #e0e0e0;
}
.btn-primary {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
background-repeat: repeat-x;
border-color: #2d6ca2;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
}
.btn-primary:active,
.btn-primary.active {
background-color: #3071a9;
border-color: #2d6ca2;
}
.btn-success {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
background-repeat: repeat-x;
border-color: #419641;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
}
.btn-success:active,
.btn-success.active {
background-color: #449d44;
border-color: #419641;
}
.btn-warning {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
background-repeat: repeat-x;
border-color: #eb9316;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
}
.btn-warning:active,
.btn-warning.active {
background-color: #ec971f;
border-color: #eb9316;
}
.btn-danger {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
background-repeat: repeat-x;
border-color: #c12e2a;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
}
.btn-danger:active,
.btn-danger.active {
background-color: #c9302c;
border-color: #c12e2a;
}
.btn-info {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
background-repeat: repeat-x;
border-color: #2aabd2;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
}
.btn-info:active,
.btn-info.active {
background-color: #31b0d5;
border-color: #2aabd2;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #357ebd;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
}
.navbar {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%);
background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
background-repeat: repeat-x;
border-radius: 4px;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
}
.navbar .navbar-nav > .active > a {
background-color: #f8f8f8;
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
}
.navbar-inverse {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%);
background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
}
.navbar-inverse .navbar-nav > .active > a {
background-color: #222222;
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.alert-success {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%);
background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
background-repeat: repeat-x;
border-color: #b2dba1;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
}
.alert-info {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%);
background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
background-repeat: repeat-x;
border-color: #9acfea;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
}
.alert-warning {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%);
background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
background-repeat: repeat-x;
border-color: #f5e79e;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
}
.alert-danger {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%);
background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
background-repeat: repeat-x;
border-color: #dca7a7;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
}
.progress {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%);
background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
}
.progress-bar {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
}
.progress-bar-success {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
}
.progress-bar-info {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
}
.progress-bar-warning {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
}
.progress-bar-danger {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9;
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
background-repeat: repeat-x;
border-color: #3278b3;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.panel-default > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%);
background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
}
.panel-primary > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
}
.panel-success > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%);
background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
}
.panel-info > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%);
background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
}
.panel-warning > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%);
background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
}
.panel-danger > .panel-heading {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%);
background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
}
.well {
background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%);
background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
background-repeat: repeat-x;
border-color: #dcdcdc;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,25 +0,0 @@
@font-face {
font-family: 'Libre Baskerville';
font-style: normal;
font-weight: 400;
src: local('Libre Baskerville'), local('LibreBaskerville-Regular'), url("../fonts/LibreBaskerville-Regular.ttf") format('truetype');
}
@font-face {
font-family: 'Libre Baskerville';
font-style: normal;
font-weight: 700;
src: local('Libre Baskerville Bold'), local('LibreBaskerville-Bold'), url("../fonts/LibreBaskerville-Bold") format('truetype');
}
body{
color: #444;
line-height: 21px;
font-size: 14px;
}
h1, h2, h3, h4, h5, h6{
font-family: 'Libre Baskerville';
color: #45b29d !important;
font-size: 20px !important;
}

@ -1,321 +0,0 @@
@font-face {
font-family: 'EntypoRegular';
src: url('../fonts/entypo.eot');
src: url('../fonts/entypo.eot?#iefix') format('embedded-opentype'),
url('../fonts/entypo.woff') format('woff'),
url('../fonts/entypo.ttf') format('truetype'),
url('../fonts/entypo.svg#EntypoRegular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'EntypoSocialRegular';
src: url('../fonts/entypo-social.eot');
src: url('../fonts/entypo-social.eot?#iefix') format('embedded-opentype'),
url('../fonts/entypo-social.woff') format('woff'),
url('../fonts/entypo-social.ttf') format('truetype'),
url('../fonts/entypo-social.svg#EntypoRegular') format('svg');
font-weight: normal;
font-style: normal;
}
.entypo {
font-family: 'EntypoRegular';
font-size: 2em;
font-weight: normal;
line-height: 0;
}
.entypo-social {
font-family: 'EntypoSocialRegular';
font-size: 2em;
font-weight: normal;
line-height: 0;
}
.entypo.phone:before{ content:'\1F4DE'; }
.entypo.mobile:before{ content:'\1F4F1'; }
.entypo.mouse:before{ content:'\E789'; }
.entypo.address:before{ content:'\E723'; }
.entypo.mail:before{ content:'\2709'; }
.entypo.paper-plane:before{ content:'\1F53F'; }
.entypo.pencil:before{ content:'\270E'; }
.entypo.feather:before{ content:'\2712'; }
.entypo.attach:before{ content:'\1F4CE'; }
.entypo.inbox:before{ content:'\E777'; }
.entypo.reply:before{ content:'\E712'; }
.entypo.reply-all:before{ content:'\E713'; }
.entypo.forward:before{ content:'\27A6'; }
.entypo.user:before{ content:'\1F464'; }
.entypo.users:before{ content:'\1F465'; }
.entypo.add-user:before{ content:'\E700'; }
.entypo.vcard:before{ content:'\E722'; }
.entypo.export:before{ content:'\E715'; }
.entypo.location:before{ content:'\E724'; }
.entypo.map:before{ content:'\E727'; }
.entypo.compass:before{ content:'\E728'; }
.entypo.direction:before{ content:'\27A2'; }
.entypo.hair-cross:before{ content:'\1F3AF'; }
.entypo.share:before{ content:'\E73C'; }
.entypo.shareable:before{ content:'\E73E'; }
.entypo.heart:before{ content:'\2665'; }
.entypo.heart-empty:before{ content:'\2661'; }
.entypo.star:before{ content:'\2605'; }
.entypo.star-empty:before{ content:'\2606'; }
.entypo.thumbs-up:before{ content:'\1F44D'; }
.entypo.thumbs-down:before{ content:'\1F44E'; }
.entypo.chat:before{ content:'\E720'; }
.entypo.comment:before{ content:'\E718'; }
.entypo.quote:before{ content:'\275E'; }
.entypo.home:before{ content:'\2302'; }
.entypo.popup:before{ content:'\E74C'; }
.entypo.search:before{ content:'\1F50D'; }
.entypo.flashlight:before{ content:'\1F526'; }
.entypo.print:before{ content:'\E716'; }
.entypo.bell:before{ content:'\1F514'; }
.entypo.link:before{ content:'\1F517'; }
.entypo.flag:before{ content:'\2691'; }
.entypo.cog:before{ content:'\2699'; }
.entypo.tools:before{ content:'\2692'; }
.entypo.trophy:before{ content:'\1F3C6'; }
.entypo.tag:before{ content:'\E70C'; }
.entypo.camera:before{ content:'\1F4F7'; }
.entypo.megaphone:before{ content:'1F4E3'; }
.entypo.moon:before{ content:'\0045'; }
.entypo.palette:before{ content:'\1F3A8'; }
.entypo.leaf:before{ content:'\1F342'; }
.entypo.note:before{ content:'\266A'; }
.entypo.beamed-note:before{ content:'\266B'; }
.entypo.new:before{ content:'\1F4A5'; }
.entypo.graduation-cap:before{ content:'\1F393'; }
.entypo.book:before{ content:'\1F4D5'; }
.entypo.newspaper:before{ content:'\1F4F0'; }
.entypo.bag:before{ content:'\1F45C'; }
.entypo.airplane:before{ content:'\2708'; }
.entypo.lifebuoy:before{ content:'\E788'; }
.entypo.eye:before{ content:'\E70A'; }
.entypo.clock:before{ content:'\1F554'; }
.entypo.mic:before{ content:'\1F3A4'; }
.entypo.calendar:before{ content:'\1F4C5'; }
.entypo.flash:before{ content:'\26A1'; }
.entypo.thunder-cloud:before{ content:'\26C8'; }
.entypo.droplet:before{ content:'\1F4A7'; }
.entypo.cd:before{ content:'\1F4BF'; }
.entypo.briefcase:before{ content:'\1F4BC'; }
.entypo.air:before{ content:'\1F4A8'; }
.entypo.hourglass:before{ content:'\23F3'; }
.entypo.gauge:before{ content:'\1F6C7'; }
.entypo.language:before{ content:'\1F394'; }
.entypo.network:before{ content:'\E776'; }
.entypo.key:before{ content:'\1F511'; }
.entypo.battery:before{ content:'\1F50B'; }
.entypo.bucket:before{ content:'\1F4FE'; }
.entypo.magnet:before{ content:'\E7A1'; }
.entypo.drive:before{ content:'\1F4FD'; }
.entypo.cup:before{ content:'\2615'; }
.entypo.rocket:before{ content:'\1F680'; }
.entypo.brush:before{ content:'\E79A'; }
.entypo.suitcase:before{ content:'\1F6C6'; }
.entypo.traffic-cone:before{ content:'\1F6C8'; }
.entypo.globe:before{ content:'\1F30E'; }
.entypo.keyboard:before{ content:'\2328'; }
.entypo.browser:before{ content:'\E74E'; }
.entypo.publish:before{ content:'\E74D'; }
.entypo.progress-3:before{ content:'\E76B'; }
.entypo.progress-2:before{ content:'\E76A'; }
.entypo.progress-1:before{ content:'\E769'; }
.entypo.progress-0:before{ content:'\E768'; }
.entypo.light-down:before{ content:'\1F505'; }
.entypo.light-up:before{ content:'\1F506'; }
.entypo.adjust:before{ content:'\25D1'; }
.entypo.code:before{ content:'\E714'; }
.entypo.monitor:before{ content:'\1F4BB'; }
.entypo.infinity:before{ content:'\221E'; }
.entypo.light-bulb:before{ content:'\1F4A1'; }
.entypo.credit-card:before{ content:'\1F4B3'; }
.entypo.database:before{ content:'\1F4F8'; }
.entypo.voicemail:before{ content:'\2707'; }
.entypo.clipboard:before{ content:'\1F4CB'; }
.entypo.cart:before{ content:'\E73D'; }
.entypo.box:before{ content:'\1F4E6'; }
.entypo.ticket:before{ content:'\1F3AB'; }
.entypo.rss:before{ content:'\E73A'; }
.entypo.signal:before{ content:'\1F4F6'; }
.entypo.thermometer:before{ content:'\1F4FF'; }
.entypo.water:before{ content:'\1F4A6'; }
.entypo.sweden:before{ content:'\F601'; }
.entypo.line-graph:before{ content:'\1F4C8'; }
.entypo.pie-chart:before{ content:'\25F4'; }
.entypo.bar-graph:before{ content:'\1F4CA'; }
.entypo.area-graph:before{ content:'\1F53E'; }
.entypo.lock:before{ content:'\1F512'; }
.entypo.lock-open:before{ content:'\1F513'; }
.entypo.logout:before{ content:'\E741'; }
.entypo.login:before{ content:'\E740'; }
.entypo.check:before{ content:'\2713'; }
.entypo.cross:before{ content:'\274C'; }
.entypo.squared-minus:before{ content:'\229F'; }
.entypo.squared-plus:before{ content:'\229E'; }
.entypo.squared-cross:before{ content:'\274E'; }
.entypo.circled-minus:before{ content:'\2296'; }
.entypo.circled-plus:before{ content:'\2295'; }
.entypo.circled-cross:before{ content:'\2716'; }
.entypo.minus:before{ content:'\2796'; }
.entypo.plus:before{ content:'\2795'; }
.entypo.erase:before{ content:'\232B'; }
.entypo.block:before{ content:'\1F6AB'; }
.entypo.info:before{ content:'\2139'; }
.entypo.circled-info:before{ content:'\E705'; }
.entypo.help:before{ content:'\2753'; }
.entypo.circled-help:before{ content:'\E704'; }
.entypo.warning:before{ content:'\26A0'; }
.entypo.cycle:before{ content:'\1F504'; }
.entypo.cw:before{ content:'\27F3'; }
.entypo.ccw:before{ content:'\27F2'; }
.entypo.shuffle:before{ content:'\1F500'; }
.entypo.back:before{ content:'\1F519'; }
.entypo.level-down:before{ content:'\21B3'; }
.entypo.retweet:before{ content:'\E717'; }
.entypo.loop:before{ content:'\1F501'; }
.entypo.back-in-time:before{ content:'\E771'; }
.entypo.level-up:before{ content:'\21B0'; }
.entypo.switch:before{ content:'\21C6'; }
.entypo.numbered-list:before{ content:'\E005'; }
.entypo.add-to-list:before{ content:'\E003'; }
.entypo.layout:before{ content:'\268F'; }
.entypo.list:before{ content:'\2630'; }
.entypo.text-doc:before{ content:'\1F4C4'; }
.entypo.text-doc-inverted:before{ content:'\E731'; }
.entypo.doc:before{ content:'\E730'; }
.entypo.docs:before{ content:'\E736'; }
.entypo.landscape-doc:before{ content:'\E737'; }
.entypo.picture:before{ content:'\1F304'; }
.entypo.video:before{ content:'\1F3AC'; }
.entypo.music:before{ content:'\1F3B5'; }
.entypo.folder:before{ content:'\1F4C1'; }
.entypo.archive:before{ content:'\E800'; }
.entypo.trash:before{ content:'\E729'; }
.entypo.upload:before{ content:'\1F4E4'; }
.entypo.download:before{ content:'\1F4E5'; }
.entypo.save:before{ content:'\1F4BE'; }
.entypo.install:before{ content:'\E778'; }
.entypo.cloud:before{ content:'\2601'; }
.entypo.upload-cloud:before{ content:'\E711'; }
.entypo.bookmark:before{ content:'\1F516'; }
.entypo.bookmarks:before{ content:'\1F4D1'; }
.entypo.open-book:before{ content:'\1F4D6'; }
.entypo.play:before{ content:'\25B6'; }
.entypo.paus:before{ content:'\2016'; }
.entypo.record:before{ content:'\25CF'; }
.entypo.stop:before{ content:'\25A0'; }
.entypo.ff:before{ content:'\23E9'; }
.entypo.fb:before{ content:'\23EA'; }
.entypo.to-start:before{ content:'\23EE'; }
.entypo.to-end:before{ content:'\23ED'; }
.entypo.resize-full:before{ content:'\E744'; }
.entypo.resize-small:before{ content:'\E746'; }
.entypo.volume:before{ content:'\23F7'; }
.entypo.sound:before{ content:'\1F50A'; }
.entypo.mute:before{ content:'\1F507'; }
.entypo.flow-cascade:before{ content:'\1F568'; }
.entypo.flow-branch:before{ content:'\1F569'; }
.entypo.flow-tree:before{ content:'\1F56A'; }
.entypo.flow-line:before{ content:'\1F56B'; }
.entypo.flow-parallel:before{ content:'\1F56C'; }
.entypo.left-bold:before{ content:'\E4AD'; }
.entypo.down-bold:before{ content:'\E4B0'; }
.entypo.up-bold:before{ content:'\E4AF'; }
.entypo.right-bold:before{ content:'\E4AE'; }
.entypo.left:before{ content:'\2B05'; }
.entypo.down:before{ content:'\2B07'; }
.entypo.up:before{ content:'\2B06'; }
.entypo.right:before{ content:'\27A1'; }
.entypo.circled-left:before{ content:'\E759'; }
.entypo.circled-down:before{ content:'\E758'; }
.entypo.circled-up:before{ content:'\E75B'; }
.entypo.circled-right:before{ content:'\E75A'; }
.entypo.triangle-left:before{ content:'\25C2'; }
.entypo.triangle-down:before{ content:'\25BE'; }
.entypo.triangle-up:before{ content:'\25B4'; }
.entypo.triangle-right:before{ content:'\25B8'; }
.entypo.chevron-left:before{ content:'\E75D'; }
.entypo.chevron-down:before{ content:'\E75C'; }
.entypo.chevron-up:before{ content:'\E75F'; }
.entypo.chevron-right:before{ content:'\E75E'; }
.entypo.chevron-small-left:before{ content:'\E761'; }
.entypo.chevron-small-down:before{ content:'\E760'; }
.entypo.chevron-small-up:before{ content:'\E763'; }
.entypo.chevron-small-right:before{ content:'\E762'; }
.entypo.chevron-thin-left:before{ content:'\E765'; }
.entypo.chevron-thin-down:before{ content:'\E764'; }
.entypo.chevron-thin-up:before{ content:'\E767'; }
.entypo.chevron-thin-right:before{ content:'\E766'; }
.entypo.left-thin:before{ content:'\2190'; }
.entypo.down-thin:before{ content:'\2193'; }
.entypo.up-thin:before{ content:'\2191'; }
.entypo.right-thin:before{ content:'\2192'; }
.entypo.arrow-combo:before{ content:'\E74F'; }
.entypo.three-dots:before{ content:'\23F6'; }
.entypo.two-dots:before{ content:'\23F5'; }
.entypo.dot:before{ content:'\23F4'; }
.entypo.cc:before{ content:'\1F545'; }
.entypo.cc-by:before{ content:'\1F546'; }
.entypo.cc-nc:before{ content:'\1F547'; }
.entypo.cc-nc-eu:before{ content:'\1F548'; }
.entypo.cc-nc-jp:before{ content:'\1F549'; }
.entypo.cc-sa:before{ content:'\1F54A'; }
.entypo.cc-nd:before{ content:'\1F54B'; }
.entypo.cc-pd:before{ content:'\1F54C'; }
.entypo.cc-zero:before{ content:'\1F54D'; }
.entypo.cc-share:before{ content:'\1F54E'; }
.entypo.cc-remix:before{ content:'\1F54F'; }
.entypo.db-logo:before{ content:'\1F5F9'; }
.entypo.db-shape:before{ content:'\1F5FA'; }
.entypo-social.github:before{ content:'\F300'; }
.entypo-social.c-github:before{ content:'\F301'; }
.entypo-social.flickr:before{ content:'\F303'; }
.entypo-social.c-flickr:before{ content:'\F304'; }
.entypo-social.vimeo:before{ content:'\F306'; }
.entypo-social.c-vimeo:before{ content:'\F307'; }
.entypo-social.twitter:before{ content:'\F309'; }
.entypo-social.c-twitter:before{ content:'\F30A'; }
.entypo-social.facebook:before{ content:'\F30C'; }
.entypo-social.c-facebook:before{ content:'\F30D'; }
.entypo-social.s-facebook:before{ content:'\F30E'; }
.entypo-social.google+:before{ content:'\F30F'; }
.entypo-social.c-google+:before{ content:'\F310'; }
.entypo-social.pinterest:before{ content:'\F312'; }
.entypo-social.c-pinterest:before{ content:'\F313'; }
.entypo-social.tumblr:before{ content:'\F315'; }
.entypo-social.c-tumblr:before{ content:'\F316'; }
.entypo-social.linkedin:before{ content:'\F318'; }
.entypo-social.c-linkedin:before{ content:'\F319'; }
.entypo-social.dribbble:before{ content:'\F31B'; }
.entypo-social.c-dribbble:before{ content:'\F31C'; }
.entypo-social.stumbleupon:before{ content:'\F31E'; }
.entypo-social.c-stumbleupon:before{ content:'\F31F'; }
.entypo-social.lastfm:before{ content:'\F321'; }
.entypo-social.c-lastfm:before{ content:'\F322'; }
.entypo-social.rdio:before{ content:'\F324'; }
.entypo-social.c-rdio:before{ content:'\F325'; }
.entypo-social.spotify:before{ content:'\F327'; }
.entypo-social.c-spotify:before{ content:'\F328'; }
.entypo-social.qq:before{ content:'\F32A'; }
.entypo-social.instagram:before{ content:'\F32D'; }
.entypo-social.dropbox:before{ content:'\F330'; }
.entypo-social.evernote:before{ content:'\F333'; }
.entypo-social.flattr:before{ content:'\F336'; }
.entypo-social.skype:before{ content:'\F339'; }
.entypo-social.c-skype:before{ content:'\F33A'; }
.entypo-social.renren:before{ content:'\F33C'; }
.entypo-social.sina-weibo:before{ content:'\F33F'; }
.entypo-social.paypal:before{ content:'\F342'; }
.entypo-social.picasa:before{ content:'\F345'; }
.entypo-social.soundcloud:before{ content:'\F348'; }
.entypo-social.mixi:before{ content:'\F34B'; }
.entypo-social.behance:before{ content:'\F34E'; }
.entypo-social.google-circles:before{ content:'\F351'; }
.entypo-social.vk:before{ content:'\F354'; }
.entypo-social.smashing:before{ content:'\F357'; }

@ -1,2 +0,0 @@
@font-face{font-family:Lato;font-style:normal;font-weight:100;src:local('Lato Hairline'),local('Lato-Hairline'),url(../fonts/Lato-Hairline.ttf) format('truetype')}@font-face{font-family:Lato;font-style:normal;font-weight:300;src:local('Lato Light'),local('Lato-Light'),url(../fonts/Lato-Light.ttf) format('truetype')}@font-face{font-family:Lato;font-style:normal;font-weight:400;src:local('Lato Regular'),local('Lato-Regular'),url(../fonts/Lato-Regular.ttf) format('truetype')}@font-face{font-family:Lato;font-style:normal;font-weight:700;src:local('Lato Bold'),local('Lato-Bold'),url(../fonts/Lato-Bold.ttf) format('truetype')}@font-face{font-family:Montserrat;font-style:normal;font-weight:400;src:local('Montserrat-Regular'),url(../fonts/Montserrat-Regular.ttf) format('truetype')}@font-face{font-family:Montserrat;font-style:normal;font-weight:700;src:local('Montserrat-Bold'),url(../fonts/Montserrat-Bold.ttf) format('truetype')}@font-face{font-family:Lobster;font-style:normal;font-weight:400;src:local('Lobster'),local('Lobster-Regular'),url(../fonts/Lobster.ttf) format('truetype')}
@font-face{font-family:'Gotham-Light';src:url('../fonts/itc/Gotham-Light.eot');src:url('../fonts/itc/Gotham-Light.woff') format('woff'),url('../fonts/itc/Gotham-Light.svg#Gotham-Light') format('svg')}@font-face{font-family:'Gotham-Medium';src:url('../fonts/itc/Gotham-Medium.eot');src:url('../fonts/itc/Gotham-Medium.woff') format('woff'),url('../fonts/itc/Gotham-Medium.svg#Gotham-Medium') format('svg')}@font-face{font-family:'Gotham-Book';src:url('../fonts/itc/Gotham-Book.eot');src:url('../fonts/itc/Gotham-Book.woff') format('woff'),url('../fonts/itc/Gotham-Book.svg#Gotham-Book') format('svg')}

@ -1,90 +0,0 @@
.fraction-slider{
position:relative;
width:100%; height:100%;
overflow:visible;
}
.fraction-slider .slide{
display:none; width:100%; height:100%;
position:absolute;
z-index:5000;
}
.fraction-slider .active-slide{
z-index:9999;
}
.fraction-slider .fs_obj{
display:block; display:none;
position:absolute;
top:0px; left:100%;
z-index:7000;
}
.fraction-slider .fs_fixed_obj{
z-index:6000;
left:0;
}
.fraction-slider .fs_obj *{
display:inline-block;
position:relative;
top:0px; left:0px;
}
.fs_loader{
width:100%; height:400px;
background:url(images/fs.spinner.gif) center center no-repeat transparent;
}
/** CONTROLS **/
.fraction-slider .prev,
.fraction-slider .next{
display:none;
position:absolute;
width:45px; height:45px;
z-index:9999;
}
.fraction-slider .prev{
left:10px; top:48%;
background:url(images/fs.prevnext.png) 0px 0px no-repeat transparent;
}
.fraction-slider .prev:hover{
background:url(images/fs.prevnext.png) 0px -45px no-repeat transparent;
}
.fraction-slider .next{
right:10px; top:48%;
background:url(images/fs.prevnext.png) -45px 0px no-repeat transparent;
}
.fraction-slider .next:hover{
background:url(images/fs.prevnext.png) -45px -45px no-repeat transparent;
}
.fraction-slider:hover .prev,
.fraction-slider:hover .next{
display:block;
}
/** PAGER **/
.fs-pager-wrapper{
position:absolute;
left:10px; bottom:10px;
z-index:9999;
}
.fs-pager-wrapper a,
.fs-custom-pager-wrapper a{
display:inline-block;
width:14px; height:14px;
margin:0 5px 0 0;
background:url(images/fs.pager.png) 0px -14px no-repeat transparent;
}
.fs-pager-wrapper .active,
.fs-custom-pager-wrapper .active{
background:url(images/fs.pager.png) 0px 0px no-repeat transparent;
}

@ -1,34 +0,0 @@
.vegas-loading {
border-radius: 10px;
background: #000;
background: rgba(0,0,0,0.7);
background: url(images/loading.gif) no-repeat center center; /* Loading Gif by http://preloaders.net/ */
height: 32px;
left: 20px;
position: fixed;
top: 20px;
width: 32px;
z-index: 0;
}
.vegas-overlay {
background: transparent url(overlays/01.png);
opacity: 0.5;
z-index: -1;
}
.vegas-background {
-ms-interpolation-mode: bicubic;
image-rendering: optimizeQuality;
max-width: none !important; /* counteracts global img modification by twitter bootstrap library */
z-index: -2;
}
.vegas-overlay,
.vegas-background {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

@ -1,363 +0,0 @@
/* Magnific Popup CSS */
.mfp-bg {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1042;
overflow: hidden;
position: fixed;
background: #0b0b0b;
opacity: 0.8;
filter: alpha(opacity=80); }
.mfp-wrap {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1043;
position: fixed;
outline: none !important;
-webkit-backface-visibility: hidden; }
.mfp-container {
text-align: center;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
padding: 0 8px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
.mfp-container:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle; }
.mfp-align-top .mfp-container:before {
display: none; }
.mfp-content {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 0 auto;
text-align: left;
z-index: 1045; }
.mfp-inline-holder .mfp-content, .mfp-ajax-holder .mfp-content {
width: 100%;
cursor: auto; }
.mfp-ajax-cur {
cursor: progress; }
.mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close {
cursor: -moz-zoom-out;
cursor: -webkit-zoom-out;
cursor: zoom-out; }
.mfp-zoom {
cursor: pointer;
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in; }
.mfp-auto-cursor .mfp-content {
cursor: auto; }
.mfp-close, .mfp-arrow, .mfp-preloader, .mfp-counter {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.mfp-loading.mfp-figure {
display: none; }
.mfp-hide {
display: none !important; }
.mfp-preloader {
color: #cccccc;
position: absolute;
top: 50%;
width: auto;
text-align: center;
margin-top: -0.8em;
left: 8px;
right: 8px;
z-index: 1044; }
.mfp-preloader a {
color: #cccccc; }
.mfp-preloader a:hover {
color: white; }
.mfp-s-ready .mfp-preloader {
display: none; }
.mfp-s-error .mfp-content {
display: none; }
button.mfp-close, button.mfp-arrow {
overflow: visible;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
display: block;
outline: none;
padding: 0;
z-index: 1046;
-webkit-box-shadow: none;
box-shadow: none; }
button::-moz-focus-inner {
padding: 0;
border: 0; }
.mfp-close {
width: 44px;
height: 44px;
line-height: 44px;
position: absolute;
right: 0;
top: 0;
text-decoration: none;
text-align: center;
opacity: 0.65;
padding: 0 0 18px 10px;
color: white;
font-style: normal;
font-size: 28px;
font-family: Arial, Baskerville, monospace; }
.mfp-close:hover, .mfp-close:focus {
opacity: 1; }
.mfp-close:active {
top: 1px; }
.mfp-close-btn-in .mfp-close {
color: #333333; }
.mfp-image-holder .mfp-close, .mfp-iframe-holder .mfp-close {
color: white;
right: -6px;
text-align: right;
padding-right: 6px;
width: 100%; }
.mfp-counter {
position: absolute;
top: 0;
right: 0;
color: #cccccc;
font-size: 12px;
line-height: 18px; }
.mfp-arrow {
position: absolute;
opacity: 0.65;
margin: 0;
top: 50%;
margin-top: -55px;
padding: 0;
width: 90px;
height: 110px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
.mfp-arrow:active {
margin-top: -54px; }
.mfp-arrow:hover, .mfp-arrow:focus {
opacity: 1; }
.mfp-arrow:before, .mfp-arrow:after, .mfp-arrow .mfp-b, .mfp-arrow .mfp-a {
content: '';
display: block;
width: 0;
height: 0;
position: absolute;
left: 0;
top: 0;
margin-top: 35px;
margin-left: 35px;
border: medium inset transparent; }
.mfp-arrow:after, .mfp-arrow .mfp-a {
border-top-width: 13px;
border-bottom-width: 13px;
top: 8px; }
.mfp-arrow:before, .mfp-arrow .mfp-b {
border-top-width: 21px;
border-bottom-width: 21px; }
.mfp-arrow-left {
left: 0; }
.mfp-arrow-left:after, .mfp-arrow-left .mfp-a {
border-right: 17px solid white;
margin-left: 31px; }
.mfp-arrow-left:before, .mfp-arrow-left .mfp-b {
margin-left: 25px;
border-right: 27px solid #3f3f3f; }
.mfp-arrow-right {
right: 0; }
.mfp-arrow-right:after, .mfp-arrow-right .mfp-a {
border-left: 17px solid white;
margin-left: 39px; }
.mfp-arrow-right:before, .mfp-arrow-right .mfp-b {
border-left: 27px solid #3f3f3f; }
.mfp-iframe-holder {
padding-top: 40px;
padding-bottom: 40px; }
.mfp-iframe-holder .mfp-content {
line-height: 0;
width: 100%;
max-width: 900px; }
.mfp-iframe-holder .mfp-close {
top: -40px; }
.mfp-iframe-scaler {
width: 100%;
height: 0;
overflow: hidden;
padding-top: 56.25%; }
.mfp-iframe-scaler iframe {
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
background: black; }
/* Main image in popup */
img.mfp-img {
width: auto;
max-width: 100%;
height: auto;
display: block;
line-height: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 40px 0 40px;
margin: 0 auto; }
/* The shadow behind the image */
.mfp-figure {
line-height: 0; }
.mfp-figure:after {
content: '';
position: absolute;
left: 0;
top: 40px;
bottom: 40px;
display: block;
right: 0;
width: auto;
height: auto;
z-index: -1;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
background: #444444; }
.mfp-figure small {
color: #bdbdbd;
display: block;
font-size: 12px;
line-height: 14px; }
.mfp-bottom-bar {
margin-top: -36px;
position: absolute;
top: 100%;
left: 0;
width: 100%;
cursor: auto; }
.mfp-title {
text-align: left;
line-height: 18px;
color: #f3f3f3;
word-wrap: break-word;
padding-right: 36px; }
.mfp-image-holder .mfp-content {
max-width: 100%; }
.mfp-gallery .mfp-image-holder .mfp-figure {
cursor: pointer; }
@media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) {
/**
* Remove all paddings around the image on small screen
*/
.mfp-img-mobile .mfp-image-holder {
padding-left: 0;
padding-right: 0; }
.mfp-img-mobile img.mfp-img {
padding: 0; }
.mfp-img-mobile .mfp-figure {
/* The shadow behind the image */ }
.mfp-img-mobile .mfp-figure:after {
top: 0;
bottom: 0; }
.mfp-img-mobile .mfp-figure small {
display: inline;
margin-left: 5px; }
.mfp-img-mobile .mfp-bottom-bar {
background: rgba(0, 0, 0, 0.6);
bottom: 0;
margin: 0;
top: auto;
padding: 3px 5px;
position: fixed;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
.mfp-img-mobile .mfp-bottom-bar:empty {
padding: 0; }
.mfp-img-mobile .mfp-counter {
right: 5px;
top: 3px; }
.mfp-img-mobile .mfp-close {
top: 0;
right: 0;
width: 35px;
height: 35px;
line-height: 35px;
background: rgba(0, 0, 0, 0.6);
position: fixed;
text-align: center;
padding: 0; } }
@media all and (max-width: 900px) {
.mfp-arrow {
-webkit-transform: scale(0.75);
transform: scale(0.75); }
.mfp-arrow-left {
-webkit-transform-origin: 0;
transform-origin: 0; }
.mfp-arrow-right {
-webkit-transform-origin: 100%;
transform-origin: 100%; }
.mfp-container {
padding-left: 6px;
padding-right: 6px; } }
.mfp-ie7 .mfp-img {
padding: 0; }
.mfp-ie7 .mfp-bottom-bar {
width: 600px;
left: 50%;
margin-left: -300px;
margin-top: 5px;
padding-bottom: 5px; }
.mfp-ie7 .mfp-container {
padding: 0; }
.mfp-ie7 .mfp-content {
padding-top: 44px; }
.mfp-ie7 .mfp-close {
top: 0;
right: 0;
padding-top: 0; }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,13 +0,0 @@
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" > <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs >
<font id="entypo-social" horiz-adv-x="555" ><font-face
font-family="Entypo Social"
units-per-em="1000"
panose-1="0 0 0 0 0 0 0 0 0 0"
ascent="750"
descent="-250"
alphabetic="0" />
<missing-glyph horiz-adv-x="500" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

@ -1,13 +0,0 @@
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" > <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs >
<font id="entypo" horiz-adv-x="508" ><font-face
font-family="Entypo"
units-per-em="1000"
panose-1="0 0 0 0 0 0 0 0 0 0"
ascent="750"
descent="-250"
alphabetic="0" />
<missing-glyph horiz-adv-x="500" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,10 @@
/*! intention.js v0.9.7.3
* http://intentionjs.com/
*
* context.js
*
* Copyright 2011, 2013 Dowjones and other contributors
* Released under the MIT license
*
*/
!function(){"use strict";var a=function(a,b){function c(a,b){var c=new Date,d=null;return function(e){var f=new Date;if(b>f-c){d&&window.clearTimeout(d);var g=function(b){return function(){a(b)}};return d=window.setTimeout(g(e),b),!1}a(e),c=f}}var d,e,f=new b;return f.responsive([{name:"base"}]).respond("base"),d=f.responsive({ID:"width",contexts:[{name:"standard",min:840},{name:"tablet",min:510},{name:"mobile",min:0}],matcher:function(a,b){return"string"==typeof a?a===b.name:a>=b.min},measure:function(b){return"string"==typeof b?b:a(window).width()}}),e=f.responsive({ID:"orientation",contexts:[{name:"portrait",rotation:0},{name:"landscape",rotation:90}],matcher:function(a,b){return a===b.rotation},measure:function(){var a=Math.abs(window.orientation);return a>0&&(a=180-a),a}}),f.responsive({ID:"touch",contexts:[{name:"touch"}],matcher:function(){return"ontouchstart"in window}}).respond(),f.responsive({ID:"highres",contexts:[{name:"highres"}],matcher:function(){return window.devicePixelRatio>1}}).respond(),a(window).on("resize",c(d.respond,100)).on("orientationchange",d.respond).on("orientationchange",e.respond),d.respond(),e.respond(),a(function(){f.elements(document)}),f};!function(a,b){"function"==typeof define&&define.amd?define("context",["jquery","intention"],b):a.intent=b(a.jQuery,a.Intention)}(this,function(b,c){return a(b,c)})}.call(this);

@ -1,14 +0,0 @@
EPUBJS.Hooks.register("beforeChapterDisplay").highlight = function(callback, renderer){
// EPUBJS.core.addScript("js/libs/jquery.highlight.js", null, renderer.doc.head);
var s = document.createElement("style");
s.innerHTML =".highlight { background: yellow; font-weight: normal; }";
renderer.render.document.head.appendChild(s);
if(callback) callback();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,11 +0,0 @@
// ----------------------------------------------------------------------------
// Vegas Fullscreen Backgrounds and Slideshows with jQuery.
// v1.3.3 - released 2013-09-03 13:27
// Licensed under the MIT license.
// http://vegas.jaysalvat.com/
// ----------------------------------------------------------------------------
// Copyright (C) 2010-2013 Jay Salvat
// http://jaysalvat.com/
// ----------------------------------------------------------------------------
(function(e){function t(a,n){var r={align:"center",valign:"center"};if(e.extend(r,n),0===a.height())return a.load(function(){t(e(this),n)}),void 0;var i,s,g,d=o(),l=d.width,u=d.height,v=a.width(),c=a.height(),p=u/l,f=c/v;p>f?(i=u/f,s=u):(i=l,s=l*f),g={width:i+"px",height:s+"px",top:"auto",bottom:"auto",left:"auto",right:"auto"},isNaN(parseInt(r.valign,10))?"top"==r.valign?g.top=0:"bottom"==r.valign?g.bottom=0:g.top=(u-s)/2:g.top=0-(s-u)/100*parseInt(r.valign,10)+"px",isNaN(parseInt(r.align,10))?"left"==r.align?g.left=0:"right"==r.align?g.right=0:g.left=(l-i)/2:g.left=0-(i-l)/100*parseInt(r.align,10)+"px",a.css(g)}function a(){d.prependTo("body").fadeIn()}function n(){d.fadeOut("fast",function(){e(this).remove()})}function r(){return e("body").css("backgroundImage")?e("body").css("backgroundImage").replace(/url\("?(.*?)"?\)/i,"$1"):void 0}function o(){var e=window,t="inner";return"innerWidth"in window||(e=document.documentElement||document.body,t="client"),{width:e[t+"Width"],height:e[t+"Height"]}}var i,s=e("<img />").addClass("vegas-background"),g=e("<div />").addClass("vegas-overlay"),d=e("<div />").addClass("vegas-loading"),l=e(),u=null,v=[],c=0,p=5e3,f=function(){},h={init:function(o){var i={src:r(),align:"center",valign:"center",fade:0,loading:!0,load:function(){},complete:function(){}};e.extend(i,e.vegas.defaults.background,o),i.loading&&a();var g=s.clone();return g.css({position:"fixed",left:"0px",top:"0px"}).bind("load",function(){g!=l&&(e(window).bind("load resize.vegas",function(){t(g,i)}),l.is("img")?(l.stop(),g.hide().insertAfter(l).fadeIn(i.fade,function(){e(".vegas-background").not(this).remove(),e("body").trigger("vegascomplete",[this,c-1]),i.complete.apply(g,[c-1])})):g.hide().prependTo("body").fadeIn(i.fade,function(){e("body").trigger("vegascomplete",[this,c-1]),i.complete.apply(this,[c-1])}),l=g,t(l,i),i.loading&&n(),e("body").trigger("vegasload",[l.get(0),c-1]),i.load.apply(l.get(0),[c-1]),c&&(e("body").trigger("vegaswalk",[l.get(0),c-1]),i.walk.apply(l.get(0),[c-1])))}).attr("src",i.src),e.vegas},destroy:function(t){return t&&"background"!=t||(e(".vegas-background, .vegas-loading").remove(),e(window).unbind("*.vegas"),l=e()),t&&"overlay"!=t||e(".vegas-overlay").remove(),clearInterval(i),e.vegas},overlay:function(t){var a={src:null,opacity:null};return e.extend(a,e.vegas.defaults.overlay,t),g.remove(),g.css({margin:"0",padding:"0",position:"fixed",left:"0px",top:"0px",width:"100%",height:"100%"}),a.src&&g.css("backgroundImage","url("+a.src+")"),a.opacity&&g.css("opacity",a.opacity),g.prependTo("body"),e.vegas},slideshow:function(t,a){var n={step:c,delay:p,preload:!1,backgrounds:v,walk:f};if(e.extend(n,e.vegas.defaults.slideshow,t),n.backgrounds!=v&&(t.step||(n.step=0),t.walk||(n.walk=function(){}),n.preload&&e.vegas("preload",n.backgrounds)),v=n.backgrounds,p=n.delay,c=n.step,f=n.walk,clearInterval(i),!v.length)return e.vegas;var r=function(){0>c&&(c=v.length-1),(c>=v.length||!v[c-1])&&(c=0);var t=v[c++];t.walk=n.walk,t.fade===void 0&&(t.fade=n.fade),t.fade>n.delay&&(t.fade=n.delay),e.vegas(t)};return r(),a||(u=!1,e("body").trigger("vegasstart",[l.get(0),c-1])),u||(i=setInterval(r,n.delay)),e.vegas},next:function(){var t=c;return c&&(e.vegas("slideshow",{step:c},!0),e("body").trigger("vegasnext",[l.get(0),c-1,t-1])),e.vegas},previous:function(){var t=c;return c&&(e.vegas("slideshow",{step:c-2},!0),e("body").trigger("vegasprevious",[l.get(0),c-1,t-1])),e.vegas},jump:function(t){var a=c;return c&&(e.vegas("slideshow",{step:t},!0),e("body").trigger("vegasjump",[l.get(0),c-1,a-1])),e.vegas},stop:function(){var t=c;return c=0,u=null,clearInterval(i),e("body").trigger("vegasstop",[l.get(0),t-1]),e.vegas},pause:function(){return u=!0,clearInterval(i),e("body").trigger("vegaspause",[l.get(0),c-1]),e.vegas},get:function(e){return null===e||"background"==e?l.get(0):"overlay"==e?g.get(0):"step"==e?c-1:"paused"==e?u:void 0},preload:function(t){var a=[];for(var n in t)if(t[n].src){var r=document.createElement("img");r.src=t[n].src,a.push(r)}return e.vegas}};e.vegas=function(t){return h[t]?h[t].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof t&&t?(e.error("Method "+t+" does not exist"),void 0):h.init.apply(this,arguments)},e.vegas.defaults={background:{},slideshow:{},overlay:{}}})(jQuery);

@ -1,145 +0,0 @@
/*!
* screenfull
* v2.0.0 - 2014-12-22
* (c) Sindre Sorhus; MIT License
*/
(function () {
'use strict';
var isCommonjs = typeof module !== 'undefined' && module.exports;
var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
var fn = (function () {
var val;
var valLength;
var fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror'
],
// new WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
// old WebKit (Safari 5.1)
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror'
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError'
]
];
var i = 0;
var l = fnMap.length;
var ret = {};
for (; i < l; i++) {
val = fnMap[i];
if (val && val[1] in document) {
for (i = 0, valLength = val.length; i < valLength; i++) {
ret[fnMap[0][i]] = val[i];
}
return ret;
}
}
return false;
})();
var screenfull = {
request: function (elem) {
var request = fn.requestFullscreen;
elem = elem || document.documentElement;
// Work around Safari 5.1 bug: reports support for
// keyboard in fullscreen even though it doesn't.
// Browser sniffing, since the alternative with
// setTimeout is even worse.
if (/5\.1[\.\d]* Safari/.test(navigator.userAgent)) {
elem[request]();
} else {
elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
}
},
exit: function () {
document[fn.exitFullscreen]();
},
toggle: function (elem) {
if (this.isFullscreen) {
this.exit();
} else {
this.request(elem);
}
},
raw: fn
};
if (!fn) {
if (isCommonjs) {
module.exports = false;
} else {
window.screenfull = false;
}
return;
}
Object.defineProperties(screenfull, {
isFullscreen: {
get: function () {
return !!document[fn.fullscreenElement];
}
},
element: {
enumerable: true,
get: function () {
return document[fn.fullscreenElement];
}
},
enabled: {
enumerable: true,
get: function () {
// Coerce to boolean in case of old WebKit
return !!document[fn.fullscreenEnabled];
}
}
});
if (isCommonjs) {
module.exports = screenfull;
} else {
window.screenfull = screenfull;
}
})();

@ -1,7 +0,0 @@
/*!
* screenfull
* v1.1.0 - 2013-09-06
* https://github.com/sindresorhus/screenfull.js
* (c) Sindre Sorhus; MIT License
*/
!function(a,b){"use strict";var c="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,d=function(){for(var a,c,d=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenchange","MSFullscreenerror"]],e=0,f=d.length,g={};f>e;e++)if(a=d[e],a&&a[1]in b){for(e=0,c=a.length;c>e;e++)g[d[0][e]]=a[e];return g}return!1}(),e={request:function(a){var e=d.requestFullscreen;a=a||b.documentElement,/5\.1[\.\d]* Safari/.test(navigator.userAgent)?a[e]():a[e](c&&Element.ALLOW_KEYBOARD_INPUT)},exit:function(){b[d.exitFullscreen]()},toggle:function(a){this.isFullscreen?this.exit():this.request(a)},onchange:function(){},onerror:function(){},raw:d};return d?(Object.defineProperties(e,{isFullscreen:{get:function(){return!!b[d.fullscreenElement]}},element:{enumerable:!0,get:function(){return b[d.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return!!b[d.fullscreenEnabled]}}}),b.addEventListener(d.fullscreenchange,function(a){e.onchange.call(e,a)}),b.addEventListener(d.fullscreenerror,function(a){e.onerror.call(e,a)}),a.screenfull=e,void 0):(a.screenfull=!1,void 0)}(window,document);

File diff suppressed because one or more lines are too long

@ -1,80 +0,0 @@
// Hypothesis Customized embedding
// This hypothesis config function returns a new constructor which modifies
// annotator for a better integration. Below we create our own EpubAnnotationSidebar
// Constructor, customizing the show and hide function to take acount for the reader UI.
window.hypothesisConfig = function() {
var Annotator = window.Annotator;
var $main = $("#main");
function EpubAnnotationSidebar(elem, options) {
options = {
server: true,
origin: true,
showHighlights: true,
Toolbar: {container: '#annotation-controls'}
}
Annotator.Host.call(this, elem, options);
}
EpubAnnotationSidebar.prototype = Object.create(Annotator.Host.prototype);
EpubAnnotationSidebar.prototype.show = function() {
this.frame.css({
'margin-left': (-1 * this.frame.width()) + "px"
});
this.frame.removeClass('annotator-collapsed');
if (!$main.hasClass('single')) {
$main.addClass("single");
this.toolbar.find('[name=sidebar-toggle]').removeClass('h-icon-chevron-left').addClass('h-icon-chevron-right');
this.setVisibleHighlights(true);
}
};
EpubAnnotationSidebar.prototype.hide = function() {
this.frame.css({
'margin-left': ''
});
this.frame.addClass('annotator-collapsed');
if ($main.hasClass('single')) {
$main.removeClass("single");
this.toolbar.find('[name=sidebar-toggle]').removeClass('h-icon-chevron-right').addClass('h-icon-chevron-left');
this.setVisibleHighlights(false);
}
};
return {
constructor: EpubAnnotationSidebar,
}
};
// This is the Epub.js plugin. Annotations are updated on location change.
EPUBJS.reader.plugins.HypothesisController = function (Book) {
var reader = this;
var $main = $("#main");
var updateAnnotations = function () {
var annotator = Book.renderer.render.window.annotator;
if (annotator && annotator.constructor.$) {
var annotations = getVisibleAnnotations(annotator.constructor.$);
annotator.showAnnotations(annotations)
}
};
var getVisibleAnnotations = function ($) {
var width = Book.renderer.render.iframe.clientWidth;
return $('.annotator-hl').map(function() {
var $this = $(this),
left = this.getBoundingClientRect().left;
if (left >= 0 && left <= width) {
return $this.data('annotation');
}
}).get();
};
Book.on("renderer:locationChanged", updateAnnotations);
return {}
};

@ -1,125 +0,0 @@
EPUBJS.reader.search = {};
// Search Server -- https://github.com/futurepress/epubjs-search
EPUBJS.reader.search.SERVER = "";
EPUBJS.reader.search.request = function(q, callback) {
var fetch = $.ajax({
dataType: "json",
url: EPUBJS.reader.search.SERVER + "/search?q=" + encodeURIComponent(q)
});
fetch.fail(function(err) {
console.error(err);
});
fetch.done(function(results) {
callback(results);
});
};
EPUBJS.reader.plugins.SearchController = function(Book) {
var reader = this;
var $searchBox = $("#searchBox"),
$searchResults = $("#searchResults"),
$searchView = $("#searchView"),
iframeDoc;
var searchShown = false;
var onShow = function() {
query();
searchShown = true;
$searchView.addClass("shown");
};
var onHide = function() {
searchShown = false;
$searchView.removeClass("shown");
};
var query = function() {
var q = $searchBox.val();
if(q == '') {
return;
}
$searchResults.empty();
$searchResults.append("<li><p>Searching...</p></li>");
EPUBJS.reader.search.request(q, function(data) {
var results = data.results;
$searchResults.empty();
if(iframeDoc) {
$(iframeDoc).find('body').unhighlight();
}
if(results.length == 0) {
$searchResults.append("<li><p>No Results Found</p></li>");
return;
}
iframeDoc = $("#viewer iframe")[0].contentDocument;
$(iframeDoc).find('body').highlight(q, { element: 'span' });
results.forEach(function(result) {
var $li = $("<li></li>");
var $item = $("<a href='"+result.href+"' data-cfi='"+result.cfi+"'><span>"+result.title+"</span><p>"+result.highlight+"</p></a>");
$item.on("click", function(e) {
var $this = $(this),
cfi = $this.data("cfi");
e.preventDefault();
Book.gotoCfi(cfi+"/1:0");
Book.on("renderer:chapterDisplayed", function() {
iframeDoc = $("#viewer iframe")[0].contentDocument;
$(iframeDoc).find('body').highlight(q, { element: 'span' });
})
});
$li.append($item);
$searchResults.append($li);
});
});
};
$searchBox.on("search", function(e) {
var q = $searchBox.val();
//-- SearchBox is empty or cleared
if(q == '') {
$searchResults.empty();
if(reader.SidebarController.getActivePanel() == "Search") {
reader.SidebarController.changePanelTo("Toc");
}
$(iframeDoc).find('body').unhighlight();
iframeDoc = false;
return;
}
reader.SidebarController.changePanelTo("Search");
e.preventDefault();
});
return {
"show" : onShow,
"hide" : onHide
};
};

@ -1,34 +0,0 @@
document.ontouchmove = function(e){
return true;
};
Sortable.create(sortTrue, {
group: "sorting",
sort: true
});
function sendData(path){
var elements;
var counter;
var maxElements;
var tmp=[];
elements=Sortable.utils.find(sortTrue,"div");
maxElements=elements.length;
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", path);
for(counter=0;counter<maxElements;counter++){
tmp[counter]=elements[counter].getAttribute("id");
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", elements[counter].getAttribute("id"));
hiddenField.setAttribute("value", counter+1);
form.appendChild(hiddenField);
}
document.body.appendChild(form);
form.submit();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -13,9 +13,9 @@
<th>{{_('Upload')}}</th> <th>{{_('Upload')}}</th>
<th>{{_('Edit')}}</th> <th>{{_('Edit')}}</th>
<th>{{_('Passwd')}}</th> <th>{{_('Passwd')}}</th>
</tr> </tr>
{% for user in content %} {% for user in content %}
{% if not user.role_anonymous() or config.ANON_BROWSE %}
<tr> <tr>
<td><a href="{{url_for('edit_user', user_id=user.id)}}">{{user.nickname}}</a></td> <td><a href="{{url_for('edit_user', user_id=user.id)}}">{{user.nickname}}</a></td>
<td>{{user.email}}</td> <td>{{user.email}}</td>
@ -26,7 +26,8 @@
<td>{% if user.role_upload() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_upload() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if user.role_edit() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_edit() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if user.role_passwd() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if user.role_passwd() %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
</tr>
{% endif %}
{% endfor %} {% endfor %}
</table> </table>
<div class="btn btn-default"><a href="{{url_for('new_user')}}">{{_('Add new user')}}</a></div> <div class="btn btn-default"><a href="{{url_for('new_user')}}">{{_('Add new user')}}</a></div>

@ -104,11 +104,11 @@
{{entry.comments[0].text|safe}} {{entry.comments[0].text|safe}}
{% endif %} {% endif %}
{% if g.user.is_authenticated %}
<div class="more-stuff"> <div class="more-stuff">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Download, send to Kindle, reading"> <div class="btn-group" role="group" aria-label="Download, send to Kindle, reading">
{% if g.user.role_download() %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-download"></span> {{_('Download')}} <span class="glyphicon glyphicon-download"></span> {{_('Download')}}
@ -120,6 +120,9 @@
{%endfor%} {%endfor%}
</ul> </ul>
</div> </div>
{% endif %}
{% if g.user.is_authenticated %}
{% if g.user.kindle_mail %} {% if g.user.kindle_mail %}
<a href="{{url_for('send_to_kindle', book_id=entry.id)}}" id="sendbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{_('Send to Kindle')}}</a> <a href="{{url_for('send_to_kindle', book_id=entry.id)}}" id="sendbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{_('Send to Kindle')}}</a>
{% endif %} {% endif %}
@ -136,10 +139,11 @@
{%endfor%} {%endfor%}
</ul> </ul>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</br> </br>
{% if g.user.is_authenticated %}
{% if g.user.shelf.all() or g.public_shelfes %} {% if g.user.shelf.all() or g.public_shelfes %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves"> <div class="btn-group" role="group" aria-label="Add to shelves">
@ -180,7 +184,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %}
{% if g.user.role_edit() %} {% if g.user.role_edit() %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Edit/Delete book"> <div class="btn-group" role="group" aria-label="Edit/Delete book">
@ -189,7 +193,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>

@ -30,7 +30,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- <p><a href="{{ url_for('edit_book', book_id=entry.id) }}">{{entry.authors[0].name}}: {{entry.title}}</a></p> -->
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

@ -12,8 +12,8 @@
<input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}"> <input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="mail_use_ssl">{{_('Server uses SSL (StartTLS)')}}</label>
<input type="checkbox" name="mail_use_ssl" id="mail_use_ssl" {% if content.mail_use_ssl %}checked{% endif %}> <input type="checkbox" name="mail_use_ssl" id="mail_use_ssl" {% if content.mail_use_ssl %}checked{% endif %}>
<label for="mail_use_ssl">{{_('Server uses SSL (StartTLS)')}}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="mail_login">{{_('SMTP login')}}</label> <label for="mail_login">{{_('SMTP login')}}</label>

@ -2,18 +2,30 @@
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
<id>urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2</id> <id>urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2</id>
<link rel="self" <link rel="self"
href="{{url_for('feed_index')}}" href="{{request.script_root + request.full_path}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="start" <link rel="start"
href="{{url_for('feed_index')}}" href="{{url_for('feed_index')}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="up" <link rel="up"
href="{{url_for('feed_index')}}" href="{{url_for('feed_index')}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% if pagination.has_prev %}
<link rel="first"
href="{{request.script_root + request.path}}"
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% endif %}
{% if pagination.has_next %}
<link rel="next" <link rel="next"
title="{{_('Next')}}" title="{{_('Next')}}"
href="{{ next_url }}" href="{{ request.script_root + request.path }}?offset={{ pagination.next_offset }}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% endif %}
{% if pagination.has_prev %}
<link rel="previous"
href="{{request.script_root + request.path}}?offset={{ pagination.previous_offset }}"
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
{% endif %}
<link rel="search" <link rel="search"
href="{{url_for('feed_osd')}}" href="{{url_for('feed_osd')}}"
type="application/opensearchdescription+xml"/> type="application/opensearchdescription+xml"/>
@ -30,7 +42,6 @@
<updated>{{entry.timestamp}}</updated> <updated>{{entry.timestamp}}</updated>
<author> <author>
<name>{{entry.authors[0].name}}</name> <name>{{entry.authors[0].name}}</name>
<uri>{{entry.authors[0].name}}</uri>
</author> </author>
<language>{{entry.language}}</language> <language>{{entry.language}}</language>
{% for tag in entry.tags %} {% for tag in entry.tags %}
@ -38,45 +49,39 @@
term="{{tag.name}}" term="{{tag.name}}"
label="{{tag.name}}"/> label="{{tag.name}}"/>
{% endfor %} {% endfor %}
<summary>{% if entry.comments[0] %}{{entry.comments[0].text|striptags}}{% endif %}</summary> {% if entry.comments[0] %}<summary>{{entry.comments[0].text|striptags}}</summary>{% endif %}
{% if entry.has_cover %} {% if entry.has_cover %}
<link rel="http://opds-spec.org/image" href="{{ url_for('feed_get_cover', cover_path=entry.path) }}" type="image/jpg"/> <link type="image/jpeg" href="{{url_for('feed_get_cover', book_id=entry.id)}}" rel="http://opds-spec.org/image"/>
<link rel="http://opds-spec.org/cover" href="{{ url_for('feed_get_cover', cover_path=entry.path) }}" type="image/jpg"/> <link type="image/jpeg" href="{{url_for('feed_get_cover', book_id=entry.id)}}" rel="http://opds-spec.org/image/thumbnail"/>
<link rel="http://opds-spec.org/image/thumbnail" href="{{ url_for('feed_get_cover', cover_path=entry.path) }}" type="image/jpg"/>
{% endif %} {% endif %}
{% for format in entry.data %} {% for format in entry.data %}
<link rel="http://opds-spec.org/acquisition" href="{{ url_for('get_opds_download_link', book_id=entry.id, format=format.format|lower)}}" <link rel="http://opds-spec.org/acquisition" href="{{ url_for('get_opds_download_link', book_id=entry.id, format=format.format|lower)}}"
length="{{format.uncompressed_size}}" mtime="{{entry.timestamp}}" length="{{format.uncompressed_size}}" mtime="{{entry.timestamp}}" type="{{format.format|lower|mimetype}}"/>
{% if format.format|lower == "epub" %}
type="application/epub+zip"/>
{% else %}
type="application/x-mobipocket-ebook"/>
{% endif %}
{% endfor %} {% endfor %}
</entry> </entry>
{% endfor %} {% endfor %}
{% for author in authors %} {% for author in authors %}
<entry> <entry>
<title>{{author.name}}</title> <title>{{author.name}}</title>
<id>{{ url_for('feed_author', name=author.name) }}</id> <id>{{ url_for('feed_author', id=author.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_author', name=author.name)}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_author', id=author.id)}}"/>
<link type="application/atom+xml" href="{{url_for('feed_author', name=author.name)}}" rel="subsection"/> <link type="application/atom+xml" href="{{url_for('feed_author', id=author.id)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
{% for entry in categorys %} {% for entry in categorys %}
<entry> <entry>
<title>{{entry.name}}</title> <title>{{entry.name}}</title>
<id>{{ url_for('feed_category', name=entry.name) }}</id> <id>{{ url_for('feed_category', id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_category', name=entry.name)}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_category', id=entry.id)}}"/>
<link type="application/atom+xml" href="{{url_for('feed_category', name=entry.name)}}" rel="subsection"/> <link type="application/atom+xml" href="{{url_for('feed_category', id=entry.id)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
{% for entry in series %} {% for entry in series %}
<entry> <entry>
<title>{{entry.name}}</title> <title>{{entry.name}}</title>
<id>{{ url_for('feed_series', name=entry.name) }}</id> <id>{{ url_for('feed_series', id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_series', name=entry.name)}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_series', id=entry.id)}}" />
<link type="application/atom+xml" href="{{url_for('feed_series', name=entry.name)}}" rel="subsection"/> <link type="application/atom+xml" href="{{url_for('feed_series', id=entry.id)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
</feed> </feed>

@ -27,28 +27,28 @@
</entry> </entry>
<entry> <entry>
<title>{{_('Random Books')}}</title> <title>{{_('Random Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_discover')}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_discover')}}"/>
<link rel="http://opds-spec.org/featured" href="{{url_for('feed_discover')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/> <link rel="http://opds-spec.org/featured" href="{{url_for('feed_discover')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<id>{{url_for('feed_discover')}}</id> <id>{{url_for('feed_discover')}}</id>
<content type="text">{{_('Show Random Books')}}</content> <content type="text">{{_('Show Random Books')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Authors')}}</title> <title>{{_('Authors')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_authorindex')}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_authorindex')}}"/>
<link rel="subsection" href="{{url_for('feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/> <link rel="subsection" href="{{url_for('feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_authorindex')}}</id> <id>{{url_for('feed_authorindex')}}</id>
<content type="text">{{_('Books ordered by Author')}}</content> <content type="text">{{_('Books ordered by Author')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Category list')}}</title> <title>{{_('Category list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_categoryindex')}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_categoryindex')}}"/>
<link rel="subsection" href="{{url_for('feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/> <link rel="subsection" href="{{url_for('feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_categoryindex')}}</id> <id>{{url_for('feed_categoryindex')}}</id>
<content type="text">{{_('Books ordered by category')}}</content> <content type="text">{{_('Books ordered by category')}}</content>
</entry> </entry>
<entry> <entry>
<title>{{_('Series list')}}</title> <title>{{_('Series list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_seriesindex')}}" /> <link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_seriesindex')}}"/>
<link rel="subsection" href="{{url_for('feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/> <link rel="subsection" href="{{url_for('feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<id>{{url_for('feed_seriesindex')}}</id> <id>{{url_for('feed_seriesindex')}}</id>
<content type="text">{{_('Books ordered by series')}}</content> <content type="text">{{_('Books ordered by series')}}</content>

@ -0,0 +1,54 @@
{
"pubdate": "{{entry.pubdate}}",
"title": "{{entry.title}}",
"format_metadata": {
{% for format in entry.data %}
"{{format.format}}": {
"mtime": "{{entry.last_modified}}",
"size": {{format.uncompressed_size}},
"path": ""
}{% if not loop.last %},{% endif %}
{% endfor %}
},
"formats": [
{% for format in entry.data %}
"{{format.format}}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"series": null,
"cover": "/opds/cover/{{entry.id}}",
"languages": [
{% for lang in entry.languages %}
"{{lang.lang_code}}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"comments": "{% if entry.comments|length > 0 %}{{entry.comments[0].text.replace('"', '\\"')|safe}}{% endif %}",
"tags": [
{% for tag in entry.tags %}
"{{tag.name}}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"application_id": {{entry.id}},
"series_index": {% if entry.series|length > 0 %}"{{entry.series_index}}"{% else %}null{% endif %},
"last_modified": "{{entry.last_modified}}",
"author_sort": "{{entry.author_sort}}",
"uuid": "{{entry.uuid}}",
"timestamp": "{{entry.timestamp}}",
"thumbnail": "/opds/cover/{{entry.id}}",
"main_format": {
"{{entry.data[0].format|lower}}": "/download/{{entry.id}}/{{entry.data[0].format|lower}}"
},
"rating":{% if entry.ratings.__len__() > 0 %} "{{entry.ratings[0].rating}}.0"{% else %}0.0{% endif %},
"authors": [
{% for author in entry.authors %}
"{{author.name}}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"other_formats": {
{% if entry.data.__len__() > 1 %}
{% for format in entry.data[1:] %}
"{{format.format|lower}}": "/download/{{entry.id}}/{{format.format|lower}}"{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %} },
"title_sort": "{{entry.sort}}"
}

@ -24,7 +24,7 @@
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<!-- Include all compiled plugins (below), or include individual files as needed --> <!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/underscore.min.js') }}"></script> <script src="{{ url_for('static', filename='js/underscore-min.js') }}"></script>
<script src="{{ url_for('static', filename='js/intention.js') }}"></script> <script src="{{ url_for('static', filename='js/intention.js') }}"></script>
<script src="{{ url_for('static', filename='js/context.js') }}"></script> <script src="{{ url_for('static', filename='js/context.js') }}"></script>
<script src="{{ url_for('static', filename='js/plugins.js') }}"></script> <script src="{{ url_for('static', filename='js/plugins.js') }}"></script>
@ -146,10 +146,11 @@
<li><a href="{{url_for('show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list"></span> {{shelf.name}}</a></li> <li><a href="{{url_for('show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list"></span> {{shelf.name}}</a></li>
{% endfor %} {% endfor %}
{% if not g.user.is_anonymous() %} {% if not g.user.is_anonymous() %}
<li class="create-shelf"><a href="{{url_for('create_shelf')}}">{{_('Create a Shelf')}}</a></li> <li id="nav_createshelf" class="create-shelf"><a href="{{url_for('create_shelf')}}">{{_('Create a Shelf')}}</a></li>
<li id="nav_about"><a href="{{url_for('stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
<li><a href="{{url_for('stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
</ul> </ul>
</nav> </nav>
</div> </div>

@ -13,7 +13,7 @@
<label for="email">{{_('Email address')}}</label> <label for="email">{{_('Email address')}}</label>
<input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off" required> <input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off" required>
</div> </div>
{% if g.user and g.user.role_passwd() or g.user.role_admin()%} {% if ( g.user and g.user.role_passwd() or g.user.role_admin() ) and not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<label for="password">{{_('Password')}}</label> <label for="password">{{_('Password')}}</label>
<input type="password" class="form-control" name="password" id="password" value="" autocomplete="off"> <input type="password" class="form-control" name="password" id="password" value="" autocomplete="off">
@ -27,7 +27,7 @@
<label for="locale">{{_('Language')}}</label> <label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control"> <select name="locale" id="locale" class="form-control">
{% for translation in translations %} {% for translation in translations %}
<option value="{{translation.language}}" {% if translation.language == content.locale %}selected{% endif %}>{{ translation.display_name }}</option> <option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %}>{{ translation.display_name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@ -62,11 +62,12 @@
</div> </div>
{% if g.user and g.user.role_admin() and not profile %} {% if g.user and g.user.role_admin() and not profile %}
{% if not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}> <input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
<label for="admin_role">{{_('Admin user')}}</label> <label for="admin_role">{{_('Admin user')}}</label>
</div> </div>
{% endif %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}> <input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}>
<label for="download_role">{{_('Allow Downloads')}}</label> <label for="download_role">{{_('Allow Downloads')}}</label>
@ -79,19 +80,21 @@
<input type="checkbox" name="edit_role" id="edit_role" {% if content.role_edit() %}checked{% endif %}> <input type="checkbox" name="edit_role" id="edit_role" {% if content.role_edit() %}checked{% endif %}>
<label for="edit_role">{{_('Allow Edit')}}</label> <label for="edit_role">{{_('Allow Edit')}}</label>
</div> </div>
{% if not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}> <input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
<label for="passwd_role">{{_('Allow Changing Password')}}</label> <label for="passwd_role">{{_('Allow Changing Password')}}</label>
</div> </div>
{% endif %} {% endif %}
{% if g.user and g.user.role_admin() and not profile and not new_user %} {% endif %}
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="delete"> {{_('Delete this user')}} <input type="checkbox" name="delete"> {{_('Delete this user')}}
</label> </label>
</div> </div>
{% endif %} {% endif %}
<button type="submit" class="btn btn-default">{{_('Submit')}}</button> <button type="submit" id="submit" class="btn btn-default">{{_('Submit')}}</button>
{% if not profile %} {% if not profile %}
<a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a> <a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a>
{% endif %} {% endif %}

@ -5,9 +5,12 @@ from sqlalchemy import *
from sqlalchemy import exc from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin
import os import os
import config import config
import traceback
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from flask_babel import gettext as _
dbpath = os.path.join(config.APP_DB_ROOT, "app.db") dbpath = os.path.join(config.APP_DB_ROOT, "app.db")
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False) engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
@ -19,28 +22,11 @@ ROLE_DOWNLOAD = 2
ROLE_UPLOAD = 4 ROLE_UPLOAD = 4
ROLE_EDIT = 8 ROLE_EDIT = 8
ROLE_PASSWD = 16 ROLE_PASSWD = 16
ROLE_ANONYMOUS = 32
DEFAULT_PASS = "admin123" DEFAULT_PASS = "admin123"
class User(Base): class UserBase():
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True)
email = Column(String(120), unique=True, default="")
role = Column(SmallInteger, default=ROLE_USER)
password = Column(String)
kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en")
random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1)
series_books = Column(Integer, default=1)
category_books = Column(Integer, default=1)
hot_books = Column(Integer, default=1)
default_language = Column(String(3), default="all")
def is_authenticated(self): def is_authenticated(self):
return True return True
@ -74,6 +60,12 @@ class User(Base):
else: else:
return False return False
def role_anonymous(self):
if self.role is not None:
return True if self.role & ROLE_ANONYMOUS == ROLE_ANONYMOUS else False
else:
return False
def is_active(self): def is_active(self):
return True return True
@ -105,6 +97,53 @@ class User(Base):
return '<User %r>' % self.nickname return '<User %r>' % self.nickname
class User(UserBase,Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True)
email = Column(String(120), unique=True, default="")
role = Column(SmallInteger, default=ROLE_USER)
password = Column(String)
kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en")
random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1)
series_books = Column(Integer, default=1)
category_books = Column(Integer, default=1)
hot_books = Column(Integer, default=1)
default_language = Column(String(3), default="all")
class Anonymous(AnonymousUserMixin,UserBase):
def __init__(self):
self.loadSettings()
def loadSettings(self):
data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
self.nickname = data.nickname
self.role = data.role
self.random_books = data.random_books
self.default_language = data.default_language
self.language_books = data.language_books
self.series_books = data.series_books
self.category_books = data.category_books
self.hot_books = data.hot_books
self.default_language = data.default_language
self.locale = data.locale
def role_admin(self):
return False
def is_active(self):
return False
def is_anonymous(self):
return config.ANON_BROWSE
class Shelf(Base): class Shelf(Base):
__tablename__ = 'shelf' __tablename__ = 'shelf'
@ -155,6 +194,8 @@ class Settings(Base):
def migrate_Database(): def migrate_Database():
if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
create_anonymous_user()
try: try:
session.query(exists().where(User.random_books)).scalar() session.query(exists().where(User.random_books)).scalar()
session.commit() session.commit()
@ -213,6 +254,20 @@ def get_mail_settings():
return data return data
def create_anonymous_user():
user = User()
user.nickname = _("Guest")
user.email='no@email'
user.role = ROLE_ANONYMOUS
user.password = generate_password_hash('1')
session.add(user)
try:
session.commit()
except:
session.rollback()
pass
def create_admin_user(): def create_admin_user():
user = User() user = User()
@ -236,6 +291,7 @@ if not os.path.exists(dbpath):
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
create_default_config() create_default_config()
create_admin_user() create_admin_user()
create_anonymous_user()
except Exception: except Exception:
pass pass
else: else:

@ -14,7 +14,7 @@ from sqlalchemy.sql.expression import func
from sqlalchemy.sql.expression import false from sqlalchemy.sql.expression import false
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from math import ceil from math import ceil
from flask_login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed
from flask_babel import Babel from flask_babel import Babel
from flask_babel import gettext as _ from flask_babel import gettext as _
@ -47,7 +47,16 @@ except ImportError, e:
from shutil import copyfile from shutil import copyfile
from cgi import escape from cgi import escape
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
mimetypes.add_type('application/x-cbr', '.cbr')
mimetypes.add_type('application/x-cbz', '.cbz')
mimetypes.add_type('application/x-cbt', '.cbt')
mimetypes.add_type('image/vnd.djvu', '.djvu')
class ReverseProxied(object): class ReverseProxied(object):
@ -115,74 +124,23 @@ global global_queue
global_queue = None global_queue = None
class Anonymous(AnonymousUserMixin):
def __init__(self):
self.nickname = 'Guest'
self.role = -1
def role_admin(self):
return False
def role_download(self):
return False
def role_upload(self):
return False
def role_edit(self):
return False
def filter_language(self):
return 'all'
def show_random_books(self):
return True
def show_hot_books(self):
return True
def show_series(self):
return True
def show_category(self):
return True
def show_language(self):
return True
def is_anonymous(self):
return config.ANON_BROWSE
lm = LoginManager(app) lm = LoginManager(app)
lm.init_app(app) lm.init_app(app)
lm.login_view = 'login' lm.login_view = 'login'
lm.anonymous_user = Anonymous lm.anonymous_user = ub.Anonymous
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
LANGUAGES = {
'en': 'English',
'de': 'Deutsch',
'fr': 'Français',
'es': 'Español',
'zh_Hans_CN': '简体中文'
}
@babel.localeselector @babel.localeselector
def get_locale(): def get_locale():
# if a user is logged in, use the locale from the user settings # if a user is logged in, use the locale from the user settings
user = getattr(g, 'user', None) user = getattr(g, 'user', None)
translations = babel.list_translations() + [LC('en')]
if user is not None and hasattr(user, "locale"): if user is not None and hasattr(user, "locale"):
if user.locale == 'zh':
return 'zh_Hans_CN'
return user.locale return user.locale
translations=[item.language for item in babel.list_translations()]+ ['en']
preferred = [x.replace('-', '_') for x in request.accept_languages.values()] preferred = [x.replace('-', '_') for x in request.accept_languages.values()]
if config.DEFAULT_LANG: return negotiate_locale(preferred, translations)
return config.DEFAULT_LANG
return negotiate_locale(preferred, LANGUAGES.keys())
@babel.timezoneselector @babel.timezoneselector
@ -243,9 +201,24 @@ def requires_basic_auth_if_no_ano(f):
# simple pagination for the feed # simple pagination for the feed
class Pagination(object): class Pagination(object):
def __init__(self, page, per_page, total_count): def __init__(self, page, per_page, total_count):
self.page = page self.page = int(page)
self.per_page = per_page self.per_page = int(per_page)
self.total_count = total_count self.total_count = int(total_count)
@property
def next_offset(self):
return int(self.page * self.per_page)
@property
def previous_offset(self):
return int((self.page-2) * self.per_page)
@property
def last_offset(self):
last = int(self.total_count) - int(self.per_page)
if last < 0:
last = 0
return int(last)
@property @property
def pages(self): def pages(self):
@ -298,6 +271,14 @@ def shortentitle_filter(s):
s = textwrap.wrap(s, 60, break_long_words=False)[0] + ' [...]' s = textwrap.wrap(s, 60, break_long_words=False)[0] + ' [...]'
return s return s
@app.template_filter('mimetype')
def mimetype_filter(val):
try:
s = mimetypes.types_map['.'+val]
except:
s= 'application/octet-stream'
return s
def admin_required(f): def admin_required(f):
""" """
@ -444,11 +425,18 @@ def feed_osd():
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/search/<query>")
def feed_cc_search(query):
return feed_search(query.strip())
@app.route("/opds/search", methods=["GET"]) @app.route("/opds/search", methods=["GET"])
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_search(): def feed_normal_search():
term = request.args.get("query").strip() return feed_search(request.args.get("query").strip())
def feed_search(term):
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
@ -457,8 +445,9 @@ def feed_search():
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all() db.Books.title.like("%" + term + "%"))).filter(filter).all()
entriescount = len(entries) if len(entries) > 0 else 1
xml = render_template('feed.xml', searchterm=term, entries=entries) pagination = Pagination( 1,entriescount,entriescount)
xml = render_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
else: else:
xml = render_template('feed.xml', searchterm="") xml = render_template('feed.xml', searchterm="")
response = make_response(xml) response = make_response(xml)
@ -469,7 +458,7 @@ def feed_search():
@app.route("/opds/new") @app.route("/opds/new")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_new(): def feed_new():
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
@ -478,8 +467,9 @@ def feed_new():
off = 0 off = 0
entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit( entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit(
config.NEWEST_BOOKS) config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Books).filter(filter).all()))
xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@ -488,16 +478,16 @@ def feed_new():
@app.route("/opds/discover") @app.route("/opds/discover")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_discover(): def feed_discover():
off = request.args.get("start_index") # off = request.args.get("start_index")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True filter = True
if not off: # if not off:
off = 0 # off = 0
entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, pagination = Pagination(1, config.NEWEST_BOOKS,int(config.NEWEST_BOOKS))
next_url="/opds/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@ -506,7 +496,7 @@ def feed_discover():
@app.route("/opds/hot") @app.route("/opds/hot")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_hot(): def feed_hot():
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
@ -515,9 +505,9 @@ def feed_hot():
off = 0 off = 0
entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset( entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset(
off).limit(config.NEWEST_BOOKS) off).limit(config.NEWEST_BOOKS)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
xml = render_template('feed.xml', entries=entries, len(db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).all()))
next_url="/opds/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@ -526,7 +516,8 @@ def feed_hot():
@app.route("/opds/author") @app.route("/opds/author")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_authorindex(): def feed_authorindex():
off = request.args.get("start_index") off = request.args.get("offset")
# ToDo: Language filter not working
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
@ -534,27 +525,29 @@ def feed_authorindex():
if not off: if not off:
off = 0 off = 0
authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', authors=authors, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Authors).all()))
xml = render_template('feed.xml', authors=authors, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/author/<name>") @app.route("/opds/author/<int:id>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_author(name): def feed_author(id):
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True filter = True
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id )).filter(
filter).offset(off).limit(config.NEWEST_BOOKS) filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id )).filter(filter).all()))
xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@ -563,31 +556,33 @@ def feed_author(name):
@app.route("/opds/category") @app.route("/opds/category")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_categoryindex(): def feed_categoryindex():
off = request.args.get("start_index") off = request.args.get("offset")
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', categorys=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Tags).all()))
xml = render_template('feed.xml', categorys=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/category/<name>") @app.route("/opds/category/<int:id>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_category(name): def feed_category(id):
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True filter = True
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.name.like("%" + name + "%"))).order_by( entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id==id)).order_by(
db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id==id)).filter(filter).all()))
xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@ -596,43 +591,50 @@ def feed_category(name):
@app.route("/opds/series") @app.route("/opds/series")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_seriesindex(): def feed_seriesindex():
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', series=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Series).all()))
xml = render_template('feed.xml', series=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/series/<name>") @app.route("/opds/series/<int:id>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_series(name): def feed_series(id):
off = request.args.get("start_index") off = request.args.get("offset")
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True filter = True
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.name.like("%" + name + "%"))).order_by( entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).order_by(
db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', entries=entries, pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS,
next_url="/opds/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) len(db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).filter(filter).all()))
xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/opds/download/<int:book_id>/<format>") @app.route("/opds/download/<book_id>/<format>/")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@download_required @download_required
def get_opds_download_link(book_id, format): def get_opds_download_link(book_id, format):
format = format.split(".")[0] format = format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first()
if current_user.is_authenticated:
helper.update_download(book_id, int(current_user.id)) helper.update_download(book_id, int(current_user.id))
author = helper.get_normalized_author(book.author_sort) author = helper.get_normalized_author(book.author_sort)
file_name = book.title file_name = book.title
@ -640,9 +642,20 @@ def get_opds_download_link(book_id, format):
file_name = author + '-' + file_name file_name = author + '-' + file_name
file_name = helper.get_valid_filename(file_name) file_name = helper.get_valid_filename(file_name)
response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format)) response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format))
response.headers["Content-Disposition"] = "attachment; filename=%s.%s" % (data.name, format) response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (data.name, format)
return response return response
@app.route("/ajax/book/<string:uuid>")
@requires_basic_auth_if_no_ano
def get_metadata_calibre_companion(uuid):
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%"+uuid+"%")).first()
if entry is not None :
js = render_template('json.txt',entry=entry)
response = make_response(js)
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response
else:
return ""
@app.route("/get_authors_json", methods=['GET', 'POST']) @app.route("/get_authors_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
@ -1042,11 +1055,14 @@ def advanced_search():
def get_cover(cover_path): def get_cover(cover_path):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg")
@app.route("/opds/thumb_240_240/<path:book_id>")
@app.route("/opds/cover/<path:cover_path>") @app.route("/opds/cover_240_240/<path:book_id>")
@app.route("/opds/cover_90_90/<path:book_id>")
@app.route("/opds/cover/<path:book_id>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_get_cover(cover_path): def feed_get_cover(book_id):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
return send_from_directory(os.path.join(config.DB_ROOT, book.path), "cover.jpg")
@app.route("/read/<int:book_id>/<format>") @app.route("/read/<int:book_id>/<format>")
@ -1110,12 +1126,14 @@ def read_book(book_id, format):
@app.route("/download/<int:book_id>/<format>") @app.route("/download/<int:book_id>/<format>")
@login_required @login_required_if_no_ano
@download_required @download_required
def get_download_link(book_id, format): def get_download_link(book_id, format):
format = format.split(".")[0] format = format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first()
if data:
if current_user.is_authenticated: # collect downloaded books only for registered user and not for anonymous user
helper.update_download(book_id, int(current_user.id)) helper.update_download(book_id, int(current_user.id))
author = helper.get_normalized_author(book.author_sort) author = helper.get_normalized_author(book.author_sort)
file_name = book.title file_name = book.title
@ -1123,6 +1141,10 @@ def get_download_link(book_id, format):
file_name = author + '-' + file_name file_name = author + '-' + file_name
file_name = helper.get_valid_filename(file_name) file_name = helper.get_valid_filename(file_name)
response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format)) response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format))
try:
response.headers["Content-Type"]=mimetypes.types_map['.'+format]
except:
pass
response.headers["Content-Disposition"] = \ response.headers["Content-Disposition"] = \
"attachment; " \ "attachment; " \
"filename={utf_filename}.{suffix};" \ "filename={utf_filename}.{suffix};" \
@ -1131,7 +1153,8 @@ def get_download_link(book_id, format):
suffix=format suffix=format
) )
return response return response
else:
abort(404)
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
def register(): def register():
@ -1249,7 +1272,7 @@ def remove_from_shelf(shelf_id, book_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if not shelf.is_public and not shelf.user_id == int(current_user.id): if not shelf.is_public and not shelf.user_id == int(current_user.id):
flash("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name) flash("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name)
return redirect(url_for('index', _external=True)) return redirect(url_for('index'))
book_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, book_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
ub.BookShelf.book_id == book_id).first() ub.BookShelf.book_id == book_id).first()
@ -1357,7 +1380,7 @@ def show_shelf(shelf_id):
@app.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"]) @app.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"])
@login_required_if_no_ano @login_required
def order_shelf(shelf_id): def order_shelf(shelf_id):
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
@ -1398,7 +1421,12 @@ def profile():
lang.name = _(isoLanguages.get(part3=lang.lang_code).name) lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
translations = babel.list_translations() + [LC('en')] translations = babel.list_translations() + [LC('en')]
for book in content.downloads: for book in content.downloads:
downloadBook=db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
if downloadBook:
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
else:
ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
ub.session.commit()
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
content.random_books = 0 content.random_books = 0
@ -1512,7 +1540,7 @@ def new_user():
ub.session.add(content) ub.session.add(content)
ub.session.commit() ub.session.commit()
flash(_("User '%(user)s' created", user=content.nickname), category="success") flash(_("User '%(user)s' created", user=content.nickname), category="success")
return redirect(url_for('admin', _external=True)) return redirect(url_for('admin'))
except IntegrityError: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Found an existing account for this email address or nickname."), category="error") flash(_(u"Found an existing account for this email address or nickname."), category="error")
@ -1541,15 +1569,13 @@ def edit_mailsettings():
flash(_(u"Mail settings updated"), category="success") flash(_(u"Mail settings updated"), category="success")
except e: except e:
flash(e, category="error") flash(e, category="error")
if to_save["test"]: if "test" in to_save and to_save["test"]:
result=helper.send_test_mail(current_user.kindle_mail) result=helper.send_test_mail(current_user.kindle_mail)
if result is None: if result is None:
flash(_(u"Test E-Mail successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), flash(_(u"Test E-Mail successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail),
category="success") category="success")
else: else:
flash(_(u"There was an error sending the Test E-Mail: %(res)s", res=result), category="error") flash(_(u"There was an error sending the Test E-Mail: %(res)s", res=result), category="error")
else:
flash(_(u"Mail settings updated"), category="success")
return render_template("email_edit.html", content=content, title=_("Edit mail settings")) return render_template("email_edit.html", content=content, title=_("Edit mail settings"))
@ -1568,15 +1594,20 @@ def edit_user(user_id):
lang.name = _(isoLanguages.get(part3=lang.lang_code).name) lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
translations = babel.list_translations() + [LC('en')] translations = babel.list_translations() + [LC('en')]
for book in content.downloads: for book in content.downloads:
downloadBook=db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
if downloadBook:
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
else:
ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
ub.session.commit()
if request.method == "POST": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
if "delete" in to_save: if "delete" in to_save:
ub.session.delete(content) ub.session.delete(content)
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
return redirect(url_for('admin', _external=True)) return redirect(url_for('admin'))
else: else:
if to_save["password"]: if "password" in to_save and to_save["password"]:
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
if "admin_role" in to_save and not content.role_admin(): if "admin_role" in to_save and not content.role_admin():
@ -1620,7 +1651,7 @@ def edit_user(user_id):
content.hot_books = 1 content.hot_books = 1
if "default_language" in to_save: if "default_language" in to_save:
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
if to_save["locale"]: if "locale" in to_save and to_save["locale"]:
content.locale = to_save["locale"] content.locale = to_save["locale"]
if to_save["email"] and to_save["email"] != content.email: if to_save["email"] and to_save["email"] != content.email:
content.email = to_save["email"] content.email = to_save["email"]
@ -1637,7 +1668,7 @@ def edit_user(user_id):
@app.route("/admin/book/<int:book_id>", methods=['GET', 'POST']) @app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
@login_required @login_required_if_no_ano
@edit_required @edit_required
def edit_book(book_id): def edit_book(book_id):
# create the function for sorting... # create the function for sorting...
@ -1835,18 +1866,18 @@ def edit_book(book_id):
for b in edited_books_id: for b in edited_books_id:
helper.update_dir_stucture(b) helper.update_dir_stucture(b)
if "detail_view" in to_save: if "detail_view" in to_save:
return redirect(url_for('show_book', id=book.id, _external=True)) return redirect(url_for('show_book', id=book.id))
else: else:
return render_template('edit_book.html', book=book, authors=author_names, cc=cc) return render_template('edit_book.html', book=book, authors=author_names, cc=cc)
else: else:
return render_template('edit_book.html', book=book, authors=author_names, cc=cc) return render_template('edit_book.html', book=book, authors=author_names, cc=cc)
else: else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("index", _external=True)) return redirect(url_for("index"))
@app.route("/upload", methods=["GET", "POST"]) @app.route("/upload", methods=["GET", "POST"])
@login_required @login_required_if_no_ano
@upload_required @upload_required
def upload(): def upload():
if not config.UPLOADING: if not config.UPLOADING:
@ -1872,12 +1903,12 @@ def upload():
os.makedirs(filepath) os.makedirs(filepath)
except OSError: except OSError:
flash(_(u"Failed to create path %s (Permission denied)." % filepath), category="error") flash(_(u"Failed to create path %s (Permission denied)." % filepath), category="error")
return redirect(url_for('index', _external=True)) return redirect(url_for('index'))
try: try:
copyfile(meta.file_path, saved_filename) copyfile(meta.file_path, saved_filename)
except OSError, e: except OSError, e:
flash(_(u"Failed to store file %s (Permission denied)." % saved_filename), category="error") flash(_(u"Failed to store file %s (Permission denied)." % saved_filename), category="error")
return redirect(url_for('index', _external=True)) return redirect(url_for('index'))
try: try:
os.unlink(meta.file_path) os.unlink(meta.file_path)
except OSError, e: except OSError, e:

@ -10,7 +10,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Bootstrap 3 HTML5 interface - Bootstrap 3 HTML5 interface
- User management - User management
- Admin interface - Admin interface
- User Interface in english, german and french - User Interface in english, french, german, simplified chinese, spanish
- OPDS feed for eBook reader apps - OPDS feed for eBook reader apps
- Filter and search by titles, authors, tags, series and language - Filter and search by titles, authors, tags, series and language
- Create custom book collection (shelves) - Create custom book collection (shelves)
@ -20,7 +20,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Support for public user registration - Support for public user registration
- Send eBooks to Kindle devices with the click of a button - Send eBooks to Kindle devices with the click of a button
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf) - Support for reading eBooks directly in the browser (.txt, .epub, .pdf)
- Upload new books in PDF format - Upload new books in PDF, epub, fb2 format
- Support for Calibre custom columns - Support for Calibre custom columns
- Fine grained per-user permissions - Fine grained per-user permissions
@ -67,13 +67,13 @@ http {
server 127.0.0.1:8083; server 127.0.0.1:8083;
} }
server { server {
location /calibre { location /calibre-web {
proxy_bind $server_addr; proxy_bind $server_addr;
proxy_pass http://127.0.0.1:8083; proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme; proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /calibre; proxy_set_header X-Script-Name /calibre-web;
} }
} }
} }
@ -81,7 +81,7 @@ http {
Apache 2.4 configuration for a local server listening on port 443, mapping calibre web to /calibre-web: Apache 2.4 configuration for a local server listening on port 443, mapping calibre web to /calibre-web:
The following modules have to be activated: headers, proxy, proxy_html, proxy_http, rewrite, xml2enc. The following modules have to be activated: headers, proxy, rewrite.
``` ```
Listen 443 Listen 443
@ -92,12 +92,11 @@ Listen 443
SSLCertificateFile "C:\Apache24\conf\ssl\test.crt" SSLCertificateFile "C:\Apache24\conf\ssl\test.crt"
SSLCertificateKeyFile "C:\Apache24\conf\ssl\test.key" SSLCertificateKeyFile "C:\Apache24\conf\ssl\test.key"
<Location /calibre-web> <Location "/calibre-web" >
ProxyHTMLEnable On RequestHeader set X-SCRIPT-NAME /calibre-web
ProxyPass http://127.0.0.1:8083/ RequestHeader set X-SCHEME https
ProxyPassReverse http://127.0.0.1:8083/ ProxyPass http://localhost:8083/
Header edit Location "^http://(.*?)/" "https://$1/calibre-web/" ProxyPassReverse http://localhost:8083/
ProxyHTMLURLMap / /calibre-web/
</Location> </Location>
</VirtualHost> </VirtualHost>
``` ```

Loading…
Cancel
Save