From: Jérémy Benoist Date: Tue, 9 May 2017 15:10:03 +0000 (+0200) Subject: Merge pull request #2751 from bdunogier/2.2-guzzle_subscribers_improvement X-Git-Tag: 2.3.0~31^2~102 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=0eb8220204953b874ebd2dbd0362973f3f45074c;hp=d047530dc07ceb5a109cd0caa95055d8b071dbd4;p=github%2Fwallabag%2Fwallabag.git Merge pull request #2751 from bdunogier/2.2-guzzle_subscribers_improvement Improved Guzzle subscribers extensibility --- 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 77872f49..d7c28388 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,6 @@ cache: - vendor - $HOME/.composer/cache - node_modules - - $HOME/.cache/bower - $HOME/.npm php: @@ -59,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; @@ -74,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/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 51b9c4d0..465200eb 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -47,7 +47,7 @@ make release master /tmp wllbg-release prod - [Create the new release on GitHub](https://github.com/wallabag/wallabag/releases/new). You have to upload on this page the package. - Delete the `release-$LAST_WALLABAG_RELEASE` branch and close the pull request (**DO NOT MERGE IT**). -- Update the URL shortener (used on `wllbg.org` to generate links like `http://wllbg.org/latest-v2-package` or `http://wllbg.org/latest-v2`) +- Update the URL shortener (used on `wllbg.org` to generate links like `https://wllbg.org/latest-v2-package` or `http://wllbg.org/latest-v2`) - Update [the downloads page](https://github.com/wallabag/wallabag.org/blob/master/content/pages/download.md) on the website (MD5 sum, release date) - Update Dockerfile https://github.com/wallabag/docker (and create a new tag) - Update wallabag.org website (downloads, releases and new blog post) 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/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/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/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 45428e23..00000000 --- a/app/Resources/static/themes/material/css/main.css +++ /dev/null @@ -1,1024 +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"); -} - -/* Scuttle */ -.icon-image--scuttle { - background-image: url("../../_global/img/icons/scuttle.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 41499478..042b423c 100644 --- a/app/Resources/static/themes/material/js/shortcuts/main.js +++ b/app/Resources/static/themes/material/js/shortcuts/main.js @@ -28,6 +28,11 @@ $(document).ready(() => { 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 4f4fb900..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: @@ -55,6 +59,7 @@ wallabag_core: 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/docs/de/user/upgrade.rst b/docs/de/user/upgrade.rst index af3b96fb..fa2aac45 100644 --- a/docs/de/user/upgrade.rst +++ b/docs/de/user/upgrade.rst @@ -61,7 +61,7 @@ Lade das letzte Release von wallabag herunter: .. code-block:: bash - wget http://wllbg.org/latest-v2-package && tar xvf latest-v2-package + wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package Du findest den `aktuellen MD5-Hash auf unserer Webseite `_. 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/upgrade.rst b/docs/en/user/upgrade.rst index 359a355f..3157684c 100644 --- a/docs/en/user/upgrade.rst +++ b/docs/en/user/upgrade.rst @@ -65,7 +65,7 @@ Download the last release of wallabag: .. code-block:: bash - wget http://wllbg.org/latest-v2-package && tar xvf latest-v2-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 `_. 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/upgrade.rst b/docs/fr/user/upgrade.rst index 5bb42deb..af198006 100644 --- a/docs/fr/user/upgrade.rst +++ b/docs/fr/user/upgrade.rst @@ -61,7 +61,7 @@ Téléchargez la dernière version de wallabag : .. code-block:: bash - wget http://wllbg.org/latest-v2-package && tar xvf latest-v2-package + wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package Vous trouverez `le hash md5 du dernier package sur notre site `_. diff --git a/docs/it/user/installation.rst b/docs/it/user/installation.rst index c067228d..174507b8 100644 --- a/docs/it/user/installation.rst +++ b/docs/it/user/installation.rst @@ -86,7 +86,7 @@ Eseguite questo comando per scaricare ed estrarre il pacchetto piú aggiornato: .. code-block:: bash - wget http://wllbg.org/latest-v2-package && tar xvf latest-v2-package + wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package Troverete il `hash md5 del pacchetto piú aggiornato sul nostro sito `_. diff --git a/docs/it/user/upgrade.rst b/docs/it/user/upgrade.rst index 52cd98c7..ede1a1a3 100644 --- a/docs/it/user/upgrade.rst +++ b/docs/it/user/upgrade.rst @@ -78,7 +78,7 @@ Scaricate l'ultima versione di wallabag: . code-block:: bash - wget http://wllbg.org/latest-v2-package && tar xvf latest-v2-package + wget https://wllbg.org/latest-v2-package && tar xvf latest-v2-package Troverete il `hash md5 dell'ultima versione del pacchetto sul nostro sito `_. 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/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index 7590efbb..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; @@ -44,9 +45,7 @@ class EntryRestController extends WallabagRestController $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? @@ -62,9 +61,7 @@ class EntryRestController extends WallabagRestController $exists = $res instanceof Entry ? $res->getId() : false; - $json = $this->get('serializer')->serialize(['exists' => $exists], 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse(['exists' => $exists]); } /** @@ -98,12 +95,13 @@ class EntryRestController extends WallabagRestController $tags = $request->query->get('tags', ''); $since = $request->query->get('since', 0); + /** @var \Pagerfanta\Pagerfanta $pager */ $pager = $this->getDoctrine() ->getRepository('WallabagCoreBundle:Entry') ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags); - $pager->setCurrentPage($page); $pager->setMaxPerPage($perPage); + $pager->setCurrentPage($page); $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); $paginatedCollection = $pagerfantaFactory->createRepresentation( @@ -124,9 +122,7 @@ class EntryRestController extends WallabagRestController ) ); - $json = $this->get('serializer')->serialize($paginatedCollection, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($paginatedCollection); } /** @@ -145,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); } /** @@ -172,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. * @@ -229,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); } /** @@ -280,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); } /** @@ -325,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); } /** @@ -353,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); } /** @@ -374,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()); } /** @@ -407,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); } /** @@ -434,9 +520,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); } /** @@ -455,45 +539,46 @@ class EntryRestController extends WallabagRestController $this->validateAuthentication(); $list = json_decode($request->query->get('list', [])); - $results = []; + + if (empty($list)) { + return $this->sendResponse([]); + } // handle multiple urls - if (!empty($list)) { - foreach ($list as $key => $element) { - $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( - $element->url, - $this->getUser()->getId() - ); + $results = []; - $results[$key]['url'] = $element->url; - $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; + foreach ($list as $key => $element) { + $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( + $element->url, + $this->getUser()->getId() + ); - $tags = $element->tags; + $results[$key]['url'] = $element->url; + $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; - if (false !== $entry && !(empty($tags))) { - $tags = explode(',', $tags); - foreach ($tags as $label) { - $label = trim($label); + $tags = $element->tags; - $tag = $this->getDoctrine() - ->getRepository('WallabagCoreBundle:Tag') - ->findOneByLabel($label); + if (false !== $entry && !(empty($tags))) { + $tags = explode(',', $tags); + foreach ($tags as $label) { + $label = trim($label); - if (false !== $tag) { - $entry->removeTag($tag); - } - } + $tag = $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Tag') + ->findOneByLabel($label); - $em = $this->getDoctrine()->getManager(); - $em->persist($entry); - $em->flush(); + if (false !== $tag) { + $entry->removeTag($tag); + } } + + $em = $this->getDoctrine()->getManager(); + $em->persist($entry); + $em->flush(); } } - $json = $this->get('serializer')->serialize($results, 'json'); - - return (new JsonResponse())->setJson($json); + return $this->sendResponse($results); } /** @@ -512,32 +597,47 @@ class EntryRestController extends WallabagRestController $this->validateAuthentication(); $list = json_decode($request->query->get('list', [])); + + if (empty($list)) { + return $this->sendResponse([]); + } + $results = []; // handle multiple urls - if (!empty($list)) { - foreach ($list as $key => $element) { - $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( - $element->url, - $this->getUser()->getId() - ); + 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; + $results[$key]['url'] = $element->url; + $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; - $tags = $element->tags; + $tags = $element->tags; - if (false !== $entry && !(empty($tags))) { - $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); + if (false !== $entry && !(empty($tags))) { + $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); - $em = $this->getDoctrine()->getManager(); - $em->persist($entry); - $em->flush(); - } + $em = $this->getDoctrine()->getManager(); + $em->persist($entry); + $em->flush(); } } - $json = $this->get('serializer')->serialize($results, 'json'); + 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/Form/Type/ClientType.php b/src/Wallabag/ApiBundle/Form/Type/ClientType.php index 0ea1a9c5..eaea4feb 100644 --- a/src/Wallabag/ApiBundle/Form/Type/ClientType.php +++ b/src/Wallabag/ApiBundle/Form/Type/ClientType.php @@ -16,7 +16,11 @@ class ClientType extends AbstractType { $builder ->add('name', TextType::class, ['label' => 'developer.client.form.name_label']) - ->add('redirect_uris', UrlType::class, ['required' => false, 'label' => 'developer.client.form.redirect_uris_label']) + ->add('redirect_uris', UrlType::class, [ + 'required' => false, + 'label' => 'developer.client.form.redirect_uris_label', + 'property_path' => 'redirectUris', + ]) ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label']) ; 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 3c4d3f25..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 @@ -464,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/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/Form/Type/ConfigType.php b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php index 7e3b9dd4..1714ce74 100644 --- a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php +++ b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php @@ -39,9 +39,11 @@ class ConfigType extends AbstractType ]) ->add('items_per_page', null, [ 'label' => 'config.form_settings.items_per_page_label', + 'property_path' => 'itemsPerPage', ]) ->add('reading_speed', ChoiceType::class, [ 'label' => 'config.form_settings.reading_speed.label', + 'property_path' => 'readingSpeed', 'choices' => [ 'config.form_settings.reading_speed.100_word' => '0.5', 'config.form_settings.reading_speed.200_word' => '1', @@ -51,6 +53,7 @@ class ConfigType extends AbstractType ]) ->add('action_mark_as_read', ChoiceType::class, [ 'label' => 'config.form_settings.action_mark_as_read.label', + 'property_path' => 'actionMarkAsRead', 'choices' => [ 'config.form_settings.action_mark_as_read.redirect_homepage' => Config::REDIRECT_TO_HOMEPAGE, 'config.form_settings.action_mark_as_read.redirect_current_page' => Config::REDIRECT_TO_CURRENT_PAGE, @@ -61,6 +64,7 @@ class ConfigType extends AbstractType 'label' => 'config.form_settings.language_label', ]) ->add('pocket_consumer_key', null, [ + 'property_path' => 'pocketConsumerKey', 'label' => 'config.form_settings.pocket_consumer_key_label', ]) ->add('save', SubmitType::class, [ diff --git a/src/Wallabag/CoreBundle/Form/Type/RssType.php b/src/Wallabag/CoreBundle/Form/Type/RssType.php index 94324fed..49b31c1e 100644 --- a/src/Wallabag/CoreBundle/Form/Type/RssType.php +++ b/src/Wallabag/CoreBundle/Form/Type/RssType.php @@ -14,6 +14,7 @@ class RssType extends AbstractType $builder ->add('rss_limit', null, [ 'label' => 'config.form_rss.rss_limit', + 'property_path' => 'rssLimit', ]) ->add('save', SubmitType::class, [ 'label' => 'config.form.save', diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 1f22e901..6972e974 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php @@ -379,4 +379,34 @@ class EntryRepository extends EntityRepository ->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/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) %}
-