Skip to content

Commit c081610

Browse files
feat: Wysiwyg editor (#73)
Co-authored-by: amandesai01 <[email protected]>
1 parent 8d28ab1 commit c081610

File tree

6 files changed

+825
-592
lines changed

6 files changed

+825
-592
lines changed

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
"@nuxtjs/tailwindcss": "6.12.1",
1818
"@profilecity/unstorage-s3-driver": "^0.0.3",
1919
"@tailwindcss/forms": "^0.5.7",
20+
"@types/quill": "^2.0.14",
2021
"@vee-validate/nuxt": "^4.13.1",
2122
"@vee-validate/zod": "^4.13.1",
22-
"@vueuse/core": "^10.11.1",
23+
"@vueuse/core": "^10.11.0",
2324
"drizzle-kit": "^0.22.7",
2425
"drizzle-orm": "^0.31.2",
2526
"jsonwebtoken": "^9.0.2",
@@ -28,10 +29,11 @@
2829
"nuxt-cropper": "^0.0.4",
2930
"nuxt-icon": "^0.6.10",
3031
"pg": "^8.12.0",
32+
"quill": "^2.0.2",
3133
"uncrypto": "^0.1.3",
3234
"unstorage": "^1.10.2",
3335
"vue": "^3.4.27",
34-
"vue-router": "^4.4.3",
36+
"vue-router": "^4.3.2",
3537
"zod": "^3.23.8"
3638
},
3739
"devDependencies": {

src/assets/css/style.css

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
/* Additional Tailwind directives: https://tailwindcss.com/docs/functions-and-directives/#responsive */
1010
@layer utilities {
11-
.rtl {
12-
direction: rtl;
13-
}
11+
.rtl {
12+
direction: rtl;
13+
}
1414
}
15-

src/components/Editor.vue

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<template>
2+
<div id="snow-wrapper">
3+
<div id="snow-container">
4+
<div class="toolbar bg-white rounded-t-xl mt-2" v-if="!readOnly">
5+
<span class="ql-formats">
6+
<select class="ql-header" defaultValue="3">
7+
<option value="1">Heading</option>
8+
<option value="2">Subheading</option>
9+
<option value="3">Normal</option>
10+
</select>
11+
</span>
12+
<span class="ql-formats">
13+
<button class="ql-bold"></button>
14+
<button class="ql-italic"></button>
15+
<button class="ql-underline"></button>
16+
</span>
17+
<span class="ql-formats">
18+
<button class="ql-list" value="ordered"></button>
19+
<button class="ql-list" value="bullet"></button>
20+
<select class="ql-align" defaultValue="false">
21+
<option label="left"></option>
22+
<option label="center" value="center"></option>
23+
<option label="right" value="right"></option>
24+
<option label="justify" value="justify"></option>
25+
</select>
26+
</span>
27+
<span class="ql-formats">
28+
<button class="ql-link"></button>
29+
</span>
30+
<span class="ql-formats">
31+
<button class="ql-clean"></button>
32+
</span>
33+
</div>
34+
<div class="bg-white rounded-b-xl border p-4" :class="readOnly ? 'ql-e-blank' : ''" :id="editorId"></div>
35+
</div>
36+
</div>
37+
</template>
38+
39+
<script setup lang="ts">
40+
import { useVModel } from "@vueuse/core";
41+
import type Quill from 'quill';
42+
43+
const props = withDefaults(defineProps<{
44+
modelValue: string;
45+
placeholder: string;
46+
id?: string;
47+
readOnly?: boolean;
48+
}>(), {
49+
id: 'vidur-editor',
50+
readOnly: false,
51+
});
52+
const emit = defineEmits<{
53+
'update:modelValue': [],
54+
}>();
55+
56+
const editorContent = useVModel(props, 'modelValue', emit);
57+
const editorId = props.id;
58+
let editorInstance: Quill | null;
59+
60+
onMounted(async () => {
61+
const QuillEditor = (await import('quill')).default;
62+
63+
editorInstance = new QuillEditor(`#${editorId}`, {
64+
bounds: '#snow-container .ql-container',
65+
modules: {
66+
// syntax: true, TODO: ref: https://quilljs.com/docs/modules/syntax
67+
toolbar: props.readOnly ? false : '#snow-container .toolbar',
68+
},
69+
placeholder: props.placeholder,
70+
theme: 'snow',
71+
readOnly: props.readOnly,
72+
});
73+
74+
if (editorContent.value) {
75+
editorInstance.root.innerHTML = editorContent.value;
76+
}
77+
78+
editorInstance.on('text-change', () => {
79+
if (!editorInstance) return;
80+
editorContent.value = editorInstance.root.innerHTML;
81+
});
82+
});
83+
84+
onUnmounted(() => {
85+
editorInstance = null;
86+
});
87+
</script>
88+
89+
<style>
90+
@import 'quill/dist/quill.snow.css';
91+
92+
.ql-editor {
93+
padding: 0% !important;
94+
border: 0px !important;
95+
}
96+
97+
.ql-e-blank {
98+
padding: 0% !important;
99+
border: 0px !important;
100+
}
101+
</style>

src/pages/admin/postings/edit.vue

+13-3
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,22 @@ const onDelete = async () => {
155155
</div>
156156
<div class="mt-4">
157157
<label class="block text-sm font-medium mb-1" for="jobdescription">Job Description</label>
158-
<textarea id="jobdescription" class="form-textarea w-full focus:border-zinc-300" rows="6" v-model="contents"
159-
placeholder="We want someone who…" :disabled="isSubmitting"></textarea>
158+
<ClientOnly>
159+
<Editor placeholder="We are looking for someone who can..." v-model="contents" />
160+
<template #fallback>
161+
Loading editor...
162+
</template>
163+
</ClientOnly>
160164
<div class="text-xs mt-1 text-rose-500">{{ errors.contents }}</div>
161165
</div>
162166
</div>
163167
</div>
164168
</form>
165169
</div>
166-
</template>
170+
</template>
171+
172+
<style>
173+
h1 {
174+
@apply text-4xl;
175+
}
176+
</style>

src/pages/postings/[id].vue

+7-5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const apply = async () => {
4242
isApplying.value = false;
4343
}
4444
}
45+
4546
</script>
4647

4748
<template>
@@ -97,11 +98,8 @@ const apply = async () => {
9798
</div>
9899
</div>
99100
</div>
100-
101101
<hr class="my-6 border-t border-zinc-100" />
102-
103-
<p class="w-full" style="white-space: pre-line;">{{ posting.contents }}</p>
104-
102+
<Editor :read-only="true" v-model="posting.contents"/>
105103
</div>
106104

107105
<!-- Sidebar -->
@@ -145,4 +143,8 @@ const apply = async () => {
145143
</a>
146144
</div>
147145
</div>
148-
</template>
146+
</template>
147+
148+
<style>
149+
@import 'quill/dist/quill.snow.css';
150+
</style>

0 commit comments

Comments
 (0)