From: Jeremy Benoist Date: Tue, 9 May 2017 11:55:31 +0000 (+0200) Subject: Merge remote-tracking branch 'origin/master' into 2.3 X-Git-Tag: 2.3.0~31^2~103 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=54c2d164a362e64a320438b439bf9dd6d2c02424;hp=c829b06ed8f757f2b96515eb872f9ccf20363c94;p=github%2Fwallabag%2Fwallabag.git Merge remote-tracking branch 'origin/master' into 2.3 --- diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..cd38bcca --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + ["env", {"modules": false}] + ] +} diff --git a/.gitignore b/.gitignore index 0bcbabb8..709f78cf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ web/bundles/* !web/bundles/wallabagcore /web/assets/images/* !web/assets/images/.gitkeep +web/bundles/wallabagcore/*.dev.js # Build /app/build diff --git a/.travis.yml b/.travis.yml index 29ca7dc3..d7c28388 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,9 @@ cache: - vendor - $HOME/.composer/cache - node_modules - - $HOME/.cache/bower - $HOME/.npm php: - - 5.5 - 5.6 - 7.0 - 7.1 @@ -60,9 +58,9 @@ before_script: - if [[ $DB = pgsql ]]; then psql -c 'create database wallabag_test;' -U postgres; fi; install: - - if [[ $ASSETS = build ]]; then source ~/.nvm/nvm.sh && nvm install 6.7; fi; - - if [[ $ASSETS = build ]]; then npm install -g npm@latest; fi; - - if [[ $ASSETS = build ]]; then npm install; fi; + - if [[ $ASSETS = build ]]; then source ~/.nvm/nvm.sh && nvm install 6.10; fi; + - if [[ $ASSETS = build ]]; then npm install -g yarn@latest; fi; + - if [[ $ASSETS = build ]]; then yarn install; fi; before_install: - if [[ $TRAVIS_REPO_SLUG = wallabag/wallabag ]]; then cp .composer-auth.json ~/.composer/auth.json; fi; @@ -75,4 +73,4 @@ script: - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/CoreBundle/Resources/translations -v ; fi; - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml app/Resources/CraueConfigBundle/translations -v ; fi; - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/UserBundle/Resources/translations -v ; fi; - - if [[ $ASSETS = build ]]; then ./node_modules/grunt-cli/bin/grunt tests; fi; + - if [[ $ASSETS = build ]]; then yarn run build:prod; fi; diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 72473b21..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,230 +0,0 @@ -module.exports = function (grunt) { - require('load-grunt-tasks')(grunt); - - grunt.initConfig({ - appDir: 'app/Resources/static', - buildDir: 'app/Resources/build', - modulesDir: 'node_modules', - releaseDir: 'web/bundles/wallabagcore', - - postcss: { - material: { - options: { - processors: [ - require('pixrem')(), - require('autoprefixer')({ browsers: 'last 2 versions' }), - require('cssnano')(), - ], - }, - src: '<%= buildDir %>/material.css', - dest: '<%= releaseDir %>/themes/material/css/style.min.css', - }, - baggy: { - options: { - processors: [ - require('pixrem')(), - require('autoprefixer')({ browsers: 'last 2 versions' }), - require('cssnano')(), - ], - }, - src: '<%= buildDir %>/baggy.css', - dest: '<%= releaseDir %>/themes/baggy/css/style.min.css', - }, - }, - concat: { - options: { - separator: ';', - }, - cssMaterial: { - src: [ - 'node_modules/materialize-css/bin/materialize.css', - '<%= appDir %>/themes/material/css/*.css', - ], - dest: '<%= buildDir %>/material.css', - }, - cssBaggy: { - src: [ - '<%= appDir %>/themes/baggy/css/*.css', - ], - dest: '<%= buildDir %>/baggy.css', - }, - }, - browserify: { - dist: { - files: { - '<%= buildDir %>/material.browser.js': ['<%= appDir %>/themes/material/js/init.js'], - '<%= buildDir %>/baggy.browser.js': ['<%= appDir %>/themes/baggy/js/init.js'] - } - }, - options: { - sourceType: "module", - transform: [ - ["babelify", { - presets: ["es2015"] - }], ["browserify-shim", { - "jquery": { - "exports": "$" - }, - "materialize": "materialize", - "jquery-ui": { - "depends": "jquery", - "exports": null - } - }] - ], - browserifyOptions: { - browser: { - "jQuery": "./node_modules/jquery/dist/jquery.js", - "jquery.tinydot": "./node_modules/jquery.tinydot/src/jquery.tinydot.js", - "jquery.ui": "./node_modules/jquery-ui-browserify/dist/jquery-ui.js" - } - } - } - - }, - uglify: { - material: { - files: { - '<%= releaseDir %>/themes/material/js/material.min.js': - ['<%= buildDir %>/material.browser.js'], - } - }, - baggy: { - files: { - '<%= releaseDir %>/themes/baggy/js/baggy.min.js': - ['<%= buildDir %>/baggy.browser.js'], - } - }, - }, - copy: { - pickerjs: { - expand: true, - cwd: '<%= modulesDir %>/pickadate/lib', - src: 'picker.js', - dest: '<%= buildDir %>', - }, - annotator: { - expand: true, - cwd: '<%= modulesDir %>/annotator/pkg', - src: 'annotator.min.js', - dest: '<%= buildDir %>/themes/_global/js/', - }, - baggyfonts: { - files: [ - { - expand: true, - cwd: '<%= modulesDir %>/icomoon-free-npm/Font', - src: 'IcoMoon-Free.ttf', - dest: '<%= releaseDir %>/themes/baggy/fonts/', - }, - { - expand: true, - cwd: '<%= modulesDir %>/ptsans-npm-webfont/fonts', - src: 'ptsansbold.woff', - dest: '<%= releaseDir %>/themes/baggy/fonts/', - }, - { - expand: true, - cwd: '<%= modulesDir %>/material-design-icons-iconfont/dist/fonts/', - src: ['MaterialIcons-Regular.eot', 'MaterialIcons-Regular.woff2', 'MaterialIcons-Regular.woff', 'MaterialIcons-Regular.ttf'], - dest: '<%= releaseDir %>/themes/baggy/fonts/', - }, - ], - }, - materialfonts: { - files: [ - { - expand: true, - overwrite: true, - cwd: '<%= modulesDir %>/icomoon-free-npm/Font', - src: 'IcoMoon-Free.ttf', - dest: '<%= releaseDir %>/themes/material/fonts', - }, - { - expand: true, - overwrite: true, - cwd: '<%= modulesDir %>/roboto-fontface/fonts/Roboto', - src: '*', - dest: '<%= releaseDir %>/themes/material/font/roboto', - }, - { - expand: true, - overwrite: true, - cwd: '<%= modulesDir %>/material-design-icons-iconfont/dist/fonts/', - src: ['MaterialIcons-Regular.eot', 'MaterialIcons-Regular.woff2', 'MaterialIcons-Regular.woff', 'MaterialIcons-Regular.ttf'], - dest: '<%= releaseDir %>/themes/material/fonts/', - }, - ], - }, - }, - symlink: { - pics: { - files: [ - { - expand: true, - overwrite: true, - cwd: '<%= appDir %>/themes/_global/', - src: 'img', - dest: '<%= releaseDir %>/themes/_global/', - }, - ], - }, - }, - clean: { - css: { - src: ['<%= buildDir %>/**/*.css'], - }, - js: { - src: ['<%= buildDir %>/**/*.js', '<%= buildDir %>/**/*.map'], - }, - all: { - src: ['./<%= buildDir %>'], - }, - release: { - src: ['./<%= releaseDir %>/*'], - } - }, - eslint: { - target: ['<%= appDir %>/themes/material/js/init.js', '<%= appDir %>/themes/baggy/js/init.js'] - }, - stylelint: { - target: ['<%= appDir %>/themes/material/css/*.css', '<%= appDir %>/themes/baggy/css/*.css'] - }, - watch: { - files: ['<%= appDir %>/**/*.css', '<%= appDir %>/**/*.js'], - tasks: ['css', 'js'] - } - }); - - grunt.registerTask( - 'fonts', - 'Install fonts', - ['copy:baggyfonts', 'copy:materialfonts'] - ); - - grunt.registerTask( - 'js', - 'Build and install js files', - ['clean:js', 'copy:pickerjs', 'browserify', 'uglify'] - ); - - grunt.registerTask( - 'default', - 'Build and install everything', - ['clean', 'copy:pickerjs', 'concat', 'browserify', 'uglify', 'postcss', 'copy', 'symlink'] - ); - - grunt.registerTask( - 'css', - 'Compiles the stylesheets.', - ['clean:css', 'concat:cssMaterial', 'concat:cssBaggy', 'postcss'] - ); - - grunt.registerTask( - 'tests', - 'Test css and js style conformity', - ['eslint', 'stylelint', 'default'] - ), - - grunt.loadNpmTasks('grunt-contrib-watch'); -}; diff --git a/app/AppKernel.php b/app/AppKernel.php index c8382d5f..b9293498 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -71,5 +71,14 @@ class AppKernel extends Kernel public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml'); + $loader->load(function ($container) { + if ($container->getParameter('use_webpack_dev_server')) { + $container->loadFromExtension('framework', [ + 'assets' => [ + 'base_url' => 'http://localhost:8080/', + ], + ]); + } + }); } } diff --git a/app/DoctrineMigrations/Version20160410190541.php b/app/DoctrineMigrations/Version20160410190541.php index 6294d842..5e5cae35 100644 --- a/app/DoctrineMigrations/Version20160410190541.php +++ b/app/DoctrineMigrations/Version20160410190541.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added foreign keys for account resetting + * Added foreign keys for account resetting. */ class Version20160410190541 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20160812120952.php b/app/DoctrineMigrations/Version20160812120952.php index bd6e8d63..13272267 100644 --- a/app/DoctrineMigrations/Version20160812120952.php +++ b/app/DoctrineMigrations/Version20160812120952.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added name field on wallabag_oauth2_clients + * Added name field on wallabag_oauth2_clients. */ class Version20160812120952 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20160911214952.php b/app/DoctrineMigrations/Version20160911214952.php index edef81ed..4eae46e7 100644 --- a/app/DoctrineMigrations/Version20160911214952.php +++ b/app/DoctrineMigrations/Version20160911214952.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added settings for RabbitMQ and Redis imports + * Added settings for RabbitMQ and Redis imports. */ class Version20160911214952 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20160916201049.php b/app/DoctrineMigrations/Version20160916201049.php index 9f8e77e7..ff34c894 100644 --- a/app/DoctrineMigrations/Version20160916201049.php +++ b/app/DoctrineMigrations/Version20160916201049.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added pocket_consumer_key field on wallabag_config + * Added pocket_consumer_key field on wallabag_config. */ class Version20160916201049 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161001072726.php b/app/DoctrineMigrations/Version20161001072726.php index f247c236..ad761541 100644 --- a/app/DoctrineMigrations/Version20161001072726.php +++ b/app/DoctrineMigrations/Version20161001072726.php @@ -9,7 +9,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Doctrine\DBAL\Migrations\SkipMigrationException; /** - * Added pocket_consumer_key field on wallabag_config + * Added pocket_consumer_key field on wallabag_config. */ class Version20161001072726 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161022134138.php b/app/DoctrineMigrations/Version20161022134138.php index c71166a0..39949e7d 100644 --- a/app/DoctrineMigrations/Version20161022134138.php +++ b/app/DoctrineMigrations/Version20161022134138.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Converted database to utf8mb4 encoding (for MySQL only) + * Converted database to utf8mb4 encoding (for MySQL only). */ class Version20161022134138 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161024212538.php b/app/DoctrineMigrations/Version20161024212538.php index ecb872d1..b2f6aaf8 100644 --- a/app/DoctrineMigrations/Version20161024212538.php +++ b/app/DoctrineMigrations/Version20161024212538.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added user_id column on oauth2_clients to prevent users to delete API clients from other users + * Added user_id column on oauth2_clients to prevent users to delete API clients from other users. */ class Version20161024212538 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161031132655.php b/app/DoctrineMigrations/Version20161031132655.php index 83b97ca9..ef846412 100644 --- a/app/DoctrineMigrations/Version20161031132655.php +++ b/app/DoctrineMigrations/Version20161031132655.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added the internal setting to enable/disable downloading pictures + * Added the internal setting to enable/disable downloading pictures. */ class Version20161031132655 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161104073720.php b/app/DoctrineMigrations/Version20161104073720.php index fb8f5fa1..0e05f02e 100644 --- a/app/DoctrineMigrations/Version20161104073720.php +++ b/app/DoctrineMigrations/Version20161104073720.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added created_at index on entry table + * Added created_at index on entry table. */ class Version20161104073720 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161106113822.php b/app/DoctrineMigrations/Version20161106113822.php index de3702a4..facc14f4 100644 --- a/app/DoctrineMigrations/Version20161106113822.php +++ b/app/DoctrineMigrations/Version20161106113822.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added action_mark_as_read field on config table + * Added action_mark_as_read field on config table. */ class Version20161106113822 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161117071626.php b/app/DoctrineMigrations/Version20161117071626.php index 8daa2142..e779eacf 100644 --- a/app/DoctrineMigrations/Version20161117071626.php +++ b/app/DoctrineMigrations/Version20161117071626.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added the internal setting to share articles to unmark.it + * Added the internal setting to share articles to unmark.it. */ class Version20161117071626 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161214094402.php b/app/DoctrineMigrations/Version20161214094402.php index db125f76..8ca32b09 100644 --- a/app/DoctrineMigrations/Version20161214094402.php +++ b/app/DoctrineMigrations/Version20161214094402.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Renamed uuid to uid in entry table + * Renamed uuid to uid in entry table. */ class Version20161214094402 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20161214094403.php b/app/DoctrineMigrations/Version20161214094403.php index 5948b5fa..c7326f95 100644 --- a/app/DoctrineMigrations/Version20161214094403.php +++ b/app/DoctrineMigrations/Version20161214094403.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added index on wallabag_entry.uid + * Added index on wallabag_entry.uid. */ class Version20161214094403 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20170127093841.php b/app/DoctrineMigrations/Version20170127093841.php index 20c79479..5bfd9670 100644 --- a/app/DoctrineMigrations/Version20170127093841.php +++ b/app/DoctrineMigrations/Version20170127093841.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Added indexes on wallabag_entry.is_starred and wallabag_entry.is_archived + * Added indexes on wallabag_entry.is_starred and wallabag_entry.is_archived. */ class Version20170127093841 extends AbstractMigration implements ContainerAwareInterface { diff --git a/app/DoctrineMigrations/Version20170327194233.php b/app/DoctrineMigrations/Version20170327194233.php new file mode 100644 index 00000000..e1466b2f --- /dev/null +++ b/app/DoctrineMigrations/Version20170327194233.php @@ -0,0 +1,54 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix').$tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $scuttle = $this->container + ->get('doctrine.orm.default_entity_manager') + ->getConnection() + ->fetchArray('SELECT * FROM '.$this->getTable('craue_config_setting')." WHERE name = 'share_scuttle'"); + + $this->skipIf(false !== $scuttle, 'It seems that you already played this migration.'); + + $this->addSql('INSERT INTO '.$this->getTable('craue_config_setting')." (name, value, section) VALUES ('share_scuttle', '1', 'entry')"); + $this->addSql('INSERT INTO '.$this->getTable('craue_config_setting')." (name, value, section) VALUES ('scuttle_url', 'http://scuttle.org', 'entry')"); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->addSql('DELETE FROM '.$this->getTable('craue_config_setting')." WHERE name = 'share_scuttle';"); + $this->addSql('DELETE FROM '.$this->getTable('craue_config_setting')." WHERE name = 'scuttle_url';"); + } +} diff --git a/app/DoctrineMigrations/Version20170405182620.php b/app/DoctrineMigrations/Version20170405182620.php new file mode 100644 index 00000000..3ef9633f --- /dev/null +++ b/app/DoctrineMigrations/Version20170405182620.php @@ -0,0 +1,65 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix').$tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $entryTable = $schema->getTable($this->getTable('entry')); + + $this->skipIf($entryTable->hasColumn('published_at'), 'It seems that you already played this migration.'); + + $entryTable->addColumn('published_at', 'datetime', [ + 'notnull' => false, + ]); + + $this->skipIf($entryTable->hasColumn('published_by'), 'It seems that you already played this migration.'); + + $entryTable->addColumn('published_by', 'text', [ + 'notnull' => false, + ]); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $entryTable = $schema->getTable($this->getTable('entry')); + + $this->skipIf(!$entryTable->hasColumn('published_at'), 'It seems that you already played this migration.'); + + $entryTable->dropColumn('published_at'); + + $this->skipIf(!$entryTable->hasColumn('published_by'), 'It seems that you already played this migration.'); + + $entryTable->dropColumn('published_by'); + } +} diff --git a/app/DoctrineMigrations/Version20170407200919.php b/app/DoctrineMigrations/Version20170407200919.php new file mode 100644 index 00000000..4b9d475a --- /dev/null +++ b/app/DoctrineMigrations/Version20170407200919.php @@ -0,0 +1,51 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix').$tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $entryTable = $schema->getTable($this->getTable('entry')); + $this->skipIf(!$entryTable->hasColumn('is_public'), 'It seems that you already played this migration.'); + + $entryTable->dropColumn('is_public'); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $entryTable = $schema->getTable($this->getTable('entry')); + $this->skipIf($entryTable->hasColumn('is_public'), 'It seems that you already played this migration.'); + + $entryTable->addColumn('is_public', 'boolean', ['notnull' => false, 'default' => 0]); + } +} diff --git a/app/DoctrineMigrations/Version20170420134133.php b/app/DoctrineMigrations/Version20170420134133.php new file mode 100644 index 00000000..b1ab7bcb --- /dev/null +++ b/app/DoctrineMigrations/Version20170420134133.php @@ -0,0 +1,52 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix').$tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $this->addSql('DELETE FROM '.$this->getTable('craue_config_setting')." WHERE name = 'download_pictures';"); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $downloadPictures = $this->container + ->get('doctrine.orm.default_entity_manager') + ->getConnection() + ->fetchArray('SELECT * FROM '.$this->getTable('craue_config_setting')." WHERE name = 'download_pictures'"); + + $this->skipIf(false !== $downloadPictures, 'It seems that you already played this migration.'); + + $this->addSql('INSERT INTO '.$this->getTable('craue_config_setting')." (name, value, section) VALUES ('download_pictures', '1', 'entry')"); + } +} diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml index c65463db..d1f7e3b5 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml @@ -15,6 +15,7 @@ shaarli_url: Shaarli-URL, hvis tjenesten er aktiv share_diaspora: Aktiver deling til Diaspora share_mail: Aktiver deling med email share_shaarli: Aktiver deling gennem Shaarli +share_scuttle: Aktiver deling gennem Scuttle share_twitter: Aktiver deling gennem Twitter share_unmark: Aktiver deling gennem Unmark.it show_printlink: Vis et link til print-indhold diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml index bc378147..1105675b 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml @@ -15,6 +15,7 @@ shaarli_url: Shaarli-URL, sofern der Service aktiviert ist share_diaspora: Teilen zu Diaspora aktiveren share_mail: Teilen via E-Mail aktiveren share_shaarli: Teilen zu Shaarli aktiveren +share_scuttle: Teilen zu Scuttle aktiveren share_twitter: Teilen zu Twitter aktiveren share_unmark: Teilen zu Unmark.it aktiveren show_printlink: Link anzeigen, um den Inhalt auszudrucken diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml index 52cb8e20..802599b3 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml @@ -15,6 +15,7 @@ shaarli_url: Shaarli URL, if the service is enabled share_diaspora: Enable share to Diaspora share_mail: Enable share by email share_shaarli: Enable share to Shaarli +share_scuttle: Enable share to Scuttle share_twitter: Enable share to Twitter share_unmark: Enable share to Unmark.it show_printlink: Display a link to print content diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml index c338836d..b3ac18ed 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml @@ -15,6 +15,7 @@ shaarli_url: URL de Shaarli, si el servicio está activado share_diaspora: Activar compartir con Diaspora share_mail: Activar compartir con Email share_shaarli: Activar compartir con Shaarli +share_scuttle: Activar compartir con Scuttle share_twitter: Activar compartir con Twitter share_unmark: Activar compartir con Unmark.it show_printlink: Mostrar un enlace para imprimir contenido diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml index 7a341e0b..c73d63e2 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml @@ -15,6 +15,7 @@ shaarli_url: نشانی Shaarli، اگر فعال بود share_diaspora: فعال‌سازی هم‌رسانی به Diaspora share_mail: فعال‌سازی هم‌رسانی با ایمیل share_shaarli: فعال‌سازی هم‌رسانی به Shaarli +share_scuttle: فعال‌سازی هم‌رسانی به Scuttle share_twitter: فعال‌سازی هم‌رسانی به Twitter share_unmark: فعال‌سازی هم‌رسانی به Unmark.it show_printlink: نمایش پیوندی برای چاپ مطلب diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml index f5c886d6..a53174ae 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml @@ -15,6 +15,7 @@ shaarli_url: URL de Shaarli, si le service Shaarli est activé share_diaspora: Activer le partage vers Diaspora share_mail: Activer le partage par email share_shaarli: Activer le partage vers Shaarli +share_scuttle: Activer le partage vers Scuttle share_twitter: Activer le partage vers Twitter share_unmark: Activer le partage vers Unmark.it show_printlink: Afficher un lien pour imprimer diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml index 88a1b4f6..3d53fc8d 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml @@ -15,6 +15,7 @@ shaarli_url: Shaarli URL, se il servizio è abilitato share_diaspora: Abilita la condivisione con Diaspora share_mail: Abilita la condivisione per email share_shaarli: Abilita la condivisione con Shaarli +share_scuttle: Abilita la condivisione con Scuttle share_twitter: Abilita la condivisione con Twitter share_unmark: Abilita la condivisione con Unmark.it show_printlink: Mostra un collegamento per stampare il contenuto diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml index 04aaf0e8..79f75245 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml @@ -15,6 +15,7 @@ shaarli_url: URL de Shaarli, se lo servici Shaarli es activat share_diaspora: Activar lo partatge cap a Diaspora share_mail: Activar lo partatge per corrièl share_shaarli: Activar lo partatge cap a Shaarli +share_scuttle: Activar lo partatge cap a Scuttle share_twitter: Activar lo partatge cap a Twitter share_unmark: Activar lo partatge cap a Unmark.it show_printlink: Afichar un ligam per imprimir diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml index 1203e159..02fe98e3 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml @@ -15,6 +15,7 @@ shaarli_url: Adress URL Shaarli, jeżeli usługa jest włączona share_diaspora: Włącz udostępnianie dla Diaspora share_mail: Włącz udostępnianie przez email share_shaarli: Włącz udostępnianie dla Shaarli +share_scuttle: Włącz udostępnianie dla Scuttle share_twitter: Włącz udostępnianie dla Twitter share_unmark: Włącz udostępnianie dla Unmark.it show_printlink: Pokaż link do wydrukowania zawartości diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml index 1edde87a..4a061bce 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml @@ -15,6 +15,7 @@ shaarli_url: URL Shaarli, se o serviço está habilitado share_diaspora: Habilitar compartilhamento para o Diaspora share_mail: Habilitar compartilhamento por e-mail share_shaarli: Habilitar compartilhamento para o Shaarli +share_scuttle: Habilitar compartilhamento para o Scuttle share_twitter: Habilitar compartilhamento para o Twitter share_unmark: Habilitar compartilhamento para o Unmark.it show_printlink: Mostrar um link para imprimir o conteúdo diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml index f0c935d3..5ee48074 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml @@ -15,6 +15,7 @@ shaarli_url: Shaarli URL, dacă serviciul este permis share_diaspora: Permite share către Diaspora share_mail: Permite share prin email share_shaarli: Permite share către Shaarli +share_scuttle: Permite share către Scuttle share_twitter: Permite share către Twitter share_unmark: Permite share către Unmark.it show_printlink: Afișează un link pentru a printa content-ul diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml index eb40fc5e..d83a4b7b 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml @@ -15,6 +15,7 @@ # share_diaspora: Enable share to Diaspora # share_mail: Enable share by email # share_shaarli: Enable share to Shaarli +# share_scuttle: Enable share to Scuttle # share_twitter: Enable share to Twitter # share_unmark: Enable share to Unmark.it # show_printlink: Display a link to print content diff --git a/app/Resources/static/themes/_global/global.scss b/app/Resources/static/themes/_global/global.scss new file mode 100644 index 00000000..0e877efb --- /dev/null +++ b/app/Resources/static/themes/_global/global.scss @@ -0,0 +1,13 @@ +/* Rules for sizing the icon. */ +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-24 { font-size: 24px; } +.material-icons.md-36 { font-size: 36px; } +.material-icons.md-48 { font-size: 48px; } + +/* Rules for using icons as black on a light background. */ +.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } +.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } + +/* Rules for using icons as white on a dark background. */ +.material-icons.md-light { color: rgba(255, 255, 255, 1); } +.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } diff --git a/app/Resources/static/themes/_global/img/icons/Diaspora-asterisk.svg b/app/Resources/static/themes/_global/img/icons/Diaspora-asterisk.svg new file mode 100644 index 00000000..91764b60 --- /dev/null +++ b/app/Resources/static/themes/_global/img/icons/Diaspora-asterisk.svg @@ -0,0 +1,334 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/app/Resources/static/themes/_global/img/icons/scuttle.png b/app/Resources/static/themes/_global/img/icons/scuttle.png new file mode 100644 index 00000000..3b8eb264 Binary files /dev/null and b/app/Resources/static/themes/_global/img/icons/scuttle.png differ diff --git a/app/Resources/static/themes/_global/index.js b/app/Resources/static/themes/_global/index.js new file mode 100644 index 00000000..00410754 --- /dev/null +++ b/app/Resources/static/themes/_global/index.js @@ -0,0 +1,52 @@ +/* jQuery */ +import $ from 'jquery'; + +/* Annotations */ +import annotator from 'annotator'; + +/* Fonts */ +import 'material-design-icons-iconfont/dist/material-design-icons.css'; +import 'lato-font/css/lato-font.css'; +import './global.scss'; + +/* Shortcuts*/ +import './js/shortcuts/entry'; +import './js/shortcuts/main'; + +import { savePercent, retrievePercent } from './js/tools'; + + +/* ========================================================================== + Annotations & Remember position + ========================================================================== */ + +$(document).ready(() => { + if ($('article').length) { + const app = new annotator.App(); + + app.include(annotator.ui.main, { + element: document.querySelector('article'), + }); + + const x = JSON.parse($('#annotationroutes').html()); + app.include(annotator.storage.http, x); + + app.start().then(() => { + app.annotations.load({ entry: x.entryId }); + }); + + $(window).scroll(() => { + const scrollTop = $(window).scrollTop(); + const docHeight = $(document).height(); + const scrollPercent = (scrollTop) / (docHeight); + const scrollPercentRounded = Math.round(scrollPercent * 100) / 100; + savePercent(x.entryId, scrollPercentRounded); + }); + + retrievePercent(x.entryId); + + $(window).resize(() => { + retrievePercent(x.entryId); + }); + } +}); diff --git a/app/Resources/static/themes/_global/js/tools.js b/app/Resources/static/themes/_global/js/tools.js index cee84fa8..774f4539 100644 --- a/app/Resources/static/themes/_global/js/tools.js +++ b/app/Resources/static/themes/_global/js/tools.js @@ -31,25 +31,4 @@ function retrievePercent(id) { return true; } -function initFilters() { - // no display if filters not available - if ($('div').is('#filters')) { - $('#button_filters').show(); - $('.js-filters-action').sideNav({ edge: 'right' }); - $('#clear_form_filters').on('click', () => { - $('#filters input').val(''); - $('#filters :checked').removeAttr('checked'); - return false; - }); - } -} - -function initExport() { - // no display if export not available - if ($('div').is('#export')) { - $('#button_export').show(); - $('.js-export-action').sideNav({ edge: 'right' }); - } -} - -export { savePercent, retrievePercent, initFilters, initExport }; +export { savePercent, retrievePercent }; diff --git a/app/Resources/static/themes/baggy/css/article.scss b/app/Resources/static/themes/baggy/css/article.scss new file mode 100644 index 00000000..9094ad55 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/article.scss @@ -0,0 +1,165 @@ + +#article { + width: 70%; + margin-bottom: 3em; + text-align: justify; + + .tags { + margin-bottom: 1em; + } + + i { + font-style: normal; + } + + h1 { + text-align: left; + } + + h2::after { + content: none; + } + + h2, + h3, + h4 { + text-transform: none; + } +} + +blockquote { + border: 1px solid #999; + background-color: #fff; + padding: 1em; + margin: 0; +} + +.topPosF { + position: fixed; + right: 20%; + bottom: 2em; + font-size: 1.5em; +} + +#article_toolbar { + margin-bottom: 1em; + + li { + display: inline-block; + margin: 3px auto; + } + + a { + background-color: #000; + padding: 0.3em 0.5em 0.2em; + color: #fff; + text-decoration: none; + + &:hover, + &:focus { + background-color: #999; + } + } +} + +#nav-btn-add-tag { + cursor: pointer; +} + +.shaarli::before { + content: "*"; +} + +.return { + text-decoration: none; + margin-top: 1em; + display: block; +} + +.return::before { + margin-right: 0.5em; +} + +.notags { + font-style: italic; + color: #999; +} + +.icon-rss { + background-color: #000; + color: #fff; + padding: 0.2em 0.5em; + + &::before { + position: relative; + top: 2px; + } +} + +.list-tags { + li { + margin-bottom: 0.5em; + } + + .icon-rss:hover, + .icon-rss:focus { + background-color: #fff; + color: #000; + text-decoration: none; + } + + a { + text-decoration: none; + + &:hover, + &:focus { + text-decoration: underline; + } + } +} + +pre code { + font-family: "Courier New", Courier, monospace; +} + +#filters { + position: fixed; + width: 20%; + height: 100%; + top: 0; + right: 0; + background-color: #fff; + padding: 30px 30px 15px 15px; + border-left: 1px #333 solid; + z-index: 12; + min-width: 300px; + + form .filter-group { + margin: 5px; + } +} + +#download-form { + position: fixed; + width: 10%; + height: 100%; + top: 0; + right: 0; + background-color: #fff; + padding: 30px 30px 15px 15px; + border-left: 1px #333 solid; + z-index: 12; + min-width: 200px; + + li { + display: block; + padding: 0.5em 2em 0.5em 1em; + color: #fff; + position: relative; + text-transform: uppercase; + text-decoration: none; + font-weight: 400; + font-family: PT Sans, sans-serif; + transition: all 0.5s ease; + } +} diff --git a/app/Resources/static/themes/baggy/css/font.css b/app/Resources/static/themes/baggy/css/font.css deleted file mode 100755 index 47edcb83..00000000 --- a/app/Resources/static/themes/baggy/css/font.css +++ /dev/null @@ -1,6 +0,0 @@ -@font-face { - font-family: "PT Sans"; - font-style: normal; - font-weight: 700; - src: local("PT Sans Bold"), local("PTSans-Bold"), url("../fonts/ptsansbold.woff") format("woff"); -} diff --git a/app/Resources/static/themes/baggy/css/guide.scss b/app/Resources/static/themes/baggy/css/guide.scss new file mode 100644 index 00000000..afb47c4a --- /dev/null +++ b/app/Resources/static/themes/baggy/css/guide.scss @@ -0,0 +1,263 @@ + +::selection { + color: #fff; + background-color: #000; +} + +.desktopHide { + display: none; +} + +.logo { + position: fixed; + z-index: 20; + top: 0.4em; + left: 0.6em; +} + +h2, +h3, +h4 { + font-family: "PT Sans", sans-serif; + text-transform: uppercase; +} + +p, +li, +label { + color: #666; +} + +a { + color: #000; + font-weight: bold; + + &.nostyle { + text-decoration: none; + } + + &:hover, + &:focus { + text-decoration: none; + } +} + +form fieldset { + border: 0; + padding: 0; + margin: 0; +} + +form input[type="text"], +form input[type="number"], +select, +form input[type="password"], +form input[type="url"], +form input[type="email"] { + border: 1px solid #999; + padding: 0.5em 1em; + min-width: 12em; + color: #666; +} + +@media screen and (-webkit-min-device-pixel-ratio: 0) { + select { + -webkit-appearance: none; + border-radius: 0; + background: #fff url("../../_global/img/bg-select.png") no-repeat right center; + } +} + +.inline { + .row { + display: inline-block; + margin-right: 0.5em; + } + + label { + min-width: 6em; + } +} + +fieldset label { + display: inline-block; + min-width: 12.5em; + color: #666; +} + +label { + margin-right: 0.5em; +} + +form .row { + margin-bottom: 0.5em; +} + +form button, +input[type="submit"] { + cursor: pointer; + background-color: #000; + color: #fff; + padding: 0.5em 1em; + display: inline-block; + border: 1px solid #000; +} + +form button:hover, +form button:focus, +input[type="submit"]:hover, +input[type="submit"]:focus { + background-color: #fff; + color: #000; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -ms-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; +} + +#bookmarklet { + cursor: move; +} + +h2::after { + content: ""; + height: 4px; + width: 20%; + background-color: #000; + display: block; +} + +.links { + padding: 0; + margin: 0; + + li { + list-style: none; + margin: 0; + padding: 0; + } +} + +#links { + position: fixed; + top: 0; + width: 10em; + left: 0; + text-align: right; + background-color: #333; + padding-top: 9.5em; + height: 100%; + box-shadow: inset -4px 0 20px rgba(0, 0, 0, 0.6); + z-index: 15; + + > li > a { + display: block; + padding: 0.5em 2em 0.5em 1em; + color: #fff; + position: relative; + text-transform: uppercase; + text-decoration: none; + font-weight: normal; + font-family: "PT Sans", sans-serif; + transition: all 0.5s ease; + + &:hover, + &:focus { + background-color: #999; + color: #000; + } + } + + .current::after { + content: ""; + width: 0; + height: 0; + position: absolute; + border: 10px solid transparent; + border-right-color: #eee; + right: 0; + top: 50%; + margin-top: -10px; + } + + li:last-child { + position: fixed; + bottom: 1em; + width: 10em; + + a::before { + font-size: 1.2em; + position: relative; + top: 2px; + } + } +} + +#main { + margin-left: 12em; + position: relative; + z-index: 10; + padding-right: 5%; + padding-bottom: 1em; +} + +#sort { + padding: 0; + list-style-type: none; + opacity: 0.5; + display: inline-block; + + li { + display: inline; + font-size: 0.9em; + + & + li { + margin-left: 10px; + } + } + + a { + padding: 2px 2px 0; + vertical-align: middle; + } + + img { + vertical-align: baseline; + + :hover { + cursor: pointer; + } + } +} + +#display-mode { + float: right; + margin-top: 10px; + margin-bottom: 10px; + opacity: 0.5; +} + +#listmode { + width: 16px; + display: inline-block; + text-decoration: none; + + &.tablemode { + background: url("../../_global/img/table.png") no-repeat bottom; + } + + .listmode { + background: url("../../_global/img/list.png") no-repeat bottom; + } +} + +#warning_message { + position: fixed; + background-color: #ff6347; + z-index: 1000; + bottom: 0; + left: 0; + width: 100%; + color: #000; +} diff --git a/app/Resources/static/themes/baggy/css/index.scss b/app/Resources/static/themes/baggy/css/index.scss new file mode 100644 index 00000000..e7a11963 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/index.scss @@ -0,0 +1,13 @@ +/* Style */ +@import 'guide'; +@import 'layout'; +@import 'article'; +@import 'pictos'; +@import 'login'; +@import 'save'; +@import 'messages'; + +/* Tools */ +@import 'media_queries'; +@import 'print'; +@import 'ratatouille'; diff --git a/app/Resources/static/themes/baggy/css/layout.scss b/app/Resources/static/themes/baggy/css/layout.scss new file mode 100644 index 00000000..cb14e62d --- /dev/null +++ b/app/Resources/static/themes/baggy/css/layout.scss @@ -0,0 +1,300 @@ +#content { + margin-top: 2em; + min-height: 30em; +} + +footer { + text-align: right; + position: relative; + bottom: 0; + right: 5em; + color: #999; + font-size: 0.8em; + font-style: italic; + z-index: 20; + + a { + color: #999; + font-weight: normal; + } +} + +.list-entries { + letter-spacing: -5px; +} + +.listmode.entry { + width: 100%; + height: inherit; +} + +.card-entry-tags { + max-height: 2em; + overflow-y: hidden; + padding: 0; + margin: 0; +} + +.card-entry-tags li, +.card-entry-tags span { + display: inline-block; + margin: 0 5px; + padding: 5px 12px; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 3px; + max-height: 2em; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-entry-tags a, +.card-entry-labels a { + text-decoration: none; + font-weight: normal; + color: #fff; +} + +.nav-panel-add-tag { + margin-top: 10px; +} + +.list-entries + .results { + margin-bottom: 2em; +} + +.reading-time, +.created-at { + color: #999; + font-style: italic; + font-weight: normal; + font-size: 0.9em; +} + +.estimatedTime small { + position: relative; + top: -1px; +} + +.entry { + background-color: #fff; + letter-spacing: normal; + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + display: inline-block; + width: 32%; + margin-bottom: 1.5em; + vertical-align: top; + margin-right: 1%; + position: relative; + overflow: hidden; + padding: 1.5em 0 3em; + height: 440px; + + img.preview { + width: 100%; + object-fit: cover; + height: 100%; + } + + &::before { + content: ""; + width: 0; + height: 0; + border: 10px solid transparent; + border-bottom-color: #000; + position: absolute; + bottom: 0.7em; + z-index: 10; + right: 1.5em; + transition: all 0.5s ease; + } + + &::after { + content: ""; + position: absolute; + height: 7px; + width: 100%; + bottom: 0; + left: 0; + background-color: #000; + transition: all 0.5s ease; + } + + &:hover { + box-shadow: 0 3px 10px rgba(0, 0, 0, 1); + + &::after { + height: 40px; + } + + &::before { + bottom: 2.3em; + } + + h2 a { + color: #666; + } + + .tools { + bottom: 0; + } + } + + h2 { + text-transform: none; + margin-bottom: 0; + line-height: 1.2; + margin-left: 5px; + } + + &::after { + content: none; + } + + a { + display: block; + text-decoration: none; + color: #000; + word-wrap: break-word; + transition: all 0.5s ease; + } + + p { + color: #666; + font-size: 0.9em; + line-height: 1.7; + margin: 5px 5px auto; + } + + h2 a::first-letter { + text-transform: uppercase; + } + + .tools { + position: absolute; + bottom: -40px; + left: 0; + background: #000; + width: 100%; + z-index: 10; + padding-right: 0.5em; + text-align: right; + transition: all 0.5s ease; + + a { + color: #666; + text-decoration: none; + display: block; + padding: 0.4em; + + &:hover { + color: #fff; + } + } + + li { + display: inline-block; + margin-top: 10px; + } + + li:first-child { + float: left; + font-size: 0.9em; + max-width: calc(100% - 40px * 4); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-height: 2em; + margin-left: 10px; + } + } + + .card-entry-labels { + position: absolute; + top: 100px; + left: -1em; + z-index: 90; + max-width: 50%; + padding-left: 0; + + li { + margin: 10px 10px 10px auto; + padding: 5px 12px 5px 25px; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 0 3px 3px 0; + color: #fff; + cursor: default; + max-height: 2em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + a { + color: #fff; + } + } + } +} + +.entry:nth-child(3n+1) { + margin-left: 0; +} + +.results { + letter-spacing: -5px; + padding: 0 0 0.5em; + + > * { + display: inline-block; + vertical-align: top; + letter-spacing: normal; + width: 50%; + text-align: right; + } +} + +div.pagination ul { + text-align: right; +} + +.nb-results { + text-align: left; + font-style: italic; + color: #999; + display: inline-flex; +} + +div.pagination ul { + a { + color: #999; + text-decoration: none; + + &:hover, + &:focus { + text-decoration: underline; + } + } + + > * { + display: inline-block; + margin-left: 0.5em; + } + + .prev.disabled, + .next.disabled { + display: none; + } + + .current { + height: 25px; + padding: 4px 8px; + border: 1px solid #d5d5d5; + text-decoration: none; + font-weight: bold; + color: #000; + background-color: #ccc; + } +} + +.hide { + display: none; +} diff --git a/app/Resources/static/themes/baggy/css/login.scss b/app/Resources/static/themes/baggy/css/login.scss new file mode 100644 index 00000000..312df670 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/login.scss @@ -0,0 +1,26 @@ +.login { + background-color: #333; + + #main { + padding: 0; + margin: 0; + } + + form { + background-color: #fff; + padding: 1.5em; + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.9); + width: 20em; + position: absolute; + top: 8em; + left: 50%; + margin-left: -10em; + } + + .logo { + position: absolute; + top: 2em; + left: 50%; + margin-left: -55px; + } +} diff --git a/app/Resources/static/themes/baggy/css/main.css b/app/Resources/static/themes/baggy/css/main.css index e16846ea..f82c6bee 100755 --- a/app/Resources/static/themes/baggy/css/main.css +++ b/app/Resources/static/themes/baggy/css/main.css @@ -912,6 +912,14 @@ a.add-to-wallabag-link-after::after { content: "\e953"; } +.icon-pencil2::before { + content: "\e906"; +} + +.icon-users::before { + content: "\e972"; +} + .icon-time::before { content: "\e952"; } @@ -947,6 +955,11 @@ a.add-to-wallabag-link-after::after { background-image: url("../../_global/img/icons/shaarli.png"); } +/* scuttle */ +.icon-image--scuttle { + background-image: url("../../_global/img/icons/scuttle.png"); +} + /* ========================================================================== Icon selected ========================================================================== */ @@ -1063,6 +1076,10 @@ blockquote { content: "*"; } +.scuttle::before { + content: "*"; +} + .return { text-decoration: none; margin-top: 1em; diff --git a/app/Resources/static/themes/baggy/css/media_queries.scss b/app/Resources/static/themes/baggy/css/media_queries.scss new file mode 100755 index 00000000..c33db0b3 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/media_queries.scss @@ -0,0 +1,172 @@ + +@media screen and (max-width: 1050px) { + .entry { + width: 49%; + } + + .entry:nth-child(3n+1) { + margin-left: 1.5%; + } + + .entry:nth-child(2n+1) { + margin-left: 0; + } +} + +@media screen and (max-width: 900px) { + #article { + width: 80%; + } + + .topPosF { + right: 2.5em; + } +} + +@media screen and (max-width: 700px) { + .entry { + width: 100%; + margin-left: 0; + } + + #display-mode { + display: none; + } +} + +@media screen and (max-height: 770px) { + .menu.users, + .menu.internal, + .menu.developer { + display: none; + } +} + +@media screen and (max-width: 500px) { + .entry { + width: 100%; + margin-left: 0; + } + + body > header { + background-color: #333; + position: fixed; + top: 0; + width: 100%; + height: 3em; + z-index: 11; + } + + #links li:last-child { + position: static; + width: auto; + } + + #links li:last-child a::before { + content: none; + } + + .logo { + width: 1.25em; + height: 1.25em; + left: 0; + top: 0; + } + + .login > header { + position: static; + } + + .login form { + width: 100%; + position: static; + margin-left: 0; + } + + .login .logo { + height: auto; + top: 0.5em; + width: 75px; + margin-left: -37.5px; + } + + .desktopHide { + display: block; + position: fixed; + z-index: 20; + top: 0; + right: 0; + border: 0; + width: 2.5em; + height: 2.5em; + cursor: pointer; + background-color: #999; + font-size: 1.2em; + } + + .desktopHide:hover, + .desktopHide:focus { + background-color: #fff; + } + + #links { + display: none; + width: 100%; + height: auto; + padding-top: 3em; + } + + #links.menu--open { + display: block; + } + + footer { + position: static; + margin-right: 3em; + } + + #main { + margin-left: 1.5em; + padding-right: 1.5em; + position: static; + margin-top: 3em; + } + + .card-entry-labels { + display: none; + } + + #article_toolbar .topPosF { + display: none; + } + + #article { + width: 100%; + } + + #article h1 { + font-size: 1.5em; + } + + #article_toolbar a { + padding: 0.3em 0.4em 0.2em; + } + + #display-mode { + display: none; + } + + .popup-form, + #bagit-form, + #search-form { + left: 0; + width: 100%; + border-left: none; + } + + .popup-form form, + #bagit-form form, + #search-form form { + width: 100%; + } +} diff --git a/app/Resources/static/themes/baggy/css/messages.css b/app/Resources/static/themes/baggy/css/messages.css deleted file mode 100755 index bfaf1448..00000000 --- a/app/Resources/static/themes/baggy/css/messages.css +++ /dev/null @@ -1,19 +0,0 @@ -.messages.error.install { - border: 1px solid #c42608; - color: #c00 !important; - background: #fff0ef; - text-align: left; -} - -.messages.notice.install { - border: 1px solid #ebcd41; - color: #000; - background: #fffcd3; - text-align: left; -} - -.messages.success.install { - border: 1px solid #6dc70c; - background: #e0fbcc !important; - text-align: left; -} diff --git a/app/Resources/static/themes/baggy/css/messages.scss b/app/Resources/static/themes/baggy/css/messages.scss new file mode 100755 index 00000000..a388419e --- /dev/null +++ b/app/Resources/static/themes/baggy/css/messages.scss @@ -0,0 +1,50 @@ +/* ========================================================================== + Messages + ========================================================================== */ + +.messages { + text-align: left; + width: 60%; + margin: auto 17%; + + > * { + display: inline-block; + } + + .install { + text-align: left; + + &.error { + border: 1px solid #c42608; + color: #c00 !important; + background: #fff0ef; + } + + &.notice { + border: 1px solid #ebcd41; + color: #000; + background: #fffcd3; + } + + &.success { + border: 1px solid #6dc70c; + background: #e0fbcc !important; + } + } +} + +.warning { + font-weight: bold; + display: block; + width: 100%; +} + +.more-info { + font-size: 0.85em; + line-height: 1.5; + color: #aaa; + + a { + color: #aaa; + } +} diff --git a/app/Resources/static/themes/baggy/css/pictos.scss b/app/Resources/static/themes/baggy/css/pictos.scss new file mode 100644 index 00000000..2ff01937 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/pictos.scss @@ -0,0 +1,210 @@ +/* ========================================================================== + Pictos + ========================================================================== */ + +@font-face { + font-family: icomoon; + src: url('~icomoon-free-npm/Font/IcoMoon-Free.ttf'); + font-weight: normal; + font-style: normal; +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 1em; /* Preferred icon size */ + width: 1em; + height: 1em; + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; + + .md-18 { font-size: 18px; } + .md-24 { font-size: 24px; } + .md-36 { font-size: 36px; } + .md-48 { font-size: 48px; } + + .vertical-align-middle { + vertical-align: middle !important; + } +} + +.icon span, +.icon-image span { + position: absolute; + top: -9999px; +} + +[class^="icon-"]::before, +[class*=" icon-"]::before { + font-family: icomoon; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Enable Ligatures ================ */ + letter-spacing: 0; + -webkit-font-feature-settings: "liga"; + -moz-font-feature-settings: "liga=1"; + -moz-font-feature-settings: "liga"; + -ms-font-feature-settings: "liga" 1; + -o-font-feature-settings: "liga"; + font-feature-settings: "liga"; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-flattr::before { + content: "\ead4"; +} + +.icon-mail::before { + content: "\ea86"; +} + +.icon-up-open::before { + content: "\e80b"; +} + +.icon-star::before { + content: "\e9d9"; +} + +.icon-check::before { + content: "\ea10"; +} + +.icon-link::before { + content: "\e9cb"; +} + +.icon-reply::before { + content: "\e806"; +} + +.icon-menu::before { + content: "\e9bd"; +} + +.icon-clock::before { + content: "\e803"; +} + +.icon-twitter::before { + content: "\ea96"; +} + +.icon-down-open::before { + content: "\e809"; +} + +.icon-trash::before { + content: "\e9ac"; +} + +.icon-delete::before { + content: "\ea0d"; +} + +.icon-power::before { + content: "\ea14"; +} + +.icon-arrow-up-thick::before { + content: "\ea3a"; +} + +.icon-rss::before { + content: "\e808"; +} + +.icon-print::before { + content: "\e954"; +} + +.icon-reload::before { + content: "\ea2e"; +} + +.icon-price-tags::before { + content: "\e936"; +} + +.icon-eye::before { + content: "\e9ce"; +} + +.icon-no-eye::before { + content: "\e9d1"; +} + +.icon-calendar::before { + content: "\e953"; +} + +.icon-time::before { + content: "\e952"; +} + +/* .icon-image class, for image-based icons + ========================================================================== */ + +.icon-image { + background: no-repeat center/80%; + padding-right: 1em !important; + padding-left: 1em !important; +} + +/* Carrot (http://carrot.org) */ +.icon-image--carrot { + background-image: url("../../_global/img/icons/carrot-icon--white.png"); +} + +/* Diaspora */ +.icon-image--diaspora { + background-image: url("../../_global/img/icons/Diaspora-asterisk.svg"); +} + +/* Unmark.it */ +.icon-image--unmark { + background-image: url("../../_global/img/icons/unmark-icon--black.png"); +} + +/* shaarli */ +.icon-image--shaarli { + background-image: url("../../_global/img/icons/shaarli.png"); +} + +/* ========================================================================== + Icon selected + ========================================================================== */ + +.icon-star.fav::before { + color: #fff; +} + +.icon-check.archive::before { + color: #fff; +} diff --git a/app/Resources/static/themes/baggy/css/print.css b/app/Resources/static/themes/baggy/css/print.scss similarity index 91% rename from app/Resources/static/themes/baggy/css/print.css rename to app/Resources/static/themes/baggy/css/print.scss index f7f6a8ad..a63f62e9 100755 --- a/app/Resources/static/themes/baggy/css/print.css +++ b/app/Resources/static/themes/baggy/css/print.scss @@ -17,7 +17,7 @@ /* ### Content ### */ /* Hide useless blocks */ - body > header, + body > .logo, #article_toolbar, #links, #sort, @@ -53,11 +53,8 @@ #main { width: 100%; - padding: 0; margin: 0; - margin-left: 0; - padding-right: 0; - padding-bottom: 0; + padding: 0; } #article { diff --git a/app/Resources/static/themes/baggy/css/ratatouille.css b/app/Resources/static/themes/baggy/css/ratatouille.scss similarity index 100% rename from app/Resources/static/themes/baggy/css/ratatouille.css rename to app/Resources/static/themes/baggy/css/ratatouille.scss diff --git a/app/Resources/static/themes/baggy/css/save.scss b/app/Resources/static/themes/baggy/css/save.scss new file mode 100644 index 00000000..ade77b40 --- /dev/null +++ b/app/Resources/static/themes/baggy/css/save.scss @@ -0,0 +1,115 @@ +/* ========================================================================== + "save a link" related styles + ========================================================================== */ + +.popup-form { + background: rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + left: 10em; + z-index: 20; + height: 100%; + width: 100%; + margin: 0; + margin-top: -30% !important; + padding: 2em; + display: none; + border-left: 1px #eee solid; + + form { + background-color: #fff; + position: absolute; + top: 0; + left: 0; + z-index: 20; + border: 10px solid #000; + width: 400px; + height: 200px; + padding: 2em; + } +} + +#bagit-form-form .addurl { + margin-left: 0; +} + +.closeMessage, +.close-button { + background-color: #000; + color: #fff; + font-size: 1.2em; + line-height: 1.6; + width: 1.6em; + height: 1.6em; + text-align: center; + text-decoration: none; + + &:hover, + &:focus { + background-color: #999; + color: #000; + } +} + +.close-button--popup { + display: inline-block; + position: absolute; + top: 0; + right: 0; + font-size: 1.4em; +} + +.active-current { + background-color: #999; + + &::after { + content: ""; + width: 0; + height: 0; + position: absolute; + border: 10px solid transparent; + border-right-color: #eee; + right: 0; + top: 50%; + margin-top: -10px; + } +} + +.opacity03 { + opacity: 0.3; +} + +.add-to-wallabag-link-after { + background-color: #000; + color: #fff; + padding: 0 3px 2px; +} + +a.add-to-wallabag-link-after { + visibility: hidden; + position: absolute; + opacity: 0; + transition-duration: 2s; + transition-timing-function: ease-out; +} + +#article article a:hover + a.add-to-wallabag-link-after, +a.add-to-wallabag-link-after:hover { + opacity: 1; + visibility: visible; + transition-duration: 0.3s; + transition-timing-function: ease-in; +} + +a.add-to-wallabag-link-after::after { + content: "w"; +} + +#add-link-result { + font-weight: bold; + font-size: 0.9em; +} + +.btn-clickable { + cursor: pointer; +} diff --git a/app/Resources/static/themes/baggy/font/icomoon.eot b/app/Resources/static/themes/baggy/font/icomoon.eot deleted file mode 100644 index 248758fb..00000000 Binary files a/app/Resources/static/themes/baggy/font/icomoon.eot and /dev/null differ diff --git a/app/Resources/static/themes/baggy/font/icomoon.svg b/app/Resources/static/themes/baggy/font/icomoon.svg deleted file mode 100644 index b1b9c92a..00000000 --- a/app/Resources/static/themes/baggy/font/icomoon.svg +++ /dev/null @@ -1,501 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/Resources/static/themes/baggy/font/icomoon.ttf b/app/Resources/static/themes/baggy/font/icomoon.ttf deleted file mode 100644 index c03770c6..00000000 Binary files a/app/Resources/static/themes/baggy/font/icomoon.ttf and /dev/null differ diff --git a/app/Resources/static/themes/baggy/font/icomoon.woff b/app/Resources/static/themes/baggy/font/icomoon.woff deleted file mode 100644 index 121e0bdd..00000000 Binary files a/app/Resources/static/themes/baggy/font/icomoon.woff and /dev/null differ diff --git a/app/Resources/static/themes/baggy/js/init.js b/app/Resources/static/themes/baggy/index.js similarity index 69% rename from app/Resources/static/themes/baggy/js/init.js rename to app/Resources/static/themes/baggy/index.js index 05360a28..5d448018 100755 --- a/app/Resources/static/themes/baggy/js/init.js +++ b/app/Resources/static/themes/baggy/index.js @@ -1,31 +1,19 @@ -/* jQuery */ import $ from 'jquery'; -/* eslint-disable no-unused-vars */ -/* jquery has default scope */ -import cookie from 'jquery.cookie'; -import ui from 'jquery-ui-browserify'; -/* eslint-enable no-unused-vars */ - -/* Annotations */ -import annotator from 'annotator'; +/* Global imports */ +import '../_global/index'; /* Shortcuts */ -import './shortcuts/main'; -import './shortcuts/entry'; -import '../../_global/js/shortcuts/main'; -import '../../_global/js/shortcuts/entry'; +import './js/shortcuts/main'; +import './js/shortcuts/entry'; /* Tools */ -import { savePercent, retrievePercent } from '../../_global/js/tools'; -import toggleSaveLinkForm from './uiTools'; - -global.jquery = $; +import toggleSaveLinkForm from './js/uiTools'; -$.fn.ready(() => { - const $listmode = $('#listmode'); - const $listentries = $('#list-entries'); +/* Theme style */ +import './css/index.scss'; +$(document).ready(() => { /* ========================================================================== Menu ========================================================================== */ @@ -38,45 +26,12 @@ $.fn.ready(() => { } }); - /* ========================================================================== - List mode or Table Mode - ========================================================================== */ - - $listmode.click(() => { - if ($.cookie('listmode') === 1) { - // Cookie - $.removeCookie('listmode'); - - $listentries.removeClass('listmode'); - $listmode.removeClass('tablemode'); - $listmode.addClass('listmode'); - } else { - // Cookie - $.cookie('listmode', 1, { expires: 365 }); - - $listentries.addClass('listmode'); - $listmode.removeClass('listmode'); - $listmode.addClass('tablemode'); - } - }); - - /* ========================================================================== - Cookie listmode - ========================================================================== */ - - if ($.cookie('listmode') === 1) { - $listentries.addClass('listmode'); - $listmode.removeClass('listmode'); - $listmode.addClass('tablemode'); - } - /* ========================================================================== Add tag panel ========================================================================== */ - $('#nav-btn-add-tag').on('click', () => { - $('.nav-panel-add-tag').toggle(100); + $('.baggy-add-tag').toggle(100); $('.nav-panel-menu').addClass('hidden'); $('#tag_label').focus(); return false; @@ -95,39 +50,6 @@ $.fn.ready(() => { }); } - /* ========================================================================== - Annotations & Remember position - ========================================================================== */ - - if ($('article').length) { - const app = new annotator.App(); - - app.include(annotator.ui.main, { - element: document.querySelector('article'), - }); - - const x = JSON.parse($('#annotationroutes').html()); - app.include(annotator.storage.http, x); - - app.start().then(() => { - app.annotations.load({ entry: x.entryId }); - }); - - $(window).scroll(() => { - const scrollTop = $(window).scrollTop(); - const docHeight = $(document).height(); - const scrollPercent = (scrollTop) / (docHeight); - const scrollPercentRounded = Math.round(scrollPercent * 100) / 100; - savePercent(x.entryId, scrollPercentRounded); - }); - - retrievePercent(x.entryId); - - $(window).resize(() => { - retrievePercent(x.entryId); - }); - } - /** * Close window after adding entry if popup */ @@ -136,6 +58,23 @@ $.fn.ready(() => { window.close(); } + /** + if ($('article').size() > 0) { + const waypoint = new Waypoint({ + element: $('.wallabag-title').get(0), + handler: (direction) => { + console.log(direction); + if (direction === 'down') { + $('aside.tags').fadeIn('slow'); + } else { + $('aside.tags').fadeOut('slow'); + } + }, + offset: 250, + }); + } + */ + /** * Tags autocomplete */ @@ -283,25 +222,24 @@ $.fn.ready(() => { toggleBagit(); }); - const $bagitFormForm = $('#bagit-form-form'); + const bagitFormForm = $('#bagit-form-form'); /* ========================================================================== bag it link and close button ========================================================================== */ // send 'bag it link' form request via ajax - $bagitFormForm.submit((event) => { + bagitFormForm.submit((event) => { $('body').css('cursor', 'wait'); $('#add-link-result').empty(); $.ajax({ - type: $bagitFormForm.attr('method'), - url: $bagitFormForm.attr('action'), - data: $bagitFormForm.serialize(), + type: bagitFormForm.attr('method'), + url: bagitFormForm.attr('action'), + data: bagitFormForm.serialize(), success: function success() { $('#add-link-result').html('Done!'); - $('#plainurl').val(''); - $('#plainurl').blur(''); + $('#plainurl').val('').blur(''); $('body').css('cursor', 'auto'); }, error: function error() { @@ -319,7 +257,7 @@ $.fn.ready(() => { $('article a[href^="http"]').after( () => `' + 'title="add to wallabag">', ); $('.add-to-wallabag-link-after').click((event) => { diff --git a/app/Resources/static/themes/baggy/js/shortcuts/main.js b/app/Resources/static/themes/baggy/js/shortcuts/main.js index aed09aee..43ebf3be 100644 --- a/app/Resources/static/themes/baggy/js/shortcuts/main.js +++ b/app/Resources/static/themes/baggy/js/shortcuts/main.js @@ -1,3 +1,6 @@ +import $ from 'jquery'; +import Mousetrap from 'mousetrap'; + $(document).ready(() => { Mousetrap.bind('s', () => { $('#search').trigger('click'); diff --git a/app/Resources/static/themes/material/css/article.scss b/app/Resources/static/themes/material/css/article.scss new file mode 100644 index 00000000..8b67f6bd --- /dev/null +++ b/app/Resources/static/themes/material/css/article.scss @@ -0,0 +1,189 @@ +/* ========================================================================== + Article + ========================================================================== */ + +#article { + font-size: 20px; + margin: 0 auto; + max-width: 45em; + + article { + color: #424242; + font-size: 18px; + line-height: 1.7em; + + h1, + h2, + h3, + h4, + h5, + h6 { + color: #212121; + + strong { + font-weight: 500; + } + } + + h6 { + font-size: 1.2rem; + } + + h5 { + font-size: 1.6rem; + } + + h4 { + font-size: 1.9rem; + } + + h3 { + font-size: 2.2rem; + } + + h2 { + font-size: 2.5rem; + } + + h1 { + font-size: 2.7rem; + } + + a { + border-bottom: 1px dotted #03a9f4; + text-decoration: none; + } + + a:hover { + border-bottom-style: solid; + } + + ul { + padding-left: 30px; + } + + ul, + ul li { + list-style-type: disc; + } + + blockquote { + font-style: italic; + } + + strong { + font-weight: bold; + } + } + + img, + figure { + max-width: 100%; + height: auto; + } + + pre { + box-sizing: border-box; + margin: 0 0 1.75em; + border: #e3f2fd 1px solid; + width: 100%; + padding: 10px; + font-family: monospace; + font-size: 0.8em; + white-space: pre; + overflow: auto; + background: #f5f5f5; + border-radius: 3px; + } + + > header > h1 { + font-size: 2em; + margin: 2.1rem 0 0.68rem; + } + + aside { + .tools { + display: flex; + flex-flow: row wrap; + + .stats { + font-size: 0.8em; + margin: 8px 15px 5px; + + li { + display: inline-flex; + vertical-align: middle; + margin: 0 5px; + } + + a { + color: #000; + text-decoration: none; + } + } + + .tags { + float: right; + margin: 5px 15px 10px; + } + } + + .chip { + background-color: $blueAccentColor; + padding: 0 15px 0 10px; + margin: auto 2px; + + a, + i { + color: #fff; + } + + i.material-icons { + float: right; + font-size: 20px; + line-height: 32px; + padding-left: 8px; + } + } + } +} + +.reader-mode { + width: 70px !important; + transition: width 0.2s ease; + + .collapsible-body { + height: 0; + overflow: hidden; + } + + span { + opacity: 0; + transition: opacity 0.2s ease; + } + + &:hover { + width: 260px !important; + + .collapsible-body { + height: auto; + + li a i.material-icons { + margin: auto 5px auto -8px; + } + } + + span { + opacity: 1; + } + } +} + +.progress { + position: fixed; + top: 0; + width: 100%; + height: 3px; + margin: 0; + z-index: 9999; +} diff --git a/app/Resources/static/themes/material/css/cards.scss b/app/Resources/static/themes/material/css/cards.scss new file mode 100644 index 00000000..f5b79193 --- /dev/null +++ b/app/Resources/static/themes/material/css/cards.scss @@ -0,0 +1,194 @@ +/* ========================================================================== + Cards + ========================================================================== */ + +main { + #content { + padding: 0 0.5rem; + } + + ul.row { + padding: 0 0.75rem; + } +} + +.data .card .card-body { + height: 19em; + overflow: hidden; +} + +.card { + .card-content .card-title, + .card-reveal .card-title { + line-height: 22.8px; + max-height: 80px; + font-size: 19px; + font-family: roberto, "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #313131; + } + + .card-stacked .card-content .card-title { + display: inline-block; + } + + .card-content .activator, + .card-reveal .activator { + cursor: pointer; + font-family: "Material Icons"; + } + + .card-content i.right, + .card-reveal i.right { + margin-left: 0; + } + + .card-content .original { + line-height: 24px; + font-size: 15px; + } + + .card-entry-labels { + position: absolute; + top: 10px; + z-index: 90; + max-width: 50%; + } + + .card-entry-labels-hidden { + margin: 2.5px auto; + } + + .card-entry-labels-hidden li { + display: inline-block; + background-color: $blueAccentColor; + margin: 0 5px; + padding: 5px 12px; + border-radius: 3px; + color: #fff; + max-height: 2em; + max-width: calc(100% - 15px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .card-entry-labels-hidden li { + display: inline-block; + background-color: $blueAccentColor; + margin: 0 5px; + padding: 5px 12px; + border-radius: 3px; + color: #fff; + max-height: 2em; + max-width: calc(100% - 15px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .card-content .estimatedTime { + margin-bottom: 10px; + } + + .card-action { + padding: 10px 5px 10px 15px; + + ul.links { + margin: 0; + font-size: 24px; + line-height: 24px; + } + + a { + color: #fff; + margin: 0; + } + + a:hover { + color: #fff; + } + + ul.tools li a.tool { + margin-right: 5px !important; + } + + .reading-time { + display: inline-flex; + vertical-align: middle; + + span { + margin-right: 5px; + } + } + } + + .card-image { + height: 10em; + } + + .card-fullimage { + height: 13.5em; + } + + .card-image .preview, + .card-fullimage .preview { + height: 14em; + background: no-repeat 50%/cover; + } + + &.sw { + max-width: 370px; + margin-left: auto; + margin-right: auto; + } +} + +a.original:not(.waves-effect) { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; +} + +.card-entry-labels li, +.card-tag-labels li { + margin: 10px 10px 10px auto; + padding: 5px 12px 5px 16px !important; + background-color: $blueAccentColor; + border-radius: 0 3px 3px 0; + color: #fff; + cursor: default; + max-height: 2em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-entry-tags a, +.card-entry-labels a, +.card-tag-labels a, +.card-entry-labels-hidden a, +#list .chip a { + text-decoration: none; + font-weight: normal; + color: #fff; +} + +.card-stacked { + &:hover ul.tools-list { + display: block; + } + + ul.tools-list { + display: none; + } +} + +.quickstart .card .card-action a, +.quickstart .card .card-action a:hover { + color: #fff !important; +} + +.settings .div_tabs { + padding-bottom: 15px; +} diff --git a/app/Resources/static/themes/material/css/entries.scss b/app/Resources/static/themes/material/css/entries.scss new file mode 100644 index 00000000..b6a46a9e --- /dev/null +++ b/app/Resources/static/themes/material/css/entries.scss @@ -0,0 +1,87 @@ +/* ========================================================================== + * Entries + * ========================================================================== */ + +.results { + height: 1em; + + .nb-results, + .pagination { + margin: 15px 15px 0; + } + + .nb-results { + display: inline-flex; + } + + a { + color: #444; + } +} + +.pagination { + float: right; + + ul { + margin: 0 !important; + + .prev.disabled, + .next.disabled { + display: none; + } + } + + li { + padding: 0; + } + + a { + padding: 0 10px; + height: 30px; + display: block; + } + + .disabled { + margin-right: 10px; + margin-left: 10px; + } + + li.active span { + padding: 0 10px; + height: 30px; + display: block; + color: #fff; + } +} + +.page-footer .footer-copyright { + min-width: 50px; + height: auto !important; + line-height: 1em !important; + + p { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + } +} + +.hidden { + display: none; +} + +.picker__date-display { + display: none; +} + +footer { + &.page-footer { + margin-top: 10px; + padding-top: 0; + } + + .row { + margin-bottom: 10px; + } +} diff --git a/app/Resources/static/themes/material/css/filters.scss b/app/Resources/static/themes/material/css/filters.scss new file mode 100644 index 00000000..299dad3d --- /dev/null +++ b/app/Resources/static/themes/material/css/filters.scss @@ -0,0 +1,15 @@ +/* ========================================================================== + * Filters slider + * ========================================================================== */ + +#filters { + button { + padding: 0; + width: 100%; + } + + div.with-checkbox { + height: 3rem; + margin-top: 0; + } +} diff --git a/app/Resources/static/themes/material/css/fonts.scss b/app/Resources/static/themes/material/css/fonts.scss new file mode 100644 index 00000000..743f1a84 --- /dev/null +++ b/app/Resources/static/themes/material/css/fonts.scss @@ -0,0 +1,13 @@ +/* ========================================================================== + * Fonts + * ========================================================================== */ + +/** + * Icomoon + */ +@font-face { + font-family: icomoon; + src: url("~icomoon-free-npm/Font/IcoMoon-Free.ttf"); + font-weight: normal; + font-style: normal; +} diff --git a/app/Resources/static/themes/material/css/icons.scss b/app/Resources/static/themes/material/css/icons.scss new file mode 100644 index 00000000..e7f215c0 --- /dev/null +++ b/app/Resources/static/themes/material/css/icons.scss @@ -0,0 +1,189 @@ +/* ========================================================================== + * Icons + * ========================================================================== */ + +/** + * + * Material icons + * + */ +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + width: 1em; + height: 1em; + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; + + .md-18 { + font-size: 18px; + } + + .md-24 { + font-size: 24px; + } + + .md-36 { + font-size: 36px; + } + + .md-48 { + font-size: 48px; + } + + .md-dark { + color: rgba(0, 0, 0, 0.54); + + .md-inactive { + color: rgba(0, 0, 0, 0.26); + } + } + + .md-light { + color: rgba(255, 255, 255, 1); + + .md-inactive { + color: rgba(255, 255, 255, 0.3); + } + } +} + +/** + * + * Icomoon icons + * + */ +[class^="icon-"]::before, +[class*=" icon-"]::before { + font-family: icomoon; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + background-size: 24px; + + /* Enable Ligatures ================ */ + letter-spacing: 0; + font-feature-settings: "liga"; +} + +.icon-eye::before { + content: "\e9ce"; +} + +.icon-no-eye::before { + content: "\e9d1"; +} + +.icon-calendar::before { + content: "\e953"; +} + +.icon-mail::before { + content: "\ea86"; +} + +.icon-time::before { + content: "\e952"; +} + +a.icon-image { + background-repeat: no-repeat; + padding-right: 0.4em !important; + padding-left: 0 !important; + margin-left: 25px; + + &::before { + content: ""; + display: block; + width: 24px; + height: 24px; + float: left; + margin: 7px 1.5px 0 0; + } + + &.carrot::before { + background: url("../../_global/img/icons/carrot-icon--black.png") no-repeat center/90%; + } + + &.diaspora::before { + background: url("../../_global/img/icons/diaspora-icon--black.png") no-repeat center/80%; + } + + &.unmark::before { + background: url("../../_global/img/icons/unmark-icon--black.png") no-repeat center/80%; + } + + &.shaarli::before { + background: url("../../_global/img/icons/shaarli.png") no-repeat center/80%; + } + + &.scuttle::before { + background: url("../../_global/img/icons/scuttle.png") no-repeat center/80%; + } +} + +.icon-google-plus2::before { + content: "\ea89"; +} + +.icon-facebook2::before { + content: "\ea8d"; +} + +.icon-twitter::before { + content: "\ea96"; +} + +.icon-apple::before { + content: "\eabf"; +} + +.icon-android::before { + content: "\eac1"; +} + +.icon-chrome::before { + content: "\eae5"; +} + +.icon-firefox::before { + content: "\eae6"; +} + +.icon-link::before { + content: "\e9cb"; +} + +footer [class^="icon-"], +footer [class*=" icon-"] { + font-size: 2em; + transition: text-shadow 0.2s ease; + padding-right: 10px; +} + +footer [class^="icon-"]:hover, +footer [class*=" icon-"]:hover { + text-shadow: 0 0 10px rgba(0, 0, 0, 0.3); +} diff --git a/app/Resources/static/themes/material/css/index.scss b/app/Resources/static/themes/material/css/index.scss new file mode 100644 index 00000000..285a6504 --- /dev/null +++ b/app/Resources/static/themes/material/css/index.scss @@ -0,0 +1,17 @@ +@import 'variables'; + +/* Style */ +@import 'article'; +@import 'cards'; +@import 'entries'; +@import 'filters'; +@import 'layout'; +@import 'nav'; +@import 'sidenav'; +@import 'various'; + +/* Tools */ +@import 'fonts'; +@import 'icons'; +@import 'print'; +@import 'media_queries'; diff --git a/app/Resources/static/themes/material/css/layout.scss b/app/Resources/static/themes/material/css/layout.scss new file mode 100755 index 00000000..8b8b06e6 --- /dev/null +++ b/app/Resources/static/themes/material/css/layout.scss @@ -0,0 +1,50 @@ +/* ========================================================================== + Layout + ========================================================================== */ + +body { + display: flex; + min-height: 100vh; + flex-direction: column; + background: #fafafa; + + &.login main { + padding: 0; + min-height: 100vh; + } +} + +.border-bottom { + border-bottom: 1px solid #ddd; +} + +nav, +main, +footer { + padding-left: 240px; +} + +main, +#content, +.valign-wrapper { + height: 100%; +} + +#main { + flex: 1 0 auto; + + .logo { + a { + height: 100pt; + } + + img { + height: 100pt; + width: 100pt; + } + + &:hover { + background: transparent; + } + } +} diff --git a/app/Resources/static/themes/material/css/main.css b/app/Resources/static/themes/material/css/main.css deleted file mode 100755 index ee4ad4e0..00000000 --- a/app/Resources/static/themes/material/css/main.css +++ /dev/null @@ -1,1019 +0,0 @@ -/* ========================================================================== - Sommaire - - 0 = Common - 1 = Nav - 2 = Side-nav - 3 = Filters slider - 4 = Cards - 5 = Article - 6 = Media queries - 7 = Font - 8 = Others - - ========================================================================== */ - -/* ========================================================================== - 0 = Common - ========================================================================== */ - -/** - * - * Material icons - * - */ - -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(../fonts/MaterialIcons-Regular.eot); - - /* For IE6-8 */ - src: local("Material Icons"), local("MaterialIcons-Regular"), url(../fonts/MaterialIcons-Regular.woff2) format("woff2"), url(../fonts/MaterialIcons-Regular.woff) format("woff"), url(../fonts/MaterialIcons-Regular.ttf) format("truetype"); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - width: 1em; - height: 1em; - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} - -/* Rules for sizing the icon. */ -.material-icons.md-18 { font-size: 18px; } -.material-icons.md-24 { font-size: 24px; } -.material-icons.md-36 { font-size: 36px; } -.material-icons.md-48 { font-size: 48px; } - -/* Rules for using icons as black on a light background. */ -.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } -.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } - -/* Rules for using icons as white on a dark background. */ -.material-icons.md-light { color: rgba(255, 255, 255, 1); } -.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } - -/** - * - * Icomoon icons - * - */ - -@font-face { - font-family: icomoon; - src: url("../fonts/IcoMoon-Free.ttf"); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"]::before, -[class*=" icon-"]::before { - font-family: icomoon; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - background-size: 24px; - - /* Enable Ligatures ================ */ - letter-spacing: 0; - -webkit-font-feature-settings: "liga"; - -moz-font-feature-settings: "liga=1"; - -moz-font-feature-settings: "liga"; - -ms-font-feature-settings: "liga" 1; - -o-font-feature-settings: "liga"; - font-feature-settings: "liga"; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-image { - background-size: 16px; - background-repeat: no-repeat; - padding-right: 1em !important; - padding-left: 1em !important; -} - -.icon-eye::before { - content: "\e9ce"; -} - -.icon-no-eye::before { - content: "\e9d1"; -} - -.icon-calendar::before { - content: "\e953"; -} - -.icon-mail::before { - content: "\ea86"; -} - -.icon-time::before { - content: "\e952"; -} - -/* Carrot (http://carrot.org) */ -.icon-image--carrot { - background-image: url("../../_global/img/icons/carrot-icon--black.png"); -} - -/* Diaspora */ -.icon-image--diaspora { - background-image: url("../../_global/img/icons/diaspora-icon--black.png"); -} - -/* Unmark.it */ -.icon-image--unmark { - background-image: url("../../_global/img/icons/unmark-icon--black.png"); -} - -/* Shaarli */ -.icon-image--shaarli { - background-image: url("../../_global/img/icons/shaarli.png"); -} - -body { - display: flex; - min-height: 100vh; - flex-direction: column; - background: #fafafa; -} - -body.login main { - padding: 0; - min-height: 100vh; -} - -.border-bottom { - border-bottom: 1px solid #ddd; -} - -nav, -main, -footer { - padding-left: 240px; -} - -main, -#content, -.valign-wrapper { - height: 100%; -} - -#main { - flex: 1 0 auto; -} - -.results { - height: 1em; -} - -.results .nb-results, -.results .pagination { - margin: 15px; - margin-bottom: 0; -} - -.results .nb-results { - display: inline-flex; -} - -.results a { - color: #444; -} - -.pagination { - float: right; -} - -.pagination ul { - margin: 0 !important; -} - -.pagination li { - padding: 0; -} - -.pagination a { - padding: 0 10px; - height: 30px; - display: block; -} - -.pagination .disabled { - margin-right: 10px; - margin-left: 10px; -} - -div.pagination ul .prev.disabled, -div.pagination ul .next.disabled { - display: none; -} - -.pagination li.active span { - padding: 0 10px; - height: 30px; - display: block; - color: #fff; -} - -.page-footer .footer-copyright { - min-width: 50px; - height: auto !important; - line-height: 1em !important; -} - -.page-footer .footer-copyright p { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; -} - -.hidden { - display: none; -} - -.picker__date-display { - display: none; -} - -footer.page-footer { - margin-top: 10px; - padding-top: 0; -} - -footer .row { - margin-bottom: 10px; -} - -/* ========================================================================== - 1 = Nav - ========================================================================== */ - -nav input { - color: #aaa; -} - -nav { - height: auto; -} - -.nav-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - min-height: 64px; -} - -.nav-wrapper .button-collapse { - padding: 0 15px; -} - -.nav-input { - display: none; -} - -.nav-panel-buttom { - display: flex; - flex-grow: 1; - justify-content: flex-end; -} - -.nav-panel-buttom li { - max-height: 64px; -} - -.nav-panels { - transition: background 0.2s ease; -} - -.nav-panel-add .add, -.nav-panel-search .search, -.nav-panels .close { - color: #444 !important; -} - -.nav-panels .action { - padding-left: 0.75rem; - font-size: 2.1rem; - white-space: nowrap; -} - -.nav-panels .input-field input { - display: block; - line-height: inherit; - padding-left: 4rem !important; - width: calc(100% - 8rem); -} - -.nav-panels .input-field input:focus { - background-color: #fff; - border: 0; - box-shadow: none; - color: #444; -} - -.input-field.nav-panel-add label, -.input-field.nav-panel-search label { - left: 1rem; -} - -.input-field.nav-panel-add .close, -.input-field.nav-panel-search .close { - position: absolute; - top: 0; - right: 1rem; - color: transparent; - cursor: pointer; - font-size: 2rem; - transition: 0.3s color; -} - -#button_filters { - display: none; -} - -#button_export { - display: none; -} - -.input-field.nav-panel-add, -.input-field.nav-panel-add form, -.input-field.nav-panel-search, -.input-field.nav-panel-search form { - display: flex; - flex: 1; -} - -/* ========================================================================== - 2 = Side-nav - ========================================================================== */ - -.side-nav.fixed a { - font-size: 13px; - line-height: 44px; - height: 44px; -} - -.side-nav .collapsible-header, -.side-nav.fixed .collapsible-header { - height: 45px; - line-height: 44px; - padding: 0 20px; -} - -.bold > a { - font-weight: bold; -} - -.side-nav > li.logo { - line-height: 0; - text-align: center; -} - -#main .logo a { - height: 100pt; -} - -#main .logo img { - height: 100pt; - width: 100pt; -} - -#main .logo:hover { - background: transparent; -} - -.side-nav li { - padding: 0; -} - -.side-nav a { - margin: 0 1rem; -} - -span.numberItems { - float: right; -} - -nav ul a:hover { - background-color: initial; -} - -/* ========================================================================== - * 3 = Filters slider - * ========================================================================== */ - -#filters button { - padding: 0; - width: 100%; -} - -.side-nav.fixed.right-aligned { - right: -250px; - left: auto !important; -} - -#filters div.with-checkbox { - height: 3rem; - margin-top: 0; -} - -/* ========================================================================== - 4 = Cards - ========================================================================== */ - -main #content { - padding: 0 0.5rem; -} - -main ul.row { - padding: 0 0.75rem; -} - -.data .card .card-body { - height: 19em; - overflow: hidden; -} - -.card .card-content .card-title, -.card .card-reveal .card-title { - line-height: 22.8px; - max-height: 80px; - font-size: 19px; - font-family: roberto, "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #313131; -} - -.card .card-content .activator, -.card .card-reveal .activator { - cursor: pointer; - font-family: "Material Icons"; -} - -.card .card-content i.right, -.card .card-reveal i.right { - margin-left: 0; -} - -.card .card-content .original { - line-height: 24px; - font-size: 15px; -} - -a.original { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - display: block; -} - -.card .card-entry-labels { - position: absolute; - top: 10px; - z-index: 90; - max-width: 50%; -} - -.card .card-entry-labels li, -.card-tag-labels li { - margin: 10px 10px 10px auto; - padding: 5px 12px 5px 16px !important; - background-color: rgba(0, 151, 167, 0.85); - border-radius: 0 3px 3px 0; - color: #fff; - cursor: default; - max-height: 2em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.card .card-entry-labels-hidden { - margin: 2.5px auto; -} - -.card .card-entry-labels-hidden li { - display: inline-block; - background-color: rgba(0, 151, 167, 0.85); - margin: 0 5px; - padding: 5px 12px; - border-radius: 3px; - color: #fff; - max-height: 2em; - max-width: calc(100% - 15px); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.card .card-entry-labels-hidden li:first-child { - margin-left: 0; -} - -.card-entry-tags a, -.card-entry-labels a, -.card-tag-labels a, -.card-entry-labels-hidden a, -#list .chip a { - text-decoration: none; - font-weight: normal; - color: #fff; -} - -.card .card-content .estimatedTime { - margin-bottom: 10px; -} - -.card .card-action { - padding: 10px 5px 10px 15px; -} - -.card .card-action ul.links { - margin: 0; - font-size: 24px; - line-height: 24px; -} - -.card .card-action ul.tools li a.tool { - margin-right: 5px !important; -} - -.card-stacked:hover ul.tools-list { - display: block; -} - -.card-stacked ul.tools-list { - display: none; -} - -.card .card-action a { - color: #fff; - margin: 0; -} - -.card .card-action a:hover { - color: #fff; -} - -.card .card-action .reading-time { - display: inline-flex; - vertical-align: middle; -} - -.quickstart .card .card-action a, -.quickstart .card .card-action a:hover { - color: #fff !important; -} - -.settings .div_tabs { - padding-bottom: 15px; -} - -.card.sw { - max-width: 370px; - margin-left: auto; - margin-right: auto; -} - -.card .card-image { - height: 10em; -} - -.card .card-fullimage { - height: 13.5em; -} - -.card .card-image .preview, -.card .card-fullimage .preview { - height: 14em; - background-size: cover; - background-repeat: no-repeat; - background-position: 50%; -} - -/* ========================================================================== - 5 = Article - ========================================================================== */ - -#article { - font-size: 20px; - margin: 0 auto; - max-width: 45em; -} - -#article article { - color: #424242; - font-size: 18px; - line-height: 1.7em; -} - -#article article h1, -#article article h2, -#article article h3, -#article article h4, -#article article h5, -#article article h6 { - color: #212121; -} - -#article article h1 strong, -#article article h2 strong, -#article article h3 strong, -#article article h4 strong, -#article article h5 strong, -#article article h6 strong { - font-weight: 500; -} - -#article article h6 { - font-size: 1.2rem; -} - -#article article h5 { - font-size: 1.6rem; -} - -#article article h4 { - font-size: 1.9rem; -} - -#article article h3 { - font-size: 2.2rem; -} - -#article article h2 { - font-size: 2.5rem; -} - -#article article h1 { - font-size: 2.7rem; -} - -#article img, -#article figure { - max-width: 100%; - height: auto; -} - -#article article a { - border-bottom: 1px dotted #03a9f4; - text-decoration: none; -} - -#article article a:hover { - border-bottom-style: solid; -} - -#article article ul { - padding-left: 30px; -} - -#article article ul, -#article article ul li { - list-style-type: disc; -} - -#article article blockquote { - font-style: italic; -} - -#article article strong { - font-weight: bold; -} - -#article article pre { - box-sizing: border-box; - margin: 0 0 1.75em; - border: #e3f2fd 1px solid; - width: 100%; - padding: 10px; - font-family: monospace; - font-size: 0.8em; - white-space: pre; - overflow: auto; - background: #f5f5f5; - border-radius: 3px; -} - -#article > header > h1 { - font-size: 2em; - margin: 2.1rem 0 0.68rem; -} - -.reader-mode { - width: 95px !important; - transition: width 0.2s ease; -} - -.reader-mode:hover { - width: 240px !important; -} - -.reader-mode .collapsible-body { - height: 0; - overflow: hidden; -} - -.reader-mode:hover .collapsible-body { - height: auto; -} - -.reader-mode span { - opacity: 0; - transition: opacity 0.2s ease; -} - -.reader-mode:hover span { - opacity: 1; -} - -.progress { - position: fixed; - top: 0; - width: 100%; - height: 3px; - margin: 0; - z-index: 9999; -} - -#article aside .tools { - font-size: 0.8em; - display: flex; - flex-flow: row wrap; - margin: 0 auto; -} - -article aside .tools li { - display: inline-flex; - vertical-align: middle; -} - -#article aside .tools a { - color: #000; - text-decoration: none; -} - -#article aside #list { - float: right; - margin: 0 15px 10px; -} - -#article aside .chip { - background-color: rgba(0, 151, 167, 0.85); - padding: 0 15px 0 10px; - margin: auto 2px; -} - -#article aside .chip a, -#article aside .chip i { - color: #fff; -} - -/* ========================================================================== - 6 = Media queries - ========================================================================== */ - -@media only screen and (max-width: 992px) { - header, - main, - footer { - padding-left: 0; - } - - nav, - main, - footer { - padding-left: 0; - } - - .pagination { - width: auto; - } - - .nav-panels .action { - padding-right: 0.75rem; - } - - .nav-panel-buttom { - justify-content: space-around; - } - - #article { - max-width: 35em; - margin-left: auto; - margin-right: auto; - font-size: 18px; - } - - #article > header > h1 { - font-size: 1.33em; - } - - .reader-mode { - width: 240px !important; - } - - .reader-mode span { - opacity: 1; - } - - .tabs { - display: inline-block; - height: auto; - } - - .tab { - min-width: 100%; - } - - .indicator { - display: none; - } - - .pagination li.prev, - .pagination li.next { - width: auto; - } - - .drag-target + .drag-target { - height: 50%; - } - - .drag-target + .drag-target + .drag-target { - top: 50%; - } -} - -@media only screen and (min-width: 1200px) and (max-width: 1650px) { - .row .col.l3 { - width: 33.33333%; - margin-left: 0; - } -} - -@media only screen and (min-width: 993px) and (max-width: 1200px) { - .row .col.l1 { - width: 25%; - margin-left: 0; - } - - .row .col.l2 { - width: 33.33333%; - margin-left: 0; - } - - .row .col.l3 { - width: 41.66667%; - margin-left: 0; - } - - .row .col.l4 { - width: 50%; - margin-left: 0; - } - - .row .col.l5 { - width: 58.33333%; - margin-left: 0; - } - - .row .col.l6 { - width: 66.66667%; - margin-left: 0; - } - - .row .col.l7 { - width: 75%; - margin-left: 0; - } - - .row .col.l8 { - width: 83.33333%; - margin-left: 0; - } - - .row .col.l9 { - width: 91.66667%; - margin-left: 0; - } - - .row .col.l10 { - width: 100%; - margin-left: 0; - } -} - -@media only screen and (max-width: 350px) { - .nb-results { - display: none; - } - - main ul.row { - padding: 0; - } - - .row .col { - padding: 0; - } -} - -/* ========================================================================== - 7 = Font - ========================================================================== */ - -.icon-google-plus2::before { - content: "\ea89"; -} - -.icon-facebook2::before { - content: "\ea8d"; -} - -.icon-twitter::before { - content: "\ea96"; -} - -.icon-apple::before { - content: "\eabf"; -} - -.icon-android::before { - content: "\eac1"; -} - -.icon-chrome::before { - content: "\eae5"; -} - -.icon-firefox::before { - content: "\eae6"; -} - -.icon-link::before { - content: "\e9cb"; -} - -footer [class^="icon-"], -footer [class*=" icon-"] { - font-size: 2em; - transition: text-shadow 0.2s ease; - padding-right: 10px; -} - -footer [class^="icon-"]:hover, -footer [class*=" icon-"]:hover { - text-shadow: 0 0 10px rgba(0, 0, 0, 0.3); -} - -/* ========================================================================== - 8 = Others - ========================================================================== */ - -/* force height on non-input field in the settings page */ -div.settings div.input-field div, -div.settings div.input-field ul { - margin-top: 40px; -} - -/* but avoid to kill all file input */ -div.settings div.file-field div { - margin-top: inherit; -} - -.input-field label.active { - font-size: 1rem; -} - -nav .input-field input { - margin: 0; -} diff --git a/app/Resources/static/themes/material/css/media_queries.scss b/app/Resources/static/themes/material/css/media_queries.scss new file mode 100644 index 00000000..96f34494 --- /dev/null +++ b/app/Resources/static/themes/material/css/media_queries.scss @@ -0,0 +1,149 @@ +/* ========================================================================== + Media queries + ========================================================================== */ + +@media only screen and (max-width: 992px) { + header, + main, + footer { + padding-left: 0; + } + + nav, + main, + footer { + padding-left: 0; + } + + .pagination { + width: auto; + } + + .nav-panels .action { + padding-right: 0.75rem; + } + + .nav-panel-buttom { + justify-content: space-around; + } + + #article { + max-width: 35em; + margin-left: auto; + margin-right: auto; + font-size: 18px; + + > header > h1 { + font-size: 1.33em; + } + } + + .reader-mode { + width: 240px !important; + + span { + opacity: 1; + } + } + + .tabs { + display: inline-block; + height: auto; + } + + .tab { + min-width: 100%; + } + + .indicator { + display: none; + } + + .pagination li.prev, + .pagination li.next { + width: auto; + } + + .drag-target + .drag-target { + height: 50%; + } + + .drag-target + .drag-target + .drag-target { + top: 50%; + } +} + +@media only screen and (min-width: 1200px) and (max-width: 1650px) { + .row .col.l3 { + width: 33.33333%; + margin-left: 0; + } +} + +@media only screen and (min-width: 993px) and (max-width: 1200px) { + .row { + .col.l1 { + width: 25%; + margin-left: 0; + } + + .col.l2 { + width: 33.33333%; + margin-left: 0; + } + + .col.l3 { + width: 41.66667%; + margin-left: 0; + } + + .col.l4 { + width: 50%; + margin-left: 0; + } + + .col.l5 { + width: 58.33333%; + margin-left: 0; + } + + .col.l6 { + width: 66.66667%; + margin-left: 0; + } + + .col.l7 { + width: 75%; + margin-left: 0; + } + + .col.l8 { + width: 83.33333%; + margin-left: 0; + } + + .col.l9 { + width: 91.66667%; + margin-left: 0; + } + + .col.l10 { + width: 100%; + margin-left: 0; + } + } +} + +@media only screen and (max-width: 350px) { + .nb-results { + display: none; + } + + main ul.row { + padding: 0; + } + + .row .col { + padding: 0; + } +} diff --git a/app/Resources/static/themes/material/css/nav.scss b/app/Resources/static/themes/material/css/nav.scss new file mode 100644 index 00000000..1a25a5be --- /dev/null +++ b/app/Resources/static/themes/material/css/nav.scss @@ -0,0 +1,106 @@ + +/* ========================================================================== + Nav + ========================================================================== */ + +nav { + height: auto; + + input { + color: #aaa; + } + + ul a:hover { + background-color: initial; + } +} + +.nav-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + min-height: 64px; + + .button-collapse { + padding: 0 15px; + } +} + +.nav-input { + display: none; +} + +.nav-panel-buttom { + display: flex; + flex-grow: 1; + justify-content: flex-end; + + li { + max-height: 64px; + } +} + +.nav-panel-add .add, +.nav-panel-search .search, +.nav-panels .close { + color: #444 !important; +} + +.nav-panels { + transition: background 0.2s ease; + + .action { + padding-left: 0.75rem; + font-size: 2.1rem; + white-space: nowrap; + } + + .input-field input { + display: block; + line-height: inherit; + padding-left: 4rem !important; + width: calc(100% - 8rem); + height: 4.1rem; + } + + .input-field input:focus { + background-color: #fff; + border: 0; + box-shadow: none; + color: #444; + } +} + +.input-field { + &.nav-panel-add label, + &.nav-panel-search label { + left: 1rem; + } + + &.nav-panel-add .close, + &.nav-panel-search .close { + position: absolute; + top: 0; + right: 1rem; + color: transparent; + cursor: pointer; + font-size: 2rem; + transition: 0.3s color; + } + + &.nav-panel-add, + &.nav-panel-add form, + &.nav-panel-search, + &.nav-panel-search form { + display: flex; + flex: 1; + } +} + +#button_filters { + display: none; +} + +#button_export { + display: none; +} diff --git a/app/Resources/static/themes/material/css/print.css b/app/Resources/static/themes/material/css/print.scss similarity index 94% rename from app/Resources/static/themes/material/css/print.css rename to app/Resources/static/themes/material/css/print.scss index 98505aae..967a8c2b 100755 --- a/app/Resources/static/themes/material/css/print.css +++ b/app/Resources/static/themes/material/css/print.scss @@ -64,11 +64,8 @@ #main { width: 100%; - padding: 0; margin: 0; - margin-left: 0; - padding-right: 0; - padding-bottom: 0; + padding: 0; } #article { diff --git a/app/Resources/static/themes/material/css/sidenav.scss b/app/Resources/static/themes/material/css/sidenav.scss new file mode 100644 index 00000000..416dc1c7 --- /dev/null +++ b/app/Resources/static/themes/material/css/sidenav.scss @@ -0,0 +1,45 @@ +/* ========================================================================== + Side-nav + ========================================================================== */ + +.side-nav { + width: 240px; + + li { + padding: 0; + + &.logo > a:hover { + background: initial; + } + } + + a { + margin: 0; + } + + &.fixed a { + font-size: 13px; + line-height: 44px; + height: 44px; + } + + .collapsible-header, + &.fixed .collapsible-header { + height: 45px; + line-height: 44px; + padding: 0 20px; + } + + > li.logo { + line-height: 0; + text-align: center; + } +} + +.bold > a { + font-weight: bold; +} + +span.numberItems { + float: right; +} diff --git a/app/Resources/static/themes/material/css/variables.scss b/app/Resources/static/themes/material/css/variables.scss new file mode 100644 index 00000000..25e22aca --- /dev/null +++ b/app/Resources/static/themes/material/css/variables.scss @@ -0,0 +1,5 @@ +/* ========================================================================== + Variables + ========================================================================== */ + +$blueAccentColor: rgba(0, 151, 167, 0.85); diff --git a/app/Resources/static/themes/material/css/various.scss b/app/Resources/static/themes/material/css/various.scss new file mode 100644 index 00000000..7daf40ec --- /dev/null +++ b/app/Resources/static/themes/material/css/various.scss @@ -0,0 +1,32 @@ +/* ========================================================================== + * Various + * ========================================================================== */ + +div.settings div.file-field { + /* force height on non-input field in the settings page */ + div, + ul { + margin-top: 40px; + } + + /* but avoid to kill all file input */ + div { + margin-top: inherit; + } +} + +.input-field label.active { + font-size: 1rem; +} + +nav .input-field input { + margin: 0; +} + +.tabs { + display: flex; +} + +.tab { + flex: 1; +} diff --git a/app/Resources/static/themes/material/js/init.js b/app/Resources/static/themes/material/index.js similarity index 57% rename from app/Resources/static/themes/material/js/init.js rename to app/Resources/static/themes/material/index.js index 0b2832c0..d6afbb8a 100755 --- a/app/Resources/static/themes/material/js/init.js +++ b/app/Resources/static/themes/material/index.js @@ -1,21 +1,21 @@ -/* jQuery */ import $ from 'jquery'; -/* Annotations */ -import annotator from 'annotator'; +/* Materialize imports */ +import 'materialize-css/dist/css/materialize.css'; +import 'materialize-css/dist/js/materialize'; + +/* Global imports */ +import '../_global/index'; /* Tools */ -import { savePercent, retrievePercent, initFilters, initExport } from '../../_global/js/tools'; +import { initExport, initFilters } from './js/tools'; /* Import shortcuts */ -import './shortcuts/main'; -import './shortcuts/entry'; -import '../../_global/js/shortcuts/main'; -import '../../_global/js/shortcuts/entry'; - -require('materialize'); // eslint-disable-line +import './js/shortcuts/main'; +import './js/shortcuts/entry'; -global.jQuery = $; +/* Theme style */ +import './css/index.scss'; $(document).ready(() => { // sideNav @@ -30,6 +30,7 @@ $(document).ready(() => { formatSubmit: 'dd/mm/yyyy', hiddenName: true, format: 'dd/mm/yyyy', + container: 'body', }); initFilters(); initExport(); @@ -74,37 +75,4 @@ $(document).ready(() => { const scrollPercent = (s / (d - c)) * 100; $('.progress .determinate').css('width', `${scrollPercent}%`); }); - -/* ========================================================================== - Annotations & Remember position - ========================================================================== */ - - if ($('article').length) { - const app = new annotator.App(); - const x = JSON.parse($('#annotationroutes').html()); - - app.include(annotator.ui.main, { - element: document.querySelector('article'), - }); - - app.include(annotator.storage.http, x); - - app.start().then(() => { - app.annotations.load({ entry: x.entryId }); - }); - - $(window).scroll(() => { - const scrollTop = $(window).scrollTop(); - const docHeight = $(document).height(); - const scrollPercent = (scrollTop) / (docHeight); - const scrollPercentRounded = Math.round(scrollPercent * 100) / 100; - savePercent(x.entryId, scrollPercentRounded); - }); - - retrievePercent(x.entryId); - - $(window).resize(() => { - retrievePercent(x.entryId); - }); - } }); diff --git a/app/Resources/static/themes/material/js/shortcuts/main.js b/app/Resources/static/themes/material/js/shortcuts/main.js index 0a2d2a69..042b423c 100644 --- a/app/Resources/static/themes/material/js/shortcuts/main.js +++ b/app/Resources/static/themes/material/js/shortcuts/main.js @@ -23,6 +23,16 @@ $(document).ready(() => { return; } + /* Show nothing on login/register page */ + if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) { + return; + } + + /* Show nothing on login/register page */ + if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) { + return; + } + /* Focus current card */ toggleFocus(card); diff --git a/app/Resources/static/themes/material/js/tools.js b/app/Resources/static/themes/material/js/tools.js new file mode 100644 index 00000000..39398fd8 --- /dev/null +++ b/app/Resources/static/themes/material/js/tools.js @@ -0,0 +1,24 @@ +import $ from 'jquery'; + +function initFilters() { + // no display if filters not available + if ($('div').is('#filters')) { + $('#button_filters').show(); + $('.js-filters-action').sideNav({ edge: 'right' }); + $('#clear_form_filters').on('click', () => { + $('#filters input').val(''); + $('#filters :checked').removeAttr('checked'); + return false; + }); + } +} + +function initExport() { + // no display if export not available + if ($('div').is('#export')) { + $('#button_export').show(); + $('.js-export-action').sideNav({ edge: 'right' }); + } +} + +export { initExport, initFilters }; diff --git a/app/config/config.yml b/app/config/config.yml index d7231112..116bb04c 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -3,6 +3,10 @@ imports: - { resource: security.yml } - { resource: services.yml } +parameters: + # Allows to use the live reload feature for changes in assets + use_webpack_dev_server: false + framework: #esi: ~ translator: @@ -52,9 +56,10 @@ wallabag_core: reading_speed: 1 cache_lifetime: 10 action_mark_as_read: 1 - list_mode: 1 + list_mode: 0 fetching_error_message: | wallabag can't retrieve contents for this article. Please troubleshoot this issue. + api_limit_mass_actions: 10 wallabag_user: registration_enabled: "%fosuser_registration%" diff --git a/app/config/config_prod.yml b/app/config/config_prod.yml index 5a4dd69e..65b02d66 100644 --- a/app/config/config_prod.yml +++ b/app/config/config_prod.yml @@ -1,9 +1,9 @@ imports: - { resource: config.yml } -#framework: -# cache: -# system: cache.adapter.apcu +framework: + assets: + # json_manifest_path: '%kernel.root_dir%/../web/bundles/wallabagcore/manifest.json' #doctrine: # orm: diff --git a/app/config/webpack/common.js b/app/config/webpack/common.js new file mode 100644 index 00000000..4f5739f0 --- /dev/null +++ b/app/config/webpack/common.js @@ -0,0 +1,40 @@ +const path = require('path'); +const webpack = require('webpack'); +const StyleLintPlugin = require('stylelint-webpack-plugin'); + +const rootDir = path.resolve(__dirname, '../../../'); + +module.exports = function() { + return { + entry: { + material: path.join(rootDir, './app/Resources/static/themes/material/index.js'), + baggy: path.join(rootDir, './app/Resources/static/themes/baggy/index.js'), + }, + + output: { + filename: '[name].js', + path: path.resolve(rootDir, 'web/bundles/wallabagcore'), + publicPath: '/bundles/wallabagcore/', + }, + plugins: [ + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + 'window.$': 'jquery', + 'window.jQuery': 'jquery' + }), + new StyleLintPlugin({ + configFile: '.stylelintrc', + failOnError: false, + quiet: false, + context: 'app/Resources/static/themes', + files: '**/*.scss', + }), + ], + resolve: { + alias: { + jquery: path.join(rootDir, 'node_modules/jquery/dist/jquery.js') + } + }, + }; +}; diff --git a/app/config/webpack/dev.js b/app/config/webpack/dev.js new file mode 100644 index 00000000..771df65b --- /dev/null +++ b/app/config/webpack/dev.js @@ -0,0 +1,62 @@ +const webpackMerge = require('webpack-merge'); +const webpack = require('webpack'); +const path = require('path'); +const commonConfig = require('./common.js'); + +module.exports = function () { + return webpackMerge(commonConfig(), { + devtool: 'eval-source-map', + output: { + filename: '[name].dev.js' + }, + + devServer: { + hot: true, + // enable HMR on the server + + contentBase: './web', + // match the output path + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + ], + module: { + rules: [ + { + enforce: 'pre', + test: /\.js$/, + loader: 'eslint-loader', + exclude: /node_modules/, + }, + { + test: /\.js$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + presets: ['env'] + } + } + }, + { + test: /\.(s)?css$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1, + } + }, + 'postcss-loader', + 'sass-loader' + ] + }, + { + test: /\.(jpg|png|gif|svg|eot|ttf|woff|woff2)$/, + use: 'url-loader' + }, + ] + }, + }) +}; diff --git a/app/config/webpack/prod.js b/app/config/webpack/prod.js new file mode 100644 index 00000000..ef41ab99 --- /dev/null +++ b/app/config/webpack/prod.js @@ -0,0 +1,99 @@ +const webpack = require('webpack'); +const webpackMerge = require('webpack-merge'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const ManifestPlugin = require('webpack-manifest-plugin'); + +const commonConfig = require('./common.js'); + +module.exports = function() { + return webpackMerge(commonConfig(), { + output: { + filename: '[name].js' + }, + devtool: 'source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': JSON.stringify('production') + } + }), + new webpack.optimize.UglifyJsPlugin({ + beautify: false, + mangle: { + screw_ie8: true, + keep_fnames: true + }, + compress: { + screw_ie8: true, + warnings: false + }, + comments: false + }), + new ExtractTextPlugin('[name].css'), + new ManifestPlugin({ + fileName: 'manifest.json', + }) + ], + module: { + rules: [ + { + enforce: 'pre', + test: /\.js$/, + loader: 'eslint-loader', + exclude: /node_modules/, + }, + { + test: /\.js$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + presets: ['env'] + } + } + }, + { + test: /\.(s)?css$/, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: [ + { + loader: 'css-loader', + options: { + importLoaders: 1, + minimize: { + discardComments: { + removeAll: true + }, + core: true, + minifyFontValues: true + } + } + }, + 'postcss-loader', + 'sass-loader' + ] + }) + }, + { + test: /\.(jpg|png|gif|svg)$/, + use: { + loader: 'file-loader', + options: { + name: 'img/[name].[ext]', + } + } + }, + { + test: /\.(eot|ttf|woff|woff2)$/, + use: { + loader: 'file-loader', + options: { + name: 'fonts/[name].[ext]', + } + } + } + ] + }, + }) +}; diff --git a/composer.json b/composer.json index 7f16e691..bfd42339 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "issues": "https://github.com/wallabag/wallabag/issues" }, "require": { - "php": ">=5.5.9", + "php": ">=5.6.0", "ext-pcre": "*", "ext-dom": "*", "ext-curl": "*", @@ -65,7 +65,7 @@ "liip/theme-bundle": "~1.1", "lexik/form-filter-bundle": "~5.0", "j0k3r/graby": "~1.0", - "friendsofsymfony/user-bundle": "2.0.x-dev", + "friendsofsymfony/user-bundle": "^2.0", "friendsofsymfony/oauth-server-bundle": "^1.5", "stof/doctrine-extensions-bundle": "^1.2", "scheb/two-factor-bundle": "~2.0", @@ -130,7 +130,7 @@ "config": { "bin-dir": "bin", "platform": { - "php": "5.5.9" + "php": "5.6.0" } }, "minimum-stability": "dev", diff --git a/docs/de/conf.py b/docs/de/conf.py index cc9dcdf8..1bc39d4c 100644 --- a/docs/de/conf.py +++ b/docs/de/conf.py @@ -11,8 +11,8 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'wallabag-fr' -copyright = u'2013-2016, Nicolas Lœuillet - MIT Licence' -version = '2.1.0' +copyright = u'2013-2017, Nicolas Lœuillet - MIT Licence' +version = '2.3.0' release = version exclude_patterns = ['_build'] pygments_style = 'sphinx' diff --git a/docs/de/user/articles.rst b/docs/de/user/articles.rst index 07cfa06c..b6893a2c 100644 --- a/docs/de/user/articles.rst +++ b/docs/de/user/articles.rst @@ -30,7 +30,7 @@ Klicke darauf, um ein neues Feld anzuzeigen, füge die Artikel-URL ein und drüc Firefox """"""" -Du kannst das `Firefox-Addon hier herunterladen`_. +Du kannst das `Firefox-Addon hier herunterladen`_. Chrome """""" @@ -43,7 +43,7 @@ Du kannst das `Chrome-Addon hier herunterladen`_. +Du kannst die `Android-App hier `_ oder auf `F-Droid `_ herunterladen. Windows 10 in general """"""""""""""""""""" @@ -82,6 +82,7 @@ Nun kannst du den Artikel teilen: - über eine öffentliche URL (es wird eine reduzierte Ansicht des Artikels zurückgegeben) - über einen Tweet - in deine Shaarli +- in deine Scuttle - mit einem Beitrag auf Diaspora* - an Carrot - mit einer E-Mail diff --git a/docs/de/user/configuration.rst b/docs/de/user/configuration.rst index c0c04bde..06cae7cb 100644 --- a/docs/de/user/configuration.rst +++ b/docs/de/user/configuration.rst @@ -115,16 +115,17 @@ Welche Variablen und Operatoren kann ich zum Regeln schreiben nutzen? Die folgenden Variablen und Operatoren können genutzt werden, um Tagging-Regeln zu erstellen (sei vorsichtig, denn bei einigen Werten musst du Anführungszeichen hinzufügen, z.B. ``language = "de"``): -=========== ============================================== ======== ========== -Variable Bedeutung Operator Bedeutung ------------ ---------------------------------------------- -------- ---------- -title Titel des Artikels <= Kleiner gleich als… -url URL des Artikels < Kleiner als… -isArchived Ob der Artikel archiviert ist oder nicht => Größer gleich als… -isStarred Ob der Artikel favorisiert ist oder nicht > Größer als… -content Inhalt des Eintrags = Gleich zu… -language Sprache des Eintrags != Nicht gleich zu… -mimetype MIME-Typ des Eintrags OR Eine Regel oder die andere -readingTime Die geschätzte Lesezeit in Minuten AND Eine Regel und die andere -domainName Der Domain-Name des Eintrags matches Testet, dass ein Feld einer Suche (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Z.B.: title matches "Fußball" -=========== ============================================== ======== ========== +=========== ============================================== ========== ========== +Variable Bedeutung Operator Bedeutung +----------- ---------------------------------------------- ---------- ---------- +title Titel des Artikels <= Kleiner gleich als… +url URL des Artikels < Kleiner als… +isArchived Ob der Artikel archiviert ist oder nicht => Größer gleich als… +isStarred Ob der Artikel favorisiert ist oder nicht > Größer als… +content Inhalt des Eintrags = Gleich zu… +language Sprache des Eintrags != Nicht gleich zu… +mimetype MIME-Typ des Eintrags OR Eine Regel oder die andere +readingTime Die geschätzte Lesezeit in Minuten AND Eine Regel und die andere +domainName Der Domain-Name des Eintrags matches Testet, dass ein Feld einer Suche (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Z.B.: title matches "Fußball" + notmatches +=========== ============================================== ========== ========== diff --git a/docs/de/user/import.rst b/docs/de/user/import.rst index 399a1b98..b4e89c39 100644 --- a/docs/de/user/import.rst +++ b/docs/de/user/import.rst @@ -100,7 +100,7 @@ Wenn du in der Vergangenheit wallabag 1.x genutzt hast, musst du deine Daten exp Wenn du mehrere Accounts auf der gleichen wallabag-Instanz hast, muss jeder Nutzer seine Daten aus 1.x exportieren und in 2.x importieren. .. note:: - Falls während des Exports oder des Imports Probleme auftreten sollten, scheue dich nicht, den `Support zu kontaktieren `__. + Falls während des Exports oder des Imports Probleme auftreten sollten, scheue dich nicht, den `Support zu kontaktieren `__. Wenn du dann die JSON-Datei mit deinen Einträgen heruntergeladen hast, kannst du `wallabag 2 über die Standard-Prozedur installieren `__. @@ -122,7 +122,7 @@ Gehe auf der alten wallabag-Instanz, die du vorher genutzt hast, auf `Alle Artik Nach dem Erstellen des Benutzeraccounts auf deiner neuen "wallabag 2.x"-Instanz, navigiere auf den Import-Bereich und wähle `Aus wallabag v2 importieren`. Wähle deine JSON-Datei und lade sie hoch. .. note:: - Falls während des Exports oder des Imports Probleme auftreten sollten, scheue dich nicht, den `Support zu kontaktieren `__. + Falls während des Exports oder des Imports Probleme auftreten sollten, scheue dich nicht, den `Support zu kontaktieren `__. Import über die Kommandozeile (CLI) ----------------------------------- diff --git a/docs/de/user/installation.rst b/docs/de/user/installation.rst index 0de6b6de..7dd489b1 100644 --- a/docs/de/user/installation.rst +++ b/docs/de/user/installation.rst @@ -4,7 +4,7 @@ Installation von wallabag Voraussetzungen --------------- -wallabag ist kompatibel mit PHP >= 5.5, inkl. PHP 7. +wallabag ist kompatibel mit **PHP >= 5.6**, inkl. PHP 7. .. note:: @@ -88,7 +88,7 @@ Führe dieses Kommando aus, um das neueste Paket herunterzuladen und zu entpacke wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Du findest die `md5 Hashsumme des neuesten Pakets auf unserer Website `_. +Du findest die `md5 Hashsumme des neuesten Pakets auf unserer Website `_. Jetzt lies die Dokumentation, um einen Virtualhost zu erstellen, dann greife auf dein wallabag zu. Wenn du die Datenbankkonfiguration eingestellt hast, MySQL oder PostgreSQL zu nutzen, musst du einen Nutzer über das folgende Kommando erstellen ``php bin/console wallabag:install --env=prod``. @@ -105,6 +105,14 @@ Kommando, um den Container zu starten docker pull wallabag/wallabag +Cloudron Installation +~~~~~~~~~~~~~~~~~~~~~~~~ + +Cloudron bietet einfache Webapp Installation auf deinem Server, mit Fokus auf System Administrator Automatisierung und Updates. +Ein Wallabag Paket ist direkt zur Installation durch den Cloudron Store verfügbar. + +`Installiere wallabag auf deinem Cloudron `__ + Virtualhosts ------------ diff --git a/docs/de/user/upgrade.rst b/docs/de/user/upgrade.rst index 76faa4e2..fa2aac45 100644 --- a/docs/de/user/upgrade.rst +++ b/docs/de/user/upgrade.rst @@ -63,7 +63,7 @@ Lade das letzte Release von wallabag herunter: wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Du findest den `aktuellen MD5-Hash auf unserer Webseite `_. +Du findest den `aktuellen MD5-Hash auf unserer Webseite `_. Extrahiere das Archiv in deinen wallabag-Ordner und ersetze die ``app/config/parameters.yml`` mit deiner. diff --git a/docs/en/conf.py b/docs/en/conf.py index 717b35f1..5926f8c9 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -11,8 +11,8 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'wallabag' -copyright = u'2013-2016, Nicolas Lœuillet - MIT Licence' -version = '2.1.0' +copyright = u'2013-2017, Nicolas Lœuillet - MIT Licence' +version = '2.3.0' release = version exclude_patterns = ['_build'] pygments_style = 'sphinx' diff --git a/docs/en/developer/console_commands.rst b/docs/en/developer/console_commands.rst new file mode 100644 index 00000000..85a8a092 --- /dev/null +++ b/docs/en/developer/console_commands.rst @@ -0,0 +1,30 @@ +Console Commands +================ + +wallabag has a number of CLI commands to manage a number of tasks. You can list all the commands by executing `bin/console` in the wallabag folder. + +Each command has a help accessible through `bin/console help %command%`. + +.. note:: + + If you're in a production environment, remember to add `-e prod` to each command. + +Notable commands +---------------- + +* `assets:install`: May be helpful if assets are missing. +* `cache:clear`: should be run after each update (included in `make update`). +* `doctrine:migrations:status`: Output the status of your database migrations. +* `fos:user:activate`: Manually activate an user. +* `fos:user:change-password`: Change a password for an user. +* `fos:user:create`: Create an user. +* `fos:user:deactivate`: Deactivate an user (not deleted). +* `fos:user:demote`: Removes a role from an user, typically admin rights. +* `fos:user:promote`: Adds a role to an user, typically admin rights. +* `rabbitmq:*`: May be useful if you're using RabbitMQ. +* `wallabag:clean-duplicates`: Removes all entry duplicates for one user or all users +* `wallabag:export`: Exports all entries for an user. You can choose the output path of the file. +* `wallabag:import`: Import entries to different formats to an user account. +* `wallabag:import:redis-worker`: Useful if you use Redis. +* `wallabag:install`: (re)Install wallabag +* `wallabag:tag:all`: Tag all entries for an user using his/her tagging rules. diff --git a/docs/en/developer/front_end.rst b/docs/en/developer/front_end.rst new file mode 100644 index 00000000..40f18a42 --- /dev/null +++ b/docs/en/developer/front_end.rst @@ -0,0 +1,33 @@ +Tips for front-end developers +============================= + +Starting from version 2.3, wallabag uses webpack to bundle its assets. + +Dev mode +-------- + +If the server runs in dev mode, you need to run ``yarn run build:dev`` to generate the outputted javascript files for each theme. These are named ``%theme%.dev.js`` and are ignored by git. You need to relaunch ``yarn run build:dev`` for each change made to one of the assets files (js, css, pictures, fonts,...). + +Live reload +----------- + +Webpack brings support for live reload, which means you don't need to regenerate the assets file for each change neither reload the page manually. Changes are applied automatically in the web page. Just set the ``use_webpack_dev_server`` setting to ``true`` in ``app/config/config.yml`` and run ``yarn run watch`` and you're good to go. + +.. note:: + + Don't forget to put back ``use_webpack_dev_server`` to ``false`` when not using the live reload feature. + +Production builds +----------------- + +When you want to commit your changes, build them in production environment by using ``yarn run build:prod``. This will build all the assets needed for wallabag. To test that it properly works, you'll need to have a server in production mode, for instance with ``bin/console server:run -e=prod``. + +.. note:: + + Don't forget to generate production builds before committing ! + + +Code style +---------- + +Code style is checked by two tools : stylelint for (S)CSS and eslint for JS. ESlint config is based on the Airbnb base preset. diff --git a/docs/en/user/articles.rst b/docs/en/user/articles.rst index 16b3b0d2..862bba3e 100644 --- a/docs/en/user/articles.rst +++ b/docs/en/user/articles.rst @@ -34,20 +34,20 @@ By using a browser add-on Firefox """"""" -You can download the `Firefox addon here `_. +You can download the `Firefox addon here `_. Chrome """""" You can download the `Chrome addon here `_. -By using your smarphone application +By using your smartphone application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Android """"""" -You can download the `Android application here `_. +You can download the `Android application here `_ or on `F-Droid `_. Windows Phone """"""""""""" @@ -86,6 +86,7 @@ Now, you can share the article: - with a public URL (you'll have a light view of the article) - with a tweet - into your Shaarli +- into your Scuttle - with a post in Diaspora* - to Carrot - with an email diff --git a/docs/en/user/configuration.rst b/docs/en/user/configuration.rst index bba12cb9..0f3ac38f 100644 --- a/docs/en/user/configuration.rst +++ b/docs/en/user/configuration.rst @@ -116,16 +116,17 @@ Which variables and operators can I use to write rules? The following variables and operators can be used to create tagging rules (be careful, for some values, you need to add quotes, for example ``language = "en"``): -=========== ============================================== ======== ========== -Variable Meaning Operator Meaning ------------ ---------------------------------------------- -------- ---------- -title Title of the entry <= Less than… -url URL of the entry < Strictly less than… -isArchived Whether the entry is archived or not => Greater than… -isStarred Whether the entry is starred or not > Strictly greater than… -content The entry's content = Equal to… -language The entry's language != Not equal to… -mimetype The entry's mime-type OR One rule or another -readingTime The estimated entry's reading time, in minutes AND One rule and another -domainName The domain name of the entry matches Tests that a subject is matches a search (case-insensitive). Example: title matches "football" -=========== ============================================== ======== ========== +=========== ============================================== ========== ========== +Variable Meaning Operator Meaning +----------- ---------------------------------------------- ---------- ---------- +title Title of the entry <= Less than… +url URL of the entry < Strictly less than… +isArchived Whether the entry is archived or not => Greater than… +isStarred Whether the entry is starred or not > Strictly greater than… +content The entry's content = Equal to… +language The entry's language != Not equal to… +mimetype The entry's mime-type OR One rule or another +readingTime The estimated entry's reading time, in minutes AND One rule and another +domainName The domain name of the entry matches Tests that a subject is matches a search (case-insensitive). Example: title matches "football" + notmatches Tests that a subject is not matches a search (case-insensitive). Example: title notmatches "football" +=========== ============================================== ========== ========== diff --git a/docs/en/user/import.rst b/docs/en/user/import.rst index f420a131..f6aaa373 100644 --- a/docs/en/user/import.rst +++ b/docs/en/user/import.rst @@ -77,7 +77,7 @@ From Instapaper --------------- Export your Instapaper data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ On the settings (`https://www.instapaper.com/user `_) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like ``instapaper-export.csv``). @@ -102,7 +102,7 @@ If you were using wallabag v1.x, you need to export your data before migrating t If you have multiple accounts on the same instance of wallabag, each user must export from v1 and import into v2 its data. .. note:: - If you encounter issues during the export or the import, don't hesitate to `ask for support `__. + If you encounter issues during the export or the import, don't hesitate to `ask for support `__. When you have retrieved the json file containing your entries, you can install wallabag v2 if needed by following `the standard procedure `__. @@ -124,7 +124,7 @@ From the previous wallabag instance on which you were before, go to `All article From your new wallabag instance, create your user account and click on the link in the menu to proceed to import. Choose import from wallabag v2 and select your json file to upload it. .. note:: - If you encounter issues during the export or the import, don't hesitate to `ask for support `__. + If you encounter issues during the export or the import, don't hesitate to `ask for support `__. Import via command-line interface (CLI) --------------------------------------- @@ -133,16 +133,21 @@ If you have a CLI access on your web server, you can execute this command to imp :: - bin/console wallabag:import 1 ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod + bin/console wallabag:import username ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod Please replace values: -* ``1`` is the user identifier in database (The ID of the first user created on wallabag is 1) +* ``username`` is the user's username * ``~/Downloads/wallabag-export-1-2016-04-05.json`` is the path of your wallabag v1 export -If you want to mark all these entries as read, you can add the ``--markAsRead`` option. +.. note:: + If you want to mark all these entries as read, you can add the ``--markAsRead`` option. -To import a wallabag v2 file, you need to add the option ``--importer=v2``. +.. note:: + To import a wallabag v2 file, you need to add the option ``--importer=v2``. + +.. note:: + If you want to pass the user id of the user instead of it's username, add the option ``--useUserId=true``. You'll have this in return: diff --git a/docs/en/user/installation.rst b/docs/en/user/installation.rst index 2b730b83..f1146b49 100644 --- a/docs/en/user/installation.rst +++ b/docs/en/user/installation.rst @@ -4,7 +4,7 @@ Install wallabag Requirements ------------ -wallabag is compatible with PHP >= 5.5, including PHP 7. +wallabag is compatible with **PHP >= 5.6**, including PHP 7. .. note:: @@ -87,7 +87,7 @@ Execute this command to download and extract the latest package: wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -You will find the `md5 hash of the latest package on our website `_. +You will find the `md5 hash of the latest package on our website `_. Now, read the following documentation to create your virtual host, then access your wallabag. If you changed the database configuration to use MySQL or PostgreSQL, you need to create a user via this command ``php bin/console wallabag:install --env=prod``. @@ -104,6 +104,14 @@ Command to launch container docker pull wallabag/wallabag +Installation on Cloudron +~~~~~~~~~~~~~~~~~~~~~~~~ + +Cloudron provides an easy way to install webapps on your server with a focus on sysadmin automation and keeping apps updated. +wallabag is packaged as a Cloudron app and available to install directly from the store. + +`Install wallabag on your Cloudron `__ + Virtual hosts ------------- @@ -155,9 +163,9 @@ Assuming you install wallabag in the ``/var/www/wallabag`` folder and that you w ErrorLog /var/log/apache2/wallabag_error.log CustomLog /var/log/apache2/wallabag_access.log combined - - -.. tip:: Note for Apache 2.4, in the section `` you have to replace the directives : + + +.. tip:: Note for Apache 2.4, in the section `` you have to replace the directives : :: @@ -166,12 +174,12 @@ Assuming you install wallabag in the ``/var/www/wallabag`` folder and that you w Allow from All -by +by :: - + Require All granted - + After reloading or restarting Apache, you should now be able to access wallabag at http://domain.tld. diff --git a/docs/en/user/upgrade.rst b/docs/en/user/upgrade.rst index 46b490c3..3157684c 100644 --- a/docs/en/user/upgrade.rst +++ b/docs/en/user/upgrade.rst @@ -67,7 +67,7 @@ Download the last release of wallabag: wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -You will find the `md5 hash of the latest package on our website `_. +You will find the `md5 hash of the latest package on our website `_. Extract the archive in your wallabag folder and replace ``app/config/parameters.yml`` with yours. diff --git a/docs/fr/conf.py b/docs/fr/conf.py index 49a57e2d..90489d83 100644 --- a/docs/fr/conf.py +++ b/docs/fr/conf.py @@ -11,8 +11,8 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'wallabag-fr' -copyright = u'2013-2016, Nicolas Lœuillet - MIT Licence' -version = '2.1.0' +copyright = u'2013-2017, Nicolas Lœuillet - MIT Licence' +version = '2.3.0' release = version exclude_patterns = ['_build'] pygments_style = 'sphinx' diff --git a/docs/fr/developer/console_commands.rst b/docs/fr/developer/console_commands.rst new file mode 100644 index 00000000..1b222b32 --- /dev/null +++ b/docs/fr/developer/console_commands.rst @@ -0,0 +1,30 @@ +Actions en ligne de commande +============================ + +wallabag a un certain nombre de commandes CLI pour effectuer des tâches. Vous pouvez lister toutes les commandes en exécutant `bin/console` dans le dossier d'installation de wallabag. + +Chaque commande a une aide correspondante accessible via `bin/console help %command%`. + +.. note:: + + Si vous êtes dans un environnement de production, souvenez-vous d'ajouter `-e prod` à chaque commande. + +Commandes notables +------------------ + +* `assets:install`: Peut-être utile si les *assets* sont manquants. +* `cache:clear`: doit être exécuté après chaque mise à jour (appelé dans `make update`). +* `doctrine:migrations:status`: Montre le statut de vos migrations de vos bases de données. +* `fos:user:activate`: Activer manuellement un utilisateur. +* `fos:user:change-password`: Changer le mot de passe pour un utilisateur. +* `fos:user:create`: Créer un utilisateur. +* `fos:user:deactivate`: Désactiver un utilisateur (non supprimé). +* `fos:user:demote`: Supprimer un rôle d'un utilisateur, typiquement les droits d'administration. +* `fos:user:promote`: Ajoute un rôle à un utilisateur, typiquement les droits d'administration. +* `rabbitmq:*`: Peut-être utile si vous utilisez RabbitMQ. +* `wallabag:clean-duplicates`: Supprime tous les articles dupliqués pour un utilisateur ou bien tous. +* `wallabag:export`: Exporte tous les articles pour un utilisateur. Vous pouvez choisir le chemin du fichier exporté. +* `wallabag:import`: Importe les articles en différents formats dans un compte utilisateur. +* `wallabag:import:redis-worker`: Utile si vous utilisez Redis. +* `wallabag:install`: (ré)Installer wallabag +* `wallabag:tag:all`: Tagger tous les articles pour un utilisateur ou une utilisatrice en utilisant ses règles de tags automatiques. diff --git a/docs/fr/developer/front-end.rst b/docs/fr/developer/front-end.rst new file mode 100644 index 00000000..714df08b --- /dev/null +++ b/docs/fr/developer/front-end.rst @@ -0,0 +1,33 @@ +Conseils pour développeurs front-end +==================================== + +Depuis la version 2.3, wallabag utilise webpack pour générer ses assets. + +Mode développeur +---------------- + +Si le serveur fonctionne en mode dev, vous devez lancer la commande ``yarn run build:dev`` pour générer les fichiers de sortie javascript pour chaque thème. Ils sont nommés ``%theme%.dev.js`` et sont ignorés par git. Vous devez relancer la commande ``yarn run build:dev`` pour chaque changement que vous effectuez dans les fichiers assets (js, css, images, polices,...). + +Live reload +----------- + +Webpack apporte le support pour la fonctionnalité de live reload, ce qui signifie que vous n'avez pas besoin de regénérer manuellement le fichier de sortie javascript ni de rafraichir la page dans votre navigateur. Les changements sont appliqués automatiquement. Vous avez juste besoin de mettre le paramètre ``use_webpack_dev_server`` à ``true`` dans ``app/config/config.yml`` et de lancer ``yarn run watch`` pour que cela soit actif. + +.. note:: + + N'oubliez pas de remettre ``use_webpack_dev_server`` à ``false`` lorsque vous n'utilisez pas la fonctionnalité de live reload. + +Production builds +----------------- + +Lorsque vous committez vos changements, vous devez les compiler dans un environnement de production en exécutant ``yarn run build:prod``. Cela compilera tous les assets nécessaires pour wallabag. Pour tester que cela fonctionne proprement, vous devrez avoir un serveur en mode de production, par exemple avec ``bin/console server:run -e=prod``. + +.. note:: + + N'oubliez pas de générer des fichiers en mode production avant de committer ! + + +Code style +---------- + +Le style de code est vérifié par deux outils : stylelint pour le (S)CSS et eslint pour le JS. La configuration ESlint config est basée sur le preset Airbnb base. diff --git a/docs/fr/user/articles.rst b/docs/fr/user/articles.rst index fb5b3837..cefc3c10 100644 --- a/docs/fr/user/articles.rst +++ b/docs/fr/user/articles.rst @@ -18,7 +18,7 @@ En utilisant le bookmarklet Sur la page ``Aide``, vous avez un onglet ``Bookmarklet``. Glissez/déposez le lien ``bag it!`` dans votre barre de favoris de votre navigateur. -Maintennat, à chaque fois que vous lisez un article et que vous souhaitez le sauvegarder, +Maintenant, à chaque fois que vous lisez un article et que vous souhaitez le sauvegarder, cliquez sur le lien ``bag it!`` dans votre barre de favoris. L'article est enregistré. En utilisant le formulaire classique @@ -40,7 +40,7 @@ En utilisant l'extension de votre navigateur Firefox """"""" -Vous pouvez télécharger `l'extension Firefox ici `_. +Vous pouvez télécharger `l'extension Firefox ici `_. Chrome """""" @@ -53,7 +53,7 @@ En utilisant l'application de votre smartphone Android """"""" -Vous pouvez télécharger `l'application Android ici `_. +Vous pouvez télécharger `l'application Android ici `_ ou sur `F-Droid `_. Windows Phone ~~~~~~~~~~~~~ @@ -92,6 +92,7 @@ Vous pouvez maintenant le partager : - avec une URL publique (vous obtiendrez une vue allégée de l'article) - avec un tweet - dans votre Shaarli +- dans votre Scuttle - avec un message dans Diaspora* - sur Carrot - avec un email diff --git a/docs/fr/user/configuration.rst b/docs/fr/user/configuration.rst index 772000c3..0f5d4375 100644 --- a/docs/fr/user/configuration.rst +++ b/docs/fr/user/configuration.rst @@ -130,4 +130,5 @@ language La langue de l'article != Différ mimetype The type MIME de l'article OR Telle règle ou telle autre règle readingTime Le temps de lecture de l'article, en minutes AND Telle règle et telle règle domainName Le nom de domaine de l'article matches Contient telle chaîne de caractère (insensible à la casse). Exemple : title matches "football" + notmaches Ne contient pas telle chaîne de caractère (insensible à la casse). Exemple : title notmatches "football" =========== ============================================== ========== ========== diff --git a/docs/fr/user/import.rst b/docs/fr/user/import.rst index 9a2dda8f..5317ee4b 100644 --- a/docs/fr/user/import.rst +++ b/docs/fr/user/import.rst @@ -103,7 +103,7 @@ Si vous utilisiez wallabag v1.x, vous devez exporter vos données avant de migre Si vous avez plusieurs comptes sur la même instance de wallabag, chaque utilisateur doit exporter ses données depuis wallabag v1 et les importer dans la v2. .. note:: - S'il vous arrive des problèmes durant l'export ou l'import, n'hésitez pas à `demander de l'aide `_. + S'il vous arrive des problèmes durant l'export ou l'import, n'hésitez pas à `demander de l'aide `_. Une fois que vous avez récupéré le fichier json contenant vos données, vous pouvez installer wallabag v2 si c'est nécessaire en suivant `la procédure standard `_. @@ -125,7 +125,7 @@ Depuis l'instance sur laquelle vous étiez, rendez-vous dans la section `Tous le Depuis votre nouvelle instance de wallabag, créez votre compte utilisateur puis cliquez sur le lien dans le menu pour accéder à l'import. Choisissez l'import depuis wallabag v2 puis sélectionnez votre fichier json pour l'uploader. .. note:: - S'il vous arrive des problèmes durant l'export ou l'import, n'hésitez pas à `demander de l'aide `_. + S'il vous arrive des problèmes durant l'export ou l'import, n'hésitez pas à `demander de l'aide `_. Import via la ligne de commande (CLI) -------------------------------------= 5.5, PHP 7 inclus. +wallabag est compatible avec **PHP >= 5.6**, PHP 7 inclus. .. note:: @@ -84,7 +84,7 @@ Exécutez cette commande pour télécharger et décompresser l'archive : wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Vous trouverez `le hash md5 du dernier package sur notre site `_. +Vous trouverez `le hash md5 du dernier package sur notre site `_. Maintenant, lisez la documentation ci-dessous pour crééer un virtual host. Accédez ensuite à votre installation de wallabag. Si vous avez changé la configuration pour modifier le type de stockage (MySQL ou PostgreSQL), vous devrez vous créer un utilisateur via la commande ``php bin/console wallabag:install --env=prod``. @@ -101,6 +101,14 @@ Commande pour démarrer le containeur docker pull wallabag/wallabag +Installation sur Cloudron +~~~~~~~~~~~~~~~~~~~~~~~~ + +Cloudron permet d'installer des applications web sur votre serveur +wallabag est proposé en tant qu'application Cloudron et est disponible directement depuis le store. + +`Installer wallabag sur Cloudron `__ + Virtual hosts ------------- @@ -162,12 +170,12 @@ En imaginant que vous vouliez installer wallabag dans le dossier ``/var/www/wall Allow from All -par +par :: - + Require All granted - + diff --git a/docs/fr/user/upgrade.rst b/docs/fr/user/upgrade.rst index af97ebb2..af198006 100644 --- a/docs/fr/user/upgrade.rst +++ b/docs/fr/user/upgrade.rst @@ -63,7 +63,7 @@ Téléchargez la dernière version de wallabag : wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Vous trouverez `le hash md5 du dernier package sur notre site `_. +Vous trouverez `le hash md5 du dernier package sur notre site `_. Décompressez l'archive dans votre répertoire d'installation et remplacez le fichier ``app/config/parameters.yml`` avec le votre. diff --git a/docs/it/conf.py b/docs/it/conf.py index 717b35f1..5926f8c9 100644 --- a/docs/it/conf.py +++ b/docs/it/conf.py @@ -11,8 +11,8 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'wallabag' -copyright = u'2013-2016, Nicolas Lœuillet - MIT Licence' -version = '2.1.0' +copyright = u'2013-2017, Nicolas Lœuillet - MIT Licence' +version = '2.3.0' release = version exclude_patterns = ['_build'] pygments_style = 'sphinx' diff --git a/docs/it/user/articles.rst b/docs/it/user/articles.rst index 944b23ef..2a415608 100644 --- a/docs/it/user/articles.rst +++ b/docs/it/user/articles.rst @@ -32,7 +32,7 @@ Usando un add-on del browser Firefox """"""" -Potete scaricare `qui l'addon per Firefox `_. +Potete scaricare `qui l'addon per Firefox `_. Chrome """""" @@ -45,7 +45,7 @@ Usando la vostra applicazione per smartphone Android """"""" -Potete scaricare `qui l'applicazione per Android `_. +Potete scaricare `qui l'applicazione per Android `_ o tramite `F-Droid `_. Windows Phone """"""""""""" @@ -83,6 +83,7 @@ Ora potete condividere l'articolo: - attraverso una URL pubblica (avrete una vista semplificata dell'articolo) - attraverso un tweet - nel vostro Shaarli +- nel vostro Scuttle - attraverso un post su Diaspora* - su Carrot - attraverso un'email diff --git a/docs/it/user/import.rst b/docs/it/user/import.rst index c249acfd..5d38d29b 100644 --- a/docs/it/user/import.rst +++ b/docs/it/user/import.rst @@ -64,9 +64,9 @@ Se state usando wallabag 1.x, dovete esportare i dati prima di migrare a wallaba Se avete account multipli nella stessa istanza di wallabag, ogni utente dovrá esportare da v1 ed importare su v2. .. nota:: - Se riscontrate problemi durante l'importazione o l'esportazione, non esitate a `chiedere supporto `__. + Se riscontrate problemi durante l'importazione o l'esportazione, non esitate a `chiedere supporto `__. -Quando avrete ottenuto il file json contenente i vostri articoli, potrete installare wallabag v2 seguendo, se necessario, `la procedura standard *link mancante*. +Quando avrete ottenuto il file json contenente i vostri articoli, potrete installare wallabag v2 seguendo, se necessario, `la procedura standard *link mancante*`. Dopo aver creato un account utente sulla vostra nuova istanza di wallabag v2, dovete andare alla sezione `Importa` e selezionare `Importa da wallabag v1`. Selezionate il vostro file json e caricatelo. @@ -86,7 +86,7 @@ Dalla istanza di wallabag precedente sulla quale eravate prima, andate su `Tutti Dalla vostra nuova istanza di wallabag, create un account utente e cliccate sul link nel menu per procedere all'importazione. Scegliete di importare da wallabag v2 e selezionate il vostro file json per caricarlo. .. nota:: - Se riscontrate problemi durante l'importazione o l'esportazione, non esitate a `chiedere supporto `__. + Se riscontrate problemi durante l'importazione o l'esportazione, non esitate a `chiedere supporto `__. Importate dall'interfaccia a riga di comando (CLI) -------------------------------------------------- diff --git a/docs/it/user/installation.rst b/docs/it/user/installation.rst index 95d781e6..174507b8 100644 --- a/docs/it/user/installation.rst +++ b/docs/it/user/installation.rst @@ -88,7 +88,7 @@ Eseguite questo comando per scaricare ed estrarre il pacchetto piú aggiornato: wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Troverete il `hash md5 del pacchetto piú aggiornato sul nostro sito `_. +Troverete il `hash md5 del pacchetto piú aggiornato sul nostro sito `_. Ora leggete la seguente documentazione per creare il vostro host virtuale poi accedete al vostro wallabag. Se avete cambiato la configurazione del database per usare MySQL o PostrgreSQL, dovrete creare un utente con il comando php bin/console wallabag:install --env=prod . @@ -103,6 +103,13 @@ Comando per avviare il container docker pull wallabag/wallabag +Installazione su Cloudron +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Cloudron fornisce un modo facile di installare webapps sul vostro server mirato ad automatizzare i compiti del sysadmin ed a mantenere le app aggiornate. +wallabag é pacchettizzata come app Cloudron ed é disponibile all'installazione direttamente dallo store. + +`Installa wallabag sul tuo Cloudron `__ Host virtuali ------------- diff --git a/docs/it/user/upgrade.rst b/docs/it/user/upgrade.rst index e1c3f041..ede1a1a3 100644 --- a/docs/it/user/upgrade.rst +++ b/docs/it/user/upgrade.rst @@ -80,7 +80,7 @@ Scaricate l'ultima versione di wallabag: wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package -Troverete il `hash md5 dell'ultima versione del pacchetto sul nostro sito `_. +Troverete il `hash md5 dell'ultima versione del pacchetto sul nostro sito `_. Estraete l'archivio nella vostra cartella di wallabag e rimpiazzate ``app/config/parameters.yml`` con il vostro. @@ -98,5 +98,5 @@ Da wallabag 1.x Non esiste uno script automatico per aggiornare da wallabag 1.x a wallabag 2.x. Dovete: - esportare i vostri dati -- installare wallabag 2.x (leggete la documentazione a proposito dell'installazione *link mancante*) +- installare wallabag 2.x (leggete la documentazione a proposito dell'installazione *link mancante*) - importate i dati in questa nuova installazione (leggete la documentazione a proposito dell'importazione) diff --git a/package.json b/package.json index 209c46c6..8b81f89b 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,13 @@ { "name": "wallabag", - "version": "2.1.0", + "version": "2.2.2", "description": "wallabag is a self hostable application for saving web pages", - "main": "index.js", "private": true, "directories": { "doc": "docs" }, "engines": { - "node": ">0.12" - }, - "browser": { - "jquery": "./node_modules/jquery/dist/jquery.js", - "jQuery": "./node_modules/jquery/dist/jquery.js", - "materialize": "./node_modules/materialize-css/bin/materialize.js" - }, - "browserify-shim": { - "jquery": { - "exports": "$" - }, - "materialize": "materialize", - "jquery-ui": { - "depends": "jquery", - "exports": null - } - }, - "browserify": { - "transform": [ - "browserify-shim" - ] + "node": ">4.8" }, "repository": { "type": "git", @@ -57,52 +36,49 @@ "url": "https://github.com/wallabag/wallabag/issues" }, "devDependencies": { + "autoprefixer": "^6.7.7", + "babel-core": "^6.24.1", + "babel-eslint": "^7.2.3", + "babel-loader": "^7.0.0", + "babel-preset-env": "^1.4.0", + "css-loader": "^0.28.0", + "eslint": "^3.19.0", + "eslint-config-airbnb-base": "^11.1.3", + "eslint-loader": "^1.7.1", + "eslint-plugin-import": "^2.2.0", + "extract-text-webpack-plugin": "^2.1.0", + "file-loader": "^0.11.0", + "lato-font": "^3.0.0", + "node-sass": "^4.5.2", + "postcss-loader": "^1.3.3", + "sass-loader": "^6.0.3", + "style-loader": "^0.16.1", + "stylelint": "^7.9.0", + "stylelint-config-standard": "^16.0.0", + "stylelint-webpack-plugin": "^0.7.0", + "url-loader": "^0.5.8", + "webpack": "^2.3.2", + "webpack-dev-server": "^2.4.4", + "webpack-manifest-plugin": "^1.1.0", + "webpack-merge": "^4.1.0" + }, + "dependencies": { "annotator": "git://github.com/wallabag/annotator.git#0f076c7d371ed25eb0793346f46982d90f2c4c85", - "autoprefixer": "^6.3.6", - "babel-eslint": "^6.1.2", - "babel-preset-es2015": "^6.14.0", - "babelify": "^7.3.0", - "browserify": "^13.0.0", - "browserify-shim": "^3.8.12", - "cssnano": "^3.5.2", - "es6-promise": "^3.2.1", - "eslint": "^3.7.1", - "eslint-config-airbnb-base": "^8.0.0", - "eslint-plugin-import": "^1.16.0", - "grunt": ">=0.4.0", - "grunt-browserify": "^5.0.0", - "grunt-cli": "^1.2.0", - "grunt-contrib-clean": "^1.0.0", - "grunt-contrib-concat": "^1.0.0", - "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-symlink": "^1.0.0", - "grunt-contrib-uglify": "^1.0.0", - "grunt-contrib-watch": "^1.0.0", - "grunt-eslint": "^19.0.0", - "grunt-postcss": "^0.8.0", - "grunt-stylelint": "^0.6.0", - "hammerjs": "^2.0.6", - "icomoon-free-npm": "0.0.0", - "jquery": "^2.2.4", - "jquery-ui-browserify": "^1.11.0-pre-seelio", + "hammerjs": "^2.0.8", + "icomoon-free-npm": "^0.0.0", + "jquery": "^2.1.4", "jquery.cookie": "^1.4.1", - "jquery.tinydot": "^0.2.1", - "load-grunt-tasks": "^3.4.1", - "material-design-icons-iconfont": "^3.0.0", - "materialize-css": "0.97.5", - "npm": "^3.8.3", - "pickadate": "^3.5.6", - "pixrem": "^3.0.0", - "postcss-cssnext": "^2.5.1", - "prismjs": "^1.4.1", - "ptsans-npm-webfont": "0.0.4", - "roboto-fontface": "^0.6.0", - "stylelint": "^7.3.1", - "stylelint-config-standard": "^13.0.2", - "through": "^2.3.8" + "jr-qrcode": "^1.0.7", + "material-design-icons-iconfont": "^3.0.3", + "materialize-css": "^0.98.1", + "mousetrap": "^1.6.0", + "ptsans-npm-webfont": "^0.0.4", + "roboto-fontface": "^0.7.0", + "waypoints": "^4.0.1" }, - "dependencies": { - "jr-qrcode": "^1.0.5", - "mousetrap": "^1.6.0" + "scripts": { + "watch": "./node_modules/.bin/webpack-dev-server --env=dev", + "build:dev": "./node_modules/.bin/webpack --env=dev", + "build:prod": "./node_modules/.bin/webpack --env=prod" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..5d146314 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: function () { + return [ + require('autoprefixer'), + ]; + } +}; diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php index 8d3f07ee..da361308 100644 --- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php +++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php @@ -122,4 +122,21 @@ class AnnotationRepository extends EntityRepository ->setParameter('userId', $userId) ->execute(); } + + /** + * Find all annotations related to archived entries. + * + * @param $userId + * + * @return mixed + */ + public function findAllArchivedEntriesByUser($userId) + { + return $this->createQueryBuilder('a') + ->leftJoin('a.entry', 'e') + ->where('a.user = :userid')->setParameter(':userid', $userId) + ->andWhere('e.isArchived = true') + ->getQuery() + ->getResult(); + } } diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index c544815e..632b16d9 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php @@ -5,6 +5,7 @@ namespace Wallabag\ApiBundle\Controller; use Hateoas\Configuration\Route; use Hateoas\Representation\Factory\PagerfantaFactory; use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -41,12 +42,10 @@ class EntryRestController extends WallabagRestController ->getRepository('WallabagCoreBundle:Entry') ->findByUrlAndUserId($url, $this->getUser()->getId()); - $results[$url] = false === $res ? false : true; + $results[$url] = $res instanceof Entry ? $res->getId() : false; } - $json = $this->get('serializer')->serialize($results, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($results); } // let's see if it is a simple url? @@ -60,11 +59,9 @@ class EntryRestController extends WallabagRestController ->getRepository('WallabagCoreBundle:Entry') ->findByUrlAndUserId($url, $this->getUser()->getId()); - $exists = false === $res ? false : true; - - $json = $this->get('serializer')->serialize(['exists' => $exists], 'json'); + $exists = $res instanceof Entry ? $res->getId() : false; - return (new JsonResponse())->setJson($json); + return $this->sendResponse(['exists' => $exists]); } /** @@ -125,9 +122,7 @@ class EntryRestController extends WallabagRestController ) ); - $json = $this->get('serializer')->serialize($paginatedCollection, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($paginatedCollection); } /** @@ -146,9 +141,7 @@ class EntryRestController extends WallabagRestController $this->validateAuthentication(); $this->validateUserAccess($entry->getUser()->getId()); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -173,6 +166,110 @@ class EntryRestController extends WallabagRestController ->exportAs($request->attributes->get('_format')); } + /** + * Handles an entries list and delete URL. + * + * @ApiDoc( + * parameters={ + * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."} + * } + * ) + * + * @return JsonResponse + */ + public function deleteEntriesListAction(Request $request) + { + $this->validateAuthentication(); + + $urls = json_decode($request->query->get('urls', [])); + + if (empty($urls)) { + return $this->sendResponse([]); + } + + $results = []; + + // handle multiple urls + foreach ($urls as $key => $url) { + $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( + $url, + $this->getUser()->getId() + ); + + $results[$key]['url'] = $url; + + if (false !== $entry) { + $em = $this->getDoctrine()->getManager(); + $em->remove($entry); + $em->flush(); + + // entry deleted, dispatch event about it! + $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); + } + + $results[$key]['entry'] = $entry instanceof Entry ? true : false; + } + + return $this->sendResponse($results); + } + + /** + * Handles an entries list and create URL. + * + * @ApiDoc( + * parameters={ + * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."} + * } + * ) + * + * @return JsonResponse + * + * @throws HttpException When limit is reached + */ + public function postEntriesListAction(Request $request) + { + $this->validateAuthentication(); + + $urls = json_decode($request->query->get('urls', [])); + $results = []; + + $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions'); + + if (count($urls) > $limit) { + throw new HttpException(400, 'API limit reached'); + } + + // handle multiple urls + if (!empty($urls)) { + foreach ($urls as $key => $url) { + $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( + $url, + $this->getUser()->getId() + ); + + $results[$key]['url'] = $url; + + if (false === $entry) { + $entry = $this->get('wallabag_core.content_proxy')->updateEntry( + new Entry($this->getUser()), + $url + ); + } + + $em = $this->getDoctrine()->getManager(); + $em->persist($entry); + $em->flush(); + + $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; + + // entry saved, dispatch event about it! + $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); + } + } + + return $this->sendResponse($results); + } + /** * Create an entry. * @@ -230,9 +327,7 @@ class EntryRestController extends WallabagRestController // entry saved, dispatch event about it! $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -281,9 +376,7 @@ class EntryRestController extends WallabagRestController $em = $this->getDoctrine()->getManager(); $em->flush(); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -326,9 +419,7 @@ class EntryRestController extends WallabagRestController // entry saved, dispatch event about it! $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -354,9 +445,7 @@ class EntryRestController extends WallabagRestController // entry deleted, dispatch event about it! $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -375,9 +464,7 @@ class EntryRestController extends WallabagRestController $this->validateAuthentication(); $this->validateUserAccess($entry->getUser()->getId()); - $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry->getTags()); } /** @@ -408,9 +495,7 @@ class EntryRestController extends WallabagRestController $em->persist($entry); $em->flush(); - $json = $this->get('serializer')->serialize($entry, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($entry); } /** @@ -435,7 +520,124 @@ class EntryRestController extends WallabagRestController $em->persist($entry); $em->flush(); - $json = $this->get('serializer')->serialize($entry, 'json'); + return $this->sendResponse($entry); + } + + /** + * Handles an entries list delete tags from them. + * + * @ApiDoc( + * parameters={ + * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."} + * } + * ) + * + * @return JsonResponse + */ + public function deleteEntriesTagsListAction(Request $request) + { + $this->validateAuthentication(); + + $list = json_decode($request->query->get('list', [])); + + if (empty($list)) { + return $this->sendResponse([]); + } + + // handle multiple urls + $results = []; + + foreach ($list as $key => $element) { + $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( + $element->url, + $this->getUser()->getId() + ); + + $results[$key]['url'] = $element->url; + $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; + + $tags = $element->tags; + + if (false !== $entry && !(empty($tags))) { + $tags = explode(',', $tags); + foreach ($tags as $label) { + $label = trim($label); + + $tag = $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Tag') + ->findOneByLabel($label); + + if (false !== $tag) { + $entry->removeTag($tag); + } + } + + $em = $this->getDoctrine()->getManager(); + $em->persist($entry); + $em->flush(); + } + } + + return $this->sendResponse($results); + } + + /** + * Handles an entries list and add tags to them. + * + * @ApiDoc( + * parameters={ + * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."} + * } + * ) + * + * @return JsonResponse + */ + public function postEntriesTagsListAction(Request $request) + { + $this->validateAuthentication(); + + $list = json_decode($request->query->get('list', [])); + + if (empty($list)) { + return $this->sendResponse([]); + } + + $results = []; + + // handle multiple urls + foreach ($list as $key => $element) { + $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( + $element->url, + $this->getUser()->getId() + ); + + $results[$key]['url'] = $element->url; + $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; + + $tags = $element->tags; + + if (false !== $entry && !(empty($tags))) { + $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); + + $em = $this->getDoctrine()->getManager(); + $em->persist($entry); + $em->flush(); + } + } + + return $this->sendResponse($results); + } + + /** + * Shortcut to send data serialized in json. + * + * @param mixed $data + * + * @return JsonResponse + */ + private function sendResponse($data) + { + $json = $this->get('serializer')->serialize($data, 'json'); return (new JsonResponse())->setJson($json); } diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php index bc6d4e64..47298d7e 100644 --- a/src/Wallabag/ApiBundle/Controller/TagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php @@ -31,7 +31,7 @@ class TagRestController extends WallabagRestController } /** - * Permanently remove one tag from **every** entry. + * Permanently remove one tag from **every** entry by passing the Tag label. * * @ApiDoc( * requirements={ @@ -106,7 +106,7 @@ class TagRestController extends WallabagRestController } /** - * Permanently remove one tag from **every** entry. + * Permanently remove one tag from **every** entry by passing the Tag ID. * * @ApiDoc( * requirements={ diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php new file mode 100644 index 00000000..65f35d8e --- /dev/null +++ b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php @@ -0,0 +1,119 @@ +setName('wallabag:clean-duplicates') + ->setDescription('Cleans the database for duplicates') + ->setHelp('This command helps you to clean your articles list in case of duplicates') + ->addArgument( + 'username', + InputArgument::OPTIONAL, + 'User to clean' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->output = $output; + + $username = $input->getArgument('username'); + + if ($username) { + try { + $user = $this->getUser($username); + $this->cleanDuplicates($user); + } catch (NoResultException $e) { + $output->writeln(sprintf('User "%s" not found.', $username)); + + return 1; + } + } else { + $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll(); + + $output->writeln(sprintf('Cleaning through %d user accounts', count($users))); + + foreach ($users as $user) { + $output->writeln(sprintf('Processing user %s', $user->getUsername())); + $this->cleanDuplicates($user); + } + $output->writeln(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates)); + } + + return 0; + } + + /** + * @param User $user + */ + private function cleanDuplicates(User $user) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry'); + + $entries = $repo->getAllEntriesIdAndUrl($user->getId()); + + $duplicatesCount = 0; + $urls = []; + foreach ($entries as $entry) { + $url = $this->similarUrl($entry['url']); + + /* @var $entry Entry */ + if (in_array($url, $urls)) { + ++$duplicatesCount; + + $em->remove($repo->find($entry['id'])); + $em->flush(); // Flushing at the end of the loop would require the instance not being online + } else { + $urls[] = $entry['url']; + } + } + + $this->duplicates += $duplicatesCount; + + $this->output->writeln(sprintf('Cleaned %d duplicates for user %s', $duplicatesCount, $user->getUserName())); + } + + private function similarUrl($url) + { + if (in_array(substr($url, -1), ['/', '#'])) { // get rid of "/" and "#" and the end of urls + return substr($url, 0, strlen($url)); + } + + return $url; + } + + /** + * Fetches a user from its username. + * + * @param string $username + * + * @return \Wallabag\UserBundle\Entity\User + */ + private function getUser($username) + { + return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username); + } + + private function getDoctrine() + { + return $this->getContainer()->get('doctrine'); + } +} diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index f0738b91..0d9364f6 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -63,6 +63,7 @@ class InstallCommand extends ContainerAwareCommand ->setupDatabase() ->setupAdmin() ->setupConfig() + ->runMigrations() ; $output->writeln('wallabag has been successfully installed.'); @@ -71,7 +72,7 @@ class InstallCommand extends ContainerAwareCommand protected function checkRequirements() { - $this->defaultOutput->writeln('Step 1 of 4. Checking system requirements.'); + $this->defaultOutput->writeln('Step 1 of 5. Checking system requirements.'); $doctrineManager = $this->getContainer()->get('doctrine')->getManager(); $rows = []; @@ -175,11 +176,11 @@ class InstallCommand extends ContainerAwareCommand protected function setupDatabase() { - $this->defaultOutput->writeln('Step 2 of 4. Setting up database.'); + $this->defaultOutput->writeln('Step 2 of 5. Setting up database.'); // user want to reset everything? Don't care about what is already here if (true === $this->defaultInput->getOption('reset')) { - $this->defaultOutput->writeln('Droping database, creating database and schema, clearing the cache'); + $this->defaultOutput->writeln('Dropping database, creating database and schema, clearing the cache'); $this ->runCommand('doctrine:database:drop', ['--force' => true]) @@ -211,7 +212,7 @@ class InstallCommand extends ContainerAwareCommand $question = new ConfirmationQuestion('It appears that your database already exists. Would you like to reset it? (y/N)', false); if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) { - $this->defaultOutput->writeln('Droping database, creating database and schema'); + $this->defaultOutput->writeln('Dropping database, creating database and schema'); $this ->runCommand('doctrine:database:drop', ['--force' => true]) @@ -221,7 +222,7 @@ class InstallCommand extends ContainerAwareCommand } elseif ($this->isSchemaPresent()) { $question = new ConfirmationQuestion('Seems like your database contains schema. Do you want to reset it? (y/N)', false); if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) { - $this->defaultOutput->writeln('Droping schema and creating schema'); + $this->defaultOutput->writeln('Dropping schema and creating schema'); $this ->runCommand('doctrine:schema:drop', ['--force' => true]) @@ -246,7 +247,7 @@ class InstallCommand extends ContainerAwareCommand protected function setupAdmin() { - $this->defaultOutput->writeln('Step 3 of 4. Administration setup.'); + $this->defaultOutput->writeln('Step 3 of 5. Administration setup.'); $questionHelper = $this->getHelperSet()->get('question'); $question = new ConfirmationQuestion('Would you like to create a new admin user (recommended) ? (Y/n)', true); @@ -285,7 +286,7 @@ class InstallCommand extends ContainerAwareCommand protected function setupConfig() { - $this->defaultOutput->writeln('Step 4 of 4. Config setup.'); + $this->defaultOutput->writeln('Step 4 of 5. Config setup.'); $em = $this->getContainer()->get('doctrine.orm.entity_manager'); // cleanup before insert new stuff @@ -332,6 +333,16 @@ class InstallCommand extends ContainerAwareCommand 'value' => 'http://myshaarli.com', 'section' => 'entry', ], + [ + 'name' => 'share_scuttle', + 'value' => '1', + 'section' => 'entry', + ], + [ + 'name' => 'scuttle_url', + 'value' => 'http://scuttle.org', + 'section' => 'entry', + ], [ 'name' => 'share_mail', 'value' => '1', @@ -454,6 +465,14 @@ class InstallCommand extends ContainerAwareCommand return $this; } + protected function runMigrations() + { + $this->defaultOutput->writeln('Step 5 of 5. Run migrations.'); + + $this + ->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]); + } + /** * Run a command. * diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 907bf78e..1a80cc1a 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -248,7 +248,7 @@ class ConfigController extends Controller break; case 'entries': - // SQLite doesn't care about cascading remove, so we need to manually remove associated stuf + // SQLite doesn't care about cascading remove, so we need to manually remove associated stuff // otherwise they won't be removed ... if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) { $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId()); @@ -260,6 +260,19 @@ class ConfigController extends Controller $this->getDoctrine() ->getRepository('WallabagCoreBundle:Entry') ->removeAllByUserId($this->getUser()->getId()); + break; + case 'archived': + if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) { + $this->removeAnnotationsForArchivedByUserId($this->getUser()->getId()); + } + + // manually remove tags to avoid orphan tag + $this->removeTagsForArchivedByUserId($this->getUser()->getId()); + + $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Entry') + ->removeArchivedByUserId($this->getUser()->getId()); + break; } $this->get('session')->getFlashBag()->add( @@ -271,14 +284,13 @@ class ConfigController extends Controller } /** - * Remove all tags for a given user and cleanup orphan tags. + * Remove all tags for given tags and a given user and cleanup orphan tags. * - * @param int $userId + * @param array $tags + * @param int $userId */ - private function removeAllTagsByUserId($userId) + private function removeAllTagsByStatusAndUserId($tags, $userId) { - $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId); - if (empty($tags)) { return; } @@ -299,6 +311,43 @@ class ConfigController extends Controller $em->flush(); } + /** + * Remove all tags for a given user and cleanup orphan tags. + * + * @param int $userId + */ + private function removeAllTagsByUserId($userId) + { + $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId); + $this->removeAllTagsByStatusAndUserId($tags, $userId); + } + + /** + * Remove all tags for a given user and cleanup orphan tags. + * + * @param int $userId + */ + private function removeTagsForArchivedByUserId($userId) + { + $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findForArchivedArticlesByUser($userId); + $this->removeAllTagsByStatusAndUserId($tags, $userId); + } + + private function removeAnnotationsForArchivedByUserId($userId) + { + $em = $this->getDoctrine()->getManager(); + + $archivedEntriesAnnotations = $this->getDoctrine() + ->getRepository('WallabagAnnotationBundle:Annotation') + ->findAllArchivedEntriesByUser($userId); + + foreach ($archivedEntriesAnnotations as $archivedEntriesAnnotation) { + $em->remove($archivedEntriesAnnotation); + } + + $em->flush(); + } + /** * Validate that a rule can be edited/deleted by the current user. * diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php index f7398e69..8d2ac6d4 100644 --- a/src/Wallabag/CoreBundle/Controller/EntryController.php +++ b/src/Wallabag/CoreBundle/Controller/EntryController.php @@ -227,7 +227,7 @@ class EntryController extends Controller public function showUnreadAction(Request $request, $page) { // load the quickstart if no entry in database - if ($page == 1 && $this->get('wallabag_core.entry_repository')->countAllEntriesByUsername($this->getUser()->getId()) == 0) { + if ($page == 1 && $this->get('wallabag_core.entry_repository')->countAllEntriesByUser($this->getUser()->getId()) == 0) { return $this->redirect($this->generateUrl('quickstart')); } diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php index a723656e..aaeb9ee9 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php @@ -50,11 +50,21 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface 'value' => '1', 'section' => 'entry', ], + [ + 'name' => 'share_scuttle', + 'value' => '1', + 'section' => 'entry', + ], [ 'name' => 'shaarli_url', 'value' => 'http://myshaarli.com', 'section' => 'entry', ], + [ + 'name' => 'scuttle_url', + 'value' => 'http://scuttle.org', + 'section' => 'entry', + ], [ 'name' => 'share_mail', 'value' => '1', diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php index 7efe6356..55abd63c 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php @@ -36,6 +36,13 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter $manager->persist($tr3); + $tr4 = new TaggingRule(); + $tr4->setRule('content notmatches "basket"'); + $tr4->setTags(['foot']); + $tr4->setConfig($this->getReference('admin-config')); + + $manager->persist($tr4); + $manager->flush(); } diff --git a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php index 006a18c3..75b37729 100644 --- a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php +++ b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php @@ -47,6 +47,9 @@ class Configuration implements ConfigurationInterface ->scalarNode('list_mode') ->defaultValue(1) ->end() + ->scalarNode('api_limit_mass_actions') + ->defaultValue(10) + ->end() ->end() ; diff --git a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php index aa9ee339..c075c19f 100644 --- a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php +++ b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php @@ -26,6 +26,7 @@ class WallabagCoreExtension extends Extension $container->setParameter('wallabag_core.action_mark_as_read', $config['action_mark_as_read']); $container->setParameter('wallabag_core.list_mode', $config['list_mode']); $container->setParameter('wallabag_core.fetching_error_message', $config['fetching_error_message']); + $container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index 7276b437..b71c467c 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php @@ -121,6 +121,24 @@ class Entry */ private $updatedAt; + /** + * @var \DateTime + * + * @ORM\Column(name="published_at", type="datetime", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) + */ + private $publishedAt; + + /** + * @var array + * + * @ORM\Column(name="published_by", type="json_array", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) + */ + private $publishedBy; + /** * @ORM\OneToMany(targetEntity="Wallabag\AnnotationBundle\Entity\Annotation", mappedBy="entry", cascade={"persist", "remove"}) * @ORM\JoinTable @@ -174,15 +192,6 @@ class Entry */ private $previewPicture; - /** - * @var bool - * - * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) - * - * @Groups({"export_all"}) - */ - private $isPublic; - /** * @var string * @@ -531,22 +540,6 @@ class Entry $this->domainName = $domainName; } - /** - * @return bool - */ - public function isPublic() - { - return $this->isPublic; - } - - /** - * @param bool $isPublic - */ - public function setIsPublic($isPublic) - { - $this->isPublic = $isPublic; - } - /** * @return ArrayCollection */ @@ -701,4 +694,44 @@ class Entry return $this; } + + /** + * @return \Datetime + */ + public function getPublishedAt() + { + return $this->publishedAt; + } + + /** + * @param \Datetime $publishedAt + * + * @return Entry + */ + public function setPublishedAt(\Datetime $publishedAt) + { + $this->publishedAt = $publishedAt; + + return $this; + } + + /** + * @return string + */ + public function getPublishedBy() + { + return $this->publishedBy; + } + + /** + * @param string $publishedBy + * + * @return Entry + */ + public function setPublishedBy($publishedBy) + { + $this->publishedBy = $publishedBy; + + return $this; + } } diff --git a/src/Wallabag/CoreBundle/Entity/TaggingRule.php b/src/Wallabag/CoreBundle/Entity/TaggingRule.php index 72651b19..84e11e26 100644 --- a/src/Wallabag/CoreBundle/Entity/TaggingRule.php +++ b/src/Wallabag/CoreBundle/Entity/TaggingRule.php @@ -31,7 +31,7 @@ class TaggingRule * @Assert\Length(max=255) * @RulerZAssert\ValidRule( * allowed_variables={"title", "url", "isArchived", "isStared", "content", "language", "mimetype", "readingTime", "domainName"}, - * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches"} + * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches", "notmatches"} * ) * @ORM\Column(name="rule", type="string", nullable=false) */ @@ -87,7 +87,7 @@ class TaggingRule /** * Set tags. * - * @param array $tags + * @param array $tags * * @return TaggingRule */ diff --git a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php index c3715646..1627cc44 100644 --- a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php +++ b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php @@ -3,7 +3,6 @@ namespace Wallabag\CoreBundle\Form\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; @@ -18,11 +17,6 @@ class EditEntryType extends AbstractType 'required' => true, 'label' => 'entry.edit.title_label', ]) - ->add('is_public', CheckboxType::class, [ - 'required' => false, - 'label' => 'entry.edit.is_public_label', - 'property_path' => 'isPublic', - ]) ->add('url', TextType::class, [ 'disabled' => true, 'required' => false, diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index f222dd88..d45aef88 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -79,6 +79,14 @@ class ContentProxy $entry->setContent($html); $entry->setHttpStatus(isset($content['status']) ? $content['status'] : ''); + if (isset($content['date']) && null !== $content['date'] && '' !== $content['date']) { + $entry->setPublishedAt(new \DateTime($content['date'])); + } + + if (!empty($content['authors'])) { + $entry->setPublishedBy($content['authors']); + } + $entry->setLanguage(isset($content['language']) ? $content['language'] : ''); $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : ''); $entry->setReadingTime(Utils::getReadingTime($html)); diff --git a/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php b/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php new file mode 100644 index 00000000..b7f9da57 --- /dev/null +++ b/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php @@ -0,0 +1,25 @@ +createQueryBuilder('e') ->select('count(e)') @@ -371,4 +371,42 @@ class EntryRepository extends EntityRepository ->setParameter('userId', $userId) ->execute(); } + + public function removeArchivedByUserId($userId) + { + $this->getEntityManager() + ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = :userId AND e.isArchived = TRUE') + ->setParameter('userId', $userId) + ->execute(); + } + + /** + * Get id and url from all entries + * Used for the clean-duplicates command. + */ + public function getAllEntriesIdAndUrl($userId) + { + $qb = $this->createQueryBuilder('e') + ->select('e.id, e.url') + ->where('e.user = :userid')->setParameter(':userid', $userId); + + return $qb->getQuery()->getArrayResult(); + } + + /** + * Find all entries by url and owner. + * + * @param $url + * @param $userId + * + * @return array + */ + public function findAllByUrlAndUserId($url, $userId) + { + return $this->createQueryBuilder('e') + ->where('e.url = :url')->setParameter('url', urldecode($url)) + ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) + ->getQuery() + ->getResult(); + } } diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php index 2182df25..6c63a6a2 100644 --- a/src/Wallabag/CoreBundle/Repository/TagRepository.php +++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php @@ -76,4 +76,24 @@ class TagRepository extends EntityRepository ->getQuery() ->getSingleResult(); } + + public function findForArchivedArticlesByUser($userId) + { + $ids = $this->createQueryBuilder('t') + ->select('t.id') + ->leftJoin('t.entries', 'e') + ->where('e.user = :userId')->setParameter('userId', $userId) + ->andWhere('e.isArchived = true') + ->groupBy('t.id') + ->orderBy('t.slug') + ->getQuery() + ->getArrayResult(); + + $tags = []; + foreach ($ids as $id) { + $tags[] = $this->find($id); + } + + return $tags; + } } diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 51d6ab47..bccb2e19 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -125,6 +125,16 @@ services: tags: - { name: rulerz.operator, target: doctrine, operator: matches, inline: true } + wallabag.operator.array.notmatches: + class: Wallabag\CoreBundle\Operator\PHP\NotMatches + tags: + - { name: rulerz.operator, target: native, operator: notmatches } + + wallabag.operator.doctrine.notmatches: + class: Wallabag\CoreBundle\Operator\Doctrine\NotMatches + tags: + - { name: rulerz.operator, target: doctrine, operator: notmatches, inline: true } + wallabag_core.helper.redirect: class: Wallabag\CoreBundle\Helper\Redirect arguments: diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index 5d9e85e4..57319af7 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,7 +155,7 @@ config: # or: 'One rule OR another' # and: 'One rule AND another' # matches: 'Tests that a subject is matches a search (case-insensitive).
Example: title matches "football"' - + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: # unread: 'Unread entries' @@ -223,6 +224,8 @@ entry: original_article: 'original' # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' created_at: 'Oprettelsesdato' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Gem ny artikel' placeholder: 'http://website.com' @@ -234,7 +237,6 @@ entry: # page_title: 'Edit an entry' # title_label: 'Title' url_label: 'Url' - # is_public_label: 'Public' save_label: 'Gem' public: # shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +512,8 @@ user: # delete: Delete # delete_confirm: Are you sure? # back_to_list: Back to list + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +532,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: # entry_already_saved: 'Entry already saved on %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index f1952a3e..a7bcecc6 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -110,6 +110,7 @@ config: annotations: Entferne ALLE Annotationen tags: Entferne ALLE Tags entries: Entferne ALLE Einträge + # archived: Remove ALL archived entries confirm: Bist du wirklich sicher? (DIES KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN) form_password: description: "Hier kannst du dein Kennwort ändern. Dieses sollte mindestens acht Zeichen enthalten." @@ -154,6 +155,7 @@ config: or: 'Eine Regel ODER die andere' and: 'Eine Regel UND eine andere' matches: 'Testet, ob eine Variable auf eine Suche zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).
Beispiel: title matches "Fußball"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' annotations_on_the_entry: '{0} Keine Anmerkungen|{1} Eine Anmerkung|]1,Inf[ %count% Anmerkungen' created_at: 'Erstellungsdatum' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Neuen Artikel speichern' placeholder: 'https://website.de' @@ -234,7 +238,6 @@ entry: page_title: 'Eintrag bearbeiten' title_label: 'Titel' url_label: 'URL' - is_public_label: 'Öffentlich' save_label: 'Speichern' public: shared_by_wallabag: "Dieser Artikel wurde mittels wallabag geteilt" @@ -510,6 +513,8 @@ user: delete: Löschen delete_confirm: Bist du sicher? back_to_list: Zurück zur Liste + search: + # placeholder: Filter by username or email error: page_title: Ein Fehler ist aufgetreten @@ -528,6 +533,7 @@ flashes: annotations_reset: Anmerkungen zurücksetzen tags_reset: Tags zurücksetzen entries_reset: Einträge zurücksetzen + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Eintrag bereits am %date% gespeichert' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index df782b01..1ef2874d 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -110,6 +110,7 @@ config: annotations: Remove ALL annotations tags: Remove ALL tags entries: Remove ALL entries + archived: Remove ALL archived entries confirm: Are you really sure? (THIS CAN'T BE UNDONE) form_password: description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: or: 'One rule OR another' and: 'One rule AND another' matches: 'Tests that a subject is matches a search (case-insensitive).
Example: title matches "football"' + notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' created_at: 'Creation date' + published_at: 'Publication date' + published_by: 'Published by' new: page_title: 'Save new entry' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Edit an entry' title_label: 'Title' url_label: 'Url' - is_public_label: 'Public' save_label: 'Save' public: shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +513,8 @@ user: delete: Delete delete_confirm: Are you sure? back_to_list: Back to list + search: + placeholder: Filter by username or email error: page_title: An error occurred @@ -528,6 +533,7 @@ flashes: annotations_reset: Annotations reset tags_reset: Tags reset entries_reset: Entries reset + archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Entry already saved on %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index 3d65c311..6cd079b0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -110,6 +110,7 @@ config: annotations: Eliminar TODAS las anotaciones tags: Eliminar TODAS las etiquetas entries: Eliminar TODOS los artículos + # archived: Remove ALL archived entries confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER) form_password: description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres." @@ -154,6 +155,7 @@ config: or: 'Una regla U otra' and: 'Una regla Y la otra' matches: 'Prueba si un sujeto corresponde a una búsqueda (insensible a mayusculas).
Ejemplo : title matches "fútbol"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones' created_at: 'Fecha de creación' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Guardar un nuevo artículo' placeholder: 'http://sitioweb.com' @@ -234,7 +238,6 @@ entry: page_title: 'Editar un artículo' title_label: 'Título' url_label: 'URL' - is_public_label: 'Es público' save_label: 'Guardar' public: shared_by_wallabag: "Este artículo se ha compartido con wallabag" @@ -510,6 +513,8 @@ user: delete: Eliminar delete_confirm: ¿Estás seguro? back_to_list: Volver a la lista + search: + # placeholder: Filter by username or email error: page_title: Ha ocurrido un error @@ -528,6 +533,7 @@ flashes: annotations_reset: Anotaciones reiniciadas tags_reset: Etiquetas reiniciadas entries_reset: Artículos reiniciados + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Artículo ya guardado el %fecha%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 80500d19..fb6e315e 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: # or: 'One rule OR another' # and: 'One rule AND another' # matches: 'Tests that a subject is matches a search (case-insensitive).
Example: title matches "football"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'اصلی' annotations_on_the_entry: '{0} بدون حاشیه|{1} یک حاشیه|]1,Inf[ %nbحاشیه% annotations' created_at: 'زمان ساخت' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'ذخیرهٔ مقالهٔ تازه' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'ویرایش مقاله' title_label: 'عنوان' url_label: 'نشانی' - is_public_label: 'عمومی' save_label: 'ذخیره' public: # shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +513,8 @@ user: # delete: Delete # delete_confirm: Are you sure? # back_to_list: Back to list + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +533,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 4f49f777..ad886363 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -46,7 +46,7 @@ footer: social: "Social" powered_by: "propulsé par" about: "À propos" - stats: Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour ! + stats: "Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !" config: page_title: "Configuration" @@ -71,16 +71,16 @@ config: 300_word: "Je lis environ 300 mots par minute" 400_word: "Je lis environ 400 mots par minute" action_mark_as_read: - label: 'Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?' - redirect_homepage: "À la page d'accueil" - redirect_current_page: 'À la page courante' - pocket_consumer_key_label: Clé d’authentification Pocket pour importer les données - android_configuration: Configurez votre application Android - help_theme: "L'affichage de wallabag est personnalisable. C'est ici que vous choisissez le thème que vous préférez." - help_items_per_page: "Vous pouvez définir le nombre d'articles affichés sur chaque page." + label: "Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?" + redirect_homepage: "À la page d’accueil" + redirect_current_page: "À la page courante" + pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données" + android_configuration: "Configurez votre application Android" + help_theme: "L’affichage de wallabag est personnalisable. C’est ici que vous choisissez le thème que vous préférez." + help_items_per_page: "Vous pouvez définir le nombre d’articles affichés sur chaque page." help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article." - help_language: "Vous pouvez définir la langue de l'interface de wallabag." - help_pocket_consumer_key: "Nécessaire pour l'import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket." + help_language: "Vous pouvez définir la langue de l’interface de wallabag." + help_pocket_consumer_key: "Nécessaire pour l’import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket." form_rss: description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d’abord créer un jeton." token_label: "Jeton RSS" @@ -100,17 +100,18 @@ config: twoFactorAuthentication_label: "Double authentification" help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." delete: - title: Supprimer mon compte (attention danger !) - description: Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c'est IRRÉVERSIBLE). Vous serez ensuite déconnecté. - confirm: Vous êtes vraiment sûr ? (C'EST IRRÉVERSIBLE) - button: 'Supprimer mon compte' + title: "Supprimer mon compte (attention danger !)" + description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté." + confirm: "Vous êtes vraiment sûr ? (C’EST IRRÉVERSIBLE)" + button: "Supprimer mon compte" reset: - title: Réinitialisation (attention danger !) - description: En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES ! - annotations: Supprimer TOUTES les annotations - tags: Supprimer TOUS les tags - entries: Supprimer TOUS les articles - confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE) + title: "Réinitialisation (attention danger !)" + description: "En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !" + annotations: "Supprimer TOUTES les annotations" + tags: "Supprimer TOUS les tags" + entries: "Supprimer TOUS les articles" + archived: "Supprimer TOUS les articles archivés" + confirm: "Êtes-vous vraiment vraiment sûr ? (C’EST IRRÉVERSIBLE)" form_password: description: "Vous pouvez changer ici votre mot de passe. Le mot de passe doit contenir au moins 8 caractères." old_password_label: "Mot de passe actuel" @@ -154,6 +155,7 @@ config: or: "Une règle OU l’autre" and: "Une règle ET l’autre" matches: "Teste si un sujet correspond à une recherche (non sensible à la casse).
Exemple : title matches \"football\"" + notmatches: "Teste si un sujet ne correspond pas à une recherche (non sensible à la casse).
Exemple : title notmatches \"football\"" entry: page_titles: @@ -162,7 +164,7 @@ entry: archived: "Articles lus" filtered: "Articles filtrés" filtered_tags: "Articles filtrés par tags :" - filtered_search: 'Articles filtrés par recherche :' + filtered_search: "Articles filtrés par recherche :" untagged: "Article sans tag" list: number_on_the_page: "{0} Il n’y a pas d’article.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles." @@ -186,7 +188,7 @@ entry: preview_picture_label: "A une photo" preview_picture_help: "Photo" language_label: "Langue" - http_status_label: 'Statut HTTP' + http_status_label: "Statut HTTP" reading_time: label: "Durée de lecture en minutes" from: "de" @@ -223,6 +225,8 @@ entry: original_article: "original" annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations" created_at: "Date de création" + published_at: "Date de publication" + published_by: "Publié par" new: page_title: "Sauvegarder un nouvel article" placeholder: "http://website.com" @@ -234,7 +238,6 @@ entry: page_title: "Éditer un article" title_label: "Titre" url_label: "Adresse" - is_public_label: "Public" save_label: "Enregistrer" public: shared_by_wallabag: "Cet article a été partagé par wallabag" @@ -295,32 +298,32 @@ howto: bookmarklet: description: "Glissez et déposez ce lien dans votre barre de favoris :" shortcuts: - page_description: Voici les raccourcis disponibles dans wallabag. - shortcut: Raccourci - action: Action - all_pages_title: Raccourcis disponibles dans toutes les pages - go_unread: Afficher les articles non lus - go_starred: Afficher les articles favoris - go_archive: Afficher les articles lus - go_all: Afficher tous les articles - go_tags: Afficher les tags - go_config: Aller à la configuration - go_import: Aller aux imports - go_developers: Aller à la section Développeurs - go_howto: Afficher l'aide (cette page !) - go_logout: Se déconnecter - list_title: Raccourcis disponibles dans les pages de liste - search: Afficher le formulaire de recherche - article_title: Raccourcis disponibles quand on affiche un article - open_original: Ouvrir l'URL originale de l'article - toggle_favorite: Changer le statut Favori de l'article - toggle_archive: Changer le status Lu de l'article - delete: Supprimer l'article - material_title: Raccourcis disponibles avec le thème Material uniquement - add_link: Ajouter un nouvel article - hide_form: Masquer le formulaire courant (recherche ou nouvel article) - arrows_navigation: Naviguer à travers les articles - open_article: Afficher l'article sélectionné + page_description: "Voici les raccourcis disponibles dans wallabag." + shortcut: "Raccourci" + action: "Action" + all_pages_title: "Raccourcis disponibles dans toutes les pages" + go_unread: "Afficher les articles non lus" + go_starred: "Afficher les articles favoris" + go_archive: "Afficher les articles lus" + go_all: "Afficher tous les articles" + go_tags: "Afficher les tags" + go_config: "Aller à la configuration" + go_import: "Aller aux imports" + go_developers: "Aller à la section Développeurs" + go_howto: "Afficher l’aide (cette page !)" + go_logout: "Se déconnecter" + list_title: "Raccourcis disponibles dans les pages de liste" + search: "Afficher le formulaire de recherche" + article_title: "Raccourcis disponibles quand on affiche un article" + open_original: "Ouvrir l’URL originale de l’article" + toggle_favorite: "Changer le statut Favori de l’article" + toggle_archive: "Changer le status Lu de l’article" + delete: "Supprimer l’article" + material_title: "Raccourcis disponibles avec le thème Material uniquement" + add_link: "Ajouter un nouvel article" + hide_form: "Masquer le formulaire courant (recherche ou nouvel article)" + arrows_navigation: "Naviguer à travers les articles" + open_article: "Afficher l’article sélectionné" quickstart: page_title: "Pour bien débuter" @@ -382,8 +385,8 @@ tag: number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags." see_untagged_entries: "Voir les articles sans tag" new: - add: 'Ajouter' - placeholder: 'Vous pouvez ajouter plusieurs tags, séparés par une virgule.' + add: "Ajouter" + placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule." import: page_title: "Importer" @@ -417,7 +420,7 @@ import: how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l’importer." worker: enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :" - download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons vivement d'activer les imports asynchrones." + download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l’import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons vivement d’activer les imports asynchrones." firefox: page_title: "Import > Firefox" description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde… ». Vous allez récupérer un fichier .json.

" @@ -486,16 +489,16 @@ developer: back: "Retour" user: - page_title: Gestion des utilisateurs - new_user: Créer un nouvel utilisateur - edit_user: Éditer un utilisateur existant - description: Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression) + page_title: "Gestion des utilisateurs" + new_user: "Créer un nouvel utilisateur" + edit_user: "Éditer un utilisateur existant" + description: "Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)" list: - actions: Actions - edit_action: Éditer - yes: Oui - no: Non - create_new_one: Créer un nouvel utilisateur + actions: "Actions" + edit_action: "Éditer" + yes: "Oui" + no: "Non" + create_new_one: "Créer un nouvel utilisateur" form: username_label: "Nom d’utilisateur" name_label: "Nom" @@ -510,9 +513,11 @@ user: delete: "Supprimer" delete_confirm: "Voulez-vous vraiment ?" back_to_list: "Revenir à la liste" + search: + placeholder: "Filtrer par nom d’utilisateur ou email" error: - page_title: Une erreur est survenue + page_title: "Une erreur est survenue" flashes: config: @@ -525,9 +530,10 @@ flashes: tagging_rules_updated: "Règles mises à jour" tagging_rules_deleted: "Règle supprimée" rss_token_updated: "Jeton RSS mis à jour" - annotations_reset: Annotations supprimées - tags_reset: Tags supprimés - entries_reset: Articles supprimés + annotations_reset: "Annotations supprimées" + tags_reset: "Tags supprimés" + entries_reset: "Articles supprimés" + archived_reset: "Articles archivés supprimés" entry: notice: entry_already_saved: "Article déjà sauvegardé le %date%" @@ -559,6 +565,6 @@ flashes: client_deleted: "Client %name% supprimé" user: notice: - added: 'Utilisateur "%username%" ajouté' - updated: 'Utilisateur "%username%" mis à jour' - deleted: 'Utilisateur "%username%" supprimé' + added: "Utilisateur \"%username%\" ajouté" + updated: "Utilisateur \"%username%\" mis à jour" + deleted: "Utilisateur \"%username%\" supprimé" diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index 992ff71c..5a9605ff 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: or: "Una regola O un'altra" and: "Una regola E un'altra" matches: 'Verifica che un oggetto risulti in una ricerca (case-insensitive).
Esempio: titolo contiene "football"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'originale' annotations_on_the_entry: '{0} Nessuna annotazione|{1} Una annotazione|]1,Inf[ %count% annotazioni' created_at: 'Data di creazione' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Salva un nuovo contenuto' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Modifica voce' title_label: 'Titolo' url_label: 'Url' - is_public_label: 'Pubblico' save_label: 'Salva' public: # shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +513,8 @@ user: # delete: Delete # delete_confirm: Are you sure? # back_to_list: Back to list + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +533,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Contenuto già salvato in data %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index f6488565..942bc257 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -110,6 +110,7 @@ config: annotations: Levar TOTAS las anotacions tags: Levar TOTAS las etiquetas entries: Levar TOTES los articles + # archived: Remove ALL archived entries confirm: Sètz vertadièrament segur ? (ES IRREVERSIBLE) form_password: description: "Podètz cambiar vòstre senhal aquí. Vòstre senhal deu èsser long d'almens 8 caractèrs." @@ -154,6 +155,7 @@ config: or: "Una règla O l'autra" and: "Una règla E l'autra" matches: 'Teste se un subjècte correspond a una recerca (non sensibla a la cassa).
Exemple : title matches \"football\"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' annotations_on_the_entry: "{0} Pas cap d'anotacion|{1} Una anotacion|]1,Inf[ %count% anotacions" created_at: 'Data de creacion' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Enregistrar un novèl article' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Modificar un article' title_label: 'Títol' url_label: 'Url' - is_public_label: 'Public' save_label: 'Enregistrar' public: shared_by_wallabag: "Aqueste article es estat partejat per wallabag" @@ -510,6 +513,8 @@ user: delete: 'Suprimir' delete_confirm: 'Sètz segur ?' back_to_list: 'Tornar a la lista' + search: + # placeholder: Filter by username or email error: page_title: Una error s'es produsida @@ -528,6 +533,7 @@ flashes: annotations_reset: Anotacions levadas tags_reset: Etiquetas levadas entries_reset: Articles levats + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Article ja salvargardat lo %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index eda9bbbf..fea90440 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -110,6 +110,7 @@ config: annotations: Usuń WSZYSTKIE adnotacje tags: Usuń WSZYSTKIE tagi entries: usuń WSZYTSTKIE wpisy + # archived: Remove ALL archived entries confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć) form_password: description: "Tutaj możesz zmienić swoje hasło. Twoje nowe hasło powinno mieć conajmniej 8 znaków." @@ -154,6 +155,7 @@ config: or: 'Jedna reguła LUB inna' and: 'Jedna reguła I inna' matches: 'Sprawdź czy temat pasuje szukaj (duże lub małe litery).
Przykład: tytuł zawiera "piłka nożna"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'oryginalny' annotations_on_the_entry: '{0} Nie ma adnotacji |{1} Jedna adnotacja |]1,Inf[ %count% adnotacji' created_at: 'Czas stworzenia' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Zapisz nowy wpis' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Edytuj wpis' title_label: 'Tytuł' url_label: 'Adres URL' - is_public_label: 'Publiczny' save_label: 'Zapisz' public: shared_by_wallabag: "Ten artykuł został udostępniony przez wallabag" @@ -510,6 +513,8 @@ user: delete: Usuń delete_confirm: Jesteś pewien? back_to_list: Powrót do listy + search: + # placeholder: Filter by username or email error: page_title: Wystąpił błąd @@ -528,6 +533,7 @@ flashes: annotations_reset: Zresetuj adnotacje tags_reset: Zresetuj tagi entries_reset: Zresetuj wpisy + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Wpis już został dodany %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml index 8a7cc6f8..c59991f8 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: or: 'Uma regra OU outra' and: 'Uma regra E outra' matches: 'Testa que um assunto corresponde a uma pesquisa (maiúscula ou minúscula).
Exemplo: título corresponde a "futebol"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações' created_at: 'Data de criação' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Salvar nova entrada' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Editar uma entrada' title_label: 'Título' url_label: 'Url' - is_public_label: 'Público' save_label: 'Salvar' public: shared_by_wallabag: "Este artigo foi compartilhado pelo wallabag" @@ -510,6 +513,8 @@ user: delete: 'Apagar' delete_confirm: 'Tem certeza?' back_to_list: 'Voltar para a lista' + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +533,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Entrada já foi salva em %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index 52b6414f..5846b7cc 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: # or: 'One rule OR another' # and: 'One rule AND another' # matches: 'Tests that a subject is matches a search (case-insensitive).
Example: title matches "football"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'original' # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' created_at: 'Data creării' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Salvează un nou articol' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: # page_title: 'Edit an entry' # title_label: 'Title' url_label: 'Url' - # is_public_label: 'Public' save_label: 'Salvează' public: # shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +513,8 @@ user: # delete: Delete # delete_confirm: Are you sure? # back_to_list: Back to list + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +533,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: # entry_already_saved: 'Entry already saved on %date%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index bfb7e206..430fb96b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -110,6 +110,7 @@ config: # annotations: Remove ALL annotations # tags: Remove ALL tags # entries: Remove ALL entries + # archived: Remove ALL archived entries # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) form_password: # description: "You can change your password here. Your new password should by at least 8 characters long." @@ -154,6 +155,7 @@ config: or: 'Bir kural veya birbaşkası' and: 'Bir kural ve diğeri' # matches: 'Tests that a subject is matches a search (case-insensitive).
Example: title matches "football"' + # notmatches: 'Tests that a subject is not matches a search (case-insensitive).
Example: title notmatches "football"' entry: page_titles: @@ -223,6 +225,8 @@ entry: original_article: 'orijinal' # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' created_at: 'Oluşturulma tarihi' + # published_at: 'Publication date' + # published_by: 'Published by' new: page_title: 'Yeni makaleyi kaydet' placeholder: 'http://website.com' @@ -234,7 +238,6 @@ entry: page_title: 'Makaleyi düzenle' title_label: 'Başlık' url_label: 'Url' - is_public_label: 'Herkes tarafından erişime açık olsun mu?' save_label: 'Kaydet' public: # shared_by_wallabag: "This article has been shared by wallabag" @@ -510,6 +513,8 @@ user: # delete: Delete # delete_confirm: Are you sure? # back_to_list: Back to list + search: + # placeholder: Filter by username or email error: # page_title: An error occurred @@ -528,6 +533,7 @@ flashes: # annotations_reset: Annotations reset # tags_reset: Tags reset # entries_reset: Entries reset + # archived_reset: Archived entries deleted entry: notice: entry_already_saved: 'Entry already saved on %date%' diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig index 3548f590..01f63a7b 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig @@ -199,6 +199,11 @@ {{ 'config.reset.tags'|trans }} +
  • + + {{ 'config.reset.archived'|trans }} + +
  • {{ 'config.reset.entries'|trans }} diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig index 859b166b..bdd44b54 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig @@ -17,9 +17,9 @@
    {{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}
    {% if (entry.previewPicture is null or listMode == 1) %}
    diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php index 28d01715..ce72837a 100644 --- a/src/Wallabag/ImportBundle/Command/ImportCommand.php +++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php @@ -15,10 +15,11 @@ class ImportCommand extends ContainerAwareCommand $this ->setName('wallabag:import') ->setDescription('Import entries from a JSON export') - ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate') + ->addArgument('username', InputArgument::REQUIRED, 'User to populate') ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file') ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1') ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false) + ->addOption('useUserId', null, InputArgument::OPTIONAL, 'Use user id instead of username to find account', false) ; } @@ -34,10 +35,14 @@ class ImportCommand extends ContainerAwareCommand // Turning off doctrine default logs queries for saving memory $em->getConnection()->getConfiguration()->setSQLLogger(null); - $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId')); + if ($input->getOption('useUserId')) { + $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('username')); + } else { + $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username')); + } if (!is_object($user)) { - throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId'))); + throw new Exception(sprintf('User "%s" not found', $input->getArgument('username'))); } switch ($input->getOption('importer')) { diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php index 92ee2b41..1c5c86d4 100644 --- a/src/Wallabag/UserBundle/Controller/ManageController.php +++ b/src/Wallabag/UserBundle/Controller/ManageController.php @@ -4,35 +4,21 @@ namespace Wallabag\UserBundle\Controller; use FOS\UserBundle\Event\UserEvent; use FOS\UserBundle\FOSUserEvents; +use Pagerfanta\Adapter\DoctrineORMAdapter; +use Pagerfanta\Exception\OutOfRangeCurrentPageException; +use Pagerfanta\Pagerfanta; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Wallabag\UserBundle\Entity\User; -use Wallabag\CoreBundle\Entity\Config; +use Wallabag\UserBundle\Form\SearchUserType; /** * User controller. */ class ManageController extends Controller { - /** - * Lists all User entities. - * - * @Route("/", name="user_index") - * @Method("GET") - */ - public function indexAction() - { - $em = $this->getDoctrine()->getManager(); - - $users = $em->getRepository('WallabagUserBundle:User')->findAll(); - - return $this->render('WallabagUserBundle:Manage:index.html.twig', array( - 'users' => $users, - )); - } - /** * Creates a new User entity. * @@ -146,4 +132,49 @@ class ManageController extends Controller ->getForm() ; } + + /** + * @param Request $request + * @param int $page + * + * @Route("/list/{page}", name="user_index", defaults={"page" = 1}) + * + * Default parameter for page is hardcoded (in duplication of the defaults from the Route) + * because this controller is also called inside the layout template without any page as argument + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function searchFormAction(Request $request, $page = 1) + { + $em = $this->getDoctrine()->getManager(); + $qb = $em->getRepository('WallabagUserBundle:User')->createQueryBuilder('u'); + + $form = $this->createForm(SearchUserType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->get('logger')->info('searching users'); + + $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : ''); + + $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm); + } + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + $pagerFanta = new Pagerfanta($pagerAdapter); + $pagerFanta->setMaxPerPage(50); + + try { + $pagerFanta->setCurrentPage($page); + } catch (OutOfRangeCurrentPageException $e) { + if ($page > 1) { + return $this->redirect($this->generateUrl('user_index', ['page' => $pagerFanta->getNbPages()]), 302); + } + } + + return $this->render('WallabagUserBundle:Manage:index.html.twig', [ + 'searchForm' => $form->createView(), + 'users' => $pagerFanta, + ]); + } } diff --git a/src/Wallabag/UserBundle/Form/SearchUserType.php b/src/Wallabag/UserBundle/Form/SearchUserType.php new file mode 100644 index 00000000..9ce46ee1 --- /dev/null +++ b/src/Wallabag/UserBundle/Form/SearchUserType.php @@ -0,0 +1,29 @@ +setMethod('GET') + ->add('term', TextType::class, [ + 'required' => true, + 'label' => 'user.new.form_search.term_label', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'csrf_protection' => false, + ]); + } +} diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php index f913f52d..6adbe329 100644 --- a/src/Wallabag/UserBundle/Repository/UserRepository.php +++ b/src/Wallabag/UserBundle/Repository/UserRepository.php @@ -52,4 +52,17 @@ class UserRepository extends EntityRepository ->getQuery() ->getSingleScalarResult(); } + + /** + * Retrieves users filtered with a search term. + * + * @param string $term + * + * @return QueryBuilder + */ + public function getQueryBuilderForSearch($term) + { + return $this->createQueryBuilder('u') + ->andWhere('lower(u.username) LIKE lower(:term) OR lower(u.email) LIKE lower(:term) OR lower(u.name) LIKE lower(:term)')->setParameter('term', '%'.$term.'%'); + } } diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig index daba29e4..15002632 100644 --- a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig +++ b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig @@ -7,37 +7,60 @@
    + {% if users.getNbPages > 1 %} + {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }} + {% endif %}
    -
    +

    {{ 'user.description'|trans|raw }}

    +
    +
    +
    + + {% if form_errors(searchForm) %} + {{ form_errors(searchForm) }} + {% endif %} + + {% if form_errors(searchForm.term) %} + {{ form_errors(searchForm.term) }} + {% endif %} - - - - - - - - - - - {% for user in users %} - - - - - - - {% endfor %} - -
    {{ 'user.form.username_label'|trans }}{{ 'user.form.email_label'|trans }}{{ 'user.form.last_login_label'|trans }}{{ 'user.list.actions'|trans }}
    {{ user.username }}{{ user.email }}{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %} - {{ 'user.list.edit_action'|trans }} -
    -
    -

    - {{ 'user.list.create_new_one'|trans }} -

    + {{ form_widget(searchForm.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'user.search.placeholder'} }) }} + + {{ form_rest(searchForm) }} + +
    + + + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
    {{ 'user.form.username_label'|trans }}{{ 'user.form.email_label'|trans }}{{ 'user.form.last_login_label'|trans }}{{ 'user.list.actions'|trans }}
    {{ user.username }}{{ user.email }}{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %} + {{ 'user.list.edit_action'|trans }} +
    +
    +

    + {{ 'user.list.create_new_one'|trans }} +

    + {% if users.getNbPages > 1 %} + {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }} + {% endif %}
    diff --git a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php index 63d70bd9..4f49f040 100644 --- a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php +++ b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php @@ -314,7 +314,7 @@ class EntryRestControllerTest extends WallabagApiTestCase $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') - ->findOneByUser(1); + ->findOneByUser(1, ['id' => 'asc']); if (!$entry) { $this->markTestSkipped('No content found in db.'); @@ -353,7 +353,7 @@ class EntryRestControllerTest extends WallabagApiTestCase $this->assertEquals(false, $content['is_starred']); $this->assertEquals('New title for my article', $content['title']); $this->assertEquals(1, $content['user_id']); - $this->assertCount(1, $content['tags']); + $this->assertCount(2, $content['tags']); } public function testPostSameEntry() @@ -372,7 +372,7 @@ class EntryRestControllerTest extends WallabagApiTestCase $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']); $this->assertEquals(true, $content['is_archived']); $this->assertEquals(false, $content['is_starred']); - $this->assertCount(2, $content['tags']); + $this->assertCount(3, $content['tags']); } public function testPostArchivedAndStarredEntry() @@ -658,7 +658,7 @@ class EntryRestControllerTest extends WallabagApiTestCase $content = json_decode($this->client->getResponse()->getContent(), true); - $this->assertEquals(true, $content['exists']); + $this->assertEquals(2, $content['exists']); } public function testGetEntriesExistsWithManyUrls() @@ -673,7 +673,7 @@ class EntryRestControllerTest extends WallabagApiTestCase $this->assertArrayHasKey($url1, $content); $this->assertArrayHasKey($url2, $content); - $this->assertEquals(true, $content[$url1]); + $this->assertEquals(2, $content[$url1]); $this->assertEquals(false, $content[$url2]); } @@ -730,4 +730,120 @@ class EntryRestControllerTest extends WallabagApiTestCase $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } + + public function testPostEntriesTagsListAction() + { + $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUrlAndUserId('http://0.0.0.0/entry4', 1); + + $tags = $entry->getTags(); + + $this->assertCount(2, $tags); + + $list = [ + [ + 'url' => 'http://0.0.0.0/entry4', + 'tags' => 'new tag 1, new tag 2', + ], + ]; + + $this->client->request('POST', '/api/entries/tags/lists?list='.json_encode($list)); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $content = json_decode($this->client->getResponse()->getContent(), true); + + $this->assertInternalType('int', $content[0]['entry']); + $this->assertEquals('http://0.0.0.0/entry4', $content[0]['url']); + + $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUrlAndUserId('http://0.0.0.0/entry4', 1); + + $tags = $entry->getTags(); + $this->assertCount(4, $tags); + } + + public function testDeleteEntriesTagsListAction() + { + $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUrlAndUserId('http://0.0.0.0/entry4', 1); + + $tags = $entry->getTags(); + + $this->assertCount(4, $tags); + + $list = [ + [ + 'url' => 'http://0.0.0.0/entry4', + 'tags' => 'new tag 1, new tag 2', + ], + ]; + + $this->client->request('DELETE', '/api/entries/tags/list?list='.json_encode($list)); + } + + public function testPostEntriesListAction() + { + $list = [ + 'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', + 'http://0.0.0.0/entry2', + ]; + + $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list)); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $content = json_decode($this->client->getResponse()->getContent(), true); + + $this->assertInternalType('int', $content[0]['entry']); + $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']); + + $this->assertInternalType('int', $content[1]['entry']); + $this->assertEquals('http://0.0.0.0/entry2', $content[1]['url']); + } + + public function testDeleteEntriesListAction() + { + $list = [ + 'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', + 'http://0.0.0.0/entry3', + ]; + + $this->client->request('DELETE', '/api/entries/list?urls='.json_encode($list)); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $content = json_decode($this->client->getResponse()->getContent(), true); + + $this->assertTrue($content[0]['entry']); + $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']); + + $this->assertFalse($content[1]['entry']); + $this->assertEquals('http://0.0.0.0/entry3', $content[1]['url']); + } + + public function testLimitBulkAction() + { + $list = [ + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + 'http://0.0.0.0/entry1', + ]; + + $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list)); + + $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); + $this->assertContains('API limit reached', $this->client->getResponse()->getContent()); + } } diff --git a/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php b/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php new file mode 100644 index 00000000..e6e57f30 --- /dev/null +++ b/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php @@ -0,0 +1,108 @@ +getClient()->getKernel()); + $application->add(new CleanDuplicatesCommand()); + + $command = $application->find('wallabag:clean-duplicates'); + + $tester = new CommandTester($command); + $tester->execute([ + 'command' => $command->getName(), + ]); + + $this->assertContains('Cleaning through 3 user accounts', $tester->getDisplay()); + $this->assertContains('Finished cleaning. 0 duplicates found in total', $tester->getDisplay()); + } + + public function testRunCleanDuplicatesCommandWithBadUsername() + { + $application = new Application($this->getClient()->getKernel()); + $application->add(new CleanDuplicatesCommand()); + + $command = $application->find('wallabag:clean-duplicates'); + + $tester = new CommandTester($command); + $tester->execute([ + 'command' => $command->getName(), + 'username' => 'unknown', + ]); + + $this->assertContains('User "unknown" not found', $tester->getDisplay()); + } + + public function testRunCleanDuplicatesCommandForUser() + { + $application = new Application($this->getClient()->getKernel()); + $application->add(new CleanDuplicatesCommand()); + + $command = $application->find('wallabag:clean-duplicates'); + + $tester = new CommandTester($command); + $tester->execute([ + 'command' => $command->getName(), + 'username' => 'admin', + ]); + + $this->assertContains('Cleaned 0 duplicates for user admin', $tester->getDisplay()); + } + + public function testDuplicate() + { + $url = 'http://www.lemonde.fr/sport/visuel/2017/05/05/rondelle-prison-blanchissage-comprendre-le-hockey-sur-glace_5122587_3242.html'; + $client = $this->getClient(); + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $this->logInAs('admin'); + + $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); + $this->assertCount(0, $nbEntries); + + $user = $em->getRepository('WallabagUserBundle:User')->findOneById($this->getLoggedInUserId()); + + $entry1 = new Entry($user); + $entry1->setUrl($url); + + $entry2 = new Entry($user); + $entry2->setUrl($url); + + $em->persist($entry1); + $em->persist($entry2); + + $em->flush(); + + $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); + $this->assertCount(2, $nbEntries); + + $application = new Application($this->getClient()->getKernel()); + $application->add(new CleanDuplicatesCommand()); + + $command = $application->find('wallabag:clean-duplicates'); + + $tester = new CommandTester($command); + $tester->execute([ + 'command' => $command->getName(), + 'username' => 'admin', + ]); + + $this->assertContains('Cleaned 1 duplicates for user admin', $tester->getDisplay()); + + $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); + $this->assertCount(1, $nbEntries); + + $query = $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.url = :url'); + $query->setParameter('url', $url); + $query->execute(); + } +} diff --git a/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php b/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php index 6798c5d7..b21f3318 100644 --- a/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php +++ b/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php @@ -70,7 +70,7 @@ class ExportCommandTest extends WallabagCoreTestCase $tester->execute([ 'command' => $command->getName(), 'username' => 'admin', - 'filepath' => 'specialexport.json' + 'filepath' => 'specialexport.json', ]); $this->assertFileExists('specialexport.json'); diff --git a/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php b/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php index 1bfd41d5..122a87d4 100644 --- a/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php +++ b/tests/Wallabag/CoreBundle/Command/InstallCommandTest.php @@ -87,6 +87,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); } public function testRunInstallCommandWithReset() @@ -115,12 +116,13 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Checking system requirements.', $tester->getDisplay()); $this->assertContains('Setting up database.', $tester->getDisplay()); - $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay()); + $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); // we force to reset everything - $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay()); + $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay()); } public function testRunInstallCommandWithDatabaseRemoved() @@ -168,6 +170,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); // the current database doesn't already exist $this->assertContains('Creating database and schema, clearing the cache', $tester->getDisplay()); @@ -205,8 +208,9 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); - $this->assertContains('Droping schema and creating schema', $tester->getDisplay()); + $this->assertContains('Dropping schema and creating schema', $tester->getDisplay()); } public function testRunInstallCommandChooseNothing() @@ -259,6 +263,7 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); $this->assertContains('Creating schema', $tester->getDisplay()); } @@ -291,5 +296,6 @@ class InstallCommandTest extends WallabagCoreTestCase $this->assertContains('Setting up database.', $tester->getDisplay()); $this->assertContains('Administration setup.', $tester->getDisplay()); $this->assertContains('Config setup.', $tester->getDisplay()); + $this->assertContains('Run migrations.', $tester->getDisplay()); } } diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index beb0598a..35888f16 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -798,11 +798,87 @@ class ConfigControllerTest extends WallabagCoreTestCase $entryReset = $em ->getRepository('WallabagCoreBundle:Entry') - ->countAllEntriesByUsername($user->getId()); + ->countAllEntriesByUser($user->getId()); $this->assertEquals(0, $entryReset, 'Entries were reset'); } + public function testResetArchivedEntries() + { + $this->logInAs('empty'); + $client = $this->getClient(); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser(); + + $tag = new Tag(); + $tag->setLabel('super'); + $em->persist($tag); + + $entry = new Entry($user); + $entry->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html'); + $entry->setContent('Youhou'); + $entry->setTitle('Youhou'); + $entry->addTag($tag); + $em->persist($entry); + + $annotation = new Annotation($user); + $annotation->setText('annotated'); + $annotation->setQuote('annotated'); + $annotation->setRanges([]); + $annotation->setEntry($entry); + $em->persist($annotation); + + $tagArchived = new Tag(); + $tagArchived->setLabel('super'); + $em->persist($tagArchived); + + $entryArchived = new Entry($user); + $entryArchived->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html'); + $entryArchived->setContent('Youhou'); + $entryArchived->setTitle('Youhou'); + $entryArchived->addTag($tagArchived); + $entryArchived->setArchived(true); + $em->persist($entryArchived); + + $annotationArchived = new Annotation($user); + $annotationArchived->setText('annotated'); + $annotationArchived->setQuote('annotated'); + $annotationArchived->setRanges([]); + $annotationArchived->setEntry($entryArchived); + $em->persist($annotationArchived); + + $em->flush(); + + $crawler = $client->request('GET', '/config#set3'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $crawler = $client->click($crawler->selectLink('config.reset.archived')->link()); + + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertContains('flashes.config.notice.archived_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]); + + $entryReset = $em + ->getRepository('WallabagCoreBundle:Entry') + ->countAllEntriesByUser($user->getId()); + + $this->assertEquals(1, $entryReset, 'Entries were reset'); + + $tagReset = $em + ->getRepository('WallabagCoreBundle:Tag') + ->countAllTags($user->getId()); + + $this->assertEquals(1, $tagReset, 'Tags were reset'); + + $annotationsReset = $em + ->getRepository('WallabagAnnotationBundle:Annotation') + ->findAnnotationsByPageId($annotationArchived->getId(), $user->getId()); + + $this->assertEmpty($annotationsReset, 'Annotations were reset'); + } + public function testResetEntriesCascade() { $this->logInAs('empty'); @@ -843,7 +919,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $entryReset = $em ->getRepository('WallabagCoreBundle:Entry') - ->countAllEntriesByUsername($user->getId()); + ->countAllEntriesByUser($user->getId()); $this->assertEquals(0, $entryReset, 'Entries were reset'); diff --git a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php index 3eb6d47f..35438c83 100644 --- a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php @@ -135,9 +135,44 @@ class EntryControllerTest extends WallabagCoreTestCase ->getRepository('WallabagCoreBundle:Entry') ->findByUrlAndUserId($this->url, $this->getLoggedInUserId()); + $author = $content->getPublishedBy(); + $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content); $this->assertEquals($this->url, $content->getUrl()); $this->assertContains('Google', $content->getTitle()); + $this->assertEquals('2015-03-28 15:37:39', $content->getPublishedAt()->format('Y-m-d H:i:s')); + $this->assertEquals('Morgane Tual', $author[0]); + } + + public function testPostWithMultipleAuthors() + { + $url = 'http://www.liberation.fr/planete/2017/04/05/donald-trump-et-xi-jinping-tentative-de-flirt-en-floride_1560768'; + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/new'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('form[name=entry]')->form(); + + $data = [ + 'entry[url]' => $url, + ]; + + $client->submit($form, $data); + + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + + $content = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUrlAndUserId($url, $this->getLoggedInUserId()); + + $authors = $content->getPublishedBy(); + $this->assertEquals('2017-04-05 19:26:13', $content->getPublishedAt()->format('Y-m-d H:i:s')); + $this->assertEquals('Raphaël Balenieri, correspondant à Pékin', $authors[0]); + $this->assertEquals('Frédéric Autran, correspondant à New York', $authors[1]); } public function testPostNewOkUrlExist() @@ -235,7 +270,7 @@ class EntryControllerTest extends WallabagCoreTestCase ->findOneByUrl($url); $tags = $entry->getTags(); - $this->assertCount(1, $tags); + $this->assertCount(2, $tags); $this->assertEquals('wallabag', $tags[0]->getLabel()); $em->remove($entry); @@ -264,8 +299,8 @@ class EntryControllerTest extends WallabagCoreTestCase $tags = $entry->getTags(); - $this->assertCount(1, $tags); - $this->assertEquals('wallabag', $tags[0]->getLabel()); + $this->assertCount(2, $tags); + $this->assertEquals('wallabag', $tags[1]->getLabel()); $em->remove($entry); $em->flush(); @@ -606,7 +641,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(2, $crawler->filter('div[class=entry]')); + $this->assertCount(3, $crawler->filter('div[class=entry]')); } public function testFilterOnReadingTimeOnlyLower() @@ -642,7 +677,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(4, $crawler->filter('div[class=entry]')); + $this->assertCount(5, $crawler->filter('div[class=entry]')); } public function testFilterOnCreationDate() @@ -661,7 +696,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(5, $crawler->filter('div[class=entry]')); + $this->assertCount(6, $crawler->filter('div[class=entry]')); $data = [ 'entry_filter[createdAt][left_date]' => date('d/m/Y'), @@ -670,7 +705,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(5, $crawler->filter('div[class=entry]')); + $this->assertCount(6, $crawler->filter('div[class=entry]')); $data = [ 'entry_filter[createdAt][left_date]' => '01/01/1970', @@ -774,7 +809,7 @@ class EntryControllerTest extends WallabagCoreTestCase $form['entry_filter[previewPicture]']->tick(); $crawler = $client->submit($form); - $this->assertCount(1, $crawler->filter('div[class=entry]')); + $this->assertCount(2, $crawler->filter('div[class=entry]')); } public function testFilterOnLanguage() @@ -789,7 +824,7 @@ class EntryControllerTest extends WallabagCoreTestCase ]; $crawler = $client->submit($form, $data); - $this->assertCount(2, $crawler->filter('div[class=entry]')); + $this->assertCount(3, $crawler->filter('div[class=entry]')); $form = $crawler->filter('button[id=submit-filter]')->form(); $data = [ @@ -1014,7 +1049,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(1, $crawler->filter('div[class=entry]')); + $this->assertCount(2, $crawler->filter('div[class=entry]')); $crawler = $client->request('GET', '/all/list'); $form = $crawler->filter('button[id=submit-filter]')->form(); @@ -1025,7 +1060,7 @@ class EntryControllerTest extends WallabagCoreTestCase $crawler = $client->submit($form, $data); - $this->assertCount(7, $crawler->filter('div[class=entry]')); + $this->assertCount(8, $crawler->filter('div[class=entry]')); } public function testSearch() diff --git a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php index 32a18e26..63f2c829 100644 --- a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php @@ -189,11 +189,9 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertContains($contentInDB[0]['language'], $csv[1]); $this->assertContains($contentInDB[0]['createdAt']->format('d/m/Y h:i:s'), $csv[1]); - $expectedTag = []; foreach ($contentInDB[0]['tags'] as $tag) { - $expectedTag[] = $tag['label']; + $this->assertContains($tag['label'], $csv[1]); } - $this->assertContains(implode(', ', $expectedTag), $csv[1]); } public function testJsonExport() @@ -241,7 +239,7 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertEquals($contentInDB->getLanguage(), $content[0]['language']); $this->assertEquals($contentInDB->getReadingtime(), $content[0]['reading_time']); $this->assertEquals($contentInDB->getDomainname(), $content[0]['domain_name']); - $this->assertEquals(['foo bar', 'baz'], $content[0]['tags']); + $this->assertEquals(['foo bar', 'baz', 'foot'], $content[0]['tags']); } public function testXmlExport() diff --git a/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php b/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php index fa1a3539..c3b22dcd 100644 --- a/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/TagControllerTest.php @@ -46,7 +46,7 @@ class TagControllerTest extends WallabagCoreTestCase ->getRepository('WallabagCoreBundle:Entry') ->findByUrlAndUserId('http://0.0.0.0/entry1', $this->getLoggedInUserId()); - $this->assertEquals(3, count($entry->getTags())); + $this->assertEquals(4, count($entry->getTags())); // tag already exists and already assigned $client->submit($form, $data); @@ -57,7 +57,7 @@ class TagControllerTest extends WallabagCoreTestCase ->getRepository('WallabagCoreBundle:Entry') ->find($entry->getId()); - $this->assertEquals(3, count($newEntry->getTags())); + $this->assertEquals(4, count($newEntry->getTags())); // tag already exists but still not assigned to this entry $data = [ @@ -72,7 +72,7 @@ class TagControllerTest extends WallabagCoreTestCase ->getRepository('WallabagCoreBundle:Entry') ->find($entry->getId()); - $this->assertEquals(3, count($newEntry->getTags())); + $this->assertEquals(4, count($newEntry->getTags())); } public function testAddMultipleTagToEntry() diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php index 5956b502..8abb1bbb 100644 --- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php +++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php @@ -111,7 +111,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase $this->assertEquals('http://domain.io', $entry->getUrl()); $this->assertEquals('my title', $entry->getTitle()); - $this->assertEquals($this->fetchingErrorMessage . '

    But we found a short description:

    desc', $entry->getContent()); + $this->assertEquals($this->fetchingErrorMessage.'

    But we found a short description:

    desc', $entry->getContent()); $this->assertEmpty($entry->getPreviewPicture()); $this->assertEmpty($entry->getLanguage()); $this->assertEmpty($entry->getHttpStatus()); diff --git a/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php index 7be1eb18..7043c345 100644 --- a/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php +++ b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php @@ -10,7 +10,7 @@ use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; class ImportCommandTest extends WallabagCoreTestCase { /** - * @expectedException Symfony\Component\Console\Exception\RuntimeException + * @expectedException \Symfony\Component\Console\Exception\RuntimeException * @expectedExceptionMessage Not enough arguments */ public function testRunImportCommandWithoutArguments() @@ -27,7 +27,7 @@ class ImportCommandTest extends WallabagCoreTestCase } /** - * @expectedException Symfony\Component\Config\Definition\Exception\Exception + * @expectedException \Symfony\Component\Config\Definition\Exception\Exception * @expectedExceptionMessage not found */ public function testRunImportCommandWithoutFilepath() @@ -40,16 +40,15 @@ class ImportCommandTest extends WallabagCoreTestCase $tester = new CommandTester($command); $tester->execute([ 'command' => $command->getName(), - 'userId' => 1, + 'username' => 'admin', 'filepath' => 1, ]); } /** - * @expectedException Symfony\Component\Config\Definition\Exception\Exception - * @expectedExceptionMessage User with id + * @expectedException \Doctrine\ORM\NoResultException */ - public function testRunImportCommandWithoutUserId() + public function testRunImportCommandWithWrongUsername() { $application = new Application($this->getClient()->getKernel()); $application->add(new ImportCommand()); @@ -59,7 +58,7 @@ class ImportCommandTest extends WallabagCoreTestCase $tester = new CommandTester($command); $tester->execute([ 'command' => $command->getName(), - 'userId' => 0, + 'username' => 'random', 'filepath' => './', ]); } @@ -74,7 +73,7 @@ class ImportCommandTest extends WallabagCoreTestCase $tester = new CommandTester($command); $tester->execute([ 'command' => $command->getName(), - 'userId' => 1, + 'username' => 'admin', 'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json', '--importer' => 'v2', ]); @@ -82,4 +81,20 @@ class ImportCommandTest extends WallabagCoreTestCase $this->assertContains('imported', $tester->getDisplay()); $this->assertContains('already saved', $tester->getDisplay()); } + + public function testRunImportCommandWithUserId() + { + $application = new Application($this->getClient()->getKernel()); + $application->add(new ImportCommand()); + + $command = $application->find('wallabag:import'); + + $tester = new CommandTester($command); + $tester->execute([ + 'command' => $command->getName(), + 'username' => 1, + 'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json', + '--useUserId' => true, + ]); + } } diff --git a/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php index c1f82ea9..8e9f65e3 100644 --- a/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php @@ -120,7 +120,7 @@ class ChromeControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.usinenouvelle.com is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.usinenouvelle.com is ok'); - $this->assertEquals(0, count($content->getTags())); + $this->assertEquals(1, count($content->getTags())); $createdAt = $content->getCreatedAt(); $this->assertEquals('2011', $createdAt->format('Y')); diff --git a/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php index 7557ea32..68453027 100644 --- a/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php @@ -121,7 +121,7 @@ class FirefoxControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://lexpansion.lexpress.fr is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://lexpansion.lexpress.fr is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://lexpansion.lexpress.fr is ok'); - $this->assertEquals(2, count($content->getTags())); + $this->assertEquals(3, count($content->getTags())); $content = $client->getContainer() ->get('doctrine.orm.entity_manager') diff --git a/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php index 3f6f2b9f..c2e5fdb7 100644 --- a/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php @@ -121,7 +121,7 @@ class InstapaperControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is ok'); - $this->assertEquals(0, count($content->getTags())); + $this->assertEquals(1, count($content->getTags())); $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); } diff --git a/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php b/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php index 75a7e332..96b32484 100644 --- a/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/PinboardControllerTest.php @@ -121,7 +121,7 @@ class PinboardControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for https://ma.ttias.be is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for https://ma.ttias.be is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for https://ma.ttias.be is ok'); - $this->assertEquals(2, count($content->getTags())); + $this->assertEquals(3, count($content->getTags())); $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); $this->assertEquals('2016-10-26', $content->getCreatedAt()->format('Y-m-d')); } diff --git a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php index acb61ca1..e6d33fe9 100644 --- a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php @@ -121,7 +121,7 @@ class ReadabilityControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.zataz.com is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.zataz.com is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.zataz.com is ok'); - $this->assertEquals(0, count($content->getTags())); + $this->assertEquals(1, count($content->getTags())); $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); $this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d')); } diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php index acc39997..0c7f97ed 100644 --- a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php @@ -129,7 +129,7 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.framablog.org is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.framablog.org is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.framablog.org is ok'); - $this->assertEquals(1, count($content->getTags())); + $this->assertEquals(2, count($content->getTags())); $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); } diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php index 26e2f40b..556ab1bd 100644 --- a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php @@ -122,7 +122,7 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for http://www.liberation.fr is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for http://www.liberation.fr is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for http://www.liberation.fr is ok'); - $this->assertEquals(0, count($content->getTags())); + $this->assertEquals(1, count($content->getTags())); $content = $client->getContainer() ->get('doctrine.orm.entity_manager') @@ -135,7 +135,7 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase $this->assertNotEmpty($content->getMimetype(), 'Mimetype for https://www.mediapart.fr is ok'); $this->assertNotEmpty($content->getPreviewPicture(), 'Preview picture for https://www.mediapart.fr is ok'); $this->assertNotEmpty($content->getLanguage(), 'Language for https://www.mediapart.fr is ok'); - $this->assertEquals(2, count($content->getTags())); + $this->assertEquals(3, count($content->getTags())); $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); $this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d')); } diff --git a/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php b/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php index 4faddfc4..44b9a030 100644 --- a/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php +++ b/tests/Wallabag/UserBundle/Controller/ManageControllerTest.php @@ -10,7 +10,7 @@ class ManageControllerTest extends WallabagCoreTestCase { $client = $this->getClient(); - $client->request('GET', '/users/'); + $client->request('GET', '/users/list'); $this->assertEquals(302, $client->getResponse()->getStatusCode()); $this->assertContains('login', $client->getResponse()->headers->get('location')); @@ -22,7 +22,7 @@ class ManageControllerTest extends WallabagCoreTestCase $client = $this->getClient(); // Create a new user in the database - $crawler = $client->request('GET', '/users/'); + $crawler = $client->request('GET', '/users/list'); $this->assertEquals(200, $client->getResponse()->getStatusCode(), 'Unexpected HTTP status code for GET /users/'); $crawler = $client->click($crawler->selectLink('user.list.create_new_one')->link()); @@ -36,7 +36,7 @@ class ManageControllerTest extends WallabagCoreTestCase $client->submit($form); $client->followRedirect(); - $crawler = $client->request('GET', '/users/'); + $crawler = $client->request('GET', '/users/list'); // Check data in the show view $this->assertGreaterThan(0, $crawler->filter('td:contains("test_user")')->count(), 'Missing element td:contains("test_user")'); @@ -57,7 +57,7 @@ class ManageControllerTest extends WallabagCoreTestCase // Check the element contains an attribute with value equals "Foo User" $this->assertGreaterThan(0, $crawler->filter('[value="Foo User"]')->count(), 'Missing element [value="Foo User"]'); - $crawler = $client->request('GET', '/users/'); + $crawler = $client->request('GET', '/users/list'); $crawler = $client->click($crawler->selectLink('user.list.edit_action')->last()->link()); // Delete the user @@ -78,4 +78,22 @@ class ManageControllerTest extends WallabagCoreTestCase $this->assertEquals('disabled', $disabled[0]); } + + public function testUserSearch() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + // Search on unread list + $crawler = $client->request('GET', '/users/list'); + + $form = $crawler->filter('form[name=search_users]')->form(); + $data = [ + 'search_user[term]' => 'admin', + ]; + + $crawler = $client->submit($form, $data); + + $this->assertCount(2, $crawler->filter('tr')); // 1 result + table header + } } diff --git a/web/bundles/wallabagcore/baggy.css b/web/bundles/wallabagcore/baggy.css new file mode 100644 index 00000000..c52f27af --- /dev/null +++ b/web/bundles/wallabagcore/baggy.css @@ -0,0 +1,2 @@ +.annotator-filter *,.annotator-notice,.annotator-widget *{font-family:Helvetica Neue,Arial,Helvetica,sans-serif;font-weight:400;text-align:left;margin:0;padding:0;background:none;-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;-moz-box-shadow:none;-webkit-box-shadow:none;-o-box-shadow:none;box-shadow:none;color:#909090}.annotator-adder{background-image:url(/bundles/wallabagcore/img/annotator-icon-sprite.png);background-repeat:no-repeat}.annotator-editor a:after,.annotator-filter .annotator-filter-navigation button:after,.annotator-filter .annotator-filter-property .annotator-filter-clear,.annotator-resize,.annotator-viewer .annotator-controls a,.annotator-viewer .annotator-controls button,.annotator-widget:after{background-image:url(/bundles/wallabagcore/img/annotator-glyph-sprite.png);background-repeat:no-repeat}.annotator-hl{background:#ffff0a;background:rgba(255,255,10,.3);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4DFFFF0A, endColorstr=#4DFFFF0A)"}.annotator-hl-temporary{background:#007cff;background:rgba(0,124,255,.3);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4D007CFF, endColorstr=#4D007CFF)"}.annotator-wrapper{position:relative}.annotator-adder,.annotator-notice,.annotator-outer{z-index:1020}.annotator-filter{z-index:1010}.annotator-adder,.annotator-notice,.annotator-outer,.annotator-widget{position:absolute;font-size:10px;line-height:1}.annotator-hide{display:none;visibility:hidden}.annotator-adder{margin-top:-48px;margin-left:-24px;width:48px;height:48px;background-position:0 0}.annotator-adder:hover{background-position:top}.annotator-adder:active{background-position:100%}.annotator-adder button{display:block;width:36px;height:41px;margin:0 auto;border:none;background:none;text-indent:-999em;cursor:pointer}.annotator-outer{width:0;height:0}.annotator-widget{margin:0;padding:0;bottom:15px;left:-18px;min-width:265px;background-color:#fbfbfb;background-color:hsla(0,0%,98%,.98);border:1px solid #7a7a7a;border:1px solid hsla(0,0%,48%,.6);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 15px rgba(0,0,0,.2);-moz-box-shadow:0 5px 15px rgba(0,0,0,.2);-o-box-shadow:0 5px 15px rgba(0,0,0,.2);box-shadow:0 5px 15px rgba(0,0,0,.2)}.annotator-invert-x .annotator-widget{left:auto;right:-18px}.annotator-invert-y .annotator-widget{bottom:auto;top:8px}.annotator-widget strong{font-weight:700}.annotator-widget .annotator-item,.annotator-widget .annotator-listing{padding:0;margin:0;list-style:none}.annotator-widget:after{content:"";display:block;width:18px;height:10px;background-position:0 0;position:absolute;bottom:-10px;left:8px}.annotator-invert-x .annotator-widget:after{left:auto;right:8px}.annotator-invert-y .annotator-widget:after{background-position:0 -15px;bottom:auto;top:-9px}.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea,.annotator-widget .annotator-item{position:relative;font-size:12px}.annotator-viewer .annotator-item{border-top:2px solid #7a7a7a;border-top:2px solid hsla(0,0%,48%,.2)}.annotator-widget .annotator-item:first-child{border-top:none}.annotator-editor .annotator-item,.annotator-viewer div{border-top:1px solid #858585;border-top:1px solid hsla(0,0%,52%,.11)}.annotator-viewer div{padding:6px}.annotator-viewer .annotator-item ol,.annotator-viewer .annotator-item ul{padding:4px 16px}.annotator-editor .annotator-item:first-child textarea,.annotator-viewer div:first-of-type{padding-top:12px;padding-bottom:12px;color:#3c3c3c;font-size:13px;font-style:italic;line-height:1.3;border-top:none}.annotator-viewer .annotator-controls{position:relative;top:5px;right:5px;padding-left:5px;opacity:0;-webkit-transition:opacity .2s ease-in;-moz-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in;float:right}.annotator-viewer li .annotator-controls.annotator-visible,.annotator-viewer li:hover .annotator-controls{opacity:1}.annotator-viewer .annotator-controls a,.annotator-viewer .annotator-controls button{cursor:pointer;display:inline-block;width:13px;height:13px;margin-left:2px;border:none;opacity:.2;text-indent:-900em;background-color:transparent;outline:none}.annotator-viewer .annotator-controls a:focus,.annotator-viewer .annotator-controls a:hover,.annotator-viewer .annotator-controls button:focus,.annotator-viewer .annotator-controls button:hover{opacity:.9}.annotator-viewer .annotator-controls a:active,.annotator-viewer .annotator-controls button:active{opacity:1}.annotator-viewer .annotator-controls button[disabled]{display:none}.annotator-viewer .annotator-controls .annotator-edit{background-position:0 -60px}.annotator-viewer .annotator-controls .annotator-delete{background-position:0 -75px}.annotator-viewer .annotator-controls .annotator-link{background-position:0 -270px}.annotator-editor .annotator-item{position:relative}.annotator-editor .annotator-item label{top:0;display:inline;cursor:pointer;font-size:12px}.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea{display:block;min-width:100%;padding:10px 8px;border:none;margin:0;color:#3c3c3c;background:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;resize:none}.annotator-editor .annotator-item textarea::-webkit-scrollbar{height:8px;width:8px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-track-piece{margin:13px 0 3px;background-color:#e5e5e5;-webkit-border-radius:4px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:vertical{height:25px;background-color:#ccc;-webkit-border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1)}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:horizontal{width:25px;background-color:#ccc;-webkit-border-radius:4px}.annotator-editor .annotator-item:first-child textarea{min-height:5.5em;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor .annotator-item input:focus,.annotator-editor .annotator-item textarea:focus{background-color:#f3f3f3;outline:none}.annotator-editor .annotator-item input[type=checkbox],.annotator-editor .annotator-item input[type=radio]{width:auto;min-width:0;padding:0;display:inline;margin:0 4px 0 0;cursor:pointer}.annotator-editor .annotator-checkbox{padding:8px 6px}.annotator-editor .annotator-controls,.annotator-filter,.annotator-filter .annotator-filter-navigation button{text-align:right;padding:3px;border-top:1px solid #d4d4d4;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(.6,#dcdcdc),to(#d2d2d2));background-image:-moz-linear-gradient(to bottom,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:-webkit-linear-gradient(180deg,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:linear-gradient(180deg,#f5f5f5,#dcdcdc 60%,#d2d2d2);-webkit-box-shadow:inset 1px 0 0 hsla(0,0%,100%,.7),inset -1px 0 0 hsla(0,0%,100%,.7),inset 0 1px 0 hsla(0,0%,100%,.7);-moz-box-shadow:inset 1px 0 0 hsla(0,0%,100%,.7),inset -1px 0 0 hsla(0,0%,100%,.7),inset 0 1px 0 hsla(0,0%,100%,.7);-o-box-shadow:inset 1px 0 0 hsla(0,0%,100%,.7),inset -1px 0 0 hsla(0,0%,100%,.7),inset 0 1px 0 hsla(0,0%,100%,.7);box-shadow:inset 1px 0 0 hsla(0,0%,100%,.7),inset -1px 0 0 hsla(0,0%,100%,.7),inset 0 1px 0 hsla(0,0%,100%,.7);-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;-o-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.annotator-editor.annotator-invert-y .annotator-controls{border-top:none;border-bottom:1px solid #b4b4b4;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor a,.annotator-filter .annotator-filter-property label{position:relative;display:inline-block;padding:0 6px 0 22px;color:#363636;text-shadow:0 1px 0 hsla(0,0%,100%,.75);text-decoration:none;line-height:24px;font-size:12px;font-weight:700;border:1px solid #a2a2a2;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(.5,#d2d2d2),color-stop(.5,#bebebe),to(#d2d2d2));background-image:-moz-linear-gradient(to bottom,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);background-image:-webkit-linear-gradient(180deg,#f5f5f5,#d2d2d2 50%,#bebebe 0,#d2d2d2);background-image:linear-gradient(180deg,#f5f5f5,#d2d2d2 50%,#bebebe 0,#d2d2d2);-webkit-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);-moz-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);-o-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);-webkit-border-radius:5px;-moz-border-radius:5px;-o-border-radius:5px;border-radius:5px}.annotator-editor a:after{position:absolute;top:50%;left:5px;display:block;content:"";width:15px;height:15px;margin-top:-7px;background-position:0 -90px}.annotator-editor a.annotator-focus,.annotator-editor a:focus,.annotator-editor a:hover,.annotator-filter .annotator-filter-active label,.annotator-filter .annotator-filter-navigation button:hover{outline:none;border-color:#435aa0;background-color:#3865f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#7691fb),color-stop(.5,#5075fb),color-stop(.5,#3865f9),to(#3665fa));background-image:-moz-linear-gradient(to bottom,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);background-image:-webkit-linear-gradient(180deg,#7691fb,#5075fb 50%,#3865f9 0,#3665fa);background-image:linear-gradient(180deg,#7691fb,#5075fb 50%,#3865f9 0,#3665fa);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.42)}.annotator-editor a:focus:after,.annotator-editor a:hover:after{margin-top:-8px;background-position:0 -105px}.annotator-editor a:active,.annotator-filter .annotator-filter-navigation button:active{border-color:#700c49;background-color:#d12e8e;background-image:-webkit-gradient(linear,left top,left bottom,from(#fc7cca),color-stop(.5,#e85db2),color-stop(.5,#d12e8e),to(#ff009c));background-image:-moz-linear-gradient(to bottom,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c);background-image:-webkit-linear-gradient(180deg,#fc7cca,#e85db2 50%,#d12e8e 0,#ff009c);background-image:linear-gradient(180deg,#fc7cca,#e85db2 50%,#d12e8e 0,#ff009c)}.annotator-editor a.annotator-save:after{background-position:0 -120px}.annotator-editor a.annotator-save.annotator-focus:after,.annotator-editor a.annotator-save:focus:after,.annotator-editor a.annotator-save:hover:after{margin-top:-8px;background-position:0 -135px}.annotator-editor .annotator-widget:after{background-position:0 -30px}.annotator-editor.annotator-invert-y .annotator-widget .annotator-controls{background-color:#f2f2f2}.annotator-editor.annotator-invert-y .annotator-widget:after{background-position:0 -45px;height:11px}.annotator-resize{position:absolute;top:0;right:0;width:12px;height:12px;background-position:2px -150px}.annotator-invert-x .annotator-resize{right:auto;left:0;background-position:0 -195px}.annotator-invert-y .annotator-resize{top:auto;bottom:0;background-position:2px -165px}.annotator-invert-y.annotator-invert-x .annotator-resize{background-position:0 -180px}.annotator-notice{color:#fff;position:fixed;top:-54px;left:0;width:100%;font-size:14px;line-height:50px;text-align:center;background:#000;background:rgba(0,0,0,.9);border-bottom:4px solid #d4d4d4;-webkit-transition:top .4s ease-out;-moz-transition:top .4s ease-out;-o-transition:top .4s ease-out;transition:top .4s ease-out}.annotator-notice-success{border-color:#3665f9}.annotator-notice-error{border-color:#ff7e00}.annotator-notice p{margin:0}.annotator-notice a{color:#fff}.annotator-notice-show{top:0}.annotator-tags{margin-bottom:-2px}.annotator-tags .annotator-tag{display:inline-block;padding:0 8px;margin-bottom:2px;line-height:1.6;font-weight:700;background-color:#e6e6e6;-webkit-border-radius:8px;-moz-border-radius:8px;-o-border-radius:8px;border-radius:8px}.annotator-filter{position:fixed;top:0;right:0;left:0;text-align:left;line-height:0;border:none;border-bottom:1px solid #878787;padding-left:10px;padding-right:10px;-webkit-border-radius:0;-moz-border-radius:0;-o-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.3);-moz-box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.3);-o-box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.3);box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.3)}.annotator-filter strong{font-size:12px;font-weight:700;color:#3c3c3c;text-shadow:0 1px 0 hsla(0,0%,100%,.7);position:relative;top:-9px}.annotator-filter .annotator-filter-navigation,.annotator-filter .annotator-filter-property{position:relative;display:inline-block;overflow:hidden;line-height:10px;padding:2px 0;margin-right:8px}.annotator-filter .annotator-filter-navigation button,.annotator-filter .annotator-filter-property label{text-align:left;display:block;float:left;line-height:20px;-webkit-border-radius:10px 0 0 10px;-moz-border-radius:10px 0 0 10px;-o-border-radius:10px 0 0 10px;border-radius:10px 0 0 10px}.annotator-filter .annotator-filter-property label{padding-left:8px}.annotator-filter .annotator-filter-property input{display:block;float:right;-webkit-appearance:none;background-color:#fff;border:1px solid #878787;border-left:none;padding:2px 4px;line-height:16px;min-height:16px;font-size:12px;width:150px;color:#333;background-color:#f8f8f8;-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);-o-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);box-shadow:inset 0 1px 1px rgba(0,0,0,.2)}.annotator-filter .annotator-filter-property input:focus{outline:none;background-color:#fff}.annotator-filter .annotator-filter-clear{position:absolute;right:3px;top:6px;border:none;text-indent:-900em;width:15px;height:15px;background-position:0 -90px;opacity:.4}.annotator-filter .annotator-filter-clear:focus,.annotator-filter .annotator-filter-clear:hover{opacity:.8}.annotator-filter .annotator-filter-clear:active{opacity:1}.annotator-filter .annotator-filter-navigation button{border:1px solid #a2a2a2;padding:0;text-indent:-900px;width:20px;min-height:22px;-webkit-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);-moz-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);-o-box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8);box-shadow:inset 0 0 5px hsla(0,0%,100%,.2),inset 0 0 1px hsla(0,0%,100%,.8)}.annotator-filter .annotator-filter-navigation button,.annotator-filter .annotator-filter-navigation button:focus,.annotator-filter .annotator-filter-navigation button:hover{color:transparent}.annotator-filter .annotator-filter-navigation button:after{position:absolute;top:8px;left:8px;content:"";display:block;width:9px;height:9px;background-position:0 -210px}.annotator-filter .annotator-filter-navigation button:hover:after{background-position:0 -225px}.annotator-filter .annotator-filter-navigation .annotator-filter-next{-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;border-left:none}.annotator-filter .annotator-filter-navigation .annotator-filter-next:after{left:auto;right:7px;background-position:0 -240px}.annotator-filter .annotator-filter-navigation .annotator-filter-next:hover:after{background-position:0 -255px}.annotator-hl-active{background:#ffff0a;background:rgba(255,255,10,.8);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#CCFFFF0A, endColorstr=#CCFFFF0A)"}.annotator-hl-filtered{background-color:transparent}@font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url(/bundles/wallabagcore/fonts/MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(/bundles/wallabagcore/fonts/MaterialIcons-Regular.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/MaterialIcons-Regular.woff) format("woff"),url(/bundles/wallabagcore/fonts/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga"}@font-face{font-family:Lato;font-weight:100;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-hairline.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-hairline.woff) format("woff")}@font-face{font-family:Lato;font-weight:100;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-hairline-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-hairline-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:200;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-thin.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-thin.woff) format("woff")}@font-face{font-family:Lato;font-weight:200;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-thin-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-thin-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:300;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-light.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-light.woff) format("woff")}@font-face{font-family:Lato;font-weight:300;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-light-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-light-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:400;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-normal.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-normal.woff) format("woff")}@font-face{font-family:Lato;font-weight:400;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-normal-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-normal-italic.woff) format("woff")}@font-face{font-family:Lato Medium;font-weight:400;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-medium.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-medium.woff) format("woff")}@font-face{font-family:Lato Medium;font-weight:400;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-medium-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-medium-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:500;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-semibold.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-semibold.woff) format("woff")}@font-face{font-family:Lato;font-weight:500;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-semibold-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-semibold-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:600;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-bold.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-bold.woff) format("woff")}@font-face{font-family:Lato;font-weight:600;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-bold-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-bold-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:800;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-heavy.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-heavy.woff) format("woff")}@font-face{font-family:Lato;font-weight:800;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-heavy-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-heavy-italic.woff) format("woff")}@font-face{font-family:Lato;font-weight:900;font-style:normal;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-black.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-black.woff) format("woff")}@font-face{font-family:Lato;font-weight:900;font-style:italic;text-rendering:optimizeLegibility;src:url(/bundles/wallabagcore/fonts/lato-black-italic.woff2) format("woff2"),url(/bundles/wallabagcore/fonts/lato-black-italic.woff) format("woff")}.material-icons.md-18{font-size:18px}.material-icons.md-24{font-size:24px}.material-icons.md-36{font-size:36px}.material-icons.md-48{font-size:48px}.material-icons.md-dark{color:rgba(0,0,0,.54)}.material-icons.md-dark.md-inactive{color:rgba(0,0,0,.26)}.material-icons.md-light{color:#fff}.material-icons.md-light.md-inactive{color:hsla(0,0%,100%,.3)}::selection{color:#fff;background-color:#000}.desktopHide{display:none}.logo{position:fixed;z-index:20;top:.4em;left:.6em}h2,h3,h4{font-family:PT Sans,sans-serif;text-transform:uppercase}label,li,p{color:#666}a{color:#000;font-weight:700}a.nostyle,a:focus,a:hover{text-decoration:none}form fieldset{border:0;padding:0;margin:0}form input[type=email],form input[type=number],form input[type=password],form input[type=text],form input[type=url],select{border:1px solid #999;padding:.5em 1em;min-width:12em;color:#666}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0;background:#fff url(/bundles/wallabagcore/img/bg-select.png) no-repeat 100%}}.inline .row{display:inline-block;margin-right:.5em}.inline label{min-width:6em}fieldset label{display:inline-block;min-width:12.5em;color:#666}label{margin-right:.5em}form .row{margin-bottom:.5em}form button,input[type=submit]{cursor:pointer;background-color:#000;color:#fff;padding:.5em 1em;display:inline-block;border:1px solid #000}form button:focus,form button:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#fff;color:#000;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-ms-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}#bookmarklet{cursor:move}h2:after{content:"";height:4px;width:20%;background-color:#000;display:block}.links,.links li{padding:0;margin:0}.links li{list-style:none}#links{position:fixed;top:0;width:10em;left:0;text-align:right;background-color:#333;padding-top:9.5em;height:100%;box-shadow:inset -4px 0 20px rgba(0,0,0,.6);z-index:15}#links>li>a{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}#links>li>a:focus,#links>li>a:hover{background-color:#999;color:#000}#links .current:after{content:"";width:0;height:0;position:absolute;border:10px solid transparent;border-right-color:#eee;right:0;top:50%;margin-top:-10px}#links li:last-child{position:fixed;bottom:1em;width:10em}#links li:last-child a:before{font-size:1.2em;position:relative;top:2px}#main{margin-left:12em;position:relative;z-index:10;padding-right:5%;padding-bottom:1em}#sort{padding:0;list-style-type:none;opacity:.5;display:inline-block}#sort li{display:inline;font-size:.9em}#sort li+li{margin-left:10px}#sort a{padding:2px 2px 0;vertical-align:middle}#sort img{vertical-align:baseline}#sort img :hover{cursor:pointer}#display-mode{float:right;margin-top:10px;margin-bottom:10px;opacity:.5}#listmode{width:16px;display:inline-block;text-decoration:none}#listmode.tablemode{background:url(/bundles/wallabagcore/img/table.png) no-repeat bottom}#listmode .listmode{background:url(/bundles/wallabagcore/img/list.png) no-repeat bottom}#warning_message{position:fixed;background-color:tomato;z-index:1000;bottom:0;left:0;width:100%;color:#000}#content{margin-top:2em;min-height:30em}footer{text-align:right;position:relative;bottom:0;right:5em;color:#999;font-size:.8em;font-style:italic;z-index:20}footer a{color:#999;font-weight:400}.list-entries{letter-spacing:-5px}.listmode.entry{width:100%;height:inherit}.card-entry-tags{max-height:2em;overflow-y:hidden;padding:0;margin:0}.card-entry-tags li,.card-entry-tags span{display:inline-block;margin:0 5px;padding:5px 12px;background-color:rgba(0,0,0,.6);border-radius:3px;max-height:2em;overflow:hidden;text-overflow:ellipsis}.card-entry-labels a,.card-entry-tags a{text-decoration:none;font-weight:400;color:#fff}.nav-panel-add-tag{margin-top:10px}.list-entries+.results{margin-bottom:2em}.created-at,.reading-time{color:#999;font-style:italic;font-weight:400;font-size:.9em}.estimatedTime small{position:relative;top:-1px}.entry{background-color:#fff;letter-spacing:normal;box-shadow:0 3px 7px rgba(0,0,0,.3);display:inline-block;width:32%;margin-bottom:1.5em;vertical-align:top;margin-right:1%;position:relative;overflow:hidden;padding:1.5em 0 3em;height:440px}.entry img.preview{width:100%;object-fit:cover;height:100%}.entry:before{width:0;height:0;border:10px solid transparent;border-bottom-color:#000;bottom:.7em;z-index:10;right:1.5em}.entry:after,.entry:before{content:"";position:absolute;transition:all .5s ease}.entry:after{height:7px;width:100%;bottom:0;left:0;background-color:#000}.entry:hover{box-shadow:0 3px 10px #000}.entry:hover:after{height:40px}.entry:hover:before{bottom:2.3em}.entry:hover h2 a{color:#666}.entry:hover .tools{bottom:0}.entry h2{text-transform:none;margin-bottom:0;line-height:1.2;margin-left:5px}.entry:after{content:none}.entry a{display:block;text-decoration:none;color:#000;word-wrap:break-word;transition:all .5s ease}.entry p{color:#666;font-size:.9em;line-height:1.7;margin:5px 5px auto}.entry h2 a:first-letter{text-transform:uppercase}.entry .tools{position:absolute;bottom:-40px;left:0;background:#000;width:100%;z-index:10;padding-right:.5em;text-align:right;transition:all .5s ease}.entry .tools a{color:#666;text-decoration:none;display:block;padding:.4em}.entry .tools a:hover{color:#fff}.entry .tools li{display:inline-block;margin-top:10px}.entry .tools li:first-child{float:left;font-size:.9em;max-width:calc(100% - 40px * 4);text-overflow:ellipsis;overflow:hidden;white-space:nowrap;max-height:2em;margin-left:10px}.entry .card-entry-labels{position:absolute;top:100px;left:-1em;z-index:90;max-width:50%;padding-left:0}.entry .card-entry-labels li{margin:10px 10px 10px auto;padding:5px 12px 5px 25px;background-color:rgba(0,0,0,.6);border-radius:0 3px 3px 0;color:#fff;cursor:default;max-height:2em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.entry .card-entry-labels li a{color:#fff}.entry:nth-child(3n+1){margin-left:0}.results{letter-spacing:-5px;padding:0 0 .5em}.results>*{display:inline-block;vertical-align:top;letter-spacing:normal;width:50%}.results>*,div.pagination ul{text-align:right}.nb-results{text-align:left;font-style:italic;color:#999;display:inline-flex}div.pagination ul a{color:#999;text-decoration:none}div.pagination ul a:focus,div.pagination ul a:hover{text-decoration:underline}div.pagination ul>*{display:inline-block;margin-left:.5em}div.pagination ul .next.disabled,div.pagination ul .prev.disabled{display:none}div.pagination ul .current{height:25px;padding:4px 8px;border:1px solid #d5d5d5;text-decoration:none;font-weight:700;color:#000;background-color:#ccc}.hide{display:none}#article{width:70%;margin-bottom:3em;text-align:justify}#article .tags{margin-bottom:1em}#article i{font-style:normal}#article h1{text-align:left}#article h2:after{content:none}#article h2,#article h3,#article h4{text-transform:none}blockquote{border:1px solid #999;background-color:#fff;padding:1em;margin:0}.topPosF{position:fixed;right:20%;bottom:2em;font-size:1.5em}#article_toolbar{margin-bottom:1em}#article_toolbar li{display:inline-block;margin:3px auto}#article_toolbar a{background-color:#000;padding:.3em .5em .2em;color:#fff;text-decoration:none}#article_toolbar a:focus,#article_toolbar a:hover{background-color:#999}#nav-btn-add-tag{cursor:pointer}.shaarli:before{content:"*"}.return{text-decoration:none;margin-top:1em;display:block}.return:before{margin-right:.5em}.notags{font-style:italic;color:#999}.icon-rss{background-color:#000;color:#fff;padding:.2em .5em}.icon-rss:before{position:relative;top:2px}.list-tags li{margin-bottom:.5em}.list-tags .icon-rss:focus,.list-tags .icon-rss:hover{background-color:#fff;color:#000;text-decoration:none}.list-tags a{text-decoration:none}.list-tags a:focus,.list-tags a:hover{text-decoration:underline}pre code{font-family:Courier New,Courier,monospace}#filters{position:fixed;width:20%;height:100%;top:0;right:0;background-color:#fff;padding:30px 30px 15px 15px;border-left:1px solid #333;z-index:12;min-width:300px}#filters form .filter-group{margin:5px}#download-form{position:fixed;width:10%;height:100%;top:0;right:0;background-color:#fff;padding:30px 30px 15px 15px;border-left:1px solid #333;z-index:12;min-width:200px}#download-form li{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}@font-face{font-family:icomoon;src:url(/bundles/wallabagcore/fonts/IcoMoon-Free.ttf);font-weight:400;font-style:normal}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:1em;width:1em;height:1em;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga"}.material-icons .md-18{font-size:18px}.material-icons .md-24{font-size:24px}.material-icons .md-36{font-size:36px}.material-icons .md-48{font-size:48px}.material-icons .vertical-align-middle{vertical-align:middle!important}.icon-image span,.icon span{position:absolute;top:-9999px}[class*=" icon-"]:before,[class^=icon-]:before{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;letter-spacing:0;-webkit-font-feature-settings:"liga";-moz-font-feature-settings:"liga=1";-moz-font-feature-settings:"liga";-ms-font-feature-settings:"liga" 1;-o-font-feature-settings:"liga";font-feature-settings:"liga";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-flattr:before{content:"\EAD4"}.icon-mail:before{content:"\EA86"}.icon-up-open:before{content:"\E80B"}.icon-star:before{content:"\E9D9"}.icon-check:before{content:"\EA10"}.icon-link:before{content:"\E9CB"}.icon-reply:before{content:"\E806"}.icon-menu:before{content:"\E9BD"}.icon-clock:before{content:"\E803"}.icon-twitter:before{content:"\EA96"}.icon-down-open:before{content:"\E809"}.icon-trash:before{content:"\E9AC"}.icon-delete:before{content:"\EA0D"}.icon-power:before{content:"\EA14"}.icon-arrow-up-thick:before{content:"\EA3A"}.icon-rss:before{content:"\E808"}.icon-print:before{content:"\E954"}.icon-reload:before{content:"\EA2E"}.icon-price-tags:before{content:"\E936"}.icon-eye:before{content:"\E9CE"}.icon-no-eye:before{content:"\E9D1"}.icon-calendar:before{content:"\E953"}.icon-time:before{content:"\E952"}.icon-image{background:no-repeat 50%/80%;padding-right:1em!important;padding-left:1em!important}.icon-image--carrot{background-image:url(/bundles/wallabagcore/img/carrot-icon--white.png)}.icon-image--diaspora{background-image:url(/bundles/wallabagcore/img/Diaspora-asterisk.svg)}.icon-image--unmark{background-image:url(/bundles/wallabagcore/img/unmark-icon--black.png)}.icon-image--shaarli{background-image:url(/bundles/wallabagcore/img/shaarli.png)}.icon-check.archive:before,.icon-star.fav:before{color:#fff}.login{background-color:#333}.login #main{padding:0;margin:0}.login form{background-color:#fff;padding:1.5em;box-shadow:0 1px 8px rgba(0,0,0,.9);width:20em;top:8em;margin-left:-10em}.login .logo,.login form{position:absolute;left:50%}.login .logo{top:2em;margin-left:-55px}.popup-form{background:rgba(0,0,0,.5);left:10em;height:100%;width:100%;margin:0;margin-top:-30%!important;display:none;border-left:1px solid #eee}.popup-form,.popup-form form{position:absolute;top:0;z-index:20;padding:2em}.popup-form form{background-color:#fff;left:0;border:10px solid #000;width:400px;height:200px}#bagit-form-form .addurl{margin-left:0}.close-button,.closeMessage{background-color:#000;color:#fff;font-size:1.2em;line-height:1.6;width:1.6em;height:1.6em;text-align:center;text-decoration:none}.close-button:focus,.close-button:hover,.closeMessage:focus,.closeMessage:hover{background-color:#999;color:#000}.close-button--popup{display:inline-block;position:absolute;top:0;right:0;font-size:1.4em}.active-current{background-color:#999}.active-current:after{content:"";width:0;height:0;position:absolute;border:10px solid transparent;border-right-color:#eee;right:0;top:50%;margin-top:-10px}.opacity03{opacity:.3}.add-to-wallabag-link-after{background-color:#000;color:#fff;padding:0 3px 2px}a.add-to-wallabag-link-after{visibility:hidden;position:absolute;opacity:0;transition-duration:2s;transition-timing-function:ease-out}#article article a:hover+a.add-to-wallabag-link-after,a.add-to-wallabag-link-after:hover{opacity:1;visibility:visible;transition-duration:.3s;transition-timing-function:ease-in}a.add-to-wallabag-link-after:after{content:"w"}#add-link-result{font-weight:700;font-size:.9em}.btn-clickable{cursor:pointer}.messages{text-align:left;width:60%;margin:auto 17%}.messages>*{display:inline-block}.messages .install{text-align:left}.messages .install.error{border:1px solid #c42608;color:#c00!important;background:#fff0ef}.messages .install.notice{border:1px solid #ebcd41;color:#000;background:#fffcd3}.messages .install.success{border:1px solid #6dc70c;background:#e0fbcc!important}.warning{font-weight:700;display:block;width:100%}.more-info{font-size:.85em;line-height:1.5;color:#aaa}.more-info a{color:#aaa}@media screen and (max-width:1050px){.entry{width:49%}.entry:nth-child(3n+1){margin-left:1.5%}.entry:nth-child(odd){margin-left:0}}@media screen and (max-width:900px){#article{width:80%}.topPosF{right:2.5em}}@media screen and (max-width:700px){.entry{width:100%;margin-left:0}#display-mode{display:none}}@media screen and (max-height:770px){.menu.developer,.menu.internal,.menu.users{display:none}}@media screen and (max-width:500px){.entry{width:100%;margin-left:0}body>header{background-color:#333;position:fixed;top:0;width:100%;height:3em;z-index:11}#links li:last-child{position:static;width:auto}#links li:last-child a:before{content:none}.logo{width:1.25em;height:1.25em;left:0;top:0}.login>header,.login form{position:static}.login form{width:100%;margin-left:0}.login .logo{height:auto;top:.5em;width:75px;margin-left:-37.5px}.desktopHide{display:block;position:fixed;z-index:20;top:0;right:0;border:0;width:2.5em;height:2.5em;cursor:pointer;background-color:#999;font-size:1.2em}.desktopHide:focus,.desktopHide:hover{background-color:#fff}#links{display:none;width:100%;height:auto;padding-top:3em}#links.menu--open{display:block}footer{margin-right:3em}#main,footer{position:static}#main{margin-left:1.5em;padding-right:1.5em;margin-top:3em}#article_toolbar .topPosF,.card-entry-labels{display:none}#article{width:100%}#article h1{font-size:1.5em}#article_toolbar a{padding:.3em .4em .2em}#display-mode{display:none}#bagit-form,#search-form,.popup-form{left:0;width:100%;border-left:none}#bagit-form form,#search-form form,.popup-form form{width:100%}}@media print{body{font-family:Serif;background-color:#fff}@page{margin:1cm}img{max-width:100%!important}#article-informations,#article .mbm a,#article_toolbar,#links,#sort,.entrie+.results,.messages,.top_link,body>.logo,body>footer,div.tools,header div{display:none!important}article{border:none!important}.vieworiginal a:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.pagination span.current{border-style:dashed}#main{margin:0;padding:0}#article,#main{width:100%}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{font-size:1em;line-height:1.5;margin:0}dl:first-child,h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,ol:first-child,p:first-child,ul:first-child{margin-top:0}code,kbd,pre,samp{font-family:monospace,serif}pre{white-space:pre-wrap}.upper{text-transform:uppercase}.bold{font-weight:700}.inner{margin:0 auto;max-width:61.25em}figure,img,table{max-width:100%;height:auto}iframe{max-width:100%}.fl{float:left}.fr{float:right}table{border-collapse:collapse}figure{margin:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}input[type=search]{-webkit-appearance:textfield}.dib{display:inline-block;vertical-align:middle}.dnone{display:none}.dtable{display:table}.dtable>*{display:table-row}.dtable>*>*{display:table-cell}.element-invisible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.small{font-size:.8em}.big{font-size:1.2em}.w100{width:100%}.w90{width:90%}.w80{width:80%}.w70{width:70%}.w60{width:60%}.w50{width:50%}.w40{width:40%}.w30{width:30%}.w20{width:20%}.w10{width:10%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0}} +/*# sourceMappingURL=baggy.css.map*/ \ No newline at end of file diff --git a/web/bundles/wallabagcore/baggy.css.map b/web/bundles/wallabagcore/baggy.css.map new file mode 100644 index 00000000..9f5934af --- /dev/null +++ b/web/bundles/wallabagcore/baggy.css.map @@ -0,0 +1 @@ +{"version":3,"sources":[],"names":[],"mappings":"","file":"baggy.css","sourceRoot":""} \ No newline at end of file diff --git a/web/bundles/wallabagcore/baggy.js b/web/bundles/wallabagcore/baggy.js new file mode 100644 index 00000000..c17062ea --- /dev/null +++ b/web/bundles/wallabagcore/baggy.js @@ -0,0 +1 @@ +!function(e){function __webpack_require__(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,__webpack_require__),r.l=!0,r.exports}var t={};__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,t,n){__webpack_require__.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},__webpack_require__.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(t,"a",t),t},__webpack_require__.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},__webpack_require__.p="/bundles/wallabagcore/",__webpack_require__(__webpack_require__.s=50)}([function(e,t,n){var r,o;!function(t,n){"object"==typeof e&&"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(t)}("undefined"!=typeof window?window:this,function(n,i){function isArrayLike(e){var t=!!e&&"length"in e&&e.length,n=m.type(e);return"function"!==n&&!m.isWindow(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}function winnow(e,t,n){if(m.isFunction(t))return m.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return m.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(C.test(t))return m.filter(t,e,n);t=m.filter(t,e)}return m.grep(e,function(e){return f.call(t,e)>-1!==n})}function sibling(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function createOptions(e){var t={};return m.each(e.match(A)||[],function(e,n){t[n]=!0}),t}function completed(){s.removeEventListener("DOMContentLoaded",completed),n.removeEventListener("load",completed),m.ready()}function Data(){this.expando=m.expando+Data.uid++}function dataAttr(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(R,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:M.test(n)?m.parseJSON(n):n)}catch(e){}L.set(e,t,n)}else n=void 0;return n}function adjustCSS(e,t,n,r){var o,i=1,a=20,s=r?function(){return r.cur()}:function(){return m.css(e,t,"")},u=s(),l=n&&n[3]||(m.cssNumber[t]?"":"px"),c=(m.cssNumber[t]||"px"!==l&&+u)&&F.exec(m.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do{i=i||".5",c/=i,m.style(e,t,c+l)}while(i!==(i=s()/u)&&1!==i&&--a)}return n&&(c=+c||+u||0,o=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=o)),o}function getAll(e,t){var n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&m.nodeName(e,t)?m.merge([e],n):n}function setGlobalEval(e,t){for(var n=0,r=e.length;n-1)o&&o.push(i);else if(l=m.contains(i.ownerDocument,i),a=getAll(f.appendChild(i),"script"),l&&setGlobalEval(a),n)for(c=0;i=a[c++];)I.test(i.type||"")&&n.push(i);return f}function returnTrue(){return!0}function returnFalse(){return!1}function safeActiveElement(){try{return s.activeElement}catch(e){}}function on(e,t,n,r,o,i){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)on(e,s,n,r,t[s],i);return e}if(null==r&&null==o?(o=n,r=n=void 0):null==o&&("string"==typeof n?(o=r,r=void 0):(o=r,r=n,n=void 0)),!1===o)o=returnFalse;else if(!o)return e;return 1===i&&(a=o,o=function(e){return m().off(e),a.apply(this,arguments)},o.guid=a.guid||(a.guid=m.guid++)),e.each(function(){m.event.add(this,t,o,r,n)})}function manipulationTarget(e,t){return m.nodeName(e,"table")&&m.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function disableScript(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function restoreScript(e){var t=G.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function cloneCopyEvent(e,t){var n,r,o,i,a,s,u,l;if(1===t.nodeType){if(O.hasData(e)&&(i=O.access(e),a=O.set(t,i),l=i.events)){delete a.handle,a.events={};for(o in l)for(n=0,r=l[o].length;n1&&"string"==typeof p&&!g.checkClone&&K.test(p))return e.each(function(o){var i=e.eq(o);v&&(t[0]=p.call(this,o,i.html())),domManip(i,t,n,r)});if(d&&(o=buildFragment(t,e[0].ownerDocument,!1,e,r),i=o.firstChild,1===o.childNodes.length&&(o=i),i||r)){for(a=m.map(getAll(o,"script"),disableScript),s=a.length;f")).appendTo(t.documentElement),t=J[0].contentDocument,t.write(),t.close(),n=actualDisplay(e,t),J.detach()),Z[e]=n),n}function curCSS(e,t,n){var r,o,i,a,s=e.style;return n=n||ne(e),a=n?n.getPropertyValue(t)||n[t]:void 0,""!==a&&void 0!==a||m.contains(e.ownerDocument,e)||(a=m.style(e,t)),n&&!g.pixelMarginRight()&&te.test(a)&&ee.test(t)&&(r=s.width,o=s.minWidth,i=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=o,s.maxWidth=i),void 0!==a?a+"":a}function addGetHookIf(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function vendorPropName(e){if(e in le)return e;for(var t=e[0].toUpperCase()+e.slice(1),n=ue.length;n--;)if((e=ue[n]+t)in le)return e}function setPositiveNumber(e,t,n){var r=F.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function augmentWidthOrHeight(e,t,n,r,o){for(var i=n===(r?"border":"content")?4:"width"===t?1:0,a=0;i<4;i+=2)"margin"===n&&(a+=m.css(e,n+H[i],!0,o)),r?("content"===n&&(a-=m.css(e,"padding"+H[i],!0,o)),"margin"!==n&&(a-=m.css(e,"border"+H[i]+"Width",!0,o))):(a+=m.css(e,"padding"+H[i],!0,o),"padding"!==n&&(a+=m.css(e,"border"+H[i]+"Width",!0,o)));return a}function getWidthOrHeight(e,t,n){var r=!0,o="width"===t?e.offsetWidth:e.offsetHeight,i=ne(e),a="border-box"===m.css(e,"boxSizing",!1,i);if(o<=0||null==o){if(o=curCSS(e,t,i),(o<0||null==o)&&(o=e.style[t]),te.test(o))return o;r=a&&(g.boxSizingReliable()||o===e.style[t]),o=parseFloat(o)||0}return o+augmentWidthOrHeight(e,t,n||(a?"border":"content"),r,i)+"px"}function showHide(e,t){for(var n,r,o,i=[],a=0,s=e.length;a=0&&n=0},isPlainObject:function(e){var t;if("object"!==m.type(e)||e.nodeType||m.isWindow(e))return!1;if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype||{},"isPrototypeOf"))return!1;for(t in e);return void 0===t||p.call(e,t)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?d[h.call(e)]||"object":typeof e},globalEval:function(e){var t,n=eval;(e=m.trim(e))&&(1===e.indexOf("use strict")?(t=s.createElement("script"),t.text=e,s.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,v)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var n,r=0;if(isArrayLike(e))for(n=e.length;rr.cacheLength&&delete cache[e.shift()],cache[t+" "]=n}var e=[];return cache}function markFunction(e){return e[b]=!0,e}function assert(e){var t=h.createElement("div");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function addHandle(e,t){for(var n=e.split("|"),o=n.length;o--;)r.attrHandle[n[o]]=t}function siblingCheck(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||A)-(~e.sourceIndex||A);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function createPositionalPseudo(e){return markFunction(function(t){return t=+t,markFunction(function(n,r){for(var o,i=e([],n.length,t),a=i.length;a--;)n[o=i[a]]&&(n[o]=!(r[o]=n[o]))})})}function testContext(e){return e&&void 0!==e.getElementsByTagName&&e}function setFilters(){}function toSelector(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var o=e.length;o--;)if(!e[o](t,n,r))return!1;return!0}:e[0]}function multipleContexts(e,t,n){for(var r=0,o=t.length;r-1&&(i[l]=!(a[l]=f))}}else v=condense(v===a?v.splice(p,v.length):v),o?o(null,a,v,u):L.apply(a,v)})}function matcherFromTokens(e){for(var t,n,o,i=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=addCombinator(function(e){return e===t},s,!0),f=addCombinator(function(e){return R(t,e)>-1},s,!0),d=[function(e,n,r){var o=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,o}];u1&&elementMatcher(d),u>1&&toSelector(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(I,"$1"),n,u0,o=e.length>0,i=function(i,a,s,u,c){var f,p,m,v=0,y="0",w=i&&[],b=[],x=l,C=i||o&&r.find.TAG("*",c),_=T+=null==x?1:Math.random()||.1,E=C.length;for(c&&(l=a===h||a||c);y!==E&&null!=(f=C[y]);y++){if(o&&f){for(p=0,a||f.ownerDocument===h||(d(f),s=!g);m=e[p++];)if(m(f,a||h,s)){u.push(f);break}c&&(T=_)}n&&((f=!m&&f)&&v--,i&&w.push(f))}if(v+=y,n&&y!==v){for(p=0;m=t[p++];)m(w,b,a,s);if(i){if(v>0)for(;y--;)w[y]||b[y]||(b[y]=P.call(u));b=condense(b)}L.apply(u,b),c&&!i&&b.length>0&&v+t.length>1&&Sizzle.uniqueSort(u)}return c&&(T=_,l=x),w};return n?markFunction(i):i}var t,n,r,o,i,a,s,u,l,c,f,d,h,p,g,m,v,y,w,b="sizzle"+1*new Date,x=e.document,T=0,C=0,_=createCache(),E=createCache(),k=createCache(),S=function(e,t){return e===t&&(f=!0),0},A=1<<31,N={}.hasOwnProperty,D=[],P=D.pop,O=D.push,L=D.push,M=D.slice,R=function(e,t){for(var n=0,r=e.length;n+~]|"+F+")"+F+"*"),$=new RegExp("="+F+"*([^\\]'\"]*?)"+F+"*\\]","g"),X=new RegExp(B),V=new RegExp("^"+H+"$"),Y={ID:new RegExp("^#("+H+")"),CLASS:new RegExp("^\\.("+H+")"),TAG:new RegExp("^("+H+"|[*])"),ATTR:new RegExp("^"+q),PSEUDO:new RegExp("^"+B),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+F+"*(even|odd|(([+-]|)(\\d*)n|)"+F+"*(?:([+-]|)"+F+"*(\\d+)|))"+F+"*\\)|)","i"),bool:new RegExp("^(?:"+j+")$","i"),needsContext:new RegExp("^"+F+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+F+"*((?:-\\d)?\\d*)"+F+"*\\)|)(?=[^-]|$)","i")},K=/^(?:input|select|textarea|button)$/i,G=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/[+~]/,ee=/'|\\/g,te=new RegExp("\\\\([\\da-f]{1,6}"+F+"?|("+F+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=function(){d()};try{L.apply(D=M.call(x.childNodes),x.childNodes),D[x.childNodes.length].nodeType}catch(e){L={apply:D.length?function(e,t){O.apply(e,M.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}n=Sizzle.support={},i=Sizzle.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},d=Sizzle.setDocument=function(e){var t,o,a=e?e.ownerDocument||e:x;return a!==h&&9===a.nodeType&&a.documentElement?(h=a,p=h.documentElement,g=!i(h),(o=h.defaultView)&&o.top!==o&&(o.addEventListener?o.addEventListener("unload",re,!1):o.attachEvent&&o.attachEvent("onunload",re)),n.attributes=assert(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=assert(function(e){return e.appendChild(h.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(h.getElementsByClassName),n.getById=assert(function(e){return p.appendChild(e).id=b,!h.getElementsByName||!h.getElementsByName(b).length}),n.getById?(r.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}},r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}}):(delete r.find.ID,r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=n.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],o=0,i=t.getElementsByTagName(e);if("*"===e){for(;n=i[o++];)1===n.nodeType&&r.push(n);return r}return i},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],m=[],(n.qsa=Q.test(h.querySelectorAll))&&(assert(function(e){p.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+F+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||m.push("\\["+F+"*(?:value|"+j+")"),e.querySelectorAll("[id~="+b+"-]").length||m.push("~="),e.querySelectorAll(":checked").length||m.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||m.push(".#.+[+~]")}),assert(function(e){var t=h.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&m.push("name"+F+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||m.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),m.push(",.*:")})),(n.matchesSelector=Q.test(y=p.matches||p.webkitMatchesSelector||p.mozMatchesSelector||p.oMatchesSelector||p.msMatchesSelector))&&assert(function(e){n.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),v.push("!=",B)}),m=m.length&&new RegExp(m.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(p.compareDocumentPosition),w=t||Q.test(p.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},S=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&r||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===h||e.ownerDocument===x&&w(x,e)?-1:t===h||t.ownerDocument===x&&w(x,t)?1:c?R(c,e)-R(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,o=e.parentNode,i=t.parentNode,a=[e],s=[t];if(!o||!i)return e===h?-1:t===h?1:o?-1:i?1:c?R(c,e)-R(c,t):0;if(o===i)return siblingCheck(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?siblingCheck(a[r],s[r]):a[r]===x?-1:s[r]===x?1:0},h):h},Sizzle.matches=function(e,t){return Sizzle(e,null,null,t)},Sizzle.matchesSelector=function(e,t){if((e.ownerDocument||e)!==h&&d(e),t=t.replace($,"='$1']"),n.matchesSelector&&g&&!k[t+" "]&&(!v||!v.test(t))&&(!m||!m.test(t)))try{var r=y.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return Sizzle(t,h,null,[e]).length>0},Sizzle.contains=function(e,t){return(e.ownerDocument||e)!==h&&d(e),w(e,t)},Sizzle.attr=function(e,t){(e.ownerDocument||e)!==h&&d(e);var o=r.attrHandle[t.toLowerCase()],i=o&&N.call(r.attrHandle,t.toLowerCase())?o(e,t,!g):void 0;return void 0!==i?i:n.attributes||!g?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null},Sizzle.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},Sizzle.uniqueSort=function(e){var t,r=[],o=0,i=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(S),f){for(;t=e[i++];)t===e[i]&&(o=r.push(i));for(;o--;)e.splice(r[o],1)}return c=null,e},o=Sizzle.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=o(t);return n},r=Sizzle.selectors={cacheLength:50,createPseudo:markFunction,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||Sizzle.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&Sizzle.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Y.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=_[e+" "];return t||(t=new RegExp("(^|"+F+")"+e+"("+F+"|$)"))&&_(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var o=Sizzle.attr(r,e);return null==o?"!="===t:!t||(o+="","="===t?o===n:"!="===t?o!==n:"^="===t?n&&0===o.indexOf(n):"*="===t?n&&o.indexOf(n)>-1:"$="===t?n&&o.slice(-n.length)===n:"~="===t?(" "+o.replace(z," ")+" ").indexOf(n)>-1:"|="===t&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,o){var i="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===o?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,h,p,g=i!==a?"nextSibling":"previousSibling",m=t.parentNode,v=s&&t.nodeName.toLowerCase(),y=!u&&!s,w=!1;if(m){if(i){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===v:1===d.nodeType)return!1;p=g="only"===e&&!p&&"nextSibling"}return!0}if(p=[a?m.firstChild:m.lastChild],a&&y){for(d=m,f=d[b]||(d[b]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],h=l[0]===T&&l[1],w=h&&l[2],d=h&&m.childNodes[h];d=++h&&d&&d[g]||(w=h=0)||p.pop();)if(1===d.nodeType&&++w&&d===t){c[e]=[T,h,w];break}}else if(y&&(d=t,f=d[b]||(d[b]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],h=l[0]===T&&l[1],w=h),!1===w)for(;(d=++h&&d&&d[g]||(w=h=0)||p.pop())&&((s?d.nodeName.toLowerCase()!==v:1!==d.nodeType)||!++w||(y&&(f=d[b]||(d[b]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),c[e]=[T,w]),d!==t)););return(w-=o)===r||w%r==0&&w/r>=0}}},PSEUDO:function(e,t){var n,o=r.pseudos[e]||r.setFilters[e.toLowerCase()]||Sizzle.error("unsupported pseudo: "+e);return o[b]?o(t):o.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?markFunction(function(e,n){for(var r,i=o(e,t),a=i.length;a--;)r=R(e,i[a]),e[r]=!(n[r]=i[a])}):function(e){return o(e,0,n)}):o}},pseudos:{not:markFunction(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[b]?markFunction(function(e,t,n,o){for(var i,a=r(e,null,o,[]),s=e.length;s--;)(i=a[s])&&(e[s]=!(t[s]=i))}):function(e,o,i){return t[0]=e,r(t,null,i,n),t[0]=null,!n.pop()}}),has:markFunction(function(e){return function(t){return Sizzle(e,t).length>0}}),contains:markFunction(function(e){return e=e.replace(te,ne),function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:markFunction(function(e){return V.test(e||"")||Sizzle.error("unsupported lang: "+e),e=e.replace(te,ne).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===p},focus:function(e){return e===h.activeElement&&(!h.hasFocus||h.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return!1===e.disabled},disabled:function(e){return!0===e.disabled},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return G.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:createPositionalPseudo(function(){return[0]}),last:createPositionalPseudo(function(e,t){return[t-1]}),eq:createPositionalPseudo(function(e,t,n){return[n<0?n+t:n]}),even:createPositionalPseudo(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:createPositionalPseudo(function(e,t,n){for(var r=n<0?n+t:n;++r2&&"ID"===(c=l[0]).type&&n.getById&&9===t.nodeType&&g&&r.relative[l[1].type]){if(!(t=(r.find.ID(c.matches[0].replace(te,ne),t)||[])[0]))return o;h&&(t=t.parentNode),e=e.slice(l.shift().value.length)}for(u=Y.needsContext.test(e)?0:l.length;u--&&(c=l[u],!r.relative[f=c.type]);)if((d=r.find[f])&&(i=d(c.matches[0].replace(te,ne),Z.test(l[0].type)&&testContext(t.parentNode)||t))){if(l.splice(u,1),!(e=i.length&&toSelector(l)))return L.apply(o,i),o;break}}return(h||s(e,p))(i,t,!g,o,!t||Z.test(e)&&testContext(t.parentNode)||t),o},n.sortStable=b.split("").sort(S).join("")===b,n.detectDuplicates=!!f,d(),n.sortDetached=assert(function(e){return 1&e.compareDocumentPosition(h.createElement("div"))}),assert(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||addHandle("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&assert(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||addHandle("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),assert(function(e){return null==e.getAttribute("disabled")})||addHandle(j,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),Sizzle}(n);m.find=y,m.expr=y.selectors,m.expr[":"]=m.expr.pseudos,m.uniqueSort=m.unique=y.uniqueSort,m.text=y.getText,m.isXMLDoc=y.isXML,m.contains=y.contains;var w=function(e,t,n){for(var r=[],o=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(o&&m(e).is(n))break;r.push(e)}return r},b=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},x=m.expr.match.needsContext,T=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,C=/^.[^:#\[\.,]*$/;m.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?m.find.matchesSelector(r,e)?[r]:[]:m.find.matches(e,m.grep(t,function(e){return 1===e.nodeType}))},m.fn.extend({find:function(e){var t,n=this.length,r=[],o=this;if("string"!=typeof e)return this.pushStack(m(e).filter(function(){for(t=0;t1?m.unique(r):r),r.selector=this.selector?this.selector+" "+e:e,r},filter:function(e){return this.pushStack(winnow(this,e||[],!1))},not:function(e){return this.pushStack(winnow(this,e||[],!0))},is:function(e){return!!winnow(this,"string"==typeof e&&x.test(e)?m(e):e||[],!1).length}});var _,E=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/;(m.fn.init=function(e,t,n){var r,o;if(!e)return this;if(n=n||_,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:E.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof m?t[0]:t,m.merge(this,m.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:s,!0)),T.test(r[1])&&m.isPlainObject(t))for(r in t)m.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return o=s.getElementById(r[2]),o&&o.parentNode&&(this.length=1,this[0]=o),this.context=s,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):m.isFunction(e)?void 0!==n.ready?n.ready(e):e(m):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),m.makeArray(e,this))}).prototype=m.fn,_=m(s);var k=/^(?:parents|prev(?:Until|All))/,S={children:!0,contents:!0,next:!0,prev:!0};m.fn.extend({has:function(e){var t=m(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&m.find.matchesSelector(n,e))){i.push(n);break}return this.pushStack(i.length>1?m.uniqueSort(i):i)},index:function(e){return e?"string"==typeof e?f.call(m(e),this[0]):f.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(m.uniqueSort(m.merge(this.get(),m(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),m.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return w(e,"parentNode")},parentsUntil:function(e,t,n){return w(e,"parentNode",n)},next:function(e){return sibling(e,"nextSibling")},prev:function(e){return sibling(e,"previousSibling")},nextAll:function(e){return w(e,"nextSibling")},prevAll:function(e){return w(e,"previousSibling")},nextUntil:function(e,t,n){return w(e,"nextSibling",n)},prevUntil:function(e,t,n){return w(e,"previousSibling",n)},siblings:function(e){return b((e.parentNode||{}).firstChild,e)},children:function(e){return b(e.firstChild)},contents:function(e){return e.contentDocument||m.merge([],e.childNodes)}},function(e,t){m.fn[e]=function(n,r){var o=m.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(o=m.filter(r,o)),this.length>1&&(S[e]||m.uniqueSort(o),k.test(e)&&o.reverse()),this.pushStack(o)}});var A=/\S+/g;m.Callbacks=function(e){e="string"==typeof e?createOptions(e):m.extend({},e);var t,n,r,o,i=[],a=[],s=-1,u=function(){for(o=e.once,r=t=!0;a.length;s=-1)for(n=a.shift();++s-1;)i.splice(n,1),n<=s&&s--}),this},has:function(e){return e?m.inArray(e,i)>-1:i.length>0},empty:function(){return i&&(i=[]),this},disable:function(){return o=a=[],i=n="",this},disabled:function(){return!i},lock:function(){return o=a=[],n||(i=n=""),this},locked:function(){return!!o},fireWith:function(e,n){return o||(n=n||[],n=[e,n.slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l},m.extend({Deferred:function(e){var t=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},then:function(){var e=arguments;return m.Deferred(function(n){m.each(t,function(t,i){var a=m.isFunction(e[t])&&e[t];o[i[1]](function(){var e=a&&a.apply(this,arguments);e&&m.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[i[0]+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?m.extend(e,r):r}},o={};return r.pipe=r.then,m.each(t,function(e,i){var a=i[2],s=i[3];r[i[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),o[i[0]]=function(){return o[i[0]+"With"](this===o?r:this,arguments),this},o[i[0]+"With"]=a.fireWith}),r.promise(o),e&&e.call(o,o),o},when:function(e){var t,n,r,o=0,i=u.call(arguments),a=i.length,s=1!==a||e&&m.isFunction(e.promise)?a:0,l=1===s?e:m.Deferred(),c=function(e,n,r){return function(o){n[e]=this,r[e]=arguments.length>1?u.call(arguments):o,r===t?l.notifyWith(n,r):--s||l.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);o0||(N.resolveWith(s,[m]),m.fn.triggerHandler&&(m(s).triggerHandler("ready"),m(s).off("ready"))))}}),m.ready.promise=function(e){return N||(N=m.Deferred(),"complete"===s.readyState||"loading"!==s.readyState&&!s.documentElement.doScroll?n.setTimeout(m.ready):(s.addEventListener("DOMContentLoaded",completed),n.addEventListener("load",completed))),N.promise(e)},m.ready.promise();var D=function(e,t,n,r,o,i,a){var s=0,u=e.length,l=null==n;if("object"===m.type(n)){o=!0;for(s in n)D(e,t,s,n[s],!0,i,a)}else if(void 0!==r&&(o=!0,m.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(m(e),n)})),t))for(;s-1&&void 0!==n&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}}),m.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=O.get(e,t),n&&(!r||m.isArray(n)?r=O.access(e,t,m.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=m.queue(e,t),r=n.length,o=n.shift(),i=m._queueHooks(e,t),a=function(){m.dequeue(e,t)};"inprogress"===o&&(o=n.shift(),r--),o&&("fx"===t&&n.unshift("inprogress"),delete i.stop,o.call(e,a,i)),!r&&i&&i.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return O.get(e,n)||O.access(e,n,{empty:m.Callbacks("once memory").add(function(){O.remove(e,[t+"queue",n])})})}}),m.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length",""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};W.optgroup=W.option,W.tbody=W.tfoot=W.colgroup=W.caption=W.thead,W.th=W.td;var U=/<|&#?\w+;/;!function(){var e=s.createDocumentFragment(),t=e.appendChild(s.createElement("div")),n=s.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),g.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="",g.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var $=/^key/,X=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,V=/^([^.]*)(?:\.(.+)|)/;m.event={global:{},add:function(e,t,n,r,o){var i,a,s,u,l,c,f,d,h,p,g,v=O.get(e);if(v)for(n.handler&&(i=n,n=i.handler,o=i.selector),n.guid||(n.guid=m.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(t){return void 0!==m&&m.event.triggered!==t.type?m.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(A)||[""],l=t.length;l--;)s=V.exec(t[l])||[],h=g=s[1],p=(s[2]||"").split(".").sort(),h&&(f=m.event.special[h]||{},h=(o?f.delegateType:f.bindType)||h,f=m.event.special[h]||{},c=m.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&m.expr.match.needsContext.test(o),namespace:p.join(".")},i),(d=u[h])||(d=u[h]=[],d.delegateCount=0,f.setup&&!1!==f.setup.call(e,r,p,a)||e.addEventListener&&e.addEventListener(h,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),o?d.splice(d.delegateCount++,0,c):d.push(c),m.event.global[h]=!0)},remove:function(e,t,n,r,o){var i,a,s,u,l,c,f,d,h,p,g,v=O.hasData(e)&&O.get(e);if(v&&(u=v.events)){for(t=(t||"").match(A)||[""],l=t.length;l--;)if(s=V.exec(t[l])||[],h=g=s[1],p=(s[2]||"").split(".").sort(),h){for(f=m.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,d=u[h]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=i=d.length;i--;)c=d[i],!o&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(i,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,p,v.handle)||m.removeEvent(e,h,v.handle),delete u[h])}else for(h in u)m.event.remove(e,h+t[l],n,r,!0);m.isEmptyObject(u)&&O.remove(e,"handle events")}},dispatch:function(e){e=m.event.fix(e);var t,n,r,o,i,a=[],s=u.call(arguments),l=(O.get(this,"events")||{})[e.type]||[],c=m.event.special[e.type]||{};if(s[0]=e,e.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,e)){for(a=m.event.handlers.call(this,e,l),t=0;(o=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=o.elem,n=0;(i=o.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(i.namespace)||(e.handleObj=i,e.data=i.data,void 0!==(r=((m.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,s))&&!1===(e.result=r)&&(e.preventDefault(),e.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,o,i,a=[],s=t.delegateCount,u=e.target;if(s&&u.nodeType&&("click"!==e.type||isNaN(e.button)||e.button<1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&(!0!==u.disabled||"click"!==e.type)){for(r=[],n=0;n-1:m.find(o,this,null,[u]).length),r[o]&&r.push(i);r.length&&a.push({elem:u,handlers:r})}return s\s*$/g;m.extend({htmlPrefilter:function(e){return e.replace(/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,"<$1>")},clone:function(e,t,n){var r,o,i,a,s=e.cloneNode(!0),u=m.contains(e.ownerDocument,e);if(!(g.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||m.isXMLDoc(e)))for(a=getAll(s),i=getAll(e),r=0,o=i.length;r0&&setGlobalEval(a,!u&&getAll(e,"script")),s},cleanData:function(e){for(var t,n,r,o=m.event.special,i=0;void 0!==(n=e[i]);i++)if(P(n)){if(t=n[O.expando]){if(t.events)for(r in t.events)o[r]?m.event.remove(n,r):m.removeEvent(n,r,t.handle);n[O.expando]=void 0}n[L.expando]&&(n[L.expando]=void 0)}}}),m.fn.extend({domManip:domManip,detach:function(e){return remove(this,e,!0)},remove:function(e){return remove(this,e)},text:function(e){return D(this,function(e){return void 0===e?m.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return domManip(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){manipulationTarget(this,e).appendChild(e)}})},prepend:function(){return domManip(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=manipulationTarget(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return domManip(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return domManip(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(m.cleanData(getAll(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return m.clone(this,e,t)})},html:function(e){return D(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Y.test(e)&&!W[(z.exec(e)||["",""])[1].toLowerCase()]){e=m.htmlPrefilter(e);try{for(;n1)},show:function(){return showHide(this,!0)},hide:function(){return showHide(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){q(this)?m(this).show():m(this).hide()})}}),m.Tween=Tween,Tween.prototype={constructor:Tween,init:function(e,t,n,r,o,i){this.elem=e,this.prop=n,this.easing=o||m.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=i||(m.cssNumber[n]?"":"px")},cur:function(){var e=Tween.propHooks[this.prop];return e&&e.get?e.get(this):Tween.propHooks._default.get(this)},run:function(e){var t,n=Tween.propHooks[this.prop];return this.options.duration?this.pos=t=m.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Tween.propHooks._default.set(this),this}},Tween.prototype.init.prototype=Tween.prototype,Tween.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=m.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){m.fx.step[e.prop]?m.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[m.cssProps[e.prop]]&&!m.cssHooks[e.prop]?e.elem[e.prop]=e.now:m.style(e.elem,e.prop,e.now+e.unit)}}},Tween.propHooks.scrollTop=Tween.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},m.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},m.fx=Tween.prototype.init,m.fx.step={};var ce,fe,de=/^(?:toggle|show|hide)$/,he=/queueHooks$/;m.Animation=m.extend(Animation,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return adjustCSS(n.elem,e,F.exec(t),n),n}]},tweener:function(e,t){m.isFunction(e)?(t=e,e=["*"]):e=e.match(A);for(var n,r=0,o=e.length;r1)},removeAttr:function(e){return this.each(function(){m.removeAttr(this,e)})}}),m.extend({attr:function(e,t,n){var r,o,i=e.nodeType;if(3!==i&&8!==i&&2!==i)return void 0===e.getAttribute?m.prop(e,t,n):(1===i&&m.isXMLDoc(e)||(t=t.toLowerCase(),o=m.attrHooks[t]||(m.expr.match.bool.test(t)?pe:void 0)),void 0!==n?null===n?void m.removeAttr(e,t):o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:(e.setAttribute(t,n+""),n):o&&"get"in o&&null!==(r=o.get(e,t))?r:(r=m.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!g.radioValue&&"radio"===t&&m.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,o=0,i=t&&t.match(A);if(i&&1===e.nodeType)for(;n=i[o++];)r=m.propFix[n]||n,m.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)}}),pe={set:function(e,t,n){return!1===t?m.removeAttr(e,n):e.setAttribute(n,n),n}},m.each(m.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ge[t]||m.find.attr;ge[t]=function(e,t,r){var o,i;return r||(i=ge[t],ge[t]=o,o=null!=n(e,t,r)?t.toLowerCase():null,ge[t]=i),o}});var me=/^(?:input|select|textarea|button)$/i,ve=/^(?:a|area)$/i;m.fn.extend({prop:function(e,t){return D(this,m.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[m.propFix[e]||e]})}}),m.extend({prop:function(e,t,n){var r,o,i=e.nodeType;if(3!==i&&8!==i&&2!==i)return 1===i&&m.isXMLDoc(e)||(t=m.propFix[t]||t,o=m.propHooks[t]),void 0!==n?o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:e[t]=n:o&&"get"in o&&null!==(r=o.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=m.find.attr(e,"tabindex");return t?parseInt(t,10):me.test(e.nodeName)||ve.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),g.optSelected||(m.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this});m.fn.extend({addClass:function(e){var t,n,r,o,i,a,s,u=0;if(m.isFunction(e))return this.each(function(t){m(this).addClass(e.call(this,t,getClass(this)))});if("string"==typeof e&&e)for(t=e.match(A)||[];n=this[u++];)if(o=getClass(n),r=1===n.nodeType&&(" "+o+" ").replace(/[\t\r\n\f]/g," ")){for(a=0;i=t[a++];)r.indexOf(" "+i+" ")<0&&(r+=i+" ");s=m.trim(r),o!==s&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,o,i,a,s,u=0;if(m.isFunction(e))return this.each(function(t){m(this).removeClass(e.call(this,t,getClass(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(A)||[];n=this[u++];)if(o=getClass(n),r=1===n.nodeType&&(" "+o+" ").replace(/[\t\r\n\f]/g," ")){for(a=0;i=t[a++];)for(;r.indexOf(" "+i+" ")>-1;)r=r.replace(" "+i+" "," ");s=m.trim(r),o!==s&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):m.isFunction(e)?this.each(function(n){m(this).toggleClass(e.call(this,n,getClass(this),t),t)}):this.each(function(){var t,r,o,i;if("string"===n)for(r=0,o=m(this),i=e.match(A)||[];t=i[r++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||(t=getClass(this),t&&O.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":O.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+getClass(n)+" ").replace(/[\t\r\n\f]/g," ").indexOf(t)>-1)return!0;return!1}});m.fn.extend({val:function(e){var t,n,r,o=this[0];{if(arguments.length)return r=m.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=r?e.call(this,n,m(this).val()):e,null==o?o="":"number"==typeof o?o+="":m.isArray(o)&&(o=m.map(o,function(e){return null==e?"":e+""})),(t=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,o,"value")||(this.value=o))});if(o)return(t=m.valHooks[o.type]||m.valHooks[o.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(o,"value"))?n:(n=o.value,"string"==typeof n?n.replace(/\r/g,""):null==n?"":n)}}}),m.extend({valHooks:{option:{get:function(e){var t=m.find.attr(e,"value");return null!=t?t:m.trim(m.text(e)).replace(/[\x20\t\r\n\f]+/g," ")}},select:{get:function(e){for(var t,n,r=e.options,o=e.selectedIndex,i="select-one"===e.type||o<0,a=i?null:[],s=i?o+1:r.length,u=o<0?s:i?o:0;u-1)&&(n=!0);return n||(e.selectedIndex=-1),i}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(e,t){if(m.isArray(t))return e.checked=m.inArray(m(e).val(),t)>-1}},g.checkOn||(m.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var ye=/^(?:focusinfocus|focusoutblur)$/;m.extend(m.event,{trigger:function(e,t,r,o){var i,a,u,l,c,f,d,h=[r||s],g=p.call(e,"type")?e.type:e,v=p.call(e,"namespace")?e.namespace.split("."):[];if(a=u=r=r||s,3!==r.nodeType&&8!==r.nodeType&&!ye.test(g+m.event.triggered)&&(g.indexOf(".")>-1&&(v=g.split("."),g=v.shift(),v.sort()),c=g.indexOf(":")<0&&"on"+g,e=e[m.expando]?e:new m.Event(g,"object"==typeof e&&e),e.isTrigger=o?2:3,e.namespace=v.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:m.makeArray(t,[e]),d=m.event.special[g]||{},o||!d.trigger||!1!==d.trigger.apply(r,t))){if(!o&&!d.noBubble&&!m.isWindow(r)){for(l=d.delegateType||g,ye.test(l+g)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||s)&&h.push(u.defaultView||u.parentWindow||n)}for(i=0;(a=h[i++])&&!e.isPropagationStopped();)e.type=i>1?l:d.bindType||g,f=(O.get(a,"events")||{})[e.type]&&O.get(a,"handle"),f&&f.apply(a,t),(f=c&&a[c])&&f.apply&&P(a)&&(e.result=f.apply(a,t),!1===e.result&&e.preventDefault());return e.type=g,o||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(h.pop(),t)||!P(r)||c&&m.isFunction(r[g])&&!m.isWindow(r)&&(u=r[c],u&&(r[c]=null),m.event.triggered=g,r[g](),m.event.triggered=void 0,u&&(r[c]=u)),e.result}},simulate:function(e,t,n){var r=m.extend(new m.Event,n,{type:e,isSimulated:!0});m.event.trigger(r,null,t)}}),m.fn.extend({trigger:function(e,t){return this.each(function(){m.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return m.event.trigger(e,t,n,!0)}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){m.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),m.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),g.focusin="onfocusin"in n,g.focusin||m.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){m.event.simulate(t,e.target,m.event.fix(e))};m.event.special[t]={setup:function(){var r=this.ownerDocument||this,o=O.access(r,t);o||r.addEventListener(e,n,!0),O.access(r,t,(o||0)+1)},teardown:function(){var r=this.ownerDocument||this,o=O.access(r,t)-1;o?O.access(r,t,o):(r.removeEventListener(e,n,!0),O.remove(r,t))}}});var we=n.location,be=m.now(),xe=/\?/;m.parseJSON=function(e){return JSON.parse(e+"")},m.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+e),t};var Te=/([?&])_=[^&]*/,Ce=/^(.*?):[ \t]*([^\r\n]*)$/gm,_e=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ee=/^(?:GET|HEAD)$/,ke={},Se={},Ae="*/".concat("*"),Ne=s.createElement("a");Ne.href=we.href,m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:we.href,type:"GET",isLocal:_e.test(we.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ae,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?ajaxExtend(ajaxExtend(e,m.ajaxSettings),t):ajaxExtend(m.ajaxSettings,e)},ajaxPrefilter:addToPrefiltersOrTransports(ke),ajaxTransport:addToPrefiltersOrTransports(Se),ajax:function(e,t){function done(e,t,a,s){var l,f,w,b,T,_=t;2!==x&&(x=2,u&&n.clearTimeout(u),r=void 0,i=s||"",C.readyState=e>0?4:0,l=e>=200&&e<300||304===e,a&&(b=ajaxHandleResponses(d,C,a)),b=ajaxConvert(d,b,C,l),l?(d.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(m.lastModified[o]=T),(T=C.getResponseHeader("etag"))&&(m.etag[o]=T)),204===e||"HEAD"===d.type?_="nocontent":304===e?_="notmodified":(_=b.state,f=b.data,w=b.error,l=!w)):(w=_,!e&&_||(_="error",e<0&&(e=0))),C.status=e,C.statusText=(t||_)+"",l?g.resolveWith(h,[f,_,C]):g.rejectWith(h,[C,_,w]),C.statusCode(y),y=void 0,c&&p.trigger(l?"ajaxSuccess":"ajaxError",[C,d,l?f:w]),v.fireWith(h,[C,_]),c&&(p.trigger("ajaxComplete",[C,d]),--m.active||m.event.trigger("ajaxStop")))}"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,o,i,a,u,l,c,f,d=m.ajaxSetup({},t),h=d.context||d,p=d.context&&(h.nodeType||h.jquery)?m(h):m.event,g=m.Deferred(),v=m.Callbacks("once memory"),y=d.statusCode||{},w={},b={},x=0,T="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!a)for(a={};t=Ce.exec(i);)a[t[1].toLowerCase()]=t[2];t=a[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?i:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,w[e]=t),this},overrideMimeType:function(e){return x||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(x<2)for(t in e)y[t]=[y[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||T;return r&&r.abort(t),done(0,t),this}};if(g.promise(C).complete=v.add,C.success=C.done,C.error=C.fail,d.url=((e||d.url||we.href)+"").replace(/#.*$/,"").replace(/^\/\//,we.protocol+"//"),d.type=t.method||t.type||d.method||d.type,d.dataTypes=m.trim(d.dataType||"*").toLowerCase().match(A)||[""],null==d.crossDomain){l=s.createElement("a");try{l.href=d.url,l.href=l.href,d.crossDomain=Ne.protocol+"//"+Ne.host!=l.protocol+"//"+l.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=m.param(d.data,d.traditional)),inspectPrefiltersOrTransports(ke,d,t,C),2===x)return C;c=m.event&&d.global,c&&0==m.active++&&m.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Ee.test(d.type),o=d.url,d.hasContent||(d.data&&(o=d.url+=(xe.test(o)?"&":"?")+d.data,delete d.data),!1===d.cache&&(d.url=Te.test(o)?o.replace(Te,"$1_="+be++):o+(xe.test(o)?"&":"?")+"_="+be++)),d.ifModified&&(m.lastModified[o]&&C.setRequestHeader("If-Modified-Since",m.lastModified[o]),m.etag[o]&&C.setRequestHeader("If-None-Match",m.etag[o])),(d.data&&d.hasContent&&!1!==d.contentType||t.contentType)&&C.setRequestHeader("Content-Type",d.contentType),C.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Ae+"; q=0.01":""):d.accepts["*"]);for(f in d.headers)C.setRequestHeader(f,d.headers[f]);if(d.beforeSend&&(!1===d.beforeSend.call(h,C,d)||2===x))return C.abort();T="abort";for(f in{success:1,error:1,complete:1})C[f](d[f]);if(r=inspectPrefiltersOrTransports(Se,d,t,C)){if(C.readyState=1,c&&p.trigger("ajaxSend",[C,d]),2===x)return C;d.async&&d.timeout>0&&(u=n.setTimeout(function(){C.abort("timeout")},d.timeout));try{x=1,r.send(w,done)}catch(e){if(!(x<2))throw e;done(-1,e)}}else done(-1,"No Transport");return C},getJSON:function(e,t,n){return m.get(e,t,n,"json")},getScript:function(e,t){return m.get(e,void 0,t,"script")}}),m.each(["get","post"],function(e,t){m[t]=function(e,n,r,o){return m.isFunction(n)&&(o=o||r,r=n,n=void 0),m.ajax(m.extend({url:e,type:t,dataType:o,data:n,success:r},m.isPlainObject(e)&&e))}}),m._evalUrl=function(e){return m.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,throws:!0})},m.fn.extend({wrapAll:function(e){var t;return m.isFunction(e)?this.each(function(t){m(this).wrapAll(e.call(this,t))}):(this[0]&&(t=m(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return m.isFunction(e)?this.each(function(t){m(this).wrapInner(e.call(this,t))}):this.each(function(){var t=m(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=m.isFunction(e);return this.each(function(n){m(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(e){return!m.expr.filters.visible(e)},m.expr.filters.visible=function(e){return e.offsetWidth>0||e.offsetHeight>0||e.getClientRects().length>0};var De=/\[\]$/,Pe=/^(?:submit|button|image|reset|file)$/i,Oe=/^(?:input|select|textarea|keygen)/i;m.param=function(e,t){var n,r=[],o=function(e,t){t=m.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(void 0===t&&(t=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(e)||e.jquery&&!m.isPlainObject(e))m.each(e,function(){o(this.name,this.value)});else for(n in e)buildParams(n,e[n],t,o);return r.join("&").replace(/%20/g,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=m.prop(this,"elements");return e?m.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!m(this).is(":disabled")&&Oe.test(this.nodeName)&&!Pe.test(e)&&(this.checked||!B.test(e))}).map(function(e,t){var n=m(this).val();return null==n?null:m.isArray(n)?m.map(n,function(e){return{name:t.name,value:e.replace(/\r?\n/g,"\r\n")}}):{name:t.name,value:n.replace(/\r?\n/g,"\r\n")}}).get()}}),m.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Le={0:200,1223:204},Me=m.ajaxSettings.xhr();g.cors=!!Me&&"withCredentials"in Me,g.ajax=Me=!!Me,m.ajaxTransport(function(e){var t,r;if(g.cors||Me&&!e.crossDomain)return{send:function(o,i){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest");for(a in o)s.setRequestHeader(a,o[a]);t=function(e){return function(){t&&(t=r=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?i(0,"error"):i(s.status,s.statusText):i(Le[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=t(),r=s.onerror=t("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{s.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return m.globalEval(e),e}}}),m.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),m.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,o){t=m("