mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
31f336d690 | |||
0e4365795b | |||
96769f52cf | |||
6660ee77e1 | |||
7f4bebe404 | |||
6a7792239e | |||
f248a6ab4e | |||
f12ce7a7c5 | |||
f8514bfdc7 | |||
01a256bbbc | |||
abae786748 | |||
d3b794c9da | |||
9d15bbfcce | |||
525f05b570 | |||
54d95c11c5 | |||
0a58e12827 | |||
818d9ac3c8 | |||
9eaab55dab | |||
36d2842b6e | |||
87681f8c0d | |||
f49a8fabab | |||
2bba57e8de | |||
f7df1cb536 | |||
2ab636e569 | |||
21ddb8a90b | |||
1ce16c80ec | |||
0eabc4ed41 | |||
965f772515 | |||
770dba8a41 | |||
0ea40ce6d0 | |||
e2bc5d3415 | |||
5df5a78df5 | |||
13d01dee27 | |||
9fd34f06aa | |||
92631df9a4 | |||
e623513e3d | |||
7e2e90ad3f | |||
7645c012a5 | |||
ecb3901bbe | |||
ee09a09833 | |||
13d77dd14a | |||
b8f90dbd95 | |||
5305bb68a4 | |||
5aa75a4150 | |||
b3c5212c84 | |||
96195f0efc | |||
d769eaed61 | |||
2df43201ba | |||
40e869b848 | |||
b7456463eb | |||
d49f76c9f1 | |||
645573a272 | |||
f5aed19bf3 | |||
28bba097c3 | |||
105a564c7d | |||
15739ac7cc | |||
0edbd9bf40 | |||
a31d5849fc | |||
180252cc64 | |||
48bb4fc803 | |||
58768acc1c | |||
42f37d8fee | |||
ec5ff5f230 | |||
7350b89a33 | |||
91f33c9e08 | |||
6b933132a5 | |||
8c87a93774 | |||
7ca582186b | |||
465a61ff4a | |||
1ad5d7475c | |||
ebd25cd9a9 | |||
b3def7fc21 | |||
5a537ba168 | |||
b4db90bcdc | |||
c6a17d33af | |||
37eabe1556 | |||
1969f0e3fa | |||
c0a474ed82 | |||
6380043a9a | |||
b75df289e9 | |||
8db72633f6 | |||
44ef66f65c | |||
7511430b2a | |||
362087ee63 | |||
5ad854e38a | |||
5882998c20 | |||
579e59e7df | |||
64ae9a7c74 | |||
6b6acb0bbf | |||
11648e055c | |||
3c83f711c9 | |||
bd7dd9a2ad | |||
02c8353175 | |||
77e51c2e10 | |||
fd448bac87 | |||
027b98ce76 | |||
edee487ff9 | |||
2702518e31 | |||
79df9b56d3 | |||
3b1544eff3 | |||
c74edbee51 | |||
8407a3f70e | |||
9c1db78b69 | |||
9b2238d423 | |||
b5d6e4837d | |||
d20fe3d95a | |||
a7a2f31dc2 | |||
b26fd88d6f | |||
a69f8563e8 | |||
24d8bf5295 | |||
5412ac14b9 | |||
38bfbfb8f3 | |||
4ba855871f | |||
0727433a9e | |||
627a8db5f3 | |||
740cc85775 | |||
48004f1117 | |||
4126de8e25 | |||
c569504ce7 | |||
8d119d2b62 | |||
f8851bf26d | |||
19e7fa94f7 | |||
06180f5b50 | |||
aa228d5125 | |||
5f4260d0a7 | |||
e7e50cfb3a | |||
09d8e5ae1c | |||
fce9c3483a | |||
a3157a48ec | |||
c35ed15946 | |||
f75b4505a1 | |||
d98474cc6a | |||
2e06422b62 | |||
0aad36228a | |||
7c77c7a87b | |||
0cf29a657a | |||
fdb029eb5c | |||
eb3b02c28d | |||
65bc6705d3 | |||
5f0706c0b4 | |||
e7ea60f293 | |||
b416868aa7 | |||
90406b1278 | |||
04a16a2a36 | |||
72e9400e1d | |||
d425b0df2e | |||
4cad09b85e | |||
a59a57fe70 | |||
0c4d984157 | |||
ea5262fa2b | |||
2ab4da11fc | |||
9090ac6fb9 | |||
eb77b6811a | |||
0945ed64ee | |||
4d9fc51819 | |||
1897297127 | |||
970b9bf06d | |||
e5f2e293f0 |
201
INSTALL.md
201
INSTALL.md
@ -1,28 +1,40 @@
|
|||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
In order to run szurubooru, you need to have installed following software:
|
In order to run `szurubooru`, you need to have installed following software:
|
||||||
|
|
||||||
- Apache2
|
- `Apache` 2.4+
|
||||||
- mod_rewrite
|
- `mod_rewrite`
|
||||||
- mod_mime_magic (recommended)
|
- `mod_mime_magic` (recommended)
|
||||||
- PHP 5.6.0
|
- `PHP` 5.6.0+
|
||||||
- pdo_sqlite
|
- `pdo_mysql`
|
||||||
- imagick or gd
|
- `imagick` or `gd`
|
||||||
- composer (PHP package manager)
|
- `MySQL` or `MariaDB`
|
||||||
- npm (node.js package manager)
|
- `composer` (`PHP` package manager)
|
||||||
|
- `npm` (`node.js` package manager)
|
||||||
|
|
||||||
Optional modules:
|
Optional software:
|
||||||
|
|
||||||
- dump-gnash or swfrender for flash thumbnails
|
- `dump-gnash`, `swfrender` or `ffmpeg` for Flash thumbnails
|
||||||
- ffmpegthumbnailer or ffmpeg for video thumbnails
|
- `ffmpegthumbnailer` or `ffmpeg` for video thumbnails
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Cloning the repository
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Download the repository somewhere you will it run from, or better yet, clone it
|
||||||
|
with `git`:
|
||||||
|
|
||||||
|
cd /srv/www/
|
||||||
|
git clone https://github.com/rr-/szurubooru booru-test
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Fetching dependencies
|
Fetching dependencies
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
To fetch dependencies that szurubooru needs in order to run, enter following
|
To fetch dependencies that `szurubooru` needs in order to run, enter following
|
||||||
commands in the terminal:
|
commands in the terminal:
|
||||||
|
|
||||||
composer update
|
composer update
|
||||||
@ -30,11 +42,11 @@ commands in the terminal:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Running grunt tasks
|
Running `grunt` tasks
|
||||||
-------------------
|
---------------------
|
||||||
|
|
||||||
Szurubooru uses grunt to run tasks like database ugprades and tests. In order
|
`szurubooru` uses `grunt` to run tasks like database upgrades and tests. In
|
||||||
to use grunt from the terminal, you can use:
|
order to use `grunt` from the terminal, you can use:
|
||||||
|
|
||||||
node_modules/grunt-cli/bin/grunt [TASK]
|
node_modules/grunt-cli/bin/grunt [TASK]
|
||||||
|
|
||||||
@ -43,25 +55,25 @@ administrator:
|
|||||||
|
|
||||||
npm install -g grunt-cli
|
npm install -g grunt-cli
|
||||||
|
|
||||||
This will add "grunt" to your PATH, making things much more human-friendly.
|
This will add `grunt` to your PATH, making things much more human-friendly.
|
||||||
|
|
||||||
grunt [TASK]
|
grunt [TASK]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Enabling required modules in PHP
|
Enabling required modules in `PHP`
|
||||||
--------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Enable required modules in php.ini (or other configuration file, depending on
|
Enable required modules in `php.ini` (or other configuration file, depending on
|
||||||
your setup):
|
your setup):
|
||||||
|
|
||||||
;Linux
|
;Linux
|
||||||
extension=pdo_sqlite.so
|
extension=pdo_mysql.so
|
||||||
|
|
||||||
;Windows
|
;Windows
|
||||||
extension=php_pdo_sqlite.dll
|
extension=php_pdo_mysql.dll
|
||||||
|
|
||||||
In order to draw thumbnails, szurubooru needs either imagick or gd2:
|
In order to draw thumbnails, `szurubooru` needs either `Imagick` or `gd2`:
|
||||||
|
|
||||||
;Linux
|
;Linux
|
||||||
extension=imagick.so
|
extension=imagick.so
|
||||||
@ -73,25 +85,16 @@ In order to draw thumbnails, szurubooru needs either imagick or gd2:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Upgrading the database
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Every time database schema changes, you should upgrade the database by running
|
|
||||||
following grunt task in the terminal:
|
|
||||||
|
|
||||||
grunt upgrade
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Creating virtual server in Apache
|
Creating virtual server in Apache
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
In order to make Szurubooru visible in your browser, you need to create a
|
In order to make `szurubooru` visible in your browser, you need to create a
|
||||||
virtual server. This guide focuses on Apache2 web server. Note that although it
|
virtual server. This guide focuses on `Apache` web server. Note that although
|
||||||
should be also possible to host szurubooru with nginx, you'd need to manually
|
it should be also possible to host `szurubooru` with `nginx`, you'd need to
|
||||||
translate the rules inside public_html/.htaccess into nginx configuration.
|
manually translate the rules inside `public_html/.htaccess` into `nginx`
|
||||||
|
configuration.
|
||||||
|
|
||||||
Creating virtual server for Apache comes with no surprises, basically all you
|
Creating virtual server for `Apache` comes with no surprises, basically all you
|
||||||
need is the most basic configuration:
|
need is the most basic configuration:
|
||||||
|
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
@ -99,30 +102,34 @@ need is the most basic configuration:
|
|||||||
DocumentRoot /path/to/szurubooru/public_html/
|
DocumentRoot /path/to/szurubooru/public_html/
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
ServerName specifies the domain under which szurubooru will be hosted.
|
`ServerName` specifies the domain under which `szurubooru` will be hosted.
|
||||||
DocumentRoot should point to the public_html/ directory.
|
`DocumentRoot` should point to the `public_html/` directory.
|
||||||
|
|
||||||
|
Some environments / configurations require extra steps to make things work - in
|
||||||
|
case you experience any problems, please consult the troubleshooting section
|
||||||
|
later in this file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Enabling required modules in Apache
|
Enabling required modules in Apache
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
Enable required modules in httpd.conf (or other configuration file, depending
|
Enable required modules in `httpd.conf` (or other configuration file, depending
|
||||||
on your setup):
|
on your setup):
|
||||||
|
|
||||||
LoadModule rewrite_module mod_rewrite.so ;Linux
|
LoadModule rewrite_module mod_rewrite.so ;Linux
|
||||||
LoadModule rewrite_module modules/mod_rewrite.so ;Windows
|
LoadModule rewrite_module modules/mod_rewrite.so ;Windows
|
||||||
|
|
||||||
Enable PHP support:
|
Enable `PHP` support:
|
||||||
|
|
||||||
LoadModule php5_module /usr/lib/apache2/modules/libphp5.so ;Linux
|
LoadModule php5_module /usr/lib/apache2/modules/libphp5.so ;Linux
|
||||||
LoadModule php5_module /path/to/php/php5apache2_4.dll ;Windows
|
LoadModule php5_module /path/to/php/php5apache2_4.dll ;Windows
|
||||||
AddType application/x-httpd-php .php
|
AddType application/x-httpd-php .php
|
||||||
PHPIniDir /path/to/php/
|
PHPIniDir /path/to/php/
|
||||||
|
|
||||||
Enable MIME auto-detection (not required, but recommended - szurubooru doesn't
|
Enable MIME auto-detection (not required, but recommended - `szurubooru`
|
||||||
use file extensions, and reporting correct Content-Type to browser is always a
|
doesn't use file extensions, and reporting correct `Content-Type` to browser is
|
||||||
good thing):
|
always a good thing):
|
||||||
|
|
||||||
;Linux
|
;Linux
|
||||||
LoadModule mime_magic_module mod_mime_magic.so
|
LoadModule mime_magic_module mod_mime_magic.so
|
||||||
@ -137,21 +144,40 @@ good thing):
|
|||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
|
|
||||||
Creating administrator account
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
By now, you should be able to view szurubooru in the browser. Registering
|
|
||||||
administrator account is simple - the first user to create an account
|
|
||||||
automatically becomes administrator and doesn't need e-mail activation.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Overwriting configuration
|
Overwriting configuration
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Everything that can be configured is stored in data/config.ini file. In order
|
Everything that can be configured is stored in `data/config.ini` file. In order
|
||||||
to make changes there, copy the file and name it local.ini. Make sure you don't
|
to make changes there, copy the file and name it `local.ini` and place it in
|
||||||
edit the file itself, especially if you want to contribute.
|
`data/` directory as well. Make sure you don't edit the `data/config.ini` file
|
||||||
|
itself, especially if you want to contribute.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Setting up the database
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Before running `szurubooru` for first time, you need to set up the database.
|
||||||
|
`szurubooru` uses MySQL, so let's fire `mysql` and type following:
|
||||||
|
|
||||||
|
create user 'maria' identified by 'arkadia';
|
||||||
|
create database booru_test;
|
||||||
|
grant all privileges on *.* to 'maria'@'%' with grant option;
|
||||||
|
|
||||||
|
Then you need to provide the above credentials in the configuration files as
|
||||||
|
described in the previous section. Example `local.ini` file:
|
||||||
|
|
||||||
|
[database]
|
||||||
|
dsn = mysql:dbname=booru_test
|
||||||
|
user = maria
|
||||||
|
password = arkadia
|
||||||
|
|
||||||
|
After that, upgrade the database using following command:
|
||||||
|
|
||||||
|
grunt upgrade
|
||||||
|
|
||||||
|
This should be also executed every time database schema changes.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -165,21 +191,36 @@ smallest possible packages, run following command:
|
|||||||
|
|
||||||
grunt build
|
grunt build
|
||||||
|
|
||||||
This should create public_html/app.min.js, public_html/app.min.css and
|
This should create `public_html/app.min.js`, `public_html/app.min.css` and
|
||||||
public_html/app.min.html. .htaccess is configured so that if these files exist,
|
`public_html/app.min.html`. `.htaccess` is configured so that if these files
|
||||||
it will load them instead of development environment. To delete these
|
exist, it will load them instead of development environment. To delete these
|
||||||
conveniently, you can run:
|
conveniently, you can run:
|
||||||
|
|
||||||
grunt clean
|
grunt clean
|
||||||
|
|
||||||
|
If, for any reason, you do not wish to minify the resources, you should at
|
||||||
|
least copy the dependencies fetched before to the `public_html/` directory with
|
||||||
|
following:
|
||||||
|
|
||||||
|
grunt copy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Creating administrator account
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
By now, you should be able to view `szurubooru` in the browser. Registering
|
||||||
|
administrator account is simple - the first user to create an account
|
||||||
|
automatically becomes administrator and doesn't need e-mail activation.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
1. Problems with Apache virtual servers
|
1. Problems with `Apache` virtual servers
|
||||||
|
|
||||||
After reloading Apache configuration, if you find yourself unable to
|
After reloading `Apache` configuration, if you find yourself unable to
|
||||||
connect to the server, make sure that connections are open, for example,
|
connect to the server, make sure that connections are open, for example,
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
@ -187,40 +228,28 @@ Troubleshooting
|
|||||||
Require all granted
|
Require all granted
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
(Note that Apache versions prior to 2.4 used "Allow from all" directive.)
|
(Note that `Apache` versions prior to 2.4 used `Allow from all` directive.)
|
||||||
|
|
||||||
Additionally, in order to access virtual host from your machine, make sure
|
Additionally, in order to access the virtual host from your machine, make
|
||||||
the domain name "example.com" supplied in <VirtualHost/> section is
|
sure the domain name `example.com` supplied in `<VirtualHost/>` section is
|
||||||
included in your hosts file (usually /etc/hosts on Linux and
|
included in your `hosts` file (usually `/etc/hosts` on Linux and
|
||||||
C:/windows/system32/drivers/etc/hosts in Windows).
|
`C:/windows/system32/drivers/etc/hosts` on Windows).
|
||||||
|
|
||||||
If the site doesn't work for you, make sure Apache can parse .htaccess
|
If the site doesn't work for you, make sure `Apache` can parse `.htaccess`
|
||||||
files. If it can't, you need to set AllowOverride option to "yes", for
|
files. If it can't, you need to set `AllowOverride` option to `yes`, for
|
||||||
example by putting following snippet inside <VirtualHost/> section:
|
example by putting following snippet inside the `<VirtualHost/>` section:
|
||||||
|
|
||||||
<Directory /path/to/szurubooru/public_html/>
|
<Directory /path/to/szurubooru/public_html/>
|
||||||
AllowOverride All
|
AllowOverride All
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
2. Problems with PHP modules or registration
|
2. Problems with `PHP` modules or registration
|
||||||
|
|
||||||
Make sure your php.ini path is correct. Make sure all the modules are
|
Make sure your `php.ini` path is correct. Make sure all the modules are
|
||||||
actually loaded by inspecting phpinfo - create small file containing:
|
actually loaded by inspecting results of `phpinfo()` call - create small
|
||||||
|
file containing:
|
||||||
|
|
||||||
<?php phpinfo(); ?>
|
<?php phpinfo(); ?>
|
||||||
|
|
||||||
Then, run it in your browser and inspect the output, looking for missing
|
Then, run it in your browser and inspect the output, looking for missing
|
||||||
modules that were supposed to be loaded.
|
modules that were supposed to be loaded.
|
||||||
|
|
||||||
3. "Attempt to write to read-only database"
|
|
||||||
|
|
||||||
Make sure Apache has permission to access the database file AND directory
|
|
||||||
it's stored in. (SQLite writes temporary journal files to the parent
|
|
||||||
database directory). If you're the only user of the system, you can run
|
|
||||||
these commands without worrying too much:
|
|
||||||
|
|
||||||
chmod 0777 data/
|
|
||||||
chmod 0777 data/db.sqlite
|
|
||||||
|
|
||||||
Otherwise, if you're feeling fancy, you can experiment with setfacl on
|
|
||||||
Linux or group policies on Windows.
|
|
||||||
|
19
README.md
19
README.md
@ -5,19 +5,21 @@ szurubooru
|
|||||||
|
|
||||||
## What is it?
|
## What is it?
|
||||||
|
|
||||||
Szurubooru is a Danbooru-style board, a gallery where users can upload, browse,
|
`szurubooru` is a Danbooru-style board, a gallery where users can upload,
|
||||||
tag and comment images, video clips and flash animations.
|
browse, tag and comment images, video clips and flash animations.
|
||||||
|
|
||||||
Its name have its roots in Polish language and has onomatopoeic meaning of
|
Its name have its roots in Polish language and has onomatopoeic meaning of
|
||||||
scraping or scrubbing. It is pronounced *"shoorubooru"* [ˌʃuruˈburu].
|
scraping or scrubbing. It is pronounced *"shoorubooru"* [ˌʃuruˈburu].
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
Please see the file named [`LICENSE`](https://github.com/rr-/szurubooru/blob/master/LICENSE).
|
Please see the file named
|
||||||
|
[`LICENSE`](https://github.com/rr-/szurubooru/blob/master/LICENSE).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Please see the file named [`INSTALL.md`](https://github.com/rr-/szurubooru/blob/master/INSTALL.md).
|
Please see the file named
|
||||||
|
[`INSTALL.md`](https://github.com/rr-/szurubooru/blob/master/INSTALL.md).
|
||||||
|
|
||||||
## Bugs and feature requests
|
## Bugs and feature requests
|
||||||
|
|
||||||
@ -29,8 +31,8 @@ please do following:
|
|||||||
to your problem, comment on that issue instead of opening a new one.
|
to your problem, comment on that issue instead of opening a new one.
|
||||||
2. If you found an issue and the issue is closed, feel free to reopen it.
|
2. If you found an issue and the issue is closed, feel free to reopen it.
|
||||||
3. If you're reporting a bug, create an isolated and reproducible scenario.
|
3. If you're reporting a bug, create an isolated and reproducible scenario.
|
||||||
4. If you're filing a feature request, provide examples - what might be obvious
|
4. If you're filing a feature request, provide examples - what might be
|
||||||
to you, might not be so obvious to the developers.
|
obvious to you, might not be so obvious to the developers.
|
||||||
|
|
||||||
## Contributing the code
|
## Contributing the code
|
||||||
|
|
||||||
@ -40,13 +42,14 @@ Here are some guidelines on how to contribute:
|
|||||||
- Respect coding standards - be consistent with existing code base.
|
- Respect coding standards - be consistent with existing code base.
|
||||||
- Watch your whitespace - don't leave any characters at the end of the lines.
|
- Watch your whitespace - don't leave any characters at the end of the lines.
|
||||||
- Always run tests before pushing.
|
- Always run tests before pushing.
|
||||||
- Before starting, see [`INSTALL.md`](https://github.com/rr-/szurubooru/blob/master/INSTALL.md).
|
- Before starting, see
|
||||||
|
[`INSTALL.md`](https://github.com/rr-/szurubooru/blob/master/INSTALL.md).
|
||||||
- Use `grunt` to do automatic tasks like minifying Javascript files or running
|
- Use `grunt` to do automatic tasks like minifying Javascript files or running
|
||||||
tests. Run `grunt --help` to see full list of available tasks.
|
tests. Run `grunt --help` to see full list of available tasks.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
Szurubooru from version 0.9+ uses REST API. Currently there is no formal
|
`szurubooru` from version 0.9+ uses REST API. Currently there is no formal
|
||||||
documentation; source code behind REST layer lies in `src/Controllers/`
|
documentation; source code behind REST layer lies in `src/Controllers/`
|
||||||
directory. In order to use the API, bear in mind that you need to:
|
directory. In order to use the API, bear in mind that you need to:
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"mnapoli/php-di": "~4.4"
|
"mnapoli/php-di": "~4.4",
|
||||||
|
"phpmailer/phpmailer": "~5.2"
|
||||||
},
|
},
|
||||||
|
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
@ -3,8 +3,12 @@ serviceName = szurubooru
|
|||||||
serviceBaseUrl = http://localhost/
|
serviceBaseUrl = http://localhost/
|
||||||
|
|
||||||
[mail]
|
[mail]
|
||||||
botName = szurubooru bot
|
smtpHost = localhost
|
||||||
botAddress = noreply@localhost
|
smtpPort = 25
|
||||||
|
smtpUserName = bot
|
||||||
|
smtpUserPass = groovy123
|
||||||
|
smtpFrom = noreply@szurubooru
|
||||||
|
smtpFromName = szurubooru bot
|
||||||
passwordResetSubject = szurubooru - password reset
|
passwordResetSubject = szurubooru - password reset
|
||||||
passwordResetBodyPath = mail/password-reset.txt
|
passwordResetBodyPath = mail/password-reset.txt
|
||||||
activationSubject = szurubooru - account activation
|
activationSubject = szurubooru - account activation
|
||||||
@ -19,7 +23,7 @@ maxCustomThumbnailSize = 1048576 ;1mb
|
|||||||
|
|
||||||
[database.tests]
|
[database.tests]
|
||||||
dsn = mysql:host=localhost
|
dsn = mysql:host=localhost
|
||||||
user = szuru_test
|
user = szuru-test
|
||||||
password = cat
|
password = cat
|
||||||
|
|
||||||
[security]
|
[security]
|
||||||
@ -27,12 +31,13 @@ secret = change
|
|||||||
minPasswordLength = 5
|
minPasswordLength = 5
|
||||||
needEmailActivationToRegister = 1
|
needEmailActivationToRegister = 1
|
||||||
defaultAccessRank = restrictedUser
|
defaultAccessRank = restrictedUser
|
||||||
|
forceHttpInPermalinks = 0
|
||||||
|
|
||||||
[security.privileges]
|
[security.privileges]
|
||||||
register = anonymous
|
register = anonymous
|
||||||
listUsers = regularUser, powerUser, moderator, administrator
|
listUsers = regularUser, powerUser, moderator, administrator
|
||||||
viewUsers = regularUser, powerUser, moderator, administrator
|
viewUsers = regularUser, powerUser, moderator, administrator
|
||||||
deleteOwnAccount = regularUser, powerUser, moderator, administrator
|
deleteOwnAccount = restrictedUser, regularUser, powerUser, moderator, administrator
|
||||||
deleteAllAccounts = administrator
|
deleteAllAccounts = administrator
|
||||||
changeOwnName = regularUser, powerUser, moderator, administrator
|
changeOwnName = regularUser, powerUser, moderator, administrator
|
||||||
changeOwnAvatarStyle = regularUser, powerUser, moderator, administrator
|
changeOwnAvatarStyle = regularUser, powerUser, moderator, administrator
|
||||||
@ -94,12 +99,13 @@ usersPerPage = 20
|
|||||||
postsPerPage = 40
|
postsPerPage = 40
|
||||||
|
|
||||||
[tags]
|
[tags]
|
||||||
categories[] = meta
|
categories[] = 'meta, meta, #aaa'
|
||||||
categories[] = artist
|
categories[] = 'artist, artist, #a00'
|
||||||
categories[] = character
|
categories[] = 'character, character, #0a0'
|
||||||
categories[] = copyright
|
categories[] = 'copyright, copyright, #a0a'
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
thumbnailCropStyle = outside
|
thumbnailCropStyle = outside
|
||||||
customFaviconUrl = /favicon.png
|
customFaviconUrl = /favicon.png
|
||||||
dumpSqlIntoQueries = 0
|
dumpSqlIntoQueries = 0
|
||||||
|
imageExtension = imagick
|
||||||
|
@ -81,12 +81,13 @@ module.exports = function(grunt) {
|
|||||||
files: [
|
files: [
|
||||||
{ src: 'node_modules/jquery/dist/jquery.min.js', dest: 'public_html/lib/jquery.min.js' },
|
{ src: 'node_modules/jquery/dist/jquery.min.js', dest: 'public_html/lib/jquery.min.js' },
|
||||||
{ src: 'node_modules/jquery.cookie/jquery.cookie.js', dest: 'public_html/lib/jquery.cookie.js' },
|
{ src: 'node_modules/jquery.cookie/jquery.cookie.js', dest: 'public_html/lib/jquery.cookie.js' },
|
||||||
{ src: 'node_modules/Mousetrap/mousetrap.min.js', dest: 'public_html/lib/mousetrap.min.js' },
|
{ src: 'node_modules/mousetrap/mousetrap.min.js', dest: 'public_html/lib/mousetrap.min.js' },
|
||||||
{ src: 'node_modules/pathjs/path.js', dest: 'public_html/lib/path.js' },
|
{ src: 'node_modules/pathjs/path.js', dest: 'public_html/lib/path.js' },
|
||||||
{ src: 'node_modules/underscore/underscore-min.js', dest: 'public_html/lib/underscore.min.js' },
|
{ src: 'node_modules/underscore/underscore-min.js', dest: 'public_html/lib/underscore.min.js' },
|
||||||
{ src: 'node_modules/marked/lib/marked.js', dest: 'public_html/lib/marked.js' },
|
{ src: 'node_modules/marked/lib/marked.js', dest: 'public_html/lib/marked.js' },
|
||||||
{ src: 'node_modules/nprogress/nprogress.js', dest: 'public_html/lib/nprogress.js' },
|
{ src: 'node_modules/nprogress/nprogress.js', dest: 'public_html/lib/nprogress.js' },
|
||||||
{ src: 'node_modules/nprogress/nprogress.css', dest: 'public_html/lib/nprogress.css' },
|
{ src: 'node_modules/nprogress/nprogress.css', dest: 'public_html/lib/nprogress.css' },
|
||||||
|
{ cwd: 'node_modules', src: 'font-awesome/**/*', dest: 'public_html/lib/', expand: true },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -136,7 +137,7 @@ module.exports = function(grunt) {
|
|||||||
templates: readTemplates(grunt),
|
templates: readTemplates(grunt),
|
||||||
timestamp: grunt.template.today('isoDateTime'),
|
timestamp: grunt.template.today('isoDateTime'),
|
||||||
maxPostSize: config.database.maxPostSize,
|
maxPostSize: config.database.maxPostSize,
|
||||||
tagCategories: config.tags.categories,
|
tagCategories: config.tags.categories.map(function(s) { return s.split(/,\s*/); }),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dist: {
|
dist: {
|
||||||
@ -162,7 +163,7 @@ module.exports = function(grunt) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('update', 'Upgrade database to newest version.', function() {
|
grunt.registerTask('update', 'Upgrade database to newest version.', function() {
|
||||||
exec('php scripts/upgrade.php');
|
exec('php scripts/upgrade');
|
||||||
});
|
});
|
||||||
grunt.registerTask('upgrade', ['update']);
|
grunt.registerTask('upgrade', ['update']);
|
||||||
|
|
||||||
|
29
package.json
29
package.json
@ -1,24 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "szurubooru",
|
"name": "szurubooru",
|
||||||
"version": "0.9.0",
|
"version": "1.0.3",
|
||||||
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery.cookie": "1.4.1",
|
|
||||||
"jquery": "~2.1.1",
|
|
||||||
"underscore": "1.7.0",
|
|
||||||
"Mousetrap": "git://github.com/ccampbell/mousetrap.git",
|
"Mousetrap": "git://github.com/ccampbell/mousetrap.git",
|
||||||
"marked": "~0.3.2",
|
"font-awesome": "^4.3.0",
|
||||||
"nprogress": "git://github.com/rstacruz/nprogress.git",
|
|
||||||
|
|
||||||
"requirejs": "*",
|
|
||||||
"ini": "*",
|
|
||||||
"grunt": "~0.4.5",
|
"grunt": "~0.4.5",
|
||||||
"grunt-processhtml": "*",
|
"grunt-cli": "*",
|
||||||
"grunt-contrib-uglify": "*",
|
"grunt-contrib-copy": "*",
|
||||||
"grunt-contrib-cssmin": "*",
|
"grunt-contrib-cssmin": "*",
|
||||||
"grunt-contrib-jshint": "~0.10.0",
|
"grunt-contrib-jshint": "~0.10.0",
|
||||||
"grunt-contrib-copy": "*",
|
"grunt-contrib-uglify": "*",
|
||||||
"grunt-cli": "*",
|
"grunt-processhtml": "*",
|
||||||
|
"ini": "*",
|
||||||
|
"jquery": "~2.1.1",
|
||||||
|
"jquery.cookie": "1.4.1",
|
||||||
|
"marked": "~0.3.2",
|
||||||
|
"nprogress": "git://github.com/rstacruz/nprogress.git",
|
||||||
|
"requirejs": "*",
|
||||||
|
"rimraf": "~2.1",
|
||||||
"shelljs": "~0.3.0",
|
"shelljs": "~0.3.0",
|
||||||
"rimraf": "~2.1"
|
"underscore": "1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,12 +145,6 @@
|
|||||||
<!-- Indentation -->
|
<!-- Indentation -->
|
||||||
<!-- **************** -->
|
<!-- **************** -->
|
||||||
|
|
||||||
<!-- Tests to make sure that a line does not contain the tab character. -->
|
|
||||||
<test name="indentation"> <!-- noTabs -->
|
|
||||||
<property name="type" value="tabs"/> <!-- tabs or spaces -->
|
|
||||||
<property name="number" value="4"/> <!-- number of spaces if type = spaces -->
|
|
||||||
</test>
|
|
||||||
|
|
||||||
<!-- Check the position of the open curly brace in a control structure (if) -->
|
<!-- Check the position of the open curly brace in a control structure (if) -->
|
||||||
<!-- sl = same line -->
|
<!-- sl = same line -->
|
||||||
<!-- nl = new line -->
|
<!-- nl = new line -->
|
||||||
|
@ -4,6 +4,9 @@ DirectoryIndex index.html
|
|||||||
ErrorDocument 404 /404.html
|
ErrorDocument 404 /404.html
|
||||||
|
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||||
|
RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
|
||||||
|
|
||||||
RewriteRule ^/?404.html$ /#/404 [NE,R,L]
|
RewriteRule ^/?404.html$ /#/404 [NE,R,L]
|
||||||
|
|
||||||
|
@ -10,27 +10,33 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments ul {
|
ul.comments {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment ul {
|
||||||
|
list-style-position: inside;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.comment {
|
.comment {
|
||||||
margin: 0 0 1em 0;
|
margin: 0 0 1em 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
display: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment .avatar {
|
.comment .avatar {
|
||||||
margin-right: 0.5em;
|
margin-top: 0.2em;
|
||||||
|
margin-right: 0.75em;
|
||||||
|
-webkit-flex-shrink: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment .content {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
}
|
|
||||||
.comment .content p:first-child {
|
.comment .content p:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@ -83,14 +89,18 @@
|
|||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
#global-comment-list .post-comment {
|
#global-comment-list .post-comment {
|
||||||
|
display: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@media all and (max-width: 40em) {
|
@media all and (max-width: 40em) {
|
||||||
#global-comment-list .post-comment {
|
#global-comment-list .post-comment {
|
||||||
|
-webkit-flex-direction: column;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#global-comment-list .post {
|
#global-comment-list .post {
|
||||||
|
-webkit-flex-shrink: 0;
|
||||||
|
-webkit-flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
@ -100,6 +110,19 @@
|
|||||||
#global-comment-list .comments>h1 {
|
#global-comment-list .comments>h1 {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#global-comment-list .post-small a {
|
#global-comment-list .post-small .link {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sjis {
|
||||||
|
font-family: 'MS PGothic', 'MS Pゴシック', 'IPAMonaPGothic', 'Trebuchet MS', Verdana, Futura, Arial, Helvetica, sans-serif;
|
||||||
|
background: #fbfbfb;
|
||||||
|
color: #111;
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
@ -5,13 +5,19 @@ body {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
color: #555;
|
color: #555;
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
font-size: 17px;
|
font-size: 15px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 40em) {
|
||||||
|
body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 30px;
|
font-size: 160%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -21,11 +27,11 @@ h2 {
|
|||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 20px;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 13px;
|
font-size: 87%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#middle {
|
#middle {
|
||||||
|
@ -40,7 +40,7 @@ input[type=password] {
|
|||||||
box-shadow: 0 1px 2px -1px #e0e0e0 inset;
|
box-shadow: 0 1px 2px -1px #e0e0e0 inset;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
font-family: 'Inconsolata', monospace;
|
font-family: 'Inconsolata', monospace;
|
||||||
font-size: 17px;
|
font-size: 100%;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -200,7 +200,6 @@ input[type=checkbox]:focus + label {
|
|||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
font-size: 15px;
|
|
||||||
}
|
}
|
||||||
.tag-input input {
|
.tag-input input {
|
||||||
border: none;
|
border: none;
|
||||||
@ -210,13 +209,13 @@ input[type=checkbox]:focus + label {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
.tag-input li a.close {
|
.tag-input li a.close {
|
||||||
font-size: 14px;
|
font-size: 85%;
|
||||||
margin-left: 0.75em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tags {
|
.related-tags {
|
||||||
line-height: 200%;
|
line-height: 200%;
|
||||||
font-size: 15px;
|
font-size: 95%;
|
||||||
display: none;
|
display: none;
|
||||||
margin: 0.5em 0.5em 1em 0.5em;
|
margin: 0.5em 0.5em 1em 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#home .post {
|
#home .post {
|
||||||
text-align: left;
|
text-align: center;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
|
min-width: 40em;
|
||||||
}
|
}
|
||||||
#home .post .left {
|
#home .post .left {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.message {
|
.message {
|
||||||
margin: 0 auto 0.2em auto;
|
margin: 1em auto;
|
||||||
padding: 0.4em 0.5em;
|
padding: 0.4em 0.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
|
@ -52,10 +52,16 @@
|
|||||||
.post-list ul.safety .safety-unsafe.disabled:before { background: linear-gradient(#DDB7B7, #C9A195); }
|
.post-list ul.safety .safety-unsafe.disabled:before { background: linear-gradient(#DDB7B7, #C9A195); }
|
||||||
|
|
||||||
.post-list ul.posts {
|
.post-list ul.posts {
|
||||||
|
display: -webkit-flex;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
-webkit-align-content: center;
|
||||||
|
-webkit-flex-wrap: wrap;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -69,8 +75,8 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.post-small .link {
|
.post-small .link {
|
||||||
display: inline-block;
|
display: block;
|
||||||
margin: 0.2em;
|
margin: 0.3em;
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -134,6 +140,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post-small:not(.post-type-image) .link::after {
|
.post-small:not(.post-type-image) .link::after {
|
||||||
|
pointer-events: none;
|
||||||
display: block;
|
display: block;
|
||||||
content: '...';
|
content: '...';
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@ -158,6 +165,9 @@
|
|||||||
.post-small.post-type-flash .link::after {
|
.post-small.post-type-flash .link::after {
|
||||||
content: 'flash';
|
content: 'flash';
|
||||||
}
|
}
|
||||||
|
.post-small.post-type-animation .link::after {
|
||||||
|
content: 'anim';
|
||||||
|
}
|
||||||
|
|
||||||
.post-small .action {
|
.post-small .action {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
#post-upload-step1 .url-handler .input-wrapper {
|
#post-upload-step1 .url-handler .input-wrapper {
|
||||||
margin-right: 8.5em;
|
margin-right: 9.5em;
|
||||||
}
|
}
|
||||||
#post-upload-step1 .url-handler button {
|
#post-upload-step1 .url-handler button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -118,14 +118,14 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#post-upload-step2 .messages {
|
#post-upload-step2 .messages {
|
||||||
margin-bottom: 1em;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
#post-upload-step2 .form-slider {
|
#post-upload-step2 .form-slider {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#post-upload-step2 .form-slider .thumbnail img {
|
#post-upload-step2 .form-slider .thumbnail img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 300px;
|
max-height: 450px;
|
||||||
margin: 0 auto 1em auto;
|
margin: 0 auto 1em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,35 +140,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lightbox {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
#lightbox img {
|
|
||||||
max-width: 400px;
|
|
||||||
max-height: 400px;
|
|
||||||
background: white;
|
|
||||||
border: 0.5em solid white;
|
|
||||||
box-shadow: 0 0 0 1px #eee;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#lightbox:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: -8px;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -8px;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: white;
|
|
||||||
border-left: 1px solid #eee;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#uploading-alert {
|
#uploading-alert {
|
||||||
display: none;
|
display: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -1,24 +1,3 @@
|
|||||||
.post-type-video video {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-type-image .image-wrapper {
|
|
||||||
max-width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.post-type-image .image-wrapper img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-type-youtube iframe {
|
|
||||||
width: 800px;
|
|
||||||
height: 600px;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#post-current-search-wrapper {
|
#post-current-search-wrapper {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -41,45 +20,52 @@
|
|||||||
|
|
||||||
#post-view-wrapper #sidebar {
|
#post-view-wrapper #sidebar {
|
||||||
line-height: 1.33em;
|
line-height: 1.33em;
|
||||||
font-size: 90%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#post-view-wrapper #sidebar h1 {
|
#post-view-wrapper #sidebar .box {
|
||||||
margin-top: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
}
|
text-align: left;
|
||||||
|
|
||||||
#post-view-wrapper #sidebar h1:first-of-type {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 62.5em) {
|
@media all and (min-width: 62.5em) {
|
||||||
#post-view-wrapper {
|
#post-view-wrapper {
|
||||||
|
display: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#post-view-wrapper #sidebar {
|
#post-view-wrapper #sidebar {
|
||||||
min-width: 15em;
|
min-width: 15em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
-webkit-flex: 1;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#post-view-wrapper #post-view {
|
#post-view-wrapper #post-view {
|
||||||
|
-webkit-flex: 5;
|
||||||
flex: 5;
|
flex: 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 62.5em) {
|
@media all and (max-width: 62.5em) {
|
||||||
#post-view-wrapper {
|
#post-view-wrapper {
|
||||||
|
display: -webkit-flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
#post-view-wrapper #sidebar {
|
#post-view-wrapper #sidebar {
|
||||||
order: 2;
|
order: 2;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#post-view-wrapper #sidebar .box {
|
||||||
|
display: inline-block;
|
||||||
|
width: 15em;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
#post-view-wrapper #post-view {
|
#post-view-wrapper #post-view {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 100%;
|
width: 100%;
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,11 +121,19 @@
|
|||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sidebar .fit-mode a {
|
||||||
|
opacity: .25;
|
||||||
|
}
|
||||||
|
#sidebar .fit-mode a.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebar .essential {
|
#sidebar .essential {
|
||||||
|
display: -webkit-flex;
|
||||||
|
-webkit-justify-content: space-around;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
max-width: 30em;
|
|
||||||
}
|
}
|
||||||
#sidebar .essential li {
|
#sidebar .essential li {
|
||||||
display: block;
|
display: block;
|
||||||
@ -147,12 +141,12 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
#sidebar .essential li i.fa {
|
#sidebar .essential li i.fa {
|
||||||
font-size: 30px;
|
font-size: 200%;
|
||||||
}
|
}
|
||||||
#sidebar .essential li a {
|
#sidebar .essential li a {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12px;
|
font-size: 87%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#post-view #post-edit-target {
|
#post-view #post-edit-target {
|
||||||
@ -172,6 +166,9 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#post-edit-target .advanced-trigger .form-input {
|
||||||
|
overflow: auto; /* fix browser's outline around the link being cut due to overflow: hidden; */
|
||||||
|
}
|
||||||
#post-edit-target .file-handler {
|
#post-edit-target .file-handler {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
@ -186,6 +183,25 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
.post-content .object-wrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.post-content .object-wrapper img,
|
||||||
|
.post-content .object-wrapper object,
|
||||||
|
.post-content .object-wrapper iframe,
|
||||||
|
.post-content .object-wrapper video {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.post-content .object-wrapper video {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
.post-notes-target {
|
.post-notes-target {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -225,12 +241,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post-note {
|
.post-note {
|
||||||
|
outline: 0;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
.post-note:focus {
|
||||||
|
border-color: rgba(255, 0, 0, 0.3);
|
||||||
|
background-color: rgba(255, 225, 225, 0.3);
|
||||||
|
}
|
||||||
.post-note .text-wrapper {
|
.post-note .text-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
@ -242,15 +263,13 @@
|
|||||||
width: -webkit-max-content;
|
width: -webkit-max-content;
|
||||||
width: -moz-max-content;
|
width: -moz-max-content;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
max-width: 22.5em;
|
||||||
}
|
}
|
||||||
.post-note .text {
|
.post-note .text {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background: lemonchiffon;
|
background: lemonchiffon;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
.post-note:hover .text-wrapper {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-note .text p:first-of-type {
|
.post-note .text p:first-of-type {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -78,19 +78,6 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-category-character,
|
*[class*='tag-category-']:not(.tag-category-default) a {
|
||||||
.tag-category-character a {
|
color: inherit;
|
||||||
color: #0a0;
|
|
||||||
}
|
|
||||||
.tag-category-copyright,
|
|
||||||
.tag-category-copyright a {
|
|
||||||
color: #a0a;
|
|
||||||
}
|
|
||||||
.tag-category-artist,
|
|
||||||
.tag-category-artist a {
|
|
||||||
color: #a00;
|
|
||||||
}
|
|
||||||
.tag-category-meta,
|
|
||||||
.tag-category-meta a {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
#tag-view small {
|
#tag-view small {
|
||||||
font-size: 12px;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tag-view .siblings ul {
|
#tag-view .siblings ul {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
font-size: 15px;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#top-navigation li a:focus,
|
#top-navigation li a:focus,
|
||||||
@ -34,7 +34,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#top-navigation i {
|
#top-navigation i {
|
||||||
font-size: 40px;
|
font-size: 3em;
|
||||||
margin: 0 10px 5px;
|
margin: 0 10px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,5 +56,5 @@
|
|||||||
#user-list .user h1 {
|
#user-list .user h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 16pt;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
data-version="dev"
|
data-version="dev"
|
||||||
data-build-time=""
|
data-build-time=""
|
||||||
data-max-post-size="10485760"
|
data-max-post-size="10485760"
|
||||||
data-tag-categories='["meta","character","artist","copyright"]'>
|
data-tag-categories='[["meta","meta","#aaa"],["character","character","#0a0"],["artist","artist","#a00"],["copyright","copyright","#a0a"]]'>
|
||||||
<!-- /build -->
|
<!-- /build -->
|
||||||
<!-- build:template
|
<!-- build:template
|
||||||
<head
|
<head
|
||||||
@ -15,6 +15,7 @@
|
|||||||
data-tag-categories='<%= JSON.stringify(tagCategories).replace(/'/g, ''') %>'>
|
data-tag-categories='<%= JSON.stringify(tagCategories).replace(/'/g, ''') %>'>
|
||||||
/build -->
|
/build -->
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
|
|
||||||
<!-- build:remove -->
|
<!-- build:remove -->
|
||||||
<title>szurubooru</title>
|
<title>szurubooru</title>
|
||||||
@ -23,11 +24,26 @@
|
|||||||
<title><%= serviceName %></title>
|
<title><%= serviceName %></title>
|
||||||
/build -->
|
/build -->
|
||||||
|
|
||||||
|
<!-- build:remove -->
|
||||||
|
<style type="text/css">
|
||||||
|
.tag-category-character { color: #0a0; }
|
||||||
|
.tag-category-copyright { color: #a0a; }
|
||||||
|
.tag-category-artist { color: #a00; }
|
||||||
|
.tag-category-meta { color: #aaa; }
|
||||||
|
</style>
|
||||||
|
<!-- /build -->
|
||||||
<!-- build:template
|
<!-- build:template
|
||||||
<link rel="stylesheet" type="text/css" href="app.min.css?<%= timestamp %>"/>
|
<link rel="stylesheet" type="text/css" href="app.min.css?<%= timestamp %>"/>
|
||||||
|
<style type="text/css">
|
||||||
|
<% _.each(tagCategories, function(item) {
|
||||||
|
var type = item[0];
|
||||||
|
var color = item[2];
|
||||||
|
%>.tag-category-<%= type %>{color:<%=color%>;}<%
|
||||||
|
}); %>
|
||||||
|
</style>
|
||||||
/build -->
|
/build -->
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css"/>
|
<link rel="stylesheet" type="text/css" href="/lib/font-awesome/css/font-awesome.min.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Droid+Sans:400,700"/>
|
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Droid+Sans:400,700"/>
|
||||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Inconsolata">
|
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Inconsolata">
|
||||||
|
|
||||||
|
@ -141,6 +141,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
|
|||||||
appState.set('loginToken', response.json.token && response.json.token.name);
|
appState.set('loginToken', response.json.token && response.json.token.name);
|
||||||
appState.set('loggedIn', response.json.user && !!response.json.user.id);
|
appState.set('loggedIn', response.json.user && !!response.json.user.id);
|
||||||
appState.set('loggedInUser', response.json.user);
|
appState.set('loggedInUser', response.json.user);
|
||||||
|
appState.set('config', response.json.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoggedIn(userName) {
|
function isLoggedIn(userName) {
|
||||||
|
@ -32,6 +32,9 @@ App.BrowsingSettings = function(
|
|||||||
sketchy: true,
|
sketchy: true,
|
||||||
unsafe: true,
|
unsafe: true,
|
||||||
},
|
},
|
||||||
|
keyboardShortcuts: true,
|
||||||
|
fitMode: 'fit-width',
|
||||||
|
upscale: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +93,6 @@ App.BrowsingSettings = function(
|
|||||||
getSettings: getSettings,
|
getSettings: getSettings,
|
||||||
setSettings: setSettings,
|
setSettings: setSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.registerSingleton('browsingSettings', ['promise', 'auth', 'api'], App.BrowsingSettings);
|
App.DI.registerSingleton('browsingSettings', ['promise', 'auth', 'api'], App.BrowsingSettings);
|
||||||
|
@ -6,7 +6,9 @@ App.Controls.AutoCompleteInput = function($input) {
|
|||||||
var jQuery = App.DI.get('jQuery');
|
var jQuery = App.DI.get('jQuery');
|
||||||
var tagList = App.DI.get('tagList');
|
var tagList = App.DI.get('tagList');
|
||||||
|
|
||||||
|
var KEY_TAB = 9;
|
||||||
var KEY_RETURN = 13;
|
var KEY_RETURN = 13;
|
||||||
|
var KEY_DELETE = 46;
|
||||||
var KEY_ESCAPE = 27;
|
var KEY_ESCAPE = 27;
|
||||||
var KEY_UP = 38;
|
var KEY_UP = 38;
|
||||||
var KEY_DOWN = 40;
|
var KEY_DOWN = 40;
|
||||||
@ -17,6 +19,7 @@ App.Controls.AutoCompleteInput = function($input) {
|
|||||||
maxResults: 15,
|
maxResults: 15,
|
||||||
minLengthToArbitrarySearch: 3,
|
minLengthToArbitrarySearch: 3,
|
||||||
onApply: null,
|
onApply: null,
|
||||||
|
onDelete: null,
|
||||||
onRender: null,
|
onRender: null,
|
||||||
additionalFilter: null,
|
additionalFilter: null,
|
||||||
};
|
};
|
||||||
@ -63,27 +66,30 @@ App.Controls.AutoCompleteInput = function($input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$input.bind('keydown', function(e) {
|
$input.bind('keydown', function(e) {
|
||||||
|
var func = null;
|
||||||
if (isShown() && e.which === KEY_ESCAPE) {
|
if (isShown() && e.which === KEY_ESCAPE) {
|
||||||
e.preventDefault();
|
func = hide;
|
||||||
e.stopPropagation();
|
} else if (isShown() && e.which === KEY_TAB) {
|
||||||
e.stopImmediatePropagation();
|
if (e.shiftKey) {
|
||||||
hide();
|
func = selectPrevious;
|
||||||
|
} else {
|
||||||
|
func = selectNext;
|
||||||
|
}
|
||||||
} else if (isShown() && e.which === KEY_DOWN) {
|
} else if (isShown() && e.which === KEY_DOWN) {
|
||||||
e.preventDefault();
|
func = selectNext;
|
||||||
e.stopPropagation();
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
selectNext();
|
|
||||||
} else if (isShown() && e.which === KEY_UP) {
|
} else if (isShown() && e.which === KEY_UP) {
|
||||||
e.preventDefault();
|
func = selectPrevious;
|
||||||
e.stopPropagation();
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
selectPrevious();
|
|
||||||
} else if (isShown() && e.which === KEY_RETURN && activeResult >= 0) {
|
} else if (isShown() && e.which === KEY_RETURN && activeResult >= 0) {
|
||||||
|
func = function() { applyAutocomplete(); hide(); };
|
||||||
|
} else if (isShown() && e.which === KEY_DELETE && activeResult >= 0) {
|
||||||
|
func = function() { applyDelete(); hide(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func !== null) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
applyAutocomplete();
|
func();
|
||||||
hide();
|
|
||||||
} else {
|
} else {
|
||||||
window.clearTimeout(showTimeout);
|
window.clearTimeout(showTimeout);
|
||||||
showTimeout = window.setTimeout(showOrHide, 250);
|
showTimeout = window.setTimeout(showOrHide, 250);
|
||||||
@ -182,6 +188,12 @@ App.Controls.AutoCompleteInput = function($input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyDelete() {
|
||||||
|
if (options.onDelete) {
|
||||||
|
options.onDelete(results[activeResult].tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyAutocomplete() {
|
function applyAutocomplete() {
|
||||||
if (options.onApply) {
|
if (options.onApply) {
|
||||||
options.onApply(results[activeResult].tag);
|
options.onApply(results[activeResult].tag);
|
||||||
@ -229,9 +241,15 @@ App.Controls.AutoCompleteInput = function($input) {
|
|||||||
options.onRender($list);
|
options.onRender($list);
|
||||||
}
|
}
|
||||||
refreshActiveResult();
|
refreshActiveResult();
|
||||||
|
|
||||||
|
var x = $input.offset().left;
|
||||||
|
var y = $input.offset().top + $input.outerHeight() - 2;
|
||||||
|
if (y + $div.height() > window.innerHeight) {
|
||||||
|
y = $input.offset().top - $div.height();
|
||||||
|
}
|
||||||
$div.css({
|
$div.css({
|
||||||
left: ($input.offset().left) + 'px',
|
left: x + 'px',
|
||||||
top: ($input.offset().top + $input.outerHeight() - 2) + 'px',
|
top: y + 'px',
|
||||||
});
|
});
|
||||||
$div.show();
|
$div.show();
|
||||||
monitorInputHiding();
|
monitorInputHiding();
|
||||||
|
@ -31,7 +31,9 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
|
|
||||||
var $wrapper = jQuery('<div class="tag-input">');
|
var $wrapper = jQuery('<div class="tag-input">');
|
||||||
var $tagList = jQuery('<ul class="tags">');
|
var $tagList = jQuery('<ul class="tags">');
|
||||||
var $input = jQuery('<input class="tag-real-input" type="text"/>');
|
var tagInputId = 'tags' + Math.random();
|
||||||
|
var $label = jQuery('<label for="' + tagInputId + '" style="display: none">Tags:</label>');
|
||||||
|
var $input = jQuery('<input class="tag-real-input" type="text" id="' + tagInputId + '"/>');
|
||||||
var $siblings = jQuery('<div class="related-tags"><span>Sibling tags:</span><ul>');
|
var $siblings = jQuery('<div class="related-tags"><span>Sibling tags:</span><ul>');
|
||||||
var $suggestions = jQuery('<div class="related-tags"><span>Suggested tags:</span><ul>');
|
var $suggestions = jQuery('<div class="related-tags"><span>Suggested tags:</span><ul>');
|
||||||
init();
|
init();
|
||||||
@ -54,6 +56,7 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
function render() {
|
function render() {
|
||||||
$underlyingInput.hide();
|
$underlyingInput.hide();
|
||||||
$wrapper.append($tagList);
|
$wrapper.append($tagList);
|
||||||
|
$wrapper.append($label);
|
||||||
$wrapper.append($input);
|
$wrapper.append($input);
|
||||||
$wrapper.insertAfter($underlyingInput);
|
$wrapper.insertAfter($underlyingInput);
|
||||||
$wrapper.click(function(e) {
|
$wrapper.click(function(e) {
|
||||||
@ -74,6 +77,10 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
|
|
||||||
function initAutoComplete() {
|
function initAutoComplete() {
|
||||||
var autoComplete = new App.Controls.AutoCompleteInput($input);
|
var autoComplete = new App.Controls.AutoCompleteInput($input);
|
||||||
|
autoComplete.onDelete = function(text) {
|
||||||
|
removeTag(text);
|
||||||
|
$input.val('');
|
||||||
|
};
|
||||||
autoComplete.onApply = function(text) {
|
autoComplete.onApply = function(text) {
|
||||||
processText(text, SOURCE_AUTOCOMPLETION);
|
processText(text, SOURCE_AUTOCOMPLETION);
|
||||||
$input.val('');
|
$input.val('');
|
||||||
@ -112,7 +119,7 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
pastedText = (e.originalEvent || e).clipboardData.getData('text/plain');
|
pastedText = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pastedText.length > 200) {
|
if (pastedText.length > 2000) {
|
||||||
window.alert('Pasted text is too long.');
|
window.alert('Pasted text is too long.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -288,7 +295,7 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
$elem.attr('data-tag', tagName.toLowerCase());
|
$elem.attr('data-tag', tagName.toLowerCase());
|
||||||
|
|
||||||
var $tagLink = jQuery('<a class="tag">');
|
var $tagLink = jQuery('<a class="tag">');
|
||||||
$tagLink.text(tagName);
|
$tagLink.text(tagName + ' ' /* for easy copying */);
|
||||||
$tagLink.click(function(e) {
|
$tagLink.click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showOrHideSiblings(tagName);
|
showOrHideSiblings(tagName);
|
||||||
@ -380,7 +387,7 @@ App.Controls.TagInput = function($underlyingInput) {
|
|||||||
return promise.make(function(resolve, reject) {
|
return promise.make(function(resolve, reject) {
|
||||||
promise.wait(api.get('/tags/' + tagName + '/siblings'))
|
promise.wait(api.get('/tags/' + tagName + '/siblings'))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
resolve(response.json.data);
|
resolve(response.json.tags);
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
var App = App || {};
|
var App = App || {};
|
||||||
|
|
||||||
App.Keyboard = function(jQuery, mousetrap) {
|
App.Keyboard = function(jQuery, mousetrap, browsingSettings) {
|
||||||
|
|
||||||
|
var enabled = browsingSettings.getSettings().keyboardShortcuts;
|
||||||
var oldStopCallback = mousetrap.stopCallback;
|
var oldStopCallback = mousetrap.stopCallback;
|
||||||
mousetrap.stopCallback = function(e, element, combo, sequence) {
|
mousetrap.stopCallback = function(e, element, combo, sequence) {
|
||||||
if (combo.indexOf('ctrl') === -1 && e.ctrlKey) {
|
if (combo.indexOf('ctrl') === -1 && e.ctrlKey) {
|
||||||
@ -14,21 +15,31 @@ App.Keyboard = function(jQuery, mousetrap) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var $focused = jQuery(':focus').eq(0);
|
var $focused = jQuery(':focus').eq(0);
|
||||||
if ($focused.length && $focused.prop('tagName').match(/embed|object/i)) {
|
if ($focused.length) {
|
||||||
|
if ($focused.prop('tagName').match(/embed|object/i)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ($focused.prop('tagName').toLowerCase() === 'input' &&
|
||||||
|
$focused.attr('type').match(/checkbox|radio/i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return oldStopCallback.apply(mousetrap, arguments);
|
return oldStopCallback.apply(mousetrap, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
function keyup(key, callback) {
|
function keyup(key, callback) {
|
||||||
unbind(key);
|
unbind(key);
|
||||||
|
if (enabled) {
|
||||||
mousetrap.bind(key, callback, 'keyup');
|
mousetrap.bind(key, callback, 'keyup');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function keydown(key, callback) {
|
function keydown(key, callback) {
|
||||||
unbind(key);
|
unbind(key);
|
||||||
|
if (enabled) {
|
||||||
mousetrap.bind(key, callback);
|
mousetrap.bind(key, callback);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
mousetrap.reset();
|
mousetrap.reset();
|
||||||
@ -47,4 +58,4 @@ App.Keyboard = function(jQuery, mousetrap) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('keyboard', ['jQuery', 'mousetrap'], App.Keyboard);
|
App.DI.register('keyboard', ['jQuery', 'mousetrap', 'browsingSettings'], App.Keyboard);
|
||||||
|
@ -71,10 +71,7 @@ App.Pager = function(
|
|||||||
var totalRecords = response.json.totalRecords;
|
var totalRecords = response.json.totalRecords;
|
||||||
totalPages = Math.ceil(totalRecords / pageSize);
|
totalPages = Math.ceil(totalRecords / pageSize);
|
||||||
|
|
||||||
resolve({
|
resolve(response);
|
||||||
entities: response.json.data,
|
|
||||||
totalRecords: totalRecords,
|
|
||||||
totalPages: totalPages});
|
|
||||||
|
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
reject(response);
|
reject(response);
|
||||||
|
@ -53,7 +53,7 @@ App.Presenters.CommentListPresenter = function(
|
|||||||
if (comments.length === 0) {
|
if (comments.length === 0) {
|
||||||
promise.wait(api.get('/comments/' + params.post.id))
|
promise.wait(api.get('/comments/' + params.post.id))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
comments = response.json.data;
|
comments = response.json.comments;
|
||||||
render();
|
render();
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
console.log(arguments);
|
console.log(arguments);
|
||||||
@ -72,8 +72,7 @@ App.Presenters.CommentListPresenter = function(
|
|||||||
{
|
{
|
||||||
commentListItemTemplate: templates.commentListItem,
|
commentListItemTemplate: templates.commentListItem,
|
||||||
commentFormTemplate: templates.commentForm,
|
commentFormTemplate: templates.commentForm,
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatMarkdown: util.formatMarkdown,
|
|
||||||
comments: comments,
|
comments: comments,
|
||||||
post: post,
|
post: post,
|
||||||
},
|
},
|
||||||
@ -102,9 +101,7 @@ App.Presenters.CommentListPresenter = function(
|
|||||||
function renderComment($targetList, comment) {
|
function renderComment($targetList, comment) {
|
||||||
var $item = jQuery('<li>' + templates.commentListItem({
|
var $item = jQuery('<li>' + templates.commentListItem({
|
||||||
comment: comment,
|
comment: comment,
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
formatMarkdown: util.formatMarkdown,
|
|
||||||
canVote: auth.isLoggedIn(),
|
canVote: auth.isLoggedIn(),
|
||||||
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.canEditOwnComments : privileges.canEditAllComments,
|
canEditComment: auth.isLoggedIn(comment.user.name) ? privileges.canEditOwnComments : privileges.canEditAllComments,
|
||||||
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.canDeleteOwnComments : privileges.canDeleteAllComments,
|
canDeleteComment: auth.isLoggedIn(comment.user.name) ? privileges.canDeleteOwnComments : privileges.canDeleteAllComments,
|
||||||
@ -179,7 +176,7 @@ App.Presenters.CommentListPresenter = function(
|
|||||||
|
|
||||||
p.then(function(response) {
|
p.then(function(response) {
|
||||||
$textarea.val('');
|
$textarea.val('');
|
||||||
var comment = response.json;
|
var comment = response.json.comment;
|
||||||
|
|
||||||
if (commentToEdit) {
|
if (commentToEdit) {
|
||||||
$form.slideUp(function() {
|
$form.slideUp(function() {
|
||||||
|
@ -38,8 +38,8 @@ App.Presenters.GlobalCommentListPresenter = function(
|
|||||||
baseUri: '#/comments',
|
baseUri: '#/comments',
|
||||||
backendUri: '/comments',
|
backendUri: '/comments',
|
||||||
$target: $el.find('.pagination-target'),
|
$target: $el.find('.pagination-target'),
|
||||||
updateCallback: function($page, data) {
|
updateCallback: function($page, response) {
|
||||||
renderComments($page, data.entities);
|
renderComments($page, response.json.comments);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
@ -53,7 +53,7 @@ App.Presenters.GlobalCommentListPresenter = function(
|
|||||||
|
|
||||||
|
|
||||||
function reinit(params, loaded) {
|
function reinit(params, loaded) {
|
||||||
pagerPresenter.reinit({query: params.query});
|
pagerPresenter.reinit({query: params.query || {}});
|
||||||
loaded();
|
loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +65,11 @@ App.Presenters.GlobalCommentListPresenter = function(
|
|||||||
$el.html(templates.list());
|
$el.html(templates.list());
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderComments($page, data) {
|
function renderComments($page, postComments) {
|
||||||
var $target = $page.find('.posts');
|
var $target = $page.find('.posts');
|
||||||
_.each(data, function(data) {
|
_.each(postComments, function(postComments) {
|
||||||
var post = data.post;
|
var post = postComments.post;
|
||||||
var comments = data.comments;
|
var comments = postComments.comments;
|
||||||
|
|
||||||
var $post = jQuery('<li>' + templates.listItem({
|
var $post = jQuery('<li>' + templates.listItem({
|
||||||
util: util,
|
util: util,
|
||||||
post: post,
|
post: post,
|
||||||
|
@ -31,8 +31,8 @@ App.Presenters.HistoryPresenter = function(
|
|||||||
baseUri: '#/history',
|
baseUri: '#/history',
|
||||||
backendUri: '/history',
|
backendUri: '/history',
|
||||||
$target: $el.find('.pagination-target'),
|
$target: $el.find('.pagination-target'),
|
||||||
updateCallback: function($page, data) {
|
updateCallback: function($page, response) {
|
||||||
renderHistory($page, data.entities);
|
renderHistory($page, response.json.history);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
@ -62,8 +62,7 @@ App.Presenters.HistoryPresenter = function(
|
|||||||
|
|
||||||
function renderHistory($page, historyItems) {
|
function renderHistory($page, historyItems) {
|
||||||
$page.append(templates.history({
|
$page.append(templates.history({
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
history: historyItems}));
|
history: historyItems}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +72,6 @@ App.Presenters.HistoryPresenter = function(
|
|||||||
deinit: deinit,
|
deinit: deinit,
|
||||||
render: render,
|
render: render,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('historyPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.HistoryPresenter);
|
App.DI.register('historyPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'pagerPresenter', 'topNavigationPresenter'], App.Presenters.HistoryPresenter);
|
||||||
|
@ -41,7 +41,14 @@ App.Presenters.HomePresenter = function(
|
|||||||
if ($el.find('#post-content-target').length > 0) {
|
if ($el.find('#post-content-target').length > 0) {
|
||||||
presenterManager.initPresenters([
|
presenterManager.initPresenters([
|
||||||
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}]],
|
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}]],
|
||||||
function() {});
|
function() {
|
||||||
|
var $wrapper = $el.find('.object-wrapper');
|
||||||
|
$wrapper.css({
|
||||||
|
maxWidth: $wrapper.attr('data-width') + 'px',
|
||||||
|
width: 'auto',
|
||||||
|
margin: '0 auto'});
|
||||||
|
postContentPresenter.updatePostNotesSize();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
@ -58,8 +65,7 @@ App.Presenters.HomePresenter = function(
|
|||||||
title: topNavigationPresenter.getBaseTitle(),
|
title: topNavigationPresenter.getBaseTitle(),
|
||||||
canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers),
|
canViewUsers: auth.hasPrivilege(auth.privileges.viewUsers),
|
||||||
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
|
canViewPosts: auth.hasPrivilege(auth.privileges.viewPosts),
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatFileSize: util.formatFileSize,
|
|
||||||
version: jQuery('head').attr('data-version'),
|
version: jQuery('head').attr('data-version'),
|
||||||
buildTime: jQuery('head').attr('data-build-time'),
|
buildTime: jQuery('head').attr('data-build-time'),
|
||||||
}));
|
}));
|
||||||
|
@ -62,16 +62,8 @@ App.Presenters.PagerPresenter = function(
|
|||||||
.fail(loaded);
|
.fail(loaded);
|
||||||
|
|
||||||
if (!endlessScroll) {
|
if (!endlessScroll) {
|
||||||
keyboard.keydown('a', function() {
|
keyboard.keydown(['a', 'left'], navigateToPrevPage);
|
||||||
if (pager.prevPage()) {
|
keyboard.keydown(['d', 'right'], navigateToNextPage);
|
||||||
syncUrl({page: pager.getPage()});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
keyboard.keydown('d', function() {
|
|
||||||
if (pager.nextPage()) {
|
|
||||||
syncUrl({page: pager.getPage()});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,11 +74,12 @@ App.Presenters.PagerPresenter = function(
|
|||||||
function getUrl(options) {
|
function getUrl(options) {
|
||||||
return util.appendComplexRouteParam(
|
return util.appendComplexRouteParam(
|
||||||
baseUri,
|
baseUri,
|
||||||
|
util.simplifySearchQuery(
|
||||||
_.extend(
|
_.extend(
|
||||||
{},
|
{},
|
||||||
pager.getSearchParams(),
|
pager.getSearchParams(),
|
||||||
{page: pager.getPage()},
|
{page: pager.getPage()},
|
||||||
options));
|
options)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncUrl(options) {
|
function syncUrl(options) {
|
||||||
@ -121,7 +114,15 @@ App.Presenters.PagerPresenter = function(
|
|||||||
updateCallback($page, response);
|
updateCallback($page, response);
|
||||||
|
|
||||||
refreshPageList();
|
refreshPageList();
|
||||||
if (!response.entities.length) {
|
|
||||||
|
var entities =
|
||||||
|
response.json.posts ||
|
||||||
|
response.json.users ||
|
||||||
|
response.json.comments ||
|
||||||
|
response.json.tags ||
|
||||||
|
response.json.history;
|
||||||
|
|
||||||
|
if (!entities.length) {
|
||||||
messagePresenter.showInfo($messages, 'No data to show');
|
messagePresenter.showInfo($messages, 'No data to show');
|
||||||
if (pager.getVisiblePages().length === 1) {
|
if (pager.getVisiblePages().length === 1) {
|
||||||
hidePageList();
|
hidePageList();
|
||||||
@ -132,7 +133,7 @@ App.Presenters.PagerPresenter = function(
|
|||||||
showPageList();
|
showPageList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pager.getPage() < response.totalPages) {
|
if (pager.getPage() < pager.getTotalPages()) {
|
||||||
attachNextPageLoader();
|
attachNextPageLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,13 +183,28 @@ App.Presenters.PagerPresenter = function(
|
|||||||
$pageList.hide();
|
$pageList.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateToPrevPage() {
|
||||||
|
console.log('!');
|
||||||
|
if (pager.prevPage()) {
|
||||||
|
syncUrl({page: pager.getPage()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToNextPage() {
|
||||||
|
if (pager.nextPage()) {
|
||||||
|
syncUrl({page: pager.getPage()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function refreshPageList() {
|
function refreshPageList() {
|
||||||
|
var $lastItem = $pageList.find('li:last-child');
|
||||||
|
var currentPage = pager.getPage();
|
||||||
var pages = pager.getVisiblePages();
|
var pages = pager.getVisiblePages();
|
||||||
$pageList.empty();
|
$pageList.find('li.page').remove();
|
||||||
var lastPage = 0;
|
var lastPage = 0;
|
||||||
_.each(pages, function(page) {
|
_.each(pages, function(page) {
|
||||||
if (page - lastPage > 1) {
|
if (page - lastPage > 1) {
|
||||||
$pageList.append(jQuery('<li><a>…</a></li>'));
|
jQuery('<li class="page ellipsis"><a>…</a></li>').insertBefore($lastItem);
|
||||||
}
|
}
|
||||||
lastPage = page;
|
lastPage = page;
|
||||||
|
|
||||||
@ -199,12 +215,19 @@ App.Presenters.PagerPresenter = function(
|
|||||||
});
|
});
|
||||||
$a.addClass('big-button');
|
$a.addClass('big-button');
|
||||||
$a.text(page);
|
$a.text(page);
|
||||||
if (page === pager.getPage()) {
|
if (page === currentPage) {
|
||||||
$a.addClass('active');
|
$a.addClass('active');
|
||||||
}
|
}
|
||||||
var $li = jQuery('<li/>');
|
jQuery('<li class="page"/>').append($a).insertBefore($lastItem);
|
||||||
$li.append($a);
|
});
|
||||||
$pageList.append($li);
|
|
||||||
|
$pageList.find('li.next a').unbind('click').bind('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
navigateToNextPage();
|
||||||
|
});
|
||||||
|
$pageList.find('li.prev a').unbind('click').bind('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
navigateToPrevPage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,15 @@ App.Presenters.PostContentPresenter = function(
|
|||||||
jQuery,
|
jQuery,
|
||||||
util,
|
util,
|
||||||
promise,
|
promise,
|
||||||
|
keyboard,
|
||||||
presenterManager,
|
presenterManager,
|
||||||
postNotesPresenter) {
|
postNotesPresenter,
|
||||||
|
browsingSettings) {
|
||||||
|
|
||||||
var post;
|
var post;
|
||||||
var templates = {};
|
var templates = {};
|
||||||
var $target;
|
var $target;
|
||||||
|
var $wrapper;
|
||||||
|
|
||||||
function init(params, loaded) {
|
function init(params, loaded) {
|
||||||
$target = params.$target;
|
$target = params.$target;
|
||||||
@ -27,14 +30,95 @@ App.Presenters.PostContentPresenter = function(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFitters() {
|
||||||
|
var originalWidth = $wrapper.attr('data-width');
|
||||||
|
var originalHeight = $wrapper.attr('data-height');
|
||||||
|
var ratio = originalWidth / originalHeight;
|
||||||
|
var containerHeight = jQuery(window).height() - $wrapper.offset().top - 10;
|
||||||
|
var containerWidth = $wrapper.parent().outerWidth() - 10;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'fit-both': function(allowUpscale) {
|
||||||
|
var width = containerWidth;
|
||||||
|
var height = containerWidth / ratio;
|
||||||
|
if (height > containerHeight) {
|
||||||
|
width = containerHeight * ratio;
|
||||||
|
height = containerHeight;
|
||||||
|
}
|
||||||
|
if (!allowUpscale) {
|
||||||
|
if (width > originalWidth) {
|
||||||
|
width = originalWidth;
|
||||||
|
height = originalWidth / ratio;
|
||||||
|
}
|
||||||
|
if (height > originalHeight) {
|
||||||
|
width = originalHeight * ratio;
|
||||||
|
height = originalHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$wrapper.css({maxWidth: width + 'px'});
|
||||||
|
},
|
||||||
|
'fit-height': function(allowUpscale) {
|
||||||
|
var width = containerHeight * ratio;
|
||||||
|
if (width > originalWidth && !allowUpscale) {
|
||||||
|
width = originalWidth;
|
||||||
|
}
|
||||||
|
$wrapper.css({maxWidth: width + 'px'});
|
||||||
|
},
|
||||||
|
'fit-width': function(allowUpscale) {
|
||||||
|
if (allowUpscale) {
|
||||||
|
$wrapper.css({maxWidth: containerWidth + 'px'});
|
||||||
|
} else {
|
||||||
|
$wrapper.css({maxWidth: originalWidth + 'px'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'original': function(allowUpscale) {
|
||||||
|
$wrapper.css({
|
||||||
|
minWidth: originalWidth + 'px',
|
||||||
|
width: originalWidth + 'px'});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFitMode() {
|
||||||
|
return $wrapper.data('fit-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFitMode(fitMode) {
|
||||||
|
$wrapper.data('fit-mode', fitMode);
|
||||||
|
$wrapper.css({
|
||||||
|
width: '', height: '',
|
||||||
|
minWidth: '', minHeight: '',
|
||||||
|
maxWidth: '', maxHeight: '',
|
||||||
|
});
|
||||||
|
getFitters()[fitMode.style](fitMode.upscale);
|
||||||
|
updatePostNotesSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleFitMode() {
|
||||||
|
var oldMode = getFitMode();
|
||||||
|
var fitterNames = Object.keys(getFitters());
|
||||||
|
var newMode = {
|
||||||
|
style: fitterNames[(fitterNames.indexOf(oldMode.style) + 1) % fitterNames.length],
|
||||||
|
upscale: oldMode.upscale,
|
||||||
|
};
|
||||||
|
changeFitMode(newMode);
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$target.html(templates.postContent({post: post}));
|
$target.html(templates.postContent({post: post}));
|
||||||
|
$wrapper = $target.find('.object-wrapper');
|
||||||
|
|
||||||
if (post.contentType === 'image') {
|
if (post.contentType === 'image' || post.contentType === 'animation') {
|
||||||
loadPostNotes();
|
loadPostNotes();
|
||||||
updatePostNotesSize();
|
updatePostNotesSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeFitMode({
|
||||||
|
style: browsingSettings.getSettings().fitMode,
|
||||||
|
upscale: browsingSettings.getSettings().upscale,
|
||||||
|
});
|
||||||
|
keyboard.keyup('f', cycleFitMode);
|
||||||
|
|
||||||
jQuery(window).resize(updatePostNotesSize);
|
jQuery(window).resize(updatePostNotesSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +129,14 @@ App.Presenters.PostContentPresenter = function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePostNotesSize() {
|
function updatePostNotesSize() {
|
||||||
$target.find('.post-notes-target').width($target.find('.image-wrapper').outerWidth());
|
var $postNotes = $target.find('.post-notes-target');
|
||||||
$target.find('.post-notes-target').height($target.find('.image-wrapper').outerHeight());
|
var $wrapper = $target.find('.object-wrapper');
|
||||||
|
$postNotes.css({
|
||||||
|
width: $wrapper.outerWidth() + 'px',
|
||||||
|
height: $wrapper.outerHeight() + 'px',
|
||||||
|
left: ($wrapper.offset().left - $wrapper.parent().offset().left) + 'px',
|
||||||
|
top: ($wrapper.offset().top - $wrapper.parent().offset().top) + 'px',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewPostNote() {
|
function addNewPostNote() {
|
||||||
@ -57,14 +147,19 @@ App.Presenters.PostContentPresenter = function(
|
|||||||
init: init,
|
init: init,
|
||||||
render: render,
|
render: render,
|
||||||
addNewPostNote: addNewPostNote,
|
addNewPostNote: addNewPostNote,
|
||||||
|
updatePostNotesSize: updatePostNotesSize,
|
||||||
|
getFitMode: getFitMode,
|
||||||
|
changeFitMode: changeFitMode,
|
||||||
|
cycleFitMode: cycleFitMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('postContentPresenter', [
|
App.DI.register('postContentPresenter', [
|
||||||
'jQuery',
|
'jQuery',
|
||||||
'util',
|
'util',
|
||||||
'promise',
|
'promise',
|
||||||
|
'keyboard',
|
||||||
'presenterManager',
|
'presenterManager',
|
||||||
'postNotesPresenter'],
|
'postNotesPresenter',
|
||||||
|
'browsingSettings'],
|
||||||
App.Presenters.PostContentPresenter);
|
App.Presenters.PostContentPresenter);
|
||||||
|
@ -2,6 +2,7 @@ var App = App || {};
|
|||||||
App.Presenters = App.Presenters || {};
|
App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
App.Presenters.PostEditPresenter = function(
|
App.Presenters.PostEditPresenter = function(
|
||||||
|
jQuery,
|
||||||
util,
|
util,
|
||||||
promise,
|
promise,
|
||||||
api,
|
api,
|
||||||
@ -46,7 +47,20 @@ App.Presenters.PostEditPresenter = function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$target.html(templates.postEdit({post: post, privileges: privileges}));
|
var $template = jQuery(templates.postEdit({post: post, privileges: privileges}));
|
||||||
|
|
||||||
|
var $advanced = $template.find('.advanced');
|
||||||
|
var $advancedTrigger = $template.find('.advanced-trigger');
|
||||||
|
$advanced.hide();
|
||||||
|
if (!$advanced.length) {
|
||||||
|
$advancedTrigger.hide();
|
||||||
|
} else {
|
||||||
|
$advancedTrigger.find('a').click(function(e) {
|
||||||
|
advancedTriggerClicked(e, $advanced, $advancedTrigger);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$target.html($template);
|
||||||
|
|
||||||
postContentFileDropper = new App.Controls.FileDropper($target.find('form [name=content]'));
|
postContentFileDropper = new App.Controls.FileDropper($target.find('form [name=content]'));
|
||||||
postContentFileDropper.onChange = postContentChanged;
|
postContentFileDropper.onChange = postContentChanged;
|
||||||
@ -63,6 +77,12 @@ App.Presenters.PostEditPresenter = function(
|
|||||||
$target.find('form').submit(editFormSubmitted);
|
$target.find('form').submit(editFormSubmitted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function advancedTriggerClicked(e, $advanced, $advancedTrigger) {
|
||||||
|
$advancedTrigger.hide();
|
||||||
|
$advanced.show();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
if (tagInput) {
|
if (tagInput) {
|
||||||
tagInput.focus();
|
tagInput.focus();
|
||||||
@ -89,7 +109,7 @@ App.Presenters.PostEditPresenter = function(
|
|||||||
function editPost() {
|
function editPost() {
|
||||||
var $form = $target.find('form');
|
var $form = $target.find('form');
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append('seenEditTime', post.lastEditTime);
|
formData.append('lastEditTime', post.lastEditTime);
|
||||||
|
|
||||||
if (privileges.canChangeContent && postContent) {
|
if (privileges.canChangeContent && postContent) {
|
||||||
formData.append('content', postContent);
|
formData.append('content', postContent);
|
||||||
@ -126,11 +146,14 @@ App.Presenters.PostEditPresenter = function(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jQuery(document.activeElement).blur();
|
||||||
|
|
||||||
promise.wait(api.post('/posts/' + post.id, formData))
|
promise.wait(api.post('/posts/' + post.id, formData))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
tagList.refreshTags();
|
tagList.refreshTags();
|
||||||
|
post = response.json.post;
|
||||||
if (typeof(updateCallback) !== 'undefined') {
|
if (typeof(updateCallback) !== 'undefined') {
|
||||||
updateCallback(post = response.json);
|
updateCallback(post);
|
||||||
}
|
}
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
showEditError(response);
|
showEditError(response);
|
||||||
@ -150,4 +173,4 @@ App.Presenters.PostEditPresenter = function(
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('postEditPresenter', ['util', 'promise', 'api', 'auth', 'tagList'], App.Presenters.PostEditPresenter);
|
App.DI.register('postEditPresenter', ['jQuery', 'util', 'promise', 'api', 'auth', 'tagList'], App.Presenters.PostEditPresenter);
|
||||||
|
@ -45,8 +45,8 @@ App.Presenters.PostListPresenter = function(
|
|||||||
baseUri: '#/posts',
|
baseUri: '#/posts',
|
||||||
backendUri: '/posts',
|
backendUri: '/posts',
|
||||||
$target: $el.find('.pagination-target'),
|
$target: $el.find('.pagination-target'),
|
||||||
updateCallback: function($page, data) {
|
updateCallback: function($page, response) {
|
||||||
renderPosts($page, data.entities);
|
renderPosts($page, response.json.posts);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
@ -217,11 +217,11 @@ App.Presenters.PostListPresenter = function(
|
|||||||
tags.push(params.query.massTag);
|
tags.push(params.query.massTag);
|
||||||
}
|
}
|
||||||
var formData = {};
|
var formData = {};
|
||||||
formData.seenEditTime = post.lastEditTime;
|
formData.lastEditTime = post.lastEditTime;
|
||||||
formData.tags = tags.join(' ');
|
formData.tags = tags.join(' ');
|
||||||
promise.wait(api.post('/posts/' + post.id, formData))
|
promise.wait(api.post('/posts/' + post.id, formData))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
post = response.json;
|
post = response.json.post;
|
||||||
$post.data('post', post);
|
$post.data('post', post);
|
||||||
softRenderPost($post);
|
softRenderPost($post);
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
|
@ -50,7 +50,7 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
privileges: privileges,
|
privileges: privileges,
|
||||||
post: post,
|
post: post,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
formatMarkdown: util.formatMarkdown}));
|
util: util}));
|
||||||
|
|
||||||
$form = $target.find('.post-note-edit');
|
$form = $target.find('.post-note-edit');
|
||||||
var $postNotes = $target.find('.post-note');
|
var $postNotes = $target.find('.post-note');
|
||||||
@ -61,8 +61,10 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
$postNote.data('postNote', postNote);
|
$postNote.data('postNote', postNote);
|
||||||
$postNote.find('.text-wrapper').click(postNoteClicked);
|
$postNote.find('.text-wrapper').click(postNoteClicked);
|
||||||
postNote.$element = $postNote;
|
postNote.$element = $postNote;
|
||||||
draggable.makeDraggable($postNote, draggable.relativeDragStrategy);
|
draggable.makeDraggable($postNote, draggable.relativeDragStrategy, true);
|
||||||
resizable.makeResizable($postNote);
|
resizable.makeResizable($postNote, true);
|
||||||
|
$postNote.mouseenter(function() { postNoteMouseEnter(postNote); });
|
||||||
|
$postNote.mouseleave(function() { postNoteMouseLeave(postNote); });
|
||||||
});
|
});
|
||||||
|
|
||||||
$form.find('button').click(formSubmitted);
|
$form.find('button').click(formSubmitted);
|
||||||
@ -97,7 +99,10 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
promise.wait(api.delete('/notes/' + postNote.id))
|
promise.wait(api.delete('/notes/' + postNote.id))
|
||||||
.then(function() {
|
.then(function() {
|
||||||
hideForm();
|
hideForm();
|
||||||
postNote.$element.remove();
|
notes = jQuery.grep(notes, function(otherNote) {
|
||||||
|
return otherNote.id !== postNote.id;
|
||||||
|
});
|
||||||
|
render();
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
window.alert(response.json && response.json.error || response);
|
window.alert(response.json && response.json.error || response);
|
||||||
});
|
});
|
||||||
@ -125,7 +130,7 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
promise.wait(p)
|
promise.wait(p)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
hideForm();
|
hideForm();
|
||||||
postNote.id = response.json.id;
|
postNote.id = response.json.note.id;
|
||||||
postNote.$element.data('postNote', postNote);
|
postNote.$element.data('postNote', postNote);
|
||||||
render();
|
render();
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
@ -141,13 +146,25 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showPostNoteText(postNote) {
|
function showPostNoteText(postNote) {
|
||||||
postNote.$element.find('.text-wrapper').show();
|
var $textWrapper = postNote.$element.find('.text-wrapper');
|
||||||
|
$textWrapper.show();
|
||||||
|
if ($textWrapper.offset().left + $textWrapper.width() > jQuery(window).outerWidth()) {
|
||||||
|
$textWrapper.offset({left: jQuery(window).outerWidth() - $textWrapper.width()});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePostNoteText(postNote) {
|
function hidePostNoteText(postNote) {
|
||||||
postNote.$element.find('.text-wrapper').css('display', '');
|
postNote.$element.find('.text-wrapper').css('display', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function postNoteMouseEnter(postNote) {
|
||||||
|
showPostNoteText(postNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
function postNoteMouseLeave(postNote) {
|
||||||
|
hidePostNoteText(postNote);
|
||||||
|
}
|
||||||
|
|
||||||
function postNoteClicked(e) {
|
function postNoteClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $postNote = jQuery(e.currentTarget).parents('.post-note');
|
var $postNote = jQuery(e.currentTarget).parents('.post-note');
|
||||||
@ -163,7 +180,7 @@ App.Presenters.PostNotesPresenter = function(
|
|||||||
$form.data('postNote', postNote);
|
$form.data('postNote', postNote);
|
||||||
$form.find('textarea').val(postNote.text);
|
$form.find('textarea').val(postNote.text);
|
||||||
$form.show();
|
$form.show();
|
||||||
draggable.makeDraggable($form, draggable.absoluteDragStrategy);
|
draggable.makeDraggable($form, draggable.absoluteDragStrategy, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideForm() {
|
function hideForm() {
|
||||||
|
@ -4,6 +4,7 @@ App.Presenters = App.Presenters || {};
|
|||||||
App.Presenters.PostPresenter = function(
|
App.Presenters.PostPresenter = function(
|
||||||
_,
|
_,
|
||||||
jQuery,
|
jQuery,
|
||||||
|
appState,
|
||||||
util,
|
util,
|
||||||
promise,
|
promise,
|
||||||
api,
|
api,
|
||||||
@ -71,7 +72,9 @@ App.Presenters.PostPresenter = function(
|
|||||||
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}],
|
[postContentPresenter, {post: post, $target: $el.find('#post-content-target')}],
|
||||||
[postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}],
|
[postEditPresenter, {post: post, $target: $el.find('#post-edit-target'), updateCallback: postEdited}],
|
||||||
[commentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]],
|
[commentListPresenter, {post: post, $target: $el.find('#post-comments-target')}]],
|
||||||
function() { });
|
function() {
|
||||||
|
syncFitModeButtons();
|
||||||
|
});
|
||||||
|
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
console.log(arguments);
|
console.log(arguments);
|
||||||
@ -89,25 +92,25 @@ App.Presenters.PostPresenter = function(
|
|||||||
if (nextPostUrl) {
|
if (nextPostUrl) {
|
||||||
$nextPost.addClass('enabled');
|
$nextPost.addClass('enabled');
|
||||||
$nextPost.attr('href', nextPostUrl);
|
$nextPost.attr('href', nextPostUrl);
|
||||||
keyboard.keyup('a', function() {
|
keyboard.keyup(['a', 'left'], function() {
|
||||||
router.navigate(nextPostUrl);
|
router.navigate(nextPostUrl);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$nextPost.removeClass('enabled');
|
$nextPost.removeClass('enabled');
|
||||||
$nextPost.removeAttr('href');
|
$nextPost.removeAttr('href');
|
||||||
keyboard.unbind('a');
|
keyboard.unbind(['a', 'left']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevPostUrl) {
|
if (prevPostUrl) {
|
||||||
$prevPost.addClass('enabled');
|
$prevPost.addClass('enabled');
|
||||||
$prevPost.attr('href', prevPostUrl);
|
$prevPost.attr('href', prevPostUrl);
|
||||||
keyboard.keyup('d', function() {
|
keyboard.keyup(['d', 'right'], function() {
|
||||||
router.navigate(prevPostUrl);
|
router.navigate(prevPostUrl);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$prevPost.removeClass('enabled');
|
$prevPost.removeClass('enabled');
|
||||||
$prevPost.removeAttr('href');
|
$prevPost.removeAttr('href');
|
||||||
keyboard.unbind('d');
|
keyboard.unbind(['d', 'right']);
|
||||||
}
|
}
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
});
|
});
|
||||||
@ -117,7 +120,7 @@ App.Presenters.PostPresenter = function(
|
|||||||
return promise.make(function(resolve, reject) {
|
return promise.make(function(resolve, reject) {
|
||||||
promise.wait(api.get('/posts/' + postNameOrId))
|
promise.wait(api.get('/posts/' + postNameOrId))
|
||||||
.then(function(postResponse) {
|
.then(function(postResponse) {
|
||||||
post = postResponse.json;
|
post = postResponse.json.post;
|
||||||
resolve();
|
resolve();
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
showGenericError(response);
|
showGenericError(response);
|
||||||
@ -135,7 +138,6 @@ App.Presenters.PostPresenter = function(
|
|||||||
});
|
});
|
||||||
|
|
||||||
attachSidebarEvents();
|
attachSidebarEvents();
|
||||||
|
|
||||||
attachLinksToPostsAround();
|
attachLinksToPostsAround();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +149,7 @@ App.Presenters.PostPresenter = function(
|
|||||||
|
|
||||||
function softRender() {
|
function softRender() {
|
||||||
renderSidebar();
|
renderSidebar();
|
||||||
|
syncFitModeButtons();
|
||||||
$el.find('video').prop('loop', post.flags.loop);
|
$el.find('video').prop('loop', post.flags.loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,13 +162,12 @@ App.Presenters.PostPresenter = function(
|
|||||||
return templates.post({
|
return templates.post({
|
||||||
query: params.query,
|
query: params.query,
|
||||||
post: post,
|
post: post,
|
||||||
|
forceHttpInPermalinks: appState.get('config').forceHttpInPermalinks,
|
||||||
ownScore: post.ownScore,
|
ownScore: post.ownScore,
|
||||||
postFavorites: post.favorites,
|
postFavorites: post.favorites,
|
||||||
postHistory: post.history,
|
postHistory: post.history,
|
||||||
|
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
formatFileSize: util.formatFileSize,
|
|
||||||
|
|
||||||
historyTemplate: templates.history,
|
historyTemplate: templates.history,
|
||||||
|
|
||||||
@ -179,6 +181,7 @@ App.Presenters.PostPresenter = function(
|
|||||||
function attachSidebarEvents() {
|
function attachSidebarEvents() {
|
||||||
$el.find('#sidebar .delete').click(deleteButtonClicked);
|
$el.find('#sidebar .delete').click(deleteButtonClicked);
|
||||||
$el.find('#sidebar .feature').click(featureButtonClicked);
|
$el.find('#sidebar .feature').click(featureButtonClicked);
|
||||||
|
$el.find('#sidebar .fit-mode a').click(fitModeButtonsClicked);
|
||||||
$el.find('#sidebar .edit').click(editButtonClicked);
|
$el.find('#sidebar .edit').click(editButtonClicked);
|
||||||
$el.find('#sidebar .history').click(historyButtonClicked);
|
$el.find('#sidebar .history').click(historyButtonClicked);
|
||||||
$el.find('#sidebar .add-favorite').click(addFavoriteButtonClicked);
|
$el.find('#sidebar .add-favorite').click(addFavoriteButtonClicked);
|
||||||
@ -208,6 +211,14 @@ App.Presenters.PostPresenter = function(
|
|||||||
}).fail(showGenericError);
|
}).fail(showGenericError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncFitModeButtons() {
|
||||||
|
var fitStyle = postContentPresenter.getFitMode().style;
|
||||||
|
$el.find('#sidebar .fit-mode a').each(function(i, item) {
|
||||||
|
var $item = jQuery(item);
|
||||||
|
$item.toggleClass('active', $item.attr('data-fit-mode') === fitStyle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function featureButtonClicked(e) {
|
function featureButtonClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
messagePresenter.hideMessages($messages);
|
messagePresenter.hideMessages($messages);
|
||||||
@ -216,6 +227,17 @@ App.Presenters.PostPresenter = function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fitModeButtonsClicked(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var oldMode = postContentPresenter.getFitMode();
|
||||||
|
var newMode = {
|
||||||
|
style: jQuery(e.target).attr('data-fit-mode'),
|
||||||
|
upscale: oldMode.upscale,
|
||||||
|
};
|
||||||
|
postContentPresenter.changeFitMode(newMode);
|
||||||
|
syncFitModeButtons();
|
||||||
|
}
|
||||||
|
|
||||||
function featurePost() {
|
function featurePost() {
|
||||||
promise.wait(api.post('/posts/' + post.id + '/feature'))
|
promise.wait(api.post('/posts/' + post.id + '/feature'))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
@ -324,6 +346,7 @@ App.Presenters.PostPresenter = function(
|
|||||||
App.DI.register('postPresenter', [
|
App.DI.register('postPresenter', [
|
||||||
'_',
|
'_',
|
||||||
'jQuery',
|
'jQuery',
|
||||||
|
'appState',
|
||||||
'util',
|
'util',
|
||||||
'promise',
|
'promise',
|
||||||
'api',
|
'api',
|
||||||
|
@ -64,6 +64,8 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
$el.find('.remove').click(removeButtonClicked);
|
$el.find('.remove').click(removeButtonClicked);
|
||||||
$el.find('.move-up').click(moveUpButtonClicked);
|
$el.find('.move-up').click(moveUpButtonClicked);
|
||||||
$el.find('.move-down').click(moveDownButtonClicked);
|
$el.find('.move-down').click(moveDownButtonClicked);
|
||||||
|
$el.find('.previous').click(selectPrevPostTableRow);
|
||||||
|
$el.find('.next').click(selectNextPostTableRow);
|
||||||
$el.find('.upload').click(uploadButtonClicked);
|
$el.find('.upload').click(uploadButtonClicked);
|
||||||
$el.find('.stop').click(stopButtonClicked);
|
$el.find('.stop').click(stopButtonClicked);
|
||||||
}
|
}
|
||||||
@ -78,7 +80,7 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
fileName: null,
|
fileName: null,
|
||||||
content: null,
|
content: null,
|
||||||
url: null,
|
url: null,
|
||||||
thumbnail: null,
|
getThumbnail: function() { return promise.makeSilent(function(resolve, reject) { resolve(null); }); },
|
||||||
$tableRow: null,
|
$tableRow: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -111,7 +113,7 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$input.val('');
|
$input.val('');
|
||||||
var post = addPostFromUrl(url);
|
var post = addPostFromURL(url);
|
||||||
selectPostTableRow(post);
|
selectPostTableRow(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,20 +139,13 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
allPosts.push(post);
|
allPosts.push(post);
|
||||||
setAllPosts(allPosts);
|
setAllPosts(allPosts);
|
||||||
createPostTableRow(post);
|
createPostTableRow(post);
|
||||||
|
updatePostThumbnailInTable(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postChanged(post) {
|
function postChanged(post) {
|
||||||
updatePostTableRow(post);
|
updatePostTableRow(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postThumbnailLoaded(post) {
|
|
||||||
var selectedPosts = getSelectedPosts();
|
|
||||||
if (selectedPosts.length === 1 && selectedPosts[0] === post && post.thumbnail !== null) {
|
|
||||||
updatePostThumbnailInForm(post);
|
|
||||||
}
|
|
||||||
updatePostThumbnailInTable(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
function postTableRowClicked(e) {
|
function postTableRowClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!interactionEnabled) {
|
if (!interactionEnabled) {
|
||||||
@ -161,6 +156,7 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
$allCheckboxes.prop('checked', false);
|
$allCheckboxes.prop('checked', false);
|
||||||
$myCheckbox.prop('checked', true);
|
$myCheckbox.prop('checked', true);
|
||||||
postTableCheckboxesChanged(e);
|
postTableCheckboxesChanged(e);
|
||||||
|
tagInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function postTableCheckboxClicked(e) {
|
function postTableCheckboxClicked(e) {
|
||||||
@ -209,24 +205,6 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
postTableSelectionChanged(selectedPosts);
|
postTableSelectionChanged(selectedPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postTableRowImageHovered(e) {
|
|
||||||
var $img = jQuery(this);
|
|
||||||
if ($img.parents('tr').data('post').thumbnail) {
|
|
||||||
var $lightbox = jQuery('#lightbox');
|
|
||||||
$lightbox.find('img').attr('src', $img.attr('src'));
|
|
||||||
$lightbox
|
|
||||||
.show()
|
|
||||||
.css({
|
|
||||||
left: ($img.position().left + $img.outerWidth()) + 'px',
|
|
||||||
top: ($img.position().top + ($img.outerHeight() - $lightbox.outerHeight()) / 2) + 'px',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function postTableRowImageUnhovered(e) {
|
|
||||||
jQuery('#lightbox').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeButtonClicked(e) {
|
function removeButtonClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
removePosts(getSelectedPosts());
|
removePosts(getSelectedPosts());
|
||||||
@ -255,34 +233,75 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
stopUpload();
|
stopUpload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPostFromFile(file) {
|
function makeThumbnail(thumbnailWidth, thumbnailHeight, file) {
|
||||||
var post = _.extend({}, getDefaultPost(), {fileName: file.name, file: file});
|
return promise.makeSilent(function(resolve, reject) {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
fileDropper.readAsDataURL(file, function(content) {
|
var img = new Image();
|
||||||
if (file.type.match('image.*')) {
|
canvas.width = thumbnailWidth;
|
||||||
post.thumbnail = content;
|
canvas.height = thumbnailHeight;
|
||||||
postThumbnailLoaded(post);
|
var context = canvas.getContext('2d');
|
||||||
|
img.onload = function() {
|
||||||
|
//memory still leaks...
|
||||||
|
img.onload = null;
|
||||||
|
context.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
|
||||||
|
URL.revokeObjectURL(img.src);
|
||||||
|
img.src = null;
|
||||||
|
resolve(canvas.toDataURL());
|
||||||
|
};
|
||||||
|
img.src = URL.createObjectURL(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addPostFromFile(file) {
|
||||||
|
var post = _.extend({}, getDefaultPost(), {
|
||||||
|
fileName: file.name,
|
||||||
|
file: file,
|
||||||
|
getThumbnail: function(thumbnailWidth, thumbnailHeight) {
|
||||||
|
return promise.makeSilent(function(resolve, reject) {
|
||||||
|
if (!file.type.match('image.*')) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thumbnailWidth === null || thumbnailHeight === null) {
|
||||||
|
resolve(URL.createObjectURL(post.file));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
makeThumbnail(thumbnailWidth, thumbnailHeight, post.file)
|
||||||
|
.then(function(thumbnailDataURL) {
|
||||||
|
resolve(thumbnailDataURL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
postAdded(post);
|
postAdded(post);
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPostFromUrl(url) {
|
function addPostFromURL(url) {
|
||||||
var post = _.extend({}, getDefaultPost(), {url: url, fileName: url});
|
var post = _.extend({}, getDefaultPost(), {
|
||||||
postAdded(post);
|
url: url,
|
||||||
setPostsSource([post], url);
|
fileName: url,
|
||||||
|
});
|
||||||
|
|
||||||
var matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/);
|
var matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var youtubeThumbnailUrl = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg';
|
var youtubeThumbnailURL = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg';
|
||||||
post.thumbnail = youtubeThumbnailUrl;
|
post.getThumbnail = function(thumbnailWidth, thumbnailHeight) {
|
||||||
postThumbnailLoaded(post);
|
return promise.makeSilent(function(resolve, reject) {
|
||||||
|
resolve(youtubeThumbnailURL);
|
||||||
|
});
|
||||||
|
};
|
||||||
} else if (url.match(/image|img|jpg|png|gif/i)) {
|
} else if (url.match(/image|img|jpg|png|gif/i)) {
|
||||||
post.thumbnail = url;
|
post.getThumbnail = function(thumbnailWidth, thumbnailHeight) {
|
||||||
postThumbnailLoaded(post);
|
return promise.makeSilent(function(resolve, reject) {
|
||||||
|
resolve(url);
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postAdded(post);
|
||||||
|
setPostsSource([post], url);
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,9 +313,8 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
|
|
||||||
$row.removeClass('template');
|
$row.removeClass('template');
|
||||||
$row.find('td:not(.checkbox)').click(postTableRowClicked);
|
$row.find('td:not(.checkbox)').click(postTableRowClicked);
|
||||||
|
$row.find('a').click(postTableRowClicked);
|
||||||
$row.find('td.checkbox').click(postTableCheckboxClicked);
|
$row.find('td.checkbox').click(postTableCheckboxClicked);
|
||||||
$row.find('img').mouseenter(postTableRowImageHovered);
|
|
||||||
$row.find('img').mouseleave(postTableRowImageUnhovered);
|
|
||||||
$row.data('post', post);
|
$row.data('post', post);
|
||||||
$table.find('tbody').append($row);
|
$table.find('tbody').append($row);
|
||||||
$row.find('td.checkbox input').attr('id', _.uniqueId());
|
$row.find('td.checkbox input').attr('id', _.uniqueId());
|
||||||
@ -314,21 +332,31 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePostThumbnailInForm(post) {
|
function updatePostThumbnailInForm(post) {
|
||||||
if (post.thumbnail === null) {
|
post.getThumbnail(null, null).then(function(thumbnailDataURL) {
|
||||||
$el.find('.form-slider .thumbnail img').hide();
|
var $thumbnail = $el.find('.form-slider .thumbnail');
|
||||||
|
var $img = $thumbnail.find('img');
|
||||||
|
var $link = $thumbnail.find('a');
|
||||||
|
if (thumbnailDataURL === null) {
|
||||||
|
$img.hide();
|
||||||
|
$link.hide();
|
||||||
} else {
|
} else {
|
||||||
$el.find('.form-slider .thumbnail img').show()[0].setAttribute('src', post.thumbnail);
|
$img.show();
|
||||||
|
$img.attr('src', thumbnailDataURL);
|
||||||
|
$link.show();
|
||||||
|
$link.attr('href', thumbnailDataURL);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePostThumbnailInTable(post) {
|
function updatePostThumbnailInTable(post) {
|
||||||
|
post.getThumbnail(30, 30).then(function(thumbnailDataURL) {
|
||||||
var $row = post.$tableRow;
|
var $row = post.$tableRow;
|
||||||
if (post.thumbnail === null) {
|
if (thumbnailDataURL === null) {
|
||||||
$row.find('img')[0].setAttribute('src', util.transparentPixel());
|
$row.find('img').attr('src', util.transparentPixel());
|
||||||
//huge speedup thanks to this condition
|
} else {
|
||||||
} else if ($row.find('img').attr('src') !== post.thumbnail) {
|
$row.find('img').attr('src', thumbnailDataURL);
|
||||||
$row.find('img')[0].setAttribute('src', post.thumbnail);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllPosts() {
|
function getAllPosts() {
|
||||||
@ -360,6 +388,9 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
showPostEditForm(selectedPosts);
|
showPostEditForm(selectedPosts);
|
||||||
}
|
}
|
||||||
$el.find('.post-table-op').prop('disabled', selectedPosts.length === 0);
|
$el.find('.post-table-op').prop('disabled', selectedPosts.length === 0);
|
||||||
|
if (selectedPosts.length === 1) {
|
||||||
|
updatePostThumbnailInForm(selectedPosts[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePostEditForm() {
|
function hidePostEditForm() {
|
||||||
@ -409,6 +440,17 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTagIndex(post, tag) {
|
||||||
|
var tags = jQuery.map(post.tags, function(tag) {
|
||||||
|
return tag.toLowerCase();
|
||||||
|
});
|
||||||
|
return tags.indexOf(tag.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTag(post, tag) {
|
||||||
|
return getTagIndex(post, tag) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
function getCombinedPost(posts) {
|
function getCombinedPost(posts) {
|
||||||
var combinedPost = _.extend({}, getDefaultPost());
|
var combinedPost = _.extend({}, getDefaultPost());
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0) {
|
||||||
@ -418,7 +460,7 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
|
|
||||||
var tagFilter = function(post) {
|
var tagFilter = function(post) {
|
||||||
return function(tag) {
|
return function(tag) {
|
||||||
return post.tags.indexOf(tag) !== -1;
|
return hasTag(post, tag);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -465,8 +507,7 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
|
|
||||||
function addTagToPosts(posts, tag) {
|
function addTagToPosts(posts, tag) {
|
||||||
jQuery.each(posts, function(i, post) {
|
jQuery.each(posts, function(i, post) {
|
||||||
var index = post.tags.indexOf(tag);
|
if (!hasTag(post, tag)) {
|
||||||
if (index === -1) {
|
|
||||||
post.tags.push(tag);
|
post.tags.push(tag);
|
||||||
}
|
}
|
||||||
postChanged(post);
|
postChanged(post);
|
||||||
@ -475,9 +516,8 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
|
|
||||||
function removeTagFromPosts(posts, tag) {
|
function removeTagFromPosts(posts, tag) {
|
||||||
jQuery.each(posts, function(i, post) {
|
jQuery.each(posts, function(i, post) {
|
||||||
var index = post.tags.indexOf(tag);
|
if (hasTag(post, tag)) {
|
||||||
if (index !== -1) {
|
post.tags.splice(getTagIndex(post, tag), 1);
|
||||||
post.tags.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
postChanged(post);
|
postChanged(post);
|
||||||
});
|
});
|
||||||
@ -500,10 +540,12 @@ App.Presenters.PostUploadPresenter = function(
|
|||||||
|
|
||||||
function selectPrevPostTableRow() {
|
function selectPrevPostTableRow() {
|
||||||
selectPostTableRow($el.find('tbody tr.selected:eq(0)').prev().data('post'));
|
selectPostTableRow($el.find('tbody tr.selected:eq(0)').prev().data('post'));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNextPostTableRow() {
|
function selectNextPostTableRow() {
|
||||||
selectPostTableRow($el.find('tbody tr.selected:eq(0)').next().data('post'));
|
selectPostTableRow($el.find('tbody tr.selected:eq(0)').next().data('post'));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showOrHidePostsTable() {
|
function showOrHidePostsTable() {
|
||||||
|
@ -60,7 +60,7 @@ App.Presenters.RegistrationPresenter = function(
|
|||||||
function registrationSuccess(apiResponse) {
|
function registrationSuccess(apiResponse) {
|
||||||
$el.find('form').slideUp(function() {
|
$el.find('form').slideUp(function() {
|
||||||
var message = 'Registration complete! ';
|
var message = 'Registration complete! ';
|
||||||
if (!apiResponse.json.confirmed) {
|
if (!apiResponse.json.user.confirmed) {
|
||||||
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
||||||
} else {
|
} else {
|
||||||
message += '<a href="#/login">Click here</a> to login.';
|
message += '<a href="#/login">Click here</a> to login.';
|
||||||
|
@ -36,8 +36,8 @@ App.Presenters.TagListPresenter = function(
|
|||||||
baseUri: '#/tags',
|
baseUri: '#/tags',
|
||||||
backendUri: '/tags',
|
backendUri: '/tags',
|
||||||
$target: $el.find('.pagination-target'),
|
$target: $el.find('.pagination-target'),
|
||||||
updateCallback: function($page, data) {
|
updateCallback: function($page, response) {
|
||||||
renderTags($page, data.entities);
|
renderTags($page, response.json.tags);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
@ -108,7 +108,7 @@ App.Presenters.TagListPresenter = function(
|
|||||||
_.each(tags, function(tag) {
|
_.each(tags, function(tag) {
|
||||||
var $item = jQuery(templates.listItem({
|
var $item = jQuery(templates.listItem({
|
||||||
tag: tag,
|
tag: tag,
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
}));
|
}));
|
||||||
$target.append($item);
|
$target.append($item);
|
||||||
});
|
});
|
||||||
|
@ -66,9 +66,9 @@ App.Presenters.TagPresenter = function(
|
|||||||
api.get('tags/' + tagName + '/siblings'),
|
api.get('tags/' + tagName + '/siblings'),
|
||||||
api.get('posts', {query: tagName}))
|
api.get('posts', {query: tagName}))
|
||||||
.then(function(tagResponse, siblingsResponse, postsResponse) {
|
.then(function(tagResponse, siblingsResponse, postsResponse) {
|
||||||
tag = tagResponse.json;
|
tag = tagResponse.json.tag;
|
||||||
siblings = siblingsResponse.json.data;
|
siblings = siblingsResponse.json.tags;
|
||||||
posts = postsResponse.json.data;
|
posts = postsResponse.json.posts;
|
||||||
posts = posts.slice(0, 8);
|
posts = posts.slice(0, 8);
|
||||||
|
|
||||||
render();
|
render();
|
||||||
@ -81,14 +81,22 @@ App.Presenters.TagPresenter = function(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTagCategories() {
|
||||||
|
var tagCategories = JSON.parse(jQuery('head').attr('data-tag-categories'));
|
||||||
|
var result = {};
|
||||||
|
jQuery.each(tagCategories, function(i, item) {
|
||||||
|
result[item[0]] = item[1];
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$el.html(templates.tag({
|
$el.html(templates.tag({
|
||||||
privileges: privileges,
|
privileges: privileges,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
siblings: siblings,
|
siblings: siblings,
|
||||||
tagCategories: JSON.parse(jQuery('head').attr('data-tag-categories')),
|
tagCategories: getTagCategories(),
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
historyTemplate: templates.history,
|
historyTemplate: templates.history,
|
||||||
}));
|
}));
|
||||||
$el.find('.post-list').hide();
|
$el.find('.post-list').hide();
|
||||||
@ -127,7 +135,7 @@ App.Presenters.TagPresenter = function(
|
|||||||
|
|
||||||
promise.wait(api.put('/tags/' + tag.name, formData))
|
promise.wait(api.put('/tags/' + tag.name, formData))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
router.navigateInplace('#/tag/' + response.json.name);
|
router.navigateInplace('#/tag/' + response.json.tag.name);
|
||||||
tagList.refreshTags();
|
tagList.refreshTags();
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
window.alert(response.json && response.json.error || 'An error occured.');
|
window.alert(response.json && response.json.error || 'An error occured.');
|
||||||
|
@ -58,7 +58,9 @@ App.Presenters.UserAccountRemovalPresenter = function(
|
|||||||
}
|
}
|
||||||
promise.wait(api.delete('/users/' + user.name))
|
promise.wait(api.delete('/users/' + user.name))
|
||||||
.then(function() {
|
.then(function() {
|
||||||
|
if (user.name === auth.getCurrentUser().name) {
|
||||||
auth.logout();
|
auth.logout();
|
||||||
|
}
|
||||||
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');
|
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');
|
||||||
$messageDiv.find('a').click(mainPageLinkClicked);
|
$messageDiv.find('a').click(mainPageLinkClicked);
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
|
@ -133,7 +133,7 @@ App.Presenters.UserAccountSettingsPresenter = function(
|
|||||||
|
|
||||||
function editSuccess(apiResponse) {
|
function editSuccess(apiResponse) {
|
||||||
var wasLoggedIn = auth.isLoggedIn(user.name);
|
var wasLoggedIn = auth.isLoggedIn(user.name);
|
||||||
user = apiResponse.json;
|
user = apiResponse.json.user;
|
||||||
if (wasLoggedIn) {
|
if (wasLoggedIn) {
|
||||||
auth.updateCurrentUser(user);
|
auth.updateCurrentUser(user);
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ App.Presenters.UserAccountSettingsPresenter = function(
|
|||||||
|
|
||||||
var $messages = jQuery(target).find('.messages');
|
var $messages = jQuery(target).find('.messages');
|
||||||
var message = 'Account settings updated!';
|
var message = 'Account settings updated!';
|
||||||
if (!apiResponse.json.confirmed) {
|
if (!apiResponse.json.user.confirmed) {
|
||||||
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
||||||
}
|
}
|
||||||
messagePresenter.showInfo($messages, message);
|
messagePresenter.showInfo($messages, message);
|
||||||
|
@ -51,6 +51,9 @@ App.Presenters.UserBrowsingSettingsPresenter = function(
|
|||||||
sketchy: $el.find('[name=listSketchyPosts]').is(':checked'),
|
sketchy: $el.find('[name=listSketchyPosts]').is(':checked'),
|
||||||
unsafe: $el.find('[name=listUnsafePosts]').is(':checked'),
|
unsafe: $el.find('[name=listUnsafePosts]').is(':checked'),
|
||||||
},
|
},
|
||||||
|
keyboardShortcuts: $el.find('[name=keyboardShortcuts]').is(':checked'),
|
||||||
|
fitMode: $el.find('[name=fitMode]:checked').val(),
|
||||||
|
upscale: $el.find('[name=upscale]').is(':checked'),
|
||||||
};
|
};
|
||||||
|
|
||||||
promise.wait(browsingSettings.setSettings(newSettings))
|
promise.wait(browsingSettings.setSettings(newSettings))
|
||||||
|
@ -35,8 +35,8 @@ App.Presenters.UserListPresenter = function(
|
|||||||
baseUri: '#/users',
|
baseUri: '#/users',
|
||||||
backendUri: '/users',
|
backendUri: '/users',
|
||||||
$target: $el.find('.pagination-target'),
|
$target: $el.find('.pagination-target'),
|
||||||
updateCallback: function($page, data) {
|
updateCallback: function($page, response) {
|
||||||
renderUsers($page, data.entities);
|
renderUsers($page, response.json.users);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
@ -76,8 +76,7 @@ App.Presenters.UserListPresenter = function(
|
|||||||
_.each(users, function(user) {
|
_.each(users, function(user) {
|
||||||
var $item = jQuery('<li>' + templates.listItem(_.extend({
|
var $item = jQuery('<li>' + templates.listItem(_.extend({
|
||||||
user: user,
|
user: user,
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
}, privileges)) + '</li>');
|
}, privileges)) + '</li>');
|
||||||
$target.append($item);
|
$target.append($item);
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ App.Presenters.UserPresenter = function(
|
|||||||
|
|
||||||
promise.wait(api.get('/users/' + userName))
|
promise.wait(api.get('/users/' + userName))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
user = response.json;
|
user = response.json.user;
|
||||||
var extendedContext = _.extend(params, {user: user});
|
var extendedContext = _.extend(params, {user: user});
|
||||||
|
|
||||||
presenterManager.initPresenters([
|
presenterManager.initPresenters([
|
||||||
@ -74,8 +74,7 @@ App.Presenters.UserPresenter = function(
|
|||||||
$el.html(templates.user({
|
$el.html(templates.user({
|
||||||
user: user,
|
user: user,
|
||||||
isLoggedIn: auth.isLoggedIn(user.name),
|
isLoggedIn: auth.isLoggedIn(user.name),
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: util.formatAbsoluteTime,
|
|
||||||
canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings,
|
canChangeBrowsingSettings: userBrowsingSettingsPresenter.getPrivileges().canChangeBrowsingSettings,
|
||||||
canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()),
|
canChangeAccountSettings: _.any(userAccountSettingsPresenter.getPrivileges()),
|
||||||
canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount}));
|
canDeleteAccount: userAccountRemovalPresenter.getPrivileges().canDeleteAccount}));
|
||||||
|
@ -2,21 +2,32 @@ var App = App || {};
|
|||||||
|
|
||||||
App.Promise = function(_, jQuery, progress) {
|
App.Promise = function(_, jQuery, progress) {
|
||||||
|
|
||||||
|
function BrokenPromiseError(promiseId) {
|
||||||
|
this.name = 'BrokenPromiseError';
|
||||||
|
this.message = 'Broken promise (promise ID: ' + promiseId + ')';
|
||||||
|
}
|
||||||
|
BrokenPromiseError.prototype = new Error();
|
||||||
|
|
||||||
var active = [];
|
var active = [];
|
||||||
var promiseId = 0;
|
var promiseId = 0;
|
||||||
|
|
||||||
function make(callback) {
|
function make(callback, useProgress) {
|
||||||
var deferred = jQuery.Deferred();
|
var deferred = jQuery.Deferred();
|
||||||
var promise = deferred.promise();
|
var promise = deferred.promise();
|
||||||
promise.promiseId = ++ promiseId;
|
promise.promiseId = ++ promiseId;
|
||||||
|
|
||||||
|
if (useProgress === true) {
|
||||||
progress.start();
|
progress.start();
|
||||||
|
}
|
||||||
callback(function() {
|
callback(function() {
|
||||||
try {
|
try {
|
||||||
deferred.resolve.apply(deferred, arguments);
|
deferred.resolve.apply(deferred, arguments);
|
||||||
active = _.without(active, promise.promiseId);
|
active = _.without(active, promise.promiseId);
|
||||||
progress.done();
|
progress.done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!(e instanceof BrokenPromiseError)) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
progress.reset();
|
progress.reset();
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
@ -25,6 +36,9 @@ App.Promise = function(_, jQuery, progress) {
|
|||||||
active = _.without(active, promise.promiseId);
|
active = _.without(active, promise.promiseId);
|
||||||
progress.done();
|
progress.done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!(e instanceof BrokenPromiseError)) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
progress.reset();
|
progress.reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -33,7 +47,7 @@ App.Promise = function(_, jQuery, progress) {
|
|||||||
|
|
||||||
promise.always(function() {
|
promise.always(function() {
|
||||||
if (!_.contains(active, promise.promiseId)) {
|
if (!_.contains(active, promise.promiseId)) {
|
||||||
throw new Error('Broken promise (promise ID: ' + promise.promiseId + ')');
|
throw new BrokenPromiseError(promise.promiseId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,7 +74,8 @@ App.Promise = function(_, jQuery, progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
make: make,
|
make: function(callback) { return make(callback, true); },
|
||||||
|
makeSilent: function(callback) { return make(callback, false); },
|
||||||
wait: wait,
|
wait: wait,
|
||||||
getActive: getActive,
|
getActive: getActive,
|
||||||
abortAll: abortAll,
|
abortAll: abortAll,
|
||||||
|
@ -93,7 +93,7 @@ App.Router = function(_, jQuery, promise, util, appState, presenterManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dispatch() {
|
function dispatch() {
|
||||||
var url = document.location.hash;
|
var url = decodeURI(document.location.hash);
|
||||||
for (var i = 0; i < routes.length; i ++) {
|
for (var i = 0; i < routes.length; i ++) {
|
||||||
var route = routes[i];
|
var route = routes[i];
|
||||||
if (route.match(url)) {
|
if (route.match(url)) {
|
||||||
|
@ -15,7 +15,7 @@ App.Services.PostsAroundCalculator = function(_, promise, util, pager) {
|
|||||||
pager.setPage(query.page);
|
pager.setPage(query.page);
|
||||||
promise.wait(pager.retrieveCached())
|
promise.wait(pager.retrieveCached())
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
var postIds = _.pluck(response.entities, 'id');
|
var postIds = _.pluck(response.json.posts, 'id');
|
||||||
var position = _.indexOf(postIds, postId);
|
var position = _.indexOf(postIds, postId);
|
||||||
|
|
||||||
if (position === -1) {
|
if (position === -1) {
|
||||||
@ -41,20 +41,28 @@ App.Services.PostsAroundCalculator = function(_, promise, util, pager) {
|
|||||||
if (position + direction >= 0 && position + direction < postIds.length) {
|
if (position + direction >= 0 && position + direction < postIds.length) {
|
||||||
var url = util.appendComplexRouteParam(
|
var url = util.appendComplexRouteParam(
|
||||||
'#/post/' + postIds[position + direction],
|
'#/post/' + postIds[position + direction],
|
||||||
_.extend({page: page}, pager.getSearchParams()));
|
util.simplifySearchQuery(
|
||||||
|
_.extend(
|
||||||
|
{page: page},
|
||||||
|
pager.getSearchParams())));
|
||||||
|
|
||||||
resolve(url);
|
resolve(url);
|
||||||
} else if (page + direction >= 1) {
|
} else if (page + direction >= 1) {
|
||||||
pager.setPage(page + direction);
|
pager.setPage(page + direction);
|
||||||
promise.wait(pager.retrieveCached())
|
promise.wait(pager.retrieveCached())
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
if (response.entities.length) {
|
if (response.json.posts.length) {
|
||||||
var post = direction === - 1 ?
|
var post = direction === - 1 ?
|
||||||
_.last(response.entities) :
|
_.last(response.json.posts) :
|
||||||
_.first(response.entities);
|
_.first(response.json.posts);
|
||||||
|
|
||||||
var url = util.appendComplexRouteParam(
|
var url = util.appendComplexRouteParam(
|
||||||
'#/post/' + post.id,
|
'#/post/' + post.id,
|
||||||
_.extend({page: page + direction}, pager.getSearchParams()));
|
util.simplifySearchQuery(
|
||||||
|
_.extend(
|
||||||
|
{page: page + direction},
|
||||||
|
pager.getSearchParams())));
|
||||||
|
|
||||||
resolve(url);
|
resolve(url);
|
||||||
} else {
|
} else {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
|
@ -2,75 +2,139 @@ var App = App || {};
|
|||||||
App.Util = App.Util || {};
|
App.Util = App.Util || {};
|
||||||
|
|
||||||
App.Util.Draggable = function(jQuery) {
|
App.Util.Draggable = function(jQuery) {
|
||||||
|
var KEY_LEFT = 37;
|
||||||
|
var KEY_UP = 38;
|
||||||
|
var KEY_RIGHT = 39;
|
||||||
|
var KEY_DOWN = 40;
|
||||||
|
|
||||||
function relativeDragStrategy($element) {
|
function relativeDragStrategy($element) {
|
||||||
var $parent = $element.parent();
|
var $parent = $element.parent();
|
||||||
var delta;
|
var delta;
|
||||||
|
var x = $element.offset().left - $parent.offset().left;
|
||||||
|
var y = $element.offset().top - $parent.offset().top;
|
||||||
|
|
||||||
|
var getPosition = function() {
|
||||||
|
return {x: x, y: y};
|
||||||
|
};
|
||||||
|
|
||||||
|
var setPosition = function(newX, newY) {
|
||||||
|
x = newX;
|
||||||
|
y = newY;
|
||||||
|
var screenX = Math.min(Math.max(newX, 0), $parent.outerWidth() - $element.outerWidth());
|
||||||
|
var screenY = Math.min(Math.max(newY, 0), $parent.outerHeight() - $element.outerHeight());
|
||||||
|
screenX *= 100.0 / $parent.outerWidth();
|
||||||
|
screenY *= 100.0 / $parent.outerHeight();
|
||||||
|
$element.css({
|
||||||
|
left: screenX + '%',
|
||||||
|
top: screenY + '%'});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
click: function(e) {
|
mouseClicked: function(e) {
|
||||||
delta = {
|
delta = {
|
||||||
x: $element.offset().left - e.clientX,
|
x: $element.offset().left - e.clientX,
|
||||||
y: $element.offset().top - e.clientY,
|
y: $element.offset().top - e.clientY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function(e) {
|
mouseMoved: function(e) {
|
||||||
var x = e.clientX + delta.x - $parent.offset().left;
|
setPosition(
|
||||||
var y = e.clientY + delta.y - $parent.offset().top;
|
e.clientX + delta.x - $parent.offset().left,
|
||||||
x = Math.min(Math.max(x, 0), $parent.outerWidth() - $element.outerWidth());
|
e.clientY + delta.y - $parent.offset().top);
|
||||||
y = Math.min(Math.max(y, 0), $parent.outerHeight() - $element.outerHeight());
|
|
||||||
x *= 100.0 / $parent.outerWidth();
|
|
||||||
y *= 100.0 / $parent.outerHeight();
|
|
||||||
$element.css({
|
|
||||||
left: x + '%',
|
|
||||||
top: y + '%'});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPosition: getPosition,
|
||||||
|
setPosition: setPosition,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function absoluteDragStrategy($element) {
|
function absoluteDragStrategy($element) {
|
||||||
var delta;
|
var delta;
|
||||||
|
var x = $element.offset().left;
|
||||||
|
var y = $element.offset().top;
|
||||||
|
|
||||||
|
var getPosition = function() {
|
||||||
|
return {x: x, y: y};
|
||||||
|
};
|
||||||
|
|
||||||
|
var setPosition = function(newX, newY) {
|
||||||
|
x = newX;
|
||||||
|
y = newY;
|
||||||
|
$element.css({
|
||||||
|
left: x + 'px',
|
||||||
|
top: y + 'px'});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
click: function(e) {
|
mouseClicked: function(e) {
|
||||||
delta = {
|
delta = {
|
||||||
x: $element.position().left - e.clientX,
|
x: $element.position().left - e.clientX,
|
||||||
y: $element.position().top - e.clientY,
|
y: $element.position().top - e.clientY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function(e) {
|
mouseMoved: function(e) {
|
||||||
var x = e.clientX + delta.x;
|
setPosition(e.clientX + delta.x, e.clientY + delta.y);
|
||||||
var y = e.clientY + delta.y;
|
|
||||||
$element.css({
|
|
||||||
left: x + 'px',
|
|
||||||
top: y + 'px'});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPosition: getPosition,
|
||||||
|
setPosition: setPosition,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeDraggable($element, dragStrategy) {
|
function makeDraggable($element, dragStrategy, enableHotkeys) {
|
||||||
var strategy = dragStrategy($element);
|
var strategy = dragStrategy($element);
|
||||||
|
$element.data('drag-strategy', strategy);
|
||||||
|
|
||||||
$element.addClass('draggable');
|
$element.addClass('draggable');
|
||||||
|
|
||||||
$element.mousedown(function(e) {
|
$element.mousedown(function(e) {
|
||||||
if (e.target !== $element.get(0)) {
|
if (e.target !== $element.get(0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
$element.focus();
|
||||||
$element.addClass('dragging');
|
$element.addClass('dragging');
|
||||||
|
|
||||||
strategy.click(e);
|
strategy.mouseClicked(e);
|
||||||
jQuery(window).bind('mousemove.elemmove', function(e) {
|
jQuery(window).bind('mousemove.elemmove', function(e) {
|
||||||
strategy.update(e);
|
strategy.mouseMoved(e);
|
||||||
}).bind('mouseup.elemmove', function(e) {
|
}).bind('mouseup.elemmove', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
strategy.update(e);
|
strategy.mouseMoved(e);
|
||||||
$element.removeClass('dragging');
|
$element.removeClass('dragging');
|
||||||
jQuery(window).unbind('mousemove.elemmove');
|
jQuery(window).unbind('mousemove.elemmove');
|
||||||
jQuery(window).unbind('mouseup.elemmove');
|
jQuery(window).unbind('mouseup.elemmove');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (enableHotkeys) {
|
||||||
|
$element.keydown(function(e) {
|
||||||
|
var position = strategy.getPosition();
|
||||||
|
var oldPosition = {x: position.x, y: position.y};
|
||||||
|
if (e.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delta = e.ctrlKey ? 10 : 1;
|
||||||
|
if (e.which === KEY_LEFT) {
|
||||||
|
position.x -= delta;
|
||||||
|
} else if (e.which === KEY_RIGHT) {
|
||||||
|
position.x += delta;
|
||||||
|
} else if (e.which === KEY_UP) {
|
||||||
|
position.y -= delta;
|
||||||
|
} else if (e.which === KEY_DOWN) {
|
||||||
|
position.y += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.x !== oldPosition.x || position.y !== oldPosition.y) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
strategy.setPosition(position.x, position.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -193,11 +193,23 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
|
|||||||
smartypants: true,
|
smartypants: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var sjis = [];
|
||||||
|
|
||||||
var preDecorator = function(text) {
|
var preDecorator = function(text) {
|
||||||
|
text = text.replace(/\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/ig, function(match, capture) {
|
||||||
|
var ret = '%%%SJIS' + sjis.length;
|
||||||
|
sjis.push(capture);
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
//prevent ^#... from being treated as headers, due to tag permalinks
|
//prevent ^#... from being treated as headers, due to tag permalinks
|
||||||
text = text.replace(/^#/g, '%%%#');
|
text = text.replace(/^#/g, '%%%#');
|
||||||
//fix \ before ~ being stripped away
|
//fix \ before ~ being stripped away
|
||||||
text = text.replace(/\\~/g, '%%%T');
|
text = text.replace(/\\~/g, '%%%T');
|
||||||
|
//post, user and tags premalinks
|
||||||
|
text = text.replace(/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g, '$1[$2]($2)');
|
||||||
|
text = text.replace(/\]\(@(\d+)\)/g, '](#/post/$1)');
|
||||||
|
text = text.replace(/\]\(\+([a-zA-Z0-9_-]+)\)/g, '](#/user/$1)');
|
||||||
|
text = text.replace(/\]\(#([a-zA-Z0-9_-]+)\)/g, '](#/posts/query=$1)');
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,6 +218,8 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
|
|||||||
text = text.replace(/%%%T/g, '\\~');
|
text = text.replace(/%%%T/g, '\\~');
|
||||||
text = text.replace(/%%%#/g, '#');
|
text = text.replace(/%%%#/g, '#');
|
||||||
|
|
||||||
|
text = text.replace(/%%%SJIS(\d+)/, function(match, capture) { return '<div class="sjis">' + sjis[capture] + '</div>'; });
|
||||||
|
|
||||||
//search permalinks
|
//search permalinks
|
||||||
text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>');
|
text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>');
|
||||||
//spoilers
|
//spoilers
|
||||||
@ -215,12 +229,6 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
|
|||||||
//strike-through
|
//strike-through
|
||||||
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
|
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
|
||||||
text = text.replace(/\\~/g, '~');
|
text = text.replace(/\\~/g, '~');
|
||||||
//post premalinks
|
|
||||||
text = text.replace(/(^|[\s<>\(\)\[\]])@(\d+)/g, '$1<a href="#/post/$2"><code>@$2</code></a>');
|
|
||||||
//user permalinks
|
|
||||||
text = text.replace(/(^|[\s<>\(\)\[\]])\+([a-zA-Z0-9_-]+)/g, '$1<a href="#/user/$2"><code>+$2</code></a>');
|
|
||||||
//tag permalinks
|
|
||||||
text = text.replace(/(^|[\s<>\(\)\[\]])\#([^\s<>/\\]+)/g, '$1<a href="#/posts/query=$2"><code>#$2</code></a>');
|
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -237,6 +245,17 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
|
|||||||
return result.slice(0, -1);
|
return result.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function simplifySearchQuery(query) {
|
||||||
|
if (typeof(query) === 'undefined') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (query.page === 1) {
|
||||||
|
delete query.page;
|
||||||
|
}
|
||||||
|
query = _.pick(query, _.identity); //remove falsy values
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
promiseTemplate: promiseTemplate,
|
promiseTemplate: promiseTemplate,
|
||||||
formatRelativeTime: formatRelativeTime,
|
formatRelativeTime: formatRelativeTime,
|
||||||
@ -249,6 +268,7 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
|
|||||||
transparentPixel: transparentPixel,
|
transparentPixel: transparentPixel,
|
||||||
loadImagesNicely: loadImagesNicely,
|
loadImagesNicely: loadImagesNicely,
|
||||||
appendComplexRouteParam: appendComplexRouteParam,
|
appendComplexRouteParam: appendComplexRouteParam,
|
||||||
|
simplifySearchQuery: simplifySearchQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -2,41 +2,103 @@ var App = App || {};
|
|||||||
App.Util = App.Util || {};
|
App.Util = App.Util || {};
|
||||||
|
|
||||||
App.Util.Resizable = function(jQuery) {
|
App.Util.Resizable = function(jQuery) {
|
||||||
function makeResizable($element) {
|
var KEY_LEFT = 37;
|
||||||
|
var KEY_UP = 38;
|
||||||
|
var KEY_RIGHT = 39;
|
||||||
|
var KEY_DOWN = 40;
|
||||||
|
|
||||||
|
function relativeResizeStrategy($element) {
|
||||||
|
var $parent = $element.parent();
|
||||||
|
var delta;
|
||||||
|
var width = $element.width();
|
||||||
|
var height = $element.height();
|
||||||
|
|
||||||
|
var getSize = function() {
|
||||||
|
return {width: width, height: height};
|
||||||
|
};
|
||||||
|
|
||||||
|
var setSize = function(newWidth, newHeight) {
|
||||||
|
width = newWidth;
|
||||||
|
height = newHeight;
|
||||||
|
var screenWidth = Math.min(Math.max(width, 20), $parent.outerWidth() + $parent.offset().left - $element.offset().left);
|
||||||
|
var screenHeight = Math.min(Math.max(height, 20), $parent.outerHeight() + $parent.offset().top - $element.offset().top);
|
||||||
|
screenWidth *= 100.0 / $parent.outerWidth();
|
||||||
|
screenHeight *= 100.0 / $parent.outerHeight();
|
||||||
|
$element.css({
|
||||||
|
width: screenWidth + '%',
|
||||||
|
height: screenHeight + '%'});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
mouseClicked: function(e) {
|
||||||
|
delta = {
|
||||||
|
x: $element.width() - e.clientX,
|
||||||
|
y: $element.height() - e.clientY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mouseMoved: function(e) {
|
||||||
|
setSize(
|
||||||
|
e.clientX + delta.x,
|
||||||
|
e.clientY + delta.y);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSize: getSize,
|
||||||
|
setSize: setSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeResizable($element, enableHotkeys) {
|
||||||
var $resizer = jQuery('<div class="resizer"></div>');
|
var $resizer = jQuery('<div class="resizer"></div>');
|
||||||
|
var strategy = relativeResizeStrategy($element);
|
||||||
$element.append($resizer);
|
$element.append($resizer);
|
||||||
|
|
||||||
$resizer.mousedown(function(e) {
|
$resizer.mousedown(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
$element.focus();
|
||||||
$element.addClass('resizing');
|
$element.addClass('resizing');
|
||||||
|
|
||||||
var $parent = $element.parent();
|
strategy.mouseClicked(e);
|
||||||
var deltaX = $element.width() - e.clientX;
|
|
||||||
var deltaY = $element.height() - e.clientY;
|
|
||||||
|
|
||||||
var update = function(e) {
|
|
||||||
var w = e.clientX + deltaX;
|
|
||||||
var h = e.clientY + deltaY;
|
|
||||||
w = Math.min(Math.max(w, 20), $parent.outerWidth() + $parent.offset().left - $element.offset().left);
|
|
||||||
h = Math.min(Math.max(h, 20), $parent.outerHeight() + $parent.offset().top - $element.offset().top);
|
|
||||||
w *= 100.0 / $parent.outerWidth();
|
|
||||||
h *= 100.0 / $parent.outerHeight();
|
|
||||||
$element.css({
|
|
||||||
width: w + '%',
|
|
||||||
height: h + '%'});
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery(window).bind('mousemove.elemsize', function(e) {
|
jQuery(window).bind('mousemove.elemsize', function(e) {
|
||||||
update(e);
|
strategy.mouseMoved(e);
|
||||||
}).bind('mouseup.elemsize', function(e) {
|
}).bind('mouseup.elemsize', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
update(e);
|
strategy.mouseMoved(e);
|
||||||
$element.removeClass('resizing');
|
$element.removeClass('resizing');
|
||||||
jQuery(window).unbind('mousemove.elemsize');
|
jQuery(window).unbind('mousemove.elemsize');
|
||||||
jQuery(window).unbind('mouseup.elemsize');
|
jQuery(window).unbind('mouseup.elemsize');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (enableHotkeys) {
|
||||||
|
$element.keydown(function(e) {
|
||||||
|
var size = strategy.getSize();
|
||||||
|
var oldSize = {width: size.width, height: size.height};
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delta = e.ctrlKey ? 10 : 1;
|
||||||
|
if (e.which === KEY_LEFT) {
|
||||||
|
size.width -= delta;
|
||||||
|
} else if (e.which === KEY_RIGHT) {
|
||||||
|
size.width += delta;
|
||||||
|
} else if (e.which === KEY_UP) {
|
||||||
|
size.height -= delta;
|
||||||
|
} else if (e.which === KEY_DOWN) {
|
||||||
|
size.height += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size.width !== oldSize.width || size.height !== oldSize.height) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
strategy.setSize(size.width, size.height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -41,6 +41,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label" for="browsing-settings-keyboard-shortcuts">Keyboard shortcuts:</label>
|
||||||
|
<div class="form-input">
|
||||||
|
<input <% print(settings.keyboardShortcuts ? 'checked="checked"' : '') %> type="checkbox" id="browsing-settings-keyboard-shortcuts" name="keyboardShortcuts"/>
|
||||||
|
<label for="browsing-settings-keyboard-shortcuts">
|
||||||
|
Enabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label">Default fit mode:</label>
|
||||||
|
<div class="form-input">
|
||||||
|
<input <% print(settings.fitMode === 'fit-width' ? 'checked="checked"' : '') %> type="radio" id="browsing-settings-fit-width" name="fitMode" value="fit-width"/>
|
||||||
|
<label for="browsing-settings-fit-width">
|
||||||
|
Fit to window width
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input <% print(settings.fitMode === 'fit-height' ? 'checked="checked"' : '') %> type="radio" id="browsing-settings-fit-height" name="fitMode" value="fit-height"/>
|
||||||
|
<label for="browsing-settings-fit-height">
|
||||||
|
Fit to window height
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input <% print(settings.fitMode === 'fit-both' ? 'checked="checked"' : '') %> type="radio" id="browsing-settings-fit-both" name="fitMode" value="fit-both"/>
|
||||||
|
<label for="browsing-settings-fit-both">
|
||||||
|
Fit to both width and height
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input <% print(settings.fitMode === 'original' ? 'checked="checked"' : '') %> type="radio" id="browsing-settings-fit-original" name="fitMode" value="original"/>
|
||||||
|
<label for="browsing-settings-fit-original">
|
||||||
|
Show at original size
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input <% print(settings.upscale ? 'checked="checked"' : '') %> type="checkbox" id="browsing-settings-upscale" name="upscale" value="upscale"/>
|
||||||
|
<label for="browsing-settings-upscale">
|
||||||
|
Upscale small posts
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label"></label>
|
<label class="form-label"></label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
@ -48,5 +92,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="date" title="<%= formatAbsoluteTime(comment.creationTime) %>">
|
<span class="date" title="<%= util.formatAbsoluteTime(comment.creationTime) %>">
|
||||||
<%= formatRelativeTime(comment.creationTime) %>
|
<%= util.formatRelativeTime(comment.creationTime) %>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="score">
|
<span class="score">
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<%= formatMarkdown(comment.text) %>
|
<%= util.formatMarkdown(comment.text) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,10 +60,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>[A]</code> and <code>[D]</code></td>
|
<td><code>[A]</code> and <code>[D]</code><br/><code>[Left]</code> and <code>[Right]</code> arrow keys</td>
|
||||||
<td>Go to newer/older page or post</td>
|
<td>Go to newer/older page or post</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>[F]</code></td>
|
||||||
|
<td>Cycle post fit mode</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>[E]</code></td>
|
<td><code>[E]</code></td>
|
||||||
<td>Edit post</td>
|
<td>Edit post</td>
|
||||||
@ -109,6 +114,8 @@
|
|||||||
{search: 'comment_count:3', description: 'having exactly three comments'},
|
{search: 'comment_count:3', description: 'having exactly three comments'},
|
||||||
{search: 'score:4', description: 'having score of 4'},
|
{search: 'score:4', description: 'having score of 4'},
|
||||||
{search: 'tag_count:7', description: 'tagged with exactly seven tags'},
|
{search: 'tag_count:7', description: 'tagged with exactly seven tags'},
|
||||||
|
{search: 'note_count:1..', description: 'having at least one post note'},
|
||||||
|
{search: 'feature_count:1..', description: 'having been featured at least once'},
|
||||||
{search: 'date:today', description: 'posted today'},
|
{search: 'date:today', description: 'posted today'},
|
||||||
{search: 'date:yesterday', description: 'posted yesterday'},
|
{search: 'date:yesterday', description: 'posted yesterday'},
|
||||||
{search: 'date:2000', description: 'posted in year 2000'},
|
{search: 'date:2000', description: 'posted in year 2000'},
|
||||||
@ -116,6 +123,10 @@
|
|||||||
{search: 'date:2000-01-01', description: 'posted on January 1st, 2000'},
|
{search: 'date:2000-01-01', description: 'posted on January 1st, 2000'},
|
||||||
{search: 'id:1', description: 'having specific post ID'},
|
{search: 'id:1', description: 'having specific post ID'},
|
||||||
{search: 'name:<em>hash</em>', description: 'having specific post name (hash in full URLs)'},
|
{search: 'name:<em>hash</em>', description: 'having specific post name (hash in full URLs)'},
|
||||||
|
{search: 'file_size:100..', description: 'having at least 100 bytes'},
|
||||||
|
{search: 'image_width:100..', description: 'being at least 100 pixels wide'},
|
||||||
|
{search: 'image_height:100..', description: 'being at least 100 pixels tall'},
|
||||||
|
{search: 'image_area:10000..', description: 'having at least 10000 pixels'},
|
||||||
{search: 'type:image', description: 'only image posts'},
|
{search: 'type:image', description: 'only image posts'},
|
||||||
{search: 'type:flash', description: 'only Flash posts'},
|
{search: 'type:flash', description: 'only Flash posts'},
|
||||||
{search: 'type:youtube', description: 'only Youtube posts'},
|
{search: 'type:youtube', description: 'only Youtube posts'},
|
||||||
@ -123,6 +134,7 @@
|
|||||||
{search: 'special:liked', description: 'posts liked by currently logged in user'},
|
{search: 'special:liked', description: 'posts liked by currently logged in user'},
|
||||||
{search: 'special:disliked', description: 'posts disliked by currently logged in user'},
|
{search: 'special:disliked', description: 'posts disliked by currently logged in user'},
|
||||||
{search: 'special:fav', description: 'posts added to favorites by currently logged in user'},
|
{search: 'special:fav', description: 'posts added to favorites by currently logged in user'},
|
||||||
|
{search: 'special:tumbleweed', description: 'posts with score of 0, without comments and without favorites'},
|
||||||
];
|
];
|
||||||
_.each(table, function(row) { %>
|
_.each(table, function(row) { %>
|
||||||
<tr>
|
<tr>
|
||||||
@ -159,17 +171,22 @@
|
|||||||
var table = [
|
var table = [
|
||||||
{search: 'order:random', description: 'as random as it can get'},
|
{search: 'order:random', description: 'as random as it can get'},
|
||||||
{search: 'order:id', description: 'highest to lowest post ID (default browse view)'},
|
{search: 'order:id', description: 'highest to lowest post ID (default browse view)'},
|
||||||
{search: 'order:date', description: 'newest to oldest (pretty much same as above)'},
|
{search: 'order:creation_date', description: 'newest to oldest (pretty much same as above)'},
|
||||||
{search: '-order:date', description: 'oldest to newest'},
|
{search: '-order:creation_date', description: 'oldest to newest'},
|
||||||
{search: 'order:date,asc', description: 'oldest to newest (ascending order, default = descending)'},
|
{search: 'order:creation_date,asc', description: 'oldest to newest (ascending order, default = descending)'},
|
||||||
|
{search: 'order:edit_date', description: 'like <code>creation_date</code>, only looks at last edit time'},
|
||||||
{search: 'order:score', description: 'highest scored'},
|
{search: 'order:score', description: 'highest scored'},
|
||||||
{search: 'order:file_size', description: 'largest files first'},
|
{search: 'order:file_size', description: 'largest files first'},
|
||||||
|
{search: 'order:image_width', description: 'widest images first'},
|
||||||
|
{search: 'order:image_height', description: 'tallest images first'},
|
||||||
|
{search: 'order:image_area', description: 'largest images first'},
|
||||||
{search: 'order:tag_count', description: 'with most tags'},
|
{search: 'order:tag_count', description: 'with most tags'},
|
||||||
{search: 'order:fav_count', description: 'loved by most'},
|
{search: 'order:fav_count', description: 'loved by most'},
|
||||||
{search: 'order:comment_count', description: 'most commented first'},
|
{search: 'order:comment_count', description: 'most commented first'},
|
||||||
{search: 'order:fav_date', description: 'recently added to favorites'},
|
{search: 'order:fav_date', description: 'recently added to favorites'},
|
||||||
{search: 'order:comment_date', description: 'recently commented'},
|
{search: 'order:comment_date', description: 'recently commented'},
|
||||||
{search: 'order:feature_date', description: 'recently featured'},
|
{search: 'order:feature_date', description: 'recently featured'},
|
||||||
|
{search: 'order:feature_count', description: 'most often featured'},
|
||||||
];
|
];
|
||||||
_.each(table, function(row) { %>
|
_.each(table, function(row) { %>
|
||||||
<tr>
|
<tr>
|
||||||
@ -181,9 +198,9 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p>As shown with <a
|
<p>As shown with <a
|
||||||
href="#/posts/query=-order:date"><code>-order:date</code></a>, any of them
|
href="#/posts/query=-order:creation_date"><code>-order:creation_date</code></a>,
|
||||||
can be reversed in the same way as negating other tags: by placing a dash
|
any of them can be reversed in the same way as negating other tags: by
|
||||||
before the tag.</p>
|
placing a dash before the tag.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tab="comments">
|
<div data-tab="comments">
|
||||||
@ -212,6 +229,10 @@
|
|||||||
<td><code>[spoiler]Lelouch survives[/spoiler]</td>
|
<td><code>[spoiler]Lelouch survives[/spoiler]</td>
|
||||||
<td>marks text as spoiler and hides it</td>
|
<td>marks text as spoiler and hides it</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>[sjis](´・ω・`)[/sjis]</td>
|
||||||
|
<td>adds SJIS art</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,8 +22,8 @@ var showDifference = function(className, difference) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<% _.each(history, function( historyEntry) { %>
|
<% _.each(history, function( historyEntry) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="time" title="<%= formatAbsoluteTime(historyEntry.time) %>">
|
<td class="time" title="<%= util.formatAbsoluteTime(historyEntry.time) %>">
|
||||||
<%= formatRelativeTime(historyEntry.time) %>
|
<%= util.formatRelativeTime(historyEntry.time) %>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="user">
|
<td class="user">
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
|
<% function showUser(name) { %>
|
||||||
|
<% var showLink = typeof(canViewUsers) !== 'undefined' && canViewUsers && name %>
|
||||||
|
|
||||||
|
<% if (showLink) { %>
|
||||||
|
<a href="#/user/<%= name %>">
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<img width="25" height="25" class="author-avatar"
|
||||||
|
src="/data/thumbnails/25x25/avatars/<%= name || '!' %>"
|
||||||
|
alt="<%= name || 'Anonymous user' %>"/>
|
||||||
|
|
||||||
|
<%= name || 'Anonymous user' %>
|
||||||
|
|
||||||
|
<% if (showLink) { %>
|
||||||
|
</a>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<div id="home">
|
<div id="home">
|
||||||
<h1><%= title %></h1>
|
<h1><%= title %></h1>
|
||||||
<p class="subheader">
|
<p class="subheader">
|
||||||
Serving <%= globals.postCount || 0 %> posts (<%= formatFileSize(globals.postSize || 0) %>)
|
Serving <%= globals.postCount || 0 %> posts (<%= util.formatFileSize(globals.postSize || 0) %>)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if (post && post.id) { %>
|
<% if (post && post.id) { %>
|
||||||
<div class="post">
|
<div class="post" style="width: <%= post.imageWidth || 800 %>px">
|
||||||
<div id="post-content-target">
|
<div id="post-content-target">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -25,29 +43,16 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
uploaded
|
uploaded
|
||||||
<%= formatRelativeTime(post.uploadTime) %>
|
<%= util.formatRelativeTime(post.creationTime) %>
|
||||||
|
by
|
||||||
|
<% showUser(post.user.name) %>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="right">
|
<span class="right">
|
||||||
featured
|
featured
|
||||||
<%= formatRelativeTime(post.lastFeatureTime) %>
|
<%= util.formatRelativeTime(post.lastFeatureTime) %>
|
||||||
by
|
by
|
||||||
|
<% showUser(user.name) %>
|
||||||
<% var showLink = canViewUsers && user.name %>
|
|
||||||
|
|
||||||
<% if (showLink) { %>
|
|
||||||
<a href="#/user/<%= user.name %>">
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<img width="25" height="25" class="author-avatar"
|
|
||||||
src="/data/thumbnails/25x25/avatars/<%= user.name || '!' %>"
|
|
||||||
alt="<%= user.name || 'Anonymous user' %>"/>
|
|
||||||
|
|
||||||
<%= user.name || 'Anonymous user' %>
|
|
||||||
|
|
||||||
<% if (showLink) { %>
|
|
||||||
</a>
|
|
||||||
<% } %>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +61,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<small class="version">
|
<small class="version">
|
||||||
Version: <a href="//github.com/rr-/szurubooru/commits/master"><%= version %></a> (built <%= formatRelativeTime(buildTime) %>)
|
Version: <a href="//github.com/rr-/szurubooru/commits/master"><%= version %></a> (built <%= util.formatRelativeTime(buildTime) %>)
|
||||||
|
|
|
|
||||||
<a href="#/history">Recent tag and post edits</a>
|
<a href="#/history">Recent tag and post edits</a>
|
||||||
</small>
|
</small>
|
||||||
|
@ -2,4 +2,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="page-list">
|
<ul class="page-list">
|
||||||
|
<li class="prev"><a href="#">Prev</a></li>
|
||||||
|
<li class="next"><a href="#">Next</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
<% var postContentUrl = '/data/posts/' + post.name + '?x=' + Math.random() /* reset gif animations */ %>
|
<%
|
||||||
|
var postContentUrl = '/data/posts/' + post.name;
|
||||||
|
var width;
|
||||||
|
var height;
|
||||||
|
if (post.contentType === 'image' || post.contentType === 'animation' || post.contentType === 'flash') {
|
||||||
|
width = post.imageWidth;
|
||||||
|
height = post.imageHeight;
|
||||||
|
}
|
||||||
|
if (!width) { width = 800; }
|
||||||
|
if (!height) { height = 450; }
|
||||||
|
%>
|
||||||
|
|
||||||
<div class="post-content post-type-<%= post.contentType %>">
|
<div class="post-content post-type-<%= post.contentType %>">
|
||||||
<div class="post-notes-target">
|
<div class="post-notes-target">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if (post.contentType === 'image') { %>
|
<div
|
||||||
|
class="object-wrapper"
|
||||||
|
data-width="<%= width %>"
|
||||||
|
data-height="<%= height %>"
|
||||||
|
style="max-width: <%= width %>px">
|
||||||
|
|
||||||
|
<% if (post.contentType === 'image' || post.contentType === 'animation') { %>
|
||||||
|
|
||||||
<div class="image-wrapper" style="width: <%= post.imageWidth %>px">
|
|
||||||
<img alt="<%= post.name %>" src="<%= postContentUrl %>"/>
|
<img alt="<%= post.name %>" src="<%= postContentUrl %>"/>
|
||||||
<div style="padding-top: calc(100% * <%= post.imageHeight %> / <%= post.imageWidth %>)"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% } else if (post.contentType === 'youtube') { %>
|
<% } else if (post.contentType === 'youtube') { %>
|
||||||
|
|
||||||
@ -19,14 +32,15 @@
|
|||||||
|
|
||||||
<object
|
<object
|
||||||
type="<%= post.contentMimeType %>"
|
type="<%= post.contentMimeType %>"
|
||||||
width="<%= post.imageWidth %>"
|
width="<%= width %>"
|
||||||
height="<%= post.imageHeight %>"
|
height="<%= height %>"
|
||||||
data="<%= postContentUrl %>">
|
data="<%= postContentUrl %>">
|
||||||
<param name="wmode" value="opaque"/>
|
<param name="wmode" value="opaque"/>
|
||||||
<param name="movie" value="<%= postContentUrl %>"/>
|
<param name="movie" value="<%= postContentUrl %>"/>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
<% } else if (post.contentType === 'video') { %>
|
<% } else if (post.contentType === 'video') { %>
|
||||||
|
|
||||||
<% if (post.flags.loop) { %>
|
<% if (post.flags.loop) { %>
|
||||||
<video id="video" controls loop="loop">
|
<video id="video" controls loop="loop">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
@ -40,4 +54,7 @@
|
|||||||
|
|
||||||
<% } else { console.log(new Error('Unknown post type')) } %>
|
<% } else { console.log(new Error('Unknown post type')) } %>
|
||||||
|
|
||||||
|
<div class="padding-fix" style="padding-bottom: calc(100% * <%= height %> / <%= width %>)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,8 +30,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<div class="form-row advanced-trigger">
|
||||||
|
<label></label>
|
||||||
|
<div class="form-input">
|
||||||
|
<a href="#">Advanced…</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% if (privileges.canChangeSource) { %>
|
<% if (privileges.canChangeSource) { %>
|
||||||
<div class="form-row">
|
<div class="form-row advanced">
|
||||||
<label class="form-label" for="post-source">Source:</label>
|
<label class="form-label" for="post-source">Source:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input maxlength="200" type="text" name="source" id="post-source" placeholder="Where did you get this? (optional)" value="<%= post.source %>"/>
|
<input maxlength="200" type="text" name="source" id="post-source" placeholder="Where did you get this? (optional)" value="<%= post.source %>"/>
|
||||||
@ -40,7 +47,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canChangeRelations) { %>
|
<% if (privileges.canChangeRelations) { %>
|
||||||
<div class="form-row">
|
<div class="form-row advanced">
|
||||||
<label class="form-label" for="post-relations">Relations:</label>
|
<label class="form-label" for="post-relations">Relations:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input maxlength="200" type="text" name="relations" id="post-relations" placeholder="Post ids, separated with space" value="<%= _.pluck(post.relations, 'id').join(' ') %>"/>
|
<input maxlength="200" type="text" name="relations" id="post-relations" placeholder="Post ids, separated with space" value="<%= _.pluck(post.relations, 'id').join(' ') %>"/>
|
||||||
@ -49,7 +56,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canChangeFlags && post.contentType === 'video') { %>
|
<% if (privileges.canChangeFlags && post.contentType === 'video') { %>
|
||||||
<div class="form-row">
|
<div class="form-row advanced">
|
||||||
<label class="form-label">Loop:</label>
|
<label class="form-label">Loop:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input type="checkbox" id="post-loop" name="loop" value="loop" <%= post.flags.loop ? 'checked="checked"' : '' %>/>
|
<input type="checkbox" id="post-loop" name="loop" value="loop" <%= post.flags.loop ? 'checked="checked"' : '' %>/>
|
||||||
@ -61,7 +68,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canChangeContent) { %>
|
<% if (privileges.canChangeContent) { %>
|
||||||
<div class="form-row">
|
<div class="form-row advanced">
|
||||||
<label class="form-label" for="post-content">Content:</label>
|
<label class="form-label" for="post-content">Content:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input type="file" id="post-content" name="content"/>
|
<input type="file" id="post-content" name="content"/>
|
||||||
@ -70,7 +77,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canChangeThumbnail) { %>
|
<% if (privileges.canChangeThumbnail) { %>
|
||||||
<div class="form-row">
|
<div class="form-row advanced">
|
||||||
<label class="form-label" for="post-thumbnail">Thumbnail:</label>
|
<label class="form-label" for="post-thumbnail">Thumbnail:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<input type="file" id="post-thumbnail" name="thumbnail"/>
|
<input type="file" id="post-thumbnail" name="thumbnail"/>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<% if (canViewPosts) { %>
|
<% if (canViewPosts) { %>
|
||||||
<a class="link"
|
<a class="link"
|
||||||
href="<%= util.appendComplexRouteParam('#/post/' + post.id, typeof(query) !== 'undefined' ? query : {}) %>"
|
href="<%= util.appendComplexRouteParam('#/post/' + post.id, util.simplifySearchQuery(typeof(query) !== 'undefined' ? query : {})) %>"
|
||||||
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>">
|
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>">
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<span class="link">
|
<span class="link">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="post-notes">
|
<div class="post-notes">
|
||||||
<% _.each(notes, function(note) { %>
|
<% _.each(notes, function(note) { %>
|
||||||
<div class="post-note"
|
<div tabindex="0" class="post-note"
|
||||||
style="left: <%= note.left %>%;
|
style="left: <%= note.left %>%;
|
||||||
top: <%= note.top %>%;
|
top: <%= note.top %>%;
|
||||||
width: <%= note.width %>%;
|
width: <%= note.width %>%;
|
||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<div class="text-wrapper">
|
<div class="text-wrapper">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<%= formatMarkdown(note.text) %>
|
<%= util.formatMarkdown(note.text) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -39,7 +39,9 @@
|
|||||||
<label></label>
|
<label></label>
|
||||||
</td>
|
</td>
|
||||||
<td class="thumbnail">
|
<td class="thumbnail">
|
||||||
|
<a href="#"/>
|
||||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="tags"></td>
|
<td class="tags"></td>
|
||||||
<td class="safety"><div class="safety-template"></div></td>
|
<td class="safety"><div class="safety-template"></div></td>
|
||||||
@ -52,10 +54,16 @@
|
|||||||
<button class="post-table-op remove"><i class="fa fa-remove"></i> Remove</button>
|
<button class="post-table-op remove"><i class="fa fa-remove"></i> Remove</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="post-table-op move-up"><i class="fa fa-chevron-up"></i> Move up</button>
|
<button class="post-table-op previous"><i class="fa fa-chevron-up"></i> Previous</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="post-table-op move-down"><i class="fa fa-chevron-down"></i> Move down</button>
|
<button class="post-table-op next"><i class="fa fa-chevron-down"></i> Next</button>
|
||||||
|
</li><!--
|
||||||
|
--><li>
|
||||||
|
<button class="post-table-op move-up"><i class="fa fa-arrow-up"></i> Move up</button>
|
||||||
|
</li><!--
|
||||||
|
--><li>
|
||||||
|
<button class="post-table-op move-down"><i class="fa fa-arrow-down"></i> Move down</button>
|
||||||
</li><!--
|
</li><!--
|
||||||
--><li>
|
--><li>
|
||||||
<button class="upload highlight" type="submit"><i class="fa fa-upload"></i> Submit</button>
|
<button class="upload highlight" type="submit"><i class="fa fa-upload"></i> Submit</button>
|
||||||
@ -73,6 +81,7 @@
|
|||||||
<div class="form-slider">
|
<div class="form-slider">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
||||||
|
<a href="#" target="_blank">Open preview in a new tab</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="form-wrapper">
|
<form class="form-wrapper">
|
||||||
@ -134,8 +143,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="lightbox">
|
|
||||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Preview">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
<% var permaLink = (window.location.origin + '/' + window.location.pathname + '/data/posts/' + post.name).replace(/([^:])\/+/g, '$1/') %>
|
<%
|
||||||
|
var permaLink = '';
|
||||||
|
permaLink += window.location.origin + '/';
|
||||||
|
permaLink += window.location.pathname + '/';
|
||||||
|
permaLink += 'data/posts/' + post.name;
|
||||||
|
permaLink = permaLink.replace(/([^:])\/+/g, '$1/');
|
||||||
|
if (forceHttpInPermalinks > 0) {
|
||||||
|
permaLink = permaLink.replace('https', 'http');
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
|
||||||
<div id="post-current-search-wrapper">
|
<div id="post-current-search-wrapper">
|
||||||
<div id="post-current-search">
|
<div id="post-current-search">
|
||||||
@ -10,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<a class="enabled" href="#/posts/query=<%= query.query %>;order=<%= query.order %>">
|
<a class="enabled" href="<%= util.appendComplexRouteParam('#/posts', util.simplifySearchQuery({query: query.query, order: query.order})) %>">
|
||||||
Current search: <%= query.query || '-' %>
|
Current search: <%= query.query || '-' %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +41,7 @@
|
|||||||
<a class="download" href="<%= permaLink %>">
|
<a class="download" href="<%= permaLink %>">
|
||||||
<i class="fa fa-download"></i>
|
<i class="fa fa-download"></i>
|
||||||
<br/>
|
<br/>
|
||||||
<%= post.contentExtension + ', ' + formatFileSize(post.originalFileSize) %>
|
<%= post.contentExtension + ', ' + util.formatFileSize(post.originalFileSize) %>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
@ -72,6 +81,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
<h1>Tags (<%= _.size(post.tags) %>)</h1>
|
<h1>Tags (<%= _.size(post.tags) %>)</h1>
|
||||||
<ul class="tags">
|
<ul class="tags">
|
||||||
<% _.each(post.tags, function(tag) { %>
|
<% _.each(post.tags, function(tag) { %>
|
||||||
@ -87,9 +97,10 @@
|
|||||||
</li>
|
</li>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
<h1>Details</h1>
|
<h1>Details</h1>
|
||||||
|
|
||||||
<div class="author-box">
|
<div class="author-box">
|
||||||
<% if (post.user.name) { %>
|
<% if (post.user.name) { %>
|
||||||
<a href="#/user/<%= post.user.name %>">
|
<a href="#/user/<%= post.user.name %>">
|
||||||
@ -109,13 +120,12 @@
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<span class="date" title="<%= formatAbsoluteTime(post.uploadTime) %>">
|
<span class="date" title="<%= util.formatAbsoluteTime(post.creationTime) %>">
|
||||||
<%= formatRelativeTime(post.uploadTime) %>
|
<%= util.formatRelativeTime(post.creationTime) %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="other-info">
|
<ul class="other-info">
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
Rating:
|
Rating:
|
||||||
<span class="safety-<%= post.safety %>">
|
<span class="safety-<%= post.safety %>">
|
||||||
@ -126,7 +136,7 @@
|
|||||||
<% if (post.originalFileSize) { %>
|
<% if (post.originalFileSize) { %>
|
||||||
<li>
|
<li>
|
||||||
File size:
|
File size:
|
||||||
<%= formatFileSize(post.originalFileSize) %>
|
<%= util.formatFileSize(post.originalFileSize) %>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
@ -137,11 +147,11 @@
|
|||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (post.lastEditTime !== post.uploadTime) { %>
|
<% if (post.lastEditTime !== post.creationTime) { %>
|
||||||
<li>
|
<li>
|
||||||
Edited:
|
Edited:
|
||||||
<span title="<%= formatAbsoluteTime(post.lastEditTime) %>">
|
<span title="<%= util.formatAbsoluteTime(post.lastEditTime) %>">
|
||||||
<%= formatRelativeTime(post.lastEditTime) %>
|
<%= util.formatRelativeTime(post.lastEditTime) %>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
@ -149,7 +159,7 @@
|
|||||||
<% if (post.featureCount > 0) { %>
|
<% if (post.featureCount > 0) { %>
|
||||||
<li>
|
<li>
|
||||||
Featured: <%= post.featureCount %> <%= post.featureCount < 2 ? 'time' : 'times' %>
|
Featured: <%= post.featureCount %> <%= post.featureCount < 2 ? 'time' : 'times' %>
|
||||||
<small>(<%= formatRelativeTime(post.lastFeatureTime) %>)</small>
|
<small>(<%= util.formatRelativeTime(post.lastFeatureTime) %>)</small>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
@ -187,8 +197,10 @@
|
|||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% if (_.any(post.relations)) { %>
|
<% if (_.any(post.relations)) { %>
|
||||||
|
<div class="box">
|
||||||
<h1>Related posts</h1>
|
<h1>Related posts</h1>
|
||||||
<ul class="related">
|
<ul class="related">
|
||||||
<% _.each(post.relations, function(relatedPost) { %>
|
<% _.each(post.relations, function(relatedPost) { %>
|
||||||
@ -199,11 +211,11 @@
|
|||||||
</li>
|
</li>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (_.any(privileges) || _.any(editPrivileges) || post.contentType === 'image') { %>
|
<div class="box">
|
||||||
<h1>Options</h1>
|
<h1>Options</h1>
|
||||||
|
|
||||||
<ul class="operations">
|
<ul class="operations">
|
||||||
<% if (_.any(editPrivileges)) { %>
|
<% if (_.any(editPrivileges)) { %>
|
||||||
<li>
|
<li>
|
||||||
@ -213,7 +225,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canAddPostNotes) { %>
|
<% if (privileges.canAddPostNotes && (post.contentType === 'image' || post.contentType === 'animation')) { %>
|
||||||
<li>
|
<li>
|
||||||
<a class="add-note" href="#">
|
<a class="add-note" href="#">
|
||||||
Add new note
|
Add new note
|
||||||
@ -245,7 +257,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (post.contentType === 'image') { %>
|
<% if (post.contentType === 'image' || post.contentType === 'animation') { %>
|
||||||
<li>
|
<li>
|
||||||
<a href="http://iqdb.org/?url=<%= permaLink %>">
|
<a href="http://iqdb.org/?url=<%= permaLink %>">
|
||||||
Search on IQDB
|
Search on IQDB
|
||||||
@ -258,9 +270,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
</ul>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
|
<li class="fit-mode">
|
||||||
|
Fit:
|
||||||
|
<a data-fit-mode="fit-width" href="#">width</a>,
|
||||||
|
<a data-fit-mode="fit-height" href="#">height</a>,
|
||||||
|
<a data-fit-mode="fit-both" href="#">both</a>,
|
||||||
|
<a data-fit-mode="original" href="#">original</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="post-view">
|
<div id="post-view">
|
||||||
@ -277,8 +296,7 @@
|
|||||||
<h1>History</h1>
|
<h1>History</h1>
|
||||||
<%= historyTemplate({
|
<%= historyTemplate({
|
||||||
history: postHistory,
|
history: postHistory,
|
||||||
formatRelativeTime: formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: formatAbsoluteTime,
|
|
||||||
}) %>
|
}) %>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label" for="tag-category">Category:</label>
|
<label class="form-label" for="tag-category">Category:</label>
|
||||||
<div class="form-input">
|
<div class="form-input">
|
||||||
<% _.each(_.extend({'default': 'default'}, _.object(tagCategories, tagCategories)), function(v, k) { %>
|
<% _.each(_.extend({'default': 'default'}, tagCategories), function(v, k) { %>
|
||||||
<input name="category" type="radio" value="<%= k %>" id="category-<%= k %>" <% print(tag.category === k ? 'checked="checked"' : '') %>>
|
<input name="category" type="radio" value="<%= k %>" id="category-<%= k %>" <% print(tag.category === k ? 'checked="checked"' : '') %>>
|
||||||
<label for="category-<%= k %>">
|
<label for="category-<%= k %>">
|
||||||
<% print(tag.category === k ? v + ' (current)' : v) %>
|
<% print(tag.category === k ? v + ' (current)' : v) %>
|
||||||
@ -103,8 +103,7 @@
|
|||||||
<h3>History</h3>
|
<h3>History</h3>
|
||||||
<%= historyTemplate({
|
<%= historyTemplate({
|
||||||
history: tag.history,
|
history: tag.history,
|
||||||
formatRelativeTime: formatRelativeTime,
|
util: util,
|
||||||
formatAbsoluteTime: formatAbsoluteTime,
|
|
||||||
}) %>
|
}) %>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
<%= user.name %>
|
<%= user.name %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="date-joined" title="<%= formatAbsoluteTime(user.registrationTime) %>">
|
<div class="date-joined" title="<%= util.formatAbsoluteTime(user.creationTime) %>">
|
||||||
Joined: <%= formatRelativeTime(user.registrationTime) %>
|
Joined: <%= util.formatRelativeTime(user.creationTime) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="date-seen" title="<%= formatAbsoluteTime(user.lastLoginTime) %>">
|
<div class="date-seen" title="<%= util.formatAbsoluteTime(user.lastLoginTime) %>">
|
||||||
Last seen: <%= formatRelativeTime(user.lastLoginTime) %>
|
Last seen: <%= util.formatRelativeTime(user.lastLoginTime) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
<a class="big-button" href="#/users/order=name,desc">Sort Z→A</a>
|
<a class="big-button" href="#/users/order=name,desc">Sort Z→A</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="big-button" href="#/users/order=registration_time,asc">Sort old→new</a>
|
<a class="big-button" href="#/users/order=creation_time,asc">Sort old→new</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="big-button" href="#/users/order=registration_time,desc">Sort new→old</a>
|
<a class="big-button" href="#/users/order=creation_time,desc">Sort new→old</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -51,15 +51,15 @@
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Registered:</td>
|
<td>Registered:</td>
|
||||||
<td title="<%= formatAbsoluteTime(user.registrationTime) %>">
|
<td title="<%= util.formatAbsoluteTime(user.creationTime) %>">
|
||||||
<%= formatRelativeTime(user.registrationTime) %>
|
<%= util.formatRelativeTime(user.creationTime) %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Seen:</td>
|
<td>Seen:</td>
|
||||||
<td title="<%= formatAbsoluteTime(user.lastLoginTime) %>">
|
<td title="<%= util.formatAbsoluteTime(user.lastLoginTime) %>">
|
||||||
<%= formatRelativeTime(user.lastLoginTime) %>
|
<%= util.formatRelativeTime(user.lastLoginTime) %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
1
scripts/cron-globals.php → scripts/cron-globals
Normal file → Executable file
1
scripts/cron-globals.php → scripts/cron-globals
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
<?php
|
<?php
|
||||||
require_once(__DIR__
|
require_once(__DIR__
|
||||||
. DIRECTORY_SEPARATOR . '..'
|
. DIRECTORY_SEPARATOR . '..'
|
43
scripts/cron-stats
Executable file
43
scripts/cron-stats
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__
|
||||||
|
. DIRECTORY_SEPARATOR . '..'
|
||||||
|
. DIRECTORY_SEPARATOR . 'src'
|
||||||
|
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
||||||
|
|
||||||
|
// Why this exists:
|
||||||
|
// 1. Some entities store a few basic stats in special columns for performance reasons. The benefit of such
|
||||||
|
// denormalization is vast.
|
||||||
|
// 2. The maintenance of the stats is implemented using triggers - when users tags a post, tag usage increases.
|
||||||
|
// 3. This mostly works.
|
||||||
|
// 4. Meanwhile, in order not to leave any orphans upon row deletions (e.g. have dangling postTags row after specific
|
||||||
|
// post removal), the database schema uses foreign keys with CASCADE option. This option recursively removes
|
||||||
|
// everything that would have missing references. This is good.
|
||||||
|
// 5. Here's the thing: row removals caused by CASCADE foreign key checks don't execute triggers. So if user removes a
|
||||||
|
// post, then although corresponding postTags entries will get deleted, ON postTags AFTER DELETE trigger will not
|
||||||
|
// execute, leaving the tags with invalid usage count.
|
||||||
|
//
|
||||||
|
// There are three possible solutions to this problem:
|
||||||
|
// 1. Implement all that logic in the appplication layer. I don't feel like doing this at all, it causes more havoc in
|
||||||
|
// the code and possibly adds even more holes to the whole denormalization maintenance process.
|
||||||
|
// 2. Convert CASCADE foreign checks to another set of triggers. This won't work for MySQL because of its limitations:
|
||||||
|
// >Can't update table 'comments' in stored function/trigger because it is already used by statement which invoked
|
||||||
|
// >this stored function/trigger
|
||||||
|
// Creating complex triggers will result very quickly in this error message (I tested it on postTags and posts, it
|
||||||
|
// did). I strongly believe the reason behind the error above is linked directly into the discussed MySQL's
|
||||||
|
// limitation.
|
||||||
|
// 3. Make a scripts like this. This is the easiest option out. The downside is that changes will be seen not
|
||||||
|
// immediately, but except for heavy tag maintenance, I don't see where such a delay in stat synchronization might
|
||||||
|
// really hurt.
|
||||||
|
|
||||||
|
use Szurubooru\DatabaseConnection;
|
||||||
|
$databaseConnection = Szurubooru\Injector::get(DatabaseConnection::class);
|
||||||
|
$pdo = $databaseConnection->getPDO();
|
||||||
|
$pdo->exec('UPDATE tags SET usages = (SELECT COUNT(1) FROM postTags WHERE tagId = tags.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET tagCount = (SELECT COUNT(1) FROM postTags WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET score = (SELECT SUM(score) FROM scores WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET favCount = (SELECT COUNT(1) FROM favorites WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET lastFavTime = (SELECT MAX(time) FROM favorites WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET commentCount = (SELECT COUNT(1) FROM comments WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET lastCommentCreationTime = (SELECT MAX(creationTime) FROM comments WHERE postId = posts.id)');
|
||||||
|
$pdo->exec('UPDATE posts SET lastCommentEditTime = (SELECT MAX(lastEditTime) FROM comments WHERE postId = posts.id)');
|
32
scripts/find-dead-posts
Executable file
32
scripts/find-dead-posts
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__
|
||||||
|
. DIRECTORY_SEPARATOR . '..'
|
||||||
|
. DIRECTORY_SEPARATOR . 'src'
|
||||||
|
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
||||||
|
|
||||||
|
use Szurubooru\Injector;
|
||||||
|
use Szurubooru\Dao\PublicFileDao;
|
||||||
|
use Szurubooru\Dao\PostDao;
|
||||||
|
|
||||||
|
$publicFileDao = Injector::get(PublicFileDao::class);
|
||||||
|
$postDao = Injector::get(PostDao::class);
|
||||||
|
|
||||||
|
$paths = [];
|
||||||
|
foreach ($postDao->findAll() as $post)
|
||||||
|
{
|
||||||
|
$paths[] = $post->getContentPath();
|
||||||
|
$paths[] = $post->getThumbnailSourceContentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
$paths = array_flip($paths);
|
||||||
|
foreach ($publicFileDao->listAll() as $path)
|
||||||
|
{
|
||||||
|
if (dirname($path) !== 'posts')
|
||||||
|
continue;
|
||||||
|
if (!isset($paths[$path]))
|
||||||
|
{
|
||||||
|
echo $path . PHP_EOL;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
35
scripts/fix-dimensions
Executable file
35
scripts/fix-dimensions
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__
|
||||||
|
. DIRECTORY_SEPARATOR . '..'
|
||||||
|
. DIRECTORY_SEPARATOR . 'src'
|
||||||
|
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
||||||
|
|
||||||
|
use Szurubooru\Injector;
|
||||||
|
use Szurubooru\Dao\PublicFileDao;
|
||||||
|
use Szurubooru\Dao\PostDao;
|
||||||
|
use Szurubooru\Services\ImageConverter;
|
||||||
|
use Szurubooru\Services\ImageManipulation\ImageManipulator;
|
||||||
|
|
||||||
|
$publicFileDao = Injector::get(PublicFileDao::class);
|
||||||
|
$postDao = Injector::get(PostDao::class);
|
||||||
|
$imageConverter = Injector::get(ImageConverter::class);
|
||||||
|
$imageManipulator = Injector::get(ImageManipulator::class);
|
||||||
|
|
||||||
|
if (!isset($argv[1]))
|
||||||
|
{
|
||||||
|
echo "No post ID specified.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$postId = intval($argv[1]);
|
||||||
|
$post = $postDao->findById($postId);
|
||||||
|
if (!$post)
|
||||||
|
{
|
||||||
|
echo "Post with this ID was not found in the database.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = $imageConverter->createImageFromBuffer($post->getContent());
|
||||||
|
$post->setImageWidth($imageManipulator->getImageWidth($image));
|
||||||
|
$post->setImageHeight($imageManipulator->getImageHeight($image));
|
||||||
|
$postDao->save($post);
|
19
scripts/test-email
Executable file
19
scripts/test-email
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__
|
||||||
|
. DIRECTORY_SEPARATOR . '..'
|
||||||
|
. DIRECTORY_SEPARATOR . 'src'
|
||||||
|
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
||||||
|
|
||||||
|
use Szurubooru\Injector;
|
||||||
|
use Szurubooru\Services\EmailService;
|
||||||
|
|
||||||
|
if (!isset($argv[1]))
|
||||||
|
{
|
||||||
|
echo "No recipient email specified.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$address = $argv[1];
|
||||||
|
|
||||||
|
$emailService = Injector::get(EmailService::class);
|
||||||
|
$emailService->sendEmail($address, 'test', "test\nąćęłóńśźż\n←↑→↓");
|
1
scripts/thumbnails.php → scripts/thumbnails
Normal file → Executable file
1
scripts/thumbnails.php → scripts/thumbnails
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
<?php
|
<?php
|
||||||
require_once(__DIR__
|
require_once(__DIR__
|
||||||
. DIRECTORY_SEPARATOR . '..'
|
. DIRECTORY_SEPARATOR . '..'
|
35
scripts/upgrade
Executable file
35
scripts/upgrade
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__
|
||||||
|
. DIRECTORY_SEPARATOR . '..'
|
||||||
|
. DIRECTORY_SEPARATOR . 'src'
|
||||||
|
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
||||||
|
|
||||||
|
$testMode = false;
|
||||||
|
|
||||||
|
if (isset($argv))
|
||||||
|
{
|
||||||
|
foreach ($argv as $arg)
|
||||||
|
{
|
||||||
|
if ($arg === '--test')
|
||||||
|
$testMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($testMode)
|
||||||
|
{
|
||||||
|
$config = \Szurubooru\Injector::get(\Szurubooru\Config::class);
|
||||||
|
$config->database->dsn = $config->database->tests->dsn;
|
||||||
|
$config->database->user = $config->database->tests->user;
|
||||||
|
$config->database->password = $config->database->tests->password;
|
||||||
|
\Szurubooru\Injector::set(\Szurubooru\Config::class, $config);
|
||||||
|
|
||||||
|
$databaseConnection = \Szurubooru\Injector::get(\Szurubooru\DatabaseConnection::class);
|
||||||
|
$pdo = $databaseConnection->getPDO();
|
||||||
|
$pdo->exec('DROP DATABASE IF EXISTS szuru_test');
|
||||||
|
$pdo->exec('CREATE DATABASE szuru_test');
|
||||||
|
$pdo->exec('USE szuru_test');
|
||||||
|
}
|
||||||
|
|
||||||
|
$upgradeService = \Szurubooru\Injector::get(\Szurubooru\Services\UpgradeService::class);
|
||||||
|
$upgradeService->runUpgradesVerbose();
|
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once(__DIR__
|
|
||||||
. DIRECTORY_SEPARATOR . '..'
|
|
||||||
. DIRECTORY_SEPARATOR . 'src'
|
|
||||||
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
|
|
||||||
|
|
||||||
$testMode = false;
|
|
||||||
|
|
||||||
if (isset($argv))
|
|
||||||
{
|
|
||||||
foreach ($argv as $arg)
|
|
||||||
{
|
|
||||||
if ($arg === '--test')
|
|
||||||
$testMode = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($testMode)
|
|
||||||
{
|
|
||||||
$config = \Szurubooru\Injector::get(\Szurubooru\Config::class);
|
|
||||||
$config->database->dsn = $config->database->tests->dsn;
|
|
||||||
$config->database->user = $config->database->tests->user;
|
|
||||||
$config->database->password = $config->database->tests->password;
|
|
||||||
\Szurubooru\Injector::set(\Szurubooru\Config::class, $config);
|
|
||||||
|
|
||||||
$databaseConnection = \Szurubooru\Injector::get(\Szurubooru\DatabaseConnection::class);
|
|
||||||
$pdo = $databaseConnection->getPDO();
|
|
||||||
$pdo->exec('DROP DATABASE IF EXISTS szuru_test');
|
|
||||||
$pdo->exec('CREATE DATABASE szuru_test');
|
|
||||||
$pdo->exec('USE szuru_test');
|
|
||||||
}
|
|
||||||
|
|
||||||
$upgradeService = \Szurubooru\Injector::get(\Szurubooru\Services\UpgradeService::class);
|
|
||||||
$upgradeService->runUpgradesVerbose();
|
|
@ -64,8 +64,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao
|
|||||||
public function findAll()
|
public function findAll()
|
||||||
{
|
{
|
||||||
$query = $this->pdo->from($this->tableName);
|
$query = $this->pdo->from($this->tableName);
|
||||||
$arrayEntities = iterator_to_array($query);
|
return $this->arrayToEntities($query);
|
||||||
return $this->arrayToEntities($arrayEntities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findById($entityId)
|
public function findById($entityId)
|
||||||
@ -248,7 +247,7 @@ abstract class AbstractDao implements ICrudDao, IBatchDao
|
|||||||
$query->where($sql, $bindings);
|
$query->where($sql, $bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function arrayToEntities(array $arrayEntities, $entityConverter = null)
|
protected function arrayToEntities($arrayEntities, $entityConverter = null)
|
||||||
{
|
{
|
||||||
if ($entityConverter === null)
|
if ($entityConverter === null)
|
||||||
$entityConverter = $this->entityConverter;
|
$entityConverter = $this->entityConverter;
|
||||||
|
@ -11,7 +11,7 @@ class PostEntityConverter extends AbstractEntityConverter implements IEntityConv
|
|||||||
[
|
[
|
||||||
'name' => $entity->getName(),
|
'name' => $entity->getName(),
|
||||||
'userId' => $entity->getUserId(),
|
'userId' => $entity->getUserId(),
|
||||||
'uploadTime' => $this->entityTimeToDbTime($entity->getUploadTime()),
|
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
|
||||||
'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()),
|
'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()),
|
||||||
'safety' => $entity->getSafety(),
|
'safety' => $entity->getSafety(),
|
||||||
'contentType' => $entity->getContentType(),
|
'contentType' => $entity->getContentType(),
|
||||||
@ -33,7 +33,7 @@ class PostEntityConverter extends AbstractEntityConverter implements IEntityConv
|
|||||||
$entity = new Post(intval($array['id']));
|
$entity = new Post(intval($array['id']));
|
||||||
$entity->setName($array['name']);
|
$entity->setName($array['name']);
|
||||||
$entity->setUserId($array['userId']);
|
$entity->setUserId($array['userId']);
|
||||||
$entity->setUploadTime($this->dbTimeToEntityTime($array['uploadTime']));
|
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
|
||||||
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
|
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
|
||||||
$entity->setSafety(intval($array['safety']));
|
$entity->setSafety(intval($array['safety']));
|
||||||
$entity->setContentType(intval($array['contentType']));
|
$entity->setContentType(intval($array['contentType']));
|
||||||
|
@ -11,6 +11,7 @@ class TagEntityConverter extends AbstractEntityConverter implements IEntityConve
|
|||||||
[
|
[
|
||||||
'name' => $entity->getName(),
|
'name' => $entity->getName(),
|
||||||
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
|
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
|
||||||
|
'lastEditTime' => $this->entityTimeToDbTime($entity->getLastEditTime()),
|
||||||
'banned' => intval($entity->isBanned()),
|
'banned' => intval($entity->isBanned()),
|
||||||
'category' => $entity->getCategory(),
|
'category' => $entity->getCategory(),
|
||||||
];
|
];
|
||||||
@ -21,6 +22,7 @@ class TagEntityConverter extends AbstractEntityConverter implements IEntityConve
|
|||||||
$entity = new Tag(intval($array['id']));
|
$entity = new Tag(intval($array['id']));
|
||||||
$entity->setName($array['name']);
|
$entity->setName($array['name']);
|
||||||
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
|
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
|
||||||
|
$entity->setLastEditTime($this->dbTimeToEntityTime($array['lastEditTime']));
|
||||||
$entity->setMeta(Tag::META_USAGES, intval($array['usages']));
|
$entity->setMeta(Tag::META_USAGES, intval($array['usages']));
|
||||||
$entity->setBanned($array['banned']);
|
$entity->setBanned($array['banned']);
|
||||||
$entity->setCategory($array['category']);
|
$entity->setCategory($array['category']);
|
||||||
|
@ -15,7 +15,7 @@ class UserEntityConverter extends AbstractEntityConverter implements IEntityConv
|
|||||||
'passwordHash' => $entity->getPasswordHash(),
|
'passwordHash' => $entity->getPasswordHash(),
|
||||||
'passwordSalt' => $entity->getPasswordSalt(),
|
'passwordSalt' => $entity->getPasswordSalt(),
|
||||||
'accessRank' => $entity->getAccessRank(),
|
'accessRank' => $entity->getAccessRank(),
|
||||||
'registrationTime' => $this->entityTimeToDbTime($entity->getRegistrationTime()),
|
'creationTime' => $this->entityTimeToDbTime($entity->getCreationTime()),
|
||||||
'lastLoginTime' => $this->entityTimeToDbTime($entity->getLastLoginTime()),
|
'lastLoginTime' => $this->entityTimeToDbTime($entity->getLastLoginTime()),
|
||||||
'avatarStyle' => $entity->getAvatarStyle(),
|
'avatarStyle' => $entity->getAvatarStyle(),
|
||||||
'browsingSettings' => json_encode($entity->getBrowsingSettings()),
|
'browsingSettings' => json_encode($entity->getBrowsingSettings()),
|
||||||
@ -33,7 +33,7 @@ class UserEntityConverter extends AbstractEntityConverter implements IEntityConv
|
|||||||
$entity->setPasswordHash($array['passwordHash']);
|
$entity->setPasswordHash($array['passwordHash']);
|
||||||
$entity->setPasswordSalt($array['passwordSalt']);
|
$entity->setPasswordSalt($array['passwordSalt']);
|
||||||
$entity->setAccessRank(intval($array['accessRank']));
|
$entity->setAccessRank(intval($array['accessRank']));
|
||||||
$entity->setRegistrationTime($this->dbTimeToEntityTime($array['registrationTime']));
|
$entity->setCreationTime($this->dbTimeToEntityTime($array['creationTime']));
|
||||||
$entity->setLastLoginTime($this->dbTimeToEntityTime($array['lastLoginTime']));
|
$entity->setLastLoginTime($this->dbTimeToEntityTime($array['lastLoginTime']));
|
||||||
$entity->setAvatarStyle(intval($array['avatarStyle']));
|
$entity->setAvatarStyle(intval($array['avatarStyle']));
|
||||||
$entity->setBrowsingSettings(json_decode($array['browsingSettings']));
|
$entity->setBrowsingSettings(json_decode($array['browsingSettings']));
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Szurubooru\Dao;
|
namespace Szurubooru\Dao;
|
||||||
use Szurubooru\Dao\EntityConverters\FavoriteEntityConverter;
|
use Szurubooru\Dao\EntityConverters\FavoriteEntityConverter;
|
||||||
|
use Szurubooru\Dao\PostDao;
|
||||||
|
use Szurubooru\Dao\UserDao;
|
||||||
use Szurubooru\DatabaseConnection;
|
use Szurubooru\DatabaseConnection;
|
||||||
use Szurubooru\Entities\Entity;
|
use Szurubooru\Entities\Entity;
|
||||||
use Szurubooru\Entities\Favorite;
|
use Szurubooru\Entities\Favorite;
|
||||||
@ -10,10 +12,14 @@ use Szurubooru\Services\TimeService;
|
|||||||
|
|
||||||
class FavoritesDao extends AbstractDao implements ICrudDao
|
class FavoritesDao extends AbstractDao implements ICrudDao
|
||||||
{
|
{
|
||||||
|
private $userDao;
|
||||||
|
private $postDao;
|
||||||
private $timeService;
|
private $timeService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
DatabaseConnection $databaseConnection,
|
DatabaseConnection $databaseConnection,
|
||||||
|
UserDao $userDao,
|
||||||
|
PostDao $postDao,
|
||||||
TimeService $timeService)
|
TimeService $timeService)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
@ -21,6 +27,8 @@ class FavoritesDao extends AbstractDao implements ICrudDao
|
|||||||
'favorites',
|
'favorites',
|
||||||
new FavoriteEntityConverter());
|
new FavoriteEntityConverter());
|
||||||
|
|
||||||
|
$this->userDao = $userDao;
|
||||||
|
$this->postDao = $postDao;
|
||||||
$this->timeService = $timeService;
|
$this->timeService = $timeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +66,23 @@ class FavoritesDao extends AbstractDao implements ICrudDao
|
|||||||
$this->deleteById($favorite->getId());
|
$this->deleteById($favorite->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function afterLoad(Entity $favorite)
|
||||||
|
{
|
||||||
|
$favorite->setLazyLoader(
|
||||||
|
Favorite::LAZY_LOADER_USER,
|
||||||
|
function (Favorite $favorite)
|
||||||
|
{
|
||||||
|
return $this->userDao->findById($favorite->getUserId());
|
||||||
|
});
|
||||||
|
|
||||||
|
$favorite->setLazyLoader(
|
||||||
|
Favorite::LAZY_LOADER_POST,
|
||||||
|
function (Favorite $favorite)
|
||||||
|
{
|
||||||
|
return $this->postDao->findById($favorite->getPostId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private function get(User $user, Entity $entity)
|
private function get(User $user, Entity $entity)
|
||||||
{
|
{
|
||||||
$query = $this->pdo->from($this->tableName)->where('userId', $user->getId());
|
$query = $this->pdo->from($this->tableName)->where('userId', $user->getId());
|
||||||
|
@ -44,10 +44,53 @@ class FileDao implements IFileDao
|
|||||||
return $this->directory . DIRECTORY_SEPARATOR . $fileName;
|
return $this->directory . DIRECTORY_SEPARATOR . $fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listAll()
|
||||||
|
{
|
||||||
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory));
|
||||||
|
$files = [];
|
||||||
|
foreach ($iterator as $path)
|
||||||
|
{
|
||||||
|
if (!$path->isDir())
|
||||||
|
$files[] = $this->getRelativePath($this->directory, $path->getPathName());
|
||||||
|
}
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
private function createFolders($fileName)
|
private function createFolders($fileName)
|
||||||
{
|
{
|
||||||
$fullPath = dirname($this->getFullPath($fileName));
|
$fullPath = dirname($this->getFullPath($fileName));
|
||||||
if (!file_exists($fullPath))
|
if (!file_exists($fullPath))
|
||||||
mkdir($fullPath, 0777, true);
|
mkdir($fullPath, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getRelativePath($from, $to)
|
||||||
|
{
|
||||||
|
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
|
||||||
|
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
|
||||||
|
$from = explode('/', str_replace('\\', '/', $from));
|
||||||
|
$to = explode('/', str_replace('\\', '/', $to));
|
||||||
|
$relPath = $to;
|
||||||
|
foreach ($from as $depth => $dir)
|
||||||
|
{
|
||||||
|
if ($dir === $to[$depth])
|
||||||
|
{
|
||||||
|
array_shift($relPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$remaining = count($from) - $depth;
|
||||||
|
if ($remaining > 1)
|
||||||
|
{
|
||||||
|
$padLength = (count($relPath) + $remaining - 1) * -1;
|
||||||
|
$relPath = array_pad($relPath, $padLength, '..');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$relPath[0] = $relPath[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode('/', $relPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elseif ($requirement->getType() === PostFilter::REQUIREMENT_COMMENT)
|
elseif ($requirement->getType() === PostFilter::REQUIREMENT_COMMENT_AUTHOR)
|
||||||
{
|
{
|
||||||
foreach ($requirement->getValue()->getValues() as $userName)
|
foreach ($requirement->getValue()->getValues() as $userName)
|
||||||
{
|
{
|
||||||
@ -194,6 +194,17 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elseif ($requirement->getType() === PostFilter::REQUIREMENT_TUMBLEWEED)
|
||||||
|
{
|
||||||
|
$sql = 'posts.score = 0
|
||||||
|
AND posts.commentCount = 0
|
||||||
|
AND posts.favCount = 0';
|
||||||
|
if ($requirement->isNegated())
|
||||||
|
$sql = 'NOT (' . $sql . ')';
|
||||||
|
$query->where($sql, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent::decorateQueryFromRequirement($query, $requirement);
|
parent::decorateQueryFromRequirement($query, $requirement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use Szurubooru\Services\ThumbnailService;
|
|||||||
class UserDao extends AbstractDao implements ICrudDao
|
class UserDao extends AbstractDao implements ICrudDao
|
||||||
{
|
{
|
||||||
const ORDER_NAME = 'name';
|
const ORDER_NAME = 'name';
|
||||||
const ORDER_REGISTRATION_TIME = 'registrationTime';
|
const ORDER_CREATION_TIME = 'creationTime';
|
||||||
|
|
||||||
private $fileDao;
|
private $fileDao;
|
||||||
private $thumbnailService;
|
private $thumbnailService;
|
||||||
|
@ -65,9 +65,12 @@ final class Dispatcher
|
|||||||
$json['__statements'] = $this->databaseConnection->getPDO()->getStatements();
|
$json['__statements'] = $this->databaseConnection->getPDO()->getStatements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this->httpHelper->isRedirecting())
|
||||||
|
{
|
||||||
$this->httpHelper->setResponseCode($code);
|
$this->httpHelper->setResponseCode($code);
|
||||||
$this->httpHelper->setHeader('Content-Type', 'application/json');
|
$this->httpHelper->setHeader('Content-Type', 'application/json');
|
||||||
$this->httpHelper->outputJSON($json);
|
$this->httpHelper->outputJSON($json);
|
||||||
|
}
|
||||||
|
|
||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ final class Post extends Entity
|
|||||||
const POST_TYPE_FLASH = 2;
|
const POST_TYPE_FLASH = 2;
|
||||||
const POST_TYPE_VIDEO = 3;
|
const POST_TYPE_VIDEO = 3;
|
||||||
const POST_TYPE_YOUTUBE = 4;
|
const POST_TYPE_YOUTUBE = 4;
|
||||||
|
const POST_TYPE_ANIMATED_IMAGE = 5;
|
||||||
|
|
||||||
const FLAG_LOOP = 1;
|
const FLAG_LOOP = 1;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ final class Post extends Entity
|
|||||||
|
|
||||||
private $name;
|
private $name;
|
||||||
private $userId;
|
private $userId;
|
||||||
private $uploadTime;
|
private $creationTime;
|
||||||
private $lastEditTime;
|
private $lastEditTime;
|
||||||
private $safety;
|
private $safety;
|
||||||
private $contentType;
|
private $contentType;
|
||||||
@ -78,14 +79,14 @@ final class Post extends Entity
|
|||||||
$this->safety = $safety;
|
$this->safety = $safety;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUploadTime()
|
public function getCreationTime()
|
||||||
{
|
{
|
||||||
return $this->uploadTime;
|
return $this->creationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUploadTime($uploadTime)
|
public function setCreationTime($creationTime)
|
||||||
{
|
{
|
||||||
$this->uploadTime = $uploadTime;
|
$this->creationTime = $creationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLastEditTime()
|
public function getLastEditTime()
|
||||||
|
@ -5,6 +5,7 @@ final class Tag extends Entity
|
|||||||
{
|
{
|
||||||
private $name;
|
private $name;
|
||||||
private $creationTime;
|
private $creationTime;
|
||||||
|
private $lastEditTime;
|
||||||
private $banned = false;
|
private $banned = false;
|
||||||
private $category = 'default';
|
private $category = 'default';
|
||||||
|
|
||||||
@ -33,6 +34,16 @@ final class Tag extends Entity
|
|||||||
$this->creationTime = $creationTime;
|
$this->creationTime = $creationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLastEditTime()
|
||||||
|
{
|
||||||
|
return $this->lastEditTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastEditTime($lastEditTime)
|
||||||
|
{
|
||||||
|
$this->lastEditTime = $lastEditTime;
|
||||||
|
}
|
||||||
|
|
||||||
public function isBanned()
|
public function isBanned()
|
||||||
{
|
{
|
||||||
return $this->banned;
|
return $this->banned;
|
||||||
|
@ -23,7 +23,7 @@ final class User extends Entity
|
|||||||
private $passwordHash;
|
private $passwordHash;
|
||||||
private $passwordSalt;
|
private $passwordSalt;
|
||||||
private $accessRank;
|
private $accessRank;
|
||||||
private $registrationTime;
|
private $creationTime;
|
||||||
private $lastLoginTime;
|
private $lastLoginTime;
|
||||||
private $avatarStyle;
|
private $avatarStyle;
|
||||||
private $browsingSettings;
|
private $browsingSettings;
|
||||||
@ -110,14 +110,14 @@ final class User extends Entity
|
|||||||
$this->accessRank = $accessRank;
|
$this->accessRank = $accessRank;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRegistrationTime()
|
public function getCreationTime()
|
||||||
{
|
{
|
||||||
return $this->registrationTime;
|
return $this->creationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRegistrationTime($registrationTime)
|
public function setCreationTime($creationTime)
|
||||||
{
|
{
|
||||||
$this->registrationTime = $registrationTime;
|
$this->creationTime = $creationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLastLoginTime()
|
public function getLastLoginTime()
|
||||||
|
@ -14,7 +14,7 @@ class PostEditFormData implements IValidatable
|
|||||||
public $relations;
|
public $relations;
|
||||||
public $flags;
|
public $flags;
|
||||||
|
|
||||||
public $seenEditTime;
|
public $lastEditTime;
|
||||||
|
|
||||||
public function __construct($inputReader = null)
|
public function __construct($inputReader = null)
|
||||||
{
|
{
|
||||||
@ -29,7 +29,7 @@ class PostEditFormData implements IValidatable
|
|||||||
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
||||||
if ($inputReader->relations !== null)
|
if ($inputReader->relations !== null)
|
||||||
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
||||||
$this->seenEditTime = $inputReader->seenEditTime;
|
$this->lastEditTime = $inputReader->lastEditTime;
|
||||||
$this->flags = new \StdClass;
|
$this->flags = new \StdClass;
|
||||||
$this->flags->loop = !empty($inputReader->loop);
|
$this->flags->loop = !empty($inputReader->loop);
|
||||||
}
|
}
|
||||||
|
@ -41,4 +41,3 @@ class TagEditFormData implements IValidatable
|
|||||||
$validator->validatePostTags($this->suggestions);
|
$validator->validatePostTags($this->suggestions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,4 +39,3 @@ class UploadFormData implements IValidatable
|
|||||||
$validator->validatePostSource($this->source);
|
$validator->validatePostSource($this->source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ class EnumHelper
|
|||||||
'video' => Post::POST_TYPE_VIDEO,
|
'video' => Post::POST_TYPE_VIDEO,
|
||||||
'flash' => Post::POST_TYPE_FLASH,
|
'flash' => Post::POST_TYPE_FLASH,
|
||||||
'youtube' => Post::POST_TYPE_YOUTUBE,
|
'youtube' => Post::POST_TYPE_YOUTUBE,
|
||||||
|
'animation' => Post::POST_TYPE_ANIMATED_IMAGE,
|
||||||
];
|
];
|
||||||
|
|
||||||
private static $snapshotTypeMap =
|
private static $snapshotTypeMap =
|
||||||
@ -103,7 +104,12 @@ class EnumHelper
|
|||||||
$key = trim(strtolower($enumString));
|
$key = trim(strtolower($enumString));
|
||||||
$lowerEnumMap = array_change_key_case($enumMap, \CASE_LOWER);
|
$lowerEnumMap = array_change_key_case($enumMap, \CASE_LOWER);
|
||||||
if (!isset($lowerEnumMap[$key]))
|
if (!isset($lowerEnumMap[$key]))
|
||||||
throw new \DomainException('Unrecognized value: ' . $enumString);
|
{
|
||||||
|
throw new \DomainException(sprintf(
|
||||||
|
'Unrecognized value: %s.' . PHP_EOL . 'Possible values: %s',
|
||||||
|
$enumString,
|
||||||
|
implode(', ', array_keys($lowerEnumMap))));
|
||||||
|
}
|
||||||
|
|
||||||
return $lowerEnumMap[$key];
|
return $lowerEnumMap[$key];
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ namespace Szurubooru\Helpers;
|
|||||||
|
|
||||||
class HttpHelper
|
class HttpHelper
|
||||||
{
|
{
|
||||||
|
private $redirected = false;
|
||||||
|
|
||||||
public function setResponseCode($code)
|
public function setResponseCode($code)
|
||||||
{
|
{
|
||||||
http_response_code($code);
|
http_response_code($code);
|
||||||
@ -29,9 +31,26 @@ class HttpHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getRequestHeaders()
|
public function getRequestHeaders()
|
||||||
|
{
|
||||||
|
if (function_exists('getallheaders'))
|
||||||
{
|
{
|
||||||
return getallheaders();
|
return getallheaders();
|
||||||
}
|
}
|
||||||
|
$result = [];
|
||||||
|
foreach ($_SERVER as $key => $value)
|
||||||
|
{
|
||||||
|
if (substr($key, 0, 5) === "HTTP_")
|
||||||
|
{
|
||||||
|
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
|
||||||
|
$result[$key] = $value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$result[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
public function getRequestHeader($key)
|
public function getRequestHeader($key)
|
||||||
{
|
{
|
||||||
@ -50,4 +69,23 @@ class HttpHelper
|
|||||||
$requestUri = preg_replace('/\?.*$/', '', $requestUri);
|
$requestUri = preg_replace('/\?.*$/', '', $requestUri);
|
||||||
return $requestUri;
|
return $requestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function redirect($destination)
|
||||||
|
{
|
||||||
|
$this->setResponseCode(307);
|
||||||
|
$this->setHeader('Location', $destination);
|
||||||
|
$this->redirected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nonCachedRedirect($destination)
|
||||||
|
{
|
||||||
|
$this->setResponseCode(303);
|
||||||
|
$this->setHeader('Location', $destination);
|
||||||
|
$this->redirected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRedirecting()
|
||||||
|
{
|
||||||
|
return $this->redirected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ final class InputReader extends \ArrayObject
|
|||||||
if (!isset($_FILES[$fileName]))
|
if (!isset($_FILES[$fileName]))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
if (!$_FILES[$fileName]['tmp_name'])
|
||||||
|
throw new \Exception('File is probably too big.');
|
||||||
|
|
||||||
return file_get_contents($_FILES[$fileName]['tmp_name']);
|
return file_get_contents($_FILES[$fileName]['tmp_name']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,12 @@ class MimeHelper
|
|||||||
return self::getMimeTypeFrom16Bytes(substr($buffer, 0, 16));
|
return self::getMimeTypeFrom16Bytes(substr($buffer, 0, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function isBufferAnimatedGif($buffer)
|
||||||
|
{
|
||||||
|
return strtolower(self::getMimeTypeFromBuffer($buffer)) === 'image/gif'
|
||||||
|
and preg_match_all('#\x21\xf9\x04.{4}\x00[\x2c\x21]#s', $buffer) > 1;
|
||||||
|
}
|
||||||
|
|
||||||
public static function isFlash($mime)
|
public static function isFlash($mime)
|
||||||
{
|
{
|
||||||
return strtolower($mime) === 'application/x-shockwave-flash';
|
return strtolower($mime) === 'application/x-shockwave-flash';
|
||||||
|
@ -3,8 +3,8 @@ namespace Szurubooru;
|
|||||||
|
|
||||||
class NotSupportedException extends \BadMethodCallException
|
class NotSupportedException extends \BadMethodCallException
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct($message = null)
|
||||||
{
|
{
|
||||||
parent::__construct('Not supported');
|
parent::__construct($message === null ? 'Not supported' : $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,6 @@ class AddComment extends AbstractCommentRoute
|
|||||||
|
|
||||||
$post = $this->postService->getByNameOrId($args['postNameOrId']);
|
$post = $this->postService->getByNameOrId($args['postNameOrId']);
|
||||||
$comment = $this->commentService->createComment($post, $this->inputReader->text);
|
$comment = $this->commentService->createComment($post, $this->inputReader->text);
|
||||||
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
|
return ['comment' => $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig())];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,6 @@ class DeleteComment extends AbstractCommentRoute
|
|||||||
? Privilege::DELETE_OWN_COMMENTS
|
? Privilege::DELETE_OWN_COMMENTS
|
||||||
: Privilege::DELETE_ALL_COMMENTS);
|
: Privilege::DELETE_ALL_COMMENTS);
|
||||||
|
|
||||||
return $this->commentService->deleteComment($comment);
|
$this->commentService->deleteComment($comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,6 @@ class EditComment extends AbstractCommentRoute
|
|||||||
: Privilege::EDIT_ALL_COMMENTS);
|
: Privilege::EDIT_ALL_COMMENTS);
|
||||||
|
|
||||||
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
|
$comment = $this->commentService->updateComment($comment, $this->inputReader->text);
|
||||||
return $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig());
|
return ['comment' => $this->commentViewProxy->fromEntity($comment, $this->getCommentsFetchConfig())];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class GetComments extends AbstractCommentRoute
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'data' => $data,
|
'comments' => $data,
|
||||||
'pageSize' => $result->getPageSize(),
|
'pageSize' => $result->getPageSize(),
|
||||||
'totalRecords' => $result->getTotalRecords()];
|
'totalRecords' => $result->getTotalRecords()];
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user