Preview & editor
This commit is contained in:
+4
-12
@@ -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"
|
||||
@@ -14,18 +14,10 @@ npm/codemirror@5.65.16/theme/nord.min.css"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/combine/npm/lzma@2.3.2/src/lzma.min.js"></script>
|
||||
</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>
|
||||
|
||||
@@ -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#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"
|
||||
>
|
||||
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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
import '../app.postcss'
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<slot />
|
||||
+14
-2
@@ -10,13 +10,16 @@
|
||||
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
|
||||
@@ -30,7 +33,7 @@
|
||||
}
|
||||
|
||||
// Markdown
|
||||
if (lang === 'md' || lang === 'gflm') {
|
||||
if (lang === 'md') {
|
||||
const html = await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
@@ -54,9 +57,18 @@
|
||||
window.location.href = '/editor'
|
||||
}
|
||||
})
|
||||
|
||||
function getUrlDataPart(): string {
|
||||
return window.location.search + window.location.hash
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<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}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
|
||||
<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 +14,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 +32,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 +53,45 @@
|
||||
updateShareUrlDebounced()
|
||||
}
|
||||
})
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
$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 +104,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
-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')
|
||||
|
||||
Reference in New Issue
Block a user