obsidian-confluence 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,220 @@
1
+ import {
2
+ Api,
3
+ Callback,
4
+ Client,
5
+ Config,
6
+ RequestConfig,
7
+ AuthenticationService,
8
+ } from "confluence.js";
9
+ import { requestUrl } from "obsidian";
10
+ import { RequiredConfluenceClient } from "md-confluence-lib";
11
+
12
+ const ATLASSIAN_TOKEN_CHECK_FLAG = "X-Atlassian-Token";
13
+ const ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE = "no-check";
14
+
15
+ export class MyBaseClient implements Client {
16
+ protected urlSuffix = "/wiki/rest";
17
+
18
+ constructor(protected readonly config: Config) {}
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ protected paramSerializer(parameters: Record<string, any>): string {
22
+ const parts: string[] = [];
23
+
24
+ Object.entries(parameters).forEach(([key, value]) => {
25
+ if (value === null || typeof value === "undefined") {
26
+ return;
27
+ }
28
+
29
+ if (Array.isArray(value)) {
30
+ // eslint-disable-next-line no-param-reassign
31
+ value = value.join(",");
32
+ }
33
+
34
+ if (value instanceof Date) {
35
+ // eslint-disable-next-line no-param-reassign
36
+ value = value.toISOString();
37
+ } else if (value !== null && typeof value === "object") {
38
+ // eslint-disable-next-line no-param-reassign
39
+ value = JSON.stringify(value);
40
+ } else if (value instanceof Function) {
41
+ const part = value();
42
+
43
+ return part && parts.push(part);
44
+ }
45
+
46
+ parts.push(`${this.encode(key)}=${this.encode(value)}`);
47
+
48
+ return;
49
+ });
50
+
51
+ return parts.join("&");
52
+ }
53
+
54
+ protected encode(value: string) {
55
+ return encodeURIComponent(value)
56
+ .replace(/%3A/gi, ":")
57
+ .replace(/%24/g, "$")
58
+ .replace(/%2C/gi, ",")
59
+ .replace(/%20/g, "+")
60
+ .replace(/%5B/gi, "[")
61
+ .replace(/%5D/gi, "]");
62
+ }
63
+
64
+ protected removeUndefinedProperties(
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ obj: Record<string, any>,
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ ): Record<string, any> {
69
+ return Object.entries(obj)
70
+ .filter(([, value]) => typeof value !== "undefined")
71
+ .reduce(
72
+ (accumulator, [key, value]) => ({
73
+ ...accumulator,
74
+ [key]: value,
75
+ }),
76
+ {},
77
+ );
78
+ }
79
+
80
+ async sendRequest<T>(
81
+ requestConfig: RequestConfig,
82
+ callback: never,
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ telemetryData?: any,
85
+ ): Promise<T>;
86
+ async sendRequest<T>(
87
+ requestConfig: RequestConfig,
88
+ callback: Callback<T>,
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ telemetryData?: any,
91
+ ): Promise<void>;
92
+ async sendRequest<T>(
93
+ requestConfig: RequestConfig,
94
+ callback: Callback<T> | never,
95
+ ): Promise<void | T> {
96
+ try {
97
+ const contentType = (requestConfig.headers ?? {})[
98
+ "content-type"
99
+ ]?.toString();
100
+ if (requestConfig.headers && contentType) {
101
+ requestConfig.headers["Content-Type"] = contentType;
102
+ delete requestConfig?.headers["content-type"];
103
+ }
104
+
105
+ const params = this.paramSerializer(requestConfig.params);
106
+
107
+ const requestContentType =
108
+ (requestConfig.headers ?? {})["Content-Type"]?.toString() ??
109
+ "application/json";
110
+
111
+ const requestBody = requestContentType.startsWith(
112
+ "multipart/form-data",
113
+ )
114
+ ? [
115
+ requestConfig.data.getHeaders(),
116
+ requestConfig.data.getBuffer().buffer,
117
+ ]
118
+ : [{}, JSON.stringify(requestConfig.data)];
119
+
120
+ const modifiedRequestConfig = {
121
+ ...requestConfig,
122
+ headers: this.removeUndefinedProperties({
123
+ "User-Agent": "Obsidian.md",
124
+ Accept: "application/json",
125
+ [ATLASSIAN_TOKEN_CHECK_FLAG]: this.config
126
+ .noCheckAtlassianToken
127
+ ? ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE
128
+ : undefined,
129
+ ...this.config.baseRequestConfig?.headers,
130
+ Authorization:
131
+ await AuthenticationService.getAuthenticationToken(
132
+ this.config.authentication,
133
+ {
134
+ // eslint-disable-next-line @typescript-eslint/naming-convention
135
+ baseURL: this.config.host,
136
+ url: `${this.config.host}${this.urlSuffix}`,
137
+ method: requestConfig.method ?? "GET",
138
+ },
139
+ ),
140
+ ...requestConfig.headers,
141
+ "Content-Type": requestContentType,
142
+ ...requestBody[0],
143
+ }),
144
+ url: `${this.config.host}${this.urlSuffix}${requestConfig.url}?${params}`,
145
+ body: requestBody[1],
146
+ method: requestConfig.method?.toUpperCase() ?? "GET",
147
+ contentType: requestContentType,
148
+ throw: false,
149
+ };
150
+ delete modifiedRequestConfig.data;
151
+
152
+ const response = await requestUrl(modifiedRequestConfig);
153
+
154
+ if (response.status >= 400) {
155
+ throw new HTTPError(`Received a ${response.status}`, {
156
+ status: response.status,
157
+ data: response.text,
158
+ });
159
+ }
160
+
161
+ const callbackResponseHandler =
162
+ callback && ((data: T): void => callback(null, data));
163
+ const defaultResponseHandler = (data: T): T => data;
164
+
165
+ const responseHandler =
166
+ callbackResponseHandler ?? defaultResponseHandler;
167
+
168
+ this.config.middlewares?.onResponse?.(response.json);
169
+
170
+ return responseHandler(response.json);
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ } catch (e: any) {
173
+ console.warn({ httpError: e, requestConfig });
174
+ const err =
175
+ this.config.newErrorHandling && e.isAxiosError
176
+ ? e.response.data
177
+ : e;
178
+
179
+ const callbackErrorHandler =
180
+ callback && ((error: Config.Error) => callback(error));
181
+ const defaultErrorHandler = (error: Error) => {
182
+ throw error;
183
+ };
184
+
185
+ const errorHandler = callbackErrorHandler ?? defaultErrorHandler;
186
+
187
+ this.config.middlewares?.onError?.(err);
188
+
189
+ return errorHandler(err);
190
+ }
191
+ }
192
+ }
193
+
194
+ export interface ErrorData {
195
+ data: unknown;
196
+ status: number;
197
+ }
198
+
199
+ export class HTTPError extends Error {
200
+ constructor(
201
+ msg: string,
202
+ public response: ErrorData,
203
+ ) {
204
+ super(msg);
205
+
206
+ // Set the prototype explicitly.
207
+ Object.setPrototypeOf(this, HTTPError.prototype);
208
+ }
209
+ }
210
+
211
+ export class ObsidianConfluenceClient
212
+ extends MyBaseClient
213
+ implements RequiredConfluenceClient
214
+ {
215
+ content = new Api.Content(this);
216
+ space = new Api.Space(this);
217
+ contentAttachments = new Api.ContentAttachments(this);
218
+ contentLabels = new Api.ContentLabels(this);
219
+ users = new Api.Users(this);
220
+ }
@@ -0,0 +1,146 @@
1
+ import { Vault, MetadataCache, App, TFile } from "obsidian";
2
+ import {
3
+ ConfluenceUploadSettings,
4
+ BinaryFile,
5
+ FilesToUpload,
6
+ LoaderAdaptor,
7
+ MarkdownFile,
8
+ ConfluencePageConfig,
9
+ } from "md-confluence-lib";
10
+ import { lookup } from "mime-types";
11
+
12
+ export default class ObsidianAdaptor implements LoaderAdaptor {
13
+ vault: Vault;
14
+ metadataCache: MetadataCache;
15
+ settings: ConfluenceUploadSettings.ConfluenceSettings;
16
+ app: App;
17
+
18
+ constructor(
19
+ vault: Vault,
20
+ metadataCache: MetadataCache,
21
+ settings: ConfluenceUploadSettings.ConfluenceSettings,
22
+ app: App,
23
+ ) {
24
+ this.vault = vault;
25
+ this.metadataCache = metadataCache;
26
+ this.settings = settings;
27
+ this.app = app;
28
+ }
29
+
30
+ async getMarkdownFilesToUpload(): Promise<FilesToUpload> {
31
+ const files = this.vault.getMarkdownFiles();
32
+ const filesToPublish = [];
33
+ for (const file of files) {
34
+ try {
35
+ if (file.path.endsWith(".excalidraw")) {
36
+ continue;
37
+ }
38
+
39
+ const fileFM = this.metadataCache.getCache(file.path);
40
+ if (!fileFM) {
41
+ throw new Error("Missing File in Metadata Cache");
42
+ }
43
+ const frontMatter = fileFM.frontmatter;
44
+
45
+ if (
46
+ (file.path.startsWith(this.settings.folderToPublish) &&
47
+ (!frontMatter ||
48
+ frontMatter["connie-publish"] !== false)) ||
49
+ (frontMatter && frontMatter["connie-publish"] === true)
50
+ ) {
51
+ filesToPublish.push(file);
52
+ }
53
+ } catch {
54
+ //ignore
55
+ }
56
+ }
57
+ const filesToUpload = [];
58
+
59
+ for (const file of filesToPublish) {
60
+ const markdownFile = await this.loadMarkdownFile(file.path);
61
+ filesToUpload.push(markdownFile);
62
+ }
63
+
64
+ return filesToUpload;
65
+ }
66
+
67
+ async loadMarkdownFile(absoluteFilePath: string): Promise<MarkdownFile> {
68
+ const file = this.app.vault.getAbstractFileByPath(absoluteFilePath);
69
+ if (!(file instanceof TFile)) {
70
+ throw new Error("Not a TFile");
71
+ }
72
+
73
+ const fileFM = this.metadataCache.getCache(file.path);
74
+ if (!fileFM) {
75
+ throw new Error("Missing File in Metadata Cache");
76
+ }
77
+ const frontMatter = fileFM.frontmatter;
78
+
79
+ const parsedFrontMatter: Record<string, unknown> = {};
80
+ if (frontMatter) {
81
+ for (const [key, value] of Object.entries(frontMatter)) {
82
+ parsedFrontMatter[key] = value;
83
+ }
84
+ }
85
+
86
+ return {
87
+ pageTitle: file.basename,
88
+ folderName: file.parent.name,
89
+ absoluteFilePath: file.path,
90
+ fileName: file.name,
91
+ contents: await this.vault.cachedRead(file),
92
+ frontmatter: parsedFrontMatter,
93
+ };
94
+ }
95
+
96
+ async readBinary(
97
+ path: string,
98
+ referencedFromFilePath: string,
99
+ ): Promise<BinaryFile | false> {
100
+ const testing = this.metadataCache.getFirstLinkpathDest(
101
+ path,
102
+ referencedFromFilePath,
103
+ );
104
+ if (testing) {
105
+ const files = await this.vault.readBinary(testing);
106
+ const mimeType =
107
+ lookup(testing.extension) || "application/octet-stream";
108
+ return {
109
+ contents: files,
110
+ filePath: testing.path,
111
+ filename: testing.name,
112
+ mimeType: mimeType,
113
+ };
114
+ }
115
+
116
+ return false;
117
+ }
118
+ async updateMarkdownValues(
119
+ absoluteFilePath: string,
120
+ values: Partial<ConfluencePageConfig.ConfluencePerPageAllValues>,
121
+ ): Promise<void> {
122
+ const config = ConfluencePageConfig.conniePerPageConfig;
123
+ const file = this.app.vault.getAbstractFileByPath(absoluteFilePath);
124
+ if (file instanceof TFile) {
125
+ this.app.fileManager.processFrontMatter(file, (fm) => {
126
+ for (const propertyKey in config) {
127
+ if (!config.hasOwnProperty(propertyKey)) {
128
+ continue;
129
+ }
130
+
131
+ const { key } =
132
+ config[
133
+ propertyKey as keyof ConfluencePageConfig.ConfluencePerPageConfig
134
+ ];
135
+ const value =
136
+ values[
137
+ propertyKey as keyof ConfluencePageConfig.ConfluencePerPageAllValues
138
+ ];
139
+ if (propertyKey in values) {
140
+ fm[key] = value;
141
+ }
142
+ }
143
+ });
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,19 @@
1
+ declare module "*.txt" {
2
+ const content: string;
3
+ export default content;
4
+ }
5
+
6
+ declare module "*.json" {
7
+ const content: unknown;
8
+ export default content;
9
+ }
10
+
11
+ declare module "mermaid_renderer.esbuild" {
12
+ const content: Buffer;
13
+ export default content;
14
+ }
15
+
16
+ declare module "sort-any" {
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ export default function sortAny<T>(item: T): T;
19
+ }