Preview & editor

master
Simon Cambier 6 months ago
parent 2809dc6295
commit 0f9faa45f0

@ -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 />

@ -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,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

@ -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

@ -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;
}

@ -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…
Cancel
Save