1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.afphoto filter=lfs diff=lfs merge=lfs -text
|
15
.gitignore
vendored
@ -1,10 +1,7 @@
|
|||||||
# Node
|
.DS_Store
|
||||||
/node_modules/
|
node_modules
|
||||||
|
/.svelte-kit
|
||||||
# Sapper
|
/package
|
||||||
/src/node_modules/@sapper/
|
/build
|
||||||
/__sapper__/
|
.vercel_build_output
|
||||||
|
|
||||||
.vercel
|
.vercel
|
||||||
data
|
|
||||||
backups
|
|
1
.graphqlrc.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
schema: "https://api.nicco.io/graphql"
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": false,
|
|
||||||
"trailingComma": "es5",
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
# Node
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
# Sapper
|
|
||||||
/src/node_modules/@sapper/
|
|
||||||
/__sapper__/
|
|
||||||
|
|
||||||
.vercel
|
|
||||||
data
|
|
||||||
backups
|
|
45
README.md
@ -1,13 +1,38 @@
|
|||||||
# [nicco.io](https://nicco.io)
|
# create-svelte
|
||||||
|
|
||||||
My personal space on the interwebs
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
|
||||||
|
|
||||||
## Stack
|
## Creating a project
|
||||||
|
|
||||||
- Wordpress as Headless CMS
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
- Advanced Custom Fields
|
|
||||||
- Custom Post Type UI
|
```bash
|
||||||
- ACF to REST API
|
# create a new project in the current directory
|
||||||
- WP Webhooks
|
npm init svelte@next
|
||||||
- Sapper Frontend (Svelte)
|
|
||||||
- Vercel for static hosting
|
# create a new project in my-app
|
||||||
|
npm init svelte@next my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: the `@next` is temporary
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
|
||||||
|
14
codegen.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
schema: https://api.nicco.io/graphql
|
||||||
|
documents: "src/**/*.graphql"
|
||||||
|
generates:
|
||||||
|
./src/lib/gql/gen.ts:
|
||||||
|
plugins:
|
||||||
|
- "@graphql-codegen/typescript"
|
||||||
|
- "@graphql-codegen/typescript-operations"
|
||||||
|
- "@graphql-codegen/typescript-graphql-request"
|
||||||
|
config:
|
||||||
|
maybeValue: "T"
|
||||||
|
typesPrefix: GQL
|
||||||
|
immutableTypes: true
|
||||||
|
useTypeImports: true
|
||||||
|
avoidOptionals: true
|
BIN
design/About.afphoto
(Stored with Git LFS)
Normal file
BIN
design/About.png
Normal file
After Width: | Height: | Size: 3.8 MiB |
BIN
design/Home.afphoto
(Stored with Git LFS)
Normal file
BIN
design/Home.png
Normal file
After Width: | Height: | Size: 4.4 MiB |
BIN
design/Signature.afphoto
(Stored with Git LFS)
Normal file
BIN
design/Signature.png
Normal file
After Width: | Height: | Size: 90 KiB |
2977
package-lock.json
generated
50
package.json
@ -1,30 +1,36 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "sapper dev",
|
"dev": "svelte-kit dev",
|
||||||
"export": "sapper export",
|
"build": "svelte-kit build",
|
||||||
"start": "serve __sapper__/export"
|
"preview": "svelte-kit preview",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"generate": "graphql-codegen",
|
||||||
|
"ci": "pnpm run generate && pnpm run check && pnpm run build"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@graphql-codegen/cli": "^2.3.0",
|
||||||
|
"@graphql-codegen/typescript": "^2.4.1",
|
||||||
|
"@graphql-codegen/typescript-graphql-request": "^4.3.2",
|
||||||
|
"@graphql-codegen/typescript-operations": "^2.2.1",
|
||||||
|
"@sveltejs/adapter-static": "next",
|
||||||
|
"@sveltejs/kit": "next",
|
||||||
|
"@types/lunr": "^2.3.4",
|
||||||
|
"graphql": "^15.8.0",
|
||||||
|
"graphql-request": "^3.7.0",
|
||||||
|
"graphql-tag": "^2.12.6",
|
||||||
|
"svelte": "^3.44.3",
|
||||||
|
"svelte-check": "^2.2.11",
|
||||||
|
"svelte-preprocess": "^4.10.1",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"dayjs": "^1.10.7",
|
||||||
"compression": "^1.7.1",
|
"highlight.js": "^11.3.1",
|
||||||
"dayjs": "^1.9.7",
|
|
||||||
"highlight.js": "^10.4.1",
|
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
"polka": "next",
|
"svelte-cloudinary": "^0.2.4"
|
||||||
"sirv": "^1.0.9",
|
|
||||||
"svelte-cloudinary": "^0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-commonjs": "^14.0.0",
|
|
||||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
|
||||||
"@rollup/plugin-replace": "^2.2.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"rollup": "^2.34.2",
|
|
||||||
"rollup-plugin-svelte": "^6.1.1",
|
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
|
||||||
"sapper": "^0.29.0",
|
|
||||||
"sapper-environment": "^1.0.1",
|
|
||||||
"svelte": "^3.35.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4116
pnpm-lock.yaml
generated
Normal file
@ -1,75 +0,0 @@
|
|||||||
import resolve from '@rollup/plugin-node-resolve'
|
|
||||||
import replace from '@rollup/plugin-replace'
|
|
||||||
import commonjs from '@rollup/plugin-commonjs'
|
|
||||||
import svelte from 'rollup-plugin-svelte'
|
|
||||||
import { terser } from 'rollup-plugin-terser'
|
|
||||||
import config from 'sapper/config/rollup.js'
|
|
||||||
import pkg from './package.json'
|
|
||||||
|
|
||||||
const mode = process.env.NODE_ENV
|
|
||||||
const dev = mode === 'development'
|
|
||||||
|
|
||||||
const onwarn = (warning, onwarn) =>
|
|
||||||
(warning.code === 'MISSING_EXPORT' && /'preload'/.test(warning.message)) ||
|
|
||||||
(warning.code === 'CIRCULAR_DEPENDENCY' &&
|
|
||||||
/[/\\]@sapper[/\\]/.test(warning.message)) ||
|
|
||||||
onwarn(warning)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
client: {
|
|
||||||
input: config.client.input(),
|
|
||||||
output: config.client.output(),
|
|
||||||
plugins: [
|
|
||||||
replace({
|
|
||||||
'process.browser': true,
|
|
||||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
||||||
preventAssignment: true,
|
|
||||||
}),
|
|
||||||
svelte({
|
|
||||||
dev,
|
|
||||||
hydratable: true,
|
|
||||||
emitCss: true,
|
|
||||||
}),
|
|
||||||
resolve({
|
|
||||||
browser: true,
|
|
||||||
dedupe: ['svelte'],
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
|
|
||||||
!dev &&
|
|
||||||
terser({
|
|
||||||
module: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
preserveEntrySignatures: false,
|
|
||||||
onwarn,
|
|
||||||
},
|
|
||||||
|
|
||||||
server: {
|
|
||||||
input: config.server.input(),
|
|
||||||
output: config.server.output(),
|
|
||||||
plugins: [
|
|
||||||
replace({
|
|
||||||
'process.browser': false,
|
|
||||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
||||||
preventAssignment: true,
|
|
||||||
}),
|
|
||||||
svelte({
|
|
||||||
generate: 'ssr',
|
|
||||||
hydratable: true,
|
|
||||||
dev,
|
|
||||||
}),
|
|
||||||
resolve({
|
|
||||||
dedupe: ['svelte'],
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
],
|
|
||||||
external: Object.keys(pkg.dependencies).concat(
|
|
||||||
require('module').builtinModules
|
|
||||||
),
|
|
||||||
|
|
||||||
preserveEntrySignatures: 'strict',
|
|
||||||
onwarn,
|
|
||||||
},
|
|
||||||
}
|
|
@ -7,8 +7,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--ff: 'Jost', Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen,
|
--ff: 'Jost', Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans,
|
||||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
Helvetica Neue, sans-serif;
|
||||||
--ff-alt: 'Playfair Display', serif;
|
--ff-alt: 'Playfair Display', serif;
|
||||||
--clr-light: #ffffff;
|
--clr-light: #ffffff;
|
||||||
--clr-dark: #010101;
|
--clr-dark: #010101;
|
@ -4,35 +4,14 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="description" content="Designer & Developer" />
|
<meta name="description" content="Designer & Developer" />
|
||||||
<meta name="keywords" content="Web Agency Blog Articles" />
|
<meta name="keywords" content="Web Agency Blog Articles" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover" />
|
||||||
name="viewport"
|
|
||||||
content="width=device-width,initial-scale=1.0,viewport-fit=cover"
|
|
||||||
/>
|
|
||||||
<meta name="theme-color" content="#333333" />
|
|
||||||
|
|
||||||
%sapper.base%
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="global.css" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/monogramm.png" />
|
<link rel="icon" type="image/png" href="/images/monogramm.png" />
|
||||||
|
|
||||||
<!-- Sapper generates a <style> tag containing critical CSS
|
%svelte.head%
|
||||||
for the current page. CSS for the rest of the app is
|
|
||||||
lazily loaded when it precaches secondary pages -->
|
|
||||||
%sapper.styles%
|
|
||||||
|
|
||||||
<!-- This contains the contents of the <svelte:head> component, if
|
|
||||||
the current page has one -->
|
|
||||||
%sapper.head%
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- The application will be rendered inside this element,
|
<div id="svelte">%svelte.body%</div>
|
||||||
because `src/client.js` references it -->
|
|
||||||
<div id="sapper">%sapper.html%</div>
|
|
||||||
|
|
||||||
<!-- Sapper creates a <script> tag containing `src/client.js`
|
|
||||||
and anything else it needs to hydrate the app and
|
|
||||||
initialise the router -->
|
|
||||||
%sapper.scripts%
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var _paq = (window._paq = window._paq || [])
|
var _paq = (window._paq = window._paq || [])
|
||||||
@ -56,11 +35,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<noscript
|
<noscript
|
||||||
><p>
|
><p>
|
||||||
<img
|
<img src="//stats.nicco.io/rainbow?idsite=1&rec=1" style="border: 0" alt="" /></p
|
||||||
src="//stats.nicco.io/rainbow?idsite=1&rec=1"
|
|
||||||
style="border: 0"
|
|
||||||
alt=""
|
|
||||||
/></p
|
|
||||||
></noscript>
|
></noscript>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,5 +0,0 @@
|
|||||||
import * as sapper from '@sapper/app';
|
|
||||||
|
|
||||||
sapper.start({
|
|
||||||
target: document.querySelector('#sapper')
|
|
||||||
});
|
|
@ -1,33 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
import { initialize } from 'svelte-cloudinary'
|
|
||||||
|
|
||||||
initialize({ cloud_name: 'cupcakearmy' })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { image } from 'svelte-cloudinary'
|
|
||||||
|
|
||||||
export let src
|
|
||||||
export let alt
|
|
||||||
|
|
||||||
$: cleaned = src.replace('https://api.nicco.io', '/nicco')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
img {
|
|
||||||
width: calc(100% - 0.25em);
|
|
||||||
object-fit: cover;
|
|
||||||
object-position: center;
|
|
||||||
border: 0.125em solid var(--clr-primary);
|
|
||||||
transition: var(--animation);
|
|
||||||
transform: scale(1);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
transform: scale(1.1);
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<img use:image={{ src: cleaned, bind: { width: true }, lazy: true }} {alt} />
|
|
@ -1,61 +0,0 @@
|
|||||||
<script>
|
|
||||||
import dj from 'dayjs'
|
|
||||||
import ImageFrame from '../components/ImageFrame.svelte'
|
|
||||||
import Icon from '../components/Icon.svelte'
|
|
||||||
|
|
||||||
export let work
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a href={work.link} target="_blank" rel="noopener">
|
|
||||||
<div class="horizontal">
|
|
||||||
<div class="title regular">{work.title}</div>
|
|
||||||
<div>
|
|
||||||
<Icon icon="link-outline" />
|
|
||||||
<span>{work.link.replace(/https?:\/\//, '')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ImageFrame
|
|
||||||
src={work.image.sizes.medium_large}
|
|
||||||
alt={work.image.description}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<div class="horizontal regular">
|
|
||||||
<div>{work.role}</div>
|
|
||||||
<div>{dj(work.date * 1000).format('MMM YY')}</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{@html work.content}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.title {
|
|
||||||
font-size: 2em;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.regular {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 30em) {
|
|
||||||
.horizontal {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
1
src/global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="@sveltejs/kit" />
|
8
src/lib/actions/cloudinary.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { initialize, image } from 'svelte-cloudinary'
|
||||||
|
|
||||||
|
initialize({ cloud_name: 'cupcakearmy', secure: true })
|
||||||
|
|
||||||
|
export function cdn(el: HTMLImageElement, src: string) {
|
||||||
|
const cleaned = src.replace('https://api.nicco.io', '/nicco')
|
||||||
|
return image(el, { src: cleaned, bind: { width: true }, lazy: true })
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
export let icon
|
export let icon: string
|
||||||
|
|
||||||
$: src = `/icons/${icon}.svg`
|
$: src = `/icons/${icon}.svg`
|
||||||
|
|
||||||
let html = null
|
let html: string | null = null
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log(src)
|
|
||||||
html = await fetch(src).then((res) => res.text())
|
html = await fetch(src).then((res) => res.text())
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -27,7 +26,6 @@
|
|||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
/* fill: currentcolor; */
|
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
transform: translateY(0.2em);
|
transform: translateY(0.2em);
|
||||||
}
|
}
|
@ -1,9 +1,15 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
|
|
||||||
export let links = []
|
type Link = {
|
||||||
|
href: string
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
function isExternal(link) {
|
export let links: Link[] = []
|
||||||
|
|
||||||
|
function isExternal(link: string) {
|
||||||
return /^https?\:\/\//.test(link)
|
return /^https?\:\/\//.test(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,11 +21,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each list as { href, name, icon, external }}
|
{#each list as { href, name, icon, external }}
|
||||||
<a
|
<a rel={external ? 'noopener noreferrer' : ''} {href} target={external ? '_blank' : ''}>
|
||||||
rel={external ? 'noopener noreferrer' : ''}
|
|
||||||
{href}
|
|
||||||
target={external ? '_blank' : ''}
|
|
||||||
>
|
|
||||||
<li>
|
<li>
|
||||||
<Icon class="icon" {icon} />
|
<Icon class="icon" {icon} />
|
||||||
{name}
|
{name}
|
25
src/lib/components/ImageFrame.svelte
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cdn } from '$lib/actions/cloudinary'
|
||||||
|
|
||||||
|
export let src: string
|
||||||
|
export let alt: string
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<img use:cdn={src} {alt} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
width: calc(100% - 0.25em);
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
border: 0.125em solid var(--clr-primary);
|
||||||
|
transition: var(--animation);
|
||||||
|
transform: scale(1);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import Icon from './Icon.svelte'
|
import { page } from '$app/stores'
|
||||||
|
|
||||||
export let segment
|
import Icon from './Icon.svelte'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ name: 'About', href: '/about' },
|
{ name: 'About', href: '/about' },
|
||||||
@ -10,13 +10,11 @@
|
|||||||
{ name: 'Blog', href: '/blog' },
|
{ name: 'Blog', href: '/blog' },
|
||||||
{ name: 'Contact', href: '/contact' },
|
{ name: 'Contact', href: '/contact' },
|
||||||
]
|
]
|
||||||
|
|
||||||
let nav
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav bind:this={nav}>
|
<nav>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<h1 class:active={segment === undefined}>NB</h1>
|
<h1 class:active={$page.path === '/'}>NB</h1>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@ -28,7 +26,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a {href}>
|
<a {href}>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
<div class:active={href.slice(1) === segment} />
|
<div class:active={$page.path.startsWith(href)} />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
@ -1,19 +1,19 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import SpacedLetters from './SpacedLetters.svelte'
|
import SpacedLetters from './SpacedLetters.svelte'
|
||||||
|
|
||||||
export let title = ''
|
export let title = ''
|
||||||
export let readable = false
|
export let readable = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
<SpacedLetters letters={title} {readable} />
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
margin-top: calc(28vh - 3em);
|
margin-top: calc(28vh - 3em);
|
||||||
margin-bottom: 3em;
|
margin-bottom: 3em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
|
||||||
<h1>
|
|
||||||
<SpacedLetters letters={title} {readable} />
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
@ -1,11 +1,12 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
|
import type { GQLBasePostFragment } from '$lib/gql/gen'
|
||||||
|
import { readingTimeInMinutes } from '$lib/utils'
|
||||||
import dj from 'dayjs'
|
import dj from 'dayjs'
|
||||||
import { readingTimeInMinutes } from '../lib/readingTime'
|
|
||||||
|
|
||||||
export let post
|
export let post: GQLBasePostFragment
|
||||||
export let full = false
|
export let full = false
|
||||||
|
|
||||||
function format(date) {
|
function format(date: string) {
|
||||||
return dj(date).format('MMM D, YYYY')
|
return dj(date).format('MMM D, YYYY')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +14,16 @@
|
|||||||
$: modified = format(post.modified)
|
$: modified = format(post.modified)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="attributes">
|
||||||
|
<div>
|
||||||
|
{created}
|
||||||
|
{#if full && created !== modified}<br /> <small>Last update: {modified}</small>{/if}
|
||||||
|
</div>
|
||||||
|
{#if post.content}
|
||||||
|
<div>~ {readingTimeInMinutes(post.content)} min</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.attributes {
|
.attributes {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -21,11 +32,3 @@
|
|||||||
margin-top: -0.125em;
|
margin-top: -0.125em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="attributes">
|
|
||||||
<div>
|
|
||||||
{created}
|
|
||||||
{#if full && created !== modified}<br /> <small>Last update: {modified}</small>{/if}
|
|
||||||
</div>
|
|
||||||
<div>~ {readingTimeInMinutes(post.content)} min</div>
|
|
||||||
</div>
|
|
@ -1,10 +1,22 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
|
import type { GQLBasePostFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
import ImageFrame from '../components/ImageFrame.svelte'
|
import ImageFrame from '../components/ImageFrame.svelte'
|
||||||
import PostAttributes from '../components/PostAttributes.svelte'
|
import PostAttributes from '../components/PostAttributes.svelte'
|
||||||
|
|
||||||
export let post
|
export let post: GQLBasePostFragment
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<a href={`blog/${post.slug}`} class:without={!post.post.featured}>
|
||||||
|
{#if post.post.featured}
|
||||||
|
<ImageFrame src={post.post.featured.sourceUrl} alt={post.post.featured.altText} />
|
||||||
|
{/if}
|
||||||
|
<PostAttributes {post} />
|
||||||
|
<h2>
|
||||||
|
{@html post.title}
|
||||||
|
</h2>
|
||||||
|
</a>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
@ -41,13 +53,3 @@
|
|||||||
transform: translateX(-5%);
|
transform: translateX(-5%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<a href={`blog/${post.slug}`} class:without={!post.featured}>
|
|
||||||
{#if post.featured}
|
|
||||||
<ImageFrame src={post.featured.url} alt={post.featured.description} />
|
|
||||||
{/if}
|
|
||||||
<PostAttributes {post} />
|
|
||||||
<h2>
|
|
||||||
{@html post.title}
|
|
||||||
</h2>
|
|
||||||
</a>
|
|
@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { spring } from 'svelte/motion'
|
import { spring } from 'svelte/motion'
|
||||||
|
import { scroll } from '$lib/stores'
|
||||||
|
|
||||||
import { scroll } from '../lib/scroll'
|
let el: SVGElement
|
||||||
|
|
||||||
let el
|
|
||||||
const springed = spring(
|
const springed = spring(
|
||||||
{ scroll: 0 },
|
{ scroll: 0 },
|
||||||
{
|
{
|
||||||
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function updateState(value) {
|
function updateState(value: number) {
|
||||||
const max = 359.99999
|
const max = 359.99999
|
||||||
const R = 50
|
const R = 50
|
||||||
let alpha = (360 / 1) * value
|
let alpha = (360 / 1) * value
|
||||||
@ -21,9 +21,7 @@
|
|||||||
const x = R + R * Math.cos(a) * 2
|
const x = R + R * Math.cos(a) * 2
|
||||||
const y = R - R * Math.sin(a) * 2
|
const y = R - R * Math.sin(a) * 2
|
||||||
const center = alpha > 180 ? 1 : 0
|
const center = alpha > 180 ? 1 : 0
|
||||||
const path = `M${R},${-50} A${R * 2},${
|
const path = `M${R},${-50} A${R * 2},${R * 2},0,${center},1,${x},${y} L${R},${R} L${R},${-R}`
|
||||||
R * 2
|
|
||||||
},0,${center},1,${x},${y} L${R},${R} L${R},${-R}`
|
|
||||||
if (el) el.setAttribute('d', path)
|
if (el) el.setAttribute('d', path)
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import dj from 'dayjs'
|
import type { GQLBaseProjectFragment } from '$lib/gql/gen'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import Icon from './Icon.svelte'
|
||||||
|
|
||||||
import Icon from '../components/Icon.svelte'
|
export let project: GQLBaseProjectFragment
|
||||||
|
|
||||||
export let project
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<a href={project.link} target="_blank" rel="noopener">
|
<a href={project.project.link} target="_blank" rel="noopener">
|
||||||
<h2>{project.title}</h2>
|
<h2>{project.title}</h2>
|
||||||
</a>
|
</a>
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
<b>{project.description}</b>
|
<b>{project.project.description}</b>
|
||||||
<b class="date">{dj(project.date * 1000).format('MMM YY')}</b>
|
<b class="date">{dayjs(project.project.date, 'X').format('MMM YY')}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
<div class="link">
|
<div class="link">
|
||||||
<Icon icon="link-outline" />
|
<Icon icon="link-outline" />
|
||||||
<a rel="noopener noreferrer" target="_blank" href={project.link}
|
<a rel="noopener noreferrer" target="_blank" href={project.project.link}
|
||||||
>{project.link.replace(/https?:\/\//, '')}</a
|
>{project.project.link.replace(/https?:\/\//, '')}</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
@ -1,5 +1,12 @@
|
|||||||
<script>
|
<script lang="ts" context="module">
|
||||||
export let result
|
export type SearchResultItem = {
|
||||||
|
ref: string
|
||||||
|
score: number
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let result: SearchResultItem
|
||||||
|
|
||||||
const [type, slug] = result.ref.split('/')
|
const [type, slug] = result.ref.split('/')
|
||||||
let href = '/'
|
let href = '/'
|
||||||
@ -7,13 +14,15 @@
|
|||||||
$: {
|
$: {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'works':
|
case 'works':
|
||||||
|
href = `${type}/${slug}`
|
||||||
|
break
|
||||||
case 'projects':
|
case 'projects':
|
||||||
href = `${type}`
|
href = `${type}`
|
||||||
break
|
break
|
||||||
case 'post':
|
case 'posts':
|
||||||
href = `/blog/${slug}`
|
href = `/blog/${slug}`
|
||||||
break
|
break
|
||||||
case 'page':
|
case 'pages':
|
||||||
href = `/${slug}`
|
href = `/${slug}`
|
||||||
break
|
break
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import PageTitle from './PageTitle.svelte'
|
import PageTitle from './PageTitle.svelte'
|
||||||
|
|
||||||
export let title = ''
|
export let title = ''
|
||||||
@ -6,6 +6,12 @@
|
|||||||
export let readable = false
|
export let readable = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<PageTitle {title} {readable} />
|
||||||
|
|
||||||
|
<section class:expanded>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
max-width: 30em;
|
max-width: 30em;
|
||||||
@ -16,9 +22,3 @@
|
|||||||
margin-top: 5em;
|
margin-top: 5em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<PageTitle {title} {readable} />
|
|
||||||
|
|
||||||
<section class:expanded>
|
|
||||||
<slot />
|
|
||||||
</section>
|
|
@ -1,9 +1,15 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
export let letters = []
|
export let letters: string = ''
|
||||||
export let even = false
|
export let even = false
|
||||||
export let readable = false
|
export let readable = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class:even class:readable>
|
||||||
|
{#if even}
|
||||||
|
{#each letters as letter}<span>{letter}</span>{/each}
|
||||||
|
{:else}{letters}{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
span {
|
span {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
@ -28,9 +34,3 @@
|
|||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class:even class:readable>
|
|
||||||
{#if even}
|
|
||||||
{#each letters as letter}<span>{letter}</span>{/each}
|
|
||||||
{:else}{letters}{/if}
|
|
||||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import 'highlight.js/styles/github.css'
|
import 'highlight.js/styles/github.css'
|
||||||
import hljs from 'highlight.js/lib/core'
|
import hljs from 'highlight.js/lib/core'
|
||||||
import javascript from 'highlight.js/lib/languages/javascript'
|
import javascript from 'highlight.js/lib/languages/javascript'
|
||||||
@ -23,9 +23,9 @@
|
|||||||
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let content
|
export let content: string
|
||||||
|
|
||||||
function encodeTextToUrl(text) {
|
function encodeTextToUrl(text: string): string {
|
||||||
return text
|
return text
|
||||||
.replace(/[^A-Za-z ]/, '')
|
.replace(/[^A-Za-z ]/, '')
|
||||||
.replace('/ +/', ' ')
|
.replace('/ +/', ' ')
|
||||||
@ -35,15 +35,15 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
hljs.highlightAll()
|
hljs.highlightAll()
|
||||||
|
|
||||||
const selector = [1, 2, 3, 4, 5, 6]
|
const selector = [1, 2, 3, 4, 5, 6].map((i) => `div.adapter h${i}`).join(', ')
|
||||||
.map((i) => `div.adapter h${i}`)
|
|
||||||
.join(', ')
|
|
||||||
const elements = window.document.querySelectorAll(selector)
|
const elements = window.document.querySelectorAll(selector)
|
||||||
for (const el of elements) {
|
elements.forEach((el) => {
|
||||||
|
if (el.textContent) {
|
||||||
const hash = encodeTextToUrl(el.textContent)
|
const hash = encodeTextToUrl(el.textContent)
|
||||||
el.innerHTML = `<a class="target-link" name="${hash}" href="${window.location.pathname}#${hash}">${el.innerHTML}</a>`
|
el.innerHTML = `<a class="target-link" name="${hash}" href="${window.location.pathname}#${hash}">${el.innerHTML}</a>`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="adapter">
|
<div class="adapter">
|
65
src/lib/components/Work.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
|
import ImageFrame from '$lib/components/ImageFrame.svelte'
|
||||||
|
import type { GQLBaseWorkFragment } from '$lib/gql/gen'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
export let work: GQLBaseWorkFragment
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<a href="/works/{work.slug}">
|
||||||
|
<div class="horizontal">
|
||||||
|
<div class="title regular">{work.title}</div>
|
||||||
|
<div>
|
||||||
|
<a href={work.work.link} target="_blank" rel="noopener">
|
||||||
|
<Icon icon="link-outline" />
|
||||||
|
<span>{work.work.link.replace(/https?:\/\//, '')}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ImageFrame src={work.work.image.sourceUrl} alt={work.work.image.altText} />
|
||||||
|
</a>
|
||||||
|
<div class="horizontal regular">
|
||||||
|
<div>{work.work.role}</div>
|
||||||
|
<div>{dayjs(work.work.date, 'X').format('MMM YY')}</div>
|
||||||
|
</div>
|
||||||
|
{#if work.content}
|
||||||
|
<p>
|
||||||
|
{@html work.content}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
font-size: 2em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regular {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 30em) {
|
||||||
|
.horizontal {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
9434
src/lib/gql/gen.ts
Normal file
5
src/lib/gql/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
import { getSdk } from './gen'
|
||||||
|
|
||||||
|
const client = new GraphQLClient('https://api.nicco.io/graphql')
|
||||||
|
export const SDK = getSdk(client)
|
145
src/lib/gql/root.graphql
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
fragment BaseMediaItem on MediaItem {
|
||||||
|
srcSet
|
||||||
|
altText
|
||||||
|
sourceUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
query MediaItemsMany {
|
||||||
|
mediaItems(first: 100, where: { status: PUBLISH }) {
|
||||||
|
nodes {
|
||||||
|
...BaseMediaItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query MediaItemsOne($slug: ID!) {
|
||||||
|
mediaItem(id: $slug, idType: URI) {
|
||||||
|
...BaseMediaItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment BasePage on Page {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
content
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
query PagesMany {
|
||||||
|
pages(first: 100, where: { status: PUBLISH }) {
|
||||||
|
nodes {
|
||||||
|
...BasePage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query PagesOne($slug: ID!) {
|
||||||
|
page(id: $slug, idType: URI) {
|
||||||
|
...BasePage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment BaseWork on Work {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
content
|
||||||
|
status
|
||||||
|
work {
|
||||||
|
date
|
||||||
|
image {
|
||||||
|
...BaseMediaItem
|
||||||
|
}
|
||||||
|
link
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query WorksMany {
|
||||||
|
works(first: 100, where: { status: PUBLISH }) {
|
||||||
|
nodes {
|
||||||
|
...BaseWork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query WorksOne($slug: ID!) {
|
||||||
|
work(id: $slug, idType: URI) {
|
||||||
|
...BaseWork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment BaseProject on Project {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
content
|
||||||
|
status
|
||||||
|
project {
|
||||||
|
date
|
||||||
|
link
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query ProjectsMany {
|
||||||
|
projects(first: 100, where: { status: PUBLISH }) {
|
||||||
|
nodes {
|
||||||
|
...BaseProject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query ProjectsOne($slug: ID!) {
|
||||||
|
project(id: $slug, idType: URI) {
|
||||||
|
...BaseProject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment BasePost on Post {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
content
|
||||||
|
status
|
||||||
|
date
|
||||||
|
modified
|
||||||
|
post {
|
||||||
|
featured {
|
||||||
|
...BaseMediaItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query PostsMany {
|
||||||
|
posts(first: 100, where: { status: PUBLISH }) {
|
||||||
|
nodes {
|
||||||
|
...BasePost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query PostsOne($slug: ID!) {
|
||||||
|
post(id: $slug, idType: URI) {
|
||||||
|
...BasePost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query Search {
|
||||||
|
posts(first: 100) {
|
||||||
|
nodes {
|
||||||
|
...BasePost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projects(first: 100) {
|
||||||
|
nodes {
|
||||||
|
...BaseProject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
works(first: 100) {
|
||||||
|
nodes {
|
||||||
|
...BaseWork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
export function readingTimeInMinutes(text, options = {}) {
|
|
||||||
options = Object.assign({ wpm: 200 }, options)
|
|
||||||
const cleaned = text.replace(/(<.*?>)|(\\n)|(&#\d*?;)/g, '')
|
|
||||||
const words = cleaned.split(' ').length
|
|
||||||
return Math.round(words / options.wpm)
|
|
||||||
}
|
|
5
src/lib/utils.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function readingTimeInMinutes(text: string, options: { wpm?: number } = {}): number {
|
||||||
|
const cleaned = text.replace(/(<.*?>)|(\\n)|(&#\d*?;)/g, '')
|
||||||
|
const words = cleaned.split(' ').length
|
||||||
|
return Math.round(words / (options.wpm ?? 200))
|
||||||
|
}
|
@ -1,50 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production' && false
|
|
||||||
axios.defaults.baseURL = `${isDev ? 'http://localhost' : 'https://api.nicco.io'}/wp-json/wp/v2`
|
|
||||||
|
|
||||||
function normalize(post) {
|
|
||||||
return {
|
|
||||||
...post,
|
|
||||||
...post.acf,
|
|
||||||
id: post.id,
|
|
||||||
title: post.title.rendered,
|
|
||||||
content: post.content.rendered,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function combineUrlAndParams(url, params) {
|
|
||||||
const p = new URLSearchParams({
|
|
||||||
per_page: 100,
|
|
||||||
...params,
|
|
||||||
}).toString()
|
|
||||||
return `${url}?${p}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOne(url, params = {}) {
|
|
||||||
const { data } = await axios(combineUrlAndParams(url, params))
|
|
||||||
if (!data.length) return null
|
|
||||||
else return normalize(data[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAll(url, params = {}) {
|
|
||||||
const { data, headers } = await axios(combineUrlAndParams(url, params))
|
|
||||||
const totalPages = parseInt(headers['x-wp-totalpages'])
|
|
||||||
const results = [...data]
|
|
||||||
if (totalPages > 1) {
|
|
||||||
for (let page = 2; page <= totalPages; page++) {
|
|
||||||
const { data } = await axios(combineUrlAndParams(url, { ...params, page }))
|
|
||||||
results.push(...data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results.map(normalize)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortByDate(data) {
|
|
||||||
return data.sort((a, b) => parseInt(b.date) - parseInt(a.date))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function respond(res, body) {
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
|
||||||
res.end(JSON.stringify(body))
|
|
||||||
}
|
|
@ -1,22 +1,26 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { stores } from '@sapper/app'
|
import { page } from '$app/stores'
|
||||||
|
|
||||||
import { scroll } from '../lib/scroll'
|
import '../app.css'
|
||||||
import Nav from '../components/Nav.svelte'
|
import '$lib/actions/cloudinary'
|
||||||
import Progress from '../components/Progress.svelte'
|
|
||||||
|
|
||||||
export let segment
|
import dayjs from 'dayjs'
|
||||||
let wrapper
|
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
||||||
let main
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
|
import { scroll } from '$lib/stores'
|
||||||
|
import Nav from '$lib/components/Nav.svelte'
|
||||||
|
import Progress from '$lib/components/Progress.svelte'
|
||||||
|
|
||||||
|
let wrapper: HTMLDivElement
|
||||||
|
let main: HTMLElement
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
wrapper.style.height = `${window.innerHeight}px`
|
wrapper.style.height = `${window.innerHeight}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page } = stores()
|
|
||||||
let last = ''
|
let last = ''
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
const { host, path } = $page
|
const { host, path } = $page
|
||||||
const full = host + path
|
const full = host + path
|
||||||
@ -26,25 +30,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScroll(e) {
|
function updateScroll(e: any) {
|
||||||
const el = e.target
|
const el = e.target
|
||||||
const percentage = el.scrollTop / (el.scrollHeight - el.offsetHeight)
|
const percentage = el.scrollTop / (el.scrollHeight - el.offsetHeight)
|
||||||
scroll.set(isNaN(percentage) ? 0 : percentage)
|
scroll.set(isNaN(percentage) ? 0 : percentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const resizeFN = window.addEventListener('resize', resize, false)
|
window.addEventListener('resize', resize, false)
|
||||||
const scrollFN = main.addEventListener('scroll', updateScroll, false)
|
main.addEventListener('scroll', updateScroll, false)
|
||||||
resize()
|
resize()
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(resizeFN)
|
window.removeEventListener('resize', resize)
|
||||||
main.removeEventListener(scrollFN)
|
main.removeEventListener('scroll', updateScroll)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={wrapper}>
|
<div bind:this={wrapper}>
|
||||||
<Nav {segment} />
|
<Nav />
|
||||||
<main bind:this={main}>
|
<main bind:this={main}>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
@ -1,28 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let status
|
|
||||||
export let error
|
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV === 'development'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
font-size: 8vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 1em auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>{status}</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<h1>{status}</h1>
|
|
||||||
|
|
||||||
<p>{error.message}</p>
|
|
||||||
|
|
||||||
{#if dev && error.stack}
|
|
||||||
<pre>{error.stack}</pre>
|
|
||||||
{/if}
|
|
@ -1,16 +1,37 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload() {
|
import type { Load } from '@sveltejs/kit'
|
||||||
return this.fetch('/api/pages/about.json').then((res) => res.json())
|
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch('/api/pages/about.json').then((r) => r.json()),
|
||||||
|
image: await fetch('/api/media/about-2.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import WPAdapter from '../components/WPAdapter.svelte'
|
import WPAdapter from '$lib/components/WPAdapter.svelte'
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
|
import type { GQLBaseMediaItemFragment, GQLBasePageFragment } from '$lib/gql/gen'
|
||||||
|
import { cdn } from '$lib/actions/cloudinary'
|
||||||
|
|
||||||
export let data
|
export let data: GQLBasePageFragment
|
||||||
|
export let image: GQLBaseMediaItemFragment
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{data.title}</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<SimplePage title={data.title} expanded={false}>
|
||||||
|
{#if data.content}
|
||||||
|
<WPAdapter content={data.content} />
|
||||||
|
<img use:cdn={image.sourceUrl} alt="decoration" />
|
||||||
|
{/if}
|
||||||
|
</SimplePage>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -34,13 +55,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>About</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<SimplePage title="About" expanded={false}>
|
|
||||||
<WPAdapter content={data.content} />
|
|
||||||
<!-- {@html data.content} -->
|
|
||||||
<img src="/images/about.jpg" alt="decoration" />
|
|
||||||
</SimplePage>
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { respond, getAll, getOne, sortByDate } from '../../lib/wp'
|
|
||||||
|
|
||||||
export async function get(req, res) {
|
|
||||||
const [type, slug] = req.params.slug
|
|
||||||
|
|
||||||
if (slug) {
|
|
||||||
const data = await getOne(type, { slug })
|
|
||||||
respond(res, { data })
|
|
||||||
} else {
|
|
||||||
const data = await getAll(type)
|
|
||||||
respond(res, { data: sortByDate(data) })
|
|
||||||
}
|
|
||||||
}
|
|
60
src/routes/api/[type]/[slug].json.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { SDK } from '$lib/gql'
|
||||||
|
import type { ServerRequest } from '@sveltejs/kit/types/hooks'
|
||||||
|
|
||||||
|
export async function get(args: ServerRequest) {
|
||||||
|
const { type, slug } = args.params
|
||||||
|
const all = slug === '*'
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'pages': {
|
||||||
|
if (all) {
|
||||||
|
const data = await SDK.PagesMany()
|
||||||
|
return { body: data.pages?.nodes }
|
||||||
|
} else {
|
||||||
|
const data = await SDK.PagesOne({ slug })
|
||||||
|
return { body: data.page }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'works': {
|
||||||
|
if (all) {
|
||||||
|
const data = await SDK.WorksMany()
|
||||||
|
return { body: data.works?.nodes }
|
||||||
|
} else {
|
||||||
|
const data = await SDK.WorksOne({ slug })
|
||||||
|
return { body: data.work }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'projects': {
|
||||||
|
if (all) {
|
||||||
|
const data = await SDK.ProjectsMany()
|
||||||
|
return { body: data.projects?.nodes }
|
||||||
|
} else {
|
||||||
|
const data = await SDK.ProjectsOne({ slug })
|
||||||
|
return { body: data.project }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'media': {
|
||||||
|
if (all) {
|
||||||
|
const data = await SDK.MediaItemsMany()
|
||||||
|
return { body: data.mediaItems?.nodes }
|
||||||
|
} else {
|
||||||
|
const data = await SDK.MediaItemsOne({ slug })
|
||||||
|
return { body: data.mediaItem }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'posts': {
|
||||||
|
if (all) {
|
||||||
|
const data = await SDK.PostsMany()
|
||||||
|
return { body: data.posts?.nodes }
|
||||||
|
} else {
|
||||||
|
const data = await SDK.PostsOne({ slug })
|
||||||
|
return { body: data.post }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return { status: 404 }
|
||||||
|
}
|
||||||
|
}
|
30
src/routes/api/search.json.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { SDK } from '$lib/gql'
|
||||||
|
import type { GQLBasePageFragment } from '$lib/gql/gen'
|
||||||
|
import lunr from 'lunr'
|
||||||
|
|
||||||
|
function removeHTML(s: string) {
|
||||||
|
return s.replace(/<.*?>|\s+|&#\d+;/g, ' ').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertForIdx(type: string, items: GQLBasePageFragment[]) {
|
||||||
|
const keys: (keyof GQLBasePageFragment)[] = ['title', 'content', 'slug']
|
||||||
|
return items.map((item) => ({
|
||||||
|
url: `${type}/${item.slug}`,
|
||||||
|
data: keys.map((field) => removeHTML(item[field] ?? '')).join(' '),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get = async () => {
|
||||||
|
const { __typename, ...all } = await SDK.Search()
|
||||||
|
const converted = Object.entries(all)
|
||||||
|
.map(([type, data]) => convertForIdx(type, data.nodes))
|
||||||
|
.flat()
|
||||||
|
|
||||||
|
const idx = lunr(function () {
|
||||||
|
this.ref('url')
|
||||||
|
this.field('data')
|
||||||
|
converted.forEach((doc) => this.add(doc))
|
||||||
|
})
|
||||||
|
|
||||||
|
return { body: idx }
|
||||||
|
}
|
@ -1,20 +1,31 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload({ params }) {
|
import type { Load } from '@sveltejs/kit'
|
||||||
return this.fetch(`/api/posts/${params.slug}.json`).then((res) =>
|
|
||||||
res.json()
|
export const load: Load = async ({ fetch, page }) => {
|
||||||
)
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch(`/api/posts/${page.params.slug}.json`).then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import SimplePage from '../../components/SimplePage.svelte'
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
import WPAdapter from '../../components/WPAdapter.svelte'
|
import PostAttributes from '$lib/components/PostAttributes.svelte'
|
||||||
import PostAttributes from '../../components/PostAttributes.svelte'
|
import WpAdapter from '$lib/components/WPAdapter.svelte'
|
||||||
|
import type { GQLBasePostFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
export let data
|
export let data: GQLBasePostFragment
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SimplePage title={data.title} expanded={false} readable>
|
<svelte:head>
|
||||||
|
<title>Blog - {data.title}</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<SimplePage title={data.title} readable>
|
||||||
<PostAttributes post={data} full />
|
<PostAttributes post={data} full />
|
||||||
<WPAdapter content={data.content} />
|
{#if data.content}
|
||||||
|
<WpAdapter content={data.content} />
|
||||||
|
{/if}
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
|
@ -1,44 +1,29 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload(page) {
|
import type { Load } from '@sveltejs/kit'
|
||||||
return this.fetch('/api/posts.json').then((res) => res.json())
|
|
||||||
|
export const prerender = true
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch('/api/posts/*.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
import SimplePage from '../../components/SimplePage.svelte'
|
import PostPreview from '$lib/components/PostPreview.svelte'
|
||||||
import PostPreview from '../../components/PostPreview.svelte'
|
import type { GQLBasePostFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
export let data
|
export let data: GQLBasePostFragment[]
|
||||||
export let redirected = false
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
redirected = new URL(location.href).searchParams.has('old')
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
margin-bottom: 6em;
|
|
||||||
background-color: var(--clr-error);
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Blog</title>
|
<title>Blog</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<SimplePage title="Blog">
|
|
||||||
{#if redirected}
|
<SimplePage title="Works">
|
||||||
<div>
|
|
||||||
<h2>You have been redirected 🔄</h2>
|
|
||||||
<p>
|
|
||||||
Probably you are coming form my old blog (blog.nicco.io)
|
|
||||||
<br />
|
|
||||||
The article you were looking for is down here 👇
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#each data as post}
|
{#each data as post}
|
||||||
<PostPreview {post} />
|
<PostPreview {post} />
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import IconList from '../components/IconList.svelte'
|
import IconList from '$lib/components/IconList.svelte'
|
||||||
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
|
@ -1,72 +1,100 @@
|
|||||||
<script>
|
<script lang="ts" context="module">
|
||||||
import SpacedLetters from '../components/SpacedLetters.svelte'
|
import type { GQLBaseMediaItemFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
|
type Data = Record<'signature' | 'home', GQLBaseMediaItemFragment>
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
const signature: GQLBaseMediaItemFragment = await fetch('/api/media/signature.json').then((r) => r.json())
|
||||||
|
const home: GQLBaseMediaItemFragment = await fetch('/api/media/home.json').then((r) => r.json())
|
||||||
|
return { props: { data: { signature, home } } }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import SpacedLetters from '$lib/components/SpacedLetters.svelte'
|
||||||
|
import type { Load } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
export let data: Data
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Niccolo Borgioli</title>
|
<title>Niccolò Borgioli</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section class="left" style="z-index: 3;">
|
<div class="wrapper">
|
||||||
|
<div class="left" style="z-index: 3;">
|
||||||
<h1>
|
<h1>
|
||||||
<SpacedLetters letters="Niccolò" even />
|
<SpacedLetters letters="Niccolò" even />
|
||||||
<SpacedLetters letters="Borgioli" even />
|
<SpacedLetters letters="Borgioli" even />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>Design & Development</p>
|
<p>Design & Development</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<section class="right" style="z-index: 2;">
|
<div class="right" style="z-index: 2;">
|
||||||
<picture>
|
<img srcset={data.home.srcSet} alt="decoration" class="home" />
|
||||||
<source media="(min-width: 60em)" srcset="/images/home@1500.webp" />
|
<img srcset={data.signature.srcSet} alt="signature" class="signature" />
|
||||||
<source media="(min-width: 45em)" srcset="/images/home@1000.webp" />
|
</div>
|
||||||
<source srcset="/images/home@500.webp" />
|
</div>
|
||||||
<img src="/images/decoration.jpg" alt="decoration" />
|
|
||||||
</picture>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p {
|
.wrapper {
|
||||||
font-size: 4vw;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
justify-content: center;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.left {
|
.left {
|
||||||
align-items: flex-start;
|
flex: 1 0 auto;
|
||||||
padding-left: 1em;
|
max-width: 64vw;
|
||||||
width: initial;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section.right {
|
.right {
|
||||||
align-items: flex-end;
|
position: relative;
|
||||||
|
top: 5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
p {
|
||||||
|
font-size: 4vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.home {
|
||||||
|
width: min(100%, 33vw);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
height: 65vh;
|
object-position: left;
|
||||||
width: 33vw;
|
max-height: 65vh;
|
||||||
transform: translateY(5vh);
|
}
|
||||||
|
img.signature {
|
||||||
|
position: absolute;
|
||||||
|
width: 50%;
|
||||||
|
top: -6%;
|
||||||
|
left: -10%;
|
||||||
|
transform: rotate(-8deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 50em) {
|
@media (max-width: 50em) {
|
||||||
img {
|
.wrapper {
|
||||||
transform: translateY(15vh);
|
flex-direction: column;
|
||||||
height: 60vh;
|
|
||||||
width: 69vw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section.left {
|
.left {
|
||||||
transform: translateY(-25vh);
|
flex: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.home {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(90vh - 35vw - 5vh);
|
||||||
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload() {
|
import type { Load } from '@sveltejs/kit'
|
||||||
return this.fetch('/api/pages/privacy.json').then((res) => res.json())
|
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch('/api/pages/privacy.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import WPAdapter from '../components/WPAdapter.svelte'
|
import WPAdapter from '$lib/components/WPAdapter.svelte'
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
|
import type { GQLBasePageFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
export let data
|
export let data: GQLBasePageFragment
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SimplePage title="Privacy Policy">
|
<svelte:head>
|
||||||
|
<title>{data.title}</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<SimplePage title={data.title} expanded={false}>
|
||||||
|
{#if data.content}
|
||||||
<WPAdapter content={data.content} />
|
<WPAdapter content={data.content} />
|
||||||
|
{/if}
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
|
@ -1,14 +1,21 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload() {
|
import type { Load } from '@sveltejs/kit'
|
||||||
return this.fetch('/api/projects.json').then((res) => res.json())
|
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch('/api/projects/*.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
import Project from '../components/Project.svelte'
|
import Project from '$lib/components/Project.svelte'
|
||||||
|
import type { GQLBaseProjectFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
export let data
|
export let data: GQLBaseProjectFragment[]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import lunr from 'lunr'
|
|
||||||
|
|
||||||
import { getAll } from '../lib/wp'
|
|
||||||
|
|
||||||
function removeHTML(s) {
|
|
||||||
return s.replace(/<.*?>|\s+|&#\d+;/g, ' ').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function convertForIdx(type, fields = []) {
|
|
||||||
const items = await getAll(type)
|
|
||||||
const keys = ['title', 'content', 'slug', ...fields]
|
|
||||||
return items.map((item) => ({
|
|
||||||
url: `${item.type}/${item.slug}`,
|
|
||||||
data: keys.map((field) => removeHTML(item[field])).join(' '),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(req, res) {
|
|
||||||
const all = await Promise.all([
|
|
||||||
convertForIdx('projects', ['description']),
|
|
||||||
convertForIdx('pages'),
|
|
||||||
convertForIdx('posts'),
|
|
||||||
convertForIdx('works', ['role']),
|
|
||||||
])
|
|
||||||
|
|
||||||
const idx = lunr(function () {
|
|
||||||
this.ref('url')
|
|
||||||
this.field('data')
|
|
||||||
|
|
||||||
all.flat().forEach((doc) => this.add(doc))
|
|
||||||
})
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
|
||||||
res.end(JSON.stringify(idx))
|
|
||||||
}
|
|
@ -1,24 +1,31 @@
|
|||||||
<script context="module">
|
<script lang="ts" context="module">
|
||||||
export async function preload({ query }) {
|
import type { Load } from '@sveltejs/kit'
|
||||||
const prebuilt = await this.fetch(`/search.json`).then((res) => res.json())
|
|
||||||
return { prebuilt }
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
prebuilt: await fetch('/api/search.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import lunr from 'lunr'
|
import lunr from 'lunr'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
import SearchResult from '../components/SearchResult.svelte'
|
import type { SearchResultItem } from '$lib/components/SearchResult.svelte'
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
import SearchResult from '$lib/components/SearchResult.svelte'
|
||||||
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
|
|
||||||
export let prebuilt
|
export let prebuilt: any
|
||||||
let needle
|
let needle: string | null = null
|
||||||
let results = []
|
let results: SearchResultItem[] = []
|
||||||
|
let input: HTMLInputElement
|
||||||
|
|
||||||
const idx = lunr.Index.load(prebuilt)
|
const idx = lunr.Index.load(prebuilt)
|
||||||
|
|
||||||
async function search(needle) {
|
async function search(needle: string) {
|
||||||
if (!needle || !idx) {
|
if (!needle || !idx) {
|
||||||
results = []
|
results = []
|
||||||
} else {
|
} else {
|
||||||
@ -28,25 +35,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (needle) {
|
$: if (needle !== null) {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.history.replaceState(null, null, `/search?q=${needle || ''}`)
|
window.history.replaceState(null, '', `/search?q=${needle ?? ''}`)
|
||||||
}
|
}
|
||||||
search(needle)
|
search(needle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
needle = new URLSearchParams(window.location.search).get('q')
|
needle = new URLSearchParams(window.location.search).get('q')
|
||||||
|
input.focus()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SimplePage title="Search" expanded={false}>
|
<SimplePage title="Search" expanded={false}>
|
||||||
<input bind:value={needle} placeholder="needle" />
|
<input bind:this={input} bind:value={needle} placeholder="needle" />
|
||||||
|
{#if needle}
|
||||||
<ul>
|
<ul>
|
||||||
{#each results as result (result.ref)}
|
{#each results as result (result.ref)}
|
||||||
<SearchResult {result} />
|
<SearchResult {result} />
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
{/if}
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import IconList from '../components/IconList.svelte'
|
import IconList from '$lib/components/IconList.svelte'
|
||||||
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
export async function preload() {
|
|
||||||
return this.fetch('/api/works.json').then((res) => res.json())
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import SimplePage from '../components/SimplePage.svelte'
|
|
||||||
import Work from '../components/Work.svelte'
|
|
||||||
|
|
||||||
export let data
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Works</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<SimplePage title="Works">
|
|
||||||
{#each data as work}
|
|
||||||
<Work {work} />
|
|
||||||
{/each}
|
|
||||||
</SimplePage>
|
|
27
src/routes/works/[slug].svelte
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
import type { Load } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
export const load: Load = async ({ fetch, page }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch(`/api/works/${page.params.slug}.json`).then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
|
import Work from '$lib/components/Work.svelte'
|
||||||
|
import type { GQLBaseWorkFragment } from '$lib/gql/gen'
|
||||||
|
|
||||||
|
export let data: GQLBaseWorkFragment
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Works</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<SimplePage title="Works">
|
||||||
|
<Work work={data} />
|
||||||
|
</SimplePage>
|
29
src/routes/works/index.svelte
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
import type { Load } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: await fetch('/api/works/*.json').then((r) => r.json()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { GQLBaseWorkFragment } from '$lib/gql/gen'
|
||||||
|
import SimplePage from '$lib/components/SimplePage.svelte'
|
||||||
|
import Work from '$lib/components/Work.svelte'
|
||||||
|
|
||||||
|
export let data: GQLBaseWorkFragment[]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Works</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<SimplePage title="Works">
|
||||||
|
{#each data as work}
|
||||||
|
<Work {work} />
|
||||||
|
{/each}
|
||||||
|
</SimplePage>
|
@ -1,15 +0,0 @@
|
|||||||
import sirv from 'sirv'
|
|
||||||
import polka from 'polka'
|
|
||||||
import compression from 'compression'
|
|
||||||
import * as sapper from '@sapper/server'
|
|
||||||
|
|
||||||
const { PORT, NODE_ENV } = process.env
|
|
||||||
const dev = NODE_ENV === 'development'
|
|
||||||
|
|
||||||
polka()
|
|
||||||
.use(compression({ threshold: 0 }))
|
|
||||||
.use(sirv('static', { dev }))
|
|
||||||
.use(sapper.middleware())
|
|
||||||
.listen(PORT, (err) => {
|
|
||||||
if (err) console.log('error', err)
|
|
||||||
})
|
|
Before Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 363 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 26 KiB |
@ -1,2 +0,0 @@
|
|||||||
User-Agent: *
|
|
||||||
Allow: /
|
|
13
svelte.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import preprocess from 'svelte-preprocess'
|
||||||
|
import adapter from '@sveltejs/adapter-static'
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
preprocess: preprocess(),
|
||||||
|
kit: {
|
||||||
|
adapter: adapter(),
|
||||||
|
target: '#svelte',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
34
tsconfig.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"target": "es2019",
|
||||||
|
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
/**
|
||||||
|
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||||
|
to enforce using \`import type\` instead of \`import\` for Types.
|
||||||
|
*/
|
||||||
|
"importsNotUsedAsValues": "error",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
To have warnings/errors of the Svelte compiler at the correct position,
|
||||||
|
enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"paths": {
|
||||||
|
"$lib/*": ["src/lib/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
|
||||||
|
}
|