Skip to content

Commit

Permalink
feat: Make ADF the same as what Confluence returns.
Browse files Browse the repository at this point in the history
* Modified version of isEqual to work with our data
* If no content in doc then make empty paragraph
* If node.content or node.marks exists but has nothing in it or just null | undefined then remove
* Merge text nodes that have the same marks
* Include width and height when uploading images
* Map codeblock languages to Confluence
* Remove unsafe urls using isSafeUrl from "@atlaskit/adf-schema" (Confluence will remove these anyway)
* Better handling of image upload results. Understands when an image is uploaded or not.
* Better debugging when ADF comes back as different with undefined items
* Replace images that don't exist to be uploaded with a paragraph saying "Invalid Image Path"

Probably more I have missed.
  • Loading branch information
andymac4182 committed Apr 21, 2023
1 parent 4933c62 commit a223c72
Show file tree
Hide file tree
Showing 13 changed files with 2,091 additions and 7,334 deletions.
8,721 changes: 1,569 additions & 7,152 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 29 additions & 13 deletions package.json
@@ -1,17 +1,33 @@
{
"name": "obsidian-confluence-root",
"private": true,
"version": "3.6.1",
"type": "module",
"scripts": {
"prepare": "husky install",
"devall": "npm run dev --workspace=@markdown-confluence/lib & npm run dev --workspace=@markdown-confluence/mermaid-electron-renderer & npm run dev --workspace=obsidian-confluence"
"name":"obsidian-confluence-root",
"private":true,
"version":"3.6.1",
"type":"module",
"scripts":{
"prepare":"husky install",
"devall":"npm run dev --workspace=@markdown-confluence/lib & npm run dev --workspace=@markdown-confluence/mermaid-electron-renderer & npm run dev --workspace=obsidian-confluence"
},
"devDependencies": {},
"workspaces": [
"packages/*"
"devDependencies":{
"@types/node":"^16.11.6",
"@typescript-eslint/eslint-plugin":"^5.29.0",
"@typescript-eslint/parser":"^5.58.0",
"builtin-modules":"3.3.0",
"esbuild":"0.17.16",
"esbuild-node-externals":"^1.7.0",
"eslint":"^8.38.0",
"eslint-config-prettier":"^8.8.0",
"husky":"^8.0.3",
"lint-staged":"^13.2.1",
"prettier":"2.8.7",
"ts-jest":"^29.1.0",
"ts-node":"^10.9.1",
"tslib":"2.5.0",
"typescript":"4.7.4"
},
"workspaces":[
"packages/*"
],
"lint-staged": {
"packages/**/*.ts": "prettier --write"
"lint-staged":{
"packages/**/*.ts":"prettier --write"
}
}
}
42 changes: 7 additions & 35 deletions packages/lib/package.json
Expand Up @@ -19,58 +19,31 @@
"license": "Apache 2.0",
"devDependencies": {
"@jest/globals": "^29.5.0",
"@types/deep-equal": "^1.0.1",
"@types/lodash": "^4.14.194",
"@types/markdown-it": "^12.2.3",
"@types/mime-types": "^2.1.1",
"@types/node": "^16.11.6",
"@types/prosemirror-model": "1.16.2",
"@types/react-dom": "^18.0.11",
"@types/prosemirror-state": "^1.3.0",
"@types/prosemirror-transform": "^1.4.2",
"@types/prosemirror-view": "^1.23.1",
"@types/spark-md5": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.58.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.16",
"esbuild-node-externals": "^1.7.0",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"lint-staged": "^13.2.1",
"markdown-it": "^13.0.1",
"prettier": "2.8.7",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"tslib": "2.5.0",
"typescript": "4.7.4"
"markdown-it": "^13.0.1"
},
"dependencies": {
"@atlaskit/adf-schema": "^25.5.0",
"@atlaskit/adf-utils": "^17.1.4",
"@atlaskit/editor-common": "^72.4.0",
"@atlaskit/editor-confluence-transformer": "^8.1.33",
"@atlaskit/editor-json-transformer": "^8.8.3",
"@atlaskit/renderer": "^107.3.2",
"@electron/remote": "^2.0.9",
"@types/prosemirror-state": "^1.3.0",
"@types/prosemirror-transform": "^1.4.2",
"@types/prosemirror-view": "^1.23.1",
"confluence.js": "^1.6.3",
"deep-equal": "^2.2.0",
"formdata-node": "^5.0.0",
"gray-matter": "^4.0.3",
"image-size": "^1.0.2",
"lodash": "^4.17.21",
"markdown-it-table": "^2.0.4",
"mermaid": "^10.1.0",
"mime-types": "^2.1.35",
"prosemirror-markdown": "^1.10.1",
"prosemirror-model": "1.14.3",
"punycode": "^1.4.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-intl-next": "npm:react-intl@^5.18.1",
"sort-any": "^4.0.5",
"spark-md5": "^3.0.2",
"uuid": "^9.0.0"
"spark-md5": "^3.0.2"
},
"resolutions": {
"prosemirror-model": "1.14.3"
Expand All @@ -90,4 +63,3 @@
"url": "https://github.com/obsidian-confluence/obsidian-confluence/issues"
}
}

18 changes: 15 additions & 3 deletions packages/lib/src/AdfEqual.ts
Expand Up @@ -2,7 +2,8 @@ import sortAny from "sort-any";
import { mapValues } from "lodash";
import { traverse } from "@atlaskit/adf-utils/traverse";
import { JSONDocNode } from "@atlaskit/editor-json-transformer";
import deepEqual from "deep-equal";
import { ADFEntityMark } from "@atlaskit/adf-utils/types";
import { isEqual } from "./isEqual";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sortDeep = (object: unknown): any => {
Expand All @@ -29,12 +30,23 @@ export function orderMarks(adf: JSONDocNode) {
any: (node, __parent) => {
if (node.marks) {
node.marks = sortDeep(node.marks);
return node;
}
return node;
},
});
}

export function adfEqual(first: JSONDocNode, second: JSONDocNode): boolean {
return deepEqual(orderMarks(first), orderMarks(second));
return isEqual(orderMarks(first), orderMarks(second));
}

export function marksEqual(
first: ADFEntityMark[] | undefined,
second: ADFEntityMark[] | undefined
) {
if (first === second) {
return true;
}

return isEqual(sortDeep(first), sortDeep(second));
}
94 changes: 92 additions & 2 deletions packages/lib/src/AdfPostProcess.ts
Expand Up @@ -2,6 +2,9 @@ import { traverse } from "@atlaskit/adf-utils/traverse";
import { JSONDocNode } from "@atlaskit/editor-json-transformer";
import { ConfluenceAdfFile, ConfluenceNode } from "./Publisher";
import { ConfluenceSettings } from "./Settings";
import { marksEqual } from "./AdfEqual";
import { ADFEntity } from "@atlaskit/adf-utils/types";
import { p } from "@atlaskit/adf-utils/builders";

export function prepareAdf(
confluencePagesToPublish: ConfluenceNode[],
Expand All @@ -13,8 +16,14 @@ export function prepareAdf(
fileToPageIdMap[node.file.fileName] = node.file;
});

confluencePagesToPublish.forEach((node) => {
node.file.contents = traverse(node.file.contents, {
confluencePagesToPublish.forEach((confluenceNode) => {
let result = confluenceNode.file.contents;

if (result.content.length === 0) {
result.content = [p()];
}

result = traverse(result, {
text: (node, _parent) => {
if (
node.marks &&
Expand Down Expand Up @@ -49,5 +58,86 @@ export function prepareAdf(
}
},
}) as JSONDocNode;

result = traverse(result, {
any: (node, _parent) => {
if (
node.content &&
node.content.filter((m) => !(m === undefined || m === null))
.length === 0
) {
delete node.content;
}

if (
node.marks &&
node.marks.filter((m) => !(m === undefined || m === null))
.length === 0
) {
delete node.marks;
}
return node;
},
}) as JSONDocNode;

result = traverse(result, {
paragraph: (node, _parent) => {
if (
node?.content === undefined ||
node.content.filter((m) => !(m === undefined || m === null))
.length === 0
) {
return node;
}
const processedContent: Array<ADFEntity | undefined> = [];
const indexToSkip: number[] = [];
const nodesToCheck = node.content.length ?? 0;
for (let index = 0; index < nodesToCheck - 1; index++) {
if (indexToSkip.includes(index)) {
continue;
}

const currentNode = node.content[index];
const nextNode = node.content[index + 1];

if (
nextNode === undefined ||
currentNode === undefined ||
currentNode.type !== "text" ||
nextNode.type !== "text"
) {
processedContent.push(currentNode);
continue;
}

for (
let lookAheadIndex = index + 1;
lookAheadIndex < nodesToCheck - 1;
lookAheadIndex++
) {
const futureNode = node.content[lookAheadIndex];

if (marksEqual(currentNode?.marks, futureNode?.marks)) {
currentNode.text =
currentNode.text ??
"" + (futureNode?.text ?? "");
indexToSkip.push(lookAheadIndex);
} else {
break;
}
}

processedContent.push(currentNode);
}

if (processedContent.length > 0) {
node.content = processedContent;
}

return node;
},
}) as JSONDocNode;

confluenceNode.file.contents = result;
});
}
23 changes: 22 additions & 1 deletion packages/lib/src/Attachments.ts
@@ -1,10 +1,16 @@
import SparkMD5 from "spark-md5";
import { CustomConfluenceClient, LoaderAdaptor } from "./adaptors";
import sizeOf from "image-size";

export type ConfluenceImageStatus = "existing" | "uploaded";

export interface UploadedImageData {
filename: string;
id: string;
collection: string;
width: number;
height: number;
status: ConfluenceImageStatus;
}

export async function uploadBuffer(
Expand All @@ -19,6 +25,7 @@ export async function uploadBuffer(
): Promise<UploadedImageData | null> {
const spark = new SparkMD5.ArrayBuffer();
const currentFileMd5 = spark.append(fileBuffer).end();
const imageSize = await sizeOf(fileBuffer);

if (
!!currentAttachments[uploadFilename] &&
Expand All @@ -28,6 +35,9 @@ export async function uploadBuffer(
filename: uploadFilename,
id: currentAttachments[uploadFilename].attachmentId,
collection: currentAttachments[uploadFilename].collectionName,
width: imageSize.width ?? 0,
height: imageSize.height ?? 0,
status: "existing",
};
}

Expand All @@ -53,6 +63,9 @@ export async function uploadBuffer(
filename: uploadFilename,
id: attachmentResponse.results[0].extensions.fileId,
collection: `contentId-${attachmentResponse.results[0].container.id}`,
width: imageSize.width ?? 0,
height: imageSize.height ?? 0,
status: "uploaded",
};
}

Expand All @@ -73,6 +86,8 @@ export async function uploadFile(
const currentFileMd5 = spark.append(testing.contents).end();
const pathMd5 = SparkMD5.hash(testing.filePath);
const uploadFilename = `${pathMd5}-${testing.filename}`;
const imageBuffer = Buffer.from(testing.contents);
const imageSize = await sizeOf(imageBuffer);

if (
!!currentAttachments[uploadFilename] &&
Expand All @@ -82,14 +97,17 @@ export async function uploadFile(
filename: fileNameToUpload,
id: currentAttachments[uploadFilename].attachmentId,
collection: currentAttachments[uploadFilename].collectionName,
width: imageSize.width ?? 0,
height: imageSize.height ?? 0,
status: "existing",
};
}

const attachmentDetails = {
id: pageId,
attachments: [
{
file: Buffer.from(testing.contents),
file: imageBuffer,
filename: uploadFilename,
minorEdit: false,
comment: currentFileMd5,
Expand All @@ -106,6 +124,9 @@ export async function uploadFile(
filename: fileNameToUpload,
id: attachmentResponse.results[0].extensions.fileId,
collection: `contentId-${attachmentResponse.results[0].container.id}`,
width: imageSize.width ?? 0,
height: imageSize.height ?? 0,
status: "uploaded",
};
}

Expand Down

0 comments on commit a223c72

Please sign in to comment.