Compare commits
3 Commits
2809dc6295
...
b620536eb8
Author | SHA1 | Date | |
---|---|---|---|
b620536eb8 | |||
ad4b21daf1 | |||
0f9faa45f0 |
|
@ -2,14 +2,14 @@
|
|||
|
||||
> 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 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.
|
||||
This is a rewrite of https://nopaste.boris.sh/, which is a rewrite of https://topaz.github.io/paste/
|
||||
|
||||
[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=)
|
||||
[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)
|
||||
|
||||
## TODO
|
||||
|
||||
- [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
|
||||
- [ ] Use CodeMirror 6
|
||||
- [ ] 100% self-contained, no dependance on jsdeliver
|
||||
|
|
14
src/app.html
14
src/app.html
|
@ -3,8 +3,8 @@
|
|||
<head>
|
||||
<title>Paste</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="%sveltekit.assets%/fonts.css" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="preload" as="font" type="text/css" href="%sveltekit.assets%/hack-subset.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
|
@ -17,15 +17,5 @@ npm/codemirror@5.65.16/theme/nord.min.css"
|
|||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<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>
|
||||
</html>
|
||||
|
|
122
src/components/EditForm.svelte
Normal file
122
src/components/EditForm.svelte
Normal file
|
@ -0,0 +1,122 @@
|
|||
<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,135 +1,17 @@
|
|||
<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
|
||||
class="flex flex-wrap justify-between items-center px-3 py-1 text-sm relative z-10 shadow-md gap-2"
|
||||
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"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-xl">Paste</h1>
|
||||
<span class="ml-8 text-xs">
|
||||
<a
|
||||
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="
|
||||
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"
|
||||
target="_blank"
|
||||
>
|
||||
About</a
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<a class="ml-4" href="https://git.scambier.xyz/scambier/paste" target="_blank">Source</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#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>
|
||||
{: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}
|
||||
<slot />
|
||||
</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,7 +1 @@
|
|||
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
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// 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,4 +28,8 @@ export const shorten = (name: string) => {
|
|||
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)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import * as brotli from '$lib/brotli'
|
||||
import * as lzma from '$lib/lzma'
|
||||
import { unified } from 'unified'
|
||||
import remarkParse from 'remark-parse'
|
||||
import remarkRehype from 'remark-rehype'
|
||||
|
@ -10,27 +8,27 @@
|
|||
import rehypeStringify from 'rehype-stringify'
|
||||
import hljs from 'highlight.js'
|
||||
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 isMarkdown = false
|
||||
let isPlainText = false
|
||||
|
||||
onMount(async () => {
|
||||
let lang = new URLSearchParams(window.location.search).get('l')
|
||||
let lang = getLangFromUrl()
|
||||
lang = lang === 'mrwn' || lang === 'gflm' ? 'md' : lang // back compatiblity with old links
|
||||
|
||||
// extract the part in the url after the hash
|
||||
const hash = window.location.hash.slice(1)
|
||||
if (hash) {
|
||||
// decompress the data
|
||||
if (hash.startsWith('XQAAA')) {
|
||||
decompressed = await lzma.decompress(hash)
|
||||
} else {
|
||||
decompressed = await brotli.decompress(hash)
|
||||
}
|
||||
const { decompress } = await import('$lib/brotli')
|
||||
decompressed = await decompress(hash)
|
||||
|
||||
// Markdown
|
||||
if (lang === 'md' || lang === 'gflm') {
|
||||
if (lang === 'md') {
|
||||
const html = await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
|
@ -54,20 +52,35 @@
|
|||
window.location.href = '/editor'
|
||||
}
|
||||
})
|
||||
|
||||
function getUrlDataPart(): string {
|
||||
return window.location.search + window.location.hash
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<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}
|
||||
{@html decompressed}
|
||||
{:else if isPlainText}
|
||||
<div class="whitespace-pre-line">
|
||||
{decompressed}
|
||||
</div>
|
||||
{:else}
|
||||
<pre><code>{@html decompressed}</code></pre>
|
||||
{/if}
|
||||
</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">
|
||||
{#if isMarkdown}
|
||||
{@html decompressed}
|
||||
{:else if isPlainText}
|
||||
<div class="whitespace-pre-line">
|
||||
{decompressed}
|
||||
</div>
|
||||
{:else}
|
||||
<pre><code>{@html decompressed}</code></pre>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
|
||||
<script lang="ts">
|
||||
import type { Editor } from 'codemirror'
|
||||
import { onMount } from 'svelte'
|
||||
import { debounce } from 'lodash-es'
|
||||
import * as brotli from '$lib/brotli'
|
||||
import * as lzma from '$lib/lzma'
|
||||
import { byId } from '$lib/utils'
|
||||
import TopBar from '../../components/TopBar.svelte'
|
||||
import { selectedLang, shareUrl } from '../../store'
|
||||
import EditForm from '../../components/EditForm.svelte'
|
||||
|
||||
let editor: Editor | null = null
|
||||
const readOnly = false
|
||||
|
@ -15,33 +13,6 @@
|
|||
let charLen = 0
|
||||
let compressed: string = ''
|
||||
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() {
|
||||
if (editor) {
|
||||
|
@ -60,7 +31,7 @@
|
|||
waiting = false
|
||||
}, 1000)
|
||||
|
||||
const initCodeEditor = () => {
|
||||
async function initCodeEditor() {
|
||||
CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/%N/%N.js'
|
||||
editor = new CodeMirror(byId('editor'), {
|
||||
lineNumbers: true,
|
||||
|
@ -81,11 +52,40 @@
|
|||
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>
|
||||
|
||||
<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">
|
||||
<TopBar {editor} {updateShareUrl} bind:setLanguage />
|
||||
<TopBar>
|
||||
<EditForm {editor} {updateShareUrl} class="grow" />
|
||||
</TopBar>
|
||||
|
||||
<div id="editor" class="grow overflow-hidden" />
|
||||
|
||||
|
@ -98,13 +98,13 @@
|
|||
? '?'
|
||||
: Math.round(($shareUrl.length / charLen) * 100)}% of original)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(div.CodeMirror) {
|
||||
height: 100%;
|
||||
font-size: 15px;
|
||||
font-family: 'Hack', monospace;
|
||||
}
|
||||
#footer {
|
||||
--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'
|
||||
|
||||
export const shareUrl = writable('')
|
||||
export const selectedLang = writable<string | null>(null)
|
||||
export const selectedLang = writable<string>('plt')
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.2 KiB |
1
static/favicon.svg
Normal file
1
static/favicon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 915 B |
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.
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.
BIN
static/fonts/hack-bold-subset.woff
Normal file
BIN
static/fonts/hack-bold-subset.woff
Normal file
Binary file not shown.
BIN
static/fonts/hack-bold-subset.woff2
Normal file
BIN
static/fonts/hack-bold-subset.woff2
Normal file
Binary file not shown.
BIN
static/fonts/hack-bolditalic-subset.woff
Normal file
BIN
static/fonts/hack-bolditalic-subset.woff
Normal file
Binary file not shown.
BIN
static/fonts/hack-bolditalic-subset.woff2
Normal file
BIN
static/fonts/hack-bolditalic-subset.woff2
Normal file
Binary file not shown.
BIN
static/fonts/hack-italic-subset.woff
Normal file
BIN
static/fonts/hack-italic-subset.woff
Normal file
Binary file not shown.
BIN
static/fonts/hack-italic-subset.woff2
Normal file
BIN
static/fonts/hack-italic-subset.woff2
Normal file
Binary file not shown.
BIN
static/fonts/hack-regular-subset.woff
Normal file
BIN
static/fonts/hack-regular-subset.woff
Normal file
Binary file not shown.
BIN
static/fonts/hack-regular-subset.woff2
Normal file
BIN
static/fonts/hack-regular-subset.woff2
Normal file
Binary file not shown.
34
static/hack-subset.css
Normal file
34
static/hack-subset.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*!
|
||||
* 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;
|
||||
}
|
||||
|
34
static/hack.css
Normal file
34
static/hack.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*!
|
||||
* 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: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
mono: ['"JetBrainsMono"', ...defaultTheme.fontFamily.mono],
|
||||
mono: ['"Hack"', ...defaultTheme.fontFamily.mono],
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
|
|
Loading…
Reference in New Issue
Block a user