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.
- package/CHANGELOG.md +1002 -0
- package/README.md +72 -0
- package/docs/screenshots/commands.png +0 -0
- package/docs/screenshots/ribbon.png +0 -0
- package/docs/screenshots/settings.png +0 -0
- package/esbuild.config.mjs +55 -0
- package/package.json +33 -0
- package/src/CompletedModal.tsx +160 -0
- package/src/ConfluencePerPageForm.tsx +525 -0
- package/src/ConfluenceSettingTab.ts +124 -0
- package/src/MyBaseClient.ts +220 -0
- package/src/adaptors/obsidian.ts +146 -0
- package/src/custom.d.ts +19 -0
- package/src/main.ts +501 -0
- package/tsconfig.json +13 -0
|
@@ -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
|
+
}
|
package/src/custom.d.ts
ADDED
|
@@ -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
|
+
}
|