awareness on quill editor
This commit is contained in:
parent
27e3ed525b
commit
e9e38db606
5 changed files with 79 additions and 10 deletions
|
@ -35,6 +35,14 @@ export default defineComponent({
|
||||||
localStorage.removeItem("refresh_token");
|
localStorage.removeItem("refresh_token");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
window.addEventListener("online", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener("online", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,14 @@
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-row gap-2 items-center">
|
<div class="flex flex-row gap-2 items-center">
|
||||||
<label :for="title">{{ title }}</label>
|
<label :for="title">{{ title }}</label>
|
||||||
<lottie-player src="/typing_animation.json" class="w-fit h-5" loop autoplay />
|
<lottie-player
|
||||||
|
v-if="currentEditors.length != 0"
|
||||||
|
src="/typing_animation.json"
|
||||||
|
class="w-fit h-5"
|
||||||
|
loop
|
||||||
|
autoplay
|
||||||
|
v-tippy="currentEditors.map((c) => c.username).join(', ')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<QuillEditor
|
<QuillEditor
|
||||||
:id="title"
|
:id="title"
|
||||||
|
@ -11,6 +18,8 @@
|
||||||
contentType="html"
|
contentType="html"
|
||||||
:options="{ modules: moduleOptions }"
|
:options="{ modules: moduleOptions }"
|
||||||
@ready="initEditor"
|
@ready="initEditor"
|
||||||
|
@selectionChange="selectionChange"
|
||||||
|
@blur="blured"
|
||||||
/>
|
/>
|
||||||
<!--
|
<!--
|
||||||
:enable="can('create', 'operation', 'mission')"
|
:enable="can('create', 'operation', 'mission')"
|
||||||
|
@ -28,7 +37,7 @@ import { QuillBinding } from "y-quill";
|
||||||
import { moduleOptions } from "@/helpers/quillConfig";
|
import { moduleOptions } from "@/helpers/quillConfig";
|
||||||
import * as Y from "yjs";
|
import * as Y from "yjs";
|
||||||
import "@lottiefiles/lottie-player";
|
import "@lottiefiles/lottie-player";
|
||||||
import { Awareness } from "@/helpers/awareness";
|
import { Awareness, type AwarenessActions, type EditorRange, type EditorState } from "@/helpers/awareness";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -39,7 +48,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
awareness: {
|
awareness: {
|
||||||
type: Object as PropType<Partial<Awareness>>,
|
type: Object as PropType<Awareness>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
@ -53,18 +62,64 @@ export default defineComponent({
|
||||||
cursors: undefined as undefined | QuillCursors,
|
cursors: undefined as undefined | QuillCursors,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
currentEditors() {
|
||||||
|
return this.awareness.getEditorObjsByField(this.title);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.awareness.emitter.on("change", this.handleChange);
|
||||||
|
this.awareness.emitter.on("clear", this.clear);
|
||||||
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.binding) {
|
if (this.binding) {
|
||||||
this.binding.destroy();
|
this.binding.destroy();
|
||||||
this.binding = undefined;
|
this.binding = undefined;
|
||||||
}
|
}
|
||||||
|
this.awareness.emitter.off("change", this.handleChange);
|
||||||
|
this.awareness.emitter.off("clear", this.clear);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initEditor(quill: Quill) {
|
initEditor(quill: Quill) {
|
||||||
quill.history.clear();
|
quill.history.clear();
|
||||||
this.binding = new QuillBinding(this.text, quill); // TODO: awareness
|
this.binding = new QuillBinding(this.text, quill);
|
||||||
this.cursors = quill.getModule("cursors") as QuillCursors;
|
this.cursors = quill.getModule("cursors") as QuillCursors;
|
||||||
},
|
},
|
||||||
|
selectionChange({ range, oldRange, source }: { range: EditorRange; oldRange: any; source: string }) {
|
||||||
|
if (range != null) {
|
||||||
|
this.awareness.publishMyState({
|
||||||
|
field: this.title,
|
||||||
|
range: range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blured() {
|
||||||
|
this.awareness.publishMyState({
|
||||||
|
field: "blured user",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleChange(
|
||||||
|
d: {
|
||||||
|
socketId: string;
|
||||||
|
action: AwarenessActions;
|
||||||
|
} & EditorState
|
||||||
|
) {
|
||||||
|
if (d.action == "remove") {
|
||||||
|
this.cursors?.removeCursor(d.socketId);
|
||||||
|
}
|
||||||
|
if (d.field == this.title) {
|
||||||
|
let user = this.awareness.getEditor(d.socketId);
|
||||||
|
if (!user || !d.range) return;
|
||||||
|
this.cursors?.createCursor(d.socketId, user.username, user.color);
|
||||||
|
this.cursors?.moveCursor(d.socketId, d.range);
|
||||||
|
this.cursors?.toggleFlag(d.socketId, true);
|
||||||
|
} else if (d.field == "blured user") {
|
||||||
|
this.cursors?.removeCursor(d.socketId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.cursors?.clearCursors();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -75,15 +75,11 @@ export default defineComponent({
|
||||||
focused() {
|
focused() {
|
||||||
this.awareness.publishMyState({
|
this.awareness.publishMyState({
|
||||||
field: this.title,
|
field: this.title,
|
||||||
cursor: undefined,
|
|
||||||
range: undefined,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
blured() {
|
blured() {
|
||||||
this.awareness.publishMyState({
|
this.awareness.publishMyState({
|
||||||
field: "blured user",
|
field: "blured user",
|
||||||
cursor: undefined,
|
|
||||||
range: undefined,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,8 +9,12 @@ export interface Editor {
|
||||||
|
|
||||||
export interface EditorState {
|
export interface EditorState {
|
||||||
field: string;
|
field: string;
|
||||||
cursor: any;
|
cursor?: number;
|
||||||
range: any;
|
range?: EditorRange;
|
||||||
|
}
|
||||||
|
export interface EditorRange {
|
||||||
|
index: number;
|
||||||
|
length: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AwarenessActions = "update" | "remove";
|
export type AwarenessActions = "update" | "remove";
|
||||||
|
@ -18,6 +22,7 @@ export type AwarenessActions = "update" | "remove";
|
||||||
export type AwarenessEvents = {
|
export type AwarenessEvents = {
|
||||||
update: { data: EditorState };
|
update: { data: EditorState };
|
||||||
change: { socketId: string; action: AwarenessActions } & EditorState;
|
change: { socketId: string; action: AwarenessActions } & EditorState;
|
||||||
|
clear: void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Awareness {
|
export class Awareness {
|
||||||
|
@ -73,6 +78,7 @@ export class Awareness {
|
||||||
public reset() {
|
public reset() {
|
||||||
this.editors.clear();
|
this.editors.clear();
|
||||||
this.editorStates.clear();
|
this.editorStates.clear();
|
||||||
|
this.emitter.emit("clear");
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
|
|
@ -149,3 +149,7 @@ summary::-webkit-details-marker {
|
||||||
@apply !order-1;
|
@apply !order-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ql-cursor-caret-container {
|
||||||
|
z-index: 1 !important;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue