Compare commits

..

No commits in common. "eb1229d859e9499482b6a530355b80359dde3e20" and "358fcc274513f085130544dd748db64a2562c56d" have entirely different histories.

28 changed files with 154 additions and 545 deletions

View file

@ -1,42 +0,0 @@
name: Deploy static content to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Set up Node.js
uses: actions/setup-node@v3
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: |
pnpm install
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

1
.gitignore vendored
View file

@ -1,2 +1 @@
node_modules node_modules
.idea

8
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="de.php_perfect.intellij.ddev.settings.DdevSettingsState">
<option name="ddevBinary" value="C:\Program Files\DDEV\ddev.exe" />
</component>
</project>

14
.idea/deployment.xml Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="Streckenkunde Digital (DDEV)">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

7
.idea/durst-rechner.iml Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="FLOW" />
</component>
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/durst-rechner.iml" filepath="$PROJECT_DIR$/.idea/durst-rechner.iml" />
</modules>
</component>
</project>

19
.idea/php.xml Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -6,33 +6,9 @@ ist auch offline verfügbar und kann ohne Internetverbindung genutzt werden (Ser
Einrichtung wird eine Internetverbindung empfohlen (das einmalige Aufrufen der Seite im Browser mit einer Internetverbindung Einrichtung wird eine Internetverbindung empfohlen (das einmalige Aufrufen der Seite im Browser mit einer Internetverbindung
genügt). genügt).
## Installation
```bash
# Installieren der Abhängigkeiten
pnpm install
```
## Entwicklung ## Entwicklung
```bash ```bash
# Startet den Entwicklungsserver # Startet den Entwicklungsserver
npx http-server -o . npx http-server -o .
``` ```
## Screenshots
<details>
<summary>Desktop Ansicht / Tablet</summary>
![2025-04-06 01_31_13-Durstrechner](https://github.com/user-attachments/assets/c3f120cf-8b7b-42ee-8be5-5a697fe57fcb)
</details>
<details>
<summary>Smartphone Ansicht</summary>
![2025-04-06 01_30_54-Durstrechner](https://github.com/user-attachments/assets/8eabc119-d776-43e5-ac14-2a38a2006aa6)
</details>

View file

@ -1,11 +0,0 @@
import './theme.js'
import './calculator.js'
import './../../node_modules/bootstrap/dist/js/bootstrap.min.js'
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('./assets/script/service-worker.js')
.then(reg => console.log('Service Worker installiert', reg))
.catch(err => console.error('Service Worker fehlgeschlagen', err));
});
}

View file

@ -1,283 +0,0 @@
class TemplateElement {
static getProductTemplate() {
const element = document.querySelector('[data-template=product]').cloneNode(true);
return this.removeTemplate(element);
}
static getCartLineTemplate() {
const element = document.querySelector('[data-template=product-line-item]').cloneNode(true);
return this.removeTemplate(element);
}
static getSettingsProductTemplate() {
const element = document.querySelector('[data-template=settings-product]').cloneNode(true);
return this.removeTemplate(element);
}
static removeTemplate(element) {
element.removeAttribute('data-template');
return element;
}
}
class Product {
constructor(name, price, image) {
this.id = Product.generateId();
this.name = name;
this.price = price;
this.image = image;
}
static generateId() {
return Math.floor(Math.random() * 1000000000) * Math.floor(Math.random() * 1000000000);
}
getProductElement() {
const productElement = TemplateElement.getProductTemplate();
productElement.setAttribute('data-id', this.id);
productElement.querySelector('[data-attr=name]').textContent = this.name;
productElement.querySelector('[data-attr=image]').src = this.image;
productElement.addEventListener('click', () => {
cart.addProduct(this);
})
return productElement;
}
getSettingsProductElement() {
const productElement = TemplateElement.getSettingsProductTemplate();
productElement.setAttribute('data-id', this.id);
productElement.textContent = this.name;
productElement.addEventListener('click', () => {
productList.removeProduct(this);
productList.setExportField();
productElement.remove();
})
return productElement;
}
}
class CartLine {
constructor(product, quantity) {
this.product = product;
this.quantity = quantity;
}
}
class ProductList {
constructor() {
this.loadFromJson(localStorage.getItem('products') ?? [])
this.setExportField();
this.getImportExportTextarea().addEventListener('input', (e) => {
try {
const json = e.target.value;
localStorage.setItem('products', json);
window.location = window.location;
} catch (e) {
console.error(e);
}
});
}
setExportField() {
this.getImportExportTextarea().textContent = JSON.stringify(this.products);
}
loadFromJson(json) {
let parse = [];
try {
parse = JSON.parse(json);
} catch (e) {}
this.products = parse.map(e => new Product(e.name, e.price, e.image));
this.renderProductList();
}
getImportExportTextarea() {
return document.querySelector('#input-export-import');
}
getProductListElement() {
return document.querySelector('.product-list');
}
getSettingListElement() {
return document.querySelector('.settings-product-list');
}
renderProductList() {
// Clear the product list element before rendering except the template
this.getProductListElement().querySelectorAll('[data-id]').forEach(e => {
if (e.getAttribute('data-template') === null) {
e.remove();
}
});
this.products.forEach(e => {
this.getProductListElement().appendChild(e.getProductElement());
this.getSettingListElement().appendChild(e.getSettingsProductElement());
})
}
addProduct(product) {
this.products.push(product);
localStorage.setItem('products', JSON.stringify(this.products));
this.getProductListElement().appendChild(product.getProductElement());
this.getSettingListElement().appendChild(product.getSettingsProductElement());
this.setExportField();
}
removeProduct(product) {
this.products = this.products.filter(e => e.id !== product.id);
const productElement = this.getProductListElement().querySelector(`[data-id="${product.id}"]`);
if (productElement) {
productElement.remove();
}
localStorage.setItem('products', JSON.stringify(this.products));
this.setExportField();
}
}
class Cart {
constructor() {
this.cartLines = [];
this.getCartButton().addEventListener('click', () => {
this.cartLines = [];
this.renderCart();
});
}
addProduct(product) {
const cartLine = this.cartLines.find(e => e.product.id === product.id);
if (cartLine) {
cartLine.quantity++;
} else {
this.cartLines.push(new CartLine(product, 1));
}
this.renderCart();
}
removeProduct(product) {
const cartLine = this.cartLines.find(e => e.product.id === product.id);
if (cartLine) {
cartLine.quantity--;
if (cartLine.quantity <= 0) {
this.cartLines = this.cartLines.filter(e => e.product.id !== product.id);
}
}
this.renderCart();
}
renderCart() {
// Clear the cart element before rendering except the template
this.getCartElement().querySelectorAll('[data-id]').forEach(e => {
if (e.getAttribute('data-template') === null) {
e.remove();
}
});
if(this.cartLines.length === 0) {
this.getAlertElement().classList.remove('d-none');
} else {
this.getAlertElement().classList.add('d-none');
}
this.calculateCartValue();
// Render each cart line
this.cartLines.forEach(cartLine => {
const cartLineElement = this.getCartLineElement(cartLine);
this.getCartElement().appendChild(cartLineElement);
});
}
getCartLineElement(cartLine) {
const cartLineElement = TemplateElement.getCartLineTemplate();
cartLineElement.setAttribute('data-id', cartLine.product.id);
cartLineElement.querySelector('[data-attr=name]').textContent = cartLine.product.name;
cartLineElement.querySelector('[data-attr=value]').textContent = Cart.getNumberFormatter().format(cartLine.product.price);
cartLineElement.querySelector('[data-attr=quantity]').textContent = cartLine.quantity;
cartLineElement.addEventListener('click', () => {
this.removeProduct(cartLine.product);
})
return cartLineElement;
}
getAlertElement() {
return document.querySelector('.alert.cart-empty');
}
getCartElement() {
return document.querySelector('.cart-items');
}
calculateCartValue() {
let cartValue = this.cartLines.reduce((acc, cartLine) => {
return acc + (cartLine.product.price * cartLine.quantity);
}, 0);
this.getCartButton().querySelector('[data-total-value]').textContent = Cart.getNumberFormatter().format(cartValue);
}
static getNumberFormatter() {
return new Intl.NumberFormat('de-DE', {
currency: 'EUR',
minimumFractionDigits: 2
});
}
getCartButton() {
return document.querySelector('button.cart-value');
}
}
class Tab {
static toggleTab() {
if(!isSettingsTab) {
this.switchTab('settings');
} else {
this.switchTab('products');
}
isSettingsTab = !isSettingsTab;
}
static switchTab(tab) {
const tabs = document.querySelectorAll('[data-tab]');
tabs.forEach(e => {
e.classList.add('d-none');
});
const activeTab = document.querySelector(`[data-tab=${tab}]`);
activeTab.classList.remove('d-none');
}
}
let isSettingsTab = false;
const cart = new Cart();
const productList = new ProductList();
document.querySelector('[data-toggle-tab]').addEventListener('click', (e) => {
Tab.toggleTab();
});
document.querySelector('#create-product').addEventListener('click', (e) => {
e.preventDefault(); // Prevent form submission (if any)
const name = document.querySelector('#product-name').value;
let fieldPrice = document.querySelector('#product-price').value;
const image = document.querySelector('#product-image').files[0];
if(name.length === 0) {
alert('Name muss ausgefüllt sein');
return;
}
if (image) {
const reader = new FileReader();
reader.onloadend = function () {
const imageBase64 = reader.result;
const imageSrcWithBase64 = `data:${image.type};base64,${imageBase64.split(',')[1]}`;
productList.addProduct(new Product(name, fieldPrice, imageSrcWithBase64));
document.querySelector('#product-name').value = '';
document.querySelector('#product-price').value = '0,00';
document.querySelector('#product-image').value = '';
};
reader.readAsDataURL(image);
} else {
productList.addProduct(new Product(name, fieldPrice, `https://placehold.co/250x250?text={${name}}`));
document.querySelector('#product-name').value = '';
document.querySelector('#product-price').value = '';
document.querySelector('#product-image').value = '';
}
});

View file

@ -1,70 +0,0 @@
const CACHE_NAME = 'durst-rechner-v2';
const BASE_PATH = self.location.pathname.includes('DurstRechner') ? '/DurstRechner' : ''; // Handle GitHub Pages vs. Localhost
const FILES_TO_CACHE = [
`${BASE_PATH}/`,
`${BASE_PATH}/index.html`,
`${BASE_PATH}/assets/manifest.json`,
`${BASE_PATH}/assets/script/service-worker.js`,
`${BASE_PATH}/assets/style/stylesheet.css`,
`${BASE_PATH}/assets/script/all.js`,
`${BASE_PATH}/assets/script/calculator.js`,
`${BASE_PATH}/assets/script/theme.js`,
`${BASE_PATH}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
`${BASE_PATH}/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js`,
`${BASE_PATH}/node_modules/bootstrap-icons/font/bootstrap-icons.min.css`,
`${BASE_PATH}/node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2`,
`${BASE_PATH}/node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff`,
`${BASE_PATH}/assets/favicon/android-chrome-192x192.png`,
`${BASE_PATH}/assets/favicon/android-chrome-512x512.png`,
`${BASE_PATH}/assets/favicon/apple-touch-icon.png`,
`${BASE_PATH}/assets/favicon/favicon-16x16.png`,
`${BASE_PATH}/assets/favicon/favicon-32x32.png`,
`${BASE_PATH}/assets/favicon/favicon.ico`,
];
// Cache files with the correct base path
const FILES_TO_CACHE_PREFIXED = FILES_TO_CACHE;
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
const cachePromises = FILES_TO_CACHE_PREFIXED.map(url => {
return fetch(url).then(response => {
if (response.ok) {
return cache.put(url, response);
} else {
console.error(`Failed to fetch: ${url}`);
}
}).catch(err => {
console.error(`Failed to fetch (network error): ${url}`, err);
});
});
return Promise.all(cachePromises);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
});

View file

@ -1,39 +0,0 @@
.line-item-details {
.amount {
display: inline-block;
min-width: 1.5em;
}
.price {
display: inline-block;
min-width: 3em;
}
}
.product-list {
padding-bottom: 1em;
}
.currency-value::after {
content: ' €';
}
.quantity-value::after {
content: 'x ';
}
.name-value {
margin-left: 1em;
font-weight: bold;
}
[data-template] {
display: none !important;
}
.cart-items, .product-list, .alert, .navbar-brand, .settings-product-list {
user-select: none;
-webkit-user-select: none;
-webkit-user-drag: none;
}
img {
user-select: none;
-webkit-user-select: none;
-webkit-user-drag: none;
}
.product-box, .cart-items, .settings-product-list {
cursor: pointer;
}

View file

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 527 B

View file

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 966 B

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -4,103 +4,65 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="theme-color" content="#2196f3"/> <meta name="theme-color" content="#2196f3"/>
<link rel="manifest" href="assets/manifest.json"> <link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png"> <link rel="stylesheet" href="stylesheet.css">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png"> <link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="node_modules/bootstrap-icons/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="node_modules/bootstrap-icons/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/style/stylesheet.css"> <title>Der Durstrechner</title>
<title>Durstrechner</title>
</head> </head>
<body class="d-flex flex-column vh-100"> <body class="d-flex flex-column vh-100">
<nav class="navbar navbar-expand-lg border-bottom mb-3"> <nav class="navbar navbar-expand-lg border-bottom mb-3">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Der Durstrechner</a> <a class="navbar-brand" href="#">Durstrechner</a>
<form class="d-flex" role="search" method="dialog"> <form class="d-flex" role="search">
<button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> Tag / Nacht</button> <button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> Tag / Nacht</button>
<button class="btn" data-toggle-tab><i class="bi bi-gear"></i> Einstellungen</button> <button class="btn">Einstellungen</button>
</form> </form>
</div> </div>
</nav> </nav>
<main class="container-fluid flex-grow-1"> <div class="container-fluid">
<div data-tab="products" class="row"> <div class="row">
<div class="col-12 col-md-4"> <div class="col-3">
<li class="list-group-item d-flex justify-content-between" data-template="product-line-item"> <ul class="list-group d-none">
<span class="line-item-details"> <li class="list-group-item">Test</li>
<span class="amount quantity-value" data-attr="quantity">5</span> </ul>
<span class="price currency-value" data-attr="value">2,50</span> <div class="alert alert-info">Keine Produkte ausgewählt</div>
<span class="name name-value" data-attr="name">Wasser mit Kohlensäure</span>
</span>
<i class="bi bi-dash-circle"></i>
</li>
<ul class="list-group cart-items"></ul>
<div class="alert alert-info cart-empty">Keine Produkte ausgewählt</div>
<hr> <hr>
<button class="btn btn-secondary btn-lg w-100 cart-value mb-3"> <button class="btn btn-secondary btn-lg w-100">
<span class="currency-value" data-total-value>0,00</span> <span class="currency-value" data-total-value>0,00</span>
</button> </button>
</div> </div>
<div class="col-12 col-md-8"> <div class="col-9">
<div class="row row-cols-4 row-gap-3 product-list h-100 overflow-auto"> <div class="row row-cols-6 row-gap-3">
<div class="col product-box" data-template="product" data-id="0"> <div class="col product-box">
<div class="card"> <div class="card">
<img data-attr="image" src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top" loading="lazy"> <img src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top">
<div class="card-body"> <div class="card-body">
<span data-attr="name">Wasser mit Kohlensäure</span> <span>Wasser mit Kohlensäure</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div data-tab="settings" class="row d-none">
<div class="row">
<div class="col">
<form class="card mb-3" method="dialog" id="new-product">
<h5 class="card-header">Neues Produkt</h5>
<div class="card-body">
<div class="mb-2">
<label for="product-name" class="form-label">Name des Produkts</label>
<input type="text" class="form-control" placeholder="Tolles Produkt" id="product-name" required>
</div> </div>
<div class="mb-2">
<label for="product-price" class="form-label">Preis</label>
<input type="number" class="form-control" step="0.01" value="0.00" id="product-price" required>
</div>
<div class="mb-2">
<label for="product-image" class="form-label">Bild auswählen</label>
<input type="file" class="form-control" placeholder="Tolles Produkt" id="product-image">
</div>
<input type="submit" class="btn btn-success" value="Speichern" id="create-product">
</div>
</form>
<div class="card">
<h5 class="card-header">Export / Import</h5>
<div class="card-body">
<label class="w-100">
Daten importieren oder exportieren (Kopieren und Einfügen)
<textarea class="form-control mt-1" id="input-export-import"></textarea>
</label>
</div>
</div>
</div>
<div class="col">
<div class="card">
<h5 class="card-header">Produkte entfernen</h5>
<div class="card-body">
<li class="list-group-item" data-template="settings-product">Test</li>
<ul class="list-group settings-product-list">
</ul>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="assets/script/all.js" type="module"></script> <script src="node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="script/all.js" type="module"></script>
<script>
// Install the service worker for offline capabilities
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
.then(reg => console.log('Service Worker registered', reg))
.catch(err => console.error('SW registration failed', err));
});
}
</script>
</body> </body>
</html> </html>

1
script/all.js Normal file
View file

@ -0,0 +1 @@
import './theme.js'

0
script/calculator.js Normal file
View file

39
service-worker.js Normal file
View file

@ -0,0 +1,39 @@
const CACHE_NAME = 'durst-rechner-v2';
const FILES_TO_CACHE = [
'/',
'/index.html',
'/manifest.json',
'/service-worker.js',
'/stylesheet.css',
'/script/all.js',
'/script/calculator.js',
'/script/theme.js',
'/node_modules/bootstrap/dist/css/bootstrap.min.css',
'/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js',
'/node_modules/bootstrap-icons/font/bootstrap-icons.min.css',
'/node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2',
'/node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff',
'/favicon/android-chrome-192x192.png',
'/favicon/android-chrome-512x512.png',
'/favicon/apple-touch-icon.png',
'/favicon/favicon-16x16.png',
'/favicon/favicon-32x32.png',
'/favicon/favicon.ico',
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(FILES_TO_CACHE);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});

3
stylesheet.css Normal file
View file

@ -0,0 +1,3 @@
.currency-value::after {
content: ' €';
}