Compare commits

...

54 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
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 4991 additions and 4385 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/
/local/
/node_modules/
/**/vendor/

View File

@ -5,6 +5,9 @@
es6: true
node: true
parserOptions:
ecmaVersion: 2020
rules:
array-bracket-spacing: [2, never]
arrow-parens: [2, as-needed]

2
.gitignore vendored
View File

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

View File

@ -1,6 +1,27 @@
# 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

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

58
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);
});
@ -64,45 +65,40 @@ ghu.task('clean', 'delete build folder', () => {
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);
@ -111,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}))
@ -145,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 => {

8890
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.2",
"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": {
@ -14,22 +13,19 @@
"lint": "eslint .",
"test": "node test",
"build": "node ghu release",
"precommit": "npm run lint && npm run test"
"precommit": "npm run -s lint && npm run -s test"
},
"devDependencies": {
"babel-loader": "7.1.1",
"babel-preset-env": "1.7.0",
"eslint": "5.15.3",
"ghu": "0.13.0",
"jsdom": "14.0.0",
"kjua": "0.2.0",
"lolight": "1.0.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.2.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 => {
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

@ -4,7 +4,7 @@ if (!global.window) {
}
const {test} = require('scar');
const {pinHtml} = require('./util/pin');
const {pin_html} = require('./util/pin');
require('./tests/premisses');
require('./tests/unit/core/event');
@ -12,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;