diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..c8758cb --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,70 @@ +module.exports = { + env: { + browser: true, + es6: true, + }, + extends: [ + 'airbnb-base', + 'plugin:unicorn/recommended', + ], + parserOptions: { + ecmaVersion: '2022', + sourceType: 'module', + }, + ignorePatterns: [ + '!.*', + '*.min.js', + ], + rules: { + 'brace-style': ['error', '1tbs'], + 'comma-dangle': ['error', { + arrays: 'always-multiline', + exports: 'always-multiline', + functions: 'never', + imports: 'always-multiline', + objects: 'always-multiline', + }], + curly: ['error', 'all'], + indent: ['error', 4, { + SwitchCase: 1, + }], + 'linebreak-style': ['error', 'unix'], + 'no-multi-spaces': ['error', { + exceptions: { + Property: true, + VariableDeclarator: true, + }, + }], + 'object-shorthand': ['error', 'never'], + 'padding-line-between-statements': ['error', { + blankLine: 'always', + next: ['continue', 'break', 'export', 'return', 'throw'], + prev: '*', + }], + 'spaced-comment': ['error', 'always', { + block: { + balanced: true, + exceptions: ['*'], + markers: ['!'], + }, + line: { + exceptions: ['-', '+'], + markers: ['/'], + }, + }], + strict: 'off', + 'wrap-iife': ['error', 'inside'], + 'func-names': 'off', + 'no-param-reassign': 'off', + 'no-use-before-define': 'off', + 'prefer-destructuring': 'off', + 'unicorn/prefer-module': 'off', + 'unicorn/prefer-query-selector': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/prevent-abbreviations': 'off', + }, + reportUnusedDisableDirectives: true, + globals: { + bootstrap: true, + }, +}; diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml new file mode 100644 index 0000000..6fc4335 --- /dev/null +++ b/.github/workflows/lintcheck.yml @@ -0,0 +1,45 @@ +name: LintCheck + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + validate: + if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name)" + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: 22 + + - name: Install Node dependencies + run: npm install --omit=optional + + - name: Lint JavaScript + run: npx eslint --ext .js . + + - name: Lint Less + run: npx stylelint "./styles/styles.less" + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + + - name: Build site + run: bundle exec jekyll build + + - name: Lint HTML + # Find generated HTML, excluding news pages as then contain HTML4 + run: find _site -name '*.html' ! -path '_site/news/*/*.html' -print0 | xargs -0 -r npx htmlhint -c .htmlhintrc.js + diff --git a/.htmlhintrc b/.htmlhintrc new file mode 100644 index 0000000..c42a29a --- /dev/null +++ b/.htmlhintrc @@ -0,0 +1,26 @@ +{ + "doctype-first": true, + "doctype-html5": true, + + "tagname-lowercase": true, + "attr-lowercase": true, + "attr-value-double-quotes": true, + "attr-no-duplication": true, + "attr-value-no-duplication": true, + + "tag-pair": true, + "empty-tag-not-self-closed": true, + "tag-no-obsolete": true, + + "spec-char-escape": true, + "src-not-empty": true, + "alt-require": true, + "id-unique": true, + + "html-lang-require": true, + "title-require": true, + + "button-type-require": true, + "input-requires-label": true, + "frame-title-require": true, +} diff --git a/Makefile b/Makefile index 72929ec..e36a222 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,14 @@ -.PHONY: all css serve-dev +.PHONY: all css js serve-dev + +define update_cache_buster + hash=$$(sha256sum "$(1)" | cut -c1-8); \ + perl -0pi -e "s|(^[[:space:]]*$(2):.*?url: '/)[^']*(')|\$$1$(1)?v=$$hash\$$2|ms" _config.yml +endef + +define update_sri + hash=$$(openssl dgst -sha384 -binary "$(1)" | openssl base64 -A); \ + perl -0pi -e "s|(^[[:space:]]*$(2):.*?sri: ')[^']*(')|\$$1sha384-$$hash\$$2|ms" _config.yml +endef all: @echo '# These are the available make targets' @@ -6,7 +16,22 @@ all: css: npm install - npx less --clean-css="--s1 --advanced" styles/styles.less > styles/styles.min.css + npx lessc --clean-css="--s1 --advanced" styles/styles.less > styles/styles.min.css + $(call update_cache_buster,styles/styles.min.css,css) + $(call update_sri,styles/styles.min.css,css) + +js: + npm install + @for file in js/*.js; do \ + case "$$file" in \ + *.min.js) continue ;; \ + esac; \ + minified="$${file%.js}.min.js"; \ + name="$$(basename "$$file" .js)"; \ + npx uglifyjs "$$file" -c -m -o "$$minified"; \ + $(call update_cache_buster,$$minified,$$name); \ + $(call update_sri,$$minified,$$name); \ + done serve-dev: docker run -it -p 4000:4000 -v $(PWD):/site -w /site library/ruby:3 bash -c 'bundle install && bundle exec jekyll serve -H 0.0.0.0' diff --git a/README.md b/README.md index 02d9161..3e9ee7e 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,7 @@ The pages are rendered with Jekyll and kindly hosted by github. ## Build CSS To build the stylesheets run `make css`. + +## Build JS + +To build the stylesheets run `make js`. diff --git a/_config.yml b/_config.yml index 09d8338..2d6f445 100644 --- a/_config.yml +++ b/_config.yml @@ -1,38 +1,56 @@ safe: true encoding: UTF-8 url: https://roundcube.net -enforce_ssl: roundcube.net -hostname: "https://roundcube.net" permalink: news/:year/:month/:day/:title exclude: [vendor] plugins: [jekyll-paginate] paginate: 10 -paginate_path: "/news/page:num/" -environment: "production" +paginate_path: '/news/page:num/' +environment: 'production' num_related_items: 5 -libs: { - fonts: { - roboto: 'https://fonts.googleapis.com/css?family=Roboto&display=swap', - }, - less: { - url: 'https://cdn.jsdelivr.net/npm/less@4.6.4/dist/less.min.js', - sri: 'sha384-WR+iiQT+XSq8wDUutASKAXx1pZTJCLRyHfpwqBiL1ji4qd2BsJIPhn8k/s72NkPj' - }, - css: '/styles/styles.min.css?v=2026051000', - bootstrap: { - css: { - url: 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css', - sri: 'sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB' - }, - js: { - url: 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js', - sri: 'sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI' - } - }, - fontawesome: { - css: { - url: 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.2.0/css/all.min.css', - sri: 'sha384-EXatlQyrOJgDaM9/a74ArMzy7/2bTMSrZj8ID1IPeVmc3GncfCugefCFWSLj8JL/' - }, - } -} +exclude: + - Gemfile + - Gemfile.lock + - node_modules/ + - Makefile + - package.json + - package-lock.json + - README.md + - stylelint.config.mjs + - vendor +libs: + css: + url: '/styles/styles.min.css?v=9bf32423' + sri: 'sha384-yb8/IfPlNNl8SDmdqLJ/CA5xbgQHXyHb2ehtoAamh+bLbMmu/tW+EBvF2BdzkzLv' + js: + banner: + url: '/js/banner.min.js?v=f44c2f56' + sri: 'sha384-zF0W4VZ06nGXgt2nFDiPoMhn5+UK+WaAIX1cprNGHkcURqccwGD+fXRZiu77TZ+b' + copy: + url: '/js/copy.min.js?v=ebb9b310' + sri: 'sha384-9TlbtUpnP3OoI5QMwNcfEMbkzjHOvlpeDEjVJCPWHJPxwCA5K2dXqlKZrb07rYVa' + theme: + url: '/js/theme.min.js?v=17d30fbf' + sri: 'sha384-VhNYv2z6f3ZriTjr7a9xelQKHZilrphb/xj36CtPT1McEdLSIPrMG4L+IJkgC1+T' + viewer: + url: '/js/viewer.min.js?v=df6dc21d' + sri: 'sha384-Gn/Zio1YIE6O7P0MXgTN8GECQMnPDd8MYeXQbJV5hk4+6O6aWhkBHcFUFs+DpNDL' + less: + js: + url: 'https://cdn.jsdelivr.net/npm/less@4.9.1/dist/less.min.js' + sri: 'sha256-QoOydTeDccOLjcHMHS1oOUe0QaqHXh2XFuki4s/zSXc=' + roboto: + css: + url: 'https://cdn.jsdelivr.net/npm/@fontsource/roboto@5.3.0/400.css' + sri: 'sha256-aYPEXwxOBquazL7uNzcM57whskXEvU0X84EZ2lI41yk=' + bootstrap: + css: + url: 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css' + sri: 'sha256-2FMn2Zx6PuH5tdBQDRNwrOo60ts5wWPC9R8jK67b3t4=' + js: + url: 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js' + sri: 'sha256-5P1JGBOIxI7FBAvT/mb1fCnI5n/NhQKzNUuW7Hq0fMc=' + fontawesome: + css: + url: 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.3.1/css/all.min.css' + sri: 'sha256-4Lad8m4ZWW1Lgb9+sMVLYEfnIh7BjV1NQMEe79Pviks=' diff --git a/_includes/footer.html b/_includes/footer.html index 9e7e268..bda5fad 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -13,9 +13,9 @@ Switch theme
@@ -41,91 +41,14 @@ - \ No newline at end of file +{% assign scripts = "theme" | split: "," %} +{% if page.scripts %}{% assign scripts = scripts | concat: page.scripts %}{% endif %} +{% for script in scripts %} + {% assign js = site.libs.js[script] %} + + {% if site.environment == "dev" %} + + {% else %} + + {% endif %} +{% endfor %} diff --git a/_includes/head.html b/_includes/head.html index 6c5fcb5..bd5c82b 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -5,20 +5,14 @@ - + {% if site.environment == "dev" %} - - + + {% else %} - + {% endif %} - diff --git a/_includes/viewer.html b/_includes/viewer.html new file mode 100644 index 0000000..15353b5 --- /dev/null +++ b/_includes/viewer.html @@ -0,0 +1,20 @@ +- gpg --verify roundcubemail-<version>.tar.gz{.asc,} + gpg --verify roundcubemail-<version>.tar.gz{.asc,}
-
+
The Nextcloud app "Roundcube Mail" integrates an existing Roundcube standalone email-webapp into Nextcloud, with optional SSO.
Roundcube for Enterprises - is a stand-alone, all-in-one email solution for service providers by Nextcloud. Your customers need a reliable, dependable and performant email service. The Nextcloud Roundcube Enterprise subscription helps you deliver exactly that. + is a stand-alone, all-in-one email solution for service providers by Nextcloud. Your customers need a reliable, dependable and performant email service. The Nextcloud Roundcube Enterprise subscription helps you deliver exactly that.
diff --git a/google2ae4ba011e8a69d4.html b/google2ae4ba011e8a69d4.html index e046b08..d24ab5a 100644 --- a/google2ae4ba011e8a69d4.html +++ b/google2ae4ba011e8a69d4.html @@ -1,4 +1,7 @@ + - + +