├── .gitignore
├── .vscode
└── settings.json
├── my-static-site
├── .gitignore
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── client.js
│ ├── components
│ │ └── Nav.svelte
│ ├── routes
│ │ ├── _error.svelte
│ │ ├── _layout.svelte
│ │ ├── blog
│ │ │ ├── [slug].json.js
│ │ │ ├── [slug].svelte
│ │ │ ├── _posts.js
│ │ │ ├── index.json.js
│ │ │ └── index.svelte
│ │ ├── dogs
│ │ │ ├── [name].json.js
│ │ │ ├── [name].svelte
│ │ │ ├── dogs.json
│ │ │ ├── index.json.js
│ │ │ └── index.svelte
│ │ ├── index.svelte
│ │ ├── paper.svelte
│ │ ├── rock.svelte
│ │ ├── rps.svelte
│ │ └── scissors.svelte
│ ├── server.js
│ ├── service-worker.js
│ └── template.html
└── static
│ ├── favicon.png
│ ├── global.css
│ ├── images
│ ├── german-shorthaired-pointer-old.jpg
│ ├── german-shorthaired-pointer.jpg
│ ├── native-american-indian-dog-old.jpg
│ ├── native-american-indian-dog.jpg
│ ├── paper.jpg
│ ├── rock.jpg
│ ├── scissors.jpg
│ ├── treeing-walker-coonhound-old.jpg
│ ├── treeing-walker-coonhound.jpg
│ ├── whippet-old.jpg
│ └── whippet.jpg
│ ├── logo-192.png
│ ├── logo-512.png
│ └── manifest.json
├── package.json
├── sanitize-html
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ ├── index.html
│ ├── sanitize-html.min.js
│ └── star.png
├── rollup.config.js
└── src
│ ├── App.svelte
│ └── main.js
├── sapper-shopping
├── .gitignore
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── client.js
│ ├── components
│ │ └── Nav.svelte
│ ├── items.js
│ ├── routes
│ │ ├── _error.svelte
│ │ ├── _layout.svelte
│ │ ├── blog
│ │ │ ├── [slug].json.js
│ │ │ ├── [slug].svelte
│ │ │ ├── _posts.js
│ │ │ ├── index.json.js
│ │ │ └── index.svelte
│ │ ├── cart.svelte
│ │ ├── index.svelte
│ │ └── ship.svelte
│ ├── server.js
│ ├── service-worker.js
│ ├── stores.js
│ └── template.html
└── static
│ ├── favicon.png
│ ├── global.css
│ ├── great-success.png
│ ├── logo-192.png
│ ├── logo-512.png
│ └── manifest.json
├── svelte-shop-hash-routing
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Cart.svelte
│ ├── NavButton.svelte
│ ├── NotFound.svelte
│ ├── Ship.svelte
│ ├── Shop.svelte
│ ├── items.js
│ ├── main.js
│ └── stores.js
├── svelte-shop-manual-routing
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Cart.svelte
│ ├── NavButton.svelte
│ ├── Ship.svelte
│ ├── Shop.svelte
│ ├── items.js
│ ├── main.js
│ └── stores.js
├── svelte-shop-page-routing
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Cart.svelte
│ ├── NotFound.svelte
│ ├── Ship.svelte
│ ├── Shop.svelte
│ ├── items.js
│ ├── main.js
│ └── stores.js
├── svelte-todo
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── LICENSE
├── README.md
├── babel.config.js
├── cypress.json
├── cypress
│ └── integration
│ │ └── TodoList.spec.js
├── jest.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── ShippingLabel.svelte
│ ├── Todo.svelte
│ ├── TodoList.spec.js
│ ├── TodoList.svelte
│ ├── main.js
│ └── todo.spec.js
├── travel-packing-ch10
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── dialog-polyfill.css
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Dialog.svelte
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── NotFound.svelte
│ ├── main.js
│ └── util.js
├── travel-packing-ch12
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── addons.js
│ └── config.js
├── .vscode
│ └── settings.json
├── README.md
├── babel.config.js
├── cypress.json
├── cypress
│ ├── examples
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── travel-packing.spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── jest.config.js
├── package-lock.json
├── package.json
├── public
│ ├── dialog-polyfill.css
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
├── src
│ ├── App.svelte
│ ├── Category.spec.js
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Dialog.svelte
│ ├── Item.spec.js
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── NotFound.svelte
│ ├── __snapshots__
│ │ ├── Category.spec.js.snap
│ │ └── Item.spec.js.snap
│ ├── main.js
│ └── util.js
└── stories
│ ├── Category.stories.js
│ ├── Checklist.stories.js
│ ├── Dialog.stories.js
│ ├── DialogWrapper.svelte
│ ├── Item.stories.js
│ ├── Login.stories.js
│ └── StyleWrapper.svelte
├── travel-packing-ch13
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── addons.js
│ └── config.js
├── .vscode
│ └── settings.json
├── Dockerfile
├── README.md
├── babel.config.js
├── cypress.json
├── cypress
│ ├── examples
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── travel-packing.spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── jest.config.js
├── package-lock.json
├── package.json
├── public
│ ├── dialog-polyfill.css
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
├── server
│ ├── package-lock.json
│ ├── package.json
│ └── server.js
├── src
│ ├── App.svelte
│ ├── Category.spec.js
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Dialog.svelte
│ ├── Item.spec.js
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── NotFound.svelte
│ ├── __snapshots__
│ │ ├── Category.spec.js.snap
│ │ └── Item.spec.js.snap
│ ├── main.js
│ └── util.js
└── stories
│ ├── Category.stories.js
│ ├── Checklist.stories.js
│ ├── Dialog.stories.js
│ ├── DialogWrapper.svelte
│ ├── Item.stories.js
│ ├── Login.stories.js
│ └── StyleWrapper.svelte
├── travel-packing-ch14
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── addons.js
│ └── config.js
├── .vscode
│ └── settings.json
├── README.md
├── babel.config.js
├── cypress.json
├── cypress
│ ├── examples
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── travel-packing.spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── jest.config.js
├── package.json
├── public
│ ├── dialog-polyfill.css
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
├── server
│ ├── package-lock.json
│ ├── package.json
│ └── server.js
├── src
│ ├── App.svelte
│ ├── Baskets.svelte
│ ├── Category.spec.js
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Dialog.svelte
│ ├── Item.spec.js
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── NotFound.svelte
│ ├── __snapshots__
│ │ ├── Category.spec.js.snap
│ │ └── Item.spec.js.snap
│ ├── main.js
│ └── util.js
└── stories
│ ├── Category.stories.js
│ ├── Checklist.stories.js
│ ├── Dialog.stories.js
│ ├── DialogWrapper.svelte
│ ├── Item.stories.js
│ ├── Login.stories.js
│ └── StyleWrapper.svelte
├── travel-packing-ch16
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── package.json
├── rollup.config.js
├── src
│ ├── client.js
│ ├── components
│ │ ├── Category.svelte
│ │ ├── Dialog.svelte
│ │ ├── Item.svelte
│ │ └── NotFound.svelte
│ ├── routes
│ │ ├── _error.svelte
│ │ ├── _layout.svelte
│ │ ├── checklist.svelte
│ │ └── index.svelte
│ ├── server.js
│ ├── service-worker.js
│ ├── template.html
│ └── util.js
└── static
│ ├── favicon.png
│ ├── global.css
│ ├── logo-192.png
│ ├── logo-512.png
│ └── manifest.json
├── travel-packing-ch17
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── client.js
│ ├── components
│ │ ├── Category.svelte
│ │ ├── Dialog.svelte
│ │ ├── Item.svelte
│ │ └── NotFound.svelte
│ ├── routes
│ │ ├── _error.svelte
│ │ ├── _layout.svelte
│ │ ├── categories
│ │ │ ├── [categoryId]
│ │ │ │ ├── index.json.js
│ │ │ │ └── items
│ │ │ │ │ ├── [itemId].json.js
│ │ │ │ │ └── index.json.js
│ │ │ ├── _helpers.js
│ │ │ └── index.json.js
│ │ ├── checklist.svelte
│ │ └── index.svelte
│ ├── server.js
│ ├── service-worker.js
│ ├── template.html
│ └── util.js
└── static
│ ├── favicon.png
│ ├── global.css
│ ├── logo-192.png
│ ├── logo-512.png
│ └── manifest.json
├── travel-packing-ch19
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── integration
│ │ └── spec.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── client.js
│ ├── components
│ │ ├── Category.svelte
│ │ ├── Dialog.svelte
│ │ ├── Item.svelte
│ │ └── NotFound.svelte
│ ├── routes
│ │ ├── _error.svelte
│ │ ├── _layout.svelte
│ │ ├── categories
│ │ │ ├── [categoryId]
│ │ │ │ ├── index.json.js
│ │ │ │ └── items
│ │ │ │ │ ├── [itemId].json.js
│ │ │ │ │ └── index.json.js
│ │ │ ├── _helpers.js
│ │ │ └── index.json.js
│ │ ├── checklist.svelte
│ │ └── index.svelte
│ ├── server.js
│ ├── service-worker.js
│ ├── template.html
│ └── util.js
└── static
│ ├── favicon.png
│ ├── global.css
│ ├── logo-192.png
│ ├── logo-512.png
│ └── manifest.json
├── travel-packing-ch3
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Login.svelte
│ └── main.js
├── travel-packing-ch4
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── report.20191218.160736.57199.0.001.json
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── main.js
│ └── util.js
├── travel-packing-ch5
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── package.json
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── main.js
│ └── util.js
├── travel-packing-ch7
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── README.md
├── package.json
├── public
│ ├── dialog-polyfill.css
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ ├── Category.svelte
│ ├── Checklist.svelte
│ ├── Dialog.svelte
│ ├── Item.svelte
│ ├── Login.svelte
│ ├── main.js
│ └── util.js
└── travel-packing-ch9
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── package-lock.json
├── package.json
├── public
├── dialog-polyfill.css
├── favicon.png
├── global.css
└── index.html
├── rollup.config.js
└── src
├── App.svelte
├── Category.svelte
├── Checklist.svelte
├── Dialog.svelte
├── Item.svelte
├── Login.svelte
├── NotFound.svelte
├── main.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .*.swp
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "uuidv"
4 | ]
5 | }
--------------------------------------------------------------------------------
/my-static-site/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /cypress/screenshots/
6 | /__sapper__/
7 |
--------------------------------------------------------------------------------
/my-static-site/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/my-static-site/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/my-static-site/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct
', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/my-static-site/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/my-static-site/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/my-static-site/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/my-static-site/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
--------------------------------------------------------------------------------
/my-static-site/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
29 |
30 |
31 | {status}
32 |
33 |
34 | {status}
35 |
36 |
{error.message}
37 |
38 | {#if dev && error.stack}
39 | {error.stack}
40 | {/if}
41 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/blog/[slug].json.js:
--------------------------------------------------------------------------------
1 | import posts from './_posts.js';
2 |
3 | const lookup = new Map();
4 | posts.forEach(post => {
5 | lookup.set(post.slug, JSON.stringify(post));
6 | });
7 |
8 | export function get(req, res, next) {
9 | // the `slug` parameter is available because
10 | // this file is called [slug].json.js
11 | const { slug } = req.params;
12 |
13 | if (lookup.has(slug)) {
14 | res.writeHead(200, {
15 | 'Content-Type': 'application/json'
16 | });
17 |
18 | res.end(lookup.get(slug));
19 | } else {
20 | res.writeHead(404, {
21 | 'Content-Type': 'application/json'
22 | });
23 |
24 | res.end(JSON.stringify({
25 | message: `Not found`
26 | }));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/blog/index.json.js:
--------------------------------------------------------------------------------
1 | import posts from './_posts.js';
2 |
3 | const contents = JSON.stringify(posts.map(post => {
4 | return {
5 | title: post.title,
6 | slug: post.slug
7 | };
8 | }));
9 |
10 | export function get(req, res) {
11 | res.writeHead(200, {
12 | 'Content-Type': 'application/json'
13 | });
14 |
15 | res.end(contents);
16 | }
--------------------------------------------------------------------------------
/my-static-site/src/routes/blog/index.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
19 |
20 |
21 | Blog
22 |
23 |
24 | Recent posts
25 |
26 |
27 | {#each posts as post}
28 |
32 | - {post.title}
33 | {/each}
34 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/dogs/[name].json.js:
--------------------------------------------------------------------------------
1 | import dogs from './dogs.json';
2 |
3 | export function get(req, res, next) {
4 | const {name} = req.params;
5 | const dog = dogs[name];
6 | // The next line is used to test the res.ok value in [name].svelte.
7 | //const bad = JSON.parse('invalid json');
8 |
9 | if (dog) {
10 | res.end(JSON.stringify(dog));
11 | } else {
12 | const error = {message: `${name} not found`};
13 | res.statusCode = 404;
14 | res.end(JSON.stringify(error));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/dogs/index.json.js:
--------------------------------------------------------------------------------
1 | import dogs from './dogs.json';
2 | const names = Object.values(dogs).map(dog => dog.name).sort();
3 |
4 | export function get(req, res) {
5 | res.end(JSON.stringify(names));
6 | }
7 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Home
7 |
8 |
9 | Purpose
10 |
11 |
12 | This is a Sapper app that can be used
13 | to demonstrate exporting a static site.
14 |
15 |
16 |
17 | Hello, {name}!
--------------------------------------------------------------------------------
/my-static-site/src/routes/paper.svelte:
--------------------------------------------------------------------------------
1 |
2 | Paper
3 |
4 |
5 | Paper
6 |
7 | I beat rock.
8 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/rock.svelte:
--------------------------------------------------------------------------------
1 |
2 | Rock
3 |
4 |
5 | Rock
6 |
7 | I beat scissors.
8 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/rps.svelte:
--------------------------------------------------------------------------------
1 |
2 | Rock Paper Scissors
3 |
4 |
5 | Rock Paper Scissors
6 | This is a game for two players.
7 | Meet rock!
8 |
--------------------------------------------------------------------------------
/my-static-site/src/routes/scissors.svelte:
--------------------------------------------------------------------------------
1 |
2 | Scissor
3 |
4 |
5 | Scissors
6 |
7 | I beat paper.
8 |
--------------------------------------------------------------------------------
/my-static-site/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 |
6 | const { PORT, NODE_ENV } = process.env;
7 | const dev = NODE_ENV === 'development';
8 |
9 | polka() // You can also use Express
10 | .use(
11 | compression({ threshold: 0 }),
12 | sirv('static', { dev }),
13 | sapper.middleware()
14 | )
15 | .listen(PORT, err => {
16 | if (err) console.log('error', err);
17 | });
18 |
--------------------------------------------------------------------------------
/my-static-site/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 | %sapper.html%
27 |
28 |
31 | %sapper.scripts%
32 |
33 |
34 |
--------------------------------------------------------------------------------
/my-static-site/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/favicon.png
--------------------------------------------------------------------------------
/my-static-site/static/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
4 | font-size: 14px;
5 | line-height: 1.5;
6 | color: #333;
7 | }
8 |
9 | h1, h2, h3, h4, h5, h6 {
10 | margin: 0 0 0.5em 0;
11 | font-weight: 400;
12 | line-height: 1.2;
13 | }
14 |
15 | h1 {
16 | font-size: 2em;
17 | }
18 |
19 | a {
20 | color: inherit;
21 | }
22 |
23 | code {
24 | font-family: menlo, inconsolata, monospace;
25 | font-size: calc(1em - 2px);
26 | color: #555;
27 | background-color: #f0f0f0;
28 | padding: 0.2em 0.4em;
29 | border-radius: 2px;
30 | }
31 |
32 | @media (min-width: 400px) {
33 | body {
34 | font-size: 16px;
35 | }
36 | }
--------------------------------------------------------------------------------
/my-static-site/static/images/german-shorthaired-pointer-old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/german-shorthaired-pointer-old.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/german-shorthaired-pointer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/german-shorthaired-pointer.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/native-american-indian-dog-old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/native-american-indian-dog-old.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/native-american-indian-dog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/native-american-indian-dog.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/paper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/paper.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/rock.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/rock.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/scissors.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/scissors.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/treeing-walker-coonhound-old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/treeing-walker-coonhound-old.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/treeing-walker-coonhound.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/treeing-walker-coonhound.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/whippet-old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/whippet-old.jpg
--------------------------------------------------------------------------------
/my-static-site/static/images/whippet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/images/whippet.jpg
--------------------------------------------------------------------------------
/my-static-site/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/logo-192.png
--------------------------------------------------------------------------------
/my-static-site/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/my-static-site/static/logo-512.png
--------------------------------------------------------------------------------
/my-static-site/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "TODO",
5 | "short_name": "TODO",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "for-zeit-now",
3 | "version": "1.0.0",
4 | "description": "tells ZEIT Now how to build the app",
5 | "scripts": {
6 | "build": "cd travel-packing-ch12; npm install; npm run build; cp -r public .."
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/mvolkmann/svelte-and-sapper-in-action.git"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "bugs": {
15 | "url": "https://github.com/mvolkmann/svelte-and-sapper-in-action/issues"
16 | },
17 | "homepage": "https://github.com/mvolkmann/svelte-and-sapper-in-action#readme"
18 | }
19 |
--------------------------------------------------------------------------------
/sanitize-html/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | public/build
4 |
--------------------------------------------------------------------------------
/sanitize-html/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sanitize-html/README.md
--------------------------------------------------------------------------------
/sanitize-html/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv public"
8 | },
9 | "devDependencies": {
10 | "rollup": "^1.12.0",
11 | "rollup-plugin-commonjs": "^10.0.0",
12 | "rollup-plugin-livereload": "^1.0.0",
13 | "rollup-plugin-node-resolve": "^5.2.0",
14 | "rollup-plugin-svelte": "^5.0.3",
15 | "rollup-plugin-terser": "^5.1.2",
16 | "svelte": "^3.0.0"
17 | },
18 | "dependencies": {
19 | "sanitize-html": "^1.20.1",
20 | "sirv-cli": "^0.4.4"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sanitize-html/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sanitize-html/public/favicon.png
--------------------------------------------------------------------------------
/sanitize-html/public/global.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | color: #333;
9 | margin: 0;
10 | padding: 8px;
11 | box-sizing: border-box;
12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13 | }
14 |
15 | a {
16 | color: rgb(0,100,200);
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | a:visited {
25 | color: rgb(0,80,160);
26 | }
27 |
28 | label {
29 | display: block;
30 | }
31 |
32 | input, button, select, textarea {
33 | font-family: inherit;
34 | font-size: inherit;
35 | padding: 0.4em;
36 | margin: 0 0 0.5em 0;
37 | box-sizing: border-box;
38 | border: 1px solid #ccc;
39 | border-radius: 2px;
40 | }
41 |
42 | input:disabled {
43 | color: #ccc;
44 | }
45 |
46 | input[type="range"] {
47 | height: 0;
48 | }
49 |
50 | button {
51 | color: #333;
52 | background-color: #f4f4f4;
53 | outline: none;
54 | }
55 |
56 | button:disabled {
57 | color: #999;
58 | }
59 |
60 | button:not(:disabled):active {
61 | background-color: #ddd;
62 | }
63 |
64 | button:focus {
65 | border-color: #666;
66 | }
67 |
--------------------------------------------------------------------------------
/sanitize-html/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Svelte app
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sanitize-html/public/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sanitize-html/public/star.png
--------------------------------------------------------------------------------
/sanitize-html/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | var app = new App({
4 | target: document.body
5 | });
6 |
7 | export default app;
--------------------------------------------------------------------------------
/sapper-shopping/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /cypress/screenshots/
6 | /__sapper__/
7 |
--------------------------------------------------------------------------------
/sapper-shopping/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/sapper-shopping/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/sapper-shopping/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct ', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/sapper-shopping/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/sapper-shopping/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/sapper-shopping/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/sapper-shopping/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TODO",
3 | "description": "TODO",
4 | "version": "0.0.1",
5 | "scripts": {
6 | "dev": "sapper dev",
7 | "build": "sapper build --legacy",
8 | "cy:open": "cypress open",
9 | "cy:run": "cypress run",
10 | "export": "sapper export --legacy",
11 | "start": "node __sapper__/build",
12 | "reinstall": "rm -rf package-lock.json node_modules; npm install",
13 | "test": "run-p --race dev cy:run"
14 | },
15 | "dependencies": {
16 | "compression": "^1.7.4",
17 | "polka": "next",
18 | "sirv": "^0.4.2"
19 | },
20 | "devDependencies": {
21 | "npm-run-all": "^4.1.5",
22 | "sapper": "^0.27.12",
23 | "svelte": "^3.22.2",
24 | "@babel/core": "^7.9.6",
25 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
26 | "@babel/plugin-transform-runtime": "^7.9.6",
27 | "@babel/preset-env": "^7.9.6",
28 | "@babel/runtime": "^7.9.6",
29 | "@rollup/plugin-commonjs": "^12.0.0",
30 | "@rollup/plugin-node-resolve": "^7.1.3",
31 | "@rollup/plugin-replace": "^2.3.2",
32 | "rollup": "^2.8.2",
33 | "rollup-plugin-babel": "^4.4.0",
34 | "rollup-plugin-svelte": "^5.2.1",
35 | "rollup-plugin-terser": "^5.3.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/sapper-shopping/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
--------------------------------------------------------------------------------
/sapper-shopping/src/items.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {description: 'socks', price: 7.0},
3 | {description: 'boots', price: 99.0},
4 | {description: 'gloves', price: 15.0},
5 | {description: 'hat', price: 10.0},
6 | {description: 'scarf', price: 20.0}
7 | ];
8 |
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
29 |
30 |
31 | {status}
32 |
33 |
34 | {status}
35 |
36 |
{error.message}
37 |
38 | {#if dev && error.stack}
39 | {error.stack}
40 | {/if}
41 |
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/blog/[slug].json.js:
--------------------------------------------------------------------------------
1 | import posts from './_posts.js';
2 |
3 | const lookup = new Map();
4 | posts.forEach(post => {
5 | lookup.set(post.slug, JSON.stringify(post));
6 | });
7 |
8 | export function get(req, res, next) {
9 | // the `slug` parameter is available because
10 | // this file is called [slug].json.js
11 | const { slug } = req.params;
12 |
13 | if (lookup.has(slug)) {
14 | res.writeHead(200, {
15 | 'Content-Type': 'application/json'
16 | });
17 |
18 | res.end(lookup.get(slug));
19 | } else {
20 | res.writeHead(404, {
21 | 'Content-Type': 'application/json'
22 | });
23 |
24 | res.end(JSON.stringify({
25 | message: `Not found`
26 | }));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/blog/index.json.js:
--------------------------------------------------------------------------------
1 | import posts from './_posts.js';
2 |
3 | const contents = JSON.stringify(posts.map(post => {
4 | return {
5 | title: post.title,
6 | slug: post.slug
7 | };
8 | }));
9 |
10 | export function get(req, res) {
11 | res.writeHead(200, {
12 | 'Content-Type': 'application/json'
13 | });
14 |
15 | res.end(contents);
16 | }
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/blog/index.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 |
19 |
20 |
21 | Blog
22 |
23 |
24 | Recent posts
25 |
26 |
27 | {#each posts as post}
28 |
32 | - {post.title}
33 | {/each}
34 |
--------------------------------------------------------------------------------
/sapper-shopping/src/routes/cart.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | Cart
12 |
13 |
14 | Cart
15 |
16 | {#if $cartStore.length === 0}
17 | empty
18 | {:else}
19 |
20 |
21 | Description |
22 | Quantity |
23 | Price |
24 |
25 | {#each $cartStore as item}
26 |
27 | {item.description} |
28 | {item.quantity} |
29 | ${item.price.toFixed(2)} |
30 |
31 | {/each}
32 |
33 | |
34 | |
35 | ${total.toFixed(2)} |
36 |
37 |
38 | {/if}
39 |
--------------------------------------------------------------------------------
/sapper-shopping/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 |
6 | const { PORT, NODE_ENV } = process.env;
7 | const dev = NODE_ENV === 'development';
8 |
9 | polka() // You can also use Express
10 | .use(
11 | compression({ threshold: 0 }),
12 | sirv('static', { dev }),
13 | sapper.middleware()
14 | )
15 | .listen(PORT, err => {
16 | if (err) console.log('error', err);
17 | });
18 |
--------------------------------------------------------------------------------
/sapper-shopping/src/stores.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const cartStore = writable([]);
4 |
--------------------------------------------------------------------------------
/sapper-shopping/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 | %sapper.html%
27 |
28 |
31 | %sapper.scripts%
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sapper-shopping/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sapper-shopping/static/favicon.png
--------------------------------------------------------------------------------
/sapper-shopping/static/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin: 0;
4 | }
5 |
6 | h1 {
7 | color: red;
8 | margin-top: 0;
9 | }
10 |
11 | input {
12 | border: solid lightgray 1px;
13 | border-radius: 4px;
14 | padding: 4px;
15 | }
16 |
17 | label {
18 | font-weight: bold;
19 | }
20 |
21 | table {
22 | border-collapse: collapse;
23 | }
24 |
25 | td,
26 | th {
27 | border: solid lightgray 1px;
28 | padding: 5px 10px;
29 | }
30 |
--------------------------------------------------------------------------------
/sapper-shopping/static/great-success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sapper-shopping/static/great-success.png
--------------------------------------------------------------------------------
/sapper-shopping/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sapper-shopping/static/logo-192.png
--------------------------------------------------------------------------------
/sapper-shopping/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/sapper-shopping/static/logo-512.png
--------------------------------------------------------------------------------
/sapper-shopping/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "TODO",
5 | "short_name": "TODO",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv public"
8 | },
9 | "devDependencies": {
10 | "@rollup/plugin-commonjs": "^11.0.0",
11 | "@rollup/plugin-node-resolve": "^7.0.0",
12 | "rollup": "^1.20.0",
13 | "rollup-plugin-livereload": "^1.0.0",
14 | "rollup-plugin-svelte": "^5.0.3",
15 | "rollup-plugin-terser": "^5.1.2",
16 | "svelte": "^3.0.0"
17 | },
18 | "dependencies": {
19 | "sirv-cli": "^0.4.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/svelte-shop-hash-routing/public/favicon.png
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin: 0;
4 | }
5 |
6 | h1 {
7 | margin-top: 0;
8 | }
9 |
10 | input {
11 | border: solid lightgray 1px;
12 | border-radius: 4px;
13 | padding: 4px;
14 | }
15 |
16 | label {
17 | font-weight: bold;
18 | }
19 |
20 | table {
21 | border-collapse: collapse;
22 | }
23 |
24 | td,
25 | th {
26 | border: solid lightgray 1px;
27 | padding: 5px 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/Cart.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | Cart
8 |
9 | {#if $cartStore.length === 0}
10 | empty
11 | {:else}
12 |
13 |
14 | Description |
15 | Quantity |
16 | Price |
17 |
18 | {#each $cartStore as item}
19 |
20 | {item.description} |
21 | {item.quantity} |
22 | ${item.price.toFixed(2)} |
23 |
24 | {/each}
25 |
26 | |
27 | |
28 | ${total.toFixed(2)} |
29 |
30 |
31 | {/if}
32 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/NavButton.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | Page Not Found
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/items.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {description: 'socks', price: 7.0},
3 | {description: 'boots', price: 99.0},
4 | {description: 'gloves', price: 15.0},
5 | {description: 'hat', price: 10.0},
6 | {description: 'scarf', price: 20.0}
7 | ];
8 |
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | props: {
6 | name: 'world'
7 | }
8 | });
9 |
10 | export default app;
--------------------------------------------------------------------------------
/svelte-shop-hash-routing/src/stores.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const cartStore = writable([]);
4 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv public"
8 | },
9 | "devDependencies": {
10 | "@rollup/plugin-commonjs": "^11.0.0",
11 | "@rollup/plugin-node-resolve": "^6.0.0",
12 | "rollup": "^1.20.0",
13 | "rollup-plugin-livereload": "^1.0.0",
14 | "rollup-plugin-svelte": "^5.0.3",
15 | "rollup-plugin-terser": "^5.1.2",
16 | "svelte": "^3.0.0"
17 | },
18 | "dependencies": {
19 | "sirv-cli": "^0.4.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/svelte-shop-manual-routing/public/favicon.png
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin: 0;
4 | }
5 |
6 | h1 {
7 | margin-top: 0;
8 | }
9 |
10 | input {
11 | border: solid lightgray 1px;
12 | border-radius: 4px;
13 | padding: 4px;
14 | }
15 |
16 | label {
17 | font-weight: bold;
18 | }
19 |
20 | table {
21 | border-collapse: collapse;
22 | }
23 |
24 | td,
25 | th {
26 | border: solid lightgray 1px;
27 | padding: 5px 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/src/Cart.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | Cart
8 |
9 | {#if $cartStore.length === 0}
10 | empty
11 | {:else}
12 |
13 |
14 | Description |
15 | Quantity |
16 | Price |
17 |
18 | {#each $cartStore as item}
19 |
20 | {item.description} |
21 | {item.quantity} |
22 | ${item.price.toFixed(2)} |
23 |
24 | {/each}
25 |
26 | |
27 | |
28 | ${total.toFixed(2)} |
29 |
30 |
31 | {/if}
32 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/src/NavButton.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/src/items.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {description: 'socks', price: 7.0},
3 | {description: 'boots', price: 99.0},
4 | {description: 'gloves', price: 15.0},
5 | {description: 'hat', price: 10.0},
6 | {description: 'scarf', price: 20.0}
7 | ];
8 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/svelte-shop-manual-routing/src/stores.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const cartStore = writable([]);
4 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | public/build
4 | public/bundle.*
5 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/README.md:
--------------------------------------------------------------------------------
1 | # svelte-routing
2 |
3 | This demonstrates using the page library to
4 | implement client-side page routing in a Svelte app.
5 |
6 | To run this,
7 |
8 | 1. `npm install`
9 | 1. `npm run dev`
10 | 1. browse localhost:5000
11 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
8 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
9 | "start": "sirv public --single"
10 | },
11 | "devDependencies": {
12 | "@rollup/plugin-node-resolve": "^6.0.0",
13 | "eslint": "^6.7.2",
14 | "eslint-plugin-import": "^2.19.1",
15 | "eslint-plugin-prettier": "^3.1.2",
16 | "eslint-plugin-svelte3": "^2.7.3",
17 | "prettier": "^1.19.1",
18 | "prettier-plugin-svelte": "^0.7.0",
19 | "rollup": "^1.12.0",
20 | "rollup-plugin-commonjs": "^10.0.0",
21 | "rollup-plugin-livereload": "^1.0.0",
22 | "rollup-plugin-svelte": "^5.0.3",
23 | "rollup-plugin-terser": "^5.1.3",
24 | "svelte": "^3.0.0"
25 | },
26 | "dependencies": {
27 | "page": "^1.11.5",
28 | "query-string": "^6.8.3",
29 | "sirv-cli": "^0.4.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/svelte-shop-page-routing/public/favicon.png
--------------------------------------------------------------------------------
/svelte-shop-page-routing/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin: 0;
4 | }
5 |
6 | h1 {
7 | margin-top: 0;
8 | }
9 |
10 | input {
11 | border: solid lightgray 1px;
12 | border-radius: 4px;
13 | padding: 4px;
14 | }
15 |
16 | label {
17 | font-weight: bold;
18 | }
19 |
20 | table {
21 | border-collapse: collapse;
22 | }
23 |
24 | td,
25 | th {
26 | border: solid lightgray 1px;
27 | padding: 5px 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/src/Cart.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | Cart
8 |
9 | {#if $cartStore.length === 0}
10 | empty
11 | {:else}
12 |
13 |
14 | Description |
15 | Quantity |
16 | Price |
17 |
18 | {#each $cartStore as item}
19 |
20 | {item.description} |
21 | {item.quantity} |
22 | ${item.price.toFixed(2)} |
23 |
24 | {/each}
25 |
26 | |
27 | |
28 | ${total.toFixed(2)} |
29 |
30 |
31 | {/if}
32 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | Page Not Found
2 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/src/items.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {description: 'socks', price: 7.0},
3 | {description: 'boots', price: 99.0},
4 | {description: 'gloves', price: 15.0},
5 | {description: 'hat', price: 10.0},
6 | {description: 'scarf', price: 20.0}
7 | ];
8 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/svelte-shop-page-routing/src/stores.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const cartStore = writable([]);
4 |
--------------------------------------------------------------------------------
/svelte-todo/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/svelte-todo/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /cypress/videos
3 | /node_modules
4 | /public/bundle.*
--------------------------------------------------------------------------------
/svelte-todo/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/svelte-todo/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Btns",
4 | "autobuild",
5 | "sirv"
6 | ]
7 | }
--------------------------------------------------------------------------------
/svelte-todo/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mark Volkmann
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 |
--------------------------------------------------------------------------------
/svelte-todo/README.md:
--------------------------------------------------------------------------------
1 | # Svelte Todo App
2 |
3 | To run this:
4 |
5 | - `npm install`
6 | - `npm run dev`
7 | - browse localhost:5000
8 |
9 | To run ESLint:
10 |
11 | - `npm run lint`
12 |
13 | To run Prettier:
14 |
15 | - `npm run format`
16 |
17 | To run unit tests:
18 |
19 | - `npm test`
20 |
21 | To run end-to-end tests:
22 |
23 | - `npm run cy:run`
24 |
--------------------------------------------------------------------------------
/svelte-todo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current'
8 | }
9 | }
10 | ]
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/svelte-todo/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/svelte-todo/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '^.+\\.js$': 'babel-jest',
4 | '^.+\\.svelte$': 'jest-transform-svelte'
5 | },
6 | moduleFileExtensions: ['js', 'svelte'],
7 | bail: false,
8 | verbose: true
9 | };
10 |
--------------------------------------------------------------------------------
/svelte-todo/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/svelte-todo/public/favicon.png
--------------------------------------------------------------------------------
/svelte-todo/public/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | position: relative;
4 | width: 100%;
5 | height: 100%;
6 | }
7 |
8 | body {
9 | color: #333;
10 | margin: 0;
11 | padding: 8px;
12 | box-sizing: border-box;
13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
14 | Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
15 | }
16 |
17 | a {
18 | color: rgb(0, 100, 200);
19 | text-decoration: none;
20 | }
21 |
22 | a:hover {
23 | text-decoration: underline;
24 | }
25 |
26 | a:visited {
27 | color: rgb(0, 80, 160);
28 | }
29 |
30 | label {
31 | display: block;
32 | }
33 |
34 | input,
35 | button,
36 | select,
37 | textarea {
38 | font-family: inherit;
39 | font-size: inherit;
40 | padding: 0.4em;
41 | margin: 0 0 0.5em 0;
42 | box-sizing: border-box;
43 | border: 1px solid #ccc;
44 | border-radius: 2px;
45 | }
46 |
47 | input:disabled {
48 | color: #ccc;
49 | }
50 |
51 | input[type='range'] {
52 | height: 0;
53 | }
54 |
55 | button {
56 | background-color: #f4f4f4;
57 | outline: none;
58 | }
59 |
60 | button:active {
61 | background-color: #ddd;
62 | }
63 |
64 | button:focus {
65 | border-color: #666;
66 | }
67 |
--------------------------------------------------------------------------------
/svelte-todo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/svelte-todo/src/ShippingLabel.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | unknown
11 |
12 |
--------------------------------------------------------------------------------
/svelte-todo/src/Todo.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
18 |
19 |
20 | dispatch('toggleDone')} />
24 | {todo.text}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/svelte-todo/src/main.js:
--------------------------------------------------------------------------------
1 | import TodoList from './TodoList.svelte';
2 |
3 | const app = new TodoList({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch10/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch10/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch10/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch10/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "dragstart",
4 | "keydown",
5 | "signup",
6 | "uuidv"
7 | ]
8 | }
--------------------------------------------------------------------------------
/travel-packing-ch10/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch10
2 |
3 | This is the chapter 10 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch10/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travel-packing",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "repository": "git@github.com:mvolkmann/svelte-and-sapper-in-action.git",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "dev": "rollup -c -w",
9 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
10 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
11 | "reinstall": "rm -rf node_modules package-lock.json && npm install",
12 | "start": "sirv public --single"
13 | },
14 | "dependencies": {
15 | "dialog-polyfill": "^0.5.0",
16 | "page": "^1.11.5",
17 | "sirv-cli": "^0.4.5",
18 | "uuid": "^7.0.3"
19 | },
20 | "devDependencies": {
21 | "@rollup/plugin-node-resolve": "^7.1.1",
22 | "eslint": "^6.8.0",
23 | "eslint-plugin-import": "^2.20.2",
24 | "eslint-plugin-prettier": "^3.1.2",
25 | "eslint-plugin-svelte3": "^2.7.3",
26 | "prettier": "^1.19.1",
27 | "prettier-plugin-svelte": "^0.7.0",
28 | "rollup": "^2.3.3",
29 | "rollup-plugin-commonjs": "^10.1.0",
30 | "rollup-plugin-livereload": "^1.1.0",
31 | "rollup-plugin-svelte": "^5.2.1",
32 | "rollup-plugin-terser": "^5.3.0",
33 | "svelte": "^3.20.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/travel-packing-ch10/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch10/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch10/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch10/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red border
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch10/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch10/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
28 |
29 |
54 |
--------------------------------------------------------------------------------
/travel-packing-ch10/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch10/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch10/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function isSafari() {
15 | const {userAgent} = navigator;
16 | const isChrome = userAgent.indexOf('Chrome') > -1;
17 | const isSafari = userAgent.indexOf('Safari') > -1;
18 | return isSafari && !isChrome;
19 | }
20 |
21 | export function sortOnName(array) {
22 | array.sort((el1, el2) =>
23 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
24 | );
25 | return array;
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.gitignore:
--------------------------------------------------------------------------------
1 | /cypress/screenshots
2 | /cypress/videos
3 | /node_modules/
4 | /public/build/
5 | /storybook-static
6 |
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/svelte';
2 |
3 | // automatically import all files ending in *.stories.js
4 | configure(require.context('../stories', true, /\.stories\.js$/), module);
5 |
--------------------------------------------------------------------------------
/travel-packing-ch12/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Btns",
4 | "dragstart",
5 | "keydown",
6 | "signup",
7 | "sirv"
8 | ]
9 | }
--------------------------------------------------------------------------------
/travel-packing-ch12/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch12
2 |
3 | This is the chapter 12 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch12/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current'
8 | }
9 | }
10 | ]
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/examples/waiting.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Waiting', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/waiting')
6 | })
7 | // BE CAREFUL of adding unnecessary wait times.
8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting
9 |
10 | // https://on.cypress.io/wait
11 | it('cy.wait() - wait for a specific amount of time', () => {
12 | cy.get('.wait-input1').type('Wait 1000ms after typing')
13 | cy.wait(1000)
14 | cy.get('.wait-input2').type('Wait 1000ms after typing')
15 | cy.wait(1000)
16 | cy.get('.wait-input3').type('Wait 1000ms after typing')
17 | cy.wait(1000)
18 | })
19 |
20 | it('cy.wait() - wait for a specific route', () => {
21 | cy.server()
22 |
23 | // Listen to GET to comments/1
24 | cy.route('GET', 'comments/*').as('getComment')
25 |
26 | // we have code that gets a comment when
27 | // the button is clicked in scripts.js
28 | cy.get('.network-btn').click()
29 |
30 | // wait for GET comments/1
31 | cy.wait('@getComment').its('status').should('eq', 200)
32 | })
33 |
34 | })
35 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/examples/window.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Window', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/window')
6 | })
7 |
8 | it('cy.window() - get the global window object', () => {
9 | // https://on.cypress.io/window
10 | cy.window().should('have.property', 'top')
11 | })
12 |
13 | it('cy.document() - get the document object', () => {
14 | // https://on.cypress.io/document
15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16 | })
17 |
18 | it('cy.title() - get the title', () => {
19 | // https://on.cypress.io/title
20 | cy.title().should('include', 'Kitchen Sink')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch12/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch12/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bail: false,
3 | moduleFileExtensions: ['js', 'svelte'],
4 | transform: {
5 | '^.+\\.js$': 'babel-jest',
6 | '^.+\\.svelte$': 'svelte-jester'
7 | },
8 | verbose: true
9 | };
10 |
--------------------------------------------------------------------------------
/travel-packing-ch12/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch12/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch12/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch12/public/global.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --heading-bg-color: #a3660a;
3 | --primary-color: #3f6fde;
4 | }
5 |
6 | body {
7 | background-color: var(--primary-color);
8 | font-family: sans-serif;
9 | height: 100vh;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 | button:not(:disabled),
15 | input:not(:disabled) {
16 | cursor: pointer;
17 | }
18 |
19 | button:disabled {
20 | color: lightgray;
21 | }
22 |
23 | button.icon {
24 | background-color: transparent;
25 | border: none;
26 | margin-bottom: 0;
27 | }
28 |
29 | input:disabled {
30 | color: #ccc;
31 | }
32 |
33 | /* This prevents Firefox from displaying a red border
34 | around required inputs that have no value. */
35 | input:invalid {
36 | box-shadow: none;
37 | }
38 |
39 | label {
40 | display: inline-block;
41 | }
42 |
43 | input,
44 | button,
45 | select,
46 | textarea {
47 | --padding: 10px;
48 |
49 | border-radius: var(--padding);
50 | border: none;
51 | box-sizing: border-box;
52 | color: black;
53 | font-family: inherit;
54 | font-size: inherit;
55 | margin: 0;
56 | padding: var(--padding);
57 | }
58 |
--------------------------------------------------------------------------------
/travel-packing-ch12/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/Item.spec.js:
--------------------------------------------------------------------------------
1 | import {cleanup, render} from '@testing-library/svelte';
2 |
3 | import Item from './Item.svelte';
4 |
5 | describe('Item', () => {
6 | const categoryId = 1;
7 | const dnd = {};
8 | const item = {id: 2, name: 'socks', packed: false};
9 |
10 | // Unmounts any components mounted in the previous test.
11 | afterEach(cleanup);
12 |
13 | test('should render', () => {
14 | const {getByTestId, getByText} = render(Item, {categoryId, dnd, item});
15 | const checkbox = document.querySelector('input[type="checkbox"]');
16 | expect(checkbox).not.toBeNull(); // found checkbox
17 | expect(getByText(item.name)); // found item name
18 | expect(getByTestId('delete')); // found delete button
19 | });
20 |
21 | test('should match snapshot', () => {
22 | const {container} = render(Item, {categoryId, dnd, item});
23 | expect(container).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
28 |
29 |
54 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/__snapshots__/Item.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Item should match snapshot 1`] = `
4 |
5 |
6 |
9 |
14 |
15 |
19 | socks
20 |
21 |
22 |
28 |
29 |
30 |
31 | `;
32 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch12/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/Category.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Category from '../src/Category.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Category'};
6 |
7 | function getOptions(items) {
8 | const category = {id: 1, name: 'Clothes', items};
9 | return {
10 | Component: Category,
11 | props: {
12 | category,
13 | categories: {[category.id]: category},
14 | dnd: {},
15 | show: 'all'
16 | },
17 | on: {delete: action('category delete dispatched')}
18 | };
19 | }
20 |
21 | export const empty = () => getOptions({});
22 | export const nonEmpty = () =>
23 | getOptions({
24 | 1: {id: 1, name: 'socks', packed: true},
25 | 2: {id: 2, name: 'shoes', packed: false}
26 | });
27 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/Checklist.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Checklist from '../src/Checklist.svelte';
3 | import StyleWrapper from './StyleWrapper.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Checklist'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Checklist,
12 | style: `
13 | background-color: var(--primary-color);
14 | color: white;
15 | height: 100vh;
16 | padding: 1rem
17 | `
18 | },
19 | on: {logout: action('logout dispatched')}
20 | });
21 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/Dialog.stories.js:
--------------------------------------------------------------------------------
1 | import DialogWrapper from './DialogWrapper.svelte';
2 | import '../public/global.css';
3 |
4 | export default {title: 'Dialog'};
5 |
6 | export const basic = () => ({Component: DialogWrapper});
7 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/DialogWrapper.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
30 |
31 |
49 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/Item.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Item from '../src/Item.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Item'};
6 |
7 | const getOptions = packed => ({
8 | Component: Item,
9 | props: {
10 | categoryId: 1,
11 | dnd: {},
12 | item: {id: 2, name: 'socks', packed}
13 | },
14 | on: {delete: action('item delete dispatched')}
15 | });
16 |
17 | export const unpacked = () => getOptions(false);
18 | export const packed = () => getOptions(true);
19 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/Login.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import StyleWrapper from './StyleWrapper.svelte';
3 | import Login from '../src/Login.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Login'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Login,
12 | style: `
13 | background-color: var(--primary-color);
14 | height: 100vh;
15 | padding: 1rem
16 | `
17 | },
18 | on: {login: action('login dispatched')}
19 | });
20 |
--------------------------------------------------------------------------------
/travel-packing-ch12/stories/StyleWrapper.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.gitignore:
--------------------------------------------------------------------------------
1 | /cypress/screenshots
2 | /cypress/videos
3 | /node_modules/
4 | /public/build/
5 | /storybook-static
6 |
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/svelte';
2 |
3 | // automatically import all files ending in *.stories.js
4 | configure(require.context('../stories', true, /\.stories\.js$/), module);
5 |
--------------------------------------------------------------------------------
/travel-packing-ch13/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Btns",
4 | "dragstart",
5 | "keydown",
6 | "signup",
7 | "sirv"
8 | ]
9 | }
--------------------------------------------------------------------------------
/travel-packing-ch13/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine
2 | WORKDIR /usr/src/app
3 | COPY package*.json ./
4 | RUN npm install
5 | COPY . .
6 | EXPOSE 5000
7 | ENV HOST=0.0.0.0
8 | CMD ["npm", "start"]
9 |
--------------------------------------------------------------------------------
/travel-packing-ch13/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch13
2 |
3 | This is the chapter 13 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch13/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current'
8 | }
9 | }
10 | ]
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/examples/waiting.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Waiting', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/waiting')
6 | })
7 | // BE CAREFUL of adding unnecessary wait times.
8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting
9 |
10 | // https://on.cypress.io/wait
11 | it('cy.wait() - wait for a specific amount of time', () => {
12 | cy.get('.wait-input1').type('Wait 1000ms after typing')
13 | cy.wait(1000)
14 | cy.get('.wait-input2').type('Wait 1000ms after typing')
15 | cy.wait(1000)
16 | cy.get('.wait-input3').type('Wait 1000ms after typing')
17 | cy.wait(1000)
18 | })
19 |
20 | it('cy.wait() - wait for a specific route', () => {
21 | cy.server()
22 |
23 | // Listen to GET to comments/1
24 | cy.route('GET', 'comments/*').as('getComment')
25 |
26 | // we have code that gets a comment when
27 | // the button is clicked in scripts.js
28 | cy.get('.network-btn').click()
29 |
30 | // wait for GET comments/1
31 | cy.wait('@getComment').its('status').should('eq', 200)
32 | })
33 |
34 | })
35 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/examples/window.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Window', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/window')
6 | })
7 |
8 | it('cy.window() - get the global window object', () => {
9 | // https://on.cypress.io/window
10 | cy.window().should('have.property', 'top')
11 | })
12 |
13 | it('cy.document() - get the document object', () => {
14 | // https://on.cypress.io/document
15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16 | })
17 |
18 | it('cy.title() - get the title', () => {
19 | // https://on.cypress.io/title
20 | cy.title().should('include', 'Kitchen Sink')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch13/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch13/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bail: false,
3 | moduleFileExtensions: ['js', 'svelte'],
4 | transform: {
5 | '^.+\\.js$': 'babel-jest',
6 | '^.+\\.svelte$': 'svelte-jester'
7 | },
8 | verbose: true
9 | };
10 |
--------------------------------------------------------------------------------
/travel-packing-ch13/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch13/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch13/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch13/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red border
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch13/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch13/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "static file server for Travel Packing app",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "dependencies": {
11 | "express": "^4.17.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/travel-packing-ch13/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 |
6 | app.use(express.static(path.resolve(__dirname + '/..', 'public')));
7 |
8 | const PORT = 1234; // can choose any port
9 | app.listen(PORT, () => console.log('listening on port', PORT));
10 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/Item.spec.js:
--------------------------------------------------------------------------------
1 | import {cleanup, render} from '@testing-library/svelte';
2 |
3 | import Item from './Item.svelte';
4 |
5 | describe('Item', () => {
6 | const categoryId = 1;
7 | const dnd = {};
8 | const item = {id: 2, name: 'socks', packed: false};
9 |
10 | // Unmounts any components mounted in the previous test.
11 | afterEach(cleanup);
12 |
13 | test('should render', () => {
14 | const {getByTestId, getByText} = render(Item, {categoryId, dnd, item});
15 | const checkbox = document.querySelector('input[type="checkbox"]');
16 | expect(checkbox).not.toBeNull(); // found checkbox
17 | expect(getByText(item.name)); // found item name
18 | expect(getByTestId('delete')); // found delete button
19 | });
20 |
21 | test('should match snapshot', () => {
22 | const {container} = render(Item, {categoryId, dnd, item});
23 | expect(container).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
28 |
29 |
54 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/__snapshots__/Item.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Item should match snapshot 1`] = `
4 |
5 |
6 |
9 |
14 |
15 |
19 | socks
20 |
21 |
22 |
28 |
29 |
30 |
31 | `;
32 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch13/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/Category.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Category from '../src/Category.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Category'};
6 |
7 | function getOptions(items) {
8 | const category = {id: 1, name: 'Clothes', items};
9 | return {
10 | Component: Category,
11 | props: {
12 | category,
13 | categories: [category],
14 | dnd: {},
15 | show: 'all'
16 | },
17 | on: {delete: action('category delete dispatched')}
18 | };
19 | }
20 |
21 | export const empty = () => getOptions({});
22 | export const nonEmpty = () =>
23 | getOptions({
24 | 1: {id: 1, name: 'socks', packed: true},
25 | 2: {id: 2, name: 'shoes', packed: false}
26 | });
27 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/Checklist.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Checklist from '../src/Checklist.svelte';
3 | import StyleWrapper from './StyleWrapper.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Checklist'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Checklist,
12 | style: `
13 | background-color: #3F6FDE;
14 | height: 100vh;
15 | padding: 1rem
16 | `
17 | },
18 | on: {logout: action('logout dispatched')}
19 | });
20 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/Dialog.stories.js:
--------------------------------------------------------------------------------
1 | import DialogWrapper from './DialogWrapper.svelte';
2 | import '../public/global.css';
3 |
4 | export default {title: 'Dialog'};
5 |
6 | export const basic = () => ({Component: DialogWrapper});
7 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/DialogWrapper.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
30 |
31 |
48 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/Item.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Item from '../src/Item.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Item'};
6 |
7 | const getOptions = packed => ({
8 | Component: Item,
9 | props: {
10 | categoryId: 1,
11 | dnd: {},
12 | item: {id: 2, name: 'socks', packed}
13 | },
14 | on: {delete: action('item delete dispatched')}
15 | });
16 |
17 | export const unpacked = () => getOptions(false);
18 | export const packed = () => getOptions(true);
19 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/Login.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import StyleWrapper from './StyleWrapper.svelte';
3 | import Login from '../src/Login.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Login'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Login,
12 | style: `
13 | background-color: #3F6FDE;
14 | height: 100vh;
15 | padding: 1rem
16 | `
17 | },
18 | on: {login: action('login dispatched')}
19 | });
20 |
--------------------------------------------------------------------------------
/travel-packing-ch13/stories/StyleWrapper.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.gitignore:
--------------------------------------------------------------------------------
1 | /cypress/screenshots
2 | /cypress/videos
3 | /node_modules/
4 | /public/build/
5 | /storybook-static
6 |
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/svelte';
2 |
3 | // automatically import all files ending in *.stories.js
4 | configure(require.context('../stories', true, /\.stories\.js$/), module);
5 |
--------------------------------------------------------------------------------
/travel-packing-ch14/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Btns",
4 | "dragstart",
5 | "keydown",
6 | "signup",
7 | "sirv"
8 | ]
9 | }
--------------------------------------------------------------------------------
/travel-packing-ch14/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch14
2 |
3 | This is the chapter 14 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch14/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current'
8 | }
9 | }
10 | ]
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/examples/waiting.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Waiting', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/waiting')
6 | })
7 | // BE CAREFUL of adding unnecessary wait times.
8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting
9 |
10 | // https://on.cypress.io/wait
11 | it('cy.wait() - wait for a specific amount of time', () => {
12 | cy.get('.wait-input1').type('Wait 1000ms after typing')
13 | cy.wait(1000)
14 | cy.get('.wait-input2').type('Wait 1000ms after typing')
15 | cy.wait(1000)
16 | cy.get('.wait-input3').type('Wait 1000ms after typing')
17 | cy.wait(1000)
18 | })
19 |
20 | it('cy.wait() - wait for a specific route', () => {
21 | cy.server()
22 |
23 | // Listen to GET to comments/1
24 | cy.route('GET', 'comments/*').as('getComment')
25 |
26 | // we have code that gets a comment when
27 | // the button is clicked in scripts.js
28 | cy.get('.network-btn').click()
29 |
30 | // wait for GET comments/1
31 | cy.wait('@getComment').its('status').should('eq', 200)
32 | })
33 |
34 | })
35 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/examples/window.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Window', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/window')
6 | })
7 |
8 | it('cy.window() - get the global window object', () => {
9 | // https://on.cypress.io/window
10 | cy.window().should('have.property', 'top')
11 | })
12 |
13 | it('cy.document() - get the document object', () => {
14 | // https://on.cypress.io/document
15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16 | })
17 |
18 | it('cy.title() - get the title', () => {
19 | // https://on.cypress.io/title
20 | cy.title().should('include', 'Kitchen Sink')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch14/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch14/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bail: false,
3 | moduleFileExtensions: ['js', 'svelte'],
4 | transform: {
5 | '^.+\\.js$': 'babel-jest',
6 | '^.+\\.svelte$': 'svelte-jester'
7 | },
8 | verbose: true
9 | };
10 |
--------------------------------------------------------------------------------
/travel-packing-ch14/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch14/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch14/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch14/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red border
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch14/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch14/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "static file server for Travel Packing app",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "dependencies": {
11 | "express": "^4.17.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/travel-packing-ch14/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 |
6 | app.use(express.static(path.resolve(__dirname + '/..', 'public')));
7 |
8 | const PORT = 1234; // can choose any port
9 | app.listen(PORT, () => console.log('listening on port', PORT));
10 |
--------------------------------------------------------------------------------
/travel-packing-ch14/src/Item.spec.js:
--------------------------------------------------------------------------------
1 | import {cleanup, render} from '@testing-library/svelte';
2 |
3 | import Item from './Item.svelte';
4 |
5 | describe('Item', () => {
6 | const categoryId = 1;
7 | const dnd = {};
8 | const item = {id: 2, name: 'socks', packed: false};
9 |
10 | // Unmounts any components mounted in the previous test.
11 | afterEach(cleanup);
12 |
13 | test('should render', () => {
14 | const {getByTestId, getByText} = render(Item, {categoryId, dnd, item});
15 | const checkbox = document.querySelector('input[type="checkbox"]');
16 | expect(checkbox).not.toBeNull(); // found checkbox
17 | expect(getByText(item.name)); // found item name
18 | expect(getByTestId('delete')); // found delete button
19 | });
20 |
21 | test('should match snapshot', () => {
22 | const {container} = render(Item, {categoryId, dnd, item});
23 | expect(container).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/travel-packing-ch14/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch14/src/__snapshots__/Item.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Item should match snapshot 1`] = `
4 |
5 |
6 |
10 |
14 |
15 |
18 | socks
19 |
20 |
21 |
27 |
28 |
29 |
30 | `;
31 |
--------------------------------------------------------------------------------
/travel-packing-ch14/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch14/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/Category.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Category from '../src/Category.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Category'};
6 |
7 | function getOptions(items) {
8 | const category = {id: 1, name: 'Clothes', items};
9 | return {
10 | Component: Category,
11 | props: {
12 | category,
13 | categories: [category],
14 | dnd: {},
15 | show: 'all'
16 | },
17 | on: {delete: action('category delete dispatched')}
18 | };
19 | }
20 |
21 | export const empty = () => getOptions({});
22 | export const nonEmpty = () =>
23 | getOptions({
24 | 1: {id: 1, name: 'socks', packed: true},
25 | 2: {id: 2, name: 'shoes', packed: false}
26 | });
27 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/Checklist.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Checklist from '../src/Checklist.svelte';
3 | import StyleWrapper from './StyleWrapper.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Checklist'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Checklist,
12 | style: `
13 | background-color: #3F6FDE;
14 | height: 100vh;
15 | padding: 1rem
16 | `
17 | },
18 | on: {logout: action('logout dispatched')}
19 | });
20 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/Dialog.stories.js:
--------------------------------------------------------------------------------
1 | import DialogWrapper from './DialogWrapper.svelte';
2 | import '../public/global.css';
3 |
4 | export default {title: 'Dialog'};
5 |
6 | export const basic = () => ({Component: DialogWrapper});
7 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/DialogWrapper.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
30 |
31 |
48 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/Item.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import Item from '../src/Item.svelte';
3 | import '../public/global.css';
4 |
5 | export default {title: 'Item'};
6 |
7 | const getOptions = packed => ({
8 | Component: Item,
9 | props: {
10 | categoryId: 1,
11 | dnd: {},
12 | item: {id: 2, name: 'socks', packed}
13 | },
14 | on: {delete: action('item delete dispatched')}
15 | });
16 |
17 | export const unpacked = () => getOptions(false);
18 | export const packed = () => getOptions(true);
19 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/Login.stories.js:
--------------------------------------------------------------------------------
1 | import {action} from '@storybook/addon-actions';
2 | import StyleWrapper from './StyleWrapper.svelte';
3 | import Login from '../src/Login.svelte';
4 | import '../public/global.css';
5 |
6 | export default {title: 'Login'};
7 |
8 | export const basic = () => ({
9 | Component: StyleWrapper,
10 | props: {
11 | component: Login,
12 | style: `
13 | background-color: #3F6FDE;
14 | height: 100vh;
15 | padding: 1rem
16 | `
17 | },
18 | on: {login: action('login dispatched')}
19 | });
20 |
--------------------------------------------------------------------------------
/travel-packing-ch14/stories/StyleWrapper.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/travel-packing-ch16/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch16/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /cypress/screenshots/
6 | /__sapper__/
7 |
--------------------------------------------------------------------------------
/travel-packing-ch16/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch16/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch16
2 |
3 | This is the chapter 16 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:3000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct ', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch16/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
6 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/components/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {status}
10 |
11 |
12 | {status}
13 |
14 |
{error.message}
15 |
16 | {#if dev && error.stack}
17 | {error.stack}
18 | {/if}
19 |
20 |
42 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 | Travel Packing Checklist
3 |
4 |
5 |
6 |
33 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 |
6 | const {PORT, NODE_ENV} = process.env;
7 | const dev = NODE_ENV === 'development';
8 |
9 | polka() // You can also use Express
10 | .use(compression({threshold: 0}), sirv('static', {dev}), sapper.middleware())
11 | .listen(PORT, err => {
12 | if (err) console.log('error', err);
13 | });
14 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 | %sapper.html%
27 |
28 |
31 | %sapper.scripts%
32 |
33 |
34 |
--------------------------------------------------------------------------------
/travel-packing-ch16/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch16/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch16/static/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch16/static/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .button {
9 | background-color: white;
10 | border-radius: 10px;
11 | color: gray;
12 | padding: 1rem;
13 | text-decoration: none;
14 | }
15 |
16 | button:not(:disabled),
17 | input:not(:disabled) {
18 | cursor: pointer;
19 | }
20 |
21 | button:disabled {
22 | color: lightgray;
23 | }
24 |
25 | button.icon {
26 | background-color: transparent;
27 | border: none;
28 | margin-bottom: 0;
29 | }
30 |
31 | input:disabled {
32 | color: #ccc;
33 | }
34 |
35 | /* This prevents Firefox from displaying a red border
36 | around required inputs that have no value. */
37 | input:invalid {
38 | box-shadow: none;
39 | }
40 |
41 | label {
42 | display: inline-block;
43 | }
44 |
45 | input,
46 | button,
47 | select,
48 | textarea {
49 | --padding: 10px;
50 |
51 | border-radius: var(--padding);
52 | border: none;
53 | box-sizing: border-box;
54 | color: gray;
55 | font-family: inherit;
56 | font-size: inherit;
57 | margin: 0;
58 | padding: var(--padding);
59 | }
60 |
--------------------------------------------------------------------------------
/travel-packing-ch16/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch16/static/logo-192.png
--------------------------------------------------------------------------------
/travel-packing-ch16/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch16/static/logo-512.png
--------------------------------------------------------------------------------
/travel-packing-ch16/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "travel-packing",
5 | "short_name": "travel-packing",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/travel-packing-ch17/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch17/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /cypress/screenshots/
6 | /__sapper__/
7 |
--------------------------------------------------------------------------------
/travel-packing-ch17/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch17/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "dragstart",
4 | "keydown",
5 | "preload",
6 | "sirv"
7 | ]
8 | }
--------------------------------------------------------------------------------
/travel-packing-ch17/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch17
2 |
3 | This is the chapter 17 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:3000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct ', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch17/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
6 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/components/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {status}
10 |
11 |
12 | {status}
13 |
14 |
{error.message}
15 |
16 | {#if dev && error.stack}
17 | {error.stack}
18 | {/if}
19 |
20 |
42 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 | Travel Packing Checklist
3 |
4 |
5 |
6 |
34 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/routes/categories/[categoryId]/items/index.json.js:
--------------------------------------------------------------------------------
1 | const {ObjectId} = require('mongodb');
2 | const send = require('@polka/send-type');
3 | import {getCollection} from '../../_helpers';
4 |
5 | // This adds an item to a category.
6 | export async function post(req, res) {
7 | const {categoryId} = req.params;
8 | const item = req.body;
9 | try {
10 | const collection = await getCollection();
11 | const itemPath = `items.${item.id}`;
12 | await collection.updateOne(
13 | {_id: ObjectId(categoryId)},
14 | {$set: {[itemPath]: item}}
15 | );
16 | res.end();
17 | } catch (e) {
18 | console.error('categories/[categoryId]/items/index.json.js post:', e);
19 | send(res, 500, {error: e});
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/routes/categories/_helpers.js:
--------------------------------------------------------------------------------
1 | const {MongoClient} = require('mongodb');
2 |
3 | const url = 'mongodb://127.0.0.1:27017';
4 | const options = {useNewUrlParser: true, useUnifiedTopology: true};
5 | let collection;
6 |
7 | export async function getCollection() {
8 | if (!collection) {
9 | const client = await MongoClient.connect(url, options);
10 | const db = client.db('travel-packing');
11 | collection = await db.collection('categories');
12 | }
13 | return collection;
14 | }
15 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/routes/categories/index.json.js:
--------------------------------------------------------------------------------
1 | const send = require('@polka/send-type');
2 | import {getCollection} from './_helpers';
3 |
4 | // This gets all the categories.
5 | export async function get(req, res) {
6 | try {
7 | const collection = await getCollection();
8 | const result = await collection.find().toArray();
9 | res.end(JSON.stringify(result));
10 | } catch (e) {
11 | console.error('categories/index.json.js get:', e);
12 | send(res, 500, {error: e});
13 | }
14 | }
15 |
16 | // This adds a category.
17 | export async function post(req, res) {
18 | const category = req.body;
19 | try {
20 | const collection = await getCollection();
21 | const result = await collection.insertOne(category);
22 | const [obj] = result.ops;
23 | res.end(JSON.stringify(obj));
24 | } catch (e) {
25 | console.error('categories/index.json.js post:', e);
26 | send(res, 500, {error: e});
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 | const {json} = require('body-parser');
6 |
7 | const {PORT, NODE_ENV} = process.env;
8 | const dev = NODE_ENV === 'development';
9 |
10 | polka() // You can also use Express
11 | .use(json())
12 | .use(compression({threshold: 0}), sirv('static', {dev}), sapper.middleware())
13 | .listen(PORT, err => {
14 | if (err) console.error('error', err);
15 | });
16 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 | %sapper.html%
27 |
28 |
31 | %sapper.scripts%
32 |
33 |
34 |
--------------------------------------------------------------------------------
/travel-packing-ch17/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch17/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch17/static/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch17/static/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .button {
9 | background-color: white;
10 | border-radius: 10px;
11 | color: gray;
12 | padding: 1rem;
13 | text-decoration: none;
14 | }
15 |
16 | button:not(:disabled),
17 | input:not(:disabled) {
18 | cursor: pointer;
19 | }
20 |
21 | button:disabled {
22 | color: lightgray;
23 | }
24 |
25 | button.icon {
26 | background-color: transparent;
27 | border: none;
28 | margin-bottom: 0;
29 | }
30 |
31 | input:disabled {
32 | color: #ccc;
33 | }
34 |
35 | /* This prevents Firefox from displaying a red border
36 | around required inputs that have no value. */
37 | input:invalid {
38 | box-shadow: none;
39 | }
40 |
41 | label {
42 | display: inline-block;
43 | }
44 |
45 | input,
46 | button,
47 | select,
48 | textarea {
49 | --padding: 10px;
50 |
51 | border-radius: var(--padding);
52 | border: none;
53 | box-sizing: border-box;
54 | color: gray;
55 | font-family: inherit;
56 | font-size: inherit;
57 | margin: 0;
58 | padding: var(--padding);
59 | }
60 |
--------------------------------------------------------------------------------
/travel-packing-ch17/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch17/static/logo-192.png
--------------------------------------------------------------------------------
/travel-packing-ch17/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch17/static/logo-512.png
--------------------------------------------------------------------------------
/travel-packing-ch17/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "travel-packing",
5 | "short_name": "travel-packing",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/travel-packing-ch19/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch19/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules/
3 | /src/node_modules/@sapper/
4 | yarn-error.log
5 | /cypress/screenshots/
6 | /__sapper__/
7 |
--------------------------------------------------------------------------------
/travel-packing-ch19/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch19/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "dragstart",
4 | "keydown",
5 | "preload",
6 | "sirv",
7 | "uuidv"
8 | ]
9 | }
--------------------------------------------------------------------------------
/travel-packing-ch19/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch19
2 |
3 | This is the chapter 19 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:3000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000",
3 | "video": false
4 | }
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress/integration/spec.js:
--------------------------------------------------------------------------------
1 | describe('Sapper template app', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | });
5 |
6 | it('has the correct ', () => {
7 | cy.contains('h1', 'Great success!')
8 | });
9 |
10 | it('navigates to /about', () => {
11 | cy.get('nav a').contains('about').click();
12 | cy.url().should('include', '/about');
13 | });
14 |
15 | it('navigates to /blog', () => {
16 | cy.get('nav a').contains('blog').click();
17 | cy.url().should('include', '/blog');
18 | });
19 | });
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/travel-packing-ch19/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/client.js:
--------------------------------------------------------------------------------
1 | import * as sapper from '@sapper/app';
2 |
3 | sapper.start({
4 | target: document.querySelector('#sapper')
5 | });
6 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/components/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/routes/_error.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {status}
10 |
11 |
12 | {status}
13 |
14 |
{error.message}
15 |
16 | {#if dev && error.stack}
17 | {error.stack}
18 | {/if}
19 |
20 |
42 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | {title}
17 |
18 |
19 |
20 |
47 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/routes/categories/[categoryId]/items/index.json.js:
--------------------------------------------------------------------------------
1 | const {ObjectId} = require('mongodb');
2 | const send = require('@polka/send-type');
3 | import {getCollection} from '../../_helpers';
4 |
5 | // This adds an item to a category.
6 | export async function post(req, res) {
7 | const {categoryId} = req.params;
8 | const item = req.body;
9 | try {
10 | const collection = await getCollection();
11 | const itemPath = `items.${item.id}`;
12 | await collection.updateOne(
13 | {_id: ObjectId(categoryId)},
14 | {$set: {[itemPath]: item}}
15 | );
16 | res.end();
17 | } catch (e) {
18 | console.error('categories/[categoryId]/items/index.json.js post:', e);
19 | send(res, 500, {error: e});
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/routes/categories/_helpers.js:
--------------------------------------------------------------------------------
1 | const {MongoClient} = require('mongodb');
2 |
3 | const url = 'mongodb://127.0.0.1:27017';
4 | const options = {useNewUrlParser: true, useUnifiedTopology: true};
5 | let collection;
6 |
7 | export async function getCollection() {
8 | if (!collection) {
9 | const client = await MongoClient.connect(url, options);
10 | const db = client.db('travel-packing');
11 | collection = await db.collection('categories');
12 | }
13 | return collection;
14 | }
15 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/routes/categories/index.json.js:
--------------------------------------------------------------------------------
1 | const send = require('@polka/send-type');
2 | import {getCollection} from './_helpers';
3 |
4 | // This gets all the categories.
5 | export async function get(req, res) {
6 | try {
7 | const collection = await getCollection();
8 | const result = await collection.find().toArray();
9 | res.end(JSON.stringify(result));
10 | } catch (e) {
11 | console.error('categories/index.json.js get:', e);
12 | send(res, 500, {error: e});
13 | }
14 | }
15 |
16 | // This adds a category.
17 | export async function post(req, res) {
18 | const category = req.body;
19 | try {
20 | const collection = await getCollection();
21 | const result = await collection.insertOne(category);
22 | const [obj] = result.ops;
23 | res.end(JSON.stringify(obj));
24 | } catch (e) {
25 | console.error('categories/index.json.js post:', e);
26 | send(res, 500, {error: e});
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/server.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import polka from 'polka';
3 | import compression from 'compression';
4 | import * as sapper from '@sapper/server';
5 | const {json} = require('body-parser');
6 |
7 | const {PORT, NODE_ENV} = process.env;
8 | const dev = NODE_ENV === 'development';
9 |
10 | polka() // You can also use Express
11 | .use(json())
12 | .use(compression({threshold: 0}), sirv('static', {dev}), sapper.middleware())
13 | .listen(PORT, err => {
14 | if (err) console.error('error', err);
15 | });
16 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 | %sapper.html%
27 |
28 |
31 | %sapper.scripts%
32 |
33 |
34 |
--------------------------------------------------------------------------------
/travel-packing-ch19/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export async function fetchPlus(path, options = {}) {
4 | if (navigator.onLine) return fetch(path, options);
5 |
6 | alert(`This operation is not available while offline.`);
7 | return {offline: true};
8 | }
9 |
10 | export function getGuid() {
11 | return uuidv4();
12 | }
13 |
14 | export function blurOnKey(event) {
15 | const {code} = event;
16 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
17 | event.target.blur();
18 | }
19 | }
20 |
21 | export function sortOnName(array) {
22 | array.sort((el1, el2) =>
23 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
24 | );
25 | return array;
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch19/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch19/static/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch19/static/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .button {
9 | background-color: white;
10 | border-radius: 10px;
11 | color: gray;
12 | padding: 1rem;
13 | text-decoration: none;
14 | }
15 |
16 | button:not(:disabled),
17 | input:not(:disabled) {
18 | cursor: pointer;
19 | }
20 |
21 | button:disabled {
22 | color: lightgray;
23 | }
24 |
25 | button.icon {
26 | background-color: transparent;
27 | border: none;
28 | margin-bottom: 0;
29 | }
30 |
31 | input:disabled {
32 | color: #ccc;
33 | }
34 |
35 | /* This prevents Firefox from displaying a red border
36 | around required inputs that have no value. */
37 | input:invalid {
38 | box-shadow: none;
39 | }
40 |
41 | label {
42 | display: inline-block;
43 | }
44 |
45 | input,
46 | button,
47 | select,
48 | textarea {
49 | --padding: 10px;
50 |
51 | border-radius: var(--padding);
52 | border: none;
53 | box-sizing: border-box;
54 | color: gray;
55 | font-family: inherit;
56 | font-size: inherit;
57 | margin: 0;
58 | padding: var(--padding);
59 | }
60 |
--------------------------------------------------------------------------------
/travel-packing-ch19/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch19/static/logo-192.png
--------------------------------------------------------------------------------
/travel-packing-ch19/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch19/static/logo-512.png
--------------------------------------------------------------------------------
/travel-packing-ch19/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#333333",
4 | "name": "travel-packing",
5 | "short_name": "travel-packing",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/travel-packing-ch3/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch3/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch3/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch3/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch3
2 |
3 | This is the initial, chapter 3 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travel-packing",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "repository": "git@github.com:mvolkmann/svelte-and-sapper-in-action.git",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "dev": "rollup -c -w",
9 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
10 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
11 | "reinstall": "rm -rf node_modules package-lock.json && npm install",
12 | "start": "sirv public"
13 | },
14 | "dependencies": {
15 | "sirv-cli": "^0.4.5"
16 | },
17 | "devDependencies": {
18 | "@rollup/plugin-node-resolve": "^7.1.1",
19 | "eslint": "^6.8.0",
20 | "eslint-plugin-import": "^2.20.2",
21 | "eslint-plugin-prettier": "^3.1.2",
22 | "eslint-plugin-svelte3": "^2.7.3",
23 | "prettier": "^1.19.1",
24 | "prettier-plugin-svelte": "^0.7.0",
25 | "rollup": "^2.3.3",
26 | "rollup-plugin-commonjs": "^10.1.0",
27 | "rollup-plugin-livereload": "^1.1.0",
28 | "rollup-plugin-svelte": "^5.2.1",
29 | "rollup-plugin-terser": "^5.3.0",
30 | "svelte": "^3.20.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/travel-packing-ch3/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch3/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch3/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch3/src/App.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Travel Packing Checklist
7 |
8 |
9 |
10 |
37 |
--------------------------------------------------------------------------------
/travel-packing-ch3/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
25 |
26 |
51 |
--------------------------------------------------------------------------------
/travel-packing-ch3/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch4/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch4/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch4/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch4/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch4
2 |
3 | This is the chapter 4 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travel-packing",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
8 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
9 | "reinstall": "rm -rf node_modules package-lock.json && npm install",
10 | "start": "sirv public"
11 | },
12 | "dependencies": {
13 | "sirv-cli": "^0.4.5",
14 | "uuid": "^7.0.3"
15 | },
16 | "devDependencies": {
17 | "@rollup/plugin-node-resolve": "^7.1.1",
18 | "eslint": "^6.8.0",
19 | "eslint-plugin-import": "^2.20.2",
20 | "eslint-plugin-prettier": "^3.1.2",
21 | "eslint-plugin-svelte3": "^2.7.3",
22 | "prettier": "^1.19.1",
23 | "prettier-plugin-svelte": "^0.7.0",
24 | "rollup": "^2.3.3",
25 | "rollup-plugin-commonjs": "^10.1.0",
26 | "rollup-plugin-livereload": "^1.1.0",
27 | "rollup-plugin-svelte": "^5.2.1",
28 | "rollup-plugin-terser": "^5.3.0",
29 | "svelte": "^3.20.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/travel-packing-ch4/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch4/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch4/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red box shadow
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch4/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch4/src/App.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | Travel Packing Checklist
8 |
9 |
10 |
11 |
12 |
39 |
--------------------------------------------------------------------------------
/travel-packing-ch4/src/Item.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 | {#if editing}
12 |
13 | (editing = false)}
17 | on:keydown={blurOnKey}
18 | type="text" />
19 | {:else}
20 | (editing = true)}>
21 | {item.name}
22 |
23 | {/if}
24 |
25 |
26 |
27 |
57 |
--------------------------------------------------------------------------------
/travel-packing-ch4/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
25 |
26 |
51 |
--------------------------------------------------------------------------------
/travel-packing-ch4/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch4/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch5/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch5/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch5/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch5/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch5
2 |
3 | This is the chapter 5 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travel-packing",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "repository": "git@github.com:mvolkmann/svelte-and-sapper-in-action.git",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "dev": "rollup -c -w",
9 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
10 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
11 | "reinstall": "rm -rf node_modules package-lock.json && npm install",
12 | "start": "sirv public"
13 | },
14 | "dependencies": {
15 | "sirv-cli": "^0.4.5",
16 | "uuid": "^7.0.3"
17 | },
18 | "devDependencies": {
19 | "@rollup/plugin-node-resolve": "^7.1.1",
20 | "eslint": "^6.8.0",
21 | "eslint-plugin-import": "^2.20.2",
22 | "eslint-plugin-prettier": "^3.1.2",
23 | "eslint-plugin-svelte3": "^2.7.3",
24 | "prettier": "^1.19.1",
25 | "prettier-plugin-svelte": "^0.7.0",
26 | "rollup": "^2.3.3",
27 | "rollup-plugin-commonjs": "^10.1.0",
28 | "rollup-plugin-livereload": "^1.1.0",
29 | "rollup-plugin-svelte": "^5.2.1",
30 | "rollup-plugin-terser": "^5.3.0",
31 | "svelte": "^3.20.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/travel-packing-ch5/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch5/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch5/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red box shadow
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch5/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch5/src/App.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Travel Packing Checklist
10 |
11 | {#if page === Login}
12 | (page = Checklist)} />
13 | {:else}
14 | (page = Login)} />
15 | {/if}
16 |
17 |
18 |
45 |
--------------------------------------------------------------------------------
/travel-packing-ch5/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
28 |
29 |
54 |
--------------------------------------------------------------------------------
/travel-packing-ch5/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch5/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch7/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch7/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch7/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch7/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "dragover",
4 | "dragstart",
5 | "keydown",
6 | "signup"
7 | ]
8 | }
--------------------------------------------------------------------------------
/travel-packing-ch7/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch7
2 |
3 | This is the chapter 7 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch7/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "travel-packing",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "repository": "git@github.com:mvolkmann/svelte-and-sapper-in-action.git",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "dev": "rollup -c -w",
9 | "format": "prettier --write 'src/**/*.{css,html,js,svelte}'",
10 | "lint": "eslint --fix --quiet src --ext .js,.svelte",
11 | "reinstall": "rm -rf node_modules package-lock.json && npm install",
12 | "start": "sirv public"
13 | },
14 | "dependencies": {
15 | "dialog-polyfill": "^0.5.0",
16 | "sirv-cli": "^0.4.5",
17 | "uuid": "^7.0.3"
18 | },
19 | "devDependencies": {
20 | "@rollup/plugin-node-resolve": "^7.1.1",
21 | "eslint": "^6.8.0",
22 | "eslint-plugin-import": "^2.20.2",
23 | "eslint-plugin-prettier": "^3.1.2",
24 | "eslint-plugin-svelte3": "^2.7.3",
25 | "prettier": "^1.19.1",
26 | "prettier-plugin-svelte": "^0.7.0",
27 | "rollup": "^2.3.3",
28 | "rollup-plugin-commonjs": "^10.1.0",
29 | "rollup-plugin-livereload": "^1.1.0",
30 | "rollup-plugin-svelte": "^5.2.1",
31 | "rollup-plugin-terser": "^5.3.0",
32 | "svelte": "^3.20.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/travel-packing-ch7/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch7/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch7/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch7/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red box shadow
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch7/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch7/src/App.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Travel Packing Checklist
10 |
11 | {#if page === Login}
12 | (page = Checklist)} />
13 | {:else}
14 | (page = Login)} />
15 | {/if}
16 |
17 |
18 |
45 |
--------------------------------------------------------------------------------
/travel-packing-ch7/src/Login.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
28 |
29 |
54 |
--------------------------------------------------------------------------------
/travel-packing-ch7/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch7/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------
/travel-packing-ch9/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:import/recommended"],
9 | "globals": {
10 | "cy": "readonly"
11 | },
12 | "overrides": [
13 | {
14 | "files": ["**/*.svelte"],
15 | "processor": "svelte3/svelte3"
16 | }
17 | ],
18 | "parserOptions": {
19 | "ecmaVersion": 2019,
20 | "sourceType": "module"
21 | },
22 | "plugins": ["import", "svelte3"],
23 | "rules": {
24 | "no-console": "off"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/travel-packing-ch9/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/travel-packing-ch9/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "svelteSortOrder": "scripts-markup-styles"
5 | }
6 |
--------------------------------------------------------------------------------
/travel-packing-ch9/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "dragstart",
4 | "keydown",
5 | "signup"
6 | ]
7 | }
--------------------------------------------------------------------------------
/travel-packing-ch9/README.md:
--------------------------------------------------------------------------------
1 | # travel-packing-ch9
2 |
3 | This is the chapter 9 version of the travel packing app.
4 |
5 | To run this:
6 |
7 | 1. `npm install`
8 | 2. `npm run dev`
9 | 3. browse localhost:5000
10 |
--------------------------------------------------------------------------------
/travel-packing-ch9/public/dialog-polyfill.css:
--------------------------------------------------------------------------------
1 | dialog {
2 | position: absolute;
3 | left: 0; right: 0;
4 | width: -moz-fit-content;
5 | width: -webkit-fit-content;
6 | width: fit-content;
7 | height: -moz-fit-content;
8 | height: -webkit-fit-content;
9 | height: fit-content;
10 | margin: auto;
11 | border: solid;
12 | padding: 1em;
13 | background: white;
14 | color: black;
15 | display: block;
16 | }
17 |
18 | dialog:not([open]) {
19 | display: none;
20 | }
21 |
22 | dialog + .backdrop {
23 | position: fixed;
24 | top: 0; right: 0; bottom: 0; left: 0;
25 | background: rgba(0,0,0,0.1);
26 | }
27 |
28 | ._dialog_overlay {
29 | position: fixed;
30 | top: 0; right: 0; bottom: 0; left: 0;
31 | }
32 |
33 | dialog.fixed {
34 | position: fixed;
35 | top: 50%;
36 | transform: translate(0, -50%);
37 | }
--------------------------------------------------------------------------------
/travel-packing-ch9/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mvolkmann/svelte-and-sapper-in-action/332f77c18234a2a06de26a1581cd0cefbe8c4428/travel-packing-ch9/public/favicon.png
--------------------------------------------------------------------------------
/travel-packing-ch9/public/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button:not(:disabled),
9 | input:not(:disabled) {
10 | cursor: pointer;
11 | }
12 |
13 | button:disabled {
14 | color: lightgray;
15 | }
16 |
17 | button.icon {
18 | background-color: transparent;
19 | border: none;
20 | margin-bottom: 0;
21 | }
22 |
23 | input:disabled {
24 | color: #ccc;
25 | }
26 |
27 | /* This prevents Firefox from displaying a red box shadow
28 | around required inputs that have no value. */
29 | input:invalid {
30 | box-shadow: none;
31 | }
32 |
33 | label {
34 | display: inline-block;
35 | }
36 |
37 | input,
38 | button,
39 | select,
40 | textarea {
41 | --padding: 10px;
42 |
43 | border-radius: var(--padding);
44 | border: none;
45 | box-sizing: border-box;
46 | color: gray;
47 | font-family: inherit;
48 | font-size: inherit;
49 | margin: 0;
50 | padding: var(--padding);
51 | }
52 |
--------------------------------------------------------------------------------
/travel-packing-ch9/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte app
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/travel-packing-ch9/src/NotFound.svelte:
--------------------------------------------------------------------------------
1 | There is nothing here to help you pack for your trip.
2 |
--------------------------------------------------------------------------------
/travel-packing-ch9/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({target: document.body});
4 |
5 | export default app;
6 |
--------------------------------------------------------------------------------
/travel-packing-ch9/src/util.js:
--------------------------------------------------------------------------------
1 | import {v4 as uuidv4} from 'uuid';
2 |
3 | export function getGuid() {
4 | return uuidv4();
5 | }
6 |
7 | export function blurOnKey(event) {
8 | const {code} = event;
9 | if (code === 'Enter' || code === 'Escape' || code === 'Tab') {
10 | event.target.blur();
11 | }
12 | }
13 |
14 | export function sortOnName(array) {
15 | array.sort((el1, el2) =>
16 | el1.name.toLowerCase().localeCompare(el2.name.toLowerCase())
17 | );
18 | return array;
19 | }
20 |
--------------------------------------------------------------------------------