├── .gitignore
├── .npmrc
├── .vscode
├── extensions.json
└── launch.json
├── README.md
├── algolia.mjs
├── astro.config.mjs
├── complete-algolia.mjs
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── images
│ ├── group-photo.png
│ └── photos
│ ├── bill.png
│ ├── christopher.png
│ ├── john.png
│ ├── tim.png
│ └── wolverine.png
├── src
├── components
│ ├── Article.astro
│ ├── Card.astro
│ ├── Counter.svelte
│ ├── Figure.svelte
│ ├── Nav.astro
│ ├── PortableText.svelte
│ └── Search.svelte
├── css
│ ├── reset.css
│ └── styles.css
├── layouts
│ ├── Base
│ │ ├── Base.astro
│ │ └── Head.astro
│ ├── BasicPage.astro
│ ├── BlogPost.astro
│ └── GalleryPage.astro
├── pages
│ ├── 404.astro
│ ├── about.astro
│ ├── basic.md
│ ├── blog.astro
│ ├── blog
│ │ ├── fourth-post.md
│ │ ├── quick-seo-tips.md
│ │ ├── second-post.md
│ │ └── third-post.md
│ ├── contact.astro
│ ├── gallery.astro
│ ├── index.astro
│ ├── js-test.astro
│ ├── rss.xml.js
│ ├── sanity
│ │ ├── [slug].astro
│ │ └── index.astro
│ └── search.astro
├── scss
│ └── styles.scss
└── utilities
│ ├── blog.js
│ └── sanity.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 |
15 | # environment variables
16 | .env
17 | .env.production
18 |
19 | # macOS-specific files
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # Expose Astro dependencies for `pnpm` users
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to [Astro](https://astro.build)
2 |
3 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
4 |
5 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
6 |
7 | ## 🚀 Project Structure
8 |
9 | Inside of your Astro project, you'll see the following folders and files:
10 |
11 | ```
12 | /
13 | ├── public/
14 | │ └── favicon.ico
15 | ├── src/
16 | │ ├── components/
17 | │ │ └── Layout.astro
18 | │ └── pages/
19 | │ └── index.astro
20 | └── package.json
21 | ```
22 |
23 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
24 |
25 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components or layouts.
26 |
27 | Any static assets, like images, can be placed in the `public/` directory.
28 |
29 | ## 🧞 Commands
30 |
31 | All commands are run from the root of the project, from a terminal:
32 |
33 | | Command | Action |
34 | | :---------------- | :------------------------------------------- |
35 | | `npm install` | Installs dependencies |
36 | | `npm run dev` | Starts local dev server at `localhost:3000` |
37 | | `npm run build` | Build your production site to `./dist/` |
38 | | `npm run preview` | Preview your build locally, before deploying |
39 |
40 | ## 👀 Want to learn more?
41 |
42 | Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
43 |
--------------------------------------------------------------------------------
/algolia.mjs:
--------------------------------------------------------------------------------
1 | import algolia from 'algoliasearch'
2 |
3 | const posts = [
4 | {
5 | title: 'First Post',
6 | description: 'My first post',
7 | link: '/blog/first-post',
8 | objectID: '1'
9 | },
10 | {
11 | title: 'Second Post',
12 | description: 'My second post',
13 | link: '/blog/second-post',
14 | objectID: '2'
15 | },
16 | {
17 | title: 'Third Post',
18 | description: 'My third post',
19 | link: '/blog/third-post',
20 | objectID: '3'
21 | }
22 | ]
23 |
24 | // Initialize Algolia
25 | const client = algolia('WGGTXJI80W', '09757e54248fd87642f464ebf36462f2')
26 | const index = client.initIndex('content')
27 |
28 | index
29 | .saveObjects(posts)
30 | .then(console.log)
31 | .catch(console.log)
32 |
--------------------------------------------------------------------------------
/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config'
2 |
3 | import svelte from '@astrojs/svelte'
4 |
5 | // https://astro.build/config
6 | export default defineConfig({
7 | site: 'https://astro-demo',
8 | integrations: [svelte()]
9 | })
10 |
--------------------------------------------------------------------------------
/complete-algolia.mjs:
--------------------------------------------------------------------------------
1 | import 'dotenv/config'
2 | import g from 'glob'
3 | import { readFile } from 'fs/promises'
4 | import { promisify } from 'util'
5 | import algoliasearch from 'algoliasearch'
6 | import path from 'path'
7 | import { JSDOM } from 'jsdom'
8 | import crypto from 'crypto'
9 |
10 | const glob = promisify(g)
11 | const ALGOLIA_ID = process.env.ALGOLIA_ID
12 | const ALGOLIA_API_KEY = process.env.ALGOLIA_API_KEY
13 |
14 | const client = algoliasearch(ALGOLIA_ID, ALGOLIA_API_KEY)
15 |
16 | // Indexing for document search
17 | // https://www.algolia.com/blog/engineering/how-to-build-a-helpful-search-for-technical-documentation-the-laravel-example/
18 | const index = client.initIndex('content')
19 | index.setSettings({
20 | attributeForDistinct: 'h1',
21 | distinct: true
22 | })
23 |
24 | // Getting records from HTML Pages
25 | const pages = await glob('./dist/**/index.html')
26 | const records = await createRecordsFromHTML(pages)
27 |
28 | // Remove redundant records
29 | await removeRedundantRecords(records)
30 |
31 | // Save records to Algolia
32 | index
33 | .saveObjects(records)
34 | .then(console.log)
35 | .catch(console.log)
36 |
37 | // ========================
38 | // Supporting Functions
39 | // ========================
40 | /**
41 | * Scrapes HTML pages and creates records
42 | * @param {array} pages HTML pages to parse
43 | * @returns
44 | */
45 | async function createRecordsFromHTML (pages) {
46 | // Improvement: Confirm which textElements are important for the records... And whether `element.textContent` gets all children content as well.
47 | const textElements = [
48 | 'h1',
49 | 'h2',
50 | 'h3',
51 | 'ul',
52 | 'ol',
53 | // 'div',
54 | 'span',
55 | 'p',
56 | // 'b',
57 | // 'em',
58 | // 'strikethrough'
59 | // 'a',
60 | 'figure'
61 | ].map(value => 'main ' + value)
62 |
63 | pages = pages.map(async (page, index) => {
64 | const pagePath = pages[index]
65 | const permalink = path.join(
66 | pagePath.replace('index.html', '').replace('dist/', '')
67 | )
68 |
69 | const buffer = await readFile(page)
70 | const html = buffer.toString()
71 |
72 | const { window } = new JSDOM(html)
73 | const document = window.document
74 |
75 | const elements = document.querySelectorAll(textElements)
76 | const stack = []
77 | let h1, h2, h3, link
78 |
79 | for (const element of elements) {
80 | let content
81 | let record = {}
82 | const tag = element.tagName
83 | if (tag === 'H1') {
84 | h1 = element.textContent
85 | link = permalink
86 | } else if (tag === 'H2') {
87 | h2 = element.textContent
88 | h3 = ''
89 | link = permalink + '#' + element.id
90 | } else if (tag === 'H3') {
91 | h3 = element.textContent
92 | link = permalink + '#' + element.id
93 | } else {
94 | content = element.textContent
95 | }
96 |
97 | // Build the record
98 | if (h1) record.h1 = h1
99 | if (h2) record.h2 = h2
100 | if (h3) record.h3 = h3
101 | if (link) record.link = link
102 | if (content) record.content = content.trim()
103 | record.objectID = quickHash(JSON.stringify(record))
104 |
105 | stack.push(record)
106 | }
107 |
108 | return stack
109 | })
110 |
111 | let records = await Promise.all(pages)
112 | records = records.flat()
113 |
114 | return records
115 | }
116 |
117 | // Fastest hash among a few tested ones.
118 | // https://medium.com/@chris_72272/what-is-the-fastest-node-js-hashing-algorithm-c15c1a0e164e
119 | function quickHash (data) {
120 | return crypto
121 | .createHash('sha1')
122 | .update(data)
123 | .digest('base64')
124 | }
125 |
126 | /**
127 | * Gets ID of current records from Algolia
128 | * @returns {array} Array of record IDs
129 | */
130 | async function getOldRecordIDs () {
131 | let oldRecords = []
132 | await index.browseObjects({
133 | query: '',
134 | attributesToRetrieve: 'objectID',
135 | batch: batch => {
136 | oldRecords = oldRecords.concat(batch)
137 | }
138 | })
139 |
140 | return oldRecords.map(record => record.objectID)
141 | }
142 |
143 | /**
144 | * Removes redundant records from Algolia
145 | * @param {array} newRecords Algolia records
146 | */
147 | async function removeRedundantRecords (newRecords) {
148 | const newRecordIDs = newRecords.map(record => record.objectID)
149 | const oldRecordIDs = await getOldRecordIDs()
150 |
151 | // Improvement: Pop the ID off both records when checked and true.
152 | // This reduces the amount of records in both arrays to filter through
153 | const redundantRecordIDs = oldRecordIDs.filter(
154 | id => !newRecordIDs.includes(id)
155 | )
156 |
157 | await index.deleteObjects(redundantRecordIDs)
158 | }
159 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/basics",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "astro dev",
7 | "start": "astro dev",
8 | "build": "astro build",
9 | "preview": "astro preview"
10 | },
11 | "devDependencies": {
12 | "@astrojs/rss": "^0.2.1",
13 | "@astrojs/svelte": "^0.1.3",
14 | "@portabletext/svelte": "^1.0.1",
15 | "@portabletext/to-html": "^1.0.3",
16 | "@sanity/client": "^3.3.2",
17 | "@sanity/image-url": "^1.0.1",
18 | "algoliasearch": "^4.13.1",
19 | "astro": "^1.0.0-beta.35",
20 | "date-fns": "^2.28.0",
21 | "dotenv": "^16.0.1",
22 | "glob": "^8.0.3",
23 | "jsdom": "^20.0.0",
24 | "postcss-nested": "^5.0.6",
25 | "postcss-preset-env": "^7.7.0",
26 | "postcss-short": "^5.0.0",
27 | "sass": "^1.52.1",
28 | "svelte": "^3.48.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-preset-env'),
4 | // require('postcss-functions')({
5 | // functions: {
6 | // ...require('./postcss/font.js')
7 | // }
8 | // }),
9 | // require('postcss-mixins'), @mixin
10 | require('postcss-nested'),
11 | // require('postcss-atroot'), @at-root
12 | // require('postcss-conditionals'), //@if
13 | // require('postcss-for'), @for
14 | // require('postcss-each'), @each
15 | require('postcss-short')
16 | // require('postcss-apply'),
17 | // require('postcss-font-magician')
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/group-photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/group-photo.png
--------------------------------------------------------------------------------
/public/images/photos/bill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/photos/bill.png
--------------------------------------------------------------------------------
/public/images/photos/christopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/photos/christopher.png
--------------------------------------------------------------------------------
/public/images/photos/john.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/photos/john.png
--------------------------------------------------------------------------------
/public/images/photos/tim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/photos/tim.png
--------------------------------------------------------------------------------
/public/images/photos/wolverine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zellwk/build-and-deploy-workshop/783ab7049d53f0622d082e46403665b05f396adc/public/images/photos/wolverine.png
--------------------------------------------------------------------------------
/src/components/Article.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import {readableDate} from '../utilities/blog.js'
3 | const {post} = Astro.props
4 | ---
5 |
7 | {readableDate(new Date(post.frontmatter.date))}
8 | {post.frontmatter.title}
9 |
10 |
Count: {count}
11 | 12 | -------------------------------------------------------------------------------- /src/components/Figure.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |Page not found
8 | 9 | -------------------------------------------------------------------------------- /src/pages/about.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from '../layouts/Base/Base.astro' 3 | --- 4 | 5 |Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam placeat fugit dignissimos cumque nihil neque odit, sit et quasi non quia quae earum ea beatae laboriosam adipisci magni commodi illo!
8 | 9 | -------------------------------------------------------------------------------- /src/pages/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: '../layouts/BasicPage.astro' 3 | setup: | 4 | import Card from '../components/Card.astro' 5 | import Counter from '../components/Counter.svelte' 6 | title: Basic Page 7 | --- 8 | 9 |Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam placeat fugit dignissimos cumque nihil neque odit, sit et quasi non quia quae earum ea beatae laboriosam adipisci magni commodi illo!
8 | 9 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/pages/gallery.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Gallery from '../layouts/GalleryPage.astro' 3 | const title = 'Gallery Page' 4 | const images = [ 5 | 'wolverine.png', 6 | 'john.png', 7 | 'bill.png', 8 | 'christopher.png', 9 | 'tim.png' 10 | ] 11 | --- 12 | 13 | 20 | 21 |Here is our group photo!
10 | 11 |15 | Lorem ipsum, dolor sit amet consectetur adipisicing elit. Recusandae impedit praesentium fuga voluptatibus dolorem facere facilis? Quos tenetur, ducimus ad facilis voluptates maxime eligendi, saepe suscipit labore ipsam veritatis possimus. 16 | 17 |
18 |Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam placeat fugit dignissimos cumque nihil neque odit, sit et quasi non quia quae earum ea beatae laboriosam adipisci magni commodi illo!
21 | 22 |Slug: {post.slug.current}
50 |Tags: {tags.map((tag, index) => { 51 | const link = `/tags/${tag}` 52 | const isLastItem = index === tags.length - 1 53 | 54 | if (isLastItem) { 55 | return ( 56 | {tag} 57 | ) 58 | } 59 | 60 | return ( 61 | {tag} 62 | , 63 | ) 64 | } 65 | 66 | 67 | )}
68 | 69 |