Compare commits
No commits in common. "b620536eb80b98f097075d5340f91f8cf74cef67" and "2809dc629509d5f5d283a2bf85ed0809faeeb305" have entirely different histories.
b620536eb8
...
2809dc6295
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
> A no-database, no-backend pastebin-like service. All the data is stored in the url.
|
> A no-database, no-backend pastebin-like service. All the data is stored in the url.
|
||||||
|
|
||||||
This is a rewrite of https://nopaste.boris.sh/, which is a rewrite of https://topaz.github.io/paste/
|
This is a rewrite of https://nopaste.boris.sh/, which was a rewrite of https://topaz.github.io/paste/. The biggest improvement of this version is that it uses brotli compression, while staying compatible with the original lzma compressed urls.
|
||||||
|
|
||||||
[More information on this self-hosted "about" page](https://paste.scambier.xyz/?l=md#G0oEIKySx5UfTkdu1XoEsNFTBka+1+MnKOMEeLUaweWLc/oYlbttJz93QOgIkhdODeUmDs/+u3T5RANkM7+ktGLRxbTN0WIKyb2AWJbsnIoNDvzMtbcgQpsQYPWXPwUeRkuMWOXIEYIfzvnjqY85WNtT9Rjq3Y4/qHB1+mjzCQsTvr9UIfLbq28+i4joe5i/F2SQCTO9G/SlgnojZOKr9BSbIq0EPVBnVgg9T8+zls2Ezgg67tZTjX8pVB70RIghiwfFeqn/ZMQv9VGcbEWDIHnUlL52CL4FeCQ6DMJ4OOtVWk9jWI3i1CRvzxbmHudPlk1iA4pD9wHgOOILFPKQUAntulosi6VRhJ9eeSmViKpbloonQX9cDQVUkg8rzdkoBllTTfnVcYwDRsMS3UjTv/I+befAQeNMiRc7ESFegwUd6yEFPyVJG8RCXcgvw1QfdBe93uXQ9qGBL6BhLpjlpqxjmf9fwkKHFYClXmn7yaZrc/HrW6YH5F6Ei9MGKMvY5be++/noh6TJ5MeGgDzeJgNr0rjvcltZrDRS8wumbWpseMjTY1bz/rp/fJVQxwy/Rd+kXjlGrMJLZsVVwED5b0Jr26wUAm85shUyLX50kklq6ZIRfnzNX4LWrnl72vWR1YaTr/3n+Cp+6IBEliYA)
|
[More information on this self-hosted "about" page](https://paste.scambier.xyz/?l=mrwn#G14EICwPbMc0BJ69xNPoItLoErDRMwMj3+vxE5RxArxajeByk+b0b1Tm2h/5ugGgK6S83AqUCi09HeYmDs+vvzQc0QDZe7v7U2rHnFe6CTWSm2AArNua4tqtXVIEFVQuqG+is15etOm1OZOPPCH4xDX1iDNF2bQcN9ugum0Icnwj51n0nlvNacn/N3OOctSGrQbLoNzG8n5bSoJQXOx+PsxV+MeUl+rkKNPIAXT9c8LoOY/772FiFkLv/5w8rvtCBc38VEE5lzmGFcZ0QtqlzBhvWLV5WPhRkROqeoKoDCtH03/fQVI5Y8HfLkq2/Nuidd1GQTKgmPJQVWbZ4cAiqUdOJG6j6pvu48qWjqamYG3nFPHcdYLHYCRV877U2mBgSHTbLWv6eu/DEA88SE5XEIqiQFyDVS2HLN7jJSloLHQJ+jOE86zb9jrkQ9tXAwkAoovCcnlrW5T/EU4L7imRsDAkZdEySO/ri/NBuHPMpSFnqGItLlPd98/SZSmySbImOLfv4WGbdCxw9TYykGsk56aQaVSa/DB2t5HfD6x28lJVlMm3Hjnkwyx95PxNGH0teCAk6CqtbBYlC3LjhY0IEpJ0WWZWY6MlJB+jEW7lAN3DSJD0QFz2FZOOV/HwA9LbFBU=)
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [x] Use brotli
|
- [x] Use brotli
|
||||||
- [x] ~~Stay compatible with original lzma-compressed urls~~
|
- [x] Stay compatible with original lzma-compressed urls
|
||||||
- [x] Have a read-only view with nice colors for code blocks
|
- [x] Have a read-only view with nice colors for code blocks
|
||||||
- [ ] Use CodeMirror 6
|
- [ ] Use CodeMirror 6
|
||||||
- [ ] 100% self-contained, no dependance on jsdeliver
|
- [ ] 100% self-contained, no dependance on jsdeliver
|
||||||
|
|
14
src/app.html
14
src/app.html
|
@ -3,8 +3,8 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Paste</title>
|
<title>Paste</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||||
<link rel="preload" as="font" type="text/css" href="%sveltekit.assets%/hack-subset.css" />
|
<link rel="stylesheet" type="text/css" href="%sveltekit.assets%/fonts.css" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
|
@ -17,5 +17,15 @@ npm/codemirror@5.65.16/theme/nord.min.css"
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div class="dark:text-gray-300 dark:bg-gray-800 min-h-screen">%sveltekit.body%</div>
|
<div class="dark:text-gray-300 dark:bg-gray-800 min-h-screen">%sveltekit.body%</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/combine/
|
||||||
|
npm/lzma@2.3.2/src/lzma.min.js,
|
||||||
|
npm/codemirror@5.65.16,
|
||||||
|
npm/codemirror@5.65.16/addon/mode/loadmode.min.js,
|
||||||
|
npm/codemirror@5.65.16/addon/mode/overlay.min.js,
|
||||||
|
npm/codemirror@5.65.16/addon/mode/multiplex.min.js,
|
||||||
|
npm/codemirror@5.65.16/addon/mode/simple.min.js,
|
||||||
|
npm/codemirror@5.65.16/addon/scroll/simplescrollbars.js,
|
||||||
|
npm/codemirror@5.65.16/mode/meta.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount, tick } from 'svelte'
|
|
||||||
import { shorten, getLangFromUrl } from '$lib/utils'
|
|
||||||
import type { Editor } from 'codemirror'
|
|
||||||
import { shareUrl, selectedLang } from '../store'
|
|
||||||
import ComboBox from './ComboBox.svelte'
|
|
||||||
import Icon from '@iconify/svelte'
|
|
||||||
|
|
||||||
type Language = {
|
|
||||||
text: string
|
|
||||||
value: string
|
|
||||||
data: { mime: string; mode: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
let cssClass = ''
|
|
||||||
export { cssClass as class }
|
|
||||||
export let editor: Editor | null = null
|
|
||||||
export let updateShareUrl: () => Promise<void>
|
|
||||||
|
|
||||||
let languages: Language[] = []
|
|
||||||
let selectedLanguage: Language | null = null
|
|
||||||
let textWrap = false
|
|
||||||
|
|
||||||
let urlInput: HTMLInputElement
|
|
||||||
let isUrlInputVisible = false
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
$selectedLang = getLangFromUrl()
|
|
||||||
selectedLanguage = languages.find((e) => e.value === $selectedLang)!
|
|
||||||
})
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (editor) {
|
|
||||||
languages = CodeMirror.modeInfo
|
|
||||||
.map((e: any) => ({
|
|
||||||
text: e.name,
|
|
||||||
value: shorten(e.name),
|
|
||||||
data: { mime: e.mime, mode: e.mode },
|
|
||||||
}))
|
|
||||||
.filter((l: any) => l.value !== 'gflm') // Remove github flavored markdown, redundant with markdown
|
|
||||||
|
|
||||||
selectedLanguage =
|
|
||||||
selectedLanguage ??
|
|
||||||
languages.find((e) => e.value === $selectedLang) ??
|
|
||||||
languages.find((e) => e.value === 'plt')!
|
|
||||||
|
|
||||||
const langData = selectedLanguage?.data ?? { mime: null, mode: null }
|
|
||||||
// @ts-ignore
|
|
||||||
editor.setOption('mode', langData.mime)
|
|
||||||
CodeMirror.autoLoadMode(editor, langData.mode)
|
|
||||||
$selectedLang = selectedLanguage?.value ?? 'plt'
|
|
||||||
|
|
||||||
// Line wrapping
|
|
||||||
editor.setOption('lineWrapping', textWrap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function showUrlInput() {
|
|
||||||
// Make sure the url is up to date
|
|
||||||
await updateShareUrl()
|
|
||||||
isUrlInputVisible = true
|
|
||||||
await tick()
|
|
||||||
urlInput.select()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyUrl() {
|
|
||||||
urlInput.select()
|
|
||||||
await navigator.clipboard.writeText(urlInput.value)
|
|
||||||
setTimeout(closeUrlInput, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeUrlInput() {
|
|
||||||
isUrlInputVisible = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={cssClass}>
|
|
||||||
{#if isUrlInputVisible}
|
|
||||||
<div class="flex gap-2 grow">
|
|
||||||
<input
|
|
||||||
bind:this={urlInput}
|
|
||||||
type="text"
|
|
||||||
class="border border-gray-300 bg-transparent p-1 grow"
|
|
||||||
value={$shareUrl}
|
|
||||||
/>
|
|
||||||
<button class="button" on:click={copyUrl}>Copy</button>
|
|
||||||
<button class="button" on:click={closeUrlInput}>Close</button>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="flex justify-end gap-2">
|
|
||||||
<div>
|
|
||||||
<ComboBox
|
|
||||||
items={languages}
|
|
||||||
bind:value={selectedLanguage}
|
|
||||||
class="bg-gray-700 border border-gray-300 p-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- Show link input-->
|
|
||||||
<button class="button" on:click={showUrlInput}>Get Link</button>
|
|
||||||
|
|
||||||
<!-- Toggle text wrap -->
|
|
||||||
<button class="button" title="Toggle text wrap" on:click={() => (textWrap = !textWrap)}>
|
|
||||||
{#if textWrap}
|
|
||||||
<Icon class="text-xl" icon="fluent:text-wrap-24-filled" />
|
|
||||||
{:else}
|
|
||||||
<Icon class="text-xl" icon="fluent:text-wrap-off-24-filled" />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Switch to readonly view -->
|
|
||||||
<a class="button" href={$shareUrl}>
|
|
||||||
<Icon class="text-xl" icon="fluent:eye-12-regular" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.button {
|
|
||||||
@apply text-sm border border-gray-300 p-1 hover:bg-gray-600/50;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,17 +1,135 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount, tick } from 'svelte'
|
||||||
|
import { shorten } from '$lib/utils'
|
||||||
|
import type { Editor } from 'codemirror'
|
||||||
|
import { shareUrl, selectedLang } from '../store'
|
||||||
|
import ComboBox from './ComboBox.svelte'
|
||||||
|
import Icon from '@iconify/svelte'
|
||||||
|
|
||||||
|
type Language = {
|
||||||
|
text: string
|
||||||
|
value: string
|
||||||
|
data: { mime: string; mode: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export let editor: Editor | null = null
|
||||||
|
export let updateShareUrl: () => Promise<void>
|
||||||
|
|
||||||
|
let languages: Language[] = []
|
||||||
|
let selectedLanguage: Language | null = null
|
||||||
|
let textWrap = false
|
||||||
|
|
||||||
|
let urlInput: HTMLInputElement
|
||||||
|
let isUrlInputVisible = false
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (editor) {
|
||||||
|
const language = selectedLanguage?.data ?? { mime: null, mode: null }
|
||||||
|
// @ts-ignore
|
||||||
|
editor.setOption('mode', language.mime)
|
||||||
|
CodeMirror.autoLoadMode(editor, language.mode)
|
||||||
|
$selectedLang = selectedLanguage?.value ?? null
|
||||||
|
|
||||||
|
// Line wrapping
|
||||||
|
editor.setOption('lineWrapping', textWrap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
languages = CodeMirror.modeInfo
|
||||||
|
.map((e: any) => ({
|
||||||
|
text: e.name,
|
||||||
|
value: shorten(e.name),
|
||||||
|
data: { mime: e.mime, mode: e.mode },
|
||||||
|
}))
|
||||||
|
.filter((l: any) => l.value !== 'gflm') // Remove github flavored markdown, redundant with markdown
|
||||||
|
console.log(languages)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function setLanguage(lang: string) {
|
||||||
|
if (lang === 'mrwn' || lang === 'gflm') {
|
||||||
|
// back compatiblity with old links
|
||||||
|
lang = 'md'
|
||||||
|
}
|
||||||
|
const language = languages.find((e) => e.value === lang)!
|
||||||
|
selectedLanguage = language
|
||||||
|
// Automatic text wrap for plain text or markdown
|
||||||
|
if (lang === 'plt' || lang === 'mrwn' || !lang) {
|
||||||
|
textWrap = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showUrlInput() {
|
||||||
|
// Make sure the url is up to date
|
||||||
|
await updateShareUrl()
|
||||||
|
isUrlInputVisible = true
|
||||||
|
await tick()
|
||||||
|
urlInput.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyUrl() {
|
||||||
|
urlInput.select()
|
||||||
|
await navigator.clipboard.writeText(urlInput.value)
|
||||||
|
closeUrlInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeUrlInput() {
|
||||||
|
isUrlInputVisible = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap justify-between items-center px-3 py-1 text-sm relative z-10 shadow-md gap-2 font-mono bg-gray-700"
|
class="flex flex-wrap justify-between items-center px-3 py-1 text-sm relative z-10 shadow-md gap-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h1 class="text-xl">Paste</h1>
|
<h1 class="text-xl">Paste</h1>
|
||||||
<span class="ml-8 text-xs">
|
<span class="ml-8 text-xs">
|
||||||
<a
|
<a
|
||||||
href="?l=md#G0oEIKySx5UfTkdu1XoEsNFTBka+1+MnKOMEeLUaweWLc/oYlbttJz93QOgIkhdODeUmDs/+u3T5RANkM7+ktGLRxbTN0WIKyb2AWJbsnIoNDvzMtbcgQpsQYPWXPwUeRkuMWOXIEYIfzvnjqY85WNtT9Rjq3Y4/qHB1+mjzCQsTvr9UIfLbq28+i4joe5i/F2SQCTO9G/SlgnojZOKr9BSbIq0EPVBnVgg9T8+zls2Ezgg67tZTjX8pVB70RIghiwfFeqn/ZMQv9VGcbEWDIHnUlL52CL4FeCQ6DMJ4OOtVWk9jWI3i1CRvzxbmHudPlk1iA4pD9wHgOOILFPKQUAntulosi6VRhJ9eeSmViKpbloonQX9cDQVUkg8rzdkoBllTTfnVcYwDRsMS3UjTv/I+befAQeNMiRc7ESFegwUd6yEFPyVJG8RCXcgvw1QfdBe93uXQ9qGBL6BhLpjlpqxjmf9fwkKHFYClXmn7yaZrc/HrW6YH5F6Ei9MGKMvY5be++/noh6TJ5MeGgDzeJgNr0rjvcltZrDRS8wumbWpseMjTY1bz/rp/fJVQxwy/Rd+kXjlGrMJLZsVVwED5b0Jr26wUAm85shUyLX50kklq6ZIRfnzNX4LWrnl72vWR1YaTr/3n+Cp+6IBEliYA"
|
href="https://paste.scambier.xyz/?l=mrwn#G14EICwPbMc0BJ69xNPoItLoErDRMwMj3+vxE5RxArxajeByk+b0b1Tm2h/5ugGgK6S83AqUCi09HeYmDs+vvzQc0QDZe7v7U2rHnFe6CTWSm2AArNua4tqtXVIEFVQuqG+is15etOm1OZOPPCH4xDX1iDNF2bQcN9ugum0Icnwj51n0nlvNacn/N3OOctSGrQbLoNzG8n5bSoJQXOx+PsxV+MeUl+rkKNPIAXT9c8LoOY/772FiFkLv/5w8rvtCBc38VEE5lzmGFcZ0QtqlzBhvWLV5WPhRkROqeoKoDCtH03/fQVI5Y8HfLkq2/Nuidd1GQTKgmPJQVWbZ4cAiqUdOJG6j6pvu48qWjqamYG3nFPHcdYLHYCRV877U2mBgSHTbLWv6eu/DEA88SE5XEIqiQFyDVS2HLN7jJSloLHQJ+jOE86zb9jrkQ9tXAwkAoovCcnlrW5T/EU4L7imRsDAkZdEySO/ri/NBuHPMpSFnqGItLlPd98/SZSmySbImOLfv4WGbdCxw9TYykGsk56aQaVSa/DB2t5HfD6x28lJVlMm3Hjnkwyx95PxNGH0teCAk6CqtbBYlC3LjhY0IEpJ0WWZWY6MlJB+jEW7lAN3DSJD0QFz2FZOOV/HwA9LbFBU="
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
About
|
About</a
|
||||||
</a>
|
>
|
||||||
<a class="ml-4" href="https://git.scambier.xyz/scambier/paste" target="_blank">Source</a>
|
<a class="ml-4" href="https://git.scambier.xyz/scambier/paste" target="_blank">Source</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
|
||||||
|
{#if isUrlInputVisible}
|
||||||
|
<div class="flex gap-2 grow">
|
||||||
|
<input
|
||||||
|
bind:this={urlInput}
|
||||||
|
type="text"
|
||||||
|
class="border border-gray-300 bg-transparent p-1 grow"
|
||||||
|
value={$shareUrl}
|
||||||
|
/>
|
||||||
|
<button on:click={copyUrl}>Copy</button>
|
||||||
|
<button on:click={closeUrlInput}>Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<div>
|
||||||
|
<ComboBox
|
||||||
|
items={languages}
|
||||||
|
bind:value={selectedLanguage}
|
||||||
|
class="bg-gray-700 border border-gray-300 p-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button title="Toggle text wrap" on:click={() => (textWrap = !textWrap)}>
|
||||||
|
{#if textWrap}
|
||||||
|
<Icon class="text-xl" icon="fluent:text-wrap-24-filled" />
|
||||||
|
{:else}
|
||||||
|
<Icon class="text-xl" icon="fluent:text-wrap-off-24-filled" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<button on:click={showUrlInput}> Generate Link </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
button {
|
||||||
|
@apply text-sm border border-gray-300 p-1 hover:bg-gray-600/50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
6
src/globals.d.ts
vendored
6
src/globals.d.ts
vendored
|
@ -1 +1,7 @@
|
||||||
|
declare class LZMA {
|
||||||
|
constructor(any): {}
|
||||||
|
compress: (value: string, level: number, callback: (result: number[]) => void) => void
|
||||||
|
decompress: (b: Uint8Array, callback: (result: string, err: any) => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
declare const CodeMirror: any
|
declare const CodeMirror: any
|
||||||
|
|
33
src/lib/lzma.ts
Normal file
33
src/lib/lzma.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// LZMA imported from <script> in index.html
|
||||||
|
const blob = new Blob([
|
||||||
|
'importScripts("https://cdn.jsdelivr.net/npm/lzma@2.3.2/src/lzma_worker.min.js");',
|
||||||
|
])
|
||||||
|
const lzma = new LZMA(window.URL.createObjectURL(blob))
|
||||||
|
|
||||||
|
async function compress(value: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
lzma.compress(value, 1, (numbers: number[]) => {
|
||||||
|
const bytes = new Uint8Array(numbers)
|
||||||
|
const b64 = btoa(String.fromCharCode(...bytes))
|
||||||
|
resolve(b64)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decompress(base64: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = new XMLHttpRequest()
|
||||||
|
req.open('GET', 'data:application/octet;base64,' + base64)
|
||||||
|
req.responseType = 'arraybuffer'
|
||||||
|
req.onload = (e) => {
|
||||||
|
const bytes = new Uint8Array(req.response)
|
||||||
|
lzma.decompress(bytes, (result, err) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
req.send()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export { compress, decompress }
|
|
@ -28,8 +28,4 @@ export const shorten = (name: string) => {
|
||||||
return n.substr(0, 2) + n.substr(n.length - 2, 2)
|
return n.substr(0, 2) + n.substr(n.length - 2, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLangFromUrl() {
|
|
||||||
return new URLSearchParams(window.location.search).get('l') ?? 'plt'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const byId = (id: string) => document.getElementById(id)
|
export const byId = (id: string) => document.getElementById(id)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
import * as brotli from '$lib/brotli'
|
||||||
|
import * as lzma from '$lib/lzma'
|
||||||
import { unified } from 'unified'
|
import { unified } from 'unified'
|
||||||
import remarkParse from 'remark-parse'
|
import remarkParse from 'remark-parse'
|
||||||
import remarkRehype from 'remark-rehype'
|
import remarkRehype from 'remark-rehype'
|
||||||
|
@ -8,27 +10,27 @@
|
||||||
import rehypeStringify from 'rehype-stringify'
|
import rehypeStringify from 'rehype-stringify'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import 'highlight.js/styles/nord.min.css'
|
import 'highlight.js/styles/nord.min.css'
|
||||||
import TopBar from '../components/TopBar.svelte'
|
|
||||||
import Icon from '@iconify/svelte'
|
|
||||||
import { getLangFromUrl } from '$lib/utils'
|
|
||||||
|
|
||||||
let decompressed: string
|
let decompressed: string
|
||||||
let isMarkdown = false
|
let isMarkdown = false
|
||||||
let isPlainText = false
|
let isPlainText = false
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let lang = getLangFromUrl()
|
let lang = new URLSearchParams(window.location.search).get('l')
|
||||||
lang = lang === 'mrwn' || lang === 'gflm' ? 'md' : lang // back compatiblity with old links
|
lang = lang === 'mrwn' || lang === 'gflm' ? 'md' : lang // back compatiblity with old links
|
||||||
|
|
||||||
// extract the part in the url after the hash
|
// extract the part in the url after the hash
|
||||||
const hash = window.location.hash.slice(1)
|
const hash = window.location.hash.slice(1)
|
||||||
if (hash) {
|
if (hash) {
|
||||||
// decompress the data
|
// decompress the data
|
||||||
const { decompress } = await import('$lib/brotli')
|
if (hash.startsWith('XQAAA')) {
|
||||||
decompressed = await decompress(hash)
|
decompressed = await lzma.decompress(hash)
|
||||||
|
} else {
|
||||||
|
decompressed = await brotli.decompress(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// Markdown
|
// Markdown
|
||||||
if (lang === 'md') {
|
if (lang === 'md' || lang === 'gflm') {
|
||||||
const html = await unified()
|
const html = await unified()
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
.use(remarkGfm)
|
.use(remarkGfm)
|
||||||
|
@ -52,23 +54,9 @@
|
||||||
window.location.href = '/editor'
|
window.location.href = '/editor'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function getUrlDataPart(): string {
|
|
||||||
return window.location.search + window.location.hash
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if decompressed}
|
|
||||||
<TopBar>
|
|
||||||
<a
|
|
||||||
href={'/editor' + getUrlDataPart()}
|
|
||||||
class="p-1 hover:bg-gray-600/50"
|
|
||||||
title="Edit a copy of this note"
|
|
||||||
>
|
|
||||||
<Icon class="text-xl" icon="fluent:document-edit-16-regular" />
|
|
||||||
</a>
|
|
||||||
</TopBar>
|
|
||||||
<div class="prose dark:prose-invert lg:py-12 p-[0.5em] md:max-w-3xl md:mx-auto lg:max-w-4xl">
|
<div class="prose dark:prose-invert lg:py-12 p-[0.5em] md:max-w-3xl md:mx-auto lg:max-w-4xl">
|
||||||
{#if isMarkdown}
|
{#if isMarkdown}
|
||||||
{@html decompressed}
|
{@html decompressed}
|
||||||
|
@ -80,7 +68,6 @@
|
||||||
<pre><code>{@html decompressed}</code></pre>
|
<pre><code>{@html decompressed}</code></pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Editor } from 'codemirror'
|
import type { Editor } from 'codemirror'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
import { debounce } from 'lodash-es'
|
import { debounce } from 'lodash-es'
|
||||||
import * as brotli from '$lib/brotli'
|
import * as brotli from '$lib/brotli'
|
||||||
|
import * as lzma from '$lib/lzma'
|
||||||
import { byId } from '$lib/utils'
|
import { byId } from '$lib/utils'
|
||||||
import TopBar from '../../components/TopBar.svelte'
|
import TopBar from '../../components/TopBar.svelte'
|
||||||
import { selectedLang, shareUrl } from '../../store'
|
import { selectedLang, shareUrl } from '../../store'
|
||||||
import EditForm from '../../components/EditForm.svelte'
|
|
||||||
|
|
||||||
let editor: Editor | null = null
|
let editor: Editor | null = null
|
||||||
const readOnly = false
|
const readOnly = false
|
||||||
|
@ -13,6 +15,33 @@
|
||||||
let charLen = 0
|
let charLen = 0
|
||||||
let compressed: string = ''
|
let compressed: string = ''
|
||||||
let waiting = false
|
let waiting = false
|
||||||
|
let setLanguage: (lang: string) => void
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
initCodeEditor()
|
||||||
|
// extract the part in the url after the hash
|
||||||
|
const hash = window.location.hash.slice(1)
|
||||||
|
if (hash) {
|
||||||
|
// decompress the data
|
||||||
|
let decompressed: string
|
||||||
|
if (hash.startsWith('XQAAA')) {
|
||||||
|
decompressed = await lzma.decompress(hash)
|
||||||
|
} else {
|
||||||
|
decompressed = await brotli.decompress(hash)
|
||||||
|
}
|
||||||
|
// set the editor value
|
||||||
|
if (editor) {
|
||||||
|
editor.setValue(decompressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = new URLSearchParams(window.location.search).get('l')
|
||||||
|
if (lang) {
|
||||||
|
setLanguage(lang)
|
||||||
|
} else {
|
||||||
|
setLanguage('plt')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function updateShareUrl() {
|
async function updateShareUrl() {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
|
@ -31,7 +60,7 @@
|
||||||
waiting = false
|
waiting = false
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
async function initCodeEditor() {
|
const initCodeEditor = () => {
|
||||||
CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/%N/%N.js'
|
CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/%N/%N.js'
|
||||||
editor = new CodeMirror(byId('editor'), {
|
editor = new CodeMirror(byId('editor'), {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
|
@ -52,40 +81,11 @@
|
||||||
updateShareUrlDebounced()
|
updateShareUrlDebounced()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// extract the part in the url after the hash
|
|
||||||
const hash = window.location.hash.slice(1)
|
|
||||||
if (hash) {
|
|
||||||
// decompress the data
|
|
||||||
const decompressed = await brotli.decompress(hash)
|
|
||||||
// set the editor value
|
|
||||||
if (editor) {
|
|
||||||
editor.setValue(decompressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$selectedLang = new URLSearchParams(window.location.search).get('l') ?? ' plt'
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<script
|
|
||||||
src="https://cdn.jsdelivr.net/combine/
|
|
||||||
npm/codemirror@5.65.16,
|
|
||||||
npm/codemirror@5.65.16/addon/mode/loadmode.min.js,
|
|
||||||
npm/codemirror@5.65.16/addon/mode/overlay.min.js,
|
|
||||||
npm/codemirror@5.65.16/addon/mode/multiplex.min.js,
|
|
||||||
npm/codemirror@5.65.16/addon/mode/simple.min.js,
|
|
||||||
npm/codemirror@5.65.16/addon/scroll/simplescrollbars.js,
|
|
||||||
npm/codemirror@5.65.16/mode/meta.min.js"
|
|
||||||
on:load={initCodeEditor}
|
|
||||||
></script>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<div class="flex flex-col font-mono h-screen bg-gray-700">
|
<div class="flex flex-col font-mono h-screen bg-gray-700">
|
||||||
<TopBar>
|
<TopBar {editor} {updateShareUrl} bind:setLanguage />
|
||||||
<EditForm {editor} {updateShareUrl} class="grow" />
|
|
||||||
</TopBar>
|
|
||||||
|
|
||||||
<div id="editor" class="grow overflow-hidden" />
|
<div id="editor" class="grow overflow-hidden" />
|
||||||
|
|
||||||
|
@ -98,13 +98,13 @@ npm/codemirror@5.65.16/mode/meta.min.js"
|
||||||
? '?'
|
? '?'
|
||||||
: Math.round(($shareUrl.length / charLen) * 100)}% of original)
|
: Math.round(($shareUrl.length / charLen) * 100)}% of original)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(div.CodeMirror) {
|
:global(div.CodeMirror) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-family: 'Hack', monospace;
|
|
||||||
}
|
}
|
||||||
#footer {
|
#footer {
|
||||||
--tw-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
export const shareUrl = writable('')
|
export const shareUrl = writable('')
|
||||||
export const selectedLang = writable<string>('plt')
|
export const selectedLang = writable<string | null>(null)
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><g fill="none"><path fill="#E19747" d="M5 4.5A1.5 1.5 0 0 1 6.5 3h19A1.5 1.5 0 0 1 27 4.5v24a1.5 1.5 0 0 1-1.5 1.5h-19A1.5 1.5 0 0 1 5 28.5v-24Z"/><path fill="#F3EEF8" d="M25 6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v21a1 1 0 0 0 1 1h10.586c.147 0 .29-.032.421-.093l.321-.783l.632-4.086l4.457-.783l.49-.247a1 1 0 0 0 .093-.422V6Z"/><path fill="#CDC4D6" d="M24.91 22H20a1 1 0 0 0-1 1v4.91a1 1 0 0 0 .293-.203l5.414-5.414A1 1 0 0 0 24.91 22Z"/><path fill="#9B9B9B" d="M18 4a2 2 0 1 0-4 0h-1a2 2 0 0 0-2 2v1.5a.5.5 0 0 0 .5.5h9a.5.5 0 0 0 .5-.5V6a2 2 0 0 0-2-2h-1Zm-1 0a1 1 0 1 1-2 0a1 1 0 0 1 2 0Zm-8 8.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5Zm0 3a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5Zm.5 2.5a.5.5 0 0 0 0 1h13a.5.5 0 0 0 0-1h-13ZM9 21.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z"/></g></svg>
|
|
Before Width: | Height: | Size: 915 B |
BIN
static/fonts/JetBrainsMono-Bold.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Bold.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-BoldItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-ExtraBold.woff2
Normal file
BIN
static/fonts/JetBrainsMono-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-ExtraBoldItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-ExtraLight.woff2
Normal file
BIN
static/fonts/JetBrainsMono-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-ExtraLightItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-Italic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Italic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-Light.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Light.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-LightItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-LightItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-Medium.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Medium.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-MediumItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-Regular.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Regular.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-SemiBold.woff2
Normal file
BIN
static/fonts/JetBrainsMono-SemiBold.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-SemiBoldItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-Thin.woff2
Normal file
BIN
static/fonts/JetBrainsMono-Thin.woff2
Normal file
Binary file not shown.
BIN
static/fonts/JetBrainsMono-ThinItalic.woff2
Normal file
BIN
static/fonts/JetBrainsMono-ThinItalic.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,34 +0,0 @@
|
||||||
/*!
|
|
||||||
* Hack typeface https://github.com/source-foundry/Hack
|
|
||||||
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
|
|
||||||
*/
|
|
||||||
/* FONT PATHS
|
|
||||||
* -------------------------- */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-regular-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular-subset.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold-subset.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic-webfont.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic-subset.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*!
|
|
||||||
* Hack typeface https://github.com/source-foundry/Hack
|
|
||||||
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
|
|
||||||
*/
|
|
||||||
/* FONT PATHS
|
|
||||||
* -------------------------- */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-bold.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-italic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Hack';
|
|
||||||
src: url('fonts/hack-bolditalic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic.woff?sha=3114f1256') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
mono: ['"Hack"', ...defaultTheme.fontFamily.mono],
|
mono: ['"JetBrainsMono"', ...defaultTheme.fontFamily.mono],
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user