1
0
mirror of https://github.com/lrsjng/h5ai.git synced 2025-08-27 15:49:51 +02:00

Compare commits

...

55 Commits

Author SHA1 Message Date
Lars Jung
aa94de4945 Update. 2021-01-24 16:10:29 +01:00
Lars Jung
d99a14eaa9 Update. 2021-01-24 16:05:44 +01:00
Lars Jung
7d914d45fb Update. 2021-01-24 16:04:10 +01:00
Lars Jung
2243f2e6a5 Merge pull request #739 from carlos22/patch-1
Add EXIF based image rotate for firefox
2021-01-24 15:59:55 +01:00
Lars Jung
4b6c8d796a Merge pull request #748 from fuchse-ohren/patch-1
Update class-setup.php
2021-01-24 15:59:06 +01:00
Lars Jung
d257b79067 Merge pull request #762 from myl7/l10n
Fix #760
2021-01-24 15:54:25 +01:00
Lars Jung
6b147c7bfb Merge pull request #761 from myl7/master
Fix #758
2021-01-24 15:54:11 +01:00
myl7
0f2c9d095d Fix class static variable usage. 2020-11-08 12:30:17 +08:00
myl7
184e01c0a6 Fix #760.
Limit l10n iso code available values.
2020-11-08 12:05:34 +08:00
myl7
136c654432 Fix #758.
Normalize paths before dirname/basename operations.
2020-11-07 22:58:22 +08:00
Fox Ears
c122d5e44a Update class-setup.php
Windows用にwhereコマンドに対応
2020-08-18 12:07:09 +09:00
Lars Jung
d81d8a9298 Update. 2020-07-26 22:45:36 +02:00
Lars Jung
a556a2be50 Update. 2020-07-25 23:06:16 +02:00
Lars Jung
6ef253e44a Update. 2020-07-24 10:03:34 +02:00
Lars Jung
252ffc5cb9 Update. 2020-07-24 09:57:30 +02:00
Lars Jung
70e112a43a Update. 2020-07-09 09:47:07 +02:00
Lars Jung
e8434f5d89 Update. 2020-07-09 09:46:32 +02:00
Karl G
984aca0093 Add EXIF based image rotate for firefox
Chrome 81 / Safari 13.1 does this automatically. Firefox still requires this CSS. According to spec (css4) it is now deprecated but implementation is optional.
2020-04-20 15:31:00 +02:00
Lars Jung
673ee7ccc7 Update. 2020-03-07 13:57:56 +01:00
Lars Jung
059b2e16ed Update. 2020-03-07 13:53:53 +01:00
Lars Jung
84d84e163b Merge pull request #732 from Woet/patch-1
Close the session for writing when sending a file
2020-03-07 13:18:29 +01:00
Woet
6da08269d8 Close the session for writing when sending a file
Right now, if you download an archive, you won't be able to browse within h5ai until the download is finished. This is because the session data is locked to prevent concurrent writes, which also prevents concurrent requests.

By adding session_write_close() in the on_download function, the session lock is released and concurrent requests will work.
2020-02-27 11:41:56 +08:00
Lars Jung
ce939c3115 Update. 2020-02-19 18:56:23 +01:00
Lars Jung
8889ac4e2e Update. 2020-02-19 18:36:12 +01:00
Lars Jung
2a3f860473 Update. 2020-02-16 17:08:31 +01:00
Lars Jung
5e72d0f6a3 Update. 2020-02-16 02:38:06 +01:00
Lars Jung
bc4f964b24 Update. 2020-02-16 02:37:00 +01:00
Lars Jung
3930e8c204 Merge pull request #726 from thomo/patch-1
Fix cache path in thumbnail comment
2020-02-16 02:25:01 +01:00
Thomas Mohaupt
b1960a0d15 Fix cache path in thumbnail comment
The mentioned cache path does not exists.
2019-12-15 10:48:08 +01:00
Lars Jung
a1bb7552dc Update changelog. 2019-08-12 22:00:22 +02:00
Lars Jung
324242a584 Update deps. 2019-08-12 21:54:07 +02:00
Lars Jung
78d6bf6c27 Update. 2019-05-05 23:03:07 +02:00
Lars Jung
745985bf18 Update. 2019-04-24 02:12:12 +02:00
Lars Jung
50167d3382 Update. 2019-04-24 02:09:57 +02:00
Lars Jung
24d8359a2e Clean up. 2019-04-16 20:59:05 +02:00
Lars Jung
869f1f5cda Merge pull request #694 from Daniel15/header-footer-basedir
Stop searching for header/footer files at web root
2019-04-16 14:19:56 +02:00
Lars Jung
5ffc5cf6fd Clean up. 2019-04-16 14:17:41 +02:00
Lars Jung
8966a517c6 Merge pull request #619 from djtm/patch-1
Fix Invalid argument supplied for foreach() in class-archive.php:138
2019-04-16 14:08:23 +02:00
Lars Jung
a92c44ef0a Merge pull request #687 from Retrobottega/master
Update Italian Translation
2019-04-16 14:06:55 +02:00
Lars Jung
59a41665b0 Merge pull request #683 from Azhe403/patch-1
add Indonesian as id.json
2019-04-16 14:06:39 +02:00
Lars Jung
f7333eb51c Merge pull request #586 from thadius856/master
Update options.json
2019-04-16 14:06:04 +02:00
Lars Jung
02bf7079dd Merge pull request #584 from carloshbcabral/master
Update pt-pt.json and added pt-br.json
2019-04-16 14:05:49 +02:00
Lars Jung
8a15390694 Update deps. 2019-04-16 14:03:51 +02:00
Lars Jung
6f61a12772 Update. 2019-04-05 23:02:49 +02:00
Lars Jung
566338020a Update. 2019-04-05 23:02:26 +02:00
Lars Jung
52ff7462a9 Clean code. 2019-03-22 23:01:35 +01:00
Lars Jung
427ca82722 Maintenance. 2019-03-22 02:33:58 +01:00
Daniel Lo Nigro
946c862dc4 Add option to stop searching for header/footer files once the root is reached
References https://github.com/lrsjng/h5ai/issues/669
2019-02-19 00:47:06 -08:00
SalGnt
2d371d112d Update it.json 2018-10-02 15:24:33 +02:00
Azhe-kun
77d6f4af7f add Indonesian as id.json 2018-09-03 18:59:06 +07:00
djtm
e2a743bca5 Fix Invalid argument supplied for foreach() in class-archive.php:139
Before this change, when trying to download a folder oder single file via the download (nginx/php7.1-fpm) option I get below error.

```
[error] 22294#22294: *1376 FastCGI sent in stderr: "PHP message: PHP Warning:  Invalid argument supplied for foreach() in ..._h5ai/private/php/ext/class-archive.php on line 138
PHP message: PHP Stack trace:
PHP message: PHP   1. {main}() ..._h5ai/public/index.php:0
PHP message: PHP   2. Bootstrap::run() /..._h5ai/public/index.php:17
PHP message: PHP   3. Api->apply() /..._h5ai/private/php/class-bootstrap.php:19
PHP message: PHP   4. Api->on_download() /..._h5ai/private/php/core/class-api.php:20
PHP message: PHP   5. Archive->output() /..._h5ai/private/php/core/class-api.php:37
PHP message: PHP   6. Archive->add_hrefs() /..._h5ai/private/php/ext/class-archive.php:28" while reading response header from upstream, 
client: 10.0.2.2, server: localhost.dev, request: "POST /downloads/? HTTP/1.1",
upstream: "fastcgi://127.0.0.1:9000", host: "localhost.dev:28888", referrer: "http://localhost.dev:28888/downloads/"
```
2017-04-24 03:09:21 +02:00
thadius856
ab4fa886c3 Update options.json
Fixed comment to indicate correct path.
2016-09-23 22:02:22 -07:00
Carlos Cabral
4b08092c18 Update pt-br.json 2016-09-09 03:38:40 -03:00
Carlos Cabral
0fd57cad51 Update and rename pt.json to pt-pt.json 2016-09-09 03:38:36 -03:00
Carlos Cabral
db764b6780 Create pt-br.json 2016-09-09 03:35:55 -03:00
39 changed files with 5182 additions and 4217 deletions

View File

@@ -12,7 +12,7 @@ insert_final_newline = true
trim_trailing_whitespace = true
[{package.json,.travis.yml,.eslintrc}]
[{*.json,*.yml}]
indent_size = 2

View File

@@ -1,3 +1,3 @@
build
node_modules
**/vendor
/build/
/local/
/node_modules/

View File

@@ -6,7 +6,7 @@
node: true
parserOptions:
ecmaVersion: 6
ecmaVersion: 2020
rules:
array-bracket-spacing: [2, never]

10
.gitignore vendored
View File

@@ -1,4 +1,6 @@
build
local
node_modules
npm-debug.log
/.nyc_output/
/build/
/coverage/
/local/
/node_modules/
/npm-debug.log

View File

@@ -1,6 +1,38 @@
# Changelog
* now require PHP 7.0.0+
* fix archive-single-item problem
* add header/footer search stop condition
* update languages (`id`, `it`, `pt-br`, `pt-pt`)
* add EXIF-based image rotation
* add `where` to command detection command list
* fix #758
* fix #760
* add `@babel/core` 7.12.10
* add `@babel/preset-env` 7.12.11
* remove `babel-loader`
* update `eslint` to 7.18.0
* update `ghu` to 0.26.0
* update `jsdom` to 16.4.0
* update `kjua` to 0.9.0
* update `lolight` to 1.4.0
* update `marked` to 1.2.7
* update `null-loader` to 4.0.1
* update `scar` to 2.3.0
## v0.29.2 - *2019-03-22*
* update `babel-loader` to 7.1.1
* update `eslint` to 5.15.3
* update `ghu` to 0.13.0
* update `jsdom` to 14.0.0
* update `kjua` to 0.2.0
* update `lolight` to 1.0.0
* update `scar` to 1.2.0
## v0.29.1 - *2019-01-20*
* replace `babel-preset-es2015` with `babel-preset-env`

View File

@@ -2,7 +2,7 @@
[![license][license-img]][github] [![web][web-img]][web] [![github][github-img]][github]
A modern HTTP web server index for Apache httpd, lighttpd, nginx and Cherokee.
A modern HTTP web server index for Apache httpd, lighttpd, and nginx.
## Important
@@ -20,7 +20,8 @@ There are installation ready packages for the latest [releases][release] and
[dev builds][develop]. But to build **h5ai** yourself either `git clone` or
download the repository. From within the root folder run the following
commands to find a fresh zipball in folder `build` (tested on linux only,
requires [`node 6.0+`][node] to be installed).
requires [`node 10.0+`][node] to be installed, might work on other
configurations).
~~~sh
> npm install
@@ -32,7 +33,7 @@ requires [`node 6.0+`][node] to be installed).
The MIT License (MIT)
Copyright (c) 2019 Lars Jung (https://larsjung.de)
Copyright (c) 2020 Lars Jung (https://larsjung.de)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

62
ghu.js
View File

@@ -1,7 +1,7 @@
const {resolve, join} = require('path');
const {
ghu, autoprefixer, cssmin, each, ife, includeit, jszip, less, mapfn,
newerThan, pug, read, remove, run, uglify, watch, webpack, wrap, write
pug, read, remove, run, uglify, watch, webpack, wrap, write
} = require('ghu');
const ROOT = resolve(__dirname);
@@ -10,24 +10,26 @@ const TEST = join(ROOT, 'test');
const BUILD = join(ROOT, 'build');
const mapper = mapfn.p(SRC, BUILD).s('.less', '.css').s('.pug', '');
const webpackCfg = include => ({
const WEBPACK_CFG = {
mode: 'none',
module: {
loaders: [
rules: [
{
include,
loader: 'babel-loader',
query: {
cacheDirectory: true,
presets: ['babel-preset-env']
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /jsdom/,
loader: 'null-loader'
use: 'null-loader'
}
]
}
});
};
ghu.defaults('release');
@@ -45,9 +47,8 @@ ghu.before(runtime => {
}
runtime.comment = `${runtime.pkg.name} v${runtime.pkg.version} - ${runtime.pkg.homepage}`;
runtime.commentJs = `/* ${runtime.comment} */\n`;
runtime.commentHtml = `<!-- ${runtime.comment} -->`;
runtime.comment_js = `/* ${runtime.comment} */\n`;
runtime.comment_html = `<!-- ${runtime.comment} -->`;
console.log(runtime.comment);
});
@@ -62,51 +63,42 @@ ghu.task('clean', 'delete build folder', () => {
return remove(BUILD);
});
ghu.task('lint', 'lint all JavaScript files with eslint', () => {
return run('eslint .', {stdio: 'inherit'});
});
ghu.task('build:scripts', runtime => {
return read(`${SRC}/_h5ai/public/js/scripts.js`)
.then(newerThan(mapper, `${SRC}/_h5ai/public/js/**`))
.then(webpack(webpackCfg([SRC]), {showStats: false}))
.then(webpack(WEBPACK_CFG))
.then(wrap('\n\n// @include "pre.js"\n\n'))
.then(includeit())
.then(ife(() => runtime.args.production, uglify({compressor: {warnings: false}})))
.then(wrap(runtime.commentJs))
.then(ife(() => runtime.args.production, uglify()))
.then(wrap(runtime.comment_js))
.then(write(mapper, {overwrite: true}));
});
ghu.task('build:styles', runtime => {
return read(`${SRC}/_h5ai/public/css/*.less`)
.then(newerThan(mapper, `${SRC}/_h5ai/public/css/**`))
.then(includeit())
.then(less())
.then(autoprefixer())
.then(ife(() => runtime.args.production, cssmin()))
.then(wrap(runtime.commentJs))
.then(wrap(runtime.comment_js))
.then(write(mapper, {overwrite: true}));
});
ghu.task('build:pages', runtime => {
return read(`${SRC}: **/*.pug, ! **/*.tpl.pug`)
.then(newerThan(mapper, `${SRC}/**/*.tpl.pug`))
.then(pug({pkg: runtime.pkg}))
.then(wrap('', runtime.commentHtml))
.then(wrap('', runtime.comment_html))
.then(write(mapper, {overwrite: true}));
});
ghu.task('build:copy', runtime => {
const mapperRoot = mapfn.p(ROOT, join(BUILD, '_h5ai'));
const mapper_root = mapfn.p(ROOT, join(BUILD, '_h5ai'));
return Promise.all([
read(`${SRC}/**/conf/*.json`)
.then(newerThan(mapper))
.then(wrap(runtime.commentJs))
.then(wrap(runtime.comment_js))
.then(write(mapper, {overwrite: true, cluster: true})),
read(`${SRC}: **, ! **/*.js, ! **/*.less, ! **/*.pug, ! **/conf/*.json`)
.then(newerThan(mapper))
.then(each(obj => {
if ((/index\.php$/).test(obj.source)) {
obj.content = obj.content.replace('{{VERSION}}', runtime.pkg.version);
@@ -115,23 +107,20 @@ ghu.task('build:copy', runtime => {
.then(write(mapper, {overwrite: true, cluster: true})),
read(`${ROOT}/*.md`)
.then(newerThan(mapperRoot))
.then(write(mapperRoot, {overwrite: true, cluster: true}))
.then(write(mapper_root, {overwrite: true, cluster: true}))
]);
});
ghu.task('build:tests', ['build:styles'], 'build the test suite', () => {
return Promise.all([
read(`${BUILD}/_h5ai/public/css/styles.css`)
.then(newerThan(`${BUILD}/test/h5ai-styles.css`))
.then(write(`${BUILD}/test/h5ai-styles.css`, {overwrite: true})),
read(`${TEST}/index.html`)
.then(newerThan(`${BUILD}/test/index.html`))
.then(write(`${BUILD}/test/index.html`, {overwrite: true})),
read(`${TEST}: index.js`)
.then(webpack(webpackCfg([SRC, TEST]), {showStats: false}))
.then(webpack(WEBPACK_CFG))
.then(wrap(`\n\n// @include "${SRC}/**/js/pre.js"\n\n`))
.then(includeit())
.then(write(mapfn.p(TEST, `${BUILD}/test`), {overwrite: true}))
@@ -149,11 +138,10 @@ ghu.task('deploy', ['build'], 'deploy to a specified path with :dest=/some/path'
}
console.log(`deploy to ${runtime.args.dest}`);
const mapperDeploy = mapfn.p(BUILD, resolve(runtime.args.dest));
const mapper_deploy = mapfn.p(BUILD, resolve(runtime.args.dest));
return read(`${BUILD}/_h5ai/**`)
.then(newerThan(mapperDeploy))
.then(write(mapperDeploy, {overwrite: true, cluster: true}));
.then(write(mapper_deploy, {overwrite: true, cluster: true}));
});
ghu.task('watch', runtime => {

8879
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
{
"name": "h5ai",
"version": "0.29.1",
"version": "0.30.0",
"description": "Modern HTTP web server index.",
"homepage": "https://larsjung.de/h5ai/",
"bugs": "https://github.com/lrsjng/h5ai/issues",
"author": "Lars Jung <lrsjng@gmail.com> (https://larsjung.de)",
"license": "MIT",
"repository": {
@@ -11,22 +10,22 @@
"url": "https://github.com/lrsjng/h5ai.git"
},
"scripts": {
"build": "node ghu release"
"lint": "eslint .",
"test": "node test",
"build": "node ghu release",
"precommit": "npm run -s lint && npm run -s test"
},
"devDependencies": {
"babel-loader": "6.2.4",
"babel-preset-env": "1.7.0",
"eslint": "5.14.1",
"ghu": "0.12.0",
"jsdom": "9.2.0",
"kjua": "0.1.2",
"lolight": "0.6.0",
"marked": "0.6.1",
"@babel/core": "7.12.10",
"@babel/preset-env": "7.12.11",
"eslint": "7.18.0",
"ghu": "0.26.0",
"jsdom": "16.4.0",
"kjua": "0.9.0",
"lolight": "1.4.0",
"marked": "1.2.7",
"normalize.css": "8.0.1",
"null-loader": "0.1.1",
"scar": "1.0.0"
},
"engines": {
"node": ">=10.0.0"
"null-loader": "4.0.1",
"scar": "2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
{
"lang": "Bahasa Indonesia",
"dateFormat": "DD-MM-YYYY HH:mm",
"details": "rincian",
"download": "unduh",
"empty": "kosong",
"files": "berkas",
"filter": "saring",
"folders": "pelipat",
"grid": "jaring",
"icons": "ikon",
"language": "Bahasa",
"lastModified": "Di modifikasi",
"name": "Nama",
"noMatch": "tidak cocok",
"parentDirectory": "Direktori induk",
"search": "cari",
"size": "Ukuran",
"tree": "Pohon",
"view": "Tampil"
}

View File

@@ -10,9 +10,13 @@
"folders": "cartelle",
"grid": "griglia",
"icons": "icone",
"language": "Linugua",
"lastModified": "Ultima modifica",
"name": "Nome",
"noMatch": "nessun risultato",
"parentDirectory": "Cartella Superiore",
"size": "Dimensione"
"search": "cerca",
"size": "Dimensione",
"tree": "Albero",
"view": "Vista"
}

View File

@@ -0,0 +1,22 @@
{
"lang": "português do Brasil",
"dateFormat": "DD-MM-YYYY HH:mm",
"details": "detalhes",
"download": "download",
"empty": "vazio",
"files": "arquivos",
"filter": "filtro",
"folders": "pastas",
"grid": "grade",
"icons": "ícones",
"language": "Idioma",
"lastModified": "Última modificação",
"name": "Nome",
"noMatch": "sem resultados",
"parentDirectory": "Diretório acima",
"search": "pesquisa",
"size": "Tamanho",
"tree": "Árvore",
"view": "Visualização"
}

View File

@@ -1,5 +1,5 @@
{
"lang": "português",
"lang": "português de Portugal",
"dateFormat": "DD-MM-YYYY HH:mm",
"details": "detalhes",
@@ -10,9 +10,13 @@
"folders": "pastas",
"grid": "grelha",
"icons": "ícones",
"language": "Idioma",
"lastModified": "última modificação",
"name": "Nome",
"noMatch": "sem resultados",
"parentDirectory": "diretório acima",
"size": "Tamanho"
"parentDirectory": "Diretório acima",
"search": "pesquisa",
"size": "Tamanho",
"tree": "Árvore",
"view": "Visualização"
}

View File

@@ -54,7 +54,7 @@
is given the view size is fixed and the selector buttons are hidden.
The user selected view size is also stored local in modern browsers
so that it will be persistent.
- theme: string, name of one of the folders in "_h5ai/images/themes", defaults to "default"
- theme: string, name of one of the folders in "_h5ai/public/images/themes", defaults to "default"
- unmanaged: array of strings, don't manage folders containing one of those files
- unmanagedInNewWindow: boolean, open unmanaged links in new window/tab
*/
@@ -108,9 +108,14 @@
Note the different filenames: "header" (only current) - "headers" (current and sub directories)!
The file's content will be placed inside a <div/> tag above/below the main content.
If a file's extension is ".md" instead of ".html" its content will be interpreted as markdown.
- stopSearchingAtRoot: boolean, only search for header and footer files until the web root
directory. if `false`, will search for header/footer up the entire directory structure,
even above the web root
*/
"custom": {
"enabled": true
"enabled": true,
"stopSearchingAtRoot": true
},
/*
@@ -343,7 +348,7 @@
},
/*
Show thumbnails for image files. Needs the "/_h5ai/cache" folder to be
Show thumbnails for image files. Needs the "/_h5ai/public/cache" folder to be
writable for the web Server.
- img: array of strings

View File

@@ -31,6 +31,7 @@ class Api {
$archive = new Archive($this->context);
set_time_limit(0);
session_write_close();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $as . '"');
header('Connection: close');

View File

@@ -3,6 +3,12 @@
class Context {
private static $DEFAULT_PASSHASH = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e';
private static $AS_ADMIN_SESSION_KEY = 'AS_ADMIN';
private static $L10N_ISO_CODES = array(
'af', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'fi', 'fr', 'he',
'hi', 'hr', 'hu', 'id', 'it', 'ja','ko', 'lv', 'nb', 'nl', 'pl',
'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk',
'zh-cn', 'zh-tw'
);
private $session;
private $request;
@@ -227,6 +233,10 @@ class Context {
$results = [];
foreach ($iso_codes as $iso_code) {
if (!in_array($iso_code, Context::$L10N_ISO_CODES)) {
continue;
}
$file = $this->setup->get('CONF_PATH') . '/l10n/' . $iso_code . '.json';
$results[$iso_code] = Json::load($file);
$results[$iso_code]['isoCode'] = $iso_code;

View File

@@ -122,12 +122,15 @@ class Setup {
if (sizeof($cmds) === 0 || $this->refresh) {
$cmds['command'] = Util::exec_0('command -v command');
$cmds['which'] = Util::exec_0('which which') || Util::exec_0('which which.exe');
$cmds['where'] = Util::exec_0('where where.exe');
$cmd = false;
if ($cmds['command']) {
$cmd = 'command -v';
} elseif ($cmds['which']) {
$cmd = 'which';
} elseif ($cmds['where']) {
$cmd = 'where';
}
foreach (['avconv', 'convert', 'du', 'ffmpeg', 'gm', 'tar', 'zip'] as $c) {

View File

@@ -135,12 +135,17 @@ class Archive {
}
private function add_hrefs($hrefs) {
if (!is_array($hrefs)) {
$hrefs = array($hrefs);
}
foreach ($hrefs as $href) {
if (trim($href) === '') {
continue;
}
$d = Util::normalize_path(dirname($href), true);
$href = Util::normalize_path($href, false);
$d = dirname($href);
$n = basename($href);
if ($this->context->is_managed_href($d) && !$this->context->is_hidden($n)) {

View File

@@ -54,6 +54,14 @@ class Custom {
if ($parent_path === $path) {
break;
}
// Stop once we reach the root
if (
$this->context->query_option('custom.stopSearchingAtRoot', true) &&
$path === $this->context->get_setup()->get('ROOT_PATH')
) {
break;
}
$path = $parent_path;
}

View File

@@ -6,6 +6,8 @@
position: absolute;
image-orientation: from-image;
max-width: 100%;
max-height: 100%;

View File

@@ -1,5 +1,3 @@
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
<g transform="translate(0,-8)">
<path d="m15.8 22.3h-0.8l-0.3-0.3c1-1.1 1.6-2.6 1.6-4.2 0-3.6-2.9-6.5-6.5-6.5-3.6 0-6.5 2.9-6.5 6.5 0 3.6 2.9 6.5 6.5 6.5 1.6 0 3.1-0.6 4.2-1.6l0.3 0.3v0.8l5 5 1.5-1.5-5-5zm-6 0c-2.5 0-4.5-2-4.5-4.5 0-2.5 2-4.5 4.5-4.5 2.5 0 4.5 2 4.5 4.5 0 2.5-2 4.5-4.5 4.5z" fill="#555"/>
</g>
</svg>
<path d="M20 4H4l6 9v5l4 2v-7z" fill="#555"/>
</svg>

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 172 B

View File

@@ -1,7 +1,7 @@
<?php
define('H5AI_VERSION', '{{VERSION}}');
define('MIN_PHP_VERSION', '5.5.0');
define('MIN_PHP_VERSION', '7.0.0');
if (!function_exists('version_compare') || version_compare(PHP_VERSION, MIN_PHP_VERSION, '<')) {
header('Content-type: text/plain;charset=utf-8');

View File

@@ -24,6 +24,7 @@ const updateGui = () => {
const addUnloadFn = el => {
el.unload = () => {
el.pause();
el.src = '';
el.load();
};

View File

@@ -28,6 +28,7 @@ const updateGui = () => {
const addUnloadFn = el => {
el.unload = () => {
el.pause();
el.src = '';
el.load();
};

View File

@@ -4,7 +4,7 @@ const XHR = global.window.XMLHttpRequest;
const request = data => {
return new Promise(resolve => {
const xhr = new XHR();
const onReadyStateChange = () => {
const on_ready_state_change = () => {
if (xhr.readyState === XHR.DONE) {
try {
resolve(JSON.parse(xhr.responseText));
@@ -15,7 +15,7 @@ const request = data => {
};
xhr.open('POST', '?', true);
xhr.onreadystatechange = onReadyStateChange;
xhr.onreadystatechange = on_ready_state_change;
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
xhr.send(JSON.stringify(data));
});

View File

@@ -3,7 +3,7 @@ const {each, filter, hasLength, is, isStr, map, isInstanceOf, toArray} = require
const win = global.window;
const doc = win.document;
const parseHtml = (() => {
const parse_html = (() => {
const create = name => doc.createElement(name);
const rules = [
[/^<t(head|body|foot)|^<c(ap|olg)/i, create('table')],
@@ -32,7 +32,7 @@ const parseHtml = (() => {
};
})();
const queryAll = (selector, context = doc) => {
const query_all = (selector, context = doc) => {
try {
return toArray(context.querySelectorAll(selector));
} catch (err) {
@@ -40,27 +40,27 @@ const queryAll = (selector, context = doc) => {
}
};
const isElement = x => isInstanceOf(x, win.Element);
const isDocument = x => isInstanceOf(x, win.Document);
const isWindow = x => is(x) && x.window === x && isDocument(x.document);
const isElDocWin = x => isElement(x) || isDocument(x) || isWindow(x);
const is_el = x => isInstanceOf(x, win.Element);
const is_doc = x => isInstanceOf(x, win.Document);
const is_win = x => is(x) && x.window === x && is_doc(x.document);
const is_el_doc_win = x => is_el(x) || is_doc(x) || is_win(x);
const addListener = (el, type, fn) => el.addEventListener(type, fn);
const removeListener = (el, type, fn) => el.removeEventListener(type, fn);
const add_listener = (el, type, fn) => el.addEventListener(type, fn);
const remove_listener = (el, type, fn) => el.removeEventListener(type, fn);
const readyPromise = new Promise(resolve => {
if (/^(i|c|loade)/.test(doc.readyState)) {
const ready_promise = new Promise(resolve => {
if ((/^(i|c|loade)/).test(doc.readyState)) {
resolve();
} else {
addListener(doc, 'DOMContentLoaded', () => resolve());
add_listener(doc, 'DOMContentLoaded', () => resolve());
}
});
const awaitReady = () => readyPromise;
const await_ready = () => ready_promise;
const loadPromise = new Promise(resolve => {
addListener(win, 'load', () => resolve());
const load_promise = new Promise(resolve => {
add_listener(win, 'load', () => resolve());
});
const awaitLoad = () => loadPromise;
const await_load = () => load_promise;
const dom = arg => {
if (isInstanceOf(arg, dom)) {
@@ -70,13 +70,13 @@ const dom = arg => {
let els;
if (isStr(arg)) {
arg = arg.trim();
els = arg[0] === '<' ? parseHtml(arg) : queryAll(arg);
} else if (isElDocWin(arg)) {
els = arg[0] === '<' ? parse_html(arg) : query_all(arg);
} else if (is_el_doc_win(arg)) {
els = [arg];
} else {
els = hasLength(arg) ? arg : [arg];
}
els = filter(els, isElDocWin);
els = filter(els, is_el_doc_win);
return Object.assign(Object.create(dom.prototype), els, {length: els.length});
};
@@ -94,15 +94,15 @@ dom.prototype = {
},
find(selector) {
return dom([].concat(...this.map(el => queryAll(selector, el))));
return dom([].concat(...this.map(el => query_all(selector, el))));
},
on(type, fn) {
return this.each(el => addListener(el, type, fn));
return this.each(el => add_listener(el, type, fn));
},
off(type, fn) {
return this.each(el => removeListener(el, type, fn));
return this.each(el => remove_listener(el, type, fn));
},
attr(key, value) {
@@ -271,7 +271,7 @@ dom.prototype = {
};
module.exports = {
awaitReady,
awaitLoad,
awaitReady: await_ready,
awaitLoad: await_load,
dom
};

View File

@@ -1,6 +1,6 @@
module.exports = Object.assign({},
require('./lo'),
require('./dom'),
require('./naturalCmp'),
require('./natural_cmp'),
require('./misc')
);

View File

@@ -1,10 +1,10 @@
const escapePattern = sequence => {
const esc_pattern = sequence => {
return sequence.replace(/[\-\[\]{}()*+?.,\\$\^|#\s]/g, '\\$&');
};
const parsePattern = (sequence, advanced) => {
const parse_pattern = (sequence, advanced) => {
if (!advanced) {
return escapePattern(sequence);
return esc_pattern(sequence);
}
if (sequence.substr(0, 3) === 're:') {
@@ -12,10 +12,10 @@ const parsePattern = (sequence, advanced) => {
}
return sequence.trim().split(/\s+/).map(part => {
return part.split('').map(char => escapePattern(char)).join('.*?');
return part.split('').map(char => esc_pattern(char)).join('.*?');
}).join('|');
};
module.exports = {
parsePattern
parsePattern: parse_pattern
};

View File

@@ -1,63 +0,0 @@
// Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license
// Author: Jim Palmer (based on chunking idea from Dave Koelle)
// Modified to make it work with h5ai
const reToken = /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi;
const reDate = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
const reHex = /^0x[0-9a-f]+$/i;
const reLeadingZero = /^0/;
/* eslint-disable complexity */
const naturalCmp = (a, b) => {
// convert all to strings strip whitespace
const x = String(a).trim();
const y = String(b).trim();
// chunk/tokenize
const xTokens = x.replace(reToken, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
const yTokens = y.replace(reToken, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
// first try and sort Hex codes or Dates
const xDate = parseInt(x.match(reHex), 16) || xTokens.length !== 1 && x.match(reDate) && Date.parse(x);
const yDate = parseInt(y.match(reHex), 16) || xDate && y.match(reDate) && Date.parse(y) || null;
if (yDate) {
if (xDate < yDate) {
return -1;
}
if (xDate > yDate) {
return 1;
}
}
// natural sorting through split numeric strings and default strings
for (let idx = 0, len = Math.max(xTokens.length, yTokens.length); idx < len; idx += 1) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
let xToken = !(xTokens[idx] || '').match(reLeadingZero) && parseFloat(xTokens[idx]) || xTokens[idx] || 0;
let yToken = !(yTokens[idx] || '').match(reLeadingZero) && parseFloat(yTokens[idx]) || yTokens[idx] || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(xToken) !== isNaN(yToken)) {
return isNaN(xToken) ? 1 : -1;
}
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
if (typeof xToken !== typeof yToken) {
xToken = String(xToken);
yToken = String(yToken);
}
if (xToken < yToken) {
return -1;
}
if (xToken > yToken) {
return 1;
}
}
return 0;
};
/* eslint-enable */
module.exports = {
naturalCmp
};

View File

@@ -0,0 +1,63 @@
// Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license
// Author: Jim Palmer (based on chunking idea from Dave Koelle)
// Modified to make it work with h5ai
const RE_TOKEN = /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi;
const RE_DATE = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
const RE_HEX = /^0x[0-9a-f]+$/i;
const RE_LEADING_ZERO = /^0/;
/* eslint-disable complexity */
const natural_cmp = (a, b) => {
// convert all to strings strip whitespace
const x = String(a).trim();
const y = String(b).trim();
// chunk/tokenize
const x_toks = x.replace(RE_TOKEN, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
const y_toks = y.replace(RE_TOKEN, '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
// first try and sort Hex codes or Dates
const x_date = parseInt(x.match(RE_HEX), 16) || x_toks.length !== 1 && x.match(RE_DATE) && Date.parse(x);
const y_date = parseInt(y.match(RE_HEX), 16) || x_date && y.match(RE_DATE) && Date.parse(y) || null;
if (y_date) {
if (x_date < y_date) {
return -1;
}
if (x_date > y_date) {
return 1;
}
}
// natural sorting through split numeric strings and default strings
for (let idx = 0, len = Math.max(x_toks.length, y_toks.length); idx < len; idx += 1) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
let x_tok = !(x_toks[idx] || '').match(RE_LEADING_ZERO) && parseFloat(x_toks[idx]) || x_toks[idx] || 0;
let y_tok = !(y_toks[idx] || '').match(RE_LEADING_ZERO) && parseFloat(y_toks[idx]) || y_toks[idx] || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(x_tok) !== isNaN(y_tok)) {
return isNaN(x_tok) ? 1 : -1;
}
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
if (typeof x_tok !== typeof y_tok) {
x_tok = String(x_tok);
y_tok = String(y_tok);
}
if (x_tok < y_tok) {
return -1;
}
if (x_tok > y_tok) {
return 1;
}
}
return 0;
};
/* eslint-enable */
module.exports = {
naturalCmp: natural_cmp
};

View File

@@ -1,7 +1,7 @@
const {dom} = require('../util');
const rootSelector = 'body';
const topbarTpl =
const SEL_ROOT = 'body';
const TPL_TOPBAR =
`<div id="topbar">
<div id="toolbar"></div>
<div id="flowbar"></div>
@@ -10,17 +10,17 @@ const topbarTpl =
<div>by h5ai</div>
</a>
</div>`;
const mainrowTpl =
const TPL_MAINROW =
`<div id="mainrow">
<div id="content"></div>
</div>`;
const init = () => {
const $root = dom(rootSelector)
const $root = dom(SEL_ROOT)
.attr('id', 'root')
.clr()
.app(topbarTpl)
.app(mainrowTpl);
.app(TPL_TOPBAR)
.app(TPL_MAINROW);
return {
$root,

View File

@@ -4,25 +4,25 @@
throw new Error('no-window');
}
var noBrowser = 'no-browser';
var docEl = win.document.documentElement;
docEl.className = '';
var no_browser = 'no-browser';
var doc_el = win.document.documentElement;
doc_el.className = '';
function assert(msg, expr) {
if (!expr) {
docEl.className = noBrowser;
throw new Error(noBrowser + ': ' + msg);
doc_el.className = no_browser;
throw new Error(no_browser + ': ' + msg);
}
}
function isFn(x) {
function is_fn(x) {
return typeof x === 'function';
}
assert('console', win.console && isFn(win.console.log));
assert('assign', win.Object && isFn(win.Object.assign));
assert('promise', isFn(win.Promise));
// assert('xhr', isFn(win.XMLHttpRequest)); // is object in safari
assert('console', win.console && is_fn(win.console.log));
assert('assign', win.Object && is_fn(win.Object.assign));
assert('promise', is_fn(win.Promise));
// assert('xhr', is_fn(win.XMLHttpRequest)); // is object in safari
assert('xhr', win.XMLHttpRequest);
}(this));
/* eslint-enable */

View File

@@ -1,9 +1,10 @@
if (!global.window) {
global.window = require('jsdom').jsdom().defaultView;
const JSDOM = require('jsdom').JSDOM;
global.window = new JSDOM('').window;
}
const {test} = require('scar');
const {pinHtml} = require('./util/pin');
const {pin_html} = require('./util/pin');
require('./tests/premisses');
require('./tests/unit/core/event');
@@ -11,6 +12,6 @@ require('./tests/unit/core/format');
require('./tests/unit/util/naturalCmp');
require('./tests/unit/util/parsePatten');
pinHtml();
pin_html();
test.cli({sync: true});

View File

@@ -1,5 +1,6 @@
const {test, assert} = require('scar');
const event = require('../../../../src/_h5ai/public/js/lib/core/event');
const reqlib = require('../../../util/reqlib');
const event = reqlib('core/event');
test('core.event', () => {
assert.equal(typeof event, 'object', 'is object');

View File

@@ -1,5 +1,6 @@
const {test, assert} = require('scar');
const format = require('../../../../src/_h5ai/public/js/lib/core/format');
const reqlib = require('../../../util/reqlib');
const format = reqlib('core/format');
test('core.format', () => {
assert.equal(typeof format, 'object');

View File

@@ -1,5 +1,6 @@
const {test, assert, insp} = require('scar');
const {naturalCmp} = require('../../../../src/_h5ai/public/js/lib/util');
const reqlib = require('../../../util/reqlib');
const {naturalCmp} = reqlib('util');
test('util.naturalCmp()', () => {
assert.equal(typeof naturalCmp, 'function', 'is function');

View File

@@ -1,5 +1,6 @@
const {test, assert, insp} = require('scar');
const {parsePattern} = require('../../../../src/_h5ai/public/js/lib/util');
const reqlib = require('../../../util/reqlib');
const {parsePattern} = reqlib('util');
test('util.parsePattern()', () => {
assert.equal(typeof parsePattern, 'function', 'is function');

View File

@@ -16,30 +16,30 @@ const attr = (el, name, value) => {
return el.setAttribute(name, value);
};
const rootChildren = () => {
const root_children = () => {
return [
...doc.querySelector('head').childNodes,
...doc.querySelector('body').childNodes
];
};
const pinHtml = () => {
const pin_html = () => {
pinned.title = doc.title;
pinned.htmlId = attr('html', 'id');
pinned.htmlClasses = attr('html', 'class');
pinned.bodyId = attr('body', 'id');
pinned.bodyClasses = attr('body', 'class');
pinned.els = rootChildren();
pinned.els = root_children();
// console.log('pinned', pinned);
};
const restoreHtml = () => {
const restore_html = () => {
doc.title = pinned.title;
attr('html', 'id', pinned.htmlId);
attr('html', 'class', pinned.htmlClasses);
attr('body', 'id', pinned.bodyId);
attr('body', 'class', pinned.bodyClasses);
rootChildren().forEach(el => {
root_children().forEach(el => {
if (pinned.els.indexOf(el) < 0) {
el.remove();
}
@@ -48,6 +48,6 @@ const restoreHtml = () => {
};
module.exports = {
pinHtml,
restoreHtml
pin_html,
restore_html
};

3
test/util/reqlib.js Normal file
View File

@@ -0,0 +1,3 @@
const reqlib = x => require(`../../src/_h5ai/public/js/lib/${x}`);
module.exports = reqlib;