Compare commits
No commits in common. "f3a8fa17e6742221f1c47c1dfe8d4473410b5efe" and "2a6523b9f132c51ce94bef9a90f58a1c2047dbbc" have entirely different histories.
f3a8fa17e6
...
2a6523b9f1
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.svelte-kit/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
test-results/
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
# tests
|
||||||
|
test-results
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Package Managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"useTabs": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
29
.vscode/settings.json
vendored
Normal file
29
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"devcontainers",
|
||||||
|
"doctypes",
|
||||||
|
"esbenp",
|
||||||
|
"iconify",
|
||||||
|
"pandoc",
|
||||||
|
"pdoc",
|
||||||
|
"rehype",
|
||||||
|
"resumarkdown",
|
||||||
|
"résumé",
|
||||||
|
"spacebar",
|
||||||
|
"tablist",
|
||||||
|
"testid",
|
||||||
|
"texlive",
|
||||||
|
"textbox"
|
||||||
|
],
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"svelte.enable-ts-plugin": true,
|
||||||
|
"[javascript][json][jsonc][typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[svelte]": {
|
||||||
|
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||||
|
}
|
||||||
|
}
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
FROM node:22.11.0-bookworm-slim AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
FROM node:22.11.0-bookworm-slim AS runner
|
||||||
|
LABEL maintainer="Nicola Clark <nicola@slottedspoon.dev>"
|
||||||
|
EXPOSE 3000
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
RUN npm ci --omit dev
|
||||||
|
CMD ["node", "build"]
|
2
LICENSE
2
LICENSE
@ -35,7 +35,7 @@ Mozilla Public License Version 2.0
|
|||||||
means any form of the work other than Source Code Form.
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
1.7. "Larger Work"
|
1.7. "Larger Work"
|
||||||
means a work that combines Covered Software with other material, in
|
means a work that combines Covered Software with other material, in
|
||||||
a separate file or files, that is not Covered Software.
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
1.8. "License"
|
1.8. "License"
|
||||||
|
21
LICENSE.up-to-ea30f9
Normal file
21
LICENSE.up-to-ea30f9
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Nicola Clark
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
22
README.md
22
README.md
@ -1,3 +1,23 @@
|
|||||||
# resumarkdown
|
# resumarkdown
|
||||||
|
|
||||||
Create a résumé with Markdown and CSS
|
Create a resume with Markdown and CSS
|
||||||
|
|
||||||
|
## Previous location
|
||||||
|
|
||||||
|
This repository was previously hosted on
|
||||||
|
[GitHub](https://github.com/tweakdeveloper/resumarkdown). Due to GitHub's
|
||||||
|
expansion of Copilot (see [the License section](#license) for further details),
|
||||||
|
it has been moved to this self-hosted repository.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This repository's code was licensed under the terms of
|
||||||
|
[the MIT license](LICENSE.up-to-ea30f9) up to and including commit ea30f9. You
|
||||||
|
may apply this license's terms to all work that appears in the repository prior
|
||||||
|
to this commit ("this commit" referring to the commit immediately following
|
||||||
|
ea30f9).
|
||||||
|
|
||||||
|
From this commit forward, the code in this repo is licensed under the terms of
|
||||||
|
the [Mozilla Public License 2.0](https://mozilla.org/MPL/2.0/). This change was
|
||||||
|
made due to GitHub's lack of transparency vis-à-vis Copilot's use of public
|
||||||
|
repositories for the purposes of training their LLM.
|
||||||
|
3891
package-lock.json
generated
Normal file
3891
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
Normal file
51
package.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "resumarkdown",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev --host",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"test:integration": "playwright test",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"lint": "prettier --check .",
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hast": "^0.0.2",
|
||||||
|
"hastscript": "^9.0.0",
|
||||||
|
"iconify-icon": "^2.1.0",
|
||||||
|
"rehype-document": "^7.0.3",
|
||||||
|
"rehype-sanitize": "^6.0.0",
|
||||||
|
"rehype-stringify": "^10.0.1",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.1",
|
||||||
|
"ua-parser-js": "^1.0.39",
|
||||||
|
"unified": "^11.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.28.1",
|
||||||
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.2.9",
|
||||||
|
"@sveltejs/kit": "^2.5.27",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.0",
|
||||||
|
"@types/node": "^22.9.0",
|
||||||
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
|
"@types/user-agents": "^1.0.4",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
|
"svelte": "^5.0.0-next.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-preprocess": "^6.0.3",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"user-agents": "^1.1.325",
|
||||||
|
"vite": "^5.4.4",
|
||||||
|
"vitest": "^2.0.0"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
28
playwright.config.ts
Normal file
28
playwright.config.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run preview',
|
||||||
|
port: 4173,
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
timeout: 2.5 * 1000,
|
||||||
|
},
|
||||||
|
testDir: 'tests',
|
||||||
|
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
|
||||||
|
timeout: 5 * 1000,
|
||||||
|
use: {
|
||||||
|
viewport: {
|
||||||
|
height: 900,
|
||||||
|
width: 1600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
19
src/app.d.ts
vendored
Normal file
19
src/app.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
17
src/app.html
Normal file
17
src/app.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
src/app.less
Normal file
25
src/app.less
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import 'styles.less';
|
||||||
|
|
||||||
|
@import (css) url('https://fonts.googleapis.com/css2?family=Rubik:wght@400..700&display=swap');
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: @fonts-sans;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: @font-w-bold;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
13
src/index.test.ts
Normal file
13
src/index.test.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
23
src/lib/components/code-input.svelte
Normal file
23
src/lib/components/code-input.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { code = $bindable() }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<textarea bind:value={code}></textarea>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
textarea {
|
||||||
|
.container-content();
|
||||||
|
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
</style>
|
42
src/lib/components/editor.svelte
Normal file
42
src/lib/components/editor.svelte
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
import { pane as navStore, type Pane } from '$lib/stores/nav.js';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pane: Pane;
|
||||||
|
testid?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { pane, testid, children }: Props = $props();
|
||||||
|
|
||||||
|
// don't show pane if we're not the current pane
|
||||||
|
let hidden: boolean = $derived(pane !== $navStore);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id={`pane-${pane}`} data-testid={testid} role="tabpanel" class:hidden>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
div {
|
||||||
|
.container();
|
||||||
|
|
||||||
|
grid-area: editor;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
border-right: 2px solid @color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
33
src/lib/components/headline.svelte
Normal file
33
src/lib/components/headline.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>{@render children()}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
h1 {
|
||||||
|
background-color: @color-dark;
|
||||||
|
font-size: @font-s-lg;
|
||||||
|
color: @color-light;
|
||||||
|
padding: @padding-md;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
grid-area: headline;
|
||||||
|
}
|
||||||
|
</style>
|
72
src/lib/components/nav-item.svelte
Normal file
72
src/lib/components/nav-item.svelte
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
import { pane, type Pane } from '$lib/stores/nav.js';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
destination: Pane;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { destination, children }: Props = $props();
|
||||||
|
|
||||||
|
let selected: boolean = $derived(destination === $pane);
|
||||||
|
|
||||||
|
function handleKey({ key }: KeyboardEvent) {
|
||||||
|
if (key === ' ' || key === 'Enter' || key === 'Spacebar') {
|
||||||
|
navigate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigate() {
|
||||||
|
pane.set(destination);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
aria-controls={`pane-${destination}`}
|
||||||
|
aria-selected={selected}
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
class:selected
|
||||||
|
onclick={navigate}
|
||||||
|
onkeyup={handleKey}
|
||||||
|
>
|
||||||
|
{@render children()}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
li {
|
||||||
|
.selectable();
|
||||||
|
|
||||||
|
@separator: 1px solid @color-dark;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: darken(@color-light, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background-color: darken(@color-light, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-bottom: @separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: @separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
34
src/lib/components/nav-toggle.svelte
Normal file
34
src/lib/components/nav-toggle.svelte
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
navOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { navOpen = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
// if nav is open, we should "hide" it on press. if not, we should "show" it.
|
||||||
|
let action: string = $derived(navOpen ? 'hide' : 'show');
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
navOpen = !navOpen;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={toggle}>{action} navigation</button>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
button {
|
||||||
|
background-color: lighten(@color-dark, 10%);
|
||||||
|
border: none;
|
||||||
|
color: @color-light;
|
||||||
|
font-size: @font-s-md;
|
||||||
|
font-weight: @font-w-semibold;
|
||||||
|
padding: @padding-sm;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
56
src/lib/components/nav-tree.svelte
Normal file
56
src/lib/components/nav-tree.svelte
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
|
import NavToggle from './nav-toggle.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mobile?: boolean;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { mobile = true, children }: Props = $props();
|
||||||
|
|
||||||
|
let open: boolean = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
{#if mobile}
|
||||||
|
<NavToggle bind:navOpen={open} />
|
||||||
|
{/if}
|
||||||
|
{#if open || !mobile}
|
||||||
|
<ul role="tablist" transition:slide={{ duration: 300 }}>
|
||||||
|
{@render children()}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
nav {
|
||||||
|
grid-area: navtree;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
@border: 2px solid @color-dark;
|
||||||
|
|
||||||
|
border-bottom: @border;
|
||||||
|
box-sizing: border-box;
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
align-items: stretch;
|
||||||
|
border-right: @border;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
102
src/lib/components/preview.svelte
Normal file
102
src/lib/components/preview.svelte
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import 'iconify-icon';
|
||||||
|
|
||||||
|
import { pane } from '$lib/stores/nav.js';
|
||||||
|
import renderPreview from '$lib/render-preview';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mobile?: boolean;
|
||||||
|
markdown: string;
|
||||||
|
stylesheet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { mobile = true, markdown, stylesheet }: Props = $props();
|
||||||
|
|
||||||
|
let hidden: boolean = $derived(mobile ? $pane !== 'preview' : true);
|
||||||
|
|
||||||
|
let output: Promise<string> = $derived(renderPreview(markdown, stylesheet));
|
||||||
|
|
||||||
|
let previewFrame = $state<HTMLIFrameElement | undefined>();
|
||||||
|
|
||||||
|
async function performRender(event: MouseEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
previewFrame?.contentWindow?.postMessage('print');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class:hidden class:mobile data-testid="preview-pane">
|
||||||
|
{#await output}
|
||||||
|
<p>processing...</p>
|
||||||
|
{:then result}
|
||||||
|
<iframe title="résumé preview" srcdoc={result} bind:this={previewFrame}></iframe>
|
||||||
|
<button type="submit" onclick={performRender}>
|
||||||
|
<span>download</span>
|
||||||
|
<iconify-icon icon="ion:download-outline" height="1.25em"></iconify-icon>
|
||||||
|
</button>
|
||||||
|
{:catch err}
|
||||||
|
<p style:color="red">error: {err}!</p>
|
||||||
|
{/await}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
main {
|
||||||
|
.container();
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: @padding-lg-y;
|
||||||
|
grid-area: preview;
|
||||||
|
|
||||||
|
button {
|
||||||
|
.selectable();
|
||||||
|
|
||||||
|
background-color: lighten(@color-dark, 10%);
|
||||||
|
border: 1px solid @color-dark;
|
||||||
|
border-radius: @border-r-xl;
|
||||||
|
color: @color-light;
|
||||||
|
font-size: @font-s-sm;
|
||||||
|
font-weight: @font-w-semibold;
|
||||||
|
padding: @padding-md;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: lighten(@color-dark, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(@color-dark, 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > iconify-icon,
|
||||||
|
& > span {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
iconify-icon {
|
||||||
|
height: 1.25em;
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
.container-content();
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
grid-area: editor;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
gap: @padding-xl-y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
79
src/lib/render-preview.ts
Normal file
79
src/lib/render-preview.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type Element as HastElement, type Root as HastRoot } from 'hast';
|
||||||
|
import { h } from 'hastscript';
|
||||||
|
import rehypeDocument from 'rehype-document';
|
||||||
|
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
||||||
|
import rehypeStringify from 'rehype-stringify';
|
||||||
|
import remarkParse from 'remark-parse';
|
||||||
|
import remarkRehype from 'remark-rehype';
|
||||||
|
import { unified, type Plugin } from 'unified';
|
||||||
|
|
||||||
|
const printScript = `
|
||||||
|
window.addEventListener('message', (evt) => {
|
||||||
|
if (evt.data === 'print') {
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const removePrintMarginsStyle = `
|
||||||
|
@page {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const injectPrintScript: Plugin<[], HastRoot> = function () {
|
||||||
|
return (tree) => {
|
||||||
|
const printScriptNode = h('script', printScript);
|
||||||
|
const htmlNode = tree.children.find(
|
||||||
|
(node) => node.type === 'element' && node.tagName === 'html',
|
||||||
|
) as HastElement;
|
||||||
|
const bodyNode = htmlNode.children.find(
|
||||||
|
(node) => node.type === 'element' && node.tagName === 'body',
|
||||||
|
) as HastElement;
|
||||||
|
bodyNode.children.push(printScriptNode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const injectRemovePrintMarginsStyle: Plugin<[], HastRoot> = function () {
|
||||||
|
return (tree) => {
|
||||||
|
const removePrintMarginsStyleNode = h('style', removePrintMarginsStyle);
|
||||||
|
const htmlNode = tree.children.find(
|
||||||
|
(node) => node.type === 'element' && node.tagName === 'html',
|
||||||
|
) as HastElement;
|
||||||
|
const headNode = htmlNode.children.find(
|
||||||
|
(node) => node.type === 'element' && node.tagName === 'head',
|
||||||
|
) as HastElement;
|
||||||
|
headNode.children.push(removePrintMarginsStyleNode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPreview = async (markdown: string, stylesheet: string): Promise<string> => {
|
||||||
|
let allowedTags: string[] = ['body', 'head', 'html', 'style'];
|
||||||
|
if (defaultSchema.tagNames) {
|
||||||
|
allowedTags = [...defaultSchema.tagNames, ...allowedTags];
|
||||||
|
}
|
||||||
|
return String(
|
||||||
|
await unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkRehype)
|
||||||
|
.use(rehypeDocument, { style: stylesheet })
|
||||||
|
.use(rehypeSanitize, {
|
||||||
|
...defaultSchema,
|
||||||
|
allowDoctypes: true,
|
||||||
|
tagNames: allowedTags,
|
||||||
|
})
|
||||||
|
.use(injectRemovePrintMarginsStyle)
|
||||||
|
.use(injectPrintScript)
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.process(markdown),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default renderPreview;
|
11
src/lib/stores/nav.ts
Normal file
11
src/lib/stores/nav.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export type Pane = 'content' | 'style' | 'preview';
|
||||||
|
|
||||||
|
export const pane = writable<Pane>('content');
|
20
src/routes/+layout.server.ts
Normal file
20
src/routes/+layout.server.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import { UAParser } from 'ua-parser-js';
|
||||||
|
|
||||||
|
export async function load({ request }: RequestEvent) {
|
||||||
|
const ua = request.headers.get('user-agent');
|
||||||
|
|
||||||
|
if (!ua) {
|
||||||
|
return { mobile: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { device } = UAParser(ua);
|
||||||
|
return { mobile: device.type === 'mobile' };
|
||||||
|
}
|
19
src/routes/+layout.svelte
Normal file
19
src/routes/+layout.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
import '../app.less';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children()}
|
143
src/routes/+page.svelte
Normal file
143
src/routes/+page.svelte
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
import { pane } from '$lib/stores/nav.js';
|
||||||
|
|
||||||
|
import CodeInput from '$lib/components/code-input.svelte';
|
||||||
|
import Editor from '$lib/components/editor.svelte';
|
||||||
|
import Headline from '$lib/components/headline.svelte';
|
||||||
|
import NavItem from '$lib/components/nav-item.svelte';
|
||||||
|
import NavTree from '$lib/components/nav-tree.svelte';
|
||||||
|
import Preview from '$lib/components/preview.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: PageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: Props = $props();
|
||||||
|
|
||||||
|
let mobile: boolean = $state(data.mobile);
|
||||||
|
|
||||||
|
function checkIsDesktop() {
|
||||||
|
// TODO: figure out how to remove hard-coded value here
|
||||||
|
mobile = !window.matchMedia('screen and (min-width: 1280px)').matches;
|
||||||
|
|
||||||
|
if (!mobile && get(pane) === 'preview') {
|
||||||
|
pane.set('content');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
checkIsDesktop();
|
||||||
|
|
||||||
|
window.addEventListener('resize', checkIsDesktop);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', checkIsDesktop);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let markdown: string = $state('');
|
||||||
|
let stylesheet: string = $state('');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>resumarkdown</title>
|
||||||
|
</svelte:head>
|
||||||
|
<div>
|
||||||
|
<Headline>resumarkdown</Headline>
|
||||||
|
<NavTree {mobile}>
|
||||||
|
<NavItem destination="content">content</NavItem>
|
||||||
|
<NavItem destination="style">style</NavItem>
|
||||||
|
{#if mobile}
|
||||||
|
<NavItem destination="preview">preview</NavItem>
|
||||||
|
{/if}
|
||||||
|
</NavTree>
|
||||||
|
<Editor pane="content" testid="content-pane">
|
||||||
|
<CodeInput bind:code={markdown} />
|
||||||
|
</Editor>
|
||||||
|
<Editor pane="style" testid="style-pane">
|
||||||
|
<CodeInput bind:code={stylesheet} />
|
||||||
|
</Editor>
|
||||||
|
<Preview {mobile} {markdown} {stylesheet} />
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
made with ❤ by
|
||||||
|
<a href="https://github.com/tweakdeveloper" title="my github profile">@tweakdeveloper</a>
|
||||||
|
</p>
|
||||||
|
<a href="/attributions">attributions</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
div {
|
||||||
|
align-content: start;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
'headline'
|
||||||
|
'navtree'
|
||||||
|
'editor'
|
||||||
|
'footer';
|
||||||
|
grid-template-rows: min-content min-content 1fr;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-areas:
|
||||||
|
'headline headline'
|
||||||
|
'navtree preview'
|
||||||
|
'editor preview'
|
||||||
|
'footer footer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
align-items: center;
|
||||||
|
background-color: @color-dark;
|
||||||
|
border-top: 2px solid @color-dark;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: @color-light;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: @padding-md-y;
|
||||||
|
grid-area: footer;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: @padding-lg;
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
gap: @padding-sm-y;
|
||||||
|
padding: @padding-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: @color-light;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: darken(@color-light, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken(@color-light, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: darken(@color-light, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
6
src/routes/attributions/+page.svelte
Normal file
6
src/routes/attributions/+page.svelte
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
78
src/styles.less
Normal file
78
src/styles.less
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// colors
|
||||||
|
@color-accent-dark: #a8c3b5;
|
||||||
|
@color-accent-light: #558b6e;
|
||||||
|
@color-danger: #d64933;
|
||||||
|
@color-dark: #2e282a;
|
||||||
|
@color-light: #fbfbfb;
|
||||||
|
|
||||||
|
// fonts
|
||||||
|
@fonts-sans: Rubik, Arial, sans-serif;
|
||||||
|
@font-s-lg: 2rem;
|
||||||
|
@font-s-md: 1.25rem;
|
||||||
|
@font-s-sm: 1rem;
|
||||||
|
@font-w-bold: 700;
|
||||||
|
@font-w-semibold: 600;
|
||||||
|
|
||||||
|
// layout
|
||||||
|
@border-r-xl: 0.75em;
|
||||||
|
@padding-md: @padding-md-y @padding-md-x;
|
||||||
|
@padding-md-x: 1em;
|
||||||
|
@padding-md-y: 0.5em;
|
||||||
|
@padding-lg: @padding-lg-y @padding-lg-x;
|
||||||
|
@padding-lg-x: 1.5em;
|
||||||
|
@padding-lg-y: 1em;
|
||||||
|
@padding-sm: @padding-sm-y @padding-sm-x;
|
||||||
|
@padding-sm-x: 0.875em;
|
||||||
|
@padding-sm-y: 0.375em;
|
||||||
|
@padding-xl: @padding-xl-y @padding-xl-x;
|
||||||
|
@padding-xl-x: 2em;
|
||||||
|
@padding-xl-y: 1.5em;
|
||||||
|
|
||||||
|
// sizes
|
||||||
|
@sizes: {
|
||||||
|
lg: 1280px;
|
||||||
|
};
|
||||||
|
|
||||||
|
// utils
|
||||||
|
.full-without-padding(@padding) {
|
||||||
|
@result: calc(100% - @padding * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared sizing mixins
|
||||||
|
.container() {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: @padding-lg;
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
padding: @padding-xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-content() {
|
||||||
|
border: 1px solid @color-dark;
|
||||||
|
border-radius: @border-r-xl;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: @font-s-md;
|
||||||
|
height: 100%;
|
||||||
|
padding: @padding-lg;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media screen and (min-width: @sizes[lg]) {
|
||||||
|
font-size: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared interactivity mixins
|
||||||
|
.selectable() {
|
||||||
|
font-weight: @font-w-semibold;
|
||||||
|
margin: 0;
|
||||||
|
padding: @padding-md;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
33
svelte.config.js
Normal file
33
svelte.config.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
|
||||||
|
import { sveltePreprocess } from 'svelte-preprocess';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: sveltePreprocess({
|
||||||
|
less: {
|
||||||
|
prependData: `@import 'src/styles.less';`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
|
adapter: adapter(),
|
||||||
|
},
|
||||||
|
|
||||||
|
compilerOptions: {
|
||||||
|
runes: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
28
tests/desktop.test.ts
Normal file
28
tests/desktop.test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test('desktop page has nav tree', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('navigation')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('desktop page does not have nav toggle', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('navigation').getByRole('button')).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('desktop page has two-column layout', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByTestId('content-pane')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('preview-pane')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('desktop page has no "preview" nav item', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('tab').filter({ hasText: 'preview' })).toBeHidden();
|
||||||
|
});
|
41
tests/general.test.ts
Normal file
41
tests/general.test.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test('page has headline', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('banner')).toHaveText('resumarkdown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nav items work', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
for (const tab of await page.getByRole('tab').all()) {
|
||||||
|
await tab.click();
|
||||||
|
await expect(page.getByTestId(`${await tab.textContent()}-pane`)).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preview accepts content', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('content').click();
|
||||||
|
await page.getByRole('textbox').fill('# résumé');
|
||||||
|
|
||||||
|
const previewFrame = page.getByTitle('résumé preview').contentFrame();
|
||||||
|
await expect(previewFrame.getByRole('heading')).toHaveText('résumé');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preview accepts styles', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('content').click();
|
||||||
|
await page.getByRole('textbox').fill('# blah');
|
||||||
|
await page.getByText('style', { exact: true }).click();
|
||||||
|
await page.getByRole('textbox').fill('h1 { color: red; }');
|
||||||
|
|
||||||
|
const previewFrame = page.getByTitle('résumé preview').contentFrame();
|
||||||
|
await expect(previewFrame.getByRole('heading')).toHaveCSS('color', 'rgb(255, 0, 0)');
|
||||||
|
});
|
44
tests/mobile.test.ts
Normal file
44
tests/mobile.test.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import UserAgent from 'user-agents';
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: { height: 2556, width: 1179 },
|
||||||
|
userAgent: new UserAgent({ deviceCategory: 'mobile' }).toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mobile page has nav tree hidden by default', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('navigation').getByRole('tablist')).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mobile page has nav toggle', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('navigation').getByRole('button')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nav toggle works', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('show navigation').click();
|
||||||
|
await expect(page.getByRole('navigation').getByRole('tablist')).toBeVisible();
|
||||||
|
await page.getByText('hide navigation').click();
|
||||||
|
await expect(page.getByRole('navigation').getByRole('tablist')).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mobile page has single-column layout', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByTestId('content-pane')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('preview-pane')).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mobile page has preview nav item', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByText('show navigation').click();
|
||||||
|
await expect(page.getByRole('tab').filter({ hasText: 'preview' })).toBeVisible();
|
||||||
|
});
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
15
vite.config.ts
Normal file
15
vite.config.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||||
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||||
|
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||||
|
},
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user