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,525 @@
1
+ import { Modal, App, FrontMatterCache } from "obsidian";
2
+ import ReactDOM from "react-dom";
3
+ import React, { useState, ChangeEvent } from "react";
4
+ import { ConfluencePageConfig } from "md-confluence-lib";
5
+ import { Property } from "csstype";
6
+
7
+ export type ConfluencePerPageUIValues = {
8
+ [K in keyof ConfluencePageConfig.ConfluencePerPageConfig]: {
9
+ value:
10
+ | ConfluencePageConfig.ConfluencePerPageConfig[K]["default"]
11
+ | undefined;
12
+ isSet: boolean;
13
+ };
14
+ };
15
+
16
+ export function mapFrontmatterToConfluencePerPageUIValues(
17
+ frontmatter: FrontMatterCache | undefined,
18
+ ): ConfluencePerPageUIValues {
19
+ const config = ConfluencePageConfig.conniePerPageConfig;
20
+ const result: Partial<ConfluencePerPageUIValues> = {};
21
+
22
+ if (!frontmatter) {
23
+ throw new Error("Missing frontmatter");
24
+ }
25
+
26
+ for (const propertyKey in config) {
27
+ if (config.hasOwnProperty(propertyKey)) {
28
+ const {
29
+ key,
30
+ inputType,
31
+ default: defaultValue,
32
+ } = config[
33
+ propertyKey as keyof ConfluencePageConfig.ConfluencePerPageConfig
34
+ ];
35
+ const frontmatterValue = frontmatter[key];
36
+
37
+ if (frontmatterValue !== undefined) {
38
+ result[propertyKey as keyof ConfluencePerPageUIValues] = {
39
+ value: frontmatterValue,
40
+ isSet: true,
41
+ };
42
+ } else {
43
+ switch (inputType) {
44
+ case "options":
45
+ case "array-text":
46
+ result[propertyKey as keyof ConfluencePerPageUIValues] =
47
+ { value: defaultValue as never, isSet: false };
48
+ break;
49
+ case "boolean":
50
+ case "text":
51
+ result[propertyKey as keyof ConfluencePerPageUIValues] =
52
+ { value: undefined, isSet: false };
53
+ break;
54
+ default:
55
+ throw new Error("Missing case for inputType");
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return result as ConfluencePerPageUIValues;
61
+ }
62
+
63
+ interface FormProps {
64
+ config: ConfluencePageConfig.ConfluencePerPageConfig;
65
+ initialValues: ConfluencePerPageUIValues;
66
+ onSubmit: (values: ConfluencePerPageUIValues) => void;
67
+ }
68
+
69
+ interface ModalProps {
70
+ config: ConfluencePageConfig.ConfluencePerPageConfig;
71
+ initialValues: ConfluencePerPageUIValues;
72
+ onSubmit: (values: ConfluencePerPageUIValues, close: () => void) => void;
73
+ }
74
+
75
+ const handleChange = (
76
+ key: string,
77
+ value: unknown,
78
+ inputValidator: ConfluencePageConfig.InputValidator<unknown>,
79
+ setValues: React.Dispatch<React.SetStateAction<ConfluencePerPageUIValues>>,
80
+ setErrors: React.Dispatch<React.SetStateAction<Record<string, Error[]>>>,
81
+ isSetValue: boolean,
82
+ ) => {
83
+ const validationResult = inputValidator(value);
84
+
85
+ setValues((prevValues) => ({
86
+ ...prevValues,
87
+ [key]: {
88
+ ...prevValues[key as keyof ConfluencePerPageUIValues],
89
+ ...(isSetValue ? { isSet: value } : { value }),
90
+ },
91
+ }));
92
+ setErrors((prevErrors) => ({
93
+ ...prevErrors,
94
+ [key]: validationResult.valid ? [] : validationResult.errors,
95
+ }));
96
+ };
97
+
98
+ const styles = {
99
+ errorTd: {
100
+ columnSpan: "all" as Property.ColumnSpan,
101
+ color: "red",
102
+ },
103
+ };
104
+
105
+ const renderTextInput = (
106
+ key: string,
107
+ config: ConfluencePageConfig.FrontmatterConfig<string, "text">,
108
+ values: ConfluencePerPageUIValues,
109
+ errors: Record<string, Error[]>,
110
+ setValues: React.Dispatch<React.SetStateAction<ConfluencePerPageUIValues>>,
111
+ setErrors: React.Dispatch<React.SetStateAction<Record<string, Error[]>>>,
112
+ ) => (
113
+ <>
114
+ <tr key={key}>
115
+ <td>
116
+ <label htmlFor={key}>{config.key}</label>
117
+ </td>
118
+ <td>
119
+ <input
120
+ type="text"
121
+ id={key}
122
+ value={
123
+ (values[key as keyof ConfluencePerPageUIValues]
124
+ .value as string) ?? ""
125
+ }
126
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
127
+ handleChange(
128
+ key,
129
+ e.target.value,
130
+ config.inputValidator,
131
+ setValues,
132
+ setErrors,
133
+ false,
134
+ )
135
+ }
136
+ />
137
+ </td>
138
+ <td>
139
+ <input
140
+ type="checkbox"
141
+ id={`${key}-isSet`}
142
+ checked={
143
+ values[key as keyof ConfluencePerPageUIValues]
144
+ .isSet as boolean
145
+ }
146
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
147
+ handleChange(
148
+ key,
149
+ e.target.checked,
150
+ config.inputValidator,
151
+ setValues,
152
+ setErrors,
153
+ true,
154
+ )
155
+ }
156
+ />
157
+ </td>
158
+ </tr>
159
+ <tr key={`${key}-errors`}>
160
+ {(errors[key]?.length ?? 0) > 0 && (
161
+ <td colSpan={3}>
162
+ <div className="error" style={styles.errorTd}>
163
+ {(errors[key] ?? []).map((error) => (
164
+ <p key={error.message}>{error.message}</p>
165
+ ))}
166
+ </div>
167
+ </td>
168
+ )}
169
+ </tr>
170
+ </>
171
+ );
172
+
173
+ const renderArrayText = (
174
+ key: string,
175
+ config: ConfluencePageConfig.FrontmatterConfig<string[], "array-text">,
176
+ values: ConfluencePerPageUIValues,
177
+ errors: Record<string, Error[]>,
178
+ setValues: React.Dispatch<React.SetStateAction<ConfluencePerPageUIValues>>,
179
+ setErrors: React.Dispatch<React.SetStateAction<Record<string, Error[]>>>,
180
+ ) => (
181
+ <>
182
+ <tr key={key}>
183
+ <td>
184
+ <label htmlFor={key}>{config.key}</label>
185
+ </td>
186
+ <td>
187
+ {(
188
+ values[key as keyof ConfluencePerPageUIValues]
189
+ .value as unknown as string[]
190
+ ).map((value, index) => (
191
+ <input
192
+ key={`${key}-${index}`}
193
+ type="text"
194
+ value={value}
195
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
196
+ const newArray = [
197
+ ...(values[
198
+ key as keyof ConfluencePerPageUIValues
199
+ ].value as unknown as string[]),
200
+ ];
201
+ newArray[index] = e.target.value;
202
+ handleChange(
203
+ key,
204
+ newArray,
205
+ config.inputValidator,
206
+ setValues,
207
+ setErrors,
208
+ false,
209
+ );
210
+ }}
211
+ />
212
+ ))}
213
+ <button
214
+ type="button"
215
+ onClick={() => {
216
+ const newArray = [
217
+ ...(values[key as keyof ConfluencePerPageUIValues]
218
+ .value as string[]),
219
+ "",
220
+ ];
221
+ handleChange(
222
+ key,
223
+ newArray,
224
+ config.inputValidator,
225
+ setValues,
226
+ setErrors,
227
+ false,
228
+ );
229
+ }}
230
+ >
231
+ +
232
+ </button>
233
+ </td>
234
+ <td>
235
+ <input
236
+ type="checkbox"
237
+ id={`${key}-isSet`}
238
+ checked={
239
+ values[key as keyof ConfluencePerPageUIValues]
240
+ .isSet as boolean
241
+ }
242
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
243
+ handleChange(
244
+ key,
245
+ e.target.checked,
246
+ config.inputValidator,
247
+ setValues,
248
+ setErrors,
249
+ true,
250
+ )
251
+ }
252
+ />
253
+ </td>
254
+ </tr>
255
+ <tr key={`${key}-errors`}>
256
+ {(errors[key]?.length ?? 0) > 0 && (
257
+ <td colSpan={3}>
258
+ <div className="error" style={styles.errorTd}>
259
+ {(errors[key] ?? []).map((error) => (
260
+ <p key={error.message}>{error.message}</p>
261
+ ))}
262
+ </div>
263
+ </td>
264
+ )}
265
+ </tr>
266
+ </>
267
+ );
268
+
269
+ const renderBoolean = (
270
+ key: string,
271
+ config: ConfluencePageConfig.FrontmatterConfig<boolean, "boolean">,
272
+ values: ConfluencePerPageUIValues,
273
+ errors: Record<string, Error[]>,
274
+ setValues: React.Dispatch<React.SetStateAction<ConfluencePerPageUIValues>>,
275
+ setErrors: React.Dispatch<React.SetStateAction<Record<string, Error[]>>>,
276
+ ) => (
277
+ <>
278
+ <tr key={key}>
279
+ <td>
280
+ <label htmlFor={key}>{config.key}</label>
281
+ </td>
282
+ <td>
283
+ <input
284
+ type="checkbox"
285
+ id={key}
286
+ checked={
287
+ values[key as keyof ConfluencePerPageUIValues]
288
+ .value as boolean
289
+ }
290
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
291
+ handleChange(
292
+ key,
293
+ e.target.checked,
294
+ config.inputValidator,
295
+ setValues,
296
+ setErrors,
297
+ false,
298
+ )
299
+ }
300
+ />
301
+ </td>
302
+ <td>
303
+ <input
304
+ type="checkbox"
305
+ id={`${key}-isSet`}
306
+ checked={
307
+ values[key as keyof ConfluencePerPageUIValues]
308
+ .isSet as boolean
309
+ }
310
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
311
+ handleChange(
312
+ key,
313
+ e.target.checked,
314
+ config.inputValidator,
315
+ setValues,
316
+ setErrors,
317
+ true,
318
+ )
319
+ }
320
+ />
321
+ </td>
322
+ </tr>
323
+ <tr key={`${key}-errors`}>
324
+ {(errors[key]?.length ?? 0) > 0 && (
325
+ <td colSpan={3}>
326
+ <div className="error" style={styles.errorTd}>
327
+ {(errors[key] ?? []).map((error) => (
328
+ <p key={error.message}>{error.message}</p>
329
+ ))}
330
+ </div>
331
+ </td>
332
+ )}
333
+ </tr>
334
+ </>
335
+ );
336
+ const renderOptions = (
337
+ key: string,
338
+ config: ConfluencePageConfig.FrontmatterConfig<
339
+ ConfluencePageConfig.PageContentType,
340
+ "options"
341
+ >,
342
+ values: ConfluencePerPageUIValues,
343
+ errors: Record<string, Error[]>,
344
+ setValues: React.Dispatch<React.SetStateAction<ConfluencePerPageUIValues>>,
345
+ setErrors: React.Dispatch<React.SetStateAction<Record<string, Error[]>>>,
346
+ ) => (
347
+ <>
348
+ <tr key={key}>
349
+ <td>
350
+ <label htmlFor={key}>{config.key}</label>
351
+ </td>
352
+ <td>
353
+ <select
354
+ id={key}
355
+ value={
356
+ values[key as keyof ConfluencePerPageUIValues]
357
+ .value as ConfluencePageConfig.PageContentType
358
+ }
359
+ onChange={(e: ChangeEvent<HTMLSelectElement>) =>
360
+ handleChange(
361
+ key,
362
+ e.target
363
+ .value as ConfluencePageConfig.PageContentType,
364
+ config.inputValidator,
365
+ setValues,
366
+ setErrors,
367
+ false,
368
+ )
369
+ }
370
+ >
371
+ {config.selectOptions.map((option) => (
372
+ <option key={option} value={option}>
373
+ {option}
374
+ </option>
375
+ ))}
376
+ </select>
377
+ </td>
378
+ <td>
379
+ <input
380
+ type="checkbox"
381
+ id={`${key}-isSet`}
382
+ checked={
383
+ values[key as keyof ConfluencePerPageUIValues]
384
+ .isSet as boolean
385
+ }
386
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
387
+ handleChange(
388
+ key,
389
+ e.target.checked,
390
+ config.inputValidator,
391
+ setValues,
392
+ setErrors,
393
+ true,
394
+ )
395
+ }
396
+ />
397
+ </td>
398
+ </tr>
399
+ <tr key={`${key}-errors`}>
400
+ {(errors[key]?.length ?? 0) > 0 && (
401
+ <td colSpan={3}>
402
+ <div className="error" style={styles.errorTd}>
403
+ {(errors[key] ?? []).map((error) => (
404
+ <p key={error.message}>{error.message}</p>
405
+ ))}
406
+ </div>
407
+ </td>
408
+ )}
409
+ </tr>
410
+ </>
411
+ );
412
+
413
+ const ConfluenceForm: React.FC<FormProps> = ({
414
+ config,
415
+ initialValues,
416
+ onSubmit,
417
+ }) => {
418
+ const [values, setValues] =
419
+ useState<ConfluencePerPageUIValues>(initialValues);
420
+ const [errors, setErrors] = useState<Record<string, Error[]>>({});
421
+
422
+ const handleSubmit = (e: React.FormEvent) => {
423
+ e.preventDefault();
424
+ onSubmit(values as ConfluencePerPageUIValues);
425
+ };
426
+
427
+ return (
428
+ <form onSubmit={handleSubmit}>
429
+ <h1>Update Confluence Page Settings</h1>
430
+ <table>
431
+ <thead>
432
+ <tr>
433
+ <td>YAML Key</td>
434
+ <td>Value</td>
435
+ <td>Update</td>
436
+ </tr>
437
+ </thead>
438
+ <tbody>
439
+ {Object.entries(config).map(([key, config]) => {
440
+ switch (config.inputType) {
441
+ case "text":
442
+ return renderTextInput(
443
+ key,
444
+ config as ConfluencePageConfig.FrontmatterConfig<
445
+ string,
446
+ "text"
447
+ >,
448
+ values,
449
+ errors,
450
+ setValues,
451
+ setErrors,
452
+ );
453
+ case "array-text":
454
+ return renderArrayText(
455
+ key,
456
+ config as ConfluencePageConfig.FrontmatterConfig<
457
+ string[],
458
+ "array-text"
459
+ >,
460
+ values,
461
+ errors,
462
+ setValues,
463
+ setErrors,
464
+ );
465
+ case "boolean":
466
+ return renderBoolean(
467
+ key,
468
+ config as ConfluencePageConfig.FrontmatterConfig<
469
+ boolean,
470
+ "boolean"
471
+ >,
472
+ values,
473
+ errors,
474
+ setValues,
475
+ setErrors,
476
+ );
477
+ case "options":
478
+ return renderOptions(
479
+ key,
480
+ config as ConfluencePageConfig.FrontmatterConfig<
481
+ ConfluencePageConfig.PageContentType,
482
+ "options"
483
+ >,
484
+ values,
485
+ errors,
486
+ setValues,
487
+ setErrors,
488
+ );
489
+ default:
490
+ return null;
491
+ }
492
+ })}
493
+ </tbody>
494
+ </table>
495
+ <button type="submit">Submit</button>
496
+ </form>
497
+ );
498
+ };
499
+
500
+ export class ConfluencePerPageForm extends Modal {
501
+ modalProps: ModalProps;
502
+
503
+ constructor(app: App, modalProps: ModalProps) {
504
+ super(app);
505
+ this.modalProps = modalProps;
506
+ }
507
+
508
+ override onOpen() {
509
+ const { contentEl } = this;
510
+ const test: FormProps = {
511
+ ...this.modalProps,
512
+ onSubmit: (values) => {
513
+ const boundClose = this.close.bind(this);
514
+ this.modalProps.onSubmit(values, boundClose);
515
+ },
516
+ };
517
+ ReactDOM.render(React.createElement(ConfluenceForm, test), contentEl);
518
+ }
519
+
520
+ override onClose() {
521
+ const { contentEl } = this;
522
+ ReactDOM.unmountComponentAtNode(contentEl);
523
+ contentEl.empty();
524
+ }
525
+ }
@@ -0,0 +1,124 @@
1
+ import { App, Setting, PluginSettingTab } from "obsidian";
2
+ import ConfluencePlugin from "./main";
3
+
4
+ export class ConfluenceSettingTab extends PluginSettingTab {
5
+ plugin: ConfluencePlugin;
6
+
7
+ constructor(app: App, plugin: ConfluencePlugin) {
8
+ super(app, plugin);
9
+ this.plugin = plugin;
10
+ }
11
+
12
+ display(): void {
13
+ const { containerEl } = this;
14
+
15
+ containerEl.empty();
16
+
17
+ containerEl.createEl("h2", {
18
+ text: "Settings for connecting to Atlassian Confluence",
19
+ });
20
+
21
+ new Setting(containerEl)
22
+ .setName("Confluence Domain")
23
+ .setDesc('Confluence Domain eg "https://mysite.atlassian.net"')
24
+ .addText((text) =>
25
+ text
26
+ .setPlaceholder("https://mysite.atlassian.net")
27
+ .setValue(this.plugin.settings.confluenceBaseUrl)
28
+ .onChange(async (value) => {
29
+ this.plugin.settings.confluenceBaseUrl = value;
30
+ await this.plugin.saveSettings();
31
+ }),
32
+ );
33
+
34
+ new Setting(containerEl)
35
+ .setName("Atlassian Username")
36
+ .setDesc('eg "username@domain.com"')
37
+ .addText((text) =>
38
+ text
39
+ .setPlaceholder("username@domain.com")
40
+ .setValue(this.plugin.settings.atlassianUserName)
41
+ .onChange(async (value) => {
42
+ this.plugin.settings.atlassianUserName = value;
43
+ await this.plugin.saveSettings();
44
+ }),
45
+ );
46
+
47
+ new Setting(containerEl)
48
+ .setName("Atlassian API Token")
49
+ .setDesc("")
50
+ .addText((text) =>
51
+ text
52
+ .setPlaceholder("")
53
+ .setValue(this.plugin.settings.atlassianApiToken)
54
+ .onChange(async (value) => {
55
+ this.plugin.settings.atlassianApiToken = value;
56
+ await this.plugin.saveSettings();
57
+ }),
58
+ );
59
+
60
+ new Setting(containerEl)
61
+ .setName("Confluence Parent Page ID")
62
+ .setDesc("Page ID to publish files under")
63
+ .addText((text) =>
64
+ text
65
+ .setPlaceholder("23232345645")
66
+ .setValue(this.plugin.settings.confluenceParentId)
67
+ .onChange(async (value) => {
68
+ this.plugin.settings.confluenceParentId = value;
69
+ await this.plugin.saveSettings();
70
+ }),
71
+ );
72
+
73
+ new Setting(containerEl)
74
+ .setName("Folder to publish")
75
+ .setDesc(
76
+ "Publish all files except notes that are excluded using YAML Frontmatter",
77
+ )
78
+ .addText((text) =>
79
+ text
80
+ .setPlaceholder("")
81
+ .setValue(this.plugin.settings.folderToPublish)
82
+ .onChange(async (value) => {
83
+ this.plugin.settings.folderToPublish = value;
84
+ await this.plugin.saveSettings();
85
+ }),
86
+ );
87
+
88
+ new Setting(containerEl)
89
+ .setName("First Header Page Name")
90
+ .setDesc("First header replaces file name as page title")
91
+ .addToggle((toggle) =>
92
+ toggle
93
+ .setValue(this.plugin.settings.firstHeadingPageTitle)
94
+ .onChange(async (value) => {
95
+ this.plugin.settings.firstHeadingPageTitle = value;
96
+ await this.plugin.saveSettings();
97
+ }),
98
+ );
99
+
100
+ new Setting(containerEl)
101
+ .setName("Mermaid Diagram Theme")
102
+ .setDesc("Pick the theme to apply to mermaid diagrams")
103
+ .addDropdown((dropdown) => {
104
+ /* eslint-disable @typescript-eslint/naming-convention */
105
+ dropdown
106
+ .addOptions({
107
+ "match-obsidian": "Match Obsidian",
108
+ "light-obsidian": "Obsidian Theme - Light",
109
+ "dark-obsidian": "Obsidian Theme - Dark",
110
+ default: "Mermaid - Default",
111
+ neutral: "Mermaid - Neutral",
112
+ dark: "Mermaid - Dark",
113
+ forest: "Mermaid - Forest",
114
+ })
115
+ .setValue(this.plugin.settings.mermaidTheme)
116
+ .onChange(async (value) => {
117
+ // @ts-expect-error
118
+ this.plugin.settings.mermaidTheme = value;
119
+ await this.plugin.saveSettings();
120
+ });
121
+ /* eslint-enable @typescript-eslint/naming-convention */
122
+ });
123
+ }
124
+ }