This commit is contained in:
2023-10-29 17:25:22 +01:00
parent 906ead2d9a
commit 36bc353ff5
35 changed files with 1056 additions and 145 deletions
+32 -9
View File
@@ -1,12 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="stylesheet" type="text/css" href="%sveltekit.assets%/fonts.css" />
<link
rel="stylesheet"
type="text/css"
href="https://cdn.jsdelivr.net/combine/
npm/bootstrap@4.6.1/dist/css/bootstrap-grid.min.css,
npm/codemirror@5.65.5/lib/codemirror.min.css,
npm/codemirror@5.65.5/addon/scroll/simplescrollbars.css,
npm/codemirror@5.65.5/theme/dracula.min.css,
npm/microtip@0.2.2/microtip.min.css"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents" class="h-screen text-gray-300">%sveltekit.body%</div>
<script src="https://cdn.jsdelivr.net/combine/
npm/lzma@2.3.2/src/lzma.min.js,
npm/clipboard@2.0.11/dist/clipboard.min.js,
npm/micromodal@0.4.10/dist/micromodal.min.js,
npm/codemirror@5.65.5,
npm/codemirror@5.65.5/addon/mode/loadmode.min.js,
npm/codemirror@5.65.5/addon/mode/overlay.min.js,
npm/codemirror@5.65.5/addon/mode/multiplex.min.js,
npm/codemirror@5.65.5/addon/mode/simple.min.js,
npm/codemirror@5.65.5/addon/scroll/simplescrollbars.js,
npm/codemirror@5.65.5/mode/meta.min.js"></script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
/* App layout */
+20
View File
@@ -0,0 +1,20 @@
<script lang="ts">
type Item = { text: string }
export let value: Item | null = null
export let items: Item[]
</script>
<select bind:value={value} class={ ($$restProps.class ?? '') }>
{#each items as item}
<option value={item}>
{item.text}
</option>
{/each}
</select>
<style lang="scss">
select {
appearance: none;
}
</style>
+121
View File
@@ -0,0 +1,121 @@
<script lang="ts">
import { onMount, tick } from 'svelte'
import { shorten } from '$lib/utils'
import Select from './Select.svelte'
import type { Editor } from 'codemirror'
type Language = {
text: string
value: string
data: { mime: string; mode: string }
}
export let editor: Editor
let languages: Language[] = []
let selectedLanguage: Language | null = null
let urlInput: HTMLInputElement
let isUrlInputVisible = false
$: setDefaultLanguage(languages)
$: {
const language = selectedLanguage?.data ?? { mime: null, mode: null }
// @ts-ignore
editor.setOption('mode', language.mime)
CodeMirror.autoLoadMode(editor, language.mode)
}
// const initLangSelector = () => {
// select = new SlimSelect({
// select: '#language',
// data: CodeMirror.modeInfo.map((e) => ({
// text: e.name,
// value: shorten(e.name),
// data: { mime: e.mime, mode: e.mode },
// })),
// settings: {
// openPosition: 'down',
// },
// events: {
// afterChange: (e) => {
// console.log(e)
// // const language = e.data || { mime: null, mode: null }
// // editor.setOption('mode', language.mime)
// // CodeMirror.autoLoadMode(editor, language.mode)
// // document.title =
// // e.text && e.text !== 'Plain Text' ? `NoPaste - ${e.text} code snippet` : 'NoPaste'
// },
// },
// })
// // Set lang selector
// const l = new URLSearchParams(window.location.search).get('l')
// }
onMount(() => {
languages = CodeMirror.modeInfo.map((e) => ({
text: e.name,
value: shorten(e.name),
data: { mime: e.mime, mode: e.mode },
}))
// initLangSelector()
})
function setDefaultLanguage(languages: Language[]) {
const lang = languages.find((e) => e.text.toLowerCase() === 'plain text')!
selectedLanguage = lang
}
async function showUrlInput() {
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 justify-between p-2 text-sm relative">
<div>NoPaste</div>
<div class="flex justify-end gap-2">
<div>
<Select
items={languages}
bind:value={selectedLanguage}
class="bg-gray-700 border border-gray-300 p-1"
/>
</div>
<div>
<button on:click={showUrlInput}> Generate Link </button>
</div>
</div>
{#if isUrlInputVisible}
<div class="absolute w-full h-full bg-gray-700 top-0 left-0 p-2">
<div class="flex gap-2">
<input
bind:this={urlInput}
type="text"
class="border border-gray-300 bg-transparent p-1 grow"
value="url goes here"
/>
<button on:click={copyUrl}> Copy </button>
<button on:click={closeUrlInput}> Close </button>
</div>
</div>
{/if}
</div>
<style lang="scss">
button {
@apply text-sm border border-gray-300 p-1 hover:bg-gray-600/50;
}
</style>
+7
View File
@@ -0,0 +1,7 @@
declare class LZMA {
constructor(any): {}
compress: (value: string, level: number, callback: (result: number[]) => void) => void
decompress: (b: string, callback: (result: any) => void) => void
}
declare const CodeMirror: any
+20
View File
@@ -0,0 +1,20 @@
import { compress as brotliCompress, decompress as brotliDecompress } from 'brotli-compress'
const textEncoder = new TextEncoder()
const textDecoder = new TextDecoder()
async function compress(value: string): Promise<string> {
const data = textEncoder.encode(value)
const compressed = await brotliCompress(data)
return btoa(String.fromCharCode(...compressed))
}
async function decompress(b: string): Promise<string> {
const data = atob(b)
const bytes = Uint8Array.from(data, (c) => c.charCodeAt(0))
const decompressed = await brotliDecompress(bytes)
const decoded = textDecoder.decode(decompressed)
return decoded
}
export { compress, decompress }
+23
View File
@@ -0,0 +1,23 @@
// 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(b: string): Promise<string> {
return new Promise((resolve, reject) => {
lzma.decompress(b, (result: any) => {
resolve(result)
})
})
}
export { compress, decompress }
+31
View File
@@ -0,0 +1,31 @@
export const slugify = (str: string) =>
str
.trim()
.toString()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/\+/g, '-p')
.replace(/#/g, '-sharp')
.replace(/[^\w\-]+/g, '')
export const shorten = (name: string) => {
let n = slugify(name).replace('script', '-s').replace('python', 'py')
const nov = (s: string) => s[0] + s.substr(1).replace(/[aeiouy-]/g, '')
if (n.replace(/-/g, '').length <= 4) {
return n.replace(/-/g, '')
}
if (n.split('-').length >= 2) {
return n
.split('-')
.map((x) => nov(x.substr(0, 2)))
.join('')
.substr(0, 4)
}
n = nov(n)
if (n.length <= 4) {
return n
}
return n.substr(0, 2) + n.substr(n.length - 2, 2)
}
export const byId = (id: string) => document.getElementById(id)
+5
View File
@@ -0,0 +1,5 @@
<script lang="ts">
import '../app.postcss'
</script>
<slot />
+2
View File
@@ -0,0 +1,2 @@
export const prerender = true
export const ssr = false
+82 -2
View File
@@ -1,2 +1,82 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<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'
const algorithm: {
compress: (data: string) => Promise<string>
decompress: (data: string) => Promise<string>
} = brotli
const host = window.location.protocol + '//' + window.location.host + '/'
let editor: Editor | null = null
const readOnly = false
let charLen = 0
let compressed: string = ''
let waiting = false
$: shareUrl = host + compressed
const updateCompressed = debounce(async () => {
if (editor) {
compressed = await algorithm.compress(editor.getValue())
console.log(compressed)
}
waiting = false
}, 1000)
const initCodeEditor = () => {
CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@5.65.5/mode/%N/%N.js'
editor = new CodeMirror(byId('editor'), {
lineNumbers: true,
theme: 'dracula',
readOnly: readOnly,
lineWrapping: false,
scrollbarStyle: 'native',
}) as Editor
console.log(editor)
if (readOnly) {
document.body.classList.add('readonly')
}
editor.on('change', async () => {
if (editor) {
waiting = true
charLen = editor.getValue().length
updateCompressed()
}
})
}
onMount(async () => {
initCodeEditor()
})
</script>
<div class="flex flex-col font-mono h-screen bg-gray-700">
{#if !!editor}
<TopBar {editor} />
{/if}
<div id="editor" class="grow overflow-hidden" />
{editor?.getValue()}
<div class="p-2 text-sm">
Data length: {charLen}
| Link length: {waiting ? '?' : shareUrl.length}
({waiting || !compressed.length || !charLen
? '?'
: Math.round((shareUrl.length / charLen) * 100)}% of original)
</div>
</div>
<style>
:global(div.CodeMirror) {
height: 100%;
}
</style>