diff --git a/js/package.json b/js/package.json
index 58f6790dd..ceb5ab3c4 100644
--- a/js/package.json
+++ b/js/package.json
@@ -48,6 +48,7 @@
"leaflet.locatecontrol": "^0.73.0",
"lodash": "^4.17.11",
"ngeohash": "^0.6.3",
+ "p-debounce": "^4.0.0",
"phoenix": "^1.4.11",
"register-service-worker": "^1.7.1",
"tippy.js": "^6.2.3",
diff --git a/js/src/components/Editor.vue b/js/src/components/Editor.vue
index a338f9a18..1ede50769 100644
--- a/js/src/components/Editor.vue
+++ b/js/src/components/Editor.vue
@@ -6,206 +6,173 @@
id="tiptab-editor"
:data-actor-id="currentActor && currentActor.id"
>
-
-
-
-
-
-
-
- {{ $t("No profiles found") }}
-
-
@@ -216,8 +183,6 @@ import { defaultExtensions } from "@tiptap/starter-kit";
import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph";
import Text from "@tiptap/extension-text";
-import tippy, { Instance, sticky } from "tippy.js";
-// import { SEARCH_PERSONS } from "../graphql/search";
import { Actor, IActor, IPerson } from "../types/actor";
import CustomImage from "./Editor/Image";
import { UPLOAD_MEDIA } from "../graphql/upload";
@@ -251,19 +216,6 @@ export default class EditorComponent extends Vue {
editor: Editor | null = null;
- /**
- * Editor Suggestions
- */
- query!: string | null;
-
- filteredActors: IActor[] = [];
-
- suggestionRange!: Record | null;
-
- navigatedActorIndex = 0;
-
- popup!: Instance[] | null;
-
get isDescriptionMode(): boolean {
return this.mode === "description" || this.isBasicMode;
}
@@ -276,14 +228,6 @@ export default class EditorComponent extends Vue {
return this.isBasicMode;
}
- get hasResults(): boolean {
- return this.filteredActors.length > 0;
- }
-
- get showSuggestions(): boolean {
- return (this.query || this.hasResults) as boolean;
- }
-
get isBasicMode(): boolean {
return this.mode === "basic";
}
@@ -312,11 +256,11 @@ export default class EditorComponent extends Vue {
}),
...defaultExtensions(),
],
- onUpdate: ({ editor }) => {
- this.$emit("input", editor.getHTML());
+ content: this.value,
+ onUpdate: () => {
+ this.$emit("input", this.editor?.getHTML());
},
});
- this.editor.commands.setContent(this.value);
}
@Watch("value")
@@ -327,8 +271,10 @@ export default class EditorComponent extends Vue {
}
}
- // eslint-disable-next-line @typescript-eslint/ban-types
- showLinkMenu(): Function | undefined {
+ /**
+ * Show a popup to get the link from the URL
+ */
+ showLinkMenu(): void {
this.$buefy.dialog.prompt({
message: this.$t("Enter the link URL") as string,
inputAttrs: {
@@ -340,106 +286,11 @@ export default class EditorComponent extends Vue {
this.editor.chain().focus().setLink({ href: value }).run();
},
});
- return undefined;
- }
-
- upHandler(): void {
- this.navigatedActorIndex =
- (this.navigatedActorIndex + this.filteredActors.length - 1) %
- this.filteredActors.length;
- }
-
- /**
- * navigate to the next item
- * if it's the last item, navigate to the first one
- */
- downHandler(): void {
- this.navigatedActorIndex =
- (this.navigatedActorIndex + 1) % this.filteredActors.length;
- }
-
- enterHandler(): void {
- const actor = this.filteredActors[this.navigatedActorIndex];
- if (actor) {
- this.selectActor(actor);
- }
- }
-
- /**
- * we have to replace our suggestion text with a mention
- * so it's important to pass also the position of your suggestion text
- * @param actor IActor
- */
- selectActor(actor: IActor): void {
- const actorModel = new Actor(actor);
- this.insertMention({
- range: this.suggestionRange,
- attrs: {
- id: actorModel.id,
- // usernameWithDomain returns with a @ prefix and tiptap adds one itself
- label: actorModel.usernameWithDomain().substring(1),
- },
- });
- if (!this.editor) return;
- this.editor.commands.focus();
- }
-
- /** We use this to programatically insert an actor mention when creating a reply to comment */
- replyToComment(comment: IComment): void {
- if (!comment.actor) return;
- // const actorModel = new Actor(comment.actor);
- if (!this.editor) return;
- // this.editor.commands.mention({
- // id: actorModel.id,
- // label: actorModel.usernameWithDomain().substring(1),
- // });
- this.editor.commands.focus();
- }
-
- /**
- * renders a popup with suggestions
- * tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups
- * @param node
- */
- renderPopup(node: Element): void {
- if (this.popup) {
- return;
- }
- this.popup = tippy("#mobilizon", {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- getReferenceClientRect: node.getBoundingClientRect,
- appendTo: () => document.body,
- content: this.$refs.suggestions as HTMLElement,
- trigger: "mouseenter",
- interactive: true,
- sticky: true, // make sure position of tippy is updated when content changes
- plugins: [sticky],
- showOnCreate: true,
- theme: "dark",
- placement: "top-start",
- inertia: true,
- duration: [400, 200],
- }) as Instance[];
- }
-
- destroyPopup(): void {
- if (this.popup) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- this.popup[0].destroy();
- this.popup = null;
- }
- if (this.observer) {
- this.observer.disconnect();
- }
}
/**
* Show a file prompt, upload picture and insert it into editor
- * @param command
*/
- // eslint-disable-next-line @typescript-eslint/ban-types
async showImagePrompt(): Promise {
const image = await listenFileUpload();
try {
@@ -470,14 +321,28 @@ export default class EditorComponent extends Vue {
}
}
- beforeDestroy(): void {
+ /**
+ * We use this to programatically insert an actor mention when creating a reply to comment
+ */
+ replyToComment(comment: IComment): void {
+ if (!comment.actor) return;
+ // const actorModel = new Actor(comment.actor);
if (!this.editor) return;
- this.destroyPopup();
- this.editor.destroy();
+ // this.editor.commands.mention({
+ // id: actorModel.id,
+ // label: actorModel.usernameWithDomain().substring(1),
+ // });
+ this.editor.commands.focus();
+ }
+
+ beforeDestroy(): void {
+ this.editor?.destroy();
}
}
diff --git a/js/src/components/Editor/style.scss b/js/src/components/Editor/style.scss
new file mode 100644
index 000000000..cced5a400
--- /dev/null
+++ b/js/src/components/Editor/style.scss
@@ -0,0 +1,58 @@
+/**
+ * From https://www.tiptap.dev/api/editor/#inject-css
+ * https://github.com/ueberdosis/tiptap/blob/main/packages/core/src/style.ts
+ */
+
+.ProseMirror {
+ position: relative;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
+
+ & [contenteditable="false"] {
+ white-space: normal;
+ }
+ & [contenteditable="false"] [contenteditable="true"] {
+ white-space: pre-wrap;
+ }
+ pre {
+ white-space: pre-wrap;
+ }
+}
+.ProseMirror-gapcursor {
+ display: none;
+ pointer-events: none;
+ position: absolute;
+
+ &:after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: -2px;
+ width: 20px;
+ border-top: 1px solid black;
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
+ }
+}
+@keyframes ProseMirror-cursor-blink {
+ to {
+ visibility: hidden;
+ }
+}
+.ProseMirror-hideselection * {
+ &::selection {
+ background: transparent;
+ }
+ &::-moz-selection {
+ background: transparent;
+ }
+ caret-color: transparent;
+}
+
+.ProseMirror-focused .ProseMirror-gapcursor {
+ display: block;
+}
+.tippy-box[data-animation="fade"][data-state="hidden"] {
+ opacity: 0;
+}
diff --git a/js/src/types/actor/actor.model.ts b/js/src/types/actor/actor.model.ts
index 42b2680af..df1ed3cae 100644
--- a/js/src/types/actor/actor.model.ts
+++ b/js/src/types/actor/actor.model.ts
@@ -52,9 +52,7 @@ export class Actor implements IActor {
}
public displayName(): string {
- return this.name != null && this.name !== ""
- ? this.name
- : this.usernameWithDomain();
+ return displayName(this);
}
}
@@ -68,6 +66,12 @@ export function usernameWithDomain(actor: IActor, force = false): string {
return actor.preferredUsername;
}
+export function displayName(actor: IActor): string {
+ return actor.name != null && actor.name !== ""
+ ? actor.name
+ : usernameWithDomain(actor);
+}
+
export function displayNameAndUsername(actor: IActor): string {
if (actor.name) {
return `${actor.name} (@${usernameWithDomain(actor)})`;
diff --git a/js/yarn.lock b/js/yarn.lock
index 6bf4aae24..8b3ebc4ad 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -9445,6 +9445,11 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+p-debounce@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/p-debounce/-/p-debounce-4.0.0.tgz#348e3f44489baa9435cc7d807f17b3bb2fb16b24"
+ integrity sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==
+
p-each-series@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"