diff --git a/server/models/helpers/ProsemirrorHelper.tsx b/server/models/helpers/ProsemirrorHelper.tsx index a31e5d961..86494a6ad 100644 --- a/server/models/helpers/ProsemirrorHelper.tsx +++ b/server/models/helpers/ProsemirrorHelper.tsx @@ -49,22 +49,21 @@ export default class ProsemirrorHelper { static parseMentions(node: Node) { const mentions: MentionAttrs[] = []; - function findMentions(node: Node) { + node.descendants((node: Node) => { if ( node.type.name === "mention" && !mentions.some((m) => m.id === node.attrs.id) ) { mentions.push(node.attrs as MentionAttrs); + return false; } if (!node.content.size) { - return; + return false; } - node.content.descendants(findMentions); - } - - findMentions(node); + return true; + }); return mentions; } diff --git a/server/utils/parseAttachmentIds.test.ts b/server/utils/parseAttachmentIds.test.ts index b2052012f..acd490760 100644 --- a/server/utils/parseAttachmentIds.test.ts +++ b/server/utils/parseAttachmentIds.test.ts @@ -6,6 +6,7 @@ import parseAttachmentIds from "./parseAttachmentIds"; it("should return an empty array with no matches", () => { expect(parseAttachmentIds(`some random text`).length).toBe(0); }); + it("should not return orphaned UUID's", () => { const uuid = uuidv4(); expect( @@ -14,6 +15,7 @@ it("should not return orphaned UUID's", () => { ![caption](/images/${uuid}.png)`).length ).toBe(0); }); + it("should parse attachment ID from markdown", () => { const uuid = uuidv4(); const results = parseAttachmentIds( @@ -22,6 +24,7 @@ it("should parse attachment ID from markdown", () => { expect(results.length).toBe(1); expect(results[0]).toBe(uuid); }); + it("should parse attachment ID from markdown with additional query params", () => { const uuid = uuidv4(); const results = parseAttachmentIds( @@ -30,6 +33,7 @@ it("should parse attachment ID from markdown with additional query params", () = expect(results.length).toBe(1); expect(results[0]).toBe(uuid); }); + it("should parse attachment ID from markdown with fully qualified url", () => { const uuid = uuidv4(); const results = parseAttachmentIds( @@ -38,6 +42,7 @@ it("should parse attachment ID from markdown with fully qualified url", () => { expect(results.length).toBe(1); expect(results[0]).toBe(uuid); }); + it("should parse attachment ID from markdown with title", () => { const uuid = uuidv4(); const results = parseAttachmentIds( @@ -46,6 +51,7 @@ it("should parse attachment ID from markdown with title", () => { expect(results.length).toBe(1); expect(results[0]).toBe(uuid); }); + it("should parse multiple attachment IDs from markdown", () => { const uuid = uuidv4(); const uuid2 = uuidv4(); @@ -58,6 +64,7 @@ some text expect(results[0]).toBe(uuid); expect(results[1]).toBe(uuid2); }); + it("should parse attachment ID from html", () => { const uuid = uuidv4(); const results = parseAttachmentIds( @@ -66,6 +73,7 @@ it("should parse attachment ID from html", () => { expect(results.length).toBe(1); expect(results[0]).toBe(uuid); }); + it("should parse attachment ID from html with fully qualified url", () => { const uuid = uuidv4(); const results = parseAttachmentIds( diff --git a/server/utils/parseDocumentIds.test.ts b/server/utils/parseDocumentIds.test.ts index 46a5e1c37..2b5c7d7dd 100644 --- a/server/utils/parseDocumentIds.test.ts +++ b/server/utils/parseDocumentIds.test.ts @@ -18,6 +18,22 @@ it("should return an array of document ids", () => { expect(result[1]).toBe("test-123456"); }); +it("should return deeply nested link document ids", () => { + const result = parseDocumentIds(`# Header + + [internal](http://app.getoutline.com/doc/test-456733) + + More text + +- one + - two + - three [internal](/doc/test-123456#heading-anchor) + `); + expect(result.length).toBe(2); + expect(result[0]).toBe("test-456733"); + expect(result[1]).toBe("test-123456"); +}); + it("should not return duplicate document ids", () => { expect(parseDocumentIds(`# Header`).length).toBe(0); const result = parseDocumentIds(`# Header diff --git a/server/utils/parseDocumentIds.ts b/server/utils/parseDocumentIds.ts index 4bd5f7c7d..33f686bc6 100644 --- a/server/utils/parseDocumentIds.ts +++ b/server/utils/parseDocumentIds.ts @@ -10,10 +10,10 @@ import { parser } from "@server/editor"; * @returns An array of document identifiers */ export default function parseDocumentIds(text: string): string[] { - const value = parser.parse(text); + const doc = parser.parse(text); const identifiers: string[] = []; - function findLinks(node: Node) { + doc.descendants((node: Node) => { // get text nodes if (node.type.name === "text") { // get marks for text nodes @@ -28,15 +28,12 @@ export default function parseDocumentIds(text: string): string[] { } } }); + + return false; } - if (!node.content.size) { - return; - } + return true; + }); - node.content.descendants(findLinks); - } - - findLinks(value); return identifiers; } diff --git a/server/utils/parseImages.test.ts b/server/utils/parseImages.test.ts index eb5c88e1b..90635abdf 100644 --- a/server/utils/parseImages.test.ts +++ b/server/utils/parseImages.test.ts @@ -3,6 +3,7 @@ import parseImages from "./parseImages"; it("should not return non images", () => { expect(parseImages(`# Header`).length).toBe(0); }); + it("should return an array of images", () => { const result = parseImages(`# Header @@ -11,9 +12,22 @@ it("should return an array of images", () => { expect(result.length).toBe(1); expect(result[0]).toBe("/attachments/image.png"); }); + +it("should return deeply nested images", () => { + const result = parseImages(`# Header + +- one + - two + - three ![internal](/attachments/image.png) + `); + expect(result.length).toBe(1); + expect(result[0]).toBe("/attachments/image.png"); +}); + it("should not return non document links", () => { expect(parseImages(`[google](http://www.google.com)`).length).toBe(0); }); + it("should not return non document relative links", () => { expect(parseImages(`[relative](/developers)`).length).toBe(0); }); diff --git a/server/utils/parseImages.ts b/server/utils/parseImages.ts index 1efe6d643..f9531a060 100644 --- a/server/utils/parseImages.ts +++ b/server/utils/parseImages.ts @@ -2,25 +2,24 @@ import { Node } from "prosemirror-model"; import { parser } from "@server/editor"; export default function parseImages(text: string): string[] { - const value = parser.parse(text); + const doc = parser.parse(text); const images: string[] = []; - function findImages(node: Node) { + doc.descendants((node: Node) => { if (node.type.name === "image") { if (!images.includes(node.attrs.src)) { images.push(node.attrs.src); } - return; + return false; } if (!node.content.size) { - return; + return false; } - node.content.descendants(findImages); - } + return true; + }); - findImages(value); return images; } diff --git a/shared/editor/commands/createAndInsertLink.ts b/shared/editor/commands/createAndInsertLink.ts index e7c88984c..e8f69f2d5 100644 --- a/shared/editor/commands/createAndInsertLink.ts +++ b/shared/editor/commands/createAndInsertLink.ts @@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view"; function findPlaceholderLink(doc: Node, href: string) { let result: { pos: number; node: Node } | undefined; - function findLinks(node: Node, pos = 0) { + doc.descendants((node: Node, pos = 0) => { // get text nodes if (node.type.name === "text") { // get marks for text nodes @@ -17,16 +17,17 @@ function findPlaceholderLink(doc: Node, href: string) { } } }); + + return false; } if (!node.content.size) { - return; + return false; } - node.descendants(findLinks); - } + return true; + }); - findLinks(doc); return result; }