Initial publish

This commit is contained in:
2025-09-04 15:49:04 +07:00
commit 1f7e1825ba
28 changed files with 4079 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+9
View File
@@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/
+15
View File
@@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+25
View File
@@ -0,0 +1,25 @@
# litewriter
A (not so) lightweight Markdown editor.
Focuses on allowing exports to various formats and theming capabilities.
Currently supports exporting to HTML, PDF, and OpenDocument Text (ODT).
Also plans to support DOCX in the future.
## Performance
HTML and PDF exporters are pretty fast. Both uses the Marked library to
convert Markdown to HTML and the PDF converter uses the browser's print
dialog to save as PDF.
However, the ODT converter requires a lot of verbose template XMLs, so it
may be slower than others, still, it won't slow the entire page down as it's
lazily loaded.
## ODT Converter
The ODT converter is based on XML content generated from LibreOffice and ONLYOFFICE.
Though, It is most compatible with LibreOffice.
There's also one known limitation, Block quotes can't be used at the moment.
+40
View File
@@ -0,0 +1,40 @@
import prettier from 'eslint-config-prettier';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default ts.config(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node }
},
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
}
);
+57
View File
@@ -0,0 +1,57 @@
{
"name": "litewriter",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0",
"@types/html2pdf.js": "^0.10.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"typescript-eslint": "^8.20.0",
"vite": "^7.0.4"
},
"pnpm": {
"onlyBuiltDependencies": [
"@tailwindcss/oxide",
"core-js",
"esbuild"
]
},
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
"dependencies": {
"@codemirror/lang-markdown": "^6.3.4",
"@codemirror/language": "^6.11.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.1",
"dompurify": "^3.2.6",
"jszip": "^3.10.1",
"marked": "^16.2.1",
"svelte-codemirror-editor": "^1.4.1",
"thememirror": "^2.0.1"
}
}
+2898
View File
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@custom-variant dark (&:where(.dark, .dark *));
body {
--theme-background: #f0f0f0;
--theme-background-darker: #e0e0e0;
--theme-foreground: #202020;
--theme-floating: #f0f0f0;
background-color: var(--theme-background);
color: var(--theme-foreground);
}
@utility bg-theme {
background-color: var(--theme-background);
}
@utility bg-theme-darker {
background-color: var(--theme-background-darker);
}
@utility bg-theme-floating {
background-color: var(--theme-floating);
}
@utility text-color-theme {
color: var(--theme-foreground);
}
.btn {
@apply px-2 py-1 cursor-pointer;
}
.btn-generic {
@apply bg-gray-400/40 hover:bg-gray-300/40 dark:hover:bg-gray-500/40 rounded transition-colors;
}
.generic-select {
@apply border py-1 px-2 rounded;
}
.printarea {
visibility: hidden;
display: none;
}
@media print {
body {
visibility: hidden;
}
.printarea {
display: block;
visibility: visible;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
+13
View File
@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+11
View File
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<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>
+27
View File
@@ -0,0 +1,27 @@
.h1 {
font-size: 2em;
}
.ͼ5 {
color: gray;
}
.h2 {
font-size: 1.5em;
}
.h3 {
font-size: 1.17em;
}
.h4 {
font-size: 1em;
}
.h5 {
font-size: 0.83em;
}
.h6 {
font-size: 0.67em;
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+82
View File
@@ -0,0 +1,82 @@
<script lang="ts">
import { download } from '$lib/download';
import DOMPurify from 'dompurify';
import { marked } from 'marked';
const { content }: { content: string } = $props();
let exportDialog: HTMLDialogElement;
let format = $state('html');
let processing = $state(false);
let printContent: string = $state('');
export function showModal() {
exportDialog.showModal();
}
async function exportHtml() {
const html = await marked.parse(content);
const blob = new Blob([html], {
type: 'text/html'
});
download(blob, 'litewriter-document.html');
}
export async function exportPdf() {
printContent = DOMPurify.sanitize(await marked.parse(content));
setTimeout(() => {
window.print();
printContent = '';
}, 1000);
}
async function exportBtn() {
switch (format) {
case 'html':
await exportHtml();
break;
case 'pdf':
await exportPdf();
break;
case 'odt':
await (await import('../opendocument')).exportOdt(content);
break;
}
exportDialog.close();
}
</script>
<div class="printarea prose">
{@html printContent}
</div>
<dialog
bind:this={exportDialog}
class="m-auto py-4 px-5 bg-theme-floating text-color-theme border border-gray-500 rounded"
>
<h1 class="text-2xl">Export</h1>
<div class="grid grid-cols-2 my-4">
<label class="flex items-center" for="formatSelect">Format</label>
<div>
<select name="formatSelect" id="formatSelect" class="generic-select" bind:value={format}>
<option value="html">HTML</option>
<option value="pdf">PDF</option>
<option value="odt">OpenDocument Text (ODT)</option>
</select>
</div>
</div>
{#if processing}
<div class="font-bold">Please wait while your document is being processed...</div>
{/if}
<div class="flex justify-end gap-2">
<button class="btn-generic btn" onclick={() => exportDialog.close()} disabled={processing}>
Cancel
</button>
<button class="btn-generic btn" onclick={exportBtn} disabled={processing}> Export </button>
</div>
</dialog>
+14
View File
@@ -0,0 +1,14 @@
<script lang="ts">
let { children, mouseOver, blurEvent, buttonClicked, name, shownMenu } = $props();
</script>
<button
class={`hover:bg-gray-500/40 btn rounded transition-colors ${shownMenu === name ? 'bg-gray-500/40' : ''}`}
aria-haspopup="menu"
onmouseover={() => mouseOver(name)}
onfocus={() => mouseOver(name)}
onblur={(e) => blurEvent(e)}
onclick={() => buttonClicked(name)}
>
{@render children()}
</button>
+10
View File
@@ -0,0 +1,10 @@
export function download(blob: Blob, fileName: string) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
}
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+124
View File
@@ -0,0 +1,124 @@
export const bulletListContentStyle = `
<text:list-style style:name="L1">
<text:list-level-style-bullet text:level="1" text:style-name="BulletSymbols" loext:num-list-format="%1%" text:bullet-char="•">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.25in" fo:margin-left="0.5in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="2" text:style-name="BulletSymbols" loext:num-list-format="%2%" text:bullet-char="◦">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.75in" fo:text-indent="-0.25in" fo:margin-left="0.75in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="3" text:style-name="BulletSymbols" loext:num-list-format="%3%" text:bullet-char="▪">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-0.25in" fo:margin-left="1in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="4" text:style-name="BulletSymbols" loext:num-list-format="%4%" text:bullet-char="•">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.25in" fo:text-indent="-0.25in" fo:margin-left="1.25in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="5" text:style-name="BulletSymbols" loext:num-list-format="%5%" text:bullet-char="◦">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5in" fo:text-indent="-0.25in" fo:margin-left="1.5in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="6" text:style-name="BulletSymbols" loext:num-list-format="%6%" text:bullet-char="▪">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.75in" fo:text-indent="-0.25in" fo:margin-left="1.75in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="7" text:style-name="BulletSymbols" loext:num-list-format="%7%" text:bullet-char="•">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2in" fo:text-indent="-0.25in" fo:margin-left="2in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="8" text:style-name="BulletSymbols" loext:num-list-format="%8%" text:bullet-char="◦">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.25in" fo:text-indent="-0.25in" fo:margin-left="2.25in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="9" text:style-name="BulletSymbols" loext:num-list-format="%9%" text:bullet-char="▪">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.5in" fo:text-indent="-0.25in" fo:margin-left="2.5in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
<text:list-level-style-bullet text:level="10" text:style-name="BulletSymbols" loext:num-list-format="%10%" text:bullet-char="•">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.75in" fo:text-indent="-0.25in" fo:margin-left="2.75in"/>
</style:list-level-properties>
<style:text-properties fo:font-family="OpenSymbol"/>
</text:list-level-style-bullet>
</text:list-style>`;
export const bulletListStyle = `
<style:style style:name="BulletSymbols" style:display-name="Bullet Symbols" style:family="text">
<style:text-properties style:font-name="OpenSymbol" fo:font-family="OpenSymbol" style:font-charset="x-symbol" style:font-name-asian="OpenSymbol" style:font-family-asian="OpenSymbol" style:font-charset-asian="x-symbol" style:font-name-complex="OpenSymbol" style:font-family-complex="OpenSymbol" style:font-charset-complex="x-symbol"/>
</style:style>`;
export const orderedListContentStyle = `
<text:list-style style:name="L2">
<text:list-level-style-number text:level="1" text:style-name="NumberingSymbols" loext:num-list-format="%1%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.25in" fo:margin-left="0.5in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="2" text:style-name="NumberingSymbols" loext:num-list-format="%2%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.75in" fo:text-indent="-0.25in" fo:margin-left="0.75in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="3" text:style-name="NumberingSymbols" loext:num-list-format="%3%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-0.25in" fo:margin-left="1in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="4" text:style-name="NumberingSymbols" loext:num-list-format="%4%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.25in" fo:text-indent="-0.25in" fo:margin-left="1.25in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="5" text:style-name="NumberingSymbols" loext:num-list-format="%5%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.5in" fo:text-indent="-0.25in" fo:margin-left="1.5in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="6" text:style-name="NumberingSymbols" loext:num-list-format="%6%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.75in" fo:text-indent="-0.25in" fo:margin-left="1.75in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="7" text:style-name="NumberingSymbols" loext:num-list-format="%7%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2in" fo:text-indent="-0.25in" fo:margin-left="2in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="8" text:style-name="NumberingSymbols" loext:num-list-format="%8%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.25in" fo:text-indent="-0.25in" fo:margin-left="2.25in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="9" text:style-name="NumberingSymbols" loext:num-list-format="%9%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.5in" fo:text-indent="-0.25in" fo:margin-left="2.5in"/>
</style:list-level-properties>
</text:list-level-style-number>
<text:list-level-style-number text:level="10" text:style-name="NumberingSymbols" loext:num-list-format="%10%." style:num-suffix="." style:num-format="1">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="2.75in" fo:text-indent="-0.25in" fo:margin-left="2.75in"/>
</style:list-level-properties>
</text:list-level-style-number>
</text:list-style>`;
export const orderedListStyle = `<style:style style:name="NumberingSymbols" style:display-name="Numbering Symbols" style:family="text"/>`;
+292
View File
@@ -0,0 +1,292 @@
import { marked, options, type Token, type Tokens, type TokensList } from "marked";
import JSZip from "jszip";
import { download } from "./download";
interface StylesRequired {
bulletList?: boolean;
orderedList?: boolean;
}
const contentTemplate = (content: string, customStyles: string) => `<?xml version="1.0" encoding="UTF-8"?>
<office:document-content
xmlns:css3t="http://www.w3.org/TR/css3-text/"
xmlns:grddl="http://www.w3.org/2003/g/data-view#"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:dom="http://www.w3.org/2001/xml-events"
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:ooo="http://openoffice.org/2004/office"
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
xmlns:ooow="http://openoffice.org/2004/writer"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:drawooo="http://openoffice.org/2010/draw"
xmlns:oooc="http://openoffice.org/2004/calc"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
xmlns:tableooo="http://openoffice.org/2009/table"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
xmlns:rpt="http://openoffice.org/2005/report"
xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
xmlns:officeooo="http://openoffice.org/2009/office"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"
office:version="1.4">
<office:scripts/>
<office:font-face-decls/>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Heading1">
<style:paragraph-properties />
<style:text-properties officeooo:rsid="0005075b" officeooo:paragraph-rsid="0005075b"/>
</style:style>
<style:style style:family="text" style:name="TextBold">
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:family="text" style:name="TextItalic">
<style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
</style:style>${customStyles}
</office:automatic-styles>
<office:body>
<office:text>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
${content}
</office:text>
</office:body>
</office:document-content>`;
const styleTemplate = (customStyles: string) => `<?xml version="1.0" encoding="UTF-8"?>
<office:document-styles
xmlns:css3t="http://www.w3.org/TR/css3-text/"
xmlns:grddl="http://www.w3.org/2003/g/data-view#"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:dom="http://www.w3.org/2001/xml-events"
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:ooo="http://openoffice.org/2004/office"
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
xmlns:ooow="http://openoffice.org/2004/writer"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:drawooo="http://openoffice.org/2010/draw"
xmlns:oooc="http://openoffice.org/2004/calc"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0"
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
xmlns:tableooo="http://openoffice.org/2009/table"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
xmlns:rpt="http://openoffice.org/2005/report"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
xmlns:officeooo="http://openoffice.org/2009/office"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
xmlns:number="urn:oasis:names:tc:opendocument:
xmlns:datastyle:1.0"
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0"
office:version="1.4">
<office:font-face-decls/>
<office:styles>
<style:style style:class="default" style:display-name="Normal" style:family="paragraph" style:name="Normal">
<style:paragraph-properties fo:line-height="115%" fo:margin-top="0pt" fo:margin-bottom="10pt" />
<style:text-properties style:font-name="Noto Sans" fo:font-family="'Noto Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" />
</style:style>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Normal" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Noto Sans" fo:font-family="'Noto Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Noto Sans Thai1" style:font-family-complex="'Noto Sans Thai'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt"/>
</style:style>
<style:style style:name="Heading1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:default-outline-level="1" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
<style:text-properties fo:font-size="32pt" fo:font-weight="bold" style:font-size-asian="32pt" style:font-weight-asian="bold" style:font-size-complex="32pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Heading2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:default-outline-level="2" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
<style:text-properties fo:font-size="24pt" fo:font-weight="bold" style:font-size-asian="24pt" style:font-weight-asian="bold" style:font-size-complex="24pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Heading3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:default-outline-level="3" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" style:contextual-spacing="false"/>
<style:text-properties fo:font-size="18.72pt" fo:font-weight="bold" style:font-size-asian="18.72pt" style:font-weight-asian="bold" style:font-size-complex="18.72pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Code" style:family="paragraph" style:parent-style-name="Normal" style:class="default">
<style:text-properties style:font-name="JetBrains Mono" fo:font-family="'JetBrains Mono'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Noto Sans Thai1" style:font-family-complex="'Noto Sans Thai'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt"/>
</style:style>
<style:style style:name="Codespan" style:family="text" style:parent-style-name="Normal" style:class="default">
<style:text-properties style:font-name="JetBrains Mono" fo:font-family="'JetBrains Mono'" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Noto Sans CJK SC" style:font-family-asian="'Noto Sans CJK SC'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="Noto Sans Thai1" style:font-family-complex="'Noto Sans Thai'" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="18pt"/>
</style:style>${customStyles}
</office:styles>
<office:automatic-styles>
<style:page-layout style:name="Mpm1">
<style:page-layout-properties fo:page-width="8.3in" fo:page-height="11.7in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:footnote-max-height="0in" loext:margin-gutter="0in">
<style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style/>
<style:footer-style/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="Mpm1"/>
</office:master-styles>
</office:document-styles>`;
const manifestRdf = `<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="styles.xml">
<rdf:type rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/odf#StylesFile"/>
</rdf:Description>
<rdf:Description rdf:about="">
<ns0:hasPart xmlns:ns0="http://docs.oasis-open.org/ns/office/1.2/meta/pkg#" rdf:resource="styles.xml"/>
</rdf:Description>
<rdf:Description rdf:about="content.xml">
<rdf:type rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/odf#ContentFile"/>
</rdf:Description>
<rdf:Description rdf:about="">
<ns0:hasPart xmlns:ns0="http://docs.oasis-open.org/ns/office/1.2/meta/pkg#" rdf:resource="content.xml"/>
</rdf:Description>
<rdf:Description rdf:about="">
<rdf:type rdf:resource="http://docs.oasis-open.org/ns/office/1.2/meta/pkg#Document"/>
</rdf:Description>
</rdf:RDF>`;
const manifestInf = `<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.4" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0">
<manifest:file-entry manifest:full-path="/" manifest:version="1.4" manifest:media-type="application/vnd.oasis.opendocument.text"/>
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="manifest.rdf" manifest:media-type="application/rdf+xml"/>
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
</manifest:manifest>`;
let stylesRequired: StylesRequired = {};
async function getCustomStyles() {
let contentStyle = "";
let style = "";
if (stylesRequired.bulletList || stylesRequired.orderedList) {
const listInfo = await import("./odt/list");
if (stylesRequired.bulletList) {
contentStyle += listInfo.bulletListContentStyle;
style += listInfo.bulletListStyle;
}
if (stylesRequired.orderedList) {
contentStyle += listInfo.orderedListContentStyle;
style += listInfo.orderedListStyle;
}
}
return {
content: contentStyle,
style: style
};
}
function parseTokens(tokens: TokensList | Token[]) {
let outputText = "";
for (const tok of tokens) {
switch (tok.type) {
case "heading":
outputText += `<text:h text:style-name="Heading${tok.depth}" text:outline-level="${tok.depth}">${parseTokens(tok.tokens ?? [])}</text:h>\n`;
break;
case "paragraph":
outputText += `<text:p text:style-name="Normal">${parseTokens(tok.tokens ?? [])}</text:p>`;
break;
case "strong":
outputText += `<text:span text:style-name="TextBold">${parseTokens(tok.tokens ?? [])}</text:span>`;
break;
case "em":
outputText += `<text:span text:style-name="TextItalic">${parseTokens(tok.tokens ?? [])}</text:span>`;
break;
case "codespan":
outputText += `<text:span text:style-name="Codespan">${tok.text}</text:span>`;
break;
case "code":
outputText += `<text:p text:style-name="Code">${(tok.text as string).replaceAll("\n", "<text:line-break/>")}</text:p>`;
break;
case "list":
if (tok.ordered) {
stylesRequired.orderedList = true;
} else {
stylesRequired.bulletList = true;
}
outputText += `<text:list text:style-name="${tok.ordered ? "L2" : "L1"}">
${parseTokens(tok.items)}</text:list>`;
break;
case "list_item":
outputText += "<text:list-item>";
const labelIndex = tok.tokens?.findIndex((t) => t.type === "text") ?? -1;
let remainingTokens = tok.tokens ?? [];
if (labelIndex !== -1) {
const label = tok.tokens?.[labelIndex];
outputText += "<text:p>";
if (tok.task) {
outputText += `[${tok.checked ? "x" : " "}] `;
}
outputText += (label as Tokens.Text).text;
outputText += "</text:p>";
remainingTokens.splice(labelIndex, 1);
}
outputText += `${parseTokens(remainingTokens)}</text:list-item>\n`;
break;
case "text":
outputText += tok.text;
break;
}
}
return outputText;
}
export async function exportOdt(content: string) {
stylesRequired = {};
const tokens = marked.lexer(content);
console.log(tokens);
const zip = new JSZip();
const xmlContent = parseTokens(tokens);
const customStyles = await getCustomStyles();
zip.file("content.xml", contentTemplate(xmlContent, customStyles.content));
zip.file("styles.xml", styleTemplate(customStyles.style));
zip.file("mimetype", "application/vnd.oasis.opendocument.text");
zip.file("manifest.rdf", manifestRdf);
const metaInf = zip.folder("META-INF");
metaInf?.file("manifest.xml", manifestInf);
const zipContent = await zip.generateAsync({ type: "blob", mimeType: "application/vnd.oasis.opendocument.text" });
download(zipContent, "litewriter-document.odt");
}
+11
View File
@@ -0,0 +1,11 @@
import type { PageTheme } from "./theme";
import { dracula as draculaCode } from "thememirror";
export const dracula: PageTheme = {
background: "#282A36",
backgroundDark: "#21222C",
floatingControls: "#343746",
foreground: "#F8F8F2",
codemirror: draculaCode,
dark: true,
};
+10
View File
@@ -0,0 +1,10 @@
import type { Extension } from "@codemirror/state";
export interface PageTheme {
background: string;
backgroundDark?: string;
floatingControls?: string;
foreground: string;
dark: boolean;
codemirror: Extension;
}
+12
View File
@@ -0,0 +1,12 @@
<script lang="ts">
import '../app.css';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
{@render children?.()}
+21
View File
@@ -0,0 +1,21 @@
import { fail, type Actions } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
export const actions = {
changeTheme: async ({ cookies, request }) => {
const data = await request.formData();
const theme = data.get("theme");
if (theme === null || typeof (theme) !== "string") {
return fail(400);
}
cookies.set("lwThemeName", theme, { path: "/" });
}
} satisfies Actions;
export const load: PageServerLoad = async ({ cookies }) => {
return {
themeName: cookies.get("lwThemeName") ?? "Default"
};
};
+272
View File
@@ -0,0 +1,272 @@
<script lang="ts">
import { markdown } from '@codemirror/lang-markdown';
import CodeMirror from 'svelte-codemirror-editor';
import '../css/codemirror.css';
import { Decoration, MatchDecorator, ViewPlugin } from '@codemirror/view';
import type { PageTheme } from '$lib/themes/theme';
import { dracula } from '$lib/themes/dracula';
import { enhance } from '$app/forms';
import { onMount } from 'svelte';
import MenuButton from '$lib/components/MenuButton.svelte';
import ExportDialog from '$lib/components/ExportDialog.svelte';
let { data } = $props();
let { themeName } = data;
let content = $state(`# Heading 1
## Heading 2
### Heading 3
**bold** *italic*`);
let changeThemeForm: HTMLFormElement;
const themeOptions: { [key: string]: PageTheme | null } = {
Default: null,
Dracula: dracula
};
let cmTheme = themeOptions[themeName]?.codemirror ?? null;
let headingDecorations: { [key: string]: Decoration } = {
'#': Decoration.mark({ class: 'h1' }),
'##': Decoration.mark({ class: 'h2' }),
'###': Decoration.mark({ class: 'h3' }),
'####': Decoration.mark({ class: 'h4' }),
'#####': Decoration.mark({ class: 'h5' }),
'######': Decoration.mark({ class: 'h6' })
};
let decorator = new MatchDecorator({
regexp: /^#{1,6} .*/gm,
decoration(match, view, pos) {
return headingDecorations[match[0].split(' ')[0]];
}
});
const headingPlugin = ViewPlugin.define(
(view) => ({
decorations: decorator.createDeco(view),
update(u) {
this.decorations = decorator.updateDeco(u, this.decorations);
}
}),
{
decorations: (v) => v.decorations
}
);
onMount(() => {
const pageTheme = themeOptions[themeName];
if (pageTheme?.dark) {
document.body.classList.add('dark');
}
document.body.style.setProperty('--theme-background', pageTheme?.background ?? null);
document.body.style.setProperty('--theme-background-darker', pageTheme?.backgroundDark ?? null);
document.body.style.setProperty('--theme-floating', pageTheme?.floatingControls ?? null);
document.body.style.setProperty('--theme-foreground', pageTheme?.foreground ?? null);
});
let shownMenu = $state<string | null>(null);
let exportDialog: ExportDialog;
function menuButtonMouseOver(menuName: string) {
if (shownMenu === null) return;
shownMenu = menuName;
}
function menuButtonClicked(menuName: string) {
if (shownMenu === menuName) {
shownMenu = null;
menuTriggeredWithAlt = false;
return;
}
shownMenu = menuName;
}
function menuButtonBlur(e: FocusEvent) {
if (
e.relatedTarget !== null &&
(e.relatedTarget as HTMLElement).classList.contains('top-menu-button')
) {
return;
}
shownMenu = null;
}
function menuActionComplete() {
shownMenu = null;
}
function newFileBtn() {
content = '';
menuActionComplete();
}
function aboutBtn() {
alert(`litewriter - Open-source online Markdown editor for writing documents`);
menuActionComplete();
}
function exportFileBtn() {
exportDialog.showModal();
menuActionComplete();
}
function printFileBtn() {
exportDialog.exportPdf();
menuActionComplete();
}
let altPressed = $state(false);
let menuTriggeredWithAlt = $state(false);
const altMenuMapping: { [key: string]: string } = {
f: 'file',
h: 'help'
};
const altSubmenuMapping: { [menu: string]: { [key: string]: Function } } = {
file: {
n: newFileBtn,
e: exportFileBtn,
p: printFileBtn
},
help: {
a: aboutBtn
}
};
function keyDownListener(ev: KeyboardEvent) {
if (menuTriggeredWithAlt && shownMenu !== null && altSubmenuMapping[shownMenu]?.[ev.key]) {
ev.preventDefault();
ev.stopPropagation();
altSubmenuMapping[shownMenu][ev.key]();
menuTriggeredWithAlt = false;
shownMenu = null;
}
if (ev.altKey) {
const menu = altMenuMapping[ev.key];
if (menu) {
ev.preventDefault();
ev.stopPropagation();
menuTriggeredWithAlt = true;
menuButtonClicked(menu);
return;
}
if (ev.key === 'Alt') {
ev.preventDefault();
ev.stopPropagation();
altPressed = true;
menuTriggeredWithAlt = false;
}
return;
}
}
function keyUpListener(ev: KeyboardEvent) {
if (ev.key === 'Alt') {
altPressed = false;
}
}
</script>
<svelte:body onkeydown={keyDownListener} onkeyup={keyUpListener} />
<header class="p-4 flex justify-between bg-theme-darker">
<ul class="flex" role="list">
<li>
<MenuButton
{shownMenu}
name="file"
mouseOver={menuButtonMouseOver}
buttonClicked={menuButtonClicked}
blurEvent={menuButtonBlur}
>
<span class={altPressed ? 'underline' : ''}>F</span>ile
</MenuButton>
<div
class={`z-10 w-44 absolute bg-theme-floating rounded ${shownMenu === 'file' ? '' : 'hidden'} shadow`}
role="menu"
>
<ul>
<li class="flex">
<button
class="hover:bg-gray-300/40 dark:hover:bg-gray-500/40 btn rounded transition-colors flex-1 text-left top-menu-button"
onclick={newFileBtn}
>
<span class={menuTriggeredWithAlt ? 'underline' : ''}>N</span>ew File
</button>
</li>
<li class="flex">
<button
class="hover:bg-gray-300/40 dark:hover:bg-gray-500/40 btn rounded transition-colors flex-1 text-left top-menu-button"
onclick={exportFileBtn}
>
<span class={menuTriggeredWithAlt ? 'underline' : ''}>E</span>xport...
</button>
</li>
<li class="flex">
<button
class="hover:bg-gray-300/40 dark:hover:bg-gray-500/40 btn rounded transition-colors flex-1 text-left top-menu-button"
onclick={printFileBtn}
>
<span class={menuTriggeredWithAlt ? 'underline' : ''}>P</span>rint...
</button>
</li>
</ul>
</div>
</li>
<li>
<MenuButton
{shownMenu}
name="help"
mouseOver={menuButtonMouseOver}
buttonClicked={menuButtonClicked}
blurEvent={menuButtonBlur}
>
<span class={altPressed ? 'underline' : ''}>H</span>elp
</MenuButton>
<div
class={`z-10 w-44 absolute bg-theme-floating rounded ${shownMenu === 'help' ? '' : 'hidden'} shadow`}
role="menu"
>
<ul>
<li class="flex">
<button
class="hover:bg-gray-300/40 dark:hover:bg-gray-500/40 btn rounded transition-colors flex-1 text-left top-menu-button"
>
<span class={menuTriggeredWithAlt ? 'underline' : ''}>A</span>bout
</button>
</li>
</ul>
</div>
</li>
</ul>
<form method="POST" use:enhance action="?/changeTheme" bind:this={changeThemeForm}>
<select
oninput={() => changeThemeForm.submit()}
value={themeName}
name="theme"
id="themeSelector"
class="generic-select"
>
<option value="Default">Default (Light)</option>
<option value="Dracula">Dracula</option>
</select>
</form>
</header>
<ExportDialog bind:this={exportDialog} {content} />
<CodeMirror bind:value={content} theme={cmTheme} lang={markdown()} extensions={[headingPlugin]} />
+3
View File
@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:
+18
View File
@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter()
},
};
export default config;
+19
View 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://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}
+7
View File
@@ -0,0 +1,7 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
});