mirror of https://github.com/sveltejs/svelte
commit
d79fa7e760
@ -1,7 +0,0 @@
|
||||
/*
|
||||
!/Dockerfile
|
||||
!/package.json
|
||||
!/package-lock.json
|
||||
!/build
|
||||
!/static
|
||||
!/content
|
||||
@ -1,13 +0,0 @@
|
||||
NODE_ENV=
|
||||
|
||||
PORT=3000
|
||||
BASEURL=http://localhost:3000
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
VITE_MAPBOX_ACCESS_TOKEN=
|
||||
|
||||
PGHOST=localhost
|
||||
PGPORT=5432
|
||||
PGUSER=username
|
||||
PGPASSWORD=password
|
||||
PGDATABASE=database_name
|
||||
@ -1,58 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
rules: {
|
||||
indent: [2, 'tab', { SwitchCase: 1 }],
|
||||
semi: [2, 'always'],
|
||||
'keyword-spacing': [2, { before: true, after: true }],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'no-mixed-spaces-and-tabs': [2, 'smart-tabs'],
|
||||
'no-cond-assign': 0,
|
||||
'no-unused-vars': 2,
|
||||
'object-shorthand': [2, 'always'],
|
||||
'no-const-assign': 2,
|
||||
'no-class-assign': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-var': 2,
|
||||
'no-unreachable': 2,
|
||||
'valid-typeof': 2,
|
||||
'quote-props': [2, 'as-needed'],
|
||||
'one-var': [2, 'never'],
|
||||
'prefer-arrow-callback': 2,
|
||||
'prefer-const': [2, { destructuring: 'all' }],
|
||||
'arrow-spacing': 2,
|
||||
'no-inner-declarations': 0,
|
||||
'require-atomic-updates': 0
|
||||
},
|
||||
env: {
|
||||
es6: true,
|
||||
browser: true,
|
||||
node: true,
|
||||
mocha: true
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings'
|
||||
],
|
||||
plugins: ['svelte3'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
processor: 'svelte3/svelte3'
|
||||
}
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 9,
|
||||
sourceType: 'module'
|
||||
},
|
||||
settings: {
|
||||
'import/core-modules': ['svelte'],
|
||||
'svelte3/compiler': (() => {
|
||||
try {
|
||||
return require('svelte/compiler');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
}
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
#!include:.dockerignore
|
||||
@ -1,22 +0,0 @@
|
||||
# IMPORTANT: Don't use this Dockerfile in your own projects without also looking at the .dockerignore file.
|
||||
# Without an appropriate .dockerignore, this Dockerfile will copy a large number of unneeded files into your image.
|
||||
|
||||
FROM mhart/alpine-node:14
|
||||
|
||||
# install dependencies
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --production
|
||||
|
||||
###
|
||||
# Only copy over the Node pieces we need
|
||||
# ~> Saves 35MB
|
||||
###
|
||||
FROM mhart/alpine-node:slim-14
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=0 /app .
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "build"]
|
||||
@ -1,22 +0,0 @@
|
||||
HASH := `git rev-parse --short HEAD`
|
||||
|
||||
SERVICE := svelte-website
|
||||
PROJECT := svelte-dev
|
||||
|
||||
IMAGE := gcr.io/$(PROJECT)/$(SERVICE):$(HASH)
|
||||
|
||||
sveltekit:
|
||||
@echo "\n~> updating template & contributors list"
|
||||
@npm run update
|
||||
@echo "\n~> building SvelteKit app"
|
||||
@npm run build
|
||||
|
||||
|
||||
docker:
|
||||
@echo "\n~> building docker image"
|
||||
@gcloud builds submit --project $(PROJECT) -t $(IMAGE)
|
||||
|
||||
|
||||
deploy: sveltekit docker
|
||||
@echo "\n~> deploying $(SERVICE) to Cloud Run servers"
|
||||
@gcloud run deploy $(SERVICE) --project $(PROJECT) --allow-unauthenticated --platform managed --region us-central1 --image $(IMAGE) --memory=512Mi
|
||||
@ -1,84 +0,0 @@
|
||||
## Running locally
|
||||
|
||||
Set up the site sub-project:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sveltejs/svelte.git
|
||||
cd site
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
and navigate to [localhost:3000](http://localhost:3000).
|
||||
|
||||
The first time you run the site locally, it will update the list of Contributors and REPL dependencies. After this it won't run again unless you force it by running:
|
||||
|
||||
```bash
|
||||
npm run update
|
||||
```
|
||||
|
||||
## Running using the local copy of Svelte
|
||||
|
||||
By default, the REPL will fetch the most recent version of Svelte from https://unpkg.com/svelte. When running the site locally, you can also use your local copy of Svelte.
|
||||
|
||||
To produce the proper browser-compatible UMD build of the compiler, you will need to run `npm run build` (or `npm run dev`) in the root of this repository with the `PUBLISH` environment variable set to any non-empty string:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sveltejs/svelte.git
|
||||
cd svelte
|
||||
npm ci
|
||||
PUBLISH=1 npm run build
|
||||
cd site
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then visit the REPL at [localhost:3000/repl?version=local](http://localhost:3000/repl?version=local). Please note that the local REPL only works with `npm run dev` and not when building the site for production usage.
|
||||
|
||||
## REPL GitHub integration
|
||||
|
||||
In order for the REPL's GitHub integration to work properly when running locally, you will need to:
|
||||
- [create a GitHub OAuth app](https://github.com/settings/developers):
|
||||
- set `Authorization callback URL` to `http://localhost:3000/auth/callback`;
|
||||
- set `Application name` as you like, and `Homepage URL` as `http://localhost:3000/`;
|
||||
- create the app and take note of `Client ID` and `Client Secret`
|
||||
- in this repo, create `site/.env` containing:
|
||||
```
|
||||
GITHUB_CLIENT_ID=[your app's Client ID]
|
||||
GITHUB_CLIENT_SECRET=[your app's Client Secret]
|
||||
BASEURL=http://localhost:3000
|
||||
```
|
||||
|
||||
## Building the site
|
||||
|
||||
To build the website, run `npm run build`. The output can be found in `build`.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests can be run using `npm run test`.
|
||||
|
||||
|
||||
## Linking `@sveltejs/site-kit` and `@sveltejs/svelte-repl`
|
||||
|
||||
This site depends on `@sveltejs/site-kit` (a collection of styles, components and icons used in common by *.svelte.dev websites), and `@sveltejs/svelte-repl`.
|
||||
|
||||
In order to work on features that depend on those packages, you need to [link](https://docs.npmjs.com/cli/link) their repositories:
|
||||
|
||||
- `cd <somewhere>`
|
||||
- `git clone https://github.com/sveltejs/site-kit`
|
||||
- `git clone https://github.com/sveltejs/svelte-repl`
|
||||
- `cd <somewhere>/site-kit`
|
||||
- `npm link`
|
||||
- `cd <somewhere>/svelte-repl`
|
||||
- `npm link`
|
||||
- `cd <svelte-repo>/site`
|
||||
- `npm link @sveltejs/site-kit`
|
||||
- `npm link @sveltejs/svelte-repl`
|
||||
|
||||
|
||||
|
||||
## Translating the API docs
|
||||
|
||||
Anchors are automatically generated using headings in the documentation and by default (for the english language) they are latinised to make sure the URL is always conforming to RFC3986.
|
||||
|
||||
If we need to translate the API documentation to a language using unicode chars, we can setup this app to export the correct anchors by setting up `SLUG_PRESERVE_UNICODE` to `true` in `config.js`.
|
||||
@ -1,2 +0,0 @@
|
||||
export const SLUG_PRESERVE_UNICODE = false;
|
||||
export const SLUG_SEPARATOR = '_';
|
||||
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "What's new in Svelte: December 2021"
|
||||
description: "Svelte Summit Fall 2021 Recap, Rich Harris joins Vercel, and Kevin goes full-time on Svelte Society"
|
||||
author: Daniel Sandoval
|
||||
authorURL: https://desandoval.net
|
||||
---
|
||||
|
||||
With SvelteKit getting more and more stable each day, there's not much to cover in terms of code changes other than bug fixes... So, in this month's newsletter, we'll be covering Svelte Summit Fall 2021!
|
||||
|
||||
If you want to dive deep into the last month's worth of bug fixes, check out the [Svelte](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md) and [SvelteKit](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md) changelogs, respectively.
|
||||
|
||||
|
||||
## What happened at Svelte Summit?
|
||||
|
||||
If you missed Svelte Summit, you can watch the entire live stream on [YouTube](https://www.youtube.com/watch?v=1Df-9EKvZr0) and catch a recap in the [#svelte-summit channel on Discord](https://discord.gg/YmHcdnhu).
|
||||
|
||||
Here are the highlights:
|
||||
- [Rich Harris](https://twitter.com/rich_harris) took us through a tour of Svelte's history and announced [his move to Vercel](https://vercel.com/blog/vercel-welcomes-rich-harris-creator-of-svelte) - where he will be helping maintain Svelte full-time! ([20:00](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=1200s))
|
||||
- [Steph Dietz](https://twitter.com/steph_dietz_) explained how Svelte's simple abstractions makes it easy for beginners and experts alike to learn and use JavaScript - without the boilerplate ([29:00](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=1740s))
|
||||
- [Kevin Bridges](https://twitter.com/kevinast) dove deep into Svelte's reactivity logic by visualizing it through `ReflectiveCounter` and showing how to "fine tune" it, as needed. A full "syllabus" for the presentation is available on [Kevin's site](https://wiibridges.com/presentations/ResponsiveSvelte/). ([42:55](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=2575s))
|
||||
- [Mateo Morris](https://twitter.com/_mateomorris) launched [Primo](https://primo.af/), an all-in-one SvelteKit CMS to help build and manage static sites ([1:12:34](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=4354s))
|
||||
- [Guillermo Rauch](https://vercel.com/about/rauchg) explained Vercel's commitment to Svelte, what it means to have Rich on the team, and what's coming next from the company... ([1:21:54](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=4914s))
|
||||
- [Geoff Rich](https://twitter.com/geoffrich_) introduced various ways to modify motion and transitions within Svelte to be more accessible to all users of the web. Slides and a full transcription of the talk are available on [Geoff's site](https://geoffrich.net/posts/svelte-summit-2021/). ([1:32:30](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=5550s))
|
||||
- [Dean Fogarty](https://df.id.au/) demoed a number of different use-cases for custom stores - transforming data to and from storage mechanisms within Svelte. Transcript and code is available on [Dean's GitHub](https://github.com/angrytongan/svelte-summit-2021). ([1:43:06](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=6186s))
|
||||
- [Kellen Mace](https://twitter.com/kellenmace) shared how we can let content creators keep using WordPress, while leveraging Svelte on the frontend to provide a phenomenal user experience ([1:49:30](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=6570ss))
|
||||
- [Ben Holmes](https://twitter.com/bholmesdev) explained the "islands" architecture and how 11ty + [Slinkity](https://slinkity.dev/) can bring these islands to any HTML template ([2:17:15](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=8235s))
|
||||
- [Scott Tolinski](https://twitter.com/stolinski) shared the lessons learned from rewriting the React-based LevelUpTutorials in Svelte and "found developer bliss" ([3:16:35](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=11795s))
|
||||
- [Svelte Sirens](https://sveltesirens.dev) was announced as the new Svelte community for women, non-binary and allies. Their first event was on November 29th - all future events can be found on [the Svelte Sirens website](https://sveltesirens.dev/events) ([3:50:45](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=13845s))
|
||||
- [Rich Harris](https://twitter.com/rich_harris) discussed creating libraries with SvelteKit, better ways to link packages when developing, and how SvelteKit helps with modern JavaScript library development ([3:56:00](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=14160s))
|
||||
- [Ken Kunz](https://twitter.com/kennethkunz) explained how finite state machines (and the svelte-fsm library) can make managing Svelte component states more... manageable. Examples from the talk are available on [Ken's GitHub](https://github.com/kenkunz/svelte-fsm/wiki/Examples). ([4:07:18](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=14838s))
|
||||
- [Austin Crim](https://twitter.com/crim_codes) connected learning to code on the web to learning how to play an instrument. By giving learners early wins and introducing the fundamentals through real-world apps, learning Svelte (and the fundamentals underneath) doesn't have to be a chore ([4:21:50](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=15710s))
|
||||
- [Jesse Skinner](https://twitter.com/JesseSkinner) brought our legacy apps into the future by explaining how to use (and reuse) Svelte components within React (and even jQuery!) projects ([4:32:30](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=16350s))
|
||||
- [Jim Fisk](https://twitter.com/jimafisk) and [Stephanie Luz](https://stephanie-luz.medium.com/) introduced [Plenti](https://plenti.co/) and its theming tools to make building new Svelte sites much faster ([4:59:00](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=17940s))
|
||||
- [Evyatar Alush](https://twitter.com/evyataral) helped us all make (and maintain) better forms using a powerful validation library called [Vest](https://github.com/ealush/vest) ([5:08:55](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=18535s))
|
||||
- Dominik G. presented a fresh take on icon libraries - one that reduces the bundle size of applications and opens up the entire iconify library for use in any Svelte app ([5:30:04](https://www.youtube.com/watch?v=1Df-9EKvZr0&t=19804s))
|
||||
|
||||
Thanks to [Kevin](https://twitter.com/kevmodrome) and all the Svelte Society volunteers for pulling together such an amazing event! Excitingly, [Kevin announced](https://twitter.com/kevmodrome/status/1463151477174714373) after the event that he will now be working full-time on Svelte Society! You can check out all the talks, broken up into individual videos for convenience, in [this Svelte Society YouTube Playlist](https://www.youtube.com/playlist?list=PL8bMgX1kyZTg2bI9IOMgfBc8lrU3v2itt).
|
||||
|
||||
If you have feedback on the Svelte Summit, Kev is [looking for feedback on the Svelte subreddit](https://www.reddit.com/r/sveltejs/comments/qzgo3k/svelte_summit_feedback/) 👀
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Community Showcase
|
||||
|
||||
**Apps & Sites**
|
||||
- [pixeldrain](https://github.com/Fornaxian/pixeldrain_web) is a free-to-use file sharing platform
|
||||
- [LifeHash](http://lifehash.info/) generates beautiful visual hashes from Blockchain Commons
|
||||
- [simple-cloud-music](https://github.com/dufu1991/simple-cloud-music) is a lightweight third-party NetEase cloud music player for modern browsers (likely only works on Chrome)
|
||||
- [palette.rocks](https://palette.rocks/) is a color palette generator with contrast-checking built-in
|
||||
- [Kadium](https://github.com/probablykasper/kadium) is an app for staying on top of YouTube channel uploads
|
||||
- [Multi-Monitor Calculator](https://multimonitorcalculator.com/) is a tool for planning your multi-monitor setup
|
||||
- [Your Home](https://yourhome.fb.com/) is an interactive overview of Facebook's privacy settings
|
||||
- [Svelte Crush](https://svelte-crush.netlify.app/) is a Candy Crush style match-3 game
|
||||
- [100.000 Corona deaths in Germany](https://twitter.com/h_i_g_s_c_h/status/1463767113563353089?s=20) is a visualization made for Spiegel Gesundheit
|
||||
|
||||
**Looking for a Svelte project to work on? Interested in helping make Svelte's presence on the web better?** Check out [the list of open issues](https://github.com/svelte-society/sveltesociety-2021/issues) if you'd like to contribute to the Svelte Society rewrite in SvelteKit.
|
||||
|
||||
|
||||
**Videos, Blogs and Podcasts**
|
||||
- [How To Make and Publish a Svelte Library](https://www.youtube.com/watch?v=_TymiadmPrc)
|
||||
- [SvelteKit is now fully supported in WebContainers](https://blog.stackblitz.com/posts/sveltekit-supported-in-webcontainers/)
|
||||
- [Introducing Svelte, and Comparing Svelte with React and Vue](https://joshcollinsworth.com/blog/introducing-svelte-comparing-with-react-vue)
|
||||
- [Testing a Svelte app with Jest](https://www.roboleary.net/2021/11/18/svelte-app-testing-jest.html)
|
||||
- [How to create a toast notification library package with SvelteKit](https://www.sarcevic.dev/blog/toasting-in-svelte)
|
||||
- [Svelte training: Here you can learn Svelte](https://sustainablewww.org/principles/svelte-training-here-you-can-learn-svelte)
|
||||
- [Introduction to Svelte Actions](https://blog.logrocket.com/svelte-actions-introduction/)
|
||||
- [Enjoy making DAPPs using SvelteWeb3](https://chiuzon.medium.com/enjoy-making-dapps-using-svelteweb3-b78dfea1d902)
|
||||
- [Svelte creator: Web development should be more fun](https://www.infoworld.com/article/3639521/svelte-creator-web-development-should-be-more-fun.html)
|
||||
- [Svelte Radio: Rich Harris is now working full-time on Svelte 🤯](https://share.transistor.fm/s/d9b04961)
|
||||
- [Web Rush: Svelte and Elder.js with Nick Reese](https://webrush.io/episodes/episode-158-svelte-and-elderjs-with-nick-reese)
|
||||
- [Building SvelteKit applications with Serverless Redis](https://blog.upstash.com/svelte-with-serverless-redis)
|
||||
|
||||
**Libraries, Tools & Components**
|
||||
- [svelte-cubed](https://github.com/Rich-Harris/svelte-cubed) is a Three.js component library for Svelte - created by Rich Harris for his presentation at Svelte Summit Fall 2021
|
||||
- [svelte-fsm](https://github.com/kenkunz/svelte-fsm) is a tiny, simple, expressive, pragmatic Finite State Machine (FSM) library, optimized for Svelte
|
||||
- [bromb](https://github.com/samuelstroschein/bromb) is a feedback widget for websites/web apps that is small and easy to integration/self-host
|
||||
- [Spaper](https://github.com/Oli8/spaper) is a set of PaperCSS components for Svelte
|
||||
- [svelte-intl-precompile](https://github.com/cibernox/svelte-intl-precompile) is an i18n library for Svelte that analyzes and compiles your translations at build time
|
||||
- [svelte-preprocess-svg](https://github.com/svitejs/svelte-preprocess-svg) automatically optimizes inline svg in Svelte components for better performance and reduced file size
|
||||
- [svelte-subcomponent-preprocessor](https://github.com/srmullen/svelte-subcomponent-preprocessor) allows you to write more than one component within a svelte file
|
||||
- [svelte-pdfjs](https://github.com/gtm-nayan/svelte-pdfjs) is a crude implementation of a Svelte PDF viewer component
|
||||
- [svelte-inview](https://github.com/maciekgrzybek/svelte-inview) is a Svelte action that monitors an element enters or leaves the viewport/parent element
|
||||
- [sveltekit-adapter-wordpress-shortcode](https://github.com/tomatrow/sveltekit-adapter-wordpress-shortcode) is an adapter for SvelteKit which turns your app into a wordpress shortcode
|
||||
- [svelte-websocket-store](https://github.com/arlac77/svelte-websocket-store) is a Svelte store with a websocket backend
|
||||
- [Svelte Auto Form](https://github.com/leveluptuts/auto-form) is a fast and fun form library focused on ease of use, rather than flexibility.
|
||||
- [set-focus](https://www.npmjs.com/package/@svackages/set-focus) is an Svelte action that will set focus on `<a>` or `<button>` elements as soon as they mount - useful for some experiences and testing
|
||||
|
||||
Got an idea for SvelteKit? Check out the new [GitHub Discussions](https://github.com/sveltejs/kit/discussions) in the Svelte repo. You can also join us on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs).
|
||||
|
||||
See you next ~~month~~ year!
|
||||
@ -0,0 +1,15 @@
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (!node.contains(event.target)) {
|
||||
node.dispatchEvent(new CustomEvent("outclick"));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<script>
|
||||
import { spring } from 'svelte/motion';
|
||||
import { pannable } from './pannable.js';
|
||||
|
||||
const coords = spring({ x: 0, y: 0 }, {
|
||||
stiffness: 0.2,
|
||||
damping: 0.4
|
||||
});
|
||||
|
||||
function handlePanStart() {
|
||||
coords.stiffness = coords.damping = 1;
|
||||
}
|
||||
|
||||
function handlePanMove(event) {
|
||||
coords.update($coords => ({
|
||||
x: $coords.x + event.detail.dx,
|
||||
y: $coords.y + event.detail.dy
|
||||
}));
|
||||
}
|
||||
|
||||
function handlePanEnd(event) {
|
||||
coords.stiffness = 0.2;
|
||||
coords.damping = 0.4;
|
||||
coords.set({ x: 0, y: 0 });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="box"
|
||||
use:pannable
|
||||
on:panstart={handlePanStart}
|
||||
on:panmove={handlePanMove}
|
||||
on:panend={handlePanEnd}
|
||||
style="transform:
|
||||
translate({$coords.x}px,{$coords.y}px)
|
||||
rotate({$coords.x * 0.2}deg)"
|
||||
></div>
|
||||
|
||||
<style>
|
||||
.box {
|
||||
--width: 100px;
|
||||
--height: 100px;
|
||||
position: absolute;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
left: calc(50% - var(--width) / 2);
|
||||
top: calc(50% - var(--height) / 2);
|
||||
border-radius: 4px;
|
||||
background-color: #ff3e00;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "A more complex action"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (!node.contains(event.target)) {
|
||||
node.dispatchEvent(new CustomEvent("outclick"));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
export function pannable(node) {
|
||||
let x;
|
||||
let y;
|
||||
|
||||
function handleMousedown(event) {
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
|
||||
node.dispatchEvent(new CustomEvent('panstart', {
|
||||
detail: { x, y }
|
||||
}));
|
||||
|
||||
window.addEventListener('mousemove', handleMousemove);
|
||||
window.addEventListener('mouseup', handleMouseup);
|
||||
}
|
||||
|
||||
function handleMousemove(event) {
|
||||
const dx = event.clientX - x;
|
||||
const dy = event.clientY - y;
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
|
||||
node.dispatchEvent(new CustomEvent('panmove', {
|
||||
detail: { x, y, dx, dy }
|
||||
}));
|
||||
}
|
||||
|
||||
function handleMouseup(event) {
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
|
||||
node.dispatchEvent(new CustomEvent('panend', {
|
||||
detail: { x, y }
|
||||
}));
|
||||
|
||||
window.removeEventListener('mousemove', handleMousemove);
|
||||
window.removeEventListener('mouseup', handleMouseup);
|
||||
}
|
||||
|
||||
node.addEventListener('mousedown', handleMousedown);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('mousedown', handleMousedown);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
version: "3.0"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13.4
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_DB=${PGDATABASE}
|
||||
- POSTGRES_USER=${PGUSER}
|
||||
- POSTGRES_PASSWORD=${PGPASSWORD}
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- "4000:8080"
|
||||
@ -1,25 +0,0 @@
|
||||
exports.up = DB => {
|
||||
DB.sql(`
|
||||
create table if not exists users (
|
||||
id serial primary key,
|
||||
uid character varying(255) not null unique,
|
||||
name character varying(255),
|
||||
username character varying(255) not null,
|
||||
avatar text,
|
||||
github_token character varying(255),
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone
|
||||
);
|
||||
|
||||
create unique index if not exists users_pkey ON users(id int4_ops);
|
||||
create unique index if not exists users_uid_key ON users(uid text_ops);
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = DB => {
|
||||
DB.sql(`
|
||||
drop table if exists users cascade;
|
||||
drop index if exists users_uid_key;
|
||||
drop index if exists users_pkey;
|
||||
`);
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
exports.up = DB => {
|
||||
DB.sql(`
|
||||
create table if not exists gists (
|
||||
id serial primary key,
|
||||
uid uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
user_id integer REFERENCES users(id) not null,
|
||||
name character varying(255) not null,
|
||||
files json not null,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone
|
||||
);
|
||||
|
||||
create unique index if not exists gists_pkey ON gists(id int4_ops);
|
||||
create index if not exists gists_user_id_key ON gists(user_id int4_ops);
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = DB => {
|
||||
DB.sql(`
|
||||
drop table if exists gists cascade;
|
||||
drop index if exists gists_user_id_key;
|
||||
drop index if exists gists_pkey;
|
||||
`);
|
||||
};
|
||||
@ -1,15 +0,0 @@
|
||||
exports.up = DB => {
|
||||
DB.sql(`
|
||||
create table if not exists sessions (
|
||||
uid uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id integer REFERENCES users(id) not null,
|
||||
expiry timestamp without time zone DEFAULT now() + interval '1 year'
|
||||
);
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = DB => {
|
||||
DB.sql(`
|
||||
drop table if exists sessions;
|
||||
`);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "svelte.dev",
|
||||
"version": "1.0.0",
|
||||
"description": "Docs and examples for Svelte",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/update.js && npm run copy-workers && svelte-kit dev",
|
||||
"copy-workers": "node scripts/copy-workers.js",
|
||||
"migrate": "node-pg-migrate -r dotenv/config",
|
||||
"build": "node scripts/update.js && npm run copy-workers && svelte-kit build",
|
||||
"update": "node scripts/update.js --force=true",
|
||||
"start": "node build",
|
||||
"test": "uvu test",
|
||||
"deploy": "make deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": "^0.4.0",
|
||||
"devalue": "^2.0.0",
|
||||
"do-not-zip": "^1.0.0",
|
||||
"flru": "^1.0.2",
|
||||
"httpie": "^1.1.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"marked": "^3.0.5",
|
||||
"pg": "^8.7.1",
|
||||
"prism-svelte": "^0.4.3",
|
||||
"prismjs": "^1.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sindresorhus/slugify": "^0.9.1",
|
||||
"@sveltejs/adapter-node": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@sveltejs/site-kit": "^1.4.0",
|
||||
"@sveltejs/svelte-repl": "^0.3.0",
|
||||
"degit": "^2.1.4",
|
||||
"dotenv": "^10.0.0",
|
||||
"jimp": "^0.8.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-pg-migrate": "^6.0.0",
|
||||
"shelljs": "^0.8.3",
|
||||
"svelte": "^3.39.0",
|
||||
"uvu": "^0.5.2"
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
import sh from 'shelljs';
|
||||
|
||||
sh.rm('-rf', 'static/workers');
|
||||
sh.cp('-r', 'node_modules/@sveltejs/svelte-repl/workers', 'static');
|
||||
@ -1,67 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import puppeteer from 'puppeteer';
|
||||
import Jimp from 'jimp';
|
||||
import c from 'kleur';
|
||||
|
||||
const slugs = [];
|
||||
|
||||
fs.readdirSync(`content/examples`).forEach(group_dir => {
|
||||
fs.readdirSync(`content/examples/${group_dir}`).filter(file => file !== 'meta.json').map(example_dir => {
|
||||
const slug = example_dir.replace(/^\d+-/, '');
|
||||
slugs.push(slug);
|
||||
});
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const browser = await puppeteer.launch({
|
||||
defaultViewport: {
|
||||
width: 400 * 10 / 4,
|
||||
height: 400 + 42,
|
||||
deviceScaleFactor: 2
|
||||
}
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
for (const slug of slugs) {
|
||||
try {
|
||||
const output_file = `static/examples/thumbnails/${slug}.jpg`;
|
||||
if (fs.existsSync(output_file)) {
|
||||
console.log(c.gray(`skipping ${slug}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(slug);
|
||||
await page.goto(`http://localhost:3000/repl/embed?example=${slug}`);
|
||||
|
||||
await page.waitForSelector('iframe.inited[title=Result]');
|
||||
await page.waitFor(1500);
|
||||
const iframe = await page.$('iframe.inited[title=Result]');
|
||||
const buffer = await iframe.screenshot();
|
||||
|
||||
const image = await Jimp.read(buffer);
|
||||
console.log(image.bitmap.width, image.bitmap.height);
|
||||
image.crop(3, 3, image.bitmap.width - 6, image.bitmap.height - 6);
|
||||
image.autocrop();
|
||||
// image.scale(0.25);
|
||||
|
||||
if (image.bitmap.width > 200 || image.bitmap.height > 200) {
|
||||
const scale = Math.min(
|
||||
200 / image.bitmap.width,
|
||||
200 / image.bitmap.height
|
||||
);
|
||||
|
||||
image.scale(scale);
|
||||
}
|
||||
|
||||
await image.quality(75).write(output_file);
|
||||
} catch (err) {
|
||||
console.log(c.bold().red(`failed to screenshot ${slug}`));
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
main();
|
||||
@ -1,77 +0,0 @@
|
||||
import sander from 'sander';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(`${__dirname}/../..`);
|
||||
|
||||
function extract_frontmatter(markdown) {
|
||||
const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown);
|
||||
const frontMatter = match[1];
|
||||
const content = markdown.slice(match[0].length);
|
||||
|
||||
const metadata = {};
|
||||
frontMatter.split('\n').forEach(pair => {
|
||||
const colonIndex = pair.indexOf(':');
|
||||
metadata[pair.slice(0, colonIndex).trim()] = pair
|
||||
.slice(colonIndex + 1)
|
||||
.trim();
|
||||
});
|
||||
|
||||
return { metadata, content };
|
||||
}
|
||||
|
||||
const tutorial_sections = sander.readdirSync(`content/tutorial`).map(section_dir => {
|
||||
const section_slug = section_dir.replace(/^\d+-/, '');
|
||||
|
||||
const meta = JSON.parse(sander.readFileSync(`content/tutorial/${section_dir}/meta.json`, { encoding: 'utf-8' }));
|
||||
|
||||
const chapters = sander.readdirSync(`content/tutorial/${section_dir}`).map(chapter_dir => {
|
||||
const app_dir = `content/tutorial/${section_dir}/${chapter_dir}/app-b`;
|
||||
if (!sander.existsSync(app_dir)) return;
|
||||
|
||||
const markdown = sander.readFileSync(`content/tutorial/${section_dir}/${chapter_dir}/text.md`, { encoding: 'utf-8' });
|
||||
const { metadata } = extract_frontmatter(markdown);
|
||||
|
||||
const chapter_slug = chapter_dir.replace(/^\d+-/, '');
|
||||
|
||||
return {
|
||||
slug: chapter_slug,
|
||||
title: metadata.title,
|
||||
files: sander.readdirSync(app_dir).map(name => {
|
||||
return {
|
||||
name,
|
||||
source: sander.readFileSync(`${app_dir}/${name}`, { encoding: 'utf-8' })
|
||||
};
|
||||
})
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
return {
|
||||
slug: section_slug,
|
||||
title: meta.title,
|
||||
chapters
|
||||
};
|
||||
}).filter(section => section.chapters.length > 0);
|
||||
|
||||
const pad = i => i < 10 ? `0${i}` : i;
|
||||
|
||||
tutorial_sections.forEach((section, i) => {
|
||||
const section_dir = `${pad(i)}-${section.slug}`;
|
||||
|
||||
sander.writeFileSync(`content/examples/${section_dir}/meta.json`, JSON.stringify({
|
||||
title: section.title
|
||||
}, null, '\t'));
|
||||
|
||||
section.chapters.forEach((chapter, i) => {
|
||||
const chapter_dir = `${pad(i)}-${chapter.slug}`;
|
||||
|
||||
sander.writeFileSync(`content/examples/${section_dir}/${chapter_dir}/meta.json`, JSON.stringify({
|
||||
title: chapter.title
|
||||
}, null, '\t'));
|
||||
|
||||
chapter.files.forEach(file => {
|
||||
sander.writeFileSync(`content/examples/${section_dir}/${chapter_dir}/${file.name}`, file.source);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,66 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
import Jimp from 'jimp';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
|
||||
const force = process.env.FORCE_UPDATE === 'true';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(__dirname);
|
||||
|
||||
const outputFile = `../src/routes/_contributors.js`;
|
||||
if (!force && fs.existsSync(outputFile)) {
|
||||
console.info(`[update/contributors] ${outputFile} exists. Skipping`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const base = `https://api.github.com/repos/sveltejs/svelte/contributors`;
|
||||
const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env;
|
||||
|
||||
const SIZE = 64;
|
||||
|
||||
async function main() {
|
||||
const contributors = [];
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
const res = await fetch(`${base}?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}&per_page=100&page=${page++}`);
|
||||
const list = await res.json();
|
||||
|
||||
if (list.length === 0) break;
|
||||
|
||||
contributors.push(...list);
|
||||
}
|
||||
|
||||
const authors = contributors
|
||||
.filter(({ login }) => !login.includes('[bot]'))
|
||||
.sort((a, b) => b.contributions - a.contributions);
|
||||
|
||||
const sprite = new Jimp(SIZE * authors.length, SIZE);
|
||||
|
||||
for (let i = 0; i < authors.length; i += 1) {
|
||||
const author = authors[i];
|
||||
console.log(`${i + 1} / ${authors.length}: ${author.login}`);
|
||||
|
||||
const image_data = await fetch(author.avatar_url);
|
||||
const buffer = await image_data.arrayBuffer();
|
||||
|
||||
const image = await Jimp.read(buffer);
|
||||
image.resize(SIZE, SIZE);
|
||||
|
||||
sprite.composite(image, i * SIZE, 0);
|
||||
}
|
||||
|
||||
await sprite.quality(80).write(`../static/contributors.jpg`);
|
||||
// TODO: Optimizing the static/contributors.jpg image should probably get automated as well
|
||||
console.log('remember to additionally optimize the resulting /static/contributors.jpg image file via e.g. https://squoosh.app ');
|
||||
|
||||
const str = `[\n\t${authors.map(a => `'${a.login}'`).join(',\n\t')}\n]`;
|
||||
|
||||
fs.writeFileSync(outputFile, `export default ${str};`);
|
||||
}
|
||||
|
||||
main();
|
||||
@ -1,63 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
import Jimp from 'jimp';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
|
||||
const force = process.env.FORCE_UPDATE === 'true';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(__dirname);
|
||||
|
||||
const outputFile = `../src/routes/_donors.js`;
|
||||
if (!force && fs.existsSync(outputFile)) {
|
||||
console.info(`[update/donors] ${outputFile} exists. Skipping`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const SIZE = 64;
|
||||
|
||||
async function main() {
|
||||
const res = await fetch('https://opencollective.com/svelte/members/all.json');
|
||||
const donors = await res.json();
|
||||
|
||||
const unique = new Map();
|
||||
donors.forEach(d => unique.set(d.profile, d));
|
||||
|
||||
let backers = [...unique.values()]
|
||||
.filter(({ role }) => role === 'BACKER')
|
||||
.sort((a, b) => b.totalAmountDonated - a.totalAmountDonated);
|
||||
|
||||
const included = [];
|
||||
for (let i = 0; i < backers.length; i += 1) {
|
||||
const backer = backers[i];
|
||||
console.log(`${i} / ${backers.length}: ${backer.name}`);
|
||||
|
||||
try {
|
||||
const image_data = await fetch(backer.image);
|
||||
const buffer = await image_data.arrayBuffer();
|
||||
const image = await Jimp.read(buffer);
|
||||
image.resize(SIZE, SIZE);
|
||||
included.push({ backer, image });
|
||||
} catch( err) {
|
||||
console.log(`Skipping ${backer.name}: no image data`);
|
||||
}
|
||||
}
|
||||
|
||||
const sprite = new Jimp(SIZE * included.length, SIZE);
|
||||
for (let i = 0; i < included.length; i += 1) {
|
||||
sprite.composite(included[i].image, i * SIZE, 0);
|
||||
}
|
||||
|
||||
await sprite.quality(80).write(`../static/donors.jpg`);
|
||||
// TODO: Optimizing the static/donors.jpg image should probably get automated as well
|
||||
console.log('remember to additionally optimize the resulting /static/donors.jpg image file via e.g. https://squoosh.app ');
|
||||
|
||||
const str = `[\n\t${included.map(a => `${JSON.stringify(a.backer.name)}`).join(',\n\t')}\n]`;
|
||||
|
||||
fs.writeFileSync(outputFile, `export default ${str};`);
|
||||
}
|
||||
|
||||
main();
|
||||
@ -1,9 +0,0 @@
|
||||
import sh from 'shelljs';
|
||||
|
||||
sh.env['FORCE_UPDATE'] = process.argv.includes('--force=true');
|
||||
|
||||
Promise.all([
|
||||
sh.exec('node ./scripts/get_contributors.js'),
|
||||
sh.exec('node ./scripts/get_donors.js'),
|
||||
sh.exec('node ./scripts/update_template.js')
|
||||
]);
|
||||
@ -1,36 +0,0 @@
|
||||
import sh from 'shelljs';
|
||||
import fs from 'fs';
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const force = process.env.FORCE_UPDATE === 'true';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
sh.cd(path.join(__dirname, '..'));
|
||||
|
||||
const outputFile = 'static/svelte-app.json';
|
||||
if (!force && fs.existsSync(outputFile)) {
|
||||
console.info(`[update/template] ${outputFile} exists. Skipping`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// fetch svelte app
|
||||
sh.rm('-rf','scripts/svelte-app');
|
||||
sh.exec('npx degit sveltejs/template scripts/svelte-app');
|
||||
|
||||
// remove src (will be recreated client-side) and node_modules
|
||||
sh.rm('-rf', 'scripts/svelte-app/src');
|
||||
sh.rm('-rf', 'scripts/svelte-app/node_modules');
|
||||
|
||||
// build svelte-app.json
|
||||
const appPath = 'scripts/svelte-app';
|
||||
const files = [];
|
||||
|
||||
for (const path of sh.find(appPath).filter(p => fs.lstatSync(p).isFile()) ) {
|
||||
const bytes = fs.readFileSync(path);
|
||||
const string = bytes.toString();
|
||||
const data = bytes.compare(Buffer.from(string)) === 0 ? string : [...bytes];
|
||||
files.push({ path: path.slice(appPath.length + 1), data });
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputFile, JSON.stringify(files));
|
||||
@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang='en' class="theme-default typo-default">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<meta name='theme-color' content='#ff3e00'>
|
||||
|
||||
<base href='/'>
|
||||
|
||||
<link rel='stylesheet' href='global.css'>
|
||||
<link rel='stylesheet' href='prism.css'>
|
||||
<link rel='manifest' href='manifest.json'>
|
||||
<link rel='icon' type='image/png' href='favicon.png'>
|
||||
|
||||
<meta name='twitter:card' content='summary_large_image'>
|
||||
<meta name='twitter:site' content='@sveltejs'>
|
||||
<meta name='twitter:creator' content='@sveltejs'>
|
||||
<meta name='twitter:image' content='https://svelte.dev/images/twitter-card.png'>
|
||||
<meta name='og:image' content='https://svelte.dev/images/twitter-card.png'>
|
||||
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
<div id='svelte'>%svelte.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,59 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let once = false;
|
||||
export let top = 0;
|
||||
export let bottom = 0;
|
||||
export let left = 0;
|
||||
export let right = 0;
|
||||
|
||||
let intersecting = false;
|
||||
let container;
|
||||
|
||||
onMount(() => {
|
||||
if (typeof IntersectionObserver !== 'undefined') {
|
||||
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
intersecting = entries[0].isIntersecting;
|
||||
if (intersecting && once) {
|
||||
observer.unobserve(container);
|
||||
}
|
||||
}, {
|
||||
rootMargin
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
return () => observer.unobserve(container);
|
||||
}
|
||||
|
||||
function handler() {
|
||||
const bcr = container.getBoundingClientRect();
|
||||
|
||||
intersecting = (
|
||||
(bcr.bottom + bottom) > 0 &&
|
||||
(bcr.right + right) > 0 &&
|
||||
(bcr.top - top) < window.innerHeight &&
|
||||
(bcr.left - left) < window.innerWidth
|
||||
);
|
||||
|
||||
if (intersecting && once) {
|
||||
window.removeEventListener('scroll', handler);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handler);
|
||||
return () => window.removeEventListener('scroll', handler);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div bind:this={container}>
|
||||
<slot {intersecting}></slot>
|
||||
</div>
|
||||
@ -1,11 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let constructor;
|
||||
|
||||
onMount(async () => {
|
||||
constructor = await $$props.this();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:component this={constructor} {...$$props}/>
|
||||
@ -1,63 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let p = 0;
|
||||
let visible = false;
|
||||
|
||||
onMount(() => {
|
||||
function next() {
|
||||
visible = true;
|
||||
p += 0.1;
|
||||
|
||||
const remaining = 1 - p;
|
||||
if (remaining > 0.15) setTimeout(next, 500 / remaining);
|
||||
}
|
||||
|
||||
setTimeout(next, 250);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.progress-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background-color: var(--prime);
|
||||
transition: width 0.4s;
|
||||
}
|
||||
|
||||
.fade {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255,255,255,0.3);
|
||||
pointer-events: none;
|
||||
z-index: 998;
|
||||
animation: fade 0.4s;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
from { opacity: 0 }
|
||||
to { opacity: 1 }
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if visible}
|
||||
<div class="progress-container">
|
||||
<div class="progress" style="width: {p * 100}%"></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if p >= 0.4}
|
||||
<div class="fade"></div>
|
||||
{/if}
|
||||
@ -1,27 +0,0 @@
|
||||
<script>
|
||||
export let checked;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.input-output-toggle {
|
||||
display: grid;
|
||||
user-select: none;
|
||||
flex: 0;
|
||||
grid-template-columns: 1fr 40px 1fr;
|
||||
grid-gap: 0.5em;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
border-top: 1px solid var(--second);
|
||||
}
|
||||
|
||||
input { display: block }
|
||||
span { color: #ccc }
|
||||
.active { color: #555 }
|
||||
</style>
|
||||
|
||||
<label class="input-output-toggle">
|
||||
<span class:active={!checked} style="text-align: right">input</span>
|
||||
<input type="checkbox" bind:checked>
|
||||
<span class:active={checked}>output</span>
|
||||
</label>
|
||||
@ -1,136 +0,0 @@
|
||||
<script>
|
||||
import Repl from '@sveltejs/svelte-repl';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
import { process_example } from '../../utils/examples';
|
||||
import InputOutputToggle from './InputOutputToggle.svelte';
|
||||
|
||||
export let version = '3';
|
||||
export let gist = null;
|
||||
export let example = null;
|
||||
export let embedded = false;
|
||||
|
||||
let repl;
|
||||
let name = 'loading...';
|
||||
let width = browser
|
||||
? window.innerWidth - 32
|
||||
: 1000;
|
||||
|
||||
let checked = false;
|
||||
|
||||
onMount(() => {
|
||||
if (version !== 'local') {
|
||||
fetch(`https://unpkg.com/svelte@${version}/package.json`)
|
||||
.then(r => r.json())
|
||||
.then(pkg => {
|
||||
version = pkg.version;
|
||||
});
|
||||
}
|
||||
|
||||
if (gist) {
|
||||
fetch(`repl/${gist}.json`).then(r => r.json()).then(data => {
|
||||
const { description, files } = data;
|
||||
|
||||
name = description;
|
||||
|
||||
const components = Object.keys(files)
|
||||
.map(file => {
|
||||
const dot = file.lastIndexOf('.');
|
||||
if (!~dot) return;
|
||||
|
||||
const source = files[file].content;
|
||||
|
||||
return {
|
||||
name: file.slice(0, dot),
|
||||
type: file.slice(dot + 1),
|
||||
source
|
||||
};
|
||||
})
|
||||
.filter(x => x.type === 'svelte' || x.type === 'js')
|
||||
.sort((a, b) => {
|
||||
if (a.name === 'App' && a.type === 'svelte') return -1;
|
||||
if (b.name === 'App' && b.type === 'svelte') return 1;
|
||||
|
||||
if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
|
||||
|
||||
return a.name < b.name ? -1 : 1;
|
||||
});
|
||||
|
||||
repl.set({ components });
|
||||
});
|
||||
} else if (example) {
|
||||
fetch(`examples/${example}.json`).then(async response => {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
repl.set({
|
||||
components: process_example(data.files)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$: if (embedded) document.title = `${name} • Svelte REPL`;
|
||||
|
||||
$: svelteUrl = browser && version === 'local' ?
|
||||
`${location.origin}/repl/local` :
|
||||
`https://unpkg.com/svelte@${version}`;
|
||||
|
||||
const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
|
||||
|
||||
$: mobile = width < 560;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.repl-outer {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--back);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
--pane-controls-h: 4.2rem;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mobile .viewport {
|
||||
width: 200%;
|
||||
height: calc(100% - 42px);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.mobile .offset {
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="repl-outer" bind:clientWidth={width} class:mobile>
|
||||
<div class="viewport" class:offset={checked}>
|
||||
{#if browser}
|
||||
<Repl
|
||||
bind:this={repl}
|
||||
workersUrl="workers"
|
||||
fixed={mobile}
|
||||
{svelteUrl}
|
||||
{rollupUrl}
|
||||
embedded
|
||||
relaxed
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if mobile}
|
||||
<InputOutputToggle bind:checked/>
|
||||
{/if}
|
||||
</div>
|
||||
@ -1,46 +0,0 @@
|
||||
<script>
|
||||
export let labels;
|
||||
export let offset = 0;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.toggle {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 4.6rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--second);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 .15em;
|
||||
width: 4em;
|
||||
height: 1em;
|
||||
padding: .3em .4em;
|
||||
border-radius: var(--border-r);
|
||||
line-height: 1em;
|
||||
box-sizing: content-box;
|
||||
color: #888;
|
||||
border: 1px solid var(--back-light);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--prime);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="toggle">
|
||||
{#each labels as label, index}
|
||||
<button
|
||||
class:selected={offset === index}
|
||||
on:click={() => offset = index}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@ -1,5 +0,0 @@
|
||||
// REPL props
|
||||
|
||||
export const svelteUrl = `https://unpkg.com/svelte@3`;
|
||||
export const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
|
||||
export const mapbox_setup = `window.MAPBOX_ACCESS_TOKEN = '${import.meta.env.VITE_MAPBOX_ACCESS_TOKEN}';`;
|
||||
@ -1,31 +0,0 @@
|
||||
// need to do this first before importing database, etc.
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import * as cookie from 'cookie';
|
||||
import { get_user, sanitize_user } from './utils/auth';
|
||||
import { query } from './utils/db';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
export async function handle({ request, resolve }) {
|
||||
if (process.env['PGHOST']) {
|
||||
// this is a convenient time to clear out expired sessions
|
||||
query('delete from sessions where expiry < now()');
|
||||
|
||||
request.locals.cookies = cookie.parse(request.headers.cookie || '');
|
||||
request.locals.user = await get_user(request.locals.cookies.sid);
|
||||
}
|
||||
|
||||
const response = await resolve(request);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/** @type {import('@sveltejs/kit').GetSession} */
|
||||
export function getSession(request) {
|
||||
return request.locals.user
|
||||
? {
|
||||
user: sanitize_user(request.locals.user)
|
||||
}
|
||||
: {};
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
<script context="module">
|
||||
/** @type {import('@sveltejs/kit').ErrorLoad} */
|
||||
export function load({ error, status }) {
|
||||
return {
|
||||
props: { error, status }
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let status;
|
||||
export let error;
|
||||
|
||||
// we don't want to use <svelte:window bind:online> here,
|
||||
// because we only care about the online state when
|
||||
// the page first loads
|
||||
const online = typeof navigator !== 'undefined'
|
||||
? navigator.onLine
|
||||
: true;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav);
|
||||
}
|
||||
|
||||
h1, p { margin: 0 auto }
|
||||
|
||||
h1 {
|
||||
font-size: 2.8em;
|
||||
font-weight: 300;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
p { margin: 1em auto }
|
||||
|
||||
.error {
|
||||
background-color: var(--second);
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
font: 600 16px/1.7 var(--font);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* @media (min-width: 480px) {
|
||||
h1 { font-size: 4em }
|
||||
} */
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>{status}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
{#if online}
|
||||
<h1>Yikes!</h1>
|
||||
|
||||
{#if error.message}
|
||||
<p class="error">{status}: {error.message}</p>
|
||||
{:else}
|
||||
<p class="error">Encountered a {status} error</p>
|
||||
{/if}
|
||||
|
||||
{#if import.meta.env.DEV && error.stack}
|
||||
<pre>{error.stack}</pre>
|
||||
{:else}
|
||||
{#if status >= 500}
|
||||
<p>Please try reloading the page.</p>
|
||||
{/if}
|
||||
|
||||
<p>If the error persists, please drop by <a href="chat">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<h1>It looks like you're offline</h1>
|
||||
|
||||
<p>Reload the page once you've found the internet.</p>
|
||||
{/if}
|
||||
</div>
|
||||
@ -1,70 +0,0 @@
|
||||
<script>
|
||||
import '@sveltejs/site-kit/base.css';
|
||||
import { setContext } from 'svelte';
|
||||
import { page, navigating, session } from '$app/stores';
|
||||
import { Icon, Icons, Nav, NavItem } from '@sveltejs/site-kit';
|
||||
import PreloadingIndicator from '../components/PreloadingIndicator.svelte';
|
||||
|
||||
export let segment;
|
||||
|
||||
setContext('app', {
|
||||
login: () => {
|
||||
const login_window = window.open(`${window.location.origin}/auth/login`, 'login', 'width=600,height=400');
|
||||
|
||||
window.addEventListener('message', function handler(event) {
|
||||
login_window.close();
|
||||
window.removeEventListener('message', handler);
|
||||
$session.user = event.data.user;
|
||||
});
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
const r = await fetch(`/auth/logout`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (r.ok) $session.user = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Icons/>
|
||||
|
||||
{#if $navigating && $navigating.to}
|
||||
<PreloadingIndicator/>
|
||||
{/if}
|
||||
|
||||
{#if $page.path !== '/repl/embed'}
|
||||
<Nav {segment} {page} logo="svelte-logo-horizontal.svg">
|
||||
<NavItem segment="tutorial">Tutorial</NavItem>
|
||||
<NavItem segment="docs">Docs</NavItem>
|
||||
<NavItem segment="examples">Examples</NavItem>
|
||||
<NavItem segment="repl">REPL</NavItem>
|
||||
<NavItem segment="blog">Blog</NavItem>
|
||||
<NavItem segment="faq">FAQ</NavItem>
|
||||
|
||||
<NavItem external="https://kit.svelte.dev">SvelteKit</NavItem>
|
||||
|
||||
<NavItem external="chat" title="Discord Chat">
|
||||
<Icon name="message-square"/>
|
||||
</NavItem>
|
||||
|
||||
<NavItem external="https://github.com/sveltejs/svelte" title="GitHub Repo">
|
||||
<Icon name="github"/>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
{/if}
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
/* padding: var(--nav-h) var(--side-nav) 0 var(--side-nav); */
|
||||
padding: var(--nav-h) 0 0 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
@ -1,26 +0,0 @@
|
||||
<script>
|
||||
import contributors from '../_contributors.js';
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.contributor {
|
||||
width: 2.4em;
|
||||
height: 2.4em;
|
||||
border-radius: 50%;
|
||||
text-indent: -9999px;
|
||||
display: inline-block;
|
||||
background: no-repeat url(/contributors.jpg);
|
||||
background-size: auto 102%;
|
||||
margin: 0 0.5em 0.5em 0;
|
||||
border: 2px solid var(--second);
|
||||
}
|
||||
</style>
|
||||
|
||||
{#each contributors as contributor, i}
|
||||
<a
|
||||
class="contributor"
|
||||
style="background-position: {(100 * i) / (contributors.length - 1)}% 0"
|
||||
href="https://github.com/{contributor}">
|
||||
{contributor}
|
||||
</a>
|
||||
{/each}
|
||||
@ -1,27 +0,0 @@
|
||||
<script>
|
||||
import donors from '../_donors.js';
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.donor {
|
||||
width: 2.4em;
|
||||
height: 2.4em;
|
||||
border-radius: 50%;
|
||||
text-indent: -9999px;
|
||||
display: inline-block;
|
||||
background: no-repeat url(/donors.jpg);
|
||||
background-size: auto 102%;
|
||||
margin: 0 0.5em 0.5em 0;
|
||||
border: 2px solid var(--second);
|
||||
}
|
||||
</style>
|
||||
|
||||
{#each donors as donor, i}
|
||||
<a
|
||||
class="donor"
|
||||
style="background-position: {(100 * i) / (donors.length - 1)}% 0"
|
||||
alt="{donor}"
|
||||
href="https://opencollective.com/svelte">
|
||||
{donor}
|
||||
</a>
|
||||
{/each}
|
||||
@ -1,52 +0,0 @@
|
||||
<script>
|
||||
import { Section } from '@sveltejs/site-kit';
|
||||
import IntersectionObserver from '../../components/IntersectionObserver.svelte';
|
||||
import ReplWidget from '../../components/Repl/ReplWidget.svelte';
|
||||
|
||||
export let id;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.example {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.example :global(a) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.example > :global(p) {
|
||||
margin: 4.4rem 2.4rem 2.4rem 0;
|
||||
}
|
||||
|
||||
.repl-container {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
border-radius: var(--border-r);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 920px) {
|
||||
.example {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-gap: 0.5em;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<Section>
|
||||
<div class="example">
|
||||
<slot></slot>
|
||||
|
||||
<div class="repl-container">
|
||||
<IntersectionObserver once let:intersecting top={400}>
|
||||
{#if intersecting}
|
||||
<!-- <Lazy this={loadReplWidget} example={id}/> -->
|
||||
<ReplWidget example={id}/>
|
||||
{/if}
|
||||
</IntersectionObserver>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
@ -1,80 +0,0 @@
|
||||
export const companies = [
|
||||
{
|
||||
href: "https://1password.com",
|
||||
filename: "1password.svg",
|
||||
alt: "1Password logo",
|
||||
},
|
||||
{
|
||||
href: "https://www.alaskaair.com/",
|
||||
style: "background-color: #01426a;",
|
||||
filename: "alaskaairlines.svg",
|
||||
alt: "Alaska Airlines logo",
|
||||
},
|
||||
{
|
||||
href: "https://avast.com",
|
||||
filename: "avast.svg",
|
||||
alt: "Avast logo",
|
||||
},
|
||||
{
|
||||
href: "https://chess.com",
|
||||
style: "background-color: #312e2b;",
|
||||
filename: "chess.svg",
|
||||
alt: "Chess.com logo",
|
||||
},
|
||||
{
|
||||
href: "https://fusioncharts.com",
|
||||
filename: "fusioncharts.svg",
|
||||
alt: "FusionCharts logo",
|
||||
},
|
||||
{
|
||||
href: "https://godaddy.com",
|
||||
filename: "godaddy.svg",
|
||||
alt: "GoDaddy logo",
|
||||
},
|
||||
{
|
||||
href: "https://www.ibm.com/",
|
||||
filename: "ibm.svg",
|
||||
alt: "IBM logo",
|
||||
},
|
||||
{
|
||||
href: "https://media.lesechos.fr/infographie",
|
||||
filename: "les-echos.svg",
|
||||
alt: "Les Echos",
|
||||
},
|
||||
{
|
||||
href: "https://www.philips.co.uk",
|
||||
filename: "philips.svg",
|
||||
alt: "Philips logo",
|
||||
},
|
||||
{
|
||||
href: "https://global.rakuten.com/corp/",
|
||||
filename: "rakuten.svg",
|
||||
alt: "Rakuten logo",
|
||||
},
|
||||
{
|
||||
href: "https://razorpay.com",
|
||||
filename: "razorpay.svg",
|
||||
alt: "Razorpay logo",
|
||||
},
|
||||
{
|
||||
href: "https://www.se.com",
|
||||
style: " background-color: #3dcd58; ",
|
||||
filename: "Schneider_Electric.svg",
|
||||
alt: "Schneider Electric",
|
||||
},
|
||||
{
|
||||
href: "https://squareup.com",
|
||||
filename: "square.svg",
|
||||
alt: "Square",
|
||||
},
|
||||
{
|
||||
href: "https://nytimes.com",
|
||||
filename: "nyt.svg",
|
||||
alt: "The New York Times logo",
|
||||
},
|
||||
{
|
||||
href: "https://transloadit.com",
|
||||
filename: "transloadit.svg",
|
||||
alt: "Transloadit",
|
||||
},
|
||||
];
|
||||
@ -1,66 +0,0 @@
|
||||
<script>
|
||||
import { companies } from './WhosUsingSvelte.js';
|
||||
const randomizer = ({ prominent }) => Math.random();
|
||||
const doSort = (a, b) => randomizer(b) - randomizer(a);
|
||||
const sortedCompanies = companies.sort(doSort);
|
||||
</script>
|
||||
|
||||
<div class="logos">
|
||||
{#each sortedCompanies as { href, filename, alt, style, picture, span }, index}
|
||||
<a target="_blank" rel="noopener" {href} style={style || ""}>
|
||||
{#if picture}
|
||||
<picture>
|
||||
{#each picture as { type, srcset }}
|
||||
<source {type} {srcset} />
|
||||
{/each}
|
||||
<img src="/whos-using-svelte/{filename}" {alt} loading="lazy" />
|
||||
</picture>
|
||||
{:else}
|
||||
<img src="/whos-using-svelte/{filename}" {alt} loading="lazy" />
|
||||
{#if span}
|
||||
<span>{span}</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.logos {
|
||||
margin: 1em 0 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
a {
|
||||
height: 40px;
|
||||
margin: 0 0.5em 0.5em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 2px solid var(--second);
|
||||
padding: 0;
|
||||
border-radius: 20px;
|
||||
color: var(--text);
|
||||
}
|
||||
picture,
|
||||
img {
|
||||
height: 100%;
|
||||
padding: 5px 10px;
|
||||
transition: transform 0.2s;
|
||||
min-width: 0; /* Avoid image overflow in Safari */
|
||||
}
|
||||
picture:hover,
|
||||
img:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
@media (min-width: 540px) {
|
||||
a {
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
picture,
|
||||
img {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +0,0 @@
|
||||
import { query as db_query } from '../../utils/db';
|
||||
|
||||
export async function get({ query, locals }) {
|
||||
if (locals.user) {
|
||||
const page_size = 100;
|
||||
const offset = query.get('offset') ? parseInt(query.get('offset')) : 0;
|
||||
const rows = await db_query(`
|
||||
select g.uid, g.name, coalesce(g.updated_at, g.created_at) as updated_at
|
||||
from gists g
|
||||
where g.user_id = $1
|
||||
order by id desc
|
||||
limit ${page_size + 1}
|
||||
offset $2
|
||||
`, [locals.user.id, offset]);
|
||||
|
||||
rows.forEach(row => {
|
||||
row.uid = row.uid.replace(/-/g, '');
|
||||
});
|
||||
|
||||
const more = rows.length > page_size;
|
||||
return { body: { apps: rows.slice(0, page_size), offset: more ? offset + page_size : null }};
|
||||
} else {
|
||||
return { status: 401 };
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export const oauth = 'https://github.com/login/oauth';
|
||||
export const baseurl = process.env['BASEURL'];
|
||||
export const secure = baseurl && baseurl.startsWith('https:');
|
||||
|
||||
export const client_id = process.env['GITHUB_CLIENT_ID'];
|
||||
export const client_secret = process.env['GITHUB_CLIENT_SECRET'];
|
||||
@ -1,54 +0,0 @@
|
||||
import devalue from 'devalue';
|
||||
import * as cookie from 'cookie';
|
||||
import * as httpie from 'httpie';
|
||||
import { parse, stringify } from 'querystring';
|
||||
import { sanitize_user, create_user, create_session } from '../../utils/auth';
|
||||
import { oauth, secure, client_id, client_secret } from './_config.js';
|
||||
|
||||
export async function get({ query }) {
|
||||
try {
|
||||
// Trade "code" for "access_token"
|
||||
const r1 = await httpie.post(`${oauth}/access_token?` + stringify({
|
||||
code: query.get('code'),
|
||||
client_id,
|
||||
client_secret,
|
||||
}));
|
||||
|
||||
// Now fetch User details
|
||||
const { access_token } = parse(r1.data);
|
||||
const r2 = await httpie.get('https://api.github.com/user', {
|
||||
headers: {
|
||||
'User-Agent': 'svelte.dev',
|
||||
Authorization: `token ${access_token}`
|
||||
}
|
||||
});
|
||||
|
||||
const user = await create_user(r2.data, access_token);
|
||||
const session = await create_session(user);
|
||||
|
||||
return {
|
||||
headers: {
|
||||
'Set-Cookie': cookie.serialize('sid', session.uid, {
|
||||
maxAge: 31536000,
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure
|
||||
}),
|
||||
'Content-Type': 'text/html; charset=utf-8'
|
||||
},
|
||||
body: `
|
||||
<script>
|
||||
window.opener.postMessage({
|
||||
user: ${devalue(sanitize_user(user))}
|
||||
}, window.location.origin);
|
||||
</script>
|
||||
`
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('GET /auth/callback', err);
|
||||
return {
|
||||
status: 500,
|
||||
body: err.data
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { stringify } from 'querystring';
|
||||
import { oauth, baseurl, client_id } from './_config.js';
|
||||
|
||||
export const get = client_id
|
||||
? () => {
|
||||
const Location = `${oauth}/authorize?` + stringify({
|
||||
scope: 'read:user',
|
||||
client_id,
|
||||
redirect_uri: `${baseurl}/auth/callback`,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location
|
||||
}
|
||||
};
|
||||
}
|
||||
: () => {
|
||||
return {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'text/html; charset=utf-8'
|
||||
},
|
||||
body: `
|
||||
<body style="font-family: sans-serif; background: rgb(255,215,215); border: 2px solid red; margin: 0; padding: 1em;">
|
||||
<h1>Missing .env file</h1>
|
||||
<p>In order to use GitHub authentication, you will need to <a target="_blank" href="https://github.com/settings/developers">register an OAuth application</a> and create a local .env file:</p>
|
||||
<pre>GITHUB_CLIENT_ID=[YOUR_APP_ID]\nGITHUB_CLIENT_SECRET=[YOUR_APP_SECRET]\nBASEURL=http://localhost:3000</pre>
|
||||
<p>The <code>BASEURL</code> variable should match the callback URL specified for your app.</p>
|
||||
<p>See also <a target="_blank" href="https://github.com/sveltejs/svelte/tree/master/site#repl-github-integration">here</a></p>
|
||||
</body>
|
||||
`
|
||||
};
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
import * as cookie from 'cookie';
|
||||
import { secure } from './_config.js';
|
||||
import { delete_session } from '../../utils/auth.js';
|
||||
|
||||
export async function get(request) {
|
||||
await delete_session(request.locals.cookies.sid);
|
||||
|
||||
return {
|
||||
headers: {
|
||||
'Set-Cookie': cookie.serialize('sid', '', {
|
||||
maxAge: -1,
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import get_posts from './_posts.js';
|
||||
|
||||
let lookup;
|
||||
|
||||
export function get({ params }) {
|
||||
if (!lookup || process.env.NODE_ENV !== 'production') {
|
||||
lookup = new Map();
|
||||
get_posts().forEach(post => {
|
||||
lookup.set(post.slug, post);
|
||||
});
|
||||
}
|
||||
|
||||
const post = lookup.get(params.slug);
|
||||
|
||||
if (post) {
|
||||
return {
|
||||
body: post,
|
||||
headers: {
|
||||
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
<script context="module">
|
||||
export async function load({ fetch, page: { params } }) {
|
||||
const res = await fetch(`/blog/${params.slug}.json`);
|
||||
if (res.ok) {
|
||||
return { props: { post: await res.json() }};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let post;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{post.metadata.title}</title>
|
||||
|
||||
<meta name="twitter:title" content={post.metadata.title}>
|
||||
<meta name="twitter:description" content={post.metadata.description}>
|
||||
<meta name="Description" content={post.metadata.description}>
|
||||
</svelte:head>
|
||||
|
||||
<article class='post listify'>
|
||||
<h1>{post.metadata.title}</h1>
|
||||
<p class='standfirst'>{post.metadata.description}</p>
|
||||
|
||||
<p class='byline'><a href='{post.metadata.authorURL}'>{post.metadata.author}</a> <time datetime='{post.metadata.pubdate}'>{post.metadata.dateString}</time></p>
|
||||
|
||||
{@html post.html}
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.post {
|
||||
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav);
|
||||
max-width: var(--main-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.standfirst {
|
||||
font-size: var(--h4);
|
||||
color: var(--second);
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
.byline {
|
||||
margin: 0 0 6rem 0;
|
||||
padding: 1.6rem 0 0 0;
|
||||
border-top: var(--border-w) solid #6767785b;
|
||||
font-size: var(--h6);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.byline a {
|
||||
/* border-bottom: none; */
|
||||
/* font-weight: 600; */
|
||||
}
|
||||
|
||||
.byline a:hover {
|
||||
/* border-bottom: 2px solid var(--prime); */
|
||||
}
|
||||
|
||||
.post h1 {
|
||||
color: var(--second);
|
||||
max-width: 20em;
|
||||
margin: 0 0 .8rem 0;
|
||||
}
|
||||
|
||||
.post :global(h2) {
|
||||
margin: 2em 0 0.5em 0;
|
||||
/* color: var(--second); */
|
||||
color: var(--text);
|
||||
font-size: var(--h3);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.post :global(figure) {
|
||||
margin: 1.6rem 0 3.2rem 0;
|
||||
}
|
||||
|
||||
.post :global(figure) :global(img) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.post :global(figcaption) {
|
||||
color: var(--second);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.post :global(video) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post :global(blockquote) {
|
||||
max-width: none;
|
||||
border-left: 4px solid #eee;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0 var(--border-r) var(--border-r) 0;
|
||||
}
|
||||
|
||||
.post :global(code) {
|
||||
padding: .3rem .8rem .3rem;
|
||||
margin: 0 0.2rem;
|
||||
top: -.1rem;
|
||||
background: var(--back-api);
|
||||
}
|
||||
|
||||
.post :global(pre) :global(code) {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
top: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.post :global(aside) {
|
||||
float: right;
|
||||
margin: 0 0 1em 1em;
|
||||
width: 16rem;
|
||||
color: var(--second);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.post :global(.max) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post :global(iframe) {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
margin: 2em 0;
|
||||
border-radius: var(--border-r);
|
||||
border: 0.8rem solid var(--second);
|
||||
}
|
||||
|
||||
.post :global(.anchor) {
|
||||
top: calc((var(--h3) - 24px) / 2);
|
||||
}
|
||||
|
||||
.post :global(a) {
|
||||
padding: 0;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.post :global(a):not(:hover) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post :global(.anchor) {
|
||||
transform: scale(0.6);
|
||||
opacity: 1;
|
||||
left: -1.0em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 910px) {
|
||||
.post :global(.max) {
|
||||
width: calc(100vw - 2 * var(--side-nav));
|
||||
margin: 0 calc(var(--main-width) / 2 - 50vw);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.post :global(.max) > :global(*) {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.post :global(iframe) {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
margin: 2em auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,59 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
|
||||
import marked from 'marked';
|
||||
import { makeSlugProcessor } from '../../utils/slug';
|
||||
import { highlight } from '../../utils/highlight';
|
||||
import { SLUG_PRESERVE_UNICODE } from '../../../config';
|
||||
|
||||
const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE);
|
||||
|
||||
export default function get_posts() {
|
||||
return fs
|
||||
.readdirSync('content/blog')
|
||||
.map(file => {
|
||||
if (path.extname(file) !== '.md') return;
|
||||
|
||||
const match = /^(\d+-\d+-\d+)-(.+)\.md$/.exec(file);
|
||||
if (!match) throw new Error(`Invalid filename '${file}'`);
|
||||
|
||||
const [, pubdate, slug] = match;
|
||||
|
||||
const markdown = fs.readFileSync(`content/blog/${file}`, 'utf-8');
|
||||
|
||||
const { content, metadata } = extract_frontmatter(markdown);
|
||||
|
||||
const date = new Date(`${pubdate} EDT`); // cheeky hack
|
||||
metadata.pubdate = pubdate;
|
||||
metadata.dateString = date.toDateString();
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.link = link_renderer;
|
||||
|
||||
renderer.code = highlight;
|
||||
|
||||
renderer.heading = (text, level, rawtext) => {
|
||||
const fragment = makeSlug(rawtext);
|
||||
|
||||
return `
|
||||
<h${level}>
|
||||
<span id="${fragment}" class="offset-anchor"></span>
|
||||
<a href="blog/${slug}#${fragment}" class="anchor" aria-hidden="true"></a>
|
||||
${text}
|
||||
</h${level}>`;
|
||||
};
|
||||
|
||||
const html = marked(
|
||||
content.replace(/^\t+/gm, match => match.split('\t').join(' ')),
|
||||
{ renderer }
|
||||
);
|
||||
|
||||
return {
|
||||
html,
|
||||
metadata,
|
||||
slug
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.metadata.pubdate < b.metadata.pubdate ? 1 : -1);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import get_posts from './_posts.js';
|
||||
|
||||
let json;
|
||||
|
||||
export function get() {
|
||||
if (!json || process.env.NODE_ENV !== 'production') {
|
||||
const posts = get_posts()
|
||||
.filter(post => !post.metadata.draft)
|
||||
.map(post => {
|
||||
return {
|
||||
slug: post.slug,
|
||||
metadata: post.metadata
|
||||
};
|
||||
});
|
||||
|
||||
json = JSON.stringify(posts);
|
||||
}
|
||||
|
||||
return {
|
||||
body: json,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
import get_posts from '../blog/_posts.js';
|
||||
|
||||
const months = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
|
||||
|
||||
function formatPubdate(str) {
|
||||
const [y, m, d] = str.split('-');
|
||||
return `${d} ${months[+m]} ${y} 12:00 +0000`;
|
||||
}
|
||||
|
||||
function escapeHTML(html) {
|
||||
const chars = {
|
||||
'"' : 'quot',
|
||||
"'": '#39',
|
||||
'&': 'amp',
|
||||
'<' : 'lt',
|
||||
'>' : 'gt'
|
||||
};
|
||||
|
||||
return html.replace(/["'&<>]/g, c => `&${chars[c]};`);
|
||||
}
|
||||
|
||||
const rss = `
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
|
||||
<channel>
|
||||
<title>Svelte blog</title>
|
||||
<link>https://svelte.dev/blog</link>
|
||||
<description>News and information about the magical disappearing UI framework</description>
|
||||
<image>
|
||||
<url>https://svelte.dev/favicon.png</url>
|
||||
<title>Svelte</title>
|
||||
<link>https://svelte.dev/blog</link>
|
||||
</image>
|
||||
${get_posts().filter(post => !post.metadata.draft).map(post => `
|
||||
<item>
|
||||
<title>${escapeHTML(post.metadata.title)}</title>
|
||||
<link>https://svelte.dev/blog/${post.slug}</link>
|
||||
<description>${escapeHTML(post.metadata.description)}</description>
|
||||
<pubDate>${formatPubdate(post.metadata.pubdate)}</pubDate>
|
||||
</item>
|
||||
`).join('')}
|
||||
</channel>
|
||||
|
||||
</rss>
|
||||
`.replace(/>[^\S]+/gm, '>').replace(/[^\S]+</gm, '<').trim();
|
||||
|
||||
export function get() {
|
||||
return {
|
||||
body: rss,
|
||||
headers: {
|
||||
'Cache-Control': `max-age=${30 * 60 * 1e3}`,
|
||||
'Content-Type': 'application/rss+xml'
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export function get() {
|
||||
return {
|
||||
status: 302,
|
||||
headers: { Location: 'https://discord.gg/yy75DKs' },
|
||||
};
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { SLUG_PRESERVE_UNICODE, SLUG_SEPARATOR } from '../../../config';
|
||||
import { extract_frontmatter, extract_metadata, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
|
||||
import { make_session_slug_processor } from '@sveltejs/site-kit/utils/slug';
|
||||
import { highlight } from '../../utils/highlight';
|
||||
import marked from 'marked';
|
||||
|
||||
const blockTypes = [
|
||||
'blockquote',
|
||||
'html',
|
||||
'heading',
|
||||
'hr',
|
||||
'list',
|
||||
'listitem',
|
||||
'paragraph',
|
||||
'table',
|
||||
'tablerow',
|
||||
'tablecell'
|
||||
];
|
||||
|
||||
export default function() {
|
||||
const make_slug = make_session_slug_processor({
|
||||
preserve_unicode: SLUG_PRESERVE_UNICODE,
|
||||
separator: SLUG_SEPARATOR
|
||||
});
|
||||
|
||||
return fs
|
||||
.readdirSync(`content/docs`)
|
||||
.filter(file => file[0] !== '.' && path.extname(file) === '.md')
|
||||
.map(file => {
|
||||
const markdown = fs.readFileSync(`content/docs/${file}`, 'utf-8');
|
||||
|
||||
const { content, metadata } = extract_frontmatter(markdown);
|
||||
|
||||
const section_slug = make_slug(metadata.title);
|
||||
|
||||
const subsections = [];
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
let block_open = false;
|
||||
|
||||
renderer.link = link_renderer;
|
||||
|
||||
renderer.hr = () => {
|
||||
block_open = true;
|
||||
|
||||
return '<div class="side-by-side"><div class="copy">';
|
||||
};
|
||||
|
||||
renderer.code = (source, lang) => {
|
||||
source = source.replace(/^ +/gm, match =>
|
||||
match.split(' ').join('\t')
|
||||
);
|
||||
|
||||
const lines = source.split('\n');
|
||||
|
||||
const meta = extract_metadata(lines[0], lang);
|
||||
|
||||
let prefix = '';
|
||||
let className = 'code-block';
|
||||
|
||||
if (meta) {
|
||||
source = lines.slice(1).join('\n');
|
||||
const filename = meta.filename || (lang === 'html' && 'App.svelte');
|
||||
if (filename) {
|
||||
prefix = `<span class='filename'>${prefix} ${filename}</span>`;
|
||||
className += ' named';
|
||||
}
|
||||
}
|
||||
|
||||
if (meta && meta.hidden) return '';
|
||||
|
||||
const html = `<div class='${className}'>${prefix}${highlight(source, lang)}</div>`;
|
||||
|
||||
if (block_open) {
|
||||
block_open = false;
|
||||
return `</div><div class="code">${html}</div></div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
renderer.heading = (text, level, rawtext) => {
|
||||
let slug;
|
||||
|
||||
const match = /<a href="([^"]+)"[^>]*>(.+)<\/a>/.exec(text);
|
||||
if (match) {
|
||||
slug = match[1];
|
||||
text = match[2];
|
||||
} else {
|
||||
slug = make_slug(rawtext);
|
||||
}
|
||||
|
||||
if (level === 3 || level === 4) {
|
||||
const title = text
|
||||
.replace(/<\/?code>/g, '')
|
||||
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
|
||||
if ($3) return `.${$1}(...)`;
|
||||
if ($2) return `.${$1}()`;
|
||||
return `.${$1}`;
|
||||
});
|
||||
|
||||
subsections.push({ slug, title, level });
|
||||
}
|
||||
|
||||
return `
|
||||
<h${level}>
|
||||
<span id="${slug}" class="offset-anchor" ${level > 4 ? 'data-scrollignore' : ''}></span>
|
||||
<a href="docs#${slug}" class="anchor" aria-hidden="true"></a>
|
||||
${text}
|
||||
</h${level}>`;
|
||||
};
|
||||
|
||||
blockTypes.forEach(type => {
|
||||
const fn = renderer[type];
|
||||
renderer[type] = function() {
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
const html = marked(content, { renderer });
|
||||
|
||||
const hashes = {};
|
||||
|
||||
return {
|
||||
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m),
|
||||
metadata,
|
||||
subsections,
|
||||
slug: section_slug,
|
||||
file,
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import get_sections from './_sections.js';
|
||||
|
||||
let json;
|
||||
|
||||
export function get() {
|
||||
if (!json || process.env.NODE_ENV !== 'production') {
|
||||
json = get_sections();
|
||||
}
|
||||
|
||||
return {
|
||||
body: json
|
||||
};
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
<script context="module">
|
||||
const title_replacements = {
|
||||
'1_export_creates_a_component_prop': 'props',
|
||||
'2_Assignments_are_reactive': 'reactive assignments',
|
||||
'3_$_marks_a_statement_as_reactive': 'reactive statements ($:)',
|
||||
'4_Prefix_stores_with_$_to_access_their_values': 'accessing stores ($)'
|
||||
};
|
||||
|
||||
export async function load({ fetch }) {
|
||||
const sections = await fetch(`docs.json`).then(r => r.json());
|
||||
for (const section of sections) {
|
||||
for (const subsection of section.subsections) {
|
||||
const { slug } = subsection;
|
||||
// show abbreviated title in the table of contents
|
||||
if (slug in title_replacements) {
|
||||
subsection.title = title_replacements[slug];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
sections
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Docs } from '@sveltejs/site-kit';
|
||||
|
||||
export let sections;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>API Docs • Svelte</title>
|
||||
|
||||
<meta name="twitter:title" content="Svelte API docs">
|
||||
<meta name="twitter:description" content="Cybernetically enhanced web apps">
|
||||
<meta name="Description" content="Cybernetically enhanced web apps">
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="visually-hidden">API Docs</h1>
|
||||
<Docs {sections}/>
|
||||
@ -1,20 +0,0 @@
|
||||
import { get_example } from './_examples.js';
|
||||
|
||||
const cache = new Map();
|
||||
|
||||
export function get({ params }) {
|
||||
const { slug } = params;
|
||||
|
||||
let example = cache.get(slug);
|
||||
|
||||
if (!example || process.env.NODE_ENV !== 'production') {
|
||||
example = get_example(slug);
|
||||
if (example) cache.set(slug, example);
|
||||
}
|
||||
|
||||
if (example) {
|
||||
return {
|
||||
body: example
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
<script>
|
||||
export let sections = [];
|
||||
export let active_section = null;
|
||||
export let isLoading = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.examples-toc {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--second);
|
||||
background-color: var(--second);
|
||||
color: white;
|
||||
padding: 3rem 3rem 0 3rem;
|
||||
}
|
||||
|
||||
.examples-toc li {
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 4.8rem 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
padding: 0 0 0.8rem 0;
|
||||
font: 400 var(--h6) var(--font);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.2rem 3rem;
|
||||
margin: 0 -3rem;
|
||||
}
|
||||
|
||||
div.active {
|
||||
background: rgba(0, 0, 0, 0.15) calc(100% - 3rem) 47% no-repeat
|
||||
url(/icons/arrow-right.svg);
|
||||
background-size: 1em 1em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.active.loading {
|
||||
background: rgba(0, 0, 0, 0.1) calc(100% - 3rem) 47% no-repeat
|
||||
url(/icons/loading.svg);
|
||||
background-size: 1em 1em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
color: var(--sidebar-text);
|
||||
border-bottom: none;
|
||||
font-size: 1.6rem;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.repl-link {
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin-right: 2.5rem;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
background-color: white;
|
||||
object-fit: contain;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.13);
|
||||
margin: 0.2em 0.5em 0.2em 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul class="examples-toc">
|
||||
{#each sections as section}
|
||||
<li>
|
||||
<span class="section-title">{section.title}</span>
|
||||
|
||||
{#each section.examples as example}
|
||||
<div
|
||||
class="row"
|
||||
class:active={example.slug === active_section}
|
||||
class:loading={isLoading}>
|
||||
<a
|
||||
href="examples#{example.slug}"
|
||||
class="row"
|
||||
class:active={example.slug === active_section}
|
||||
class:loading={isLoading}>
|
||||
<img
|
||||
class="thumbnail"
|
||||
alt="{example.title} thumbnail"
|
||||
src="examples/thumbnails/{example.slug}.jpg" />
|
||||
|
||||
<span>{example.title}</span>
|
||||
</a>
|
||||
{#if example.slug === active_section}
|
||||
<a href="repl/{example.slug}" class="repl-link">REPL</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@ -1,50 +0,0 @@
|
||||
import fs from 'fs';
|
||||
|
||||
let lookup;
|
||||
const titles = new Map();
|
||||
|
||||
export function get_examples() {
|
||||
lookup = new Map();
|
||||
|
||||
return fs.readdirSync(`content/examples`).map(group_dir => {
|
||||
const metadata = JSON.parse(fs.readFileSync(`content/examples/${group_dir}/meta.json`, 'utf-8'));
|
||||
|
||||
return {
|
||||
title: metadata.title,
|
||||
examples: fs.readdirSync(`content/examples/${group_dir}`).filter(file => file !== 'meta.json').map(example_dir => {
|
||||
const slug = example_dir.replace(/^\d+-/, '');
|
||||
|
||||
if (lookup.has(slug)) throw new Error(`Duplicate example slug "${slug}"`);
|
||||
lookup.set(slug, `${group_dir}/${example_dir}`);
|
||||
|
||||
const metadata = JSON.parse(fs.readFileSync(`content/examples/${group_dir}/${example_dir}/meta.json`, 'utf-8'));
|
||||
titles.set(slug, metadata.title);
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: metadata.title
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function get_example(slug) {
|
||||
if (!lookup || !lookup.has(slug)) get_examples();
|
||||
|
||||
const dir = lookup.get(slug);
|
||||
const title = titles.get(slug);
|
||||
|
||||
if (!dir || !title) return null;
|
||||
|
||||
const files = fs.readdirSync(`content/examples/${dir}`)
|
||||
.filter(name => name[0] !== '.' && name !== 'meta.json')
|
||||
.map(name => {
|
||||
return {
|
||||
name,
|
||||
source: fs.readFileSync(`content/examples/${dir}/${name}`, 'utf-8')
|
||||
};
|
||||
});
|
||||
|
||||
return { title, files };
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import { get_examples } from './_examples.js';
|
||||
|
||||
let cached;
|
||||
|
||||
export function get() {
|
||||
if (!cached || process.env.NODE_ENV !== 'production') {
|
||||
cached = get_examples().filter(section => section.title);
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
body: cached
|
||||
};
|
||||
} catch(err) {
|
||||
return {
|
||||
status: e.status || 500,
|
||||
body: {
|
||||
message: e.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,183 +0,0 @@
|
||||
<!-- FIXME sometimes it adds a trailing slash when landing -->
|
||||
<script context="module">
|
||||
export async function load({ fetch }) {
|
||||
const sections = await fetch(`examples.json`).then(r => r.json());
|
||||
const title_by_slug = sections.reduce((acc, {examples}) => {
|
||||
examples.forEach(({slug, title}) => {
|
||||
acc[slug] = title;
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
props: {
|
||||
sections,
|
||||
title_by_slug
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Repl from '@sveltejs/svelte-repl';
|
||||
|
||||
import ScreenToggle from '../../components/ScreenToggle.svelte';
|
||||
import {
|
||||
mapbox_setup, // see site/content/examples/15-context/00-context-api
|
||||
rollupUrl,
|
||||
svelteUrl
|
||||
} from '../../config';
|
||||
import { process_example } from '../../utils/examples';
|
||||
import { getFragment } from '@sveltejs/site-kit/utils/navigation';
|
||||
import TableOfContents from './_TableOfContents.svelte';
|
||||
|
||||
export let sections;
|
||||
export let title_by_slug;
|
||||
|
||||
let active_slug;
|
||||
let width;
|
||||
let offset = 1;
|
||||
let repl;
|
||||
let isLoading = false;
|
||||
const cache = {};
|
||||
|
||||
function showExampleCodeOnChange() {
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
$: title = title_by_slug[active_slug] || '';
|
||||
$: first_slug = sections[0].examples[0].slug;
|
||||
$: mobile = width < 768; // note: same as per media query below
|
||||
$: replOrientation = (mobile || width > 1080) ? 'columns' : 'rows';
|
||||
$: if (repl && active_slug) {
|
||||
if (active_slug in cache) {
|
||||
repl.set({ components: cache[active_slug] });
|
||||
showExampleCodeOnChange();
|
||||
} else {
|
||||
isLoading = true;
|
||||
fetch(`examples/${active_slug}.json`)
|
||||
.then(async response => {
|
||||
if (response.ok) {
|
||||
const {files} = await response.json();
|
||||
return process_example(files);
|
||||
}
|
||||
})
|
||||
.then(components => {
|
||||
cache[active_slug] = components;
|
||||
repl.set({components});
|
||||
showExampleCodeOnChange();
|
||||
isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const onhashchange = () => {
|
||||
active_slug = getFragment();
|
||||
};
|
||||
window.addEventListener('hashchange', onhashchange, false);
|
||||
|
||||
const fragment = getFragment();
|
||||
if (fragment) {
|
||||
active_slug = fragment;
|
||||
} else {
|
||||
active_slug = first_slug;
|
||||
goto(`examples#${active_slug}`);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', onhashchange, false);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{title} {title ? '•' : ''} Svelte Examples</title>
|
||||
|
||||
<meta name="twitter:title" content="Svelte examples">
|
||||
<meta name="twitter:description" content="Cybernetically enhanced web apps">
|
||||
<meta name="Description" content="Interactive example Svelte apps">
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="visually-hidden">Examples</h1>
|
||||
<div class='examples-container' bind:clientWidth={width}>
|
||||
<div class="viewport offset-{offset}">
|
||||
<TableOfContents {sections} active_section={active_slug} {isLoading} />
|
||||
<div class="repl-container" class:loading={isLoading}>
|
||||
<Repl
|
||||
bind:this={repl}
|
||||
workersUrl="workers"
|
||||
{svelteUrl}
|
||||
{rollupUrl}
|
||||
orientation={replOrientation}
|
||||
fixed={mobile}
|
||||
relaxed
|
||||
injectedJS={mapbox_setup}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if mobile}
|
||||
<ScreenToggle bind:offset labels={['index', 'input', 'output']}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.examples-container {
|
||||
position: relative;
|
||||
height: calc(100vh - var(--nav-h));
|
||||
overflow: hidden;
|
||||
padding: 0 0 42px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
display: grid;
|
||||
width: 300%;
|
||||
height: 100%;
|
||||
grid-template-columns: 33.333% 66.666%;
|
||||
transition: transform .3s;
|
||||
grid-auto-rows: 100%;
|
||||
}
|
||||
|
||||
.repl-container.loading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* temp fix for #2499 and #2550 while waiting for a fix for https://github.com/sveltejs/svelte-repl/issues/8 */
|
||||
|
||||
.repl-container :global(.tab-content),
|
||||
.repl-container :global(.tab-content.visible) {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
.repl-container :global(.tab-content) {
|
||||
visibility: hidden;
|
||||
}
|
||||
.repl-container :global(.tab-content.visible) {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.offset-1 { transform: translate(-33.333%, 0); }
|
||||
.offset-2 { transform: translate(-66.666%, 0); }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.examples-container { padding: 0 }
|
||||
|
||||
.viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-mid-w) auto;
|
||||
grid-auto-rows: 100%;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.offset-1, .offset-2 { transform: none; }
|
||||
}
|
||||
</style>
|
||||
@ -1,58 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
|
||||
import marked from 'marked';
|
||||
import { makeSlugProcessor } from '../../utils/slug';
|
||||
import { highlight } from '../../utils/highlight';
|
||||
import { SLUG_PRESERVE_UNICODE } from '../../../config';
|
||||
|
||||
const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE);
|
||||
|
||||
export default function get_faqs() {
|
||||
return fs
|
||||
.readdirSync('content/faq')
|
||||
.map(file => {
|
||||
if (path.extname(file) !== '.md') return;
|
||||
|
||||
const match = /^([0-9]+)-(.+)\.md$/.exec(file);
|
||||
if (!match) throw new Error(`Invalid filename '${file}'`);
|
||||
|
||||
const [, order, slug] = match;
|
||||
|
||||
const markdown = fs.readFileSync(`content/faq/${file}`, 'utf-8');
|
||||
|
||||
const { content, metadata } = extract_frontmatter(markdown);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.link = link_renderer;
|
||||
|
||||
renderer.code = highlight;
|
||||
|
||||
renderer.heading = (text, level, rawtext) => {
|
||||
const fragment = makeSlug(rawtext);
|
||||
|
||||
return `
|
||||
<h${level}>
|
||||
<span id="${fragment}" class="offset-anchor"></span>
|
||||
<a href="faq#${fragment}" class="anchor" aria-hidden="true"></a>
|
||||
${text}
|
||||
</h${level}>`;
|
||||
};
|
||||
|
||||
const answer = marked(
|
||||
content.replace(/^\t+/gm, match => match.split('\t').join(' ')),
|
||||
{ renderer }
|
||||
);
|
||||
|
||||
const fragment = makeSlug(slug);
|
||||
|
||||
return {
|
||||
fragment,
|
||||
order,
|
||||
answer,
|
||||
metadata
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import get_faqs from './_faqs.js';
|
||||
|
||||
let json;
|
||||
|
||||
export function get() {
|
||||
if (!json || process.env.NODE_ENV !== 'production') {
|
||||
const faqs = get_faqs()
|
||||
.map(faq => {
|
||||
return {
|
||||
fragment: faq.fragment,
|
||||
answer: faq.answer,
|
||||
metadata: faq.metadata
|
||||
};
|
||||
});
|
||||
|
||||
json = JSON.stringify(faqs);
|
||||
}
|
||||
|
||||
return {
|
||||
body: json,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
<script>
|
||||
import { Blurb, Hero, Section } from '@sveltejs/site-kit';
|
||||
import Contributors from './_components/Contributors.svelte';
|
||||
import Donors from './_components/Donors.svelte';
|
||||
import Example from './_components/Example.svelte';
|
||||
import WhosUsingSvelte from './_components/WhosUsingSvelte.svelte';
|
||||
// import Lazy from '../components/Lazy.svelte';
|
||||
|
||||
// TODO this causes a Sapper CSS bug...
|
||||
// function loadReplWidget() {
|
||||
// console.log('lazy loading');
|
||||
// return import('../components/Repl/ReplWidget.svelte').then(mod => mod.default);
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* darken text for accessibility */
|
||||
/* TODO does this belong elsewhere? */
|
||||
:global(.back-light) {
|
||||
--text: hsl(36, 3%, 44%);
|
||||
}
|
||||
|
||||
.examples {
|
||||
background: var(--second);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>Svelte • Cybernetically enhanced web apps</title>
|
||||
|
||||
<meta name="twitter:title" content="Svelte">
|
||||
<meta name="twitter:description" content="Cybernetically enhanced web apps">
|
||||
<meta name="Description" content="Cybernetically enhanced web apps">
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="visually-hidden">Svelte</h1>
|
||||
<Hero
|
||||
title="Svelte"
|
||||
tagline="Cybernetically enhanced web apps"
|
||||
outline="svelte-logo-outline.svg"
|
||||
logotype="svelte-logotype.svg"
|
||||
/>
|
||||
|
||||
<Blurb>
|
||||
<a href="blog/write-less-code" slot="one">
|
||||
<h2>Write less code</h2>
|
||||
<p>Build boilerplate-free components using languages you already know — HTML, CSS and JavaScript</p>
|
||||
|
||||
<span class="learn-more">learn more</span>
|
||||
</a>
|
||||
|
||||
<a href="blog/virtual-dom-is-pure-overhead" slot="two">
|
||||
<h2>No virtual DOM</h2>
|
||||
<p>Svelte compiles your code to tiny, framework-less vanilla JS — your app starts fast and stays fast</p>
|
||||
|
||||
<span class="learn-more">learn more</span>
|
||||
</a>
|
||||
|
||||
<a href="blog/svelte-3-rethinking-reactivity" slot="three">
|
||||
<h2>Truly reactive</h2>
|
||||
<p>No more complex state management libraries — Svelte brings reactivity to JavaScript itself</p>
|
||||
|
||||
<span class="learn-more">learn more</span>
|
||||
</a>
|
||||
|
||||
<div class="description" slot="what">
|
||||
<p>Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the <em>browser</em>, Svelte shifts that work into a <em>compile step</em> that happens when you build your app.</p>
|
||||
|
||||
<p>Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.</p>
|
||||
|
||||
<p>We're proud that Svelte was recently voted the <a href="https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-web-frameworks">most loved web framework</a> with the <a href="https://2020.stateofjs.com/en-US/technologies/front-end-frameworks/">most satisfied developers</a> in a pair of industry surveys. We think you'll love it too. <a href="blog/svelte-3-rethinking-reactivity">Read the introductory blog post</a> to learn more.</p>
|
||||
</div>
|
||||
|
||||
<div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how">
|
||||
<pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0">
|
||||
npx degit <a href="https://github.com/sveltejs/template" style="user-select: initial;">sveltejs/template</a> my-svelte-project
|
||||
<span class="token comment"># or download and extract <a href="https://github.com/sveltejs/template/archive/master.zip">this .zip file</a></span>
|
||||
cd my-svelte-project
|
||||
<span class="token comment"># to use <a href="blog/svelte-and-typescript">TypeScript</a> run:</span>
|
||||
<span class="token comment"># node scripts/setupTypeScript.js</span>
|
||||
|
||||
npm install
|
||||
npm run dev
|
||||
</pre>
|
||||
|
||||
<p style="flex: 1">See the <a href="blog/the-easiest-way-to-get-started">quickstart guide</a> for more information.</p>
|
||||
|
||||
<p class="cta"><a sveltekit:prefetch href="tutorial">Learn Svelte</a></p>
|
||||
</div>
|
||||
</Blurb>
|
||||
|
||||
<div class="examples">
|
||||
<Example id="hello-world">
|
||||
<p>Svelte components are built on top of HTML. Just add data.</p>
|
||||
</Example>
|
||||
|
||||
<Example id="nested-components">
|
||||
<p>CSS is component-scoped by default — no more style collisions or specificity wars. Or you can <a href="/blog/svelte-css-in-js">use your favourite CSS-in-JS library</a>.</p>
|
||||
</Example>
|
||||
|
||||
<Example id="reactive-assignments">
|
||||
<p>Trigger efficient, granular updates by assigning to local variables. The compiler does the rest.</p>
|
||||
</Example>
|
||||
|
||||
<Example id="svg-transitions">
|
||||
<p>Build beautiful UIs with a powerful, performant transition engine built right into the framework.</p>
|
||||
</Example>
|
||||
</div>
|
||||
|
||||
<Section>
|
||||
<h3>Who's using Svelte?</h3>
|
||||
|
||||
<WhosUsingSvelte/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<h3>Supporters</h3>
|
||||
|
||||
<p>Svelte is free and open source software, made possible by the work of hundreds of volunteers and donors. <a href="https://github.com/sveltejs/svelte">Join us</a> or <a href="https://opencollective.com/svelte">give</a>!</p>
|
||||
|
||||
<h3>Contributors</h3>
|
||||
|
||||
<Contributors/>
|
||||
|
||||
<p></p>
|
||||
|
||||
<h3>Donors</h3>
|
||||
|
||||
<Donors/>
|
||||
</Section>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue