├── .browserslistrc
├── Dockerfile
├── .stylelintignore
├── icon.png
├── htdocs
├── favicon.ico
├── assets
│ ├── thumbnails
│ │ ├── blisk.png
│ │ ├── chrome.png
│ │ ├── firefox.png
│ │ ├── forkme.png
│ │ ├── github.png
│ │ ├── modal.png
│ │ ├── npms-io.png
│ │ ├── pingdom.png
│ │ ├── pixabay.png
│ │ ├── schema.png
│ │ ├── trello.png
│ │ ├── vivaldi.png
│ │ ├── analytics.png
│ │ ├── bitbucket.png
│ │ ├── modern-ie.png
│ │ ├── pagespeed.png
│ │ ├── safari-tp.png
│ │ ├── sdi-blog.png
│ │ ├── autocomplete.png
│ │ ├── browserlist.png
│ │ ├── codecademy.png
│ │ ├── design-uber.png
│ │ ├── firefox-dev.png
│ │ ├── google-dev.png
│ │ ├── microformats.png
│ │ ├── mozilla-dev.png
│ │ ├── rich-snippet.png
│ │ ├── sdi-homepage.png
│ │ ├── smartmockups.png
│ │ ├── accessibility.png
│ │ ├── design-airbnb.png
│ │ ├── design-firefox.png
│ │ ├── design-google.png
│ │ ├── html-validator.png
│ │ ├── magic-mockups.png
│ │ ├── sdi-startpage.png
│ │ ├── webmastertools.png
│ │ ├── codepen-patterns.png
│ │ ├── design-atlassian.png
│ │ ├── design-facebook.png
│ │ ├── design-invision.png
│ │ ├── design-material.png
│ │ ├── design-microsoft.png
│ │ ├── design-unsplash.png
│ │ ├── mobile-friendly.png
│ │ ├── sdi-personalnews.png
│ │ ├── frontend-bookmarks.png
│ │ └── sdi-little-helpers.png
│ ├── wallpaper
│ │ └── default.jpg
│ ├── qr-codes
│ │ ├── design-system.png
│ │ ├── github-project.png
│ │ ├── little-helpers.png
│ │ ├── metafolio-de.png
│ │ ├── metaideen-de.png
│ │ ├── startpage-demo.png
│ │ └── personalnews-landing.png
│ ├── js
│ │ └── site.js
│ └── css
│ │ └── site.css
├── manifest.json
├── favicon.svg
├── maskIcon.svg
├── application.manifest.php
├── serviceworker.js
├── config
│ └── config.php
├── index.php
└── data
│ └── data.json
├── src
├── assets
│ ├── scss
│ │ ├── _your-themes
│ │ │ ├── variables.scss
│ │ │ └── your-theme.scss
│ │ ├── 2-tools
│ │ │ ├── _align-vertical.scss
│ │ │ ├── _clearfix.scss
│ │ │ ├── _content-visibility.scss
│ │ │ ├── _align-horizontal.scss
│ │ │ ├── _ul-reset.scss
│ │ │ ├── _align-centered.scss
│ │ │ └── _responsive.scss
│ │ ├── 4-elements
│ │ │ ├── _links.scss
│ │ │ ├── _navigation.scss
│ │ │ ├── _buttons.scss
│ │ │ ├── _main.scss
│ │ │ ├── _footer.scss
│ │ │ ├── _details-summary.scss
│ │ │ └── _header.scss
│ │ ├── 7-utilities
│ │ │ ├── sticky.scss
│ │ │ ├── _js-utilities.scss
│ │ │ └── _theme-default.scss
│ │ ├── 3-generic
│ │ │ ├── _font-smoothing.scss
│ │ │ ├── _box-sizing.scss
│ │ │ ├── _page.scss
│ │ │ └── _reset.scss
│ │ ├── 5-objects
│ │ │ ├── _list-horizontal.scss
│ │ │ ├── _tabbed-content.scss
│ │ │ ├── _list-vertical.scss
│ │ │ └── _list-tiles.scss
│ │ ├── 6-components
│ │ │ ├── _modal-qr.scss
│ │ │ ├── _wallpaper.scss
│ │ │ ├── _backdrop.scss
│ │ │ ├── _notification.scss
│ │ │ ├── _overlay.scss
│ │ │ ├── _modal-list.scss
│ │ │ ├── _modal.scss
│ │ │ ├── _tile.scss
│ │ │ └── _flyout.scss
│ │ ├── _shame.scss
│ │ ├── 1-settings
│ │ │ ├── _globals.scss
│ │ │ └── _colors.scss
│ │ └── site.scss
│ └── js
│ │ ├── functions
│ │ ├── find.js
│ │ ├── scrollToPos.js
│ │ ├── addClass.js
│ │ ├── findAll.js
│ │ ├── removeClass.js
│ │ ├── toggleClass.js
│ │ ├── localStorage.js
│ │ ├── stickyElement.js
│ │ ├── fixScrollPos.js
│ │ └── handleTriggers.js
│ │ ├── components
│ │ ├── setJsAvailability.js
│ │ ├── notificationKeydown.js
│ │ └── handleTabsLocalStorage.js
│ │ └── site.js
├── images
│ └── favicon.svg
├── serviceworker.js
└── manifests
│ └── default.pp
├── .screenshots
└── startpage-macbook-iphone.jpg
├── .gitignore
├── __tests__
└── e2e
│ ├── modal.spec.ts-snapshots
│ ├── open-firefox-darwin.png
│ ├── open-chromium-darwin.png
│ └── open-Mobile-Safari-darwin.png
│ ├── render.spec.ts-snapshots
│ ├── render-chromium-darwin.png
│ ├── render-firefox-darwin.png
│ └── render-Mobile-Safari-darwin.png
│ ├── sidebar.spec.ts-snapshots
│ ├── sidebar-firefox-darwin.png
│ ├── sidebar-chromium-darwin.png
│ └── sidebar-Mobile-Safari-darwin.png
│ ├── render.spec.ts
│ ├── modal.spec.ts
│ ├── tabswitch.spec.ts
│ └── sidebar.spec.ts
├── .editorconfig
├── _tasks
├── _config.json
├── clean.js
├── scripts.js
├── environment.js
├── imagemin.js
├── styles.js
└── vm.js
├── docker-compose.yml
├── gulpfile.js
├── postcss.config.js
├── LICENSE
├── webpack.config.js
├── Vagrantfile
├── package.json
├── playwright.config.ts
├── README.md
├── .stylelintrc.json
└── tests
└── example.spec.ts
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 0.25%
2 | not dead
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.3-apache
2 | ADD htdocs /htdocs
3 | EXPOSE 8080
4 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | htdocs
2 | node_modules
3 | *.md
4 | src/assets/scss/site.scss
5 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/icon.png
--------------------------------------------------------------------------------
/htdocs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/favicon.ico
--------------------------------------------------------------------------------
/src/assets/scss/_your-themes/variables.scss:
--------------------------------------------------------------------------------
1 | /*
2 | put values you want to overwrite here
3 | e.g. $brand-color: #f00;
4 | */
5 |
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/blisk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/blisk.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/chrome.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/firefox.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/forkme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/forkme.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/github.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/modal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/modal.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/npms-io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/npms-io.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/pingdom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/pingdom.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/pixabay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/pixabay.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/schema.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/trello.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/trello.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/vivaldi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/vivaldi.png
--------------------------------------------------------------------------------
/htdocs/assets/wallpaper/default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/wallpaper/default.jpg
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/analytics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/analytics.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/bitbucket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/bitbucket.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/modern-ie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/modern-ie.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/pagespeed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/pagespeed.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/safari-tp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/safari-tp.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/sdi-blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/sdi-blog.png
--------------------------------------------------------------------------------
/.screenshots/startpage-macbook-iphone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/.screenshots/startpage-macbook-iphone.jpg
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/design-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/design-system.png
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/github-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/github-project.png
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/little-helpers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/little-helpers.png
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/metafolio-de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/metafolio-de.png
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/metaideen-de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/metaideen-de.png
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/startpage-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/startpage-demo.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/autocomplete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/autocomplete.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/browserlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/browserlist.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/codecademy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/codecademy.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-uber.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-uber.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/firefox-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/firefox-dev.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/google-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/google-dev.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/microformats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/microformats.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/mozilla-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/mozilla-dev.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/rich-snippet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/rich-snippet.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/sdi-homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/sdi-homepage.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/smartmockups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/smartmockups.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/accessibility.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/accessibility.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-airbnb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-airbnb.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-firefox.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-google.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/html-validator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/html-validator.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/magic-mockups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/magic-mockups.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/sdi-startpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/sdi-startpage.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/webmastertools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/webmastertools.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/codepen-patterns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/codepen-patterns.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-atlassian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-atlassian.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-facebook.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-invision.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-invision.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-material.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-material.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-microsoft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-microsoft.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/design-unsplash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/design-unsplash.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/mobile-friendly.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/mobile-friendly.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/sdi-personalnews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/sdi-personalnews.png
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_align-vertical.scss:
--------------------------------------------------------------------------------
1 | @mixin align-vertical {
2 | position: absolute;
3 | top: 50%;
4 | transform: translateY(-50%);
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_clearfix.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &::after {
3 | clear: both;
4 | content: "";
5 | display: table;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_content-visibility.scss:
--------------------------------------------------------------------------------
1 | @mixin content-visibility {
2 | contain-intrinsic-size: 1px 2000px;
3 | content-visibility: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/htdocs/assets/qr-codes/personalnews-landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/qr-codes/personalnews-landing.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/frontend-bookmarks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/frontend-bookmarks.png
--------------------------------------------------------------------------------
/htdocs/assets/thumbnails/sdi-little-helpers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/htdocs/assets/thumbnails/sdi-little-helpers.png
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_align-horizontal.scss:
--------------------------------------------------------------------------------
1 | @mixin align-horizontal {
2 | left: 50%;
3 | position: absolute;
4 | transform: translateX(-50%);
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_ul-reset.scss:
--------------------------------------------------------------------------------
1 | @mixin ul-reset {
2 | list-style-type: none;
3 | margin-bottom: 0;
4 | margin-top: 0;
5 | padding-left: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _private
2 | _private_m
3 | node_modules
4 | .idea
5 | .vscode
6 | .DS_Store
7 | /__test-results__/
8 | /playwright-report/
9 | /playwright/.cache/
10 |
--------------------------------------------------------------------------------
/__tests__/e2e/modal.spec.ts-snapshots/open-firefox-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/modal.spec.ts-snapshots/open-firefox-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/modal.spec.ts-snapshots/open-chromium-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/modal.spec.ts-snapshots/open-chromium-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/render.spec.ts-snapshots/render-chromium-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/render.spec.ts-snapshots/render-chromium-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/render.spec.ts-snapshots/render-firefox-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/render.spec.ts-snapshots/render-firefox-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-firefox-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-firefox-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/modal.spec.ts-snapshots/open-Mobile-Safari-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/modal.spec.ts-snapshots/open-Mobile-Safari-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-chromium-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-chromium-darwin.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/__tests__/e2e/render.spec.ts-snapshots/render-Mobile-Safari-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/render.spec.ts-snapshots/render-Mobile-Safari-darwin.png
--------------------------------------------------------------------------------
/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-Mobile-Safari-darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saschadiercks/browserStartpage/HEAD/__tests__/e2e/sidebar.spec.ts-snapshots/sidebar-Mobile-Safari-darwin.png
--------------------------------------------------------------------------------
/_tasks/_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": "src",
3 | "dist": "htdocs",
4 | "assetSrc": "src/assets",
5 | "assetDist": "htdocs/assets",
6 | "envProduction": "production",
7 | "envDevelopment": "development"
8 | }
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | services:
3 | web:
4 | build: .
5 | command: php -S 0.0.0.0:8080 -t /htdocs
6 | ports:
7 | - "8080:8080"
8 | volumes:
9 | - ./htdocs:/htdocs
10 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_links.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | a {
9 | color: inherit;
10 | }
11 |
--------------------------------------------------------------------------------
/src/assets/scss/7-utilities/sticky.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .sdi-sticky {
9 | left: 0;
10 | position: sticky;
11 | top: 0;
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/js/functions/find.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function find(selector) {
9 | return document.querySelector(selector);
10 | }
11 |
--------------------------------------------------------------------------------
/htdocs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Startpage",
3 | "name": "Browser Startpage",
4 | "icons": [
5 | {
6 | "src": "favicon.svg",
7 | "type": "image/svg+xml",
8 | "sizes": "any"
9 | }
10 | ],
11 | "start_url": "./?utm_source=homescreen"
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/scss/3-generic/_font-smoothing.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | body {
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/scss/5-objects/_list-horizontal.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .list-horizontal {
9 | @include ul-reset;
10 |
11 | li {
12 | display: inline-block;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_align-centered.scss:
--------------------------------------------------------------------------------
1 | @mixin align-centered($scale, $position: absolute) {
2 | @if $scale {
3 | transform: translate(-50%, -50%) scale(#{$scale});
4 | }
5 |
6 | @else {
7 | transform: translate(-50%, -50%);
8 | }
9 | left: 50%;
10 | position: #{$position};
11 | top: 50%;
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/js/functions/scrollToPos.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function scrollToPos(x,y) {
9 | window.scroll({
10 | top: y,
11 | left: x,
12 | behavior: 'smooth'
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_modal-qr.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .modal-content--qr {
9 | text-align: center;
10 | }
11 |
12 | .modal-content--qr img {
13 | mix-blend-mode: multiply;
14 | }
15 |
--------------------------------------------------------------------------------
/src/assets/scss/_shame.scss:
--------------------------------------------------------------------------------
1 | /*
2 | The class js-hidden is used to hide the notification
3 | this class uses display:none. To be able to animate the notification via css,
4 | we need to overwrite that class
5 | */
6 | #notification {
7 | display: block !important;
8 | }
9 |
10 | .overlay {
11 | display: block !important;
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/js/functions/addClass.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function addClass(elements, className) {
9 | elements.forEach(function(element){
10 | element.classList.add(className);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/js/functions/findAll.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function findAll(selector) {
9 | var elements = document.querySelectorAll(selector);
10 | return Array.prototype.slice.call(elements)
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/js/functions/removeClass.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function removeClass(elements,className) {
9 | elements.forEach(function(element){
10 | element.classList.remove(className);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/js/functions/toggleClass.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function toggleClass(elements, className) {
9 | elements.forEach(function(element){
10 | element.classList.toggle(className);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_navigation.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | nav {
9 | ul {
10 | @include ul-reset;
11 | @include clearfix;
12 | }
13 |
14 | a {
15 | display: block;
16 | text-decoration: none;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/_tasks/clean.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 | const del = require("del");
5 |
6 | /* ################# */
7 | /* ##### Tasks ##### */
8 | /* ################# */
9 | gulp.task("clean:scripts", function () {
10 | return del([config.assetDist + "/js/**", config.assetDist + "/css/**"]);
11 | });
12 |
--------------------------------------------------------------------------------
/src/assets/js/functions/localStorage.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 |
7 | // ###### script ######
8 | export default function doLocalStorage(item,value) {
9 | if(value) {
10 | localStorage.setItem(item,value);
11 | } else {
12 | return localStorage.getItem(item);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/__tests__/e2e/render.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | test("Render", async ({ page }) => {
4 | // Go to http://localhost:8080/
5 | await page.goto("/");
6 |
7 | // take a screenshot
8 | test.slow(); // give time to fetch
9 | expect(await page.screenshot()).toMatchSnapshot("render.png", {
10 | threshold: 0.3
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/assets/scss/3-generic/_box-sizing.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Set the global `box-sizing` state to `border-box`.
3 | *
4 | * css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice
5 | * paulirish.com/2012/box-sizing-border-box-ftw
6 | */
7 |
8 | html {
9 | box-sizing: border-box;
10 | }
11 |
12 | *,
13 | *::before,
14 | *::after {
15 | box-sizing: inherit;
16 | }
17 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_buttons.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $button-background-color: transparent !default;
5 |
6 | //---------------------
7 | // ###### Layout ######
8 | //---------------------
9 | button {
10 | background-color: $button-background-color;
11 | border-width: 0;
12 | color: inherit;
13 | cursor: pointer;
14 | font-size: inherit;
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_main.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | main {
9 | // we need to set the width via CSS to avoid jumping, when position:fixed is added
10 | left: 0;
11 | margin: $base-spacing-size;
12 | position: relative;
13 | right: 0;
14 | z-index: 1;
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/scss/5-objects/_tabbed-content.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .tabbed-content {
9 | @include content-visibility;
10 | }
11 |
12 | // Hide Tabs if JS is available, otherwise show all content
13 | .js .tabbed-content:not(.sdi-is-active) {
14 | display: none;
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_wallpaper.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | #wallpaper {
9 | background-position: center center;
10 | background-size: cover;
11 | bottom: 0;
12 | left: 0;
13 | position: fixed;
14 | right: 0;
15 | top: 0;
16 | filter: var(--wallpaper-filter);
17 | }
18 |
--------------------------------------------------------------------------------
/src/assets/scss/5-objects/_list-vertical.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .list-vertical {
9 | @include ul-reset;
10 | padding-bottom: $base-spacing-size * 0.75;
11 | }
12 |
13 | .list-vertical__link {
14 | display: block;
15 | text-decoration: none;
16 |
17 | &:hover {
18 | text-decoration: underline;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/_tasks/scripts.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 | const exec = require("child_process").exec;
5 |
6 | /* ################# */
7 | /* ##### Tasks ##### */
8 | /* ################# */
9 | gulp.task("scripts:build", function(cb) {
10 | exec("npx webpack --config webpack.config.js", function(err, stdout, stderr) {
11 | console.log(stdout);
12 | console.log(stderr);
13 | cb(err);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/assets/scss/2-tools/_responsive.scss:
--------------------------------------------------------------------------------
1 | // Responsive Layout
2 | @mixin phone-only {
3 | @media (max-width: 670px) {
4 | @content;
5 | }
6 | }
7 |
8 | @mixin tablet-portrait {
9 | @media (min-width: 671px) and (max-width: 1023px) {
10 | @content;
11 | }
12 | }
13 |
14 | @mixin tablet-landscape-up {
15 | @media (min-width: 1024px) {
16 | @content;
17 | }
18 | }
19 |
20 | @mixin default {
21 | @media (min-width: 671px) {
22 | @content;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/_tasks/environment.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 |
5 | /* ################# */
6 | /* ##### Tasks ##### */
7 | /* ################# */
8 | gulp.task("set-dev-node-env", function () {
9 | return Promise.resolve((process.env.NODE_ENV = config.envDevelopment));
10 | });
11 |
12 | gulp.task("set-prod-node-env", function () {
13 | return Promise.resolve((process.env.NODE_ENV = config.envProduction));
14 | });
15 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_backdrop.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $backdrop-color__this: var(--backdrop-color) !default;
5 |
6 | //---------------------
7 | // ###### Layout ######
8 | //---------------------
9 | .backdrop {
10 | background-color: $backdrop-color__this;
11 | backdrop-filter: blur(10px);
12 | height: 100vh;
13 | position: absolute;
14 | top: 0;
15 | transition: opacity 0s ease;
16 | width: 100vw;
17 | z-index: -1;
18 | }
19 |
--------------------------------------------------------------------------------
/src/assets/scss/3-generic/_page.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 |
9 | // ---- theme: auto
10 | // the default is light
11 | html {
12 | @include theme-light;
13 | }
14 |
15 | @media (prefers-color-scheme: dark) {
16 | html {
17 | @include theme-dark;
18 | }
19 | }
20 |
21 | body {
22 | background-color: var(--bg);
23 | font-family: $document-font-family;
24 | width: 100%;
25 | }
26 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_notification.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $notification-background-color: var(--bg) !default;
5 | $notification-color: var(--color) !default;
6 |
7 | //---------------------
8 | // ###### Layout ######
9 | //---------------------
10 | .notification {
11 | transition: all 0.2s;
12 |
13 | &.js-hidden {
14 | transform: translateX(100%);
15 | }
16 |
17 | &.js-visible {
18 | right: $base-spacing-size;
19 | transform: translateX(0%);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/assets/scss/7-utilities/_js-utilities.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | // ---- js-hidden (Elements to be hidden via JS) ----
9 | .js-hidden {
10 | // display: none;
11 | // z-index: -1;
12 | }
13 |
14 | // ---- js-fixed (Element to be fixed via JS) ----
15 | .sdi-is-fixed {
16 | position: fixed;
17 | }
18 |
19 | // ---- Js-sticky (Element to be fixed via JS) ----
20 | .js-sticky {
21 | position: fixed;
22 | }
23 |
--------------------------------------------------------------------------------
/src/assets/scss/3-generic/_reset.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | html {
9 | font-size: 100%;
10 | height: 100%;
11 | overflow-x: hidden;
12 | overflow-y: scroll;
13 | -webkit-text-size-adjust: 100%;
14 | -ms-text-size-adjust: 100%;
15 | }
16 |
17 | body {
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | img {
23 | height: auto;
24 | max-width: 100%;
25 | }
26 |
27 | /* HTML 5 fixes */
28 | header,
29 | main,
30 | footer {
31 | display: block;
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/scss/_your-themes/your-theme.scss:
--------------------------------------------------------------------------------
1 | /*
2 | How to theme the site:
3 | Place your css/scss here to change the layout of the page
4 | Copy Variables from 1-settings/globals.scss and paste them here to change them
5 | Use 'gulp build' to build the theme
6 |
7 | If you want to make some deeper changes, duplicate 7-trumps/theme-default and
8 | name it to your liking.
9 | After that add the new file at the bottom of site.scss (look there for
10 | additonal information)
11 |
12 | This way it's the easiest to stay up to date if there are further changes in
13 | the repository - which is not unlikely.
14 | */
15 |
--------------------------------------------------------------------------------
/htdocs/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/js/components/setJsAvailability.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 | import findAll from "../functions/findAll.js";
3 | import addClass from "../functions/addClass.js";
4 | import removeClass from "../functions/removeClass.js";
5 |
6 | // ####################
7 | // ##### settings #####
8 | // ####################
9 | const class__jsIsAvailable = 'js';
10 | const class__jsIsNotAvailable = 'no-js';
11 |
12 | // ###### script ######
13 | export default function setJsAvailability(selector) {
14 | var selector = findAll(selector);
15 |
16 | selector.forEach(function() {
17 | addClass(selector, class__jsIsAvailable);
18 | removeClass(selector, class__jsIsNotAvailable);
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/src/assets/js/functions/stickyElement.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 | import addClass from "./addClass.js";
3 | import find from "./find.js";
4 | import findAll from "./findAll.js";
5 |
6 | // ####################
7 | // ##### settings #####
8 | // ####################
9 | const class__sticky = 'js-sticky';
10 |
11 | // ###### script ######
12 | export default function stickyElement(selectorSticky, selectorCompensate, propertyCompensate) {
13 | var stickyElement = findAll(selectorSticky);
14 | var stickyHeight = find(selectorSticky).clientHeight + 'px';
15 | addClass(stickyElement, class__sticky);
16 |
17 | find(selectorCompensate).style.setProperty(propertyCompensate,stickyHeight);
18 | }
19 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_overlay.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $overlay-background-color: var(--bg) !default;
5 | $overlay-color: var(--color) !default;
6 |
7 | //---------------------
8 | // ###### Layout ######
9 | //---------------------
10 | .overlay {
11 | display: block;
12 | position: fixed;
13 | z-index: 3;
14 | }
15 |
16 | .overlay-close {
17 | float: right;
18 | }
19 |
20 | .overlay-title {
21 | font-size: 1rem;
22 | font-weight: 600;
23 | margin-top: 0;
24 | padding-top: inherit;
25 | }
26 |
27 | .overlay-content {
28 | height: 100vh;
29 | overflow-y: auto;
30 | transition: all $base-animation-speed ease;
31 | width: 100%;
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/scss/5-objects/_list-tiles.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 |
5 | //---------------------
6 | // ###### Layout ######
7 | //---------------------
8 | .list-tiles {
9 | @include ul-reset;
10 | align-items: stretch;
11 | display: flex;
12 | flex-wrap: wrap;
13 | margin-left: auto;
14 | margin-right: auto;
15 | }
16 |
17 | @supports (display: grid) {
18 | .list-tiles {
19 | display: grid;
20 | grid-gap: $base-spacing-size;
21 | grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
22 | }
23 |
24 | @media (min-width: 768px) {
25 | .list-tiles {
26 | grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/_tasks/imagemin.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 | const imagemin = require("gulp-imagemin");
5 |
6 | const imgExt = "{jpg,png,svg}";
7 |
8 | /* ################# */
9 | /* ##### Tasks ##### */
10 | /* ################# */
11 | gulp.task("imagemin", function () {
12 | return gulp
13 | .src(config.assetDist + "/**/*." + imgExt)
14 | .pipe(
15 | imagemin([
16 | imagemin.jpegtran({ progressive: true }),
17 | imagemin.optipng({ optimizationLevel: 5 }),
18 | imagemin.svgo({
19 | plugins: [{ removeViewBox: true }, { cleanupIDs: false }],
20 | }),
21 | ])
22 | )
23 | .pipe(gulp.dest(config.assetDist));
24 | });
25 |
--------------------------------------------------------------------------------
/src/assets/scss/1-settings/_globals.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 |
3 | // ----- Setup -----
4 | $document-font-family: -apple-system, sans-serif !default;
5 | $wallpaper-filter: blur(0.1px) !default;
6 |
7 | $base-spacing-size: 1vw !default;
8 | $base-spacing-size-px: 9px !default;
9 | $base-animation-speed: 0.2s !default;
10 |
11 | $tile-button-padding: math.div($base-spacing-size, 2) !default;
12 | $tile-button-color: inherit;
13 |
14 | $background-blur-setting: 1px;
15 |
16 | $modal-border: none !default;
17 | $modal-border-radius: 4px !default;
18 | $modal-content-max-height: 400px !default;
19 | $modal-content-height: auto !default;
20 | $modal-content-max-width: 500px !default;
21 | $modal-content-width: 90% !default;
22 |
23 | $bookmarks-max-width: 400px !default;
24 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_modal-list.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $modal-list-link-color-hover: var(--border-color) !default;
5 |
6 | //---------------------
7 | // ###### Layout ######
8 | //---------------------
9 | .modal-list {
10 | @include ul-reset;
11 | }
12 |
13 | .modal-list__item {
14 | display: inline-block;
15 | width: 49%;
16 | }
17 |
18 | @supports (display: grid) {
19 | .modal-list {
20 | display: grid;
21 | grid-template-columns: 50% auto;
22 | }
23 |
24 | .modal-list__item {
25 | width: 100%;
26 | }
27 | }
28 |
29 | .modal-list__link {
30 | display: block;
31 | padding: $base-spacing-size-px;
32 | text-decoration: none;
33 |
34 | &:hover {
35 | background-color: $modal-list-link-color-hover;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/assets/js/functions/fixScrollPos.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 |
3 | // ####################
4 | // ##### settings #####
5 | // ####################
6 | const selector__body = document.querySelector('body');
7 | const class__elementIsFixed = 'sdi-is-fixed';
8 |
9 | var scrollYSaved;
10 |
11 | // ###### script ######
12 | export default function fixScrollPos(event) {
13 | var scrollY = window.pageYOffset;
14 | //console.log(event);
15 |
16 | if(selector__body.classList.contains(class__elementIsFixed)) {
17 | selector__body.classList.remove(class__elementIsFixed);
18 | selector__body.style.top = '';
19 | window.scrollTo(0,scrollYSaved);
20 | } else {
21 | selector__body.classList.add(class__elementIsFixed);
22 | selector__body.style.top = '-' + scrollY + 'px';
23 | scrollYSaved = scrollY;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_footer.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $footer-bg: var(--brand-color) !default;
5 | $footer-color: var(--color-light) !default;
6 |
7 | //---------------------
8 | // ###### Layout ######
9 | //---------------------
10 | footer {
11 | @include clearfix;
12 | background-color: $footer-bg;
13 | color: $footer-color;
14 | font-size: 0.75rem;
15 | padding-left: $base-spacing-size;
16 | padding-right: $base-spacing-size;
17 | width: 100%;
18 | z-index: 1;
19 |
20 | &.js-sticky {
21 | bottom: 0;
22 | }
23 |
24 | a {
25 | color: inherit;
26 | display: inline-block;
27 | padding-bottom: $base-spacing-size-px;
28 | padding-top: $base-spacing-size-px;
29 | }
30 |
31 | .description {
32 | float: left;
33 | }
34 |
35 | .social-profiles {
36 | float: right;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const gulp = require("gulp");
3 | require("require-dir")("./_tasks");
4 |
5 | /* ################# */
6 | /* ##### Tasks ##### */
7 | /* ################# */
8 | // --- set environment ----
9 | gulp.task("production", gulp.series("set-prod-node-env"));
10 | gulp.task("development", gulp.series("set-dev-node-env"));
11 |
12 | // --- group tasks ----
13 | gulp.task("clean", gulp.series("clean:scripts"));
14 | gulp.task("lint", gulp.series("lint:css"));
15 | gulp.task("scripts", gulp.series("scripts:build"));
16 | gulp.task("styles", gulp.series("lint:css", "compile:css"));
17 |
18 | // --- run tasks ----
19 | gulp.task("update", gulp.series("development", "styles", "scripts"));
20 | gulp.task(
21 | "build",
22 | gulp.series("production", "clean", "styles", "scripts", "imagemin")
23 | );
24 |
25 | // --- run application ----
26 | gulp.task("serve", gulp.series("build", "docker:up"));
27 | gulp.task("stop", gulp.series("docker:down"));
28 |
--------------------------------------------------------------------------------
/src/assets/js/components/notificationKeydown.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 | import findAll from "../functions/findAll.js";
3 | import addClass from "../functions/addClass.js";
4 | import removeClass from "../functions/removeClass.js";
5 |
6 | // ####################
7 | // ##### settings #####
8 | // ####################
9 | const class__isHidden = 'js-hidden';
10 | const class__isVisible = 'js-visible';
11 |
12 | // ###### script ######
13 | export default function notificationKeyDown(selector) {
14 | var targetElement = findAll(selector);
15 |
16 | document.addEventListener('keydown', function() {
17 | targetElement.forEach( function() {
18 | addClass(targetElement, class__isVisible);
19 | removeClass(targetElement, class__isHidden);
20 | });
21 | });
22 |
23 | document.addEventListener('keyup', function() {
24 | targetElement.forEach( function() {
25 | addClass(targetElement, class__isHidden);
26 | removeClass(targetElement, class__isVisible);
27 | });
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/assets/js/functions/handleTriggers.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 | import findAll from "./findAll.js";
3 | import toggleClass from "./toggleClass.js";
4 |
5 | // ####################
6 | // ##### settings #####
7 | // ####################
8 | const class__isActive = 'js-is-active';
9 |
10 | // ###### script ######
11 | export default function handleTriggers(selector, callback) {
12 | let targetElementsObject = findAll(selector);
13 |
14 | // convert the object into an array, so we can run forEach in IE on it
15 | let targetElements = Array.prototype.slice.call(targetElementsObject);
16 |
17 | targetElements.forEach(function(element) {
18 | element.addEventListener('click', function() {
19 | let elementTarget = findAll(this.getAttribute('data-target'));
20 | toggleClass(elementTarget, class__isActive);
21 |
22 | // check if a callback is defined
23 | if(typeof callback === "function") {
24 | callback();
25 | }
26 | event.preventDefault();
27 | });
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | // `api.file` - path to the file
3 | // `api.mode` - `mode` value of webpack, please read https://webpack.js.org/configuration/mode/
4 | // `api.webpackLoaderContext` - loader context for complex use cases
5 | // `api.env` - alias `api.mode` for compatibility with `postcss-cli`
6 | // `api.options` - the `postcssOptions` options
7 |
8 | if (/\.sss$/.test(api.file)) {
9 | return {
10 | // You can specify any options from https://postcss.org/api/#processoptions here
11 | parser: "sugarss",
12 | plugins: [
13 | // Plugins for PostCSS
14 | ["postcss-short", { prefix: "x" }],
15 | "postcss-preset-env",
16 | ],
17 | };
18 | }
19 |
20 | return {
21 | // You can specify any options from https://postcss.org/api/#processoptions here
22 | plugins: [
23 | // Plugins for PostCSS
24 | ["postcss-short", { prefix: "x" }],
25 | "postcss-preset-env",
26 | ],
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/_tasks/styles.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 | const sass = require("gulp-sass")(require('sass'));
5 | const gulpStylelint = require("gulp-stylelint");
6 | const autoprefixer = require("gulp-autoprefixer");
7 |
8 | /* ################# */
9 | /* ##### Tasks ##### */
10 | /* ################# */
11 | gulp.task("compile:css", function () {
12 | return gulp
13 | .src(config.assetSrc + "/scss/*.scss")
14 | .pipe(sass({ outputStyle: "compressed" }).on("error", sass.logError))
15 | .pipe(
16 | autoprefixer({
17 | browsers: ["last 2 versions", ">5%"],
18 | cascade: false,
19 | })
20 | )
21 | .pipe(gulp.dest(config.assetDist + "/css"));
22 | });
23 |
24 | // lint
25 | gulp.task("lint:css", function () {
26 | return gulp.src(config.assetSrc + "/scss/**/*.scss").pipe(
27 | gulpStylelint({
28 | fix: true,
29 | reporters: [{ formatter: "string", console: true }],
30 | })
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_modal.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $modal-header-background-color: transparent !default;
5 | $modal-header-color: var(--color) !default;
6 | $modal-header-border: 2px solid var(--brand-color) !default;
7 | $modal-content-background-color: var(--bg) !default;
8 | $modal-content-color: var(--color) !default;
9 |
10 | //---------------------
11 | // ###### Layout ######
12 | //---------------------
13 | .js .modal {
14 | transition: visibility $base-animation-speed ease-in-out;
15 | visibility: hidden;
16 | }
17 |
18 | .modal.js-is-active {
19 | position: fixed;
20 | top: 0;
21 | visibility: visible;
22 | z-index: 2;
23 | }
24 |
25 | .modal-overlay {
26 | @include align-centered($scale: 0, $position: fixed);
27 | }
28 |
29 | .modal-header {
30 | font-weight: 600;
31 | position: relative;
32 | }
33 |
34 | .modal-content {
35 | overflow-y: auto;
36 | }
37 |
38 | .modal-header__close {
39 | @include align-vertical;
40 | right: 0;
41 | }
42 |
--------------------------------------------------------------------------------
/__tests__/e2e/modal.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | //which modal to test for?
4 | const modalSelector = "#linksasmodal";
5 |
6 | test("modal", async ({ page }) => {
7 | // Go to http://localhost:8080/
8 | await page.goto("/");
9 |
10 | // find modal link
11 | await page.locator(`a[data-target="${modalSelector}"]`).click();
12 |
13 | // open modal
14 | expect(page.locator(`${modalSelector} .modal-overlay`)).toBeVisible;
15 |
16 | // click on uncritical element to wait until the animation is done
17 | await page.locator(`${modalSelector} .modal-header`).click();
18 |
19 | // take a screenshot
20 | test.slow(); // give time to fetch
21 | expect(await page.screenshot()).toMatchSnapshot("open.png", {
22 | threshold: 0.3
23 | });
24 |
25 | // click on close button to hide modal
26 | await page
27 | .locator(`button.js-modal-trigger[data-target="${modalSelector}"]`)
28 | .click();
29 |
30 | expect(page.locator(`${modalSelector} .modal-overlay`)).toBeHidden;
31 | });
32 |
--------------------------------------------------------------------------------
/__tests__/e2e/tabswitch.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | const menuSelector = "#header-nav";
4 |
5 | test("test", async ({ page, browserName }) => {
6 | // Go to http://localhost:8080/
7 | await page.goto("/");
8 |
9 | // open the mobile menu
10 | if (browserName === "webkit") {
11 | await page.locator(`button[data-target="${menuSelector}"]`).click();
12 | await expect(page.locator(menuSelector)).toBeVisible;
13 | }
14 |
15 | // click second tab
16 | await page.locator(".tablist .tablist__item:nth-child(2) a").click();
17 |
18 | // is the url updated
19 | await expect(page).toHaveURL("/#tab-2");
20 |
21 | // do the rtabs react in a correct manner?
22 | await expect(page.locator("#tab-1")).toBeHidden;
23 | await expect(page.locator("#tab-2")).toBeVisible;
24 |
25 | // close the mobile menu
26 | if (browserName === "webkit") {
27 | await page.locator(`button[data-target="${menuSelector}"]`).click();
28 | await expect(page.locator(menuSelector)).toBeHidden;
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Sascha Diercks
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_tasks/vm.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_config.json");
3 | const gulp = require("gulp");
4 | const exec = require("child_process").exec;
5 |
6 | /* ################# */
7 | /* ##### Tasks ##### */
8 | /* ################# */
9 | gulp.task("vagrant:up", cb => {
10 | exec("vagrant up", (err, stdout, stderr) => {
11 | console.log(stdout);
12 | console.log(stderr);
13 | cb(err);
14 | });
15 | });
16 | gulp.task("vagrant:halt", cb => {
17 | exec("vagrant halt", (err, stdout, stderr) => {
18 | console.log(stdout);
19 | console.log(stderr);
20 | cb(err);
21 | });
22 | });
23 | gulp.task("vagrant:reload", cb => {
24 | exec("vagrant reload", (err, stdout, stderr) => {
25 | console.log(stdout);
26 | console.log(stderr);
27 | cb(err);
28 | });
29 | });
30 |
31 | gulp.task("docker:up", cb => {
32 | exec("docker-compose up -d", (err, stdout, stderr) => {
33 | console.log(stdout);
34 | console.log(stderr);
35 | cb(err);
36 | });
37 | });
38 | gulp.task("docker:down", cb => {
39 | exec("docker-compose down", (err, stdout, stderr) => {
40 | console.log(stdout);
41 | console.log(stderr);
42 | cb(err);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* #### Setting #### */
2 | const config = require("./_tasks/_config.json");
3 | const path = require("path");
4 | const ReplaceHashInFileWebpackPlugin = require("replace-hash-in-file-webpack-plugin");
5 |
6 | /* ################# */
7 | /* ##### Tasks ##### */
8 | /* ################# */
9 | module.exports = [
10 | {
11 | name: "site",
12 | entry: {
13 | site: [`./${config.assetSrc}/js/site.js`]
14 | },
15 | output: {
16 | filename: "[name].js",
17 | path: path.resolve(__dirname, `${config.assetDist}/js`)
18 | }
19 | },
20 | {
21 | name: "serviceworker",
22 | entry: {
23 | serviceworker: [`./${config.src}/serviceworker.js`]
24 | },
25 | output: {
26 | filename: "[name].js",
27 | path: path.resolve(__dirname, `${config.dist}`)
28 | },
29 | plugins: [
30 | new ReplaceHashInFileWebpackPlugin([
31 | {
32 | dir: `${config.dist}`,
33 | files: ["serviceworker.js"],
34 | rules: [
35 | {
36 | search: "[contenthash]",
37 | replace: "[hash]"
38 | }
39 | ]
40 | }
41 | ])
42 | ]
43 | }
44 | ];
45 |
--------------------------------------------------------------------------------
/src/assets/js/site.js:
--------------------------------------------------------------------------------
1 | // ###### import ######
2 | import setJsAvailability from "./components/setJsAvailability.js";
3 | import notificationKeydown from "./components/notificationKeydown.js";
4 | import handleTabs from "./components/handleTabsLocalStorage.js";
5 |
6 | import stickyElement from "./functions/stickyElement.js";
7 | import fixScrollPos from "./functions/fixScrollPos.js";
8 | import handleTriggers from "./functions/handleTriggers.js";
9 |
10 | // ####################
11 | // ##### settings #####
12 | // ####################
13 |
14 | // ###### script ######
15 | // is the DOM ready for manipulation?
16 | document.addEventListener("DOMContentLoaded", function() {
17 | // --- Toggle JS Availability
18 | setJsAvailability("body");
19 |
20 | // handle tabs
21 | handleTabs(".js-tab-trigger", ".tabbed-content");
22 |
23 | // handle triggers
24 | handleTriggers(".js-flyout-trigger", fixScrollPos);
25 | handleTriggers(".js-collapse-trigger", false);
26 | handleTriggers(".js-modal-trigger", fixScrollPos);
27 |
28 | // -- make elements sticky
29 | stickyElement("#application-footer", "#content", "padding-bottom");
30 |
31 | // --- Show/hide notification
32 | notificationKeydown(".notification");
33 | });
34 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # Quelle: https://github.com/sapienza/vagrant-php-box
2 |
3 | # -*- mode: ruby -*-
4 | # vi: set ft=ruby :
5 |
6 | Vagrant.configure("2") do |config|
7 | # All Vagrant configuration is done here. The most common configuration
8 | # options are documented and commented below. For a complete reference,
9 | # please see the online documentation at vagrantup.com.
10 |
11 | # Every Vagrant virtual environment requires a box to build off of.
12 | config.vm.box = "ubuntu/trusty64"
13 | config.vm.post_up_message = "Box up and running (https://localhost:8080)"
14 |
15 | forward_port = ->(guest, host = guest) do
16 | config.vm.network :forwarded_port,
17 | guest: guest,
18 | host: host,
19 | auto_correct: true
20 | end
21 |
22 | # Sync between the web root of the VM and the 'sites' directory
23 | config.vm.synced_folder "./htdocs/", "/var/www/html"
24 |
25 | forward_port[1080] # mailcatcher
26 | forward_port[3306] # mysql
27 | forward_port[80, 8080] # nginx/apache
28 |
29 | config.vm.provision :puppet do |puppet|
30 | puppet.manifests_path = "src/manifests"
31 | puppet.manifest_file = "default.pp"
32 | end
33 |
34 | config.vm.network :private_network, ip: "33.33.33.10"
35 | end
36 |
--------------------------------------------------------------------------------
/__tests__/e2e/sidebar.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | const sidebarSelector = "#bookmarks";
4 | const collapseSelector = "#bookmarks details:first-of-type";
5 |
6 | test("Sidebar", async ({ page }) => {
7 | // Go to http://localhost:8080/
8 | await page.goto("/");
9 |
10 | // Sidebar
11 | await page
12 | .locator(`#application-header button[data-target="${sidebarSelector}"]`)
13 | .click();
14 |
15 | // Click on the sidebar to make sure it is stable
16 | await page.locator(`${sidebarSelector} .flyout-title`).click();
17 |
18 | // take a screenshot
19 | test.slow(); // give time to fetch
20 | expect(await page.screenshot()).toMatchSnapshot("sidebar.png", {
21 | maxDiffPixels: 10
22 | });
23 |
24 | // Open collapse
25 | await page.locator(`${collapseSelector}`).click();
26 | expect(page.locator(`${collapseSelector} .list-vertical`)).toBeVisible;
27 |
28 | // close collapse
29 | await page.locator(`${collapseSelector} summary`).click();
30 | expect(page.locator(`${collapseSelector} .list-vertical`)).toBeHidden;
31 |
32 | // close sidebar
33 | await page.locator(`${sidebarSelector} .flyout-close`).click();
34 | expect(page.locator(`${sidebarSelector}`)).toBeHidden;
35 | });
36 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_details-summary.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 |
3 | // ###### switches #####
4 |
5 | // ###### Settings #####
6 | $details-border: 1px solid var(--border-color-hover) !default;
7 | $summary-marker: '+' !default;
8 | $summary-marker-size: 1.188em !default; /*-- 19px / 16px --*/
9 | $summary-padding: math.div($base-spacing-size, 2) $summary-marker-size math.div($base-spacing-size, 2) 0 !default;
10 |
11 | //---------------------
12 | // ###### Layout ######
13 | //---------------------
14 | details {
15 | border-top: $details-border;
16 | border-bottom: $details-border;
17 |
18 | & + details {
19 | border-top-width: 0;
20 | }
21 | }
22 |
23 | summary {
24 | cursor: pointer;
25 | display: block;
26 | font-size: inherit;
27 | margin: 0;
28 | padding: $summary-padding;
29 | position: relative;
30 | opacity: 1;
31 |
32 | &::before {
33 | content: $summary-marker;
34 | position: absolute;
35 | right: $summary-marker-size;
36 | display: inline-block;
37 | transition: .3s transform linear;
38 | }
39 |
40 | &::-webkit-details-marker { display: none }
41 | }
42 |
43 | // -- animate icon
44 | details[open] {
45 | summary {
46 | &::before {
47 | transform: rotate(45deg);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/assets/scss/4-elements/_header.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $header-background-color: var(--bg) !default;
5 | $header-link-color: var(--color) !default;
6 | $header-link-active-bg: var(--brand-color) !default;
7 | $header-link-active-color: var(--color-light) !default;
8 |
9 | //---------------------
10 | // ###### Layout ######
11 | //---------------------
12 | header {
13 | background-color: $header-background-color;
14 | color: $header-link-color;
15 | box-shadow: $box-shadow-default;
16 | position: relative;
17 | width: 100%;
18 | z-index: 2;
19 |
20 | @include default {
21 | padding: $base-spacing-size * 0.75 $base-spacing-size;
22 | a {
23 | padding: $base-spacing-size * 0.75 $base-spacing-size;
24 | }
25 | }
26 |
27 | @include phone-only {
28 | display: grid;
29 | grid-template-columns: repeat(2, 1fr);
30 | padding: $base-spacing-size * 2 $base-spacing-size * 1.5;
31 | a {
32 | padding: $base-spacing-size $base-spacing-size * 2;
33 | }
34 | }
35 |
36 | &.js-sticky {
37 | top: 0;
38 | }
39 |
40 | .collapse {
41 | border-width: 0;
42 | }
43 |
44 | // overwrite collapse-styles
45 | .collapse-main {
46 | overflow: visible;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/htdocs/maskIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_tile.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $main-link-border-color: var(--border-color) !default;
5 | $main-link-background-color: var(--bg) !default;
6 | $main-link-background-image: linear-gradient(
7 | to top,
8 | var(--bg),
9 | var(--border-color)
10 | ) !default;
11 | $main-link-background-image-hover: linear-gradient(
12 | to bottom,
13 | var(--bg),
14 | var(--border-color)
15 | ) !default;
16 | $main-link-active-border-color: var(--border-color-hover) !default;
17 | $main-link-description-bg: var(--border-color) !default;
18 | $main-link-description-color: var(--color) !default;
19 | $main-link-active-description-bg: var(--brand-color) !default;
20 | $main-link-active-description-color: var(--color-light) !default;
21 |
22 | //---------------------
23 | // ###### Layout ######
24 | //---------------------
25 |
26 | .tile {
27 | display: block;
28 | opacity: 0.97;
29 | position: relative; // allow postioning of child-elements
30 | }
31 |
32 | // prevent whitespace on images
33 | .tile-image {
34 | display: inline-block;
35 | filter: var(--tile-filter);
36 | max-height: 100%;
37 | max-width: 100%;
38 | object-fit: contain;
39 | }
40 |
41 | // Clip text of Description if nessecary
42 | .tile-title {
43 | overflow: hidden;
44 | text-overflow: ellipsis;
45 | white-space: nowrap;
46 | }
47 |
--------------------------------------------------------------------------------
/htdocs/application.manifest.php:
--------------------------------------------------------------------------------
1 | IsFile() && $file != "'./' . $manifestUrl" && substr($file -> getFilename(), 0, 1) != ".") {
33 | // Replace spaces with %20 or it will break
34 | echo str_replace(' ', '%20', $file) . "\n";
35 | // Add this file's hash to the $hashes string
36 | $hashes .= md5_file($file);
37 | }
38 | }
39 | }
40 | }
41 | create_manifest(".");
42 |
43 | // Write the $hashes string
44 | echo "# Hash: " . md5($hashes) . "\n";
45 |
--------------------------------------------------------------------------------
/src/assets/scss/6-components/_flyout.scss:
--------------------------------------------------------------------------------
1 | // ###### switches #####
2 |
3 | // ###### Settings #####
4 | $flyout-background-color: var(--bg) !default;
5 | $flyout-box-shadow: var(--box-shadow-heavy) !default;
6 | $flyout-color: var(--color) !default;
7 | //---------------------
8 | // ###### Layout ######
9 | //---------------------
10 | .flyout {
11 | left: 0;
12 | position: fixed;
13 | top: 0;
14 | z-index: 3;
15 | }
16 |
17 | .flyout-backdrop {
18 | display: none;
19 | }
20 |
21 | .flyout-close {
22 | float: right;
23 | }
24 |
25 | .flyout-title {
26 | font-size: 1rem;
27 | font-weight: 600;
28 | margin-top: 0;
29 | padding-top: inherit;
30 | }
31 |
32 | .flyout-content {
33 | background-color: $flyout-background-color;
34 | box-shadow: $flyout-box-shadow;
35 | color: $flyout-color;
36 | height: 100vh;
37 | max-width: $bookmarks-max-width;
38 | overflow-y: auto;
39 | padding: $base-spacing-size $base-spacing-size * 1.5;
40 | position: fixed;
41 | right: 0;
42 | top: 0;
43 | transform: translateX(100%);
44 | transition: all $base-animation-speed ease;
45 | width: 100%;
46 |
47 | ul {
48 | @include ul-reset;
49 | }
50 |
51 | a {
52 | font-size: 0.875rem; /* 14px / 16px */
53 | padding: 2px 0;
54 | }
55 | }
56 |
57 | // -- show flyout
58 | .js-is-active {
59 | .flyout-content {
60 | transform: translateX(0);
61 | }
62 |
63 | .flyout-backdrop {
64 | display: block;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/htdocs/serviceworker.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){e.exports=n(1)},function(e,t){var n=["./assets/css/site.css","./assets/js/site.js","./assets/wallpaper/default.jpg"];self.addEventListener("install",function(e){e.waitUntil(caches.open("4dd644c592e0090f2ea3").then(function(e){return e.addAll(n)}).then(function(){return self.skipWaiting()}))}),self.addEventListener("fetch",function(e){e.respondWith(caches.match(e.request).then(function(t){return t||("only-if-cached"!==e.request.cache||"same-origin"===e.request.mode?fetch(e.request):void 0)}))}),self.addEventListener("activate",function(e){e.waitUntil(self.clients.claim())})}]);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "browserstartpage",
3 | "version": "1.0.0",
4 | "description": "Crossbrowser Speeddial and bookmarks",
5 | "main": "gulpfile.js",
6 | "devDependencies": {
7 | "@playwright/test": "^1.22.2",
8 | "del": "^3.0.0",
9 | "gulp": "^4.0.2",
10 | "gulp-autoprefixer": "^3.1.1",
11 | "gulp-if": "^3.0.0",
12 | "gulp-imagemin": "^4.1.0",
13 | "gulp-sass": "^5.1.0",
14 | "gulp-sourcemaps": "^1.9.1",
15 | "gulp-stylelint": "^8.0.0",
16 | "gulp-terser": "^1.2.0",
17 | "imagemin-gifsicle": "^7.0.0",
18 | "imagemin-jpegtran": "^7.0.0",
19 | "imagemin-optipng": "^8.0.0",
20 | "imagemin-svgo": "^10.0.1",
21 | "prettier-stylelint": "^0.4.2",
22 | "replace-hash-in-file-webpack-plugin": "^1.0.8",
23 | "require-dir": "^1.2.0",
24 | "sass": "^1.49.7",
25 | "stylelint": "^9.10.1",
26 | "stylelint-order": "^2.0.0",
27 | "webpack": "^4.39.3",
28 | "webpack-cli": "^4.6.0",
29 | "webpack-stream": "^4.0.3"
30 | },
31 | "scripts": {
32 | "test": "npx playwright test",
33 | "test:record": "echo 'Test name' && read testname && npx playwright $testname localhost:8080",
34 | "test:report": "npx playwright show-report",
35 | "test:updateSnapshots": "npx playwright test --update-snapshots"
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "git+https://github.com/saschadiercks/browserStartpage.git"
40 | },
41 | "author": "Sascha Diercks",
42 | "license": "ISC",
43 | "bugs": {
44 | "url": "https://github.com/saschadiercks/browserStartpage/issues"
45 | },
46 | "homepage": "https://github.com/saschadiercks/browserStartpage#readme"
47 | }
48 |
--------------------------------------------------------------------------------
/src/assets/scss/site.scss:
--------------------------------------------------------------------------------
1 | //----your overwrites: place files with variable overwrites here
2 | @import "_your-themes/variables";
3 |
4 | // ---- Main Theme
5 | // 1 - Globals
6 | @import "1-settings/colors";
7 | @import "1-settings/globals";
8 |
9 | // 2 - Tools
10 | @import "2-tools/clearfix";
11 | @import "2-tools/responsive";
12 | @import "2-tools/ul-reset";
13 | @import "2-tools/align-vertical";
14 | @import "2-tools/align-centered";
15 | @import "2-tools/content-visibility";
16 |
17 | // 3 - Generic
18 | @import "3-generic/box-sizing";
19 | @import "3-generic/font-smoothing";
20 | @import "3-generic/page";
21 | @import "3-generic/reset";
22 |
23 | // 4 - Elements
24 | @import "4-elements/buttons";
25 | @import "4-elements/details-summary";
26 | @import "4-elements/header";
27 | @import "4-elements/links";
28 | @import "4-elements/main";
29 | @import "4-elements/footer";
30 | @import "4-elements/navigation";
31 |
32 | // 5 - Objects
33 | @import "5-objects/list-tiles";
34 | @import "5-objects/list-vertical";
35 | @import "5-objects/list-horizontal";
36 | @import "5-objects/tabbed-content";
37 |
38 | // 6 - Components
39 | @import "6-components/backdrop";
40 | @import "6-components/flyout";
41 | @import "6-components/modal";
42 | @import "6-components/modal-list";
43 | @import "6-components/modal-qr";
44 | @import "6-components/notification";
45 | @import "6-components/overlay";
46 | @import "6-components/tile";
47 | @import "6-components/wallpaper";
48 |
49 | // 7 - Utilities
50 | @import "7-utilities/sticky";
51 | @import "7-utilities/js-utilities";
52 | @import "7-utilities/theme-default";
53 |
54 | // shame
55 | @import "shame";
56 |
57 | // ---- Apply your own deeper layout changes
58 | @import "_your-themes/your-theme";
59 |
--------------------------------------------------------------------------------
/src/serviceworker.js:
--------------------------------------------------------------------------------
1 | var CACHE_NAME = "[contenthash]";
2 | var REQUIRED_FILES = [
3 | "./assets/css/site.css",
4 | "./assets/js/site.js",
5 | "./assets/wallpaper/default.jpg"
6 | ];
7 |
8 | self.addEventListener("install", function(event) {
9 | // Perform install step: loading each required file into cache
10 | event.waitUntil(
11 | caches
12 | .open(CACHE_NAME)
13 | .then(function(cache) {
14 | // Add all offline dependencies to the cache
15 | return cache.addAll(REQUIRED_FILES);
16 | })
17 | .then(function() {
18 | // At this point everything has been cached
19 | return self.skipWaiting();
20 | })
21 | );
22 | });
23 |
24 | self.addEventListener("fetch", function(event) {
25 | event.respondWith(
26 | caches.match(event.request).then(function(response) {
27 | // Cache hit - return the response from the cached version
28 | if (response) {
29 | return response;
30 | }
31 |
32 | // DevTools opening will trigger these o-i-c requests, which this SW can't handle.
33 | // There's probaly more going on here, but I'd rather just ignore this problem. :)
34 | // https://github.com/paulirish/caltrainschedule.io/issues/49
35 | if (
36 | event.request.cache === "only-if-cached" &&
37 | event.request.mode !== "same-origin"
38 | )
39 | return;
40 |
41 | // Not in cache - return the result from the live server
42 | // `fetch` is essentially a "fallback"
43 | return fetch(event.request);
44 | })
45 | );
46 | });
47 |
48 | self.addEventListener("activate", function(event) {
49 | // Calling claim() to force a "controllerchange" event on navigator.serviceWorker
50 | event.waitUntil(self.clients.claim());
51 | });
52 |
--------------------------------------------------------------------------------
/htdocs/config/config.php:
--------------------------------------------------------------------------------
1 | 0) {
20 | handleTabs(document.location.hash);
21 |
22 | // overwrite scroll-position of hash
23 | window.scrollTo(0, 0);
24 | } else {
25 |
26 | // -- check local storage
27 | var value__localStorage = localStorage.getItem(key__localStorage);
28 | if (value__localStorage !== null) {
29 | handleTabs(value__localStorage);
30 | } else {
31 | // wihtout localStorage we'll just show the first tab
32 | document.querySelector(selectorTrigger).classList.add(class__isActive);
33 | document.querySelector(selectorContent).classList.add(class__isActive);
34 | }
35 | }
36 |
37 | // -- listen for click on triggers and show/hide content
38 | tabTrigger.forEach(function (element) {
39 | element.addEventListener('click', function (event) {
40 | event.preventDefault();
41 |
42 | var triggerTarget = this.getAttribute('data-target');
43 |
44 | // now handle tabs
45 | handleTabs(triggerTarget);
46 |
47 | // save to local storage
48 | saveToLocalStorage(triggerTarget);
49 |
50 | // update hash in URL to allow easy copy/paste
51 | history.pushState(null, null, triggerTarget);
52 | });
53 | });
54 |
55 | // -- open tab and mark button as active
56 | function handleTabs(selector) {
57 |
58 | // hide all tabs and remove active class from buttons
59 | removeClass(tabTrigger, class__isActive);
60 | removeClass(tabContent, class__isActive);
61 |
62 | // open saved tab
63 | addClass(findAll(selector), class__isActive);
64 |
65 | // add active class to button
66 | addClass(findAll('a[data-target="' + selector + '"]'), class__isActive);
67 | }
68 |
69 | // -- save to localStorage
70 | function saveToLocalStorage(selector) {
71 | // save to local storage, when key is not pressed
72 | if (event.altKey !== true) {
73 | localStorage.setItem(key__localStorage, selector);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/manifests/default.pp:
--------------------------------------------------------------------------------
1 | # Puppet configurations
2 |
3 | Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }
4 |
5 | class base {
6 |
7 | ## Update apt-get ##
8 | exec { 'apt-get update':
9 | command => '/usr/bin/apt-get update'
10 | }
11 | }
12 |
13 | class http {
14 |
15 | define apache::loadmodule () {
16 | exec { "/usr/sbin/a2enmod $name" :
17 | unless => "/bin/readlink -e /etc/apache2/mods-enabled/${name}.load",
18 | notify => Service[apache2]
19 | }
20 | }
21 |
22 | apache::loadmodule{"rewrite":}
23 |
24 | package { "apache2":
25 | ensure => present,
26 | }
27 |
28 | service { "apache2":
29 | ensure => running,
30 | require => Package["apache2"],
31 | }
32 | }
33 |
34 | class php{
35 |
36 | package { "php5":
37 | ensure => present,
38 | }
39 |
40 | package { "php5-cli":
41 | ensure => present,
42 | }
43 |
44 | package { "php5-xdebug":
45 | ensure => present,
46 | }
47 |
48 | package { "php5-mysql":
49 | ensure => present,
50 | }
51 |
52 | package { "php5-imagick":
53 | ensure => present,
54 | }
55 |
56 | package { "php5-mcrypt":
57 | ensure => present,
58 | }
59 |
60 | package { "php-pear":
61 | ensure => present,
62 | }
63 |
64 | package { "php5-dev":
65 | ensure => present,
66 | }
67 |
68 | package { "php5-curl":
69 | ensure => present,
70 | }
71 |
72 | package { "php5-sqlite":
73 | ensure => present,
74 | }
75 |
76 | package { "libapache2-mod-php5":
77 | ensure => present,
78 | }
79 |
80 | }
81 |
82 | class mysql{
83 |
84 | package { "mysql-server":
85 | ensure => present,
86 | }
87 |
88 | service { "mysql":
89 | ensure => running,
90 | require => Package["mysql-server"],
91 | notify => Exec["set-mysql-password"],
92 | }
93 |
94 | exec { "set-mysql-password":
95 | command => "mysqladmin -u root password root",
96 | }
97 | }
98 |
99 | class phpmyadmin{
100 |
101 | package
102 | {
103 | "phpmyadmin":
104 | ensure => present,
105 | require => [
106 | Exec['apt-get update'],
107 | Package["php5", "php5-mysql", "apache2"],
108 | ]
109 | }
110 |
111 | file
112 | {
113 | "/etc/apache2/conf-available/phpmyadmin.conf":
114 | ensure => link,
115 | target => "/etc/phpmyadmin/apache.conf",
116 | require => Package['apache2'],
117 | notify => Exec["load_phpmyadmin_conf"],
118 | }
119 |
120 | exec { "load_phpmyadmin_conf":
121 | command => "/usr/sbin/a2enconf phpmyadmin",
122 | notify => Exec["reload_apache"],
123 | }
124 | exec { "reload_apache":
125 | command => "/etc/init.d/apache2 reload",
126 | }
127 | }
128 |
129 | include base
130 | include http
131 | include php
132 | include mysql
133 | include phpmyadmin
134 |
--------------------------------------------------------------------------------
/htdocs/assets/js/site.js:
--------------------------------------------------------------------------------
1 | !function(t){var e={};function n(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(o,r,function(e){return t[e]}.bind(null,r));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){t.exports=n(1)},function(t,e,n){"use strict";function o(t){var e=document.querySelectorAll(t);return Array.prototype.slice.call(e)}function r(t,e){t.forEach(function(t){t.classList.add(e)})}function c(t,e){t.forEach(function(t){t.classList.remove(e)})}n.r(e);const i="js",a="no-js";const u="js-hidden",s="js-visible";const l="sdi-is-active",f="lastTabID";function d(t){return document.querySelector(t)}const p="js-sticky";const y=document.querySelector("body"),v="sdi-is-fixed";var g;function b(t){var e=window.pageYOffset;y.classList.contains(v)?(y.classList.remove(v),y.style.top="",window.scrollTo(0,g)):(y.classList.add(v),y.style.top="-"+e+"px",g=e)}const m="js-is-active";function h(t,e){let n=o(t);Array.prototype.slice.call(n).forEach(function(t){t.addEventListener("click",function(){let t=o(this.getAttribute("data-target"));var n;n=m,t.forEach(function(t){t.classList.toggle(n)}),"function"==typeof e&&e(),event.preventDefault()})})}document.addEventListener("DOMContentLoaded",function(){var t,e,n,y,v,g;(t=o(t="body")).forEach(function(){r(t,i),c(t,a)}),function(t,e){var n=o(t),i=o(e);if(document.location.hash&&o('a[data-target="'+document.location.hash+'"]').length>0)u(document.location.hash),window.scrollTo(0,0);else{var a=localStorage.getItem(f);null!==a?u(a):(document.querySelector(t).classList.add(l),document.querySelector(e).classList.add(l))}function u(t){c(n,l),c(i,l),r(o(t),l),r(o('a[data-target="'+t+'"]'),l)}function s(t){!0!==event.altKey&&localStorage.setItem(f,t)}n.forEach(function(t){t.addEventListener("click",function(t){t.preventDefault();var e=this.getAttribute("data-target");u(e),s(e),history.pushState(null,null,e)})})}(".js-tab-trigger",".tabbed-content"),h(".js-flyout-trigger",b),h(".js-modal-trigger",b),n="#content",y="padding-bottom",v=o(e="#application-footer"),g=d(e).clientHeight+"px",r(v,p),d(n).style.setProperty(y,g),function(t){var e=o(t);document.addEventListener("keydown",function(){e.forEach(function(){r(e,s),c(e,u)})}),document.addEventListener("keyup",function(){e.forEach(function(){r(e,u),c(e,s)})})}(".notification")})}]);
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from '@playwright/test';
2 | import { devices } from '@playwright/test';
3 |
4 | /**
5 | * Read environment variables from file.
6 | * https://github.com/motdotla/dotenv
7 | */
8 | // require('dotenv').config();
9 |
10 | /**
11 | * See https://playwright.dev/docs/test-configuration.
12 | */
13 | const config: PlaywrightTestConfig = {
14 | testDir: './__tests__',
15 | /* Maximum time one test can run for. */
16 | timeout: 30 * 1000,
17 | expect: {
18 | /**
19 | * Maximum time expect() should wait for the condition to be met.
20 | * For example in `await expect(locator).toHaveText();`
21 | */
22 | timeout: 5000
23 | },
24 | /* Run tests in files in parallel */
25 | fullyParallel: true,
26 | /* Fail the build on CI if you accidentally left test.only in the source code. */
27 | forbidOnly: !!process.env.CI,
28 | /* Retry on CI only */
29 | retries: process.env.CI ? 2 : 0,
30 | /* Opt out of parallel tests on CI. */
31 | workers: process.env.CI ? 1 : undefined,
32 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
33 | reporter: 'html',
34 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
35 | use: {
36 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
37 | actionTimeout: 0,
38 | /* Base URL to use in actions like `await page.goto('/')`. */
39 | baseURL: 'http://localhost:8080',
40 |
41 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
42 | trace: 'on-first-retry',
43 | },
44 |
45 | /* Configure projects for major browsers */
46 | projects: [
47 | {
48 | name: 'chromium',
49 | use: {
50 | ...devices['Desktop Chrome'],
51 | },
52 | },
53 |
54 | {
55 | name: 'firefox',
56 | use: {
57 | ...devices['Desktop Firefox'],
58 | },
59 | },
60 |
61 | // {
62 | // name: 'webkit',
63 | // use: {
64 | // ...devices['Desktop Safari'],
65 | // },
66 | // },
67 |
68 | /* Test against mobile viewports. */
69 | // {
70 | // name: 'Mobile Chrome',
71 | // use: {
72 | // ...devices['Pixel 5'],
73 | // },
74 | // },
75 | {
76 | name: 'Mobile Safari',
77 | use: {
78 | ...devices['iPhone 12'],
79 | },
80 | },
81 |
82 | /* Test against branded browsers. */
83 | // {
84 | // name: 'Microsoft Edge',
85 | // use: {
86 | // channel: 'msedge',
87 | // },
88 | // },
89 | // {
90 | // name: 'Google Chrome',
91 | // use: {
92 | // channel: 'chrome',
93 | // },
94 | // },
95 | ],
96 |
97 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */
98 | outputDir: '__test-results__/',
99 |
100 | /* Run your local dev server before starting the tests */
101 | // webServer: {
102 | // command: 'npm run start',
103 | // port: 3000,
104 | // },
105 | };
106 |
107 | export default config;
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # browserStartpage
2 |
3 | Are you switching browsers often?
4 | Are you tired, that every browser uses it's own speeddial and you can't import that in your new browser?
5 | The solution: host your own speeddial with this repo!
6 | This is especially useful if you need/want to share a of bunch links with friends or colleagues.
7 |
8 | ## How does it work?
9 |
10 | Just place the folder `/htdocs` on your own webserver. Make sure it supports _php_ - this is the only requirement.
11 |
12 | ### Want a demo?
13 |
14 | [https://demo.saschadiercks.de/startpage/](https://demo.saschadiercks.de/startpage/)
15 |
16 | Read more about it here (in german): [https://saschadiercks.de/projekte/browserstartpage/](https://saschadiercks.de/projekte/browserstartpage/)
17 |
18 | 
19 |
20 | ### Setup your own links
21 |
22 | The browserStartpage comes with a default list of links, to show you how it works. It shows up with a list of popular browsers and some development-ressources. You change that. Just head over to `/htdocs/data/data.json` and play with that file. You can edit the tabs and links to your own liking. Just play with it - it's quite self explanatory. All you need to do is to create images for your links and place them on your server too. Usually here `/htdocs/assets/thumbnails`.
23 |
24 | You can setup the usage of serviceworkers or define your own wallpaper in the json (on the top of the file).
25 |
26 | ## New
27 |
28 | Add a hash to the url to open tabs via direct call like so: `yourUrl#tab-1`
29 | You can just click on the desired tab and copy the url.
30 |
31 | ## Features
32 |
33 | - call tabs via hash
34 | - easy configurable Speeddial via json
35 | - easily add bookmarks via json
36 | - only requires php on your server
37 | - uses vanillaJS
38 | - uses apllicationCache to minimize traffic (it even works offline, after first visit)
39 | - uses localStorage to store last opened tab
40 |
41 | ### Planned Features
42 |
43 | - allow theming (see Hints & Tips)
44 | - allow onsite-editing so you don't have to fiddle with the json-file
45 | - allow static export of content to sync via Dropbox, iCloud or wathever
46 |
47 | ## Further insights (want to help building this?)
48 |
49 | - `/src/manifests` Docker is used as a local development-environment
50 | - `/src/scss` the development files to build the CSS (via gulp)
51 | - `/src/js` the development JS to compile the JS (via gulp)
52 | - `/src/data` dummy-datafile. Use `/htdocs/data/data.json` for local development
53 | - `/htdocs/startpage.manifest.php` automatic generation of application cache
54 | - `/htdocs/index.php`the speeddial itself
55 | - `/htdocs/assets/css` compiled css-files (uesd live)
56 | - `/htdocs/assets/js` compiled js-files (uesd live)
57 | - `/htdocs/assets/thumbnails` store your link-images here
58 |
59 | ### Usage of docker (preferred)
60 |
61 | 1. install docker on your machine (https://docs.docker.com/get-docker/)
62 | 2. head to the local repository and run `docker-compose up`
63 | 3. Wait a while until all components are loaded an the box is running. (The first start can take a while)
64 | 4. visit (http://127.0.0.1:8080/)
65 |
66 | ### Usage of Vagrant
67 |
68 | 1. install vagrant on your machine (https://www.vagrantup.com/)
69 | 2. install Virtualbox (https://www.virtualbox.org/wiki/Downloads)
70 | 3. head to your local repository an enter `vagrant up`
71 | 4. Wait a while until all components are loaded an the box is running. (The first start can take a while)
72 | 5. visit (http://127.0.0.1:8080/)
73 |
74 | ### Usage of gulp
75 |
76 | 1. Make sure, you have node.js installed on your computer (https://nodejs.org/en/)
77 | 2. run `npm install gulp-cli -g` to install gulp
78 | 3. run `npm install` to install gulp in your project
79 | 4. use `gulp build` to compile the css and minify Javascript for production (without sourcemaps) and imagemin
80 | 5. use `gulp update` to compile the css and Javascript for development
81 | 6. use `gulp serve` start the server for local development (localhost:8080)
82 | 7. use `gulp stop` stop the server
83 | 8. use `gulp reboot` restart the server and build assets
84 |
85 | Always run `gulp build`before deploying assets
86 |
87 | ### Hints & Tips
88 |
89 | - Change the Wallpaper by changing the value of variable `wallpaper` in `/data/data.json` (at the top of the document)
90 | - Do you want every link to be opened in a new tab? Change the value of `linktarget` in /data/data.json to a desired value. e.g. `_blank`
91 | - if you want to change the look of the page, you can find more information in `src/scss/7-utilities/your-theme.scss`
92 |
--------------------------------------------------------------------------------
/src/assets/scss/7-utilities/_theme-default.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 |
3 | // ---- Elements ----
4 | .modal-overlay {
5 | @include align-centered($scale: 0, $position: fixed);
6 | background-color: $modal-content-background-color;
7 | border-radius: $modal-border-radius;
8 | box-shadow: $box-shadow-heavy;
9 | color: $modal-content-color;
10 | height: $modal-content-height;
11 | max-height: $modal-content-max-height;
12 | max-width: $modal-content-max-width;
13 | transition: transform $base-animation-speed ease-in-out;
14 | width: $modal-content-width;
15 | }
16 |
17 | .js-is-active .modal-overlay {
18 | @include align-centered($scale: 1, $position: fixed);
19 | }
20 |
21 | .modal-header {
22 | background-color: $modal-header-background-color;
23 | border-bottom: $modal-header-border;
24 | color: $modal-header-color;
25 | margin-bottom: $base-spacing-size-px;
26 | padding: $base-spacing-size-px * 2;
27 | }
28 |
29 | .modal-content {
30 | padding: $base-spacing-size-px;
31 | }
32 |
33 | .overlay {
34 | height: 100vh;
35 | width: 100%;
36 | }
37 |
38 | .overlay-content {
39 | background-color: $overlay-background-color;
40 | box-shadow: $box-shadow-heavy;
41 | color: $overlay-color;
42 | }
43 |
44 | .js-hidden {
45 | .backdrop {
46 | height: 0;
47 | opacity: 0;
48 | }
49 |
50 | .overlay-content {
51 | transform: translateX(100%);
52 | }
53 | }
54 |
55 | .js-visible {
56 | .backdrop {
57 | opacity: 1;
58 | }
59 |
60 | .overlay-content {
61 | transform: translateX(0);
62 | }
63 | }
64 |
65 | // ---- Content ----
66 | // fallback for older browsers
67 | .tile-container {
68 | display: flex;
69 | height: 100px;
70 | padding: 0;
71 | position: relative;
72 | width: 25%;
73 | }
74 |
75 | @media (min-width: 768px) {
76 | .tile-container {
77 | height: 156px;
78 | }
79 | }
80 |
81 | // modern browsers
82 | @supports (display: grid) {
83 | .tile-container {
84 | width: auto;
85 | }
86 | }
87 |
88 | .tile {
89 | background-clip: padding-box;
90 | background-image: $main-link-background-image;
91 | border: 1px solid $main-link-border-color;
92 | border-radius: 2px;
93 | box-shadow: $box-shadow-default;
94 | display: flex;
95 | font-size: 0.75rem;
96 | position: relative;
97 | text-align: center;
98 | text-decoration: none;
99 | width: 100%;
100 |
101 | &:hover {
102 | background-image: $main-link-background-image-hover;
103 | border-color: $main-link-active-border-color;
104 | z-index: 2;
105 | }
106 | }
107 |
108 | .tile-title {
109 | background-color: $main-link-description-bg;
110 | bottom: 0;
111 | color: $main-link-description-color;
112 | display: block;
113 | left: 0;
114 | padding: math.div($base-spacing-size, 2);
115 | position: absolute;
116 | right: 0;
117 | }
118 |
119 | .tile__button {
120 | color: $tile-button-color;
121 | left: 1px;
122 | padding: $tile-button-padding;
123 | position: absolute;
124 | top: 1px;
125 | z-index: 2;
126 | }
127 |
128 | // center images when parent-item has display: flex;
129 | .tile-image {
130 | margin: auto;
131 | }
132 |
133 | @media (max-width: 767px) {
134 | .tile-image {
135 | max-height: 50%;
136 | max-width: 50%;
137 | }
138 | }
139 |
140 | // ---- Notification ----
141 | .notification {
142 | background-color: $notification-background-color;
143 | bottom: $base-spacing-size * 3;
144 | box-shadow: $box-shadow-heavy;
145 | color: $notification-color;
146 | padding: $base-spacing-size;
147 | position: fixed;
148 | right: 0;
149 | z-index: 99;
150 | }
151 |
152 | // -- navigation
153 | nav {
154 | li {
155 | display: inline-block;
156 | }
157 |
158 | a {
159 | color: $header-link-color;
160 |
161 | &:hover,
162 | &.sdi-is-active {
163 | background-color: $header-link-active-bg;
164 | border-radius: 2px;
165 | color: $header-link-active-color;
166 | }
167 | }
168 | }
169 |
170 | // ---- fx when overlays are visible ----
171 | .js-fx {
172 | header,
173 | main,
174 | footer {
175 | filter: blur($background-blur-setting);
176 | }
177 | }
178 |
179 | // ---- Responsive ----
180 | // Mobile view
181 | @include phone-only {
182 | button {
183 | padding: $base-spacing-size $base-spacing-size * 2;
184 | }
185 |
186 | .overlay-content {
187 | padding: $base-spacing-size * 2 $base-spacing-size * 1.5;
188 | }
189 |
190 | .overlay-title {
191 | padding-bottom: $base-spacing-size;
192 | padding-top: $base-spacing-size;
193 | }
194 |
195 | #bookmarks-toggle {
196 | justify-self: end;
197 | }
198 |
199 | #header-nav-toggle {
200 | justify-self: start;
201 | }
202 |
203 | #header-nav {
204 | grid-column: 1 / span 2;
205 | overflow: hidden;
206 | transition: max-height 0.5s ease;
207 |
208 | &.js-opened {
209 | max-height: 100vh;
210 | }
211 |
212 | li {
213 | display: block;
214 | }
215 | }
216 | }
217 |
218 | // Default View
219 | @include default {
220 | button {
221 | padding: $base-spacing-size * 0.5 $base-spacing-size;
222 | }
223 |
224 | .overlay-content {
225 | padding: $base-spacing-size * 0.75 $base-spacing-size;
226 | }
227 |
228 | #bookmarks-toggle {
229 | float: right;
230 | }
231 |
232 | #application-header {
233 | .collapse-main {
234 | max-height: none;
235 | }
236 | }
237 |
238 | .overlay-title {
239 | padding-bottom: $base-spacing-size * 0.75;
240 | padding-top: $base-spacing-size * 0.75;
241 | }
242 |
243 | .tile-title {
244 | opacity: 0;
245 | transition: opacity $base-animation-speed;
246 | }
247 |
248 | .tile:hover {
249 | .tile-title {
250 | opacity: 1;
251 | }
252 | }
253 |
254 | button[data-target*="nav"] {
255 | display: none;
256 | }
257 | }
258 |
259 | // ---- Fallbacks / special cases ----
260 | // $no-js-separator-color: var(--border-color) !default;
261 | // separation of content if JS is disabled / not loaded
262 | // body.no-js main nav:not(:last-child) {
263 | // border-bottom: 1px dotted $no-js-separator-color;
264 | // margin-bottom: $base-spacing-size*2;
265 | // padding-bottom: $base-spacing-size*2;
266 | // }
267 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["stylelint-order"],
3 | "rules": {
4 | "order/properties-alphabetical-order": true,
5 |
6 | "color-no-invalid-hex": true,
7 | "font-family-no-duplicate-names": true,
8 | "font-family-no-missing-generic-family-keyword": true,
9 | "function-calc-no-invalid": true,
10 | "function-calc-no-unspaced-operator": true,
11 | "function-linear-gradient-no-nonstandard-direction": true,
12 | "string-no-newline": true,
13 | "unit-no-unknown": true,
14 | "property-no-unknown": null,
15 | "keyframe-declaration-no-important": true,
16 | "declaration-block-no-duplicate-properties": [
17 | true,
18 | { "ignore": ["consecutive-duplicates-with-different-values"] }
19 | ],
20 | "declaration-block-no-shorthand-property-overrides": true,
21 | "block-no-empty": true,
22 | "selector-pseudo-class-no-unknown": true,
23 | "selector-pseudo-element-no-unknown": true,
24 | "selector-type-no-unknown": true,
25 | "at-rule-no-unknown": [
26 | true,
27 | { "ignoreAtRules": ["mixin", "each", "include", "if", "extend", "else", "use"] }
28 | ],
29 | "comment-no-empty": true,
30 | "no-descending-specificity": true,
31 | "no-duplicate-at-import-rules": true,
32 | "no-duplicate-selectors": true,
33 | "no-empty-source": true,
34 | "no-extra-semicolons": true,
35 | "no-invalid-double-slash-comments": true,
36 |
37 | "color-named": "never",
38 | "color-no-hex": null,
39 | "function-blacklist": [""],
40 | "function-url-no-scheme-relative": true,
41 | "function-url-scheme-blacklist": ["ftp", "http", "file", "data"],
42 | "function-url-scheme-whitelist": ["https"],
43 | "keyframes-name-pattern": null,
44 | "number-max-precision": 3,
45 | "time-min-milliseconds": 200,
46 | "unit-blacklist": ["in", "pc"],
47 | "shorthand-property-no-redundant-values": true,
48 | "value-no-vendor-prefix": [
49 | true,
50 | { "ignoreValues": ["font-smoothing", "text-size-adjust"] }
51 | ],
52 | "custom-property-pattern": null,
53 | "property-blacklist": null,
54 | "property-no-vendor-prefix": [
55 | true,
56 | { "ignoreProperties": ["text-size-adjust"] }
57 | ],
58 | "property-whitelist": null,
59 | "declaration-block-no-redundant-longhand-properties": [
60 | true,
61 | { "ignoreShorthands": ["grid-row"] }
62 | ],
63 | "declaration-no-important": null,
64 | "declaration-property-unit-blacklist": null,
65 | "declaration-property-unit-whitelist": null,
66 | "declaration-property-value-blacklist": null,
67 | "declaration-property-value-whitelist": null,
68 | "declaration-block-single-line-max-declarations": 1,
69 | "selector-attribute-operator-blacklist": null,
70 | "selector-attribute-operator-whitelist": null,
71 | "selector-class-pattern": null,
72 | "selector-combinator-blacklist": [">"],
73 | "selector-combinator-whitelist": null,
74 | "selector-id-pattern": null,
75 | "selector-max-attribute": 1,
76 | "selector-max-class": 3,
77 | "selector-max-combinators": 1,
78 | "selector-max-compound-selectors": 2,
79 | "selector-max-empty-lines": 0,
80 | "selector-max-id": 1,
81 | "selector-max-pseudo-class": 2,
82 | "selector-max-specificity": "1,2,1",
83 | "selector-max-type": 2,
84 | "selector-max-universal": 1,
85 | "selector-nested-pattern": null,
86 | "selector-no-qualifying-type": [true, { "ignore": ["attribute", "class"] }],
87 | "selector-no-vendor-prefix": true,
88 | "selector-pseudo-class-blacklist": null,
89 | "selector-pseudo-class-whitelist": null,
90 | "selector-pseudo-element-blacklist": null,
91 | "selector-pseudo-element-whitelist": null,
92 | "media-feature-name-blacklist": null,
93 | "media-feature-name-no-vendor-prefix": true,
94 | "media-feature-name-value-whitelist": null,
95 | "media-feature-name-whitelist": [
96 | "min-width",
97 | "max-width",
98 | "orientation",
99 | "prefers-color-scheme"
100 | ],
101 | "custom-media-pattern": null,
102 | "at-rule-blacklist": null,
103 | "at-rule-no-vendor-prefix": true,
104 | "at-rule-whitelist": null,
105 | "comment-word-blacklist": null,
106 | "max-nesting-depth": 3,
107 |
108 | "color-hex-case": "lower",
109 | "color-hex-length": "short",
110 | "font-family-name-quotes": "always-where-recommended",
111 | "font-weight-notation": "named-where-possible",
112 | "function-comma-newline-after": "never-multi-line",
113 | "function-comma-newline-before": "never-multi-line",
114 | "function-comma-space-after": "always-single-line",
115 | "function-comma-space-before": "never",
116 | "function-max-empty-lines": 0,
117 | "function-name-case": "lower",
118 | "function-parentheses-newline-inside": "never-multi-line",
119 | "function-parentheses-space-inside": "never",
120 | "function-whitespace-after": "always",
121 | "function-url-quotes": "always",
122 | "number-leading-zero": "never",
123 | "number-no-trailing-zeros": true,
124 | "string-quotes": "double",
125 | "length-zero-no-unit": true,
126 | "unit-case": "lower",
127 | "value-keyword-case": "lower",
128 | "value-list-comma-newline-after": "always-multi-line",
129 | "value-list-comma-newline-before": null,
130 | "value-list-comma-space-after": "always-single-line",
131 | "value-list-comma-space-before": "never",
132 | "value-list-max-empty-lines": 0,
133 | "custom-property-empty-line-before": [
134 | "always",
135 | {
136 | "except": ["after-custom-property", "first-nested"],
137 | "ignore": ["after-comment", "inside-single-line-block"]
138 | }
139 | ],
140 | "property-case": "lower",
141 | "declaration-bang-space-after": "never",
142 | "declaration-bang-space-before": "always",
143 | "declaration-colon-newline-after": "always-multi-line",
144 | "declaration-colon-space-after": "always-single-line",
145 | "declaration-colon-space-before": "never",
146 | "declaration-empty-line-before": "never",
147 | "declaration-block-semicolon-newline-after": "always-multi-line",
148 | "declaration-block-semicolon-newline-before": null,
149 | "declaration-block-semicolon-space-after": "always-single-line",
150 | "declaration-block-semicolon-space-before": "never",
151 | "declaration-block-trailing-semicolon": "always",
152 | "block-closing-brace-empty-line-before": "never",
153 | "block-closing-brace-newline-after": "always",
154 | "block-closing-brace-newline-before": "always-multi-line",
155 | "block-closing-brace-space-after": null,
156 | "block-closing-brace-space-before": "always-single-line",
157 | "block-opening-brace-newline-after": "always-multi-line",
158 | "block-opening-brace-newline-before": null,
159 | "block-opening-brace-space-after": "always-single-line",
160 | "block-opening-brace-space-before": "always",
161 | "selector-attribute-brackets-space-inside": "never",
162 | "selector-attribute-operator-space-after": "never",
163 | "selector-attribute-operator-space-before": "never",
164 | "selector-attribute-quotes": "always",
165 | "selector-combinator-space-after": "always",
166 | "selector-combinator-space-before": "always",
167 | "selector-descendant-combinator-no-non-space": true,
168 | "selector-pseudo-class-case": "lower",
169 | "selector-pseudo-class-parentheses-space-inside": "never",
170 | "selector-pseudo-element-case": "lower",
171 | "selector-pseudo-element-colon-notation": "double",
172 | "selector-type-case": "lower",
173 | "selector-list-comma-newline-after": "always-multi-line",
174 | "selector-list-comma-newline-before": null,
175 | "selector-list-comma-space-after": "always-single-line",
176 | "selector-list-comma-space-before": "never",
177 | "rule-empty-line-before": [
178 | "always-multi-line",
179 | {
180 | "except": ["first-nested"],
181 | "ignore": ["after-comment"]
182 | }
183 | ],
184 | "media-feature-colon-space-after": "always",
185 | "media-feature-colon-space-before": "never",
186 | "media-feature-name-case": "lower",
187 | "media-feature-parentheses-space-inside": "never",
188 | "media-feature-range-operator-space-after": "always",
189 | "media-feature-range-operator-space-before": "always",
190 | "media-query-list-comma-newline-after": "always-multi-line",
191 | "media-query-list-comma-newline-before": null,
192 | "media-query-list-comma-space-after": "always-single-line",
193 | "media-query-list-comma-space-before": "never",
194 | "at-rule-empty-line-before": [
195 | "always",
196 | {
197 | "except": ["blockless-after-same-name-blockless", "first-nested"],
198 | "ignore": ["after-comment"]
199 | }
200 | ],
201 | "at-rule-name-case": "lower",
202 | "at-rule-name-newline-after": null,
203 | "at-rule-name-space-after": "always",
204 | "at-rule-semicolon-newline-after": "always",
205 | "at-rule-semicolon-space-before": "never",
206 | "comment-empty-line-before": [
207 | "always",
208 | {
209 | "except": ["first-nested"],
210 | "ignore": ["stylelint-commands"]
211 | }
212 | ],
213 | "comment-whitespace-inside": "always",
214 |
215 | "indentation": 2,
216 | "linebreaks": "unix",
217 | "max-empty-lines": 1,
218 | "max-line-length": null,
219 | "no-eol-whitespace": true,
220 | "no-missing-end-of-source-newline": true,
221 | "no-empty-first-line": true
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/htdocs/assets/css/site.css:
--------------------------------------------------------------------------------
1 | html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html{--bg: #fdfdfd;--color: #333;--color-light: #fff;--backdrop-color: rgba(8, 34, 63, 0.9);--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);--box-shadow-heavy: 0 2px 4px rgba(0, 0, 0, 0.2);--brand-color: #007acc;--border-color: #f5f5f5;--border-color-hover: #ddd;--tile-filter: none;--wallpaper-filter: none}@media(prefers-color-scheme: dark){html{--bg: #18191d;--color: #d2d0cc;--color-light: #fff;--backdrop-color: rgba(0, 0, 0, 0.9);--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);--box-shadow-heavy: 0 2px 4px rgba(0, 0, 0, 0.2);--brand-color: #ff1448;--border-color: #313131;--border-color-hover: #333;--tile-filter: grayscale(1) contrast(1.5);--wallpaper-filter: grayscale(1) opacity(0.1)}}body{background-color:var(--bg);font-family:-apple-system,sans-serif;width:100%}html{font-size:100%;height:100%;overflow-x:hidden;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0;padding:0}img{height:auto;max-width:100%}header,main,footer{display:block}button{background-color:transparent;border-width:0;color:inherit;cursor:pointer;font-size:inherit}details{border-top:1px solid var(--border-color-hover);border-bottom:1px solid var(--border-color-hover)}details+details{border-top-width:0}summary{cursor:pointer;display:block;font-size:inherit;margin:0;padding:.5vw 1.188em .5vw 0;position:relative;opacity:1}summary::before{content:"+";position:absolute;right:1.188em;display:inline-block;-webkit-transition:.3s transform linear;transition:.3s transform linear}summary::-webkit-details-marker{display:none}details[open] summary::before{-webkit-transform:rotate(45deg);transform:rotate(45deg)}header{background-color:var(--bg);color:var(--color);box-shadow:0 1px 1px rgba(0,0,0,.2);position:relative;width:100%;z-index:2}@media(min-width: 671px){header{padding:.75vw 1vw}header a{padding:.75vw 1vw}}@media(max-width: 670px){header{display:-ms-grid;display:grid;-ms-grid-columns:(1fr)[2];grid-template-columns:repeat(2, 1fr);padding:2vw 1.5vw}header a{padding:1vw 2vw}}header.js-sticky{top:0}header .collapse{border-width:0}header .collapse-main{overflow:visible}a{color:inherit}main{left:0;margin:1vw;position:relative;right:0;z-index:1}footer{background-color:var(--brand-color);color:var(--color-light);font-size:.75rem;padding-left:1vw;padding-right:1vw;width:100%;z-index:1}footer::after{clear:both;content:"";display:table}footer.js-sticky{bottom:0}footer a{color:inherit;display:inline-block;padding-bottom:9px;padding-top:9px}footer .description{float:left}footer .social-profiles{float:right}nav ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0}nav ul::after{clear:both;content:"";display:table}nav a{display:block;text-decoration:none}.list-tiles{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:auto;margin-right:auto}@supports((display: -ms-grid) or (display: grid)){.list-tiles{display:-ms-grid;display:grid;grid-gap:1vw;-ms-grid-columns:(minmax(160px, 1fr))[auto-fit];grid-template-columns:repeat(auto-fit, minmax(160px, 1fr))}@media(min-width: 768px){.list-tiles{-ms-grid-columns:(minmax(256px, 1fr))[auto-fit];grid-template-columns:repeat(auto-fit, minmax(256px, 1fr))}}}.list-vertical{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0;padding-bottom:.75vw}.list-vertical__link{display:block;text-decoration:none}.list-vertical__link:hover{text-decoration:underline}.list-horizontal{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0}.list-horizontal li{display:inline-block}.tabbed-content{contain-intrinsic-size:1px 2000px;content-visibility:auto}.js .tabbed-content:not(.sdi-is-active){display:none}.backdrop{background-color:var(--backdrop-color);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);height:100vh;position:absolute;top:0;-webkit-transition:opacity 0s ease;transition:opacity 0s ease;width:100vw;z-index:-1}.flyout{left:0;position:fixed;top:0;z-index:3}.flyout-backdrop{display:none}.flyout-close{float:right}.flyout-title{font-size:1rem;font-weight:600;margin-top:0;padding-top:inherit}.flyout-content{background-color:var(--bg);box-shadow:var(--box-shadow-heavy);color:var(--color);height:100vh;max-width:400px;overflow-y:auto;padding:1vw 1.5vw;position:fixed;right:0;top:0;-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transition:all .2s ease;transition:all .2s ease;width:100%}.flyout-content ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0}.flyout-content a{font-size:.875rem;padding:2px 0}.js-is-active .flyout-content{-webkit-transform:translateX(0);transform:translateX(0)}.js-is-active .flyout-backdrop{display:block}.js .modal{-webkit-transition:visibility .2s ease-in-out;transition:visibility .2s ease-in-out;visibility:hidden}.modal.js-is-active{position:fixed;top:0;visibility:visible;z-index:2}.modal-overlay{-webkit-transform:translate(-50%, -50%) scale(0);transform:translate(-50%, -50%) scale(0);left:50%;position:fixed;top:50%}.modal-header{font-weight:600;position:relative}.modal-content{overflow-y:auto}.modal-header__close{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);right:0}.modal-list{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:0}.modal-list__item{display:inline-block;width:49%}@supports((display: -ms-grid) or (display: grid)){.modal-list{display:-ms-grid;display:grid;-ms-grid-columns:50% auto;grid-template-columns:50% auto}.modal-list__item{width:100%}}.modal-list__link{display:block;padding:9px;text-decoration:none}.modal-list__link:hover{background-color:var(--border-color)}.modal-content--qr{text-align:center}.modal-content--qr img{mix-blend-mode:multiply}.notification{-webkit-transition:all .2s;transition:all .2s}.notification.js-hidden{-webkit-transform:translateX(100%);transform:translateX(100%)}.notification.js-visible{right:1vw;-webkit-transform:translateX(0%);transform:translateX(0%)}.overlay{display:block;position:fixed;z-index:3}.overlay-close{float:right}.overlay-title{font-size:1rem;font-weight:600;margin-top:0;padding-top:inherit}.overlay-content{height:100vh;overflow-y:auto;-webkit-transition:all .2s ease;transition:all .2s ease;width:100%}.tile{display:block;opacity:.97;position:relative}.tile-image{display:inline-block;-webkit-filter:var(--tile-filter);filter:var(--tile-filter);max-height:100%;max-width:100%;object-fit:contain}.tile-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#wallpaper{background-position:center center;background-size:cover;bottom:0;left:0;position:fixed;right:0;top:0;-webkit-filter:var(--wallpaper-filter);filter:var(--wallpaper-filter)}.sdi-sticky{left:0;position:-webkit-sticky;position:sticky;top:0}.sdi-is-fixed{position:fixed}.js-sticky{position:fixed}.modal-overlay{-webkit-transform:translate(-50%, -50%) scale(0);transform:translate(-50%, -50%) scale(0);left:50%;position:fixed;top:50%;background-color:var(--bg);border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,.2);color:var(--color);height:auto;max-height:400px;max-width:500px;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out, -webkit-transform .2s ease-in-out;width:90%}.js-is-active .modal-overlay{-webkit-transform:translate(-50%, -50%) scale(1);transform:translate(-50%, -50%) scale(1);left:50%;position:fixed;top:50%}.modal-header{background-color:transparent;border-bottom:2px solid var(--brand-color);color:var(--color);margin-bottom:9px;padding:18px}.modal-content{padding:9px}.overlay{height:100vh;width:100%}.overlay-content{background-color:var(--bg);box-shadow:0 2px 4px rgba(0,0,0,.2);color:var(--color)}.js-hidden .backdrop{height:0;opacity:0}.js-hidden .overlay-content{-webkit-transform:translateX(100%);transform:translateX(100%)}.js-visible .backdrop{opacity:1}.js-visible .overlay-content{-webkit-transform:translateX(0);transform:translateX(0)}.tile-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:100px;padding:0;position:relative;width:25%}@media(min-width: 768px){.tile-container{height:156px}}@supports((display: -ms-grid) or (display: grid)){.tile-container{width:auto}}.tile{background-clip:padding-box;background-image:-webkit-linear-gradient(bottom, var(--bg), var(--border-color));background-image:linear-gradient(to top, var(--bg), var(--border-color));border:1px solid var(--border-color);border-radius:2px;box-shadow:0 1px 1px rgba(0,0,0,.2);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.75rem;position:relative;text-align:center;text-decoration:none;width:100%}.tile:hover{background-image:-webkit-linear-gradient(top, var(--bg), var(--border-color));background-image:linear-gradient(to bottom, var(--bg), var(--border-color));border-color:var(--border-color-hover);z-index:2}.tile-title{background-color:var(--border-color);bottom:0;color:var(--color);display:block;left:0;padding:.5vw;position:absolute;right:0}.tile__button{color:inherit;left:1px;padding:.5vw;position:absolute;top:1px;z-index:2}.tile-image{margin:auto}@media(max-width: 767px){.tile-image{max-height:50%;max-width:50%}}.notification{background-color:var(--bg);bottom:3vw;box-shadow:0 2px 4px rgba(0,0,0,.2);color:var(--color);padding:1vw;position:fixed;right:0;z-index:99}nav li{display:inline-block}nav a{color:var(--color)}nav a:hover,nav a.sdi-is-active{background-color:var(--brand-color);border-radius:2px;color:var(--color-light)}.js-fx header,.js-fx main,.js-fx footer{-webkit-filter:blur(1px);filter:blur(1px)}@media(max-width: 670px){button{padding:1vw 2vw}.overlay-content{padding:2vw 1.5vw}.overlay-title{padding-bottom:1vw;padding-top:1vw}#bookmarks-toggle{justify-self:end}#header-nav-toggle{justify-self:start}#header-nav{-ms-grid-column-span:2;-ms-grid-column:1;grid-column:1/span 2;overflow:hidden;-webkit-transition:max-height .5s ease;transition:max-height .5s ease}#header-nav.js-opened{max-height:100vh}#header-nav li{display:block}}@media(min-width: 671px){button{padding:.5vw 1vw}.overlay-content{padding:.75vw 1vw}#bookmarks-toggle{float:right}#application-header .collapse-main{max-height:none}.overlay-title{padding-bottom:.75vw;padding-top:.75vw}.tile-title{opacity:0;-webkit-transition:opacity .2s;transition:opacity .2s}.tile:hover .tile-title{opacity:1}button[data-target*=nav]{display:none}}#notification{display:block !important}.overlay{display:block !important}
--------------------------------------------------------------------------------
/htdocs/index.php:
--------------------------------------------------------------------------------
1 | 0) {
25 | echo '';
26 | }
27 | }
28 |
29 | function returnImage($url, $alt, $class)
30 | {
31 | list($width, $height, $type, $atr) = getimagesize($url);
32 | return '
';
33 | }
34 |
35 | // define linktarget if isset and filled otherwise use default
36 | if (empty($linktarget)) {
37 | $linktarget = "_self";
38 | }
39 | ?>
40 |
41 |
42 | >
48 |
49 |
50 |
51 | ' . $projectTitle . '' : FALSE;
53 | echo isset($projectDescription) ? '' : FALSE;
54 | echo isset($projectKeywords) ? '' : FALSE;
55 | echo isset($projectLanguage) ? '' : FALSE;
56 | ?>
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
117 |
118 |
119 |
120 |
121 |
122 |
143 |
144 |
145 |
146 |
147 | 0) { ?>
148 |
149 |
150 |
151 |
Bookmarks
152 | $contentItems) : ?>
153 |
154 | = $key ?>
155 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | 0) { ?>
173 |
191 |
192 |
193 |
194 |
195 |
199 |
200 | = returnImage($contentItem['imageQr'], $contentItem['title'], "tile-image") ?>
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
224 |
225 |
226 | press [alt] to open a tab and prevent remembering it
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
245 |
246 |
247 |
--------------------------------------------------------------------------------
/tests/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect, type Page } from '@playwright/test';
2 |
3 | test.beforeEach(async ({ page }) => {
4 | await page.goto('https://demo.playwright.dev/todomvc');
5 | });
6 |
7 | const TODO_ITEMS = [
8 | 'buy some cheese',
9 | 'feed the cat',
10 | 'book a doctors appointment'
11 | ];
12 |
13 | test.describe('New Todo', () => {
14 | test('should allow me to add todo items', async ({ page }) => {
15 | // Create 1st todo.
16 | await page.locator('.new-todo').fill(TODO_ITEMS[0]);
17 | await page.locator('.new-todo').press('Enter');
18 |
19 | // Make sure the list only has one todo item.
20 | await expect(page.locator('.view label')).toHaveText([
21 | TODO_ITEMS[0]
22 | ]);
23 |
24 | // Create 2nd todo.
25 | await page.locator('.new-todo').fill(TODO_ITEMS[1]);
26 | await page.locator('.new-todo').press('Enter');
27 |
28 | // Make sure the list now has two todo items.
29 | await expect(page.locator('.view label')).toHaveText([
30 | TODO_ITEMS[0],
31 | TODO_ITEMS[1]
32 | ]);
33 |
34 | await checkNumberOfTodosInLocalStorage(page, 2);
35 | });
36 |
37 | test('should clear text input field when an item is added', async ({ page }) => {
38 | // Create one todo item.
39 | await page.locator('.new-todo').fill(TODO_ITEMS[0]);
40 | await page.locator('.new-todo').press('Enter');
41 |
42 | // Check that input is empty.
43 | await expect(page.locator('.new-todo')).toBeEmpty();
44 | await checkNumberOfTodosInLocalStorage(page, 1);
45 | });
46 |
47 | test('should append new items to the bottom of the list', async ({ page }) => {
48 | // Create 3 items.
49 | await createDefaultTodos(page);
50 |
51 | // Check test using different methods.
52 | await expect(page.locator('.todo-count')).toHaveText('3 items left');
53 | await expect(page.locator('.todo-count')).toContainText('3');
54 | await expect(page.locator('.todo-count')).toHaveText(/3/);
55 |
56 | // Check all items in one call.
57 | await expect(page.locator('.view label')).toHaveText(TODO_ITEMS);
58 | await checkNumberOfTodosInLocalStorage(page, 3);
59 | });
60 |
61 | test('should show #main and #footer when items added', async ({ page }) => {
62 | await page.locator('.new-todo').fill(TODO_ITEMS[0]);
63 | await page.locator('.new-todo').press('Enter');
64 |
65 | await expect(page.locator('.main')).toBeVisible();
66 | await expect(page.locator('.footer')).toBeVisible();
67 | await checkNumberOfTodosInLocalStorage(page, 1);
68 | });
69 | });
70 |
71 | test.describe('Mark all as completed', () => {
72 | test.beforeEach(async ({ page }) => {
73 | await createDefaultTodos(page);
74 | await checkNumberOfTodosInLocalStorage(page, 3);
75 | });
76 |
77 | test.afterEach(async ({ page }) => {
78 | await checkNumberOfTodosInLocalStorage(page, 3);
79 | });
80 |
81 | test('should allow me to mark all items as completed', async ({ page }) => {
82 | // Complete all todos.
83 | await page.locator('.toggle-all').check();
84 |
85 | // Ensure all todos have 'completed' class.
86 | await expect(page.locator('.todo-list li')).toHaveClass(['completed', 'completed', 'completed']);
87 | await checkNumberOfCompletedTodosInLocalStorage(page, 3);
88 | });
89 |
90 | test('should allow me to clear the complete state of all items', async ({ page }) => {
91 | // Check and then immediately uncheck.
92 | await page.locator('.toggle-all').check();
93 | await page.locator('.toggle-all').uncheck();
94 |
95 | // Should be no completed classes.
96 | await expect(page.locator('.todo-list li')).toHaveClass(['', '', '']);
97 | });
98 |
99 | test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => {
100 | const toggleAll = page.locator('.toggle-all');
101 | await toggleAll.check();
102 | await expect(toggleAll).toBeChecked();
103 | await checkNumberOfCompletedTodosInLocalStorage(page, 3);
104 |
105 | // Uncheck first todo.
106 | const firstTodo = page.locator('.todo-list li').nth(0);
107 | await firstTodo.locator('.toggle').uncheck();
108 |
109 | // Reuse toggleAll locator and make sure its not checked.
110 | await expect(toggleAll).not.toBeChecked();
111 |
112 | await firstTodo.locator('.toggle').check();
113 | await checkNumberOfCompletedTodosInLocalStorage(page, 3);
114 |
115 | // Assert the toggle all is checked again.
116 | await expect(toggleAll).toBeChecked();
117 | });
118 | });
119 |
120 | test.describe('Item', () => {
121 |
122 | test('should allow me to mark items as complete', async ({ page }) => {
123 | // Create two items.
124 | for (const item of TODO_ITEMS.slice(0, 2)) {
125 | await page.locator('.new-todo').fill(item);
126 | await page.locator('.new-todo').press('Enter');
127 | }
128 |
129 | // Check first item.
130 | const firstTodo = page.locator('.todo-list li').nth(0);
131 | await firstTodo.locator('.toggle').check();
132 | await expect(firstTodo).toHaveClass('completed');
133 |
134 | // Check second item.
135 | const secondTodo = page.locator('.todo-list li').nth(1);
136 | await expect(secondTodo).not.toHaveClass('completed');
137 | await secondTodo.locator('.toggle').check();
138 |
139 | // Assert completed class.
140 | await expect(firstTodo).toHaveClass('completed');
141 | await expect(secondTodo).toHaveClass('completed');
142 | });
143 |
144 | test('should allow me to un-mark items as complete', async ({ page }) => {
145 | // Create two items.
146 | for (const item of TODO_ITEMS.slice(0, 2)) {
147 | await page.locator('.new-todo').fill(item);
148 | await page.locator('.new-todo').press('Enter');
149 | }
150 |
151 | const firstTodo = page.locator('.todo-list li').nth(0);
152 | const secondTodo = page.locator('.todo-list li').nth(1);
153 | await firstTodo.locator('.toggle').check();
154 | await expect(firstTodo).toHaveClass('completed');
155 | await expect(secondTodo).not.toHaveClass('completed');
156 | await checkNumberOfCompletedTodosInLocalStorage(page, 1);
157 |
158 | await firstTodo.locator('.toggle').uncheck();
159 | await expect(firstTodo).not.toHaveClass('completed');
160 | await expect(secondTodo).not.toHaveClass('completed');
161 | await checkNumberOfCompletedTodosInLocalStorage(page, 0);
162 | });
163 |
164 | test('should allow me to edit an item', async ({ page }) => {
165 | await createDefaultTodos(page);
166 |
167 | const todoItems = page.locator('.todo-list li');
168 | const secondTodo = todoItems.nth(1);
169 | await secondTodo.dblclick();
170 | await expect(secondTodo.locator('.edit')).toHaveValue(TODO_ITEMS[1]);
171 | await secondTodo.locator('.edit').fill('buy some sausages');
172 | await secondTodo.locator('.edit').press('Enter');
173 |
174 | // Explicitly assert the new text value.
175 | await expect(todoItems).toHaveText([
176 | TODO_ITEMS[0],
177 | 'buy some sausages',
178 | TODO_ITEMS[2]
179 | ]);
180 | await checkTodosInLocalStorage(page, 'buy some sausages');
181 | });
182 | });
183 |
184 | test.describe('Editing', () => {
185 | test.beforeEach(async ({ page }) => {
186 | await createDefaultTodos(page);
187 | await checkNumberOfTodosInLocalStorage(page, 3);
188 | });
189 |
190 | test('should hide other controls when editing', async ({ page }) => {
191 | const todoItem = page.locator('.todo-list li').nth(1);
192 | await todoItem.dblclick();
193 | await expect(todoItem.locator('.toggle')).not.toBeVisible();
194 | await expect(todoItem.locator('label')).not.toBeVisible();
195 | await checkNumberOfTodosInLocalStorage(page, 3);
196 | });
197 |
198 | test('should save edits on blur', async ({ page }) => {
199 | const todoItems = page.locator('.todo-list li');
200 | await todoItems.nth(1).dblclick();
201 | await todoItems.nth(1).locator('.edit').fill('buy some sausages');
202 | await todoItems.nth(1).locator('.edit').dispatchEvent('blur');
203 |
204 | await expect(todoItems).toHaveText([
205 | TODO_ITEMS[0],
206 | 'buy some sausages',
207 | TODO_ITEMS[2],
208 | ]);
209 | await checkTodosInLocalStorage(page, 'buy some sausages');
210 | });
211 |
212 | test('should trim entered text', async ({ page }) => {
213 | const todoItems = page.locator('.todo-list li');
214 | await todoItems.nth(1).dblclick();
215 | await todoItems.nth(1).locator('.edit').fill(' buy some sausages ');
216 | await todoItems.nth(1).locator('.edit').press('Enter');
217 |
218 | await expect(todoItems).toHaveText([
219 | TODO_ITEMS[0],
220 | 'buy some sausages',
221 | TODO_ITEMS[2],
222 | ]);
223 | await checkTodosInLocalStorage(page, 'buy some sausages');
224 | });
225 |
226 | test('should remove the item if an empty text string was entered', async ({ page }) => {
227 | const todoItems = page.locator('.todo-list li');
228 | await todoItems.nth(1).dblclick();
229 | await todoItems.nth(1).locator('.edit').fill('');
230 | await todoItems.nth(1).locator('.edit').press('Enter');
231 |
232 | await expect(todoItems).toHaveText([
233 | TODO_ITEMS[0],
234 | TODO_ITEMS[2],
235 | ]);
236 | });
237 |
238 | test('should cancel edits on escape', async ({ page }) => {
239 | const todoItems = page.locator('.todo-list li');
240 | await todoItems.nth(1).dblclick();
241 | await todoItems.nth(1).locator('.edit').press('Escape');
242 | await expect(todoItems).toHaveText(TODO_ITEMS);
243 | });
244 | });
245 |
246 | test.describe('Counter', () => {
247 | test('should display the current number of todo items', async ({ page }) => {
248 | await page.locator('.new-todo').fill(TODO_ITEMS[0]);
249 | await page.locator('.new-todo').press('Enter');
250 | await expect(page.locator('.todo-count')).toContainText('1');
251 |
252 | await page.locator('.new-todo').fill(TODO_ITEMS[1]);
253 | await page.locator('.new-todo').press('Enter');
254 | await expect(page.locator('.todo-count')).toContainText('2');
255 |
256 | await checkNumberOfTodosInLocalStorage(page, 2);
257 | });
258 | });
259 |
260 | test.describe('Clear completed button', () => {
261 | test.beforeEach(async ({ page }) => {
262 | await createDefaultTodos(page);
263 | });
264 |
265 | test('should display the correct text', async ({ page }) => {
266 | await page.locator('.todo-list li .toggle').first().check();
267 | await expect(page.locator('.clear-completed')).toHaveText('Clear completed');
268 | });
269 |
270 | test('should remove completed items when clicked', async ({ page }) => {
271 | const todoItems = page.locator('.todo-list li');
272 | await todoItems.nth(1).locator('.toggle').check();
273 | await page.locator('.clear-completed').click();
274 | await expect(todoItems).toHaveCount(2);
275 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
276 | });
277 |
278 | test('should be hidden when there are no items that are completed', async ({ page }) => {
279 | await page.locator('.todo-list li .toggle').first().check();
280 | await page.locator('.clear-completed').click();
281 | await expect(page.locator('.clear-completed')).toBeHidden();
282 | });
283 | });
284 |
285 | test.describe('Persistence', () => {
286 | test('should persist its data', async ({ page }) => {
287 | for (const item of TODO_ITEMS.slice(0, 2)) {
288 | await page.locator('.new-todo').fill(item);
289 | await page.locator('.new-todo').press('Enter');
290 | }
291 |
292 | const todoItems = page.locator('.todo-list li');
293 | await todoItems.nth(0).locator('.toggle').check();
294 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
295 | await expect(todoItems).toHaveClass(['completed', '']);
296 |
297 | // Ensure there is 1 completed item.
298 | checkNumberOfCompletedTodosInLocalStorage(page, 1);
299 |
300 | // Now reload.
301 | await page.reload();
302 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
303 | await expect(todoItems).toHaveClass(['completed', '']);
304 | });
305 | });
306 |
307 | test.describe('Routing', () => {
308 | test.beforeEach(async ({ page }) => {
309 | await createDefaultTodos(page);
310 | // make sure the app had a chance to save updated todos in storage
311 | // before navigating to a new view, otherwise the items can get lost :(
312 | // in some frameworks like Durandal
313 | await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
314 | });
315 |
316 | test('should allow me to display active items', async ({ page }) => {
317 | await page.locator('.todo-list li .toggle').nth(1).check();
318 | await checkNumberOfCompletedTodosInLocalStorage(page, 1);
319 | await page.locator('.filters >> text=Active').click();
320 | await expect(page.locator('.todo-list li')).toHaveCount(2);
321 | await expect(page.locator('.todo-list li')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
322 | });
323 |
324 | test('should respect the back button', async ({ page }) => {
325 | await page.locator('.todo-list li .toggle').nth(1).check();
326 | await checkNumberOfCompletedTodosInLocalStorage(page, 1);
327 |
328 | await test.step('Showing all items', async () => {
329 | await page.locator('.filters >> text=All').click();
330 | await expect(page.locator('.todo-list li')).toHaveCount(3);
331 | });
332 |
333 | await test.step('Showing active items', async () => {
334 | await page.locator('.filters >> text=Active').click();
335 | });
336 |
337 | await test.step('Showing completed items', async () => {
338 | await page.locator('.filters >> text=Completed').click();
339 | });
340 |
341 | await expect(page.locator('.todo-list li')).toHaveCount(1);
342 | await page.goBack();
343 | await expect(page.locator('.todo-list li')).toHaveCount(2);
344 | await page.goBack();
345 | await expect(page.locator('.todo-list li')).toHaveCount(3);
346 | });
347 |
348 | test('should allow me to display completed items', async ({ page }) => {
349 | await page.locator('.todo-list li .toggle').nth(1).check();
350 | await checkNumberOfCompletedTodosInLocalStorage(page, 1);
351 | await page.locator('.filters >> text=Completed').click();
352 | await expect(page.locator('.todo-list li')).toHaveCount(1);
353 | });
354 |
355 | test('should allow me to display all items', async ({ page }) => {
356 | await page.locator('.todo-list li .toggle').nth(1).check();
357 | await checkNumberOfCompletedTodosInLocalStorage(page, 1);
358 | await page.locator('.filters >> text=Active').click();
359 | await page.locator('.filters >> text=Completed').click();
360 | await page.locator('.filters >> text=All').click();
361 | await expect(page.locator('.todo-list li')).toHaveCount(3);
362 | });
363 |
364 | test('should highlight the currently applied filter', async ({ page }) => {
365 | await expect(page.locator('.filters >> text=All')).toHaveClass('selected');
366 | await page.locator('.filters >> text=Active').click();
367 | // Page change - active items.
368 | await expect(page.locator('.filters >> text=Active')).toHaveClass('selected');
369 | await page.locator('.filters >> text=Completed').click();
370 | // Page change - completed items.
371 | await expect(page.locator('.filters >> text=Completed')).toHaveClass('selected');
372 | });
373 | });
374 |
375 | async function createDefaultTodos(page: Page) {
376 | for (const item of TODO_ITEMS) {
377 | await page.locator('.new-todo').fill(item);
378 | await page.locator('.new-todo').press('Enter');
379 | }
380 | }
381 |
382 | async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
383 | return await page.waitForFunction(e => {
384 | return JSON.parse(localStorage['react-todos']).length === e;
385 | }, expected);
386 | }
387 |
388 | async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
389 | return await page.waitForFunction(e => {
390 | return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e;
391 | }, expected);
392 | }
393 |
394 | async function checkTodosInLocalStorage(page: Page, title: string) {
395 | return await page.waitForFunction(t => {
396 | return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
397 | }, title);
398 | }
399 |
--------------------------------------------------------------------------------
/htdocs/data/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "linktarget": "_self",
3 | "useServiceWorker": true,
4 | "wallpaper": "assets/wallpaper/default.jpg",
5 | "wallpaperPreload": false,
6 | "content": {
7 | "Browser": [
8 | {
9 | "url": "https://github.com/saschadiercks/browserStartpage",
10 | "title": "Fork me on github",
11 | "image": "assets/thumbnails/forkme.png",
12 | "imageQr": "assets/qr-codes/github-project.png"
13 | },
14 | {
15 | "url": "https://github.com/saschadiercks/browserStartpage",
16 | "title": "Links as modal",
17 | "image": "assets/thumbnails/modal.png",
18 | "modal": [
19 | {
20 | "url": "https://www.google.de/chrome/browser/desktop/",
21 | "title": "Chrome"
22 | },
23 | {
24 | "url": "https://www.mozilla.org/firefox",
25 | "title": "Firefox"
26 | },
27 | {
28 | "url": "https://www.mozilla.org/de/firefox/developer/",
29 | "title": "Firefox (Dev)"
30 | },
31 | {
32 | "url": "https://developer.apple.com/safari/technology-preview/",
33 | "title": "Safari (TP)"
34 | },
35 | {
36 | "url": "https://vivaldi.com",
37 | "title": "Vivaldi"
38 | },
39 | {
40 | "url": "https://blisk.io/",
41 | "title": "Blisk"
42 | },
43 | {
44 | "url": "https://developer.microsoft.com/en-us/microsoft-edge/tools/",
45 | "title": "Modern IE"
46 | }
47 | ]
48 | },
49 | {
50 | "url": "https://www.google.de/chrome/browser/desktop/",
51 | "title": "Chrome",
52 | "image": "assets/thumbnails/chrome.png"
53 | },
54 | {
55 | "url": "https://www.mozilla.org/firefox",
56 | "title": "Firefox",
57 | "image": "assets/thumbnails/firefox.png"
58 | },
59 | {
60 | "url": "https://www.mozilla.org/de/firefox/developer/",
61 | "title": "Firefox (Dev)",
62 | "image": "assets/thumbnails/firefox-dev.png"
63 | },
64 | {
65 | "url": "https://developer.apple.com/safari/technology-preview/",
66 | "title": "Safari (TP)",
67 | "image": "assets/thumbnails/safari-tp.png"
68 | },
69 | {
70 | "url": "https://vivaldi.com",
71 | "title": "Vivaldi",
72 | "image": "assets/thumbnails/vivaldi.png"
73 | },
74 | {
75 | "url": "https://blisk.io/",
76 | "title": "Blisk",
77 | "image": "assets/thumbnails/blisk.png"
78 | },
79 | {
80 | "url": "https://developer.microsoft.com/en-us/microsoft-edge/tools/",
81 | "title": "Modern IE",
82 | "image": "assets/thumbnails/modern-ie.png"
83 | }
84 | ],
85 | "Dev": [
86 | {
87 | "url": "https://schema.org/docs/schemas.html",
88 | "title": "Schema.org",
89 | "image": "assets/thumbnails/schema.png"
90 | },
91 | {
92 | "url": "https://html.spec.whatwg.org/multipage/forms.html#autofill",
93 | "title": "Autocomplete",
94 | "image": "assets/thumbnails/autocomplete.png"
95 | },
96 | {
97 | "url": "https://microformats.org/wiki/hcard-input-formats",
98 | "title": "Microformats",
99 | "image": "assets/thumbnails/microformats.png"
100 | },
101 | {
102 | "url": "https://github.com/",
103 | "title": "Github",
104 | "image": "assets/thumbnails/github.png"
105 | },
106 | {
107 | "url": "https://bitbucket.org/",
108 | "title": "Bitbucket",
109 | "image": "assets/thumbnails/bitbucket.png"
110 | },
111 | {
112 | "url": "https://validator.w3.org/nu/",
113 | "title": "HTML-Validator",
114 | "image": "assets/thumbnails/html-validator.png"
115 | },
116 | {
117 | "url": "https://developers.google.com/structured-data/testing-tool/",
118 | "title": "RichSnippet-Testing",
119 | "image": "assets/thumbnails/rich-snippet.png"
120 | },
121 | {
122 | "url": "https://developers.google.com/speed/pagespeed/insights/?hl=de",
123 | "title": "Google Pagespeeds",
124 | "image": "assets/thumbnails/pagespeed.png"
125 | },
126 | {
127 | "url": "https://search.google.com/test/mobile-friendly",
128 | "title": "Google Mobiltest",
129 | "image": "assets/thumbnails/mobile-friendly.png"
130 | },
131 | {
132 | "url": "https://www.google.com/webmasters/tools/",
133 | "title": "Google Webmaster-Tools",
134 | "image": "assets/thumbnails/webmastertools.png"
135 | },
136 | {
137 | "url": "https://analytics.google.com/",
138 | "title": "Analytics",
139 | "image": "assets/thumbnails/analytics.png"
140 | },
141 | {
142 | "url": "https://github.com/dypsilon/frontend-dev-bookmarks",
143 | "title": "Frontend Bookmarks",
144 | "image": "assets/thumbnails/frontend-bookmarks.png"
145 | },
146 | {
147 | "url": "https://developer.mozilla.org/de/",
148 | "title": "Mozilla (dev)",
149 | "image": "assets/thumbnails/mozilla-dev.png"
150 | },
151 | {
152 | "url": "https://developers.google.com/web/?hl=de",
153 | "title": "Google (dev)",
154 | "image": "assets/thumbnails/google-dev.png"
155 | },
156 | {
157 | "url": "https://codepen.io/patterns/",
158 | "title": "Codepen patterns",
159 | "image": "assets/thumbnails/codepen-patterns.png"
160 | },
161 | {
162 | "url": "https://browserl.ist/",
163 | "title": "Browserlist",
164 | "image": "assets/thumbnails/browserlist.png"
165 | },
166 | {
167 | "url": "https://www.codecademy.com",
168 | "title": "Codecademy",
169 | "image": "assets/thumbnails/codecademy.png"
170 | },
171 | {
172 | "url": "https://tools.pingdom.com/fpt/",
173 | "title": "Pingdom",
174 | "image": "assets/thumbnails/pingdom.png"
175 | },
176 | {
177 | "url": "https://dequeuniversity.com/library/",
178 | "title": "Accessibility Library",
179 | "image": "assets/thumbnails/accessibility.png"
180 | },
181 | {
182 | "url": "https://pixabay.com/de/",
183 | "title": "Pixabay",
184 | "image": "assets/thumbnails/pixabay.png"
185 | },
186 | {
187 | "url": "https://npms.io/search?term=hyperterm",
188 | "title": "npms.io",
189 | "image": "assets/thumbnails/npms-io.png"
190 | }
191 | ],
192 | "Design": [
193 | {
194 | "url": "https://www.microsoft.com/en-us/design",
195 | "image": "assets/thumbnails/design-microsoft.png",
196 | "title": "Microsoft Design"
197 | },
198 | {
199 | "url": "https://design.google/resources/",
200 | "image": "assets/thumbnails/design-google.png",
201 | "title": "Google Design"
202 | },
203 | {
204 | "url": "https://material.io/",
205 | "image": "assets/thumbnails/design-material.png",
206 | "title": "Material Design"
207 | },
208 | {
209 | "url": "https://design.firefox.com/",
210 | "image": "assets/thumbnails/design-firefox.png",
211 | "title": "Firefox Design"
212 | },
213 | {
214 | "url": "https://facebook.design",
215 | "image": "assets/thumbnails/design-facebook.png",
216 | "title": "Facebook Design"
217 | },
218 | {
219 | "url": "https://design.trello.com/",
220 | "image": "assets/thumbnails/trello.png",
221 | "title": "Trello Design"
222 | },
223 | {
224 | "url": "https://github.com/showcases/design-essentials",
225 | "image": "assets/thumbnails/github.png",
226 | "title": "Github Design Essentials"
227 | },
228 | {
229 | "url": "https://www.uber.design",
230 | "image": "assets/thumbnails/design-uber.png",
231 | "title": "Uber design"
232 | },
233 | {
234 | "url": "https://airbnb.design",
235 | "image": "assets/thumbnails/design-airbnb.png",
236 | "title": "airbnb Design"
237 | },
238 | {
239 | "url": "https://atlassian.design",
240 | "image": "assets/thumbnails/design-atlassian.png",
241 | "title": "Atlassian Design"
242 | },
243 |
244 | {
245 | "url": "https://unsplash.com/search/photos/macbook",
246 | "image": "assets/thumbnails/design-unsplash.png",
247 | "title": "Unsplash"
248 | },
249 | {
250 | "url": "https://magicmockups.com/",
251 | "image": "assets/thumbnails/magic-mockups.png",
252 | "title": "Magic Mockups"
253 | },
254 | {
255 | "url": "https://smartmockups.com/",
256 | "image": "assets/thumbnails/smartmockups.png",
257 | "title": "Smart Mockups"
258 | },
259 | {
260 | "url": "https://www.designbetter.co",
261 | "image": "assets/thumbnails/design-invision.png",
262 | "title": "Design better"
263 | }
264 | ],
265 | "SDI": [
266 | {
267 | "url": "https://www.saschadiercks.de/",
268 | "image": "assets/thumbnails/sdi-homepage.png",
269 | "imageQr": "assets/qr-codes/metafolio-de.png",
270 | "title": "About me"
271 | },
272 | {
273 | "url": "https://design.saschadiercks.de/",
274 | "image": "assets/thumbnails/sdi-blog.png",
275 | "imageQr": "assets/qr-codes/design-system.png",
276 | "title": "SDI Design System"
277 | },
278 | {
279 | "url": "https://demo.saschadiercks.de/personalnews/",
280 | "image": "assets/thumbnails/sdi-personalnews.png",
281 | "imageQr": "assets/qr-codes/personalnews-landing.png",
282 | "title": "personalNews"
283 | },
284 | {
285 | "url": "https://demo.saschadiercks.de/little-helpers/",
286 | "image": "assets/thumbnails/sdi-little-helpers.png",
287 | "imageQr": "assets/qr-codes/little-helpers.png",
288 | "title": "Little Helpers"
289 | },
290 | {
291 | "url": "https://demo.saschadiercks.de/startpage/",
292 | "image": "assets/thumbnails/sdi-startpage.png",
293 | "imageQr": "assets/qr-codes/startpage-demo.png",
294 | "title": "Browser Startpage (this)"
295 | }
296 | ]
297 | },
298 | "bookmarks": {
299 | "Frontend": [
300 | {
301 | "url": "https://github.com/dypsilon/frontend-dev-bookmarks",
302 | "title": "Frontend Dev Bookmarks"
303 | },
304 | {
305 | "url": "https://github.com/thedaviddias/Front-End-Checklist",
306 | "title": "Frontend Checklist"
307 | },
308 | {
309 | "url": "https://github.com/bendc/frontend-guidelines",
310 | "title": "Frontend Guidelines"
311 | },
312 | {
313 | "url": "https://codeguide.co/",
314 | "title": "Codeguide by @mdo"
315 | },
316 | {
317 | "url": "https://cssreference.io",
318 | "title": "CSS Reference"
319 | },
320 | {
321 | "url": "https://cssguidelin.es/",
322 | "title": "CSS Guidelines"
323 | },
324 | {
325 | "url": "https://bundlephobia.com/",
326 | "title": "Bundlephobia"
327 | },
328 | {
329 | "url": "https://ausi.github.io/respimagelint/",
330 | "title": "ImageLint for responsive Images"
331 | },
332 | {
333 | "url": "https://uitest.com/de/",
334 | "title": "UI-Test"
335 | },
336 | {
337 | "url": "https://whatdoesmysitecost.com/",
338 | "title": "What does my site cost"
339 | },
340 | {
341 | "url": "https://www.campaignmonitor.com/css/",
342 | "title": "CSS in eMails"
343 | },
344 | {
345 | "url": "https://webfieldmanual.com/",
346 | "title": "Webfield manual"
347 | },
348 | {
349 | "url": "https://docs.google.com/spreadsheets/d/1tZYPnzLG0y51QinLxrV97Xflzr2MbTqwWNvaHYN04BE/edit#gid=0",
350 | "title": "Styleguide/Boilerplate patterns"
351 | },
352 | {
353 | "url": "https://www.filamentgroup.com/lab/font-events.html",
354 | "title": "Font Loading via API"
355 | },
356 | {
357 | "url": "https://developer.apple.com",
358 | "title": "Developer @Apple"
359 | },
360 | {
361 | "url": "https://www.interaction-design.org",
362 | "title": "Interaction Design"
363 | },
364 | {
365 | "url": "https://www.barrierefreies-webdesign.de/knowhow/",
366 | "title": "Barrierefreies Webdesign"
367 | },
368 | {
369 | "url": "https://webkrauts.de/",
370 | "title": "Webkrauts"
371 | },
372 | {
373 | "url": "https://a11yproject.com/",
374 | "title": "A11Y"
375 | },
376 | {
377 | "url": "https://www.w3.org/WAI/beta/",
378 | "title": "WAI"
379 | }
380 | ],
381 | "Frameworks": [
382 | {
383 | "url": "https://bulma.io/",
384 | "title": "Bulma"
385 | },
386 | {
387 | "url": "https://getbootstrap.com/",
388 | "title": "Bootstrap"
389 | },
390 | {
391 | "url": "https://foundation.zurb.com",
392 | "title": "Foundation"
393 | },
394 | {
395 | "url": "https://jquery.com/",
396 | "title": "jQuery"
397 | }
398 | ],
399 | "Design Systems": [
400 | {
401 | "url": "https://www.designsystems.com/",
402 | "title": "Design Systems (by figma)"
403 | },
404 | {
405 | "url": "https://designsystemsrepo.com/",
406 | "title": "Design Systems Repo"
407 | },
408 | {
409 | "url": "https://www.lightningdesignsystem.com/",
410 | "title": "Lightning Design System"
411 | },
412 | {
413 | "url": "https://ux.mailchimp.com/patterns",
414 | "title": "Mailchimp UX"
415 | },
416 | {
417 | "url": "https://www.otto.de/pattern-library/",
418 | "title": "Otto Pattern Library"
419 | },
420 | {
421 | "url": "https://patternlab.io/",
422 | "title": "Patternlab"
423 | }
424 | ],
425 | "Patterns": [
426 | {
427 | "url": "https://codepen.io/patterns/",
428 | "title": "Patterns @codepen"
429 | },
430 | {
431 | "url": "https://inclusive-components.design/",
432 | "title": "Inclusive components"
433 | },
434 | {
435 | "url": "https://patterntap.com/patterntap",
436 | "title": "Patterns by Zurb"
437 | },
438 | {
439 | "url": "https://ui-patterns.com/",
440 | "title": "UI-Patterns"
441 | },
442 | {
443 | "url": "https://www.goodui.org/",
444 | "title": "Good UI"
445 | },
446 | {
447 | "url": "https://ui-patterns.com/blog/How-to-get-better-at-UI-design",
448 | "title": "Get better at UI"
449 | }
450 | ],
451 | "Styleguides": [
452 | {
453 | "url": "https://www.designtagebuch.de/wiki/corporate-design-manuals/",
454 | "title": "Corporate Design manuals"
455 | },
456 | {
457 | "url": "https://www.designtagebuch.de/wiki/",
458 | "title": "Designtagebuch Wiki"
459 | },
460 | {
461 | "url": "https://saijogeorge.com/brand-style-guide-examples/",
462 | "title": "Styleguide Examples"
463 | },
464 | {
465 | "url": "https://www.theuxbookmark.com/2010/08/interaction-design/a-monster-list-of-ui-guidelines-style-guides",
466 | "title": "UX-Bookmark: Styleguides"
467 | }
468 | ],
469 | "Design": [
470 | {
471 | "url": "https://makersandfounders.com/DIETER-RAMS",
472 | "title": "Dieter Rams"
473 | },
474 | {
475 | "url": "https://www.customspaces.com/",
476 | "title": "Custom spaces"
477 | },
478 | {
479 | "url": "https://startupsthisishowdesignworks.com/",
480 | "title": "this is how design works"
481 | }
482 | ],
483 | "Helpers": [
484 | {
485 | "url": "https://scrumguides.org/",
486 | "title": "Scrum Guides"
487 | },
488 | {
489 | "url": "https://support.apple.com/de-de/HT205655",
490 | "title": "Ergonomie @Apple"
491 | },
492 | {
493 | "url": "https://makerbook.net/",
494 | "title": "Makerbook"
495 | },
496 | {
497 | "url": "https://www.paypalobjects.com/en_AU/vhelp/paypalmanager_help/credit_card_numbers.htm",
498 | "title": "Test Creditcard Numbers"
499 | },
500 | {
501 | "url": "https://sizecalc.com",
502 | "title": "Size Calculator"
503 | },
504 | {
505 | "url": "https://www.flickr.com/photos/jasontravis/sets/72157603258446753/",
506 | "title": "Persona (Flickr)"
507 | },
508 | {
509 | "url": "https://randomuser.me/",
510 | "title": "Random User Generator"
511 | },
512 | {
513 | "url": "https://sneakpeekit.com",
514 | "title": "Sketch Sheets for Webdesingers"
515 | },
516 | {
517 | "url": "https://ctamagazine.unbounce.com/",
518 | "title": "Call to Action Magazine"
519 | }
520 | ],
521 | "eBooks": [
522 | {
523 | "url": "https://adaptivewebdesign.info/1st-edition/read/",
524 | "title": "Adaptive Webdesign"
525 | },
526 | {
527 | "url": "https://eloquentjavascript.net/",
528 | "title": "eloquent Javascipt"
529 | },
530 | {
531 | "url": "https://resilientwebdesign.com/",
532 | "title": "resilient webdesign"
533 | },
534 | {
535 | "url": "https://www.thebookoflife.org/",
536 | "title": "The book of life"
537 | }
538 | ]
539 | },
540 | "footer": {
541 | "description": [
542 | {
543 | "url": "https://github.com/saschadiercks/browserStartpage",
544 | "title": "Fork me on Github"
545 | }
546 | ],
547 | "links": [
548 | {
549 | "url": "https://saschadiercks.de",
550 | "title": "home"
551 | },
552 | {
553 | "url": "https://github.com/saschadiercks",
554 | "title": "github"
555 | },
556 | {
557 | "url": "https://m.twitter.com/saschadiercks",
558 | "title": "twitter"
559 | }
560 | ]
561 | }
562 | }
563 |
--------------------------------------------------------------------------------