Skip to content

Commit 54b9a87

Browse files
committed
feat: highlighting and better persistence
1 parent 4123bdf commit 54b9a87

File tree

5 files changed

+85
-67
lines changed

5 files changed

+85
-67
lines changed

src/components/editor-rich.vue

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
<template>
2-
<div
3-
name=""
4-
ref="texteditor"
5-
autofocus="true"
6-
class="flex-1 w-full code-editor"
7-
id="editor"
8-
></div>
2+
<div name="" ref="texteditor" autofocus="true" class="flex-1 w-full code-editor" id="editor"></div>
93
</template>
104
<script>
115
import "quill/dist/quill.bubble.css";
126
import "quilljs-markdown/dist/quilljs-markdown-common-style.css";
7+
import hljs from "highlight.js";
138
149
import Quill from "quill";
1510
import QuillMarkdown from "quilljs-markdown";
@@ -21,6 +16,7 @@ export default {
2116
name: "EditorRich",
2217
emits: ["change"],
2318
props: {
19+
opsState: String,
2420
initialCode: String,
2521
},
2622
setup(props, { emit }) {
@@ -29,20 +25,45 @@ export default {
2925
onMounted(() => {
3026
quill = new Quill("#editor", {
3127
theme: "bubble",
28+
modules: {
29+
syntax: {
30+
hljs,
31+
},
32+
toolbar: [
33+
["bold", "italic", "underline", "strike"],
34+
["blockquote", "code-block"],
35+
],
36+
},
3237
});
3338
3439
// enable markdown conversion
35-
new QuillMarkdown(quill, {});
40+
new QuillMarkdown(quill, {
41+
debug: true,
42+
});
3643
3744
const converter = new MarkdownToQuill({});
38-
const ops = converter.convert(props.initialCode);
45+
let ops = [];
46+
47+
try {
48+
ops = JSON.parse(props.opsState);
49+
} catch (err) {
50+
// Migration change to move from
51+
// storing markdown to quill delta
52+
// if a syntax error is found, try converting it
53+
if (err instanceof SyntaxError) {
54+
ops = converter.convert(props.opsState);
55+
}
56+
}
57+
3958
quill.setContents(ops);
4059
4160
quill.on("text-change", () => {
42-
const markdownCode = deltaToMarkdown(quill.getContents().ops);
43-
console.log({ deltas: quill.getContents().ops });
44-
console.log({ markdownCode });
45-
emit("change", markdownCode);
61+
const { ops } = quill.getContents();
62+
const markdownCode = deltaToMarkdown(ops);
63+
emit("change", {
64+
code: markdownCode,
65+
ops: JSON.stringify(ops),
66+
});
4667
});
4768
4869
if (texteditor.value) {

src/lib/marked/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ const renderer = {
3232
);
3333
}
3434

35+
const langPrefix = this.options.langPrefix || "";
3536
return (
3637
'<pre class="codeblock ' +
37-
this.options.langPrefix +
38+
langPrefix +
3839
escape(lang, true) +
3940
'">' +
4041
(escaped ? code : escape(code, true)) +

src/lib/quill/delta-md.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
22
import TurndownService from "turndown";
3+
const turndownService = new TurndownService({
4+
codeBlockStyle: "fenced",
5+
});
36

4-
const turndownService = new TurndownService();
7+
turndownService.addRule("code-fence-language", {
8+
filter: ["pre"],
9+
replacement(content, node) {
10+
const lang = node.dataset.language || "";
11+
return "```" + lang + "\n" + content + "\n```";
12+
},
13+
});
514

615
export const htmlToMarkdown = (html) => turndownService.turndown(html);
716

@@ -10,8 +19,9 @@ export const deltaToMarkdown = (delta) => {
1019
html.beforeRender((groupType, data) => {
1120
if (groupType !== "block") return;
1221
if (!(data.op && "code-block" in data.op.attributes)) return;
13-
14-
return `<pre><code>${data.ops
22+
return `<pre data-language="${
23+
data.op.attributes["code-block"]
24+
}"><code>${data.ops
1525
.map((d) => {
1626
return d.insert.value;
1727
})

src/pages/Home.vue

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,26 @@
11
<template>
22
<BaseLayout>
33
<Toast ref="toastRef" />
4-
<Editor v-on:change="handleChange" v-bind:initialCode="state.code"></Editor>
4+
<Editor v-on:change="handleChange" v-bind:initialCode="state.code" v-bind:opsState="state.opsFromStorage"></Editor>
55
<Toolbar>
66
<Menu triggerLabel="Menu">
7-
<MenuItem label="Copy as Markdown" @click="handleCopyAsMD"
8-
modifier="⌘ + ⇧ + c"
9-
/>
7+
<MenuItem label="Copy as Markdown" @click="handleCopyAsMD" modifier="⌘ + ⇧ + c" />
108
<MenuItem label="Copy as HTML" @click="handleCopyAsHTML" />
119
<MenuItem label="Save File" modifier="⌘ + s" @click="handleSaveFile" />
12-
<MenuItem
13-
label="Save File as HTML"
14-
modifier="⌘ + ⇧ + s "
15-
@click="handleSaveAsHTML"
16-
/>
10+
<MenuItem label="Save File as HTML" modifier="⌘ + ⇧ + s " @click="handleSaveAsHTML" />
1711
<MenuItem label="Save File as PDF" @click="handleSaveAsPDF" />
1812
<MenuItem label="Save File as Image" @click="handleSaveAsImage" />
1913
</Menu>
2014
<div class="flex align-center">
21-
<Button
22-
class="trigger ghost"
23-
v-bind:class="{ active: state.copied }"
24-
@click="handleCopyAsHTML"
25-
>
26-
<svg
27-
v-if="!state.copied"
28-
xmlns="http://www.w3.org/2000/svg"
29-
width="24"
30-
height="24"
31-
viewBox="0 0 24 24"
32-
stroke-width="1.5"
33-
stroke="currentColor"
34-
fill="none"
35-
stroke-linecap="round"
36-
stroke-linejoin="round"
37-
>
15+
<Button class="trigger ghost" v-bind:class="{ active: state.copied }" @click="handleCopyAsHTML">
16+
<svg v-if="!state.copied" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
17+
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
3818
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
3919
<rect x="8" y="8" width="12" height="12" rx="2"></rect>
40-
<path
41-
d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"
42-
></path>
20+
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path>
4321
</svg>
44-
<svg
45-
v-if="state.copied"
46-
xmlns="http://www.w3.org/2000/svg"
47-
width="24"
48-
height="24"
49-
viewBox="0 0 24 24"
50-
stroke-width="1.5"
51-
stroke="currentColor"
52-
fill="none"
53-
stroke-linecap="round"
54-
stroke-linejoin="round"
55-
>
22+
<svg v-if="state.copied" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
23+
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
5624
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
5725
<circle cx="12" cy="12" r="9"></circle>
5826
<path d="M9 12l2 2l4 -4"></path>
@@ -82,22 +50,32 @@ import html2pdf from "html2pdf.js";
8250
import getMDStyles from "../lib/get-md-styles";
8351
import toImage from "dom-to-image";
8452
import download from "downloadjs";
53+
import { deltaToMarkdown } from "../lib/quill/delta-md.js";
8554
8655
const toastRef = ref(null);
8756
8857
const STORAGE_TOKEN = Symbol("reaper-mark").toString();
8958
9059
const getDefaultCode = () => {
91-
const existingCode = localStorage.getItem(STORAGE_TOKEN);
92-
if (existingCode && existingCode.length) {
93-
return existingCode;
60+
const existingState = localStorage.getItem(STORAGE_TOKEN);
61+
try {
62+
const ops = JSON.parse(existingState || [])
63+
const markdownText = deltaToMarkdown(ops)
64+
return markdownText;
65+
} catch (err) {
66+
return defaultMarkdownText
9467
}
95-
return defaultMarkdownText;
68+
};
69+
70+
const getFromStorage = () => {
71+
const existingState = localStorage.getItem(STORAGE_TOKEN) || [];
72+
return existingState;
9673
};
9774
9875
const state = reactive({
9976
copied: false,
10077
code: getDefaultCode(),
78+
opsFromStorage: getFromStorage(),
10179
});
10280
10381
onMounted(() => {
@@ -125,17 +103,17 @@ function shortcutListener(e) {
125103
}
126104
}
127105
128-
function handleChange(code) {
106+
function handleChange({ code, ops }) {
129107
state.code = code;
130-
localStorage.setItem(STORAGE_TOKEN, code);
108+
localStorage.setItem(STORAGE_TOKEN, ops);
131109
}
132110
133-
async function handleCopyAsMD(){
111+
async function handleCopyAsMD() {
134112
if (!state.code) {
135113
return;
136114
}
137-
await copy(state.code)
138-
state.copied = true
115+
await copy(state.code);
116+
state.copied = true;
139117
setTimeout(() => {
140118
state.copied = false;
141119
}, 2500);

src/styles/index.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,11 @@ a {
162162
background: var(--overlay);
163163
color: var(--text);
164164
}
165+
166+
.ql-code-block-container .ql-ui {
167+
background: var(--surface);
168+
padding: 4px 8px;
169+
color: var(--text);
170+
border:0px;
171+
border-radius:4px;
172+
}

0 commit comments

Comments
 (0)