@real1ty-obsidian-plugins/utils 2.31.0 → 2.33.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.
@@ -47,6 +47,10 @@ export interface WhatsNewModalConfig {
47
47
  * Example: "https://docs.example.com" or "https://docs.example.com/"
48
48
  */
49
49
  documentation: string;
50
+ /**
51
+ * URL to GitHub repository.
52
+ */
53
+ github: string;
50
54
  /**
51
55
  * URL to tools page showcasing all plugins and productivity tools.
52
56
  * Defaults to DEFAULT_WHATS_NEW_LINKS.TOOLS if not provided.
@@ -62,6 +66,260 @@ export interface WhatsNewModalConfig {
62
66
  /**
63
67
  * Generic "What's New" modal that displays changelog entries between versions.
64
68
  * Supports custom CSS prefixes, plugin names, and configurable links.
69
+ *
70
+ * ## CSS Classes
71
+ *
72
+ * This modal uses the following CSS classes (with your custom prefix).
73
+ * Replace `{prefix}` with your `cssPrefix` value (e.g., "my-plugin").
74
+ *
75
+ * ### Main Container
76
+ * - `.{prefix}-whats-new-modal` - Applied to the main content element
77
+ * - `.{prefix}-whats-new-modal .modal` - Modal dialog styling (max-width, width)
78
+ *
79
+ * ### Title and Subtitle
80
+ * - Modal title is set via `setTitle()` - Obsidian handles the styling and X close button
81
+ * - `.{prefix}-whats-new-subtitle` - Subtitle text ("Changes since vX.X.X")
82
+ *
83
+ * ### Support Section
84
+ * - `.{prefix}-whats-new-support` - Support section container
85
+ * - Contains donation, tools, and YouTube links
86
+ * - Should have background, padding, border-radius
87
+ * - `.{prefix}-whats-new-support h3` - Support section heading
88
+ * - `.{prefix}-whats-new-support p` - Support section paragraph text (one per row)
89
+ * - `.{prefix}-whats-new-support a` - Links in support section (consistent styling)
90
+ * - `.{prefix}-whats-new-support a:hover` - Link hover state
91
+ *
92
+ * ### Changelog Content
93
+ * - `.{prefix}-whats-new-content` - Changelog content container
94
+ * - Should have max-height, overflow-y: auto for scrolling
95
+ * - `.{prefix}-whats-new-content h2` - Version headings in changelog
96
+ * - `.{prefix}-whats-new-content h3` - Section headings in changelog
97
+ * - `.{prefix}-whats-new-content ul` - Changelog lists
98
+ * - `.{prefix}-whats-new-content li` - Changelog list items
99
+ * - `.{prefix}-whats-new-content code` - Inline code in changelog
100
+ * - `.{prefix}-whats-new-content pre` - Code blocks in changelog
101
+ * - `.{prefix}-whats-new-content a.external-link` - External links (auto-added)
102
+ * - `.{prefix}-whats-new-empty` - Empty state message
103
+ *
104
+ * ### Sticky Footer
105
+ * - `.{prefix}-whats-new-sticky-footer` - Footer container (should be sticky)
106
+ * - Has border-top to separate from content
107
+ * - `.{prefix}-whats-new-buttons` - Button container
108
+ * - `.{prefix}-whats-new-buttons button` - Individual buttons
109
+ *
110
+ * ## Example CSS Implementation
111
+ *
112
+ * ```css
113
+ * // Main Container
114
+ * .my-plugin-whats-new-modal .modal {
115
+ * max-width: 800px;
116
+ * width: 90%;
117
+ * }
118
+ *
119
+ * // Plugin Name Link (in title)
120
+ * .my-plugin-whats-new-plugin-name {
121
+ * color: var(--link-color);
122
+ * text-decoration: none;
123
+ * transition: all 0.2s ease;
124
+ * position: relative;
125
+ * font-weight: 600;
126
+ * }
127
+ *
128
+ * .my-plugin-whats-new-plugin-name:hover {
129
+ * color: var(--link-color-hover);
130
+ * text-decoration: none;
131
+ * }
132
+ *
133
+ * .my-plugin-whats-new-plugin-name::after {
134
+ * content: '';
135
+ * position: absolute;
136
+ * bottom: -2px;
137
+ * left: 0;
138
+ * width: 0;
139
+ * height: 2px;
140
+ * background-color: var(--interactive-accent);
141
+ * transition: width 0.3s ease;
142
+ * }
143
+ *
144
+ * .my-plugin-whats-new-plugin-name:hover::after {
145
+ * width: 100%;
146
+ * }
147
+ *
148
+ * // Subtitle
149
+ * .my-plugin-whats-new-subtitle {
150
+ * color: var(--text-muted);
151
+ * font-size: 0.9rem;
152
+ * margin: 0 0 1rem 0;
153
+ * }
154
+ *
155
+ * // Support Section (with donation, tools, and YouTube links)
156
+ * .my-plugin-whats-new-support {
157
+ * margin: 0 0 1rem 0;
158
+ * padding: 1rem;
159
+ * background-color: var(--background-secondary);
160
+ * border-radius: 8px;
161
+ * }
162
+ *
163
+ * .my-plugin-whats-new-support h3 {
164
+ * margin-top: 0;
165
+ * margin-bottom: 0.5rem;
166
+ * font-size: 1rem;
167
+ * }
168
+ *
169
+ * .my-plugin-whats-new-support p {
170
+ * margin: 0.5rem 0;
171
+ * color: var(--text-normal);
172
+ * }
173
+ *
174
+ * .my-plugin-whats-new-support a {
175
+ * color: var(--link-color);
176
+ * text-decoration: none;
177
+ * transition: all 0.2s ease;
178
+ * position: relative;
179
+ * }
180
+ *
181
+ * .my-plugin-whats-new-support a:hover {
182
+ * color: var(--link-color-hover);
183
+ * text-decoration: none;
184
+ * }
185
+ *
186
+ * .my-plugin-whats-new-support a::after {
187
+ * content: '';
188
+ * position: absolute;
189
+ * bottom: -2px;
190
+ * left: 0;
191
+ * width: 0;
192
+ * height: 2px;
193
+ * background-color: var(--interactive-accent);
194
+ * transition: width 0.3s ease;
195
+ * }
196
+ *
197
+ * .my-plugin-whats-new-support a:hover::after {
198
+ * width: 100%;
199
+ * }
200
+ *
201
+ * // Changelog Content (Scrollable Area)
202
+ * .my-plugin-whats-new-content {
203
+ * max-height: 400px;
204
+ * overflow-y: auto;
205
+ * margin-bottom: 1rem;
206
+ * padding-right: 0.5rem;
207
+ * border-radius: 8px;
208
+ * }
209
+ *
210
+ * .my-plugin-whats-new-content h2 {
211
+ * font-size: 1.3rem;
212
+ * margin-top: 1.5rem;
213
+ * margin-bottom: 0.5rem;
214
+ * color: var(--text-accent);
215
+ * }
216
+ *
217
+ * .my-plugin-whats-new-content h3 {
218
+ * font-size: 1.1rem;
219
+ * margin-top: 1rem;
220
+ * margin-bottom: 0.5rem;
221
+ * }
222
+ *
223
+ * .my-plugin-whats-new-content ul {
224
+ * padding-left: 1.5rem;
225
+ * }
226
+ *
227
+ * .my-plugin-whats-new-content li {
228
+ * margin-bottom: 0.5rem;
229
+ * line-height: 1.6;
230
+ * }
231
+ *
232
+ * .my-plugin-whats-new-content code {
233
+ * background: var(--code-background);
234
+ * padding: 0.2em 0.4em;
235
+ * border-radius: 3px;
236
+ * font-size: 0.9em;
237
+ * }
238
+ *
239
+ * .my-plugin-whats-new-content pre {
240
+ * background: var(--code-background);
241
+ * padding: 1rem;
242
+ * border-radius: 6px;
243
+ * overflow-x: auto;
244
+ * }
245
+ *
246
+ * .my-plugin-whats-new-content a.external-link {
247
+ * color: var(--link-external-color);
248
+ * }
249
+ *
250
+ * .my-plugin-whats-new-content a.external-link::after {
251
+ * content: "↗";
252
+ * margin-left: 0.2em;
253
+ * font-size: 0.8em;
254
+ * }
255
+ *
256
+ * .my-plugin-whats-new-empty {
257
+ * text-align: center;
258
+ * color: var(--text-muted);
259
+ * padding: 2rem;
260
+ * font-style: italic;
261
+ * }
262
+ *
263
+ * // Sticky Footer
264
+ * .my-plugin-whats-new-sticky-footer {
265
+ * position: sticky;
266
+ * bottom: 0;
267
+ * background: var(--background-primary);
268
+ * padding-top: 0.75rem;
269
+ * margin-top: 0;
270
+ * z-index: 10;
271
+ * border-top: 1px solid var(--background-modifier-border);
272
+ * }
273
+ *
274
+ * .my-plugin-whats-new-buttons {
275
+ * display: flex;
276
+ * gap: 0.5rem;
277
+ * justify-content: space-between;
278
+ * flex-wrap: wrap;
279
+ * padding-bottom: 0.5rem;
280
+ * width: 100%;
281
+ * }
282
+ *
283
+ * .my-plugin-whats-new-buttons button {
284
+ * flex: 1;
285
+ * min-width: 0;
286
+ * padding: 0.5rem 1rem;
287
+ * border-radius: 4px;
288
+ * cursor: pointer;
289
+ * border: 1px solid var(--background-modifier-border);
290
+ * background: var(--interactive-normal);
291
+ * color: var(--text-normal);
292
+ * transition: all 0.2s ease;
293
+ * text-align: center;
294
+ * }
295
+ *
296
+ * .my-plugin-whats-new-buttons button:hover {
297
+ * background: var(--interactive-hover);
298
+ * border-color: var(--interactive-accent);
299
+ * transform: translateY(-1px);
300
+ * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
301
+ * }
302
+ *
303
+ * .my-plugin-whats-new-buttons button:active {
304
+ * transform: translateY(0);
305
+ * box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
306
+ * }
307
+ * ```
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * const modal = new WhatsNewModal(app, plugin, {
312
+ * cssPrefix: "my-plugin",
313
+ * pluginName: "My Plugin",
314
+ * changelogContent: rawChangelog,
315
+ * links: {
316
+ * support: "https://...",
317
+ * changelog: "https://...",
318
+ * documentation: "https://..."
319
+ * }
320
+ * }, "1.0.0", "2.0.0");
321
+ * modal.open();
322
+ * ```
65
323
  */
66
324
  export declare class WhatsNewModal extends Modal {
67
325
  private plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"whats-new-modal.d.ts","sourceRoot":"","sources":["../../src/components/whats-new-modal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAoB,KAAK,EAAE,MAAM,UAAU,CAAC;AAGnD;;;GAGG;AACH,eAAO,MAAM,uBAAuB;IACnC;;OAEG;;IAGH;;;OAGG;;CAEM,CAAC;AAEX,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,EAAE;QACN;;WAEG;QACH,OAAO,EAAE,MAAM,CAAC;QAEhB;;WAEG;QACH,SAAS,EAAE,MAAM,CAAC;QAElB;;;WAGG;QACH,aAAa,EAAE,MAAM,CAAC;QAEtB;;;WAGG;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QAEf;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACF;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGtC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,SAAS;gBAJjB,GAAG,EAAE,GAAG,EACA,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAK1B;;OAEG;IACH,OAAO,CAAC,GAAG;IAIX;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAmC5B,MAAM;IA6IZ,OAAO;CAGP"}
1
+ {"version":3,"file":"whats-new-modal.d.ts","sourceRoot":"","sources":["../../src/components/whats-new-modal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAoB,KAAK,EAAE,MAAM,UAAU,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,uBAAuB;IACnC;;OAEG;;IAGH;;;OAGG;;CAEM,CAAC;AAEX,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,EAAE;QACN;;WAEG;QACH,OAAO,EAAE,MAAM,CAAC;QAEhB;;WAEG;QACH,SAAS,EAAE,MAAM,CAAC;QAElB;;;WAGG;QACH,aAAa,EAAE,MAAM,CAAC;QAEtB;;WAEG;QACH,MAAM,EAAE,MAAM,CAAC;QAEf;;;WAGG;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QAEf;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiQG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGtC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,SAAS;gBAJjB,GAAG,EAAE,GAAG,EACA,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAK1B;;OAEG;IACH,OAAO,CAAC,GAAG;IAIX;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAmC5B,MAAM;IAoJZ,OAAO;CAGP"}
@@ -1,6 +1,6 @@
1
1
  import { __awaiter } from "tslib";
2
+ import { formatChangelogSections, getChangelogSince } from "@real1ty-obsidian-plugins/utils";
2
3
  import { MarkdownRenderer, Modal } from "obsidian";
3
- import { formatChangelogSections, getChangelogSince } from "../string/changelog-parser";
4
4
  /**
5
5
  * Default URLs for the What's New modal.
6
6
  * These can be overridden in the config.
@@ -19,6 +19,260 @@ export const DEFAULT_WHATS_NEW_LINKS = {
19
19
  /**
20
20
  * Generic "What's New" modal that displays changelog entries between versions.
21
21
  * Supports custom CSS prefixes, plugin names, and configurable links.
22
+ *
23
+ * ## CSS Classes
24
+ *
25
+ * This modal uses the following CSS classes (with your custom prefix).
26
+ * Replace `{prefix}` with your `cssPrefix` value (e.g., "my-plugin").
27
+ *
28
+ * ### Main Container
29
+ * - `.{prefix}-whats-new-modal` - Applied to the main content element
30
+ * - `.{prefix}-whats-new-modal .modal` - Modal dialog styling (max-width, width)
31
+ *
32
+ * ### Title and Subtitle
33
+ * - Modal title is set via `setTitle()` - Obsidian handles the styling and X close button
34
+ * - `.{prefix}-whats-new-subtitle` - Subtitle text ("Changes since vX.X.X")
35
+ *
36
+ * ### Support Section
37
+ * - `.{prefix}-whats-new-support` - Support section container
38
+ * - Contains donation, tools, and YouTube links
39
+ * - Should have background, padding, border-radius
40
+ * - `.{prefix}-whats-new-support h3` - Support section heading
41
+ * - `.{prefix}-whats-new-support p` - Support section paragraph text (one per row)
42
+ * - `.{prefix}-whats-new-support a` - Links in support section (consistent styling)
43
+ * - `.{prefix}-whats-new-support a:hover` - Link hover state
44
+ *
45
+ * ### Changelog Content
46
+ * - `.{prefix}-whats-new-content` - Changelog content container
47
+ * - Should have max-height, overflow-y: auto for scrolling
48
+ * - `.{prefix}-whats-new-content h2` - Version headings in changelog
49
+ * - `.{prefix}-whats-new-content h3` - Section headings in changelog
50
+ * - `.{prefix}-whats-new-content ul` - Changelog lists
51
+ * - `.{prefix}-whats-new-content li` - Changelog list items
52
+ * - `.{prefix}-whats-new-content code` - Inline code in changelog
53
+ * - `.{prefix}-whats-new-content pre` - Code blocks in changelog
54
+ * - `.{prefix}-whats-new-content a.external-link` - External links (auto-added)
55
+ * - `.{prefix}-whats-new-empty` - Empty state message
56
+ *
57
+ * ### Sticky Footer
58
+ * - `.{prefix}-whats-new-sticky-footer` - Footer container (should be sticky)
59
+ * - Has border-top to separate from content
60
+ * - `.{prefix}-whats-new-buttons` - Button container
61
+ * - `.{prefix}-whats-new-buttons button` - Individual buttons
62
+ *
63
+ * ## Example CSS Implementation
64
+ *
65
+ * ```css
66
+ * // Main Container
67
+ * .my-plugin-whats-new-modal .modal {
68
+ * max-width: 800px;
69
+ * width: 90%;
70
+ * }
71
+ *
72
+ * // Plugin Name Link (in title)
73
+ * .my-plugin-whats-new-plugin-name {
74
+ * color: var(--link-color);
75
+ * text-decoration: none;
76
+ * transition: all 0.2s ease;
77
+ * position: relative;
78
+ * font-weight: 600;
79
+ * }
80
+ *
81
+ * .my-plugin-whats-new-plugin-name:hover {
82
+ * color: var(--link-color-hover);
83
+ * text-decoration: none;
84
+ * }
85
+ *
86
+ * .my-plugin-whats-new-plugin-name::after {
87
+ * content: '';
88
+ * position: absolute;
89
+ * bottom: -2px;
90
+ * left: 0;
91
+ * width: 0;
92
+ * height: 2px;
93
+ * background-color: var(--interactive-accent);
94
+ * transition: width 0.3s ease;
95
+ * }
96
+ *
97
+ * .my-plugin-whats-new-plugin-name:hover::after {
98
+ * width: 100%;
99
+ * }
100
+ *
101
+ * // Subtitle
102
+ * .my-plugin-whats-new-subtitle {
103
+ * color: var(--text-muted);
104
+ * font-size: 0.9rem;
105
+ * margin: 0 0 1rem 0;
106
+ * }
107
+ *
108
+ * // Support Section (with donation, tools, and YouTube links)
109
+ * .my-plugin-whats-new-support {
110
+ * margin: 0 0 1rem 0;
111
+ * padding: 1rem;
112
+ * background-color: var(--background-secondary);
113
+ * border-radius: 8px;
114
+ * }
115
+ *
116
+ * .my-plugin-whats-new-support h3 {
117
+ * margin-top: 0;
118
+ * margin-bottom: 0.5rem;
119
+ * font-size: 1rem;
120
+ * }
121
+ *
122
+ * .my-plugin-whats-new-support p {
123
+ * margin: 0.5rem 0;
124
+ * color: var(--text-normal);
125
+ * }
126
+ *
127
+ * .my-plugin-whats-new-support a {
128
+ * color: var(--link-color);
129
+ * text-decoration: none;
130
+ * transition: all 0.2s ease;
131
+ * position: relative;
132
+ * }
133
+ *
134
+ * .my-plugin-whats-new-support a:hover {
135
+ * color: var(--link-color-hover);
136
+ * text-decoration: none;
137
+ * }
138
+ *
139
+ * .my-plugin-whats-new-support a::after {
140
+ * content: '';
141
+ * position: absolute;
142
+ * bottom: -2px;
143
+ * left: 0;
144
+ * width: 0;
145
+ * height: 2px;
146
+ * background-color: var(--interactive-accent);
147
+ * transition: width 0.3s ease;
148
+ * }
149
+ *
150
+ * .my-plugin-whats-new-support a:hover::after {
151
+ * width: 100%;
152
+ * }
153
+ *
154
+ * // Changelog Content (Scrollable Area)
155
+ * .my-plugin-whats-new-content {
156
+ * max-height: 400px;
157
+ * overflow-y: auto;
158
+ * margin-bottom: 1rem;
159
+ * padding-right: 0.5rem;
160
+ * border-radius: 8px;
161
+ * }
162
+ *
163
+ * .my-plugin-whats-new-content h2 {
164
+ * font-size: 1.3rem;
165
+ * margin-top: 1.5rem;
166
+ * margin-bottom: 0.5rem;
167
+ * color: var(--text-accent);
168
+ * }
169
+ *
170
+ * .my-plugin-whats-new-content h3 {
171
+ * font-size: 1.1rem;
172
+ * margin-top: 1rem;
173
+ * margin-bottom: 0.5rem;
174
+ * }
175
+ *
176
+ * .my-plugin-whats-new-content ul {
177
+ * padding-left: 1.5rem;
178
+ * }
179
+ *
180
+ * .my-plugin-whats-new-content li {
181
+ * margin-bottom: 0.5rem;
182
+ * line-height: 1.6;
183
+ * }
184
+ *
185
+ * .my-plugin-whats-new-content code {
186
+ * background: var(--code-background);
187
+ * padding: 0.2em 0.4em;
188
+ * border-radius: 3px;
189
+ * font-size: 0.9em;
190
+ * }
191
+ *
192
+ * .my-plugin-whats-new-content pre {
193
+ * background: var(--code-background);
194
+ * padding: 1rem;
195
+ * border-radius: 6px;
196
+ * overflow-x: auto;
197
+ * }
198
+ *
199
+ * .my-plugin-whats-new-content a.external-link {
200
+ * color: var(--link-external-color);
201
+ * }
202
+ *
203
+ * .my-plugin-whats-new-content a.external-link::after {
204
+ * content: "↗";
205
+ * margin-left: 0.2em;
206
+ * font-size: 0.8em;
207
+ * }
208
+ *
209
+ * .my-plugin-whats-new-empty {
210
+ * text-align: center;
211
+ * color: var(--text-muted);
212
+ * padding: 2rem;
213
+ * font-style: italic;
214
+ * }
215
+ *
216
+ * // Sticky Footer
217
+ * .my-plugin-whats-new-sticky-footer {
218
+ * position: sticky;
219
+ * bottom: 0;
220
+ * background: var(--background-primary);
221
+ * padding-top: 0.75rem;
222
+ * margin-top: 0;
223
+ * z-index: 10;
224
+ * border-top: 1px solid var(--background-modifier-border);
225
+ * }
226
+ *
227
+ * .my-plugin-whats-new-buttons {
228
+ * display: flex;
229
+ * gap: 0.5rem;
230
+ * justify-content: space-between;
231
+ * flex-wrap: wrap;
232
+ * padding-bottom: 0.5rem;
233
+ * width: 100%;
234
+ * }
235
+ *
236
+ * .my-plugin-whats-new-buttons button {
237
+ * flex: 1;
238
+ * min-width: 0;
239
+ * padding: 0.5rem 1rem;
240
+ * border-radius: 4px;
241
+ * cursor: pointer;
242
+ * border: 1px solid var(--background-modifier-border);
243
+ * background: var(--interactive-normal);
244
+ * color: var(--text-normal);
245
+ * transition: all 0.2s ease;
246
+ * text-align: center;
247
+ * }
248
+ *
249
+ * .my-plugin-whats-new-buttons button:hover {
250
+ * background: var(--interactive-hover);
251
+ * border-color: var(--interactive-accent);
252
+ * transform: translateY(-1px);
253
+ * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
254
+ * }
255
+ *
256
+ * .my-plugin-whats-new-buttons button:active {
257
+ * transform: translateY(0);
258
+ * box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
259
+ * }
260
+ * ```
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const modal = new WhatsNewModal(app, plugin, {
265
+ * cssPrefix: "my-plugin",
266
+ * pluginName: "My Plugin",
267
+ * changelogContent: rawChangelog,
268
+ * links: {
269
+ * support: "https://...",
270
+ * changelog: "https://...",
271
+ * documentation: "https://..."
272
+ * }
273
+ * }, "1.0.0", "2.0.0");
274
+ * modal.open();
275
+ * ```
22
276
  */
23
277
  export class WhatsNewModal extends Modal {
24
278
  constructor(app, plugin, config, fromVersion, toVersion) {
@@ -81,12 +335,23 @@ export class WhatsNewModal extends Modal {
81
335
  const { contentEl } = this;
82
336
  contentEl.empty();
83
337
  this.addCls(contentEl, "whats-new-modal");
84
- // Header section
85
- const header = contentEl.createDiv({ cls: this.cls("whats-new-header") });
86
- header.createEl("h2", {
87
- text: `${this.config.pluginName} updated to v${this.toVersion}`,
338
+ this.setTitle("");
339
+ const titleEl = this.titleEl;
340
+ titleEl.empty();
341
+ const pluginNameLink = titleEl.createEl("a", {
342
+ text: this.config.pluginName,
343
+ cls: this.cls("whats-new-plugin-name"),
344
+ href: "#",
88
345
  });
89
- header.createEl("p", {
346
+ pluginNameLink.addEventListener("click", (e) => {
347
+ e.preventDefault();
348
+ window.open(this.config.links.github, "_blank");
349
+ });
350
+ titleEl.createSpan({
351
+ text: ` updated to v${this.toVersion}`,
352
+ });
353
+ // Subtitle
354
+ contentEl.createEl("p", {
90
355
  text: `Changes since v${this.fromVersion}`,
91
356
  cls: this.cls("whats-new-subtitle"),
92
357
  });
@@ -94,42 +359,29 @@ export class WhatsNewModal extends Modal {
94
359
  const supportSection = contentEl.createDiv({
95
360
  cls: this.cls("whats-new-support"),
96
361
  });
97
- supportSection.createEl("h3", { text: "Support My Work" });
98
- const supportText = supportSection.createEl("p");
99
- supportText.createSpan({ text: "If you enjoy using this plugin, please consider " });
100
- supportText.createEl("a", {
101
- text: "supporting my work",
362
+ supportSection.createEl("h3", { text: "Support the development of this plugin" });
363
+ const introText = supportSection.createEl("p");
364
+ introText.setText("If this plugin saves you time or improves how you work in Obsidian, consider supporting its development. Your support helps fund ongoing maintenance, new features, and long-term stability.");
365
+ const supportLinkText = supportSection.createEl("p");
366
+ supportLinkText.createSpan({ text: "👉 " });
367
+ supportLinkText.createEl("a", {
368
+ text: "Support my work",
102
369
  href: this.config.links.support,
103
370
  });
104
- supportText.createSpan({
105
- text: ". Your support helps keep this plugin maintained and improved!",
106
- });
107
- // Discover more section
108
- const discoverSection = contentEl.createDiv({
109
- cls: this.cls("whats-new-discover"),
110
- });
111
- discoverSection.createEl("h3", { text: "Discover More" });
112
- // Other tools
113
- const toolsText = discoverSection.createEl("p");
114
- toolsText.createSpan({ text: "🔧 Check out my " });
115
- toolsText.createEl("a", {
116
- text: "other plugins and productivity tools",
371
+ const exploreText = supportSection.createEl("p");
372
+ exploreText.createSpan({ text: "You can also explore my " });
373
+ exploreText.createEl("a", {
374
+ text: "other Obsidian plugins and productivity tools",
117
375
  href: (_a = this.config.links.tools) !== null && _a !== void 0 ? _a : DEFAULT_WHATS_NEW_LINKS.TOOLS,
118
376
  });
119
- toolsText.createSpan({
120
- text: " to enhance your workflow even further.",
121
- });
122
- // YouTube channel
123
- const youtubeText = discoverSection.createEl("p");
124
- youtubeText.createSpan({ text: "📺 Subscribe to my " });
125
- youtubeText.createEl("a", {
377
+ exploreText.createSpan({ text: ", or follow my " });
378
+ exploreText.createEl("a", {
126
379
  text: "YouTube channel",
127
380
  href: (_b = this.config.links.youtube) !== null && _b !== void 0 ? _b : DEFAULT_WHATS_NEW_LINKS.YOUTUBE,
128
381
  });
129
- youtubeText.createSpan({
130
- text: " for Obsidian tutorials and productivity tips!",
382
+ exploreText.createSpan({
383
+ text: " for in-depth tutorials and workflow ideas.",
131
384
  });
132
- contentEl.createEl("hr");
133
385
  // Changelog content
134
386
  const changelogSections = getChangelogSince(this.config.changelogContent, this.fromVersion, this.toVersion);
135
387
  if (changelogSections.length === 0) {
@@ -147,13 +399,24 @@ export class WhatsNewModal extends Modal {
147
399
  // Make external links clickable
148
400
  this.makeExternalLinksClickable(changelogContainer);
149
401
  }
402
+ // Sticky footer section (hr + buttons)
403
+ const stickyFooter = contentEl.createDiv({
404
+ cls: this.cls("whats-new-sticky-footer"),
405
+ });
150
406
  // Action buttons
151
- const buttonContainer = contentEl.createDiv({
407
+ const buttonContainer = stickyFooter.createDiv({
152
408
  cls: this.cls("whats-new-buttons"),
153
409
  });
410
+ // GitHub button
411
+ const githubBtn = buttonContainer.createEl("button", {
412
+ text: "GitHub",
413
+ });
414
+ githubBtn.addEventListener("click", () => {
415
+ window.open(this.config.links.github, "_blank");
416
+ });
154
417
  // Full changelog button
155
418
  const changelogBtn = buttonContainer.createEl("button", {
156
- text: "Full Changelog",
419
+ text: "Changelog",
157
420
  });
158
421
  changelogBtn.addEventListener("click", () => {
159
422
  window.open(this.config.links.changelog, "_blank");
@@ -167,7 +430,7 @@ export class WhatsNewModal extends Modal {
167
430
  });
168
431
  // Tools button
169
432
  const toolsBtn = buttonContainer.createEl("button", {
170
- text: "My Tools",
433
+ text: "Other Plugins",
171
434
  });
172
435
  toolsBtn.addEventListener("click", () => {
173
436
  var _a;
@@ -175,18 +438,12 @@ export class WhatsNewModal extends Modal {
175
438
  });
176
439
  // YouTube button
177
440
  const youtubeBtn = buttonContainer.createEl("button", {
178
- text: "YouTube Channel",
441
+ text: "YouTube",
179
442
  });
180
443
  youtubeBtn.addEventListener("click", () => {
181
444
  var _a;
182
445
  window.open((_a = this.config.links.youtube) !== null && _a !== void 0 ? _a : DEFAULT_WHATS_NEW_LINKS.YOUTUBE, "_blank");
183
446
  });
184
- // Close button (always present)
185
- const closeBtn = buttonContainer.createEl("button", {
186
- text: "Close",
187
- cls: this.cls("mod-cta"),
188
- });
189
- closeBtn.addEventListener("click", () => this.close());
190
447
  });
191
448
  }
192
449
  onClose() {
@@ -1 +1 @@
1
- {"version":3,"file":"whats-new-modal.js","sourceRoot":"","sources":["../../src/components/whats-new-modal.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAExF;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACtC;;OAEG;IACH,KAAK,EAAE,2CAA2C;IAElD;;;OAGG;IACH,OAAO,EAAE,oEAAoE;CACpE,CAAC;AAsDX;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IACvC,YACC,GAAQ,EACA,MAAc,EACd,MAA2B,EAC3B,WAAmB,EACnB,SAAiB;QAEzB,KAAK,CAAC,GAAG,CAAC,CAAC;QALH,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAqB;QAC3B,gBAAW,GAAX,WAAW,CAAQ;QACnB,cAAS,GAAT,SAAS,CAAQ;IAG1B,CAAC;IAED;;OAEG;IACK,GAAG,CAAC,MAAc;QACzB,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,EAAE,CAAC;IAC7C,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,EAAe,EAAE,MAAc;QAC7C,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACK,0BAA0B,CAAC,SAAsB;QACxD,MAAM,KAAK,GAAG,SAAS,CAAC,gBAAgB,CAAoB,SAAS,CAAC,CAAC;QAEvE,0CAA0C;QAC1C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAClC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,IAAI,QAAQ,GAAkB,IAAI,CAAC;YAEnC,gCAAgC;YAChC,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/D,QAAQ,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,0CAA0C;iBACrC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,8DAA8D;gBAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;gBAChD,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC9E,QAAQ,GAAG,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC;YACvC,CAAC;YAED,uCAAuC;YACvC,IAAI,QAAQ,EAAE,CAAC;gBACd,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAiB,EAAE,EAAE;oBACpD,KAAK,CAAC,cAAc,EAAE,CAAC;oBACvB,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACjC,CAAC,CAAC,CAAC;gBAEH,kDAAkD;gBAClD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACrC,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAEK,MAAM;;;YACX,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;YAC3B,SAAS,CAAC,KAAK,EAAE,CAAC;YAElB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;YAE1C,iBAAiB;YACjB,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;YAC1E,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;gBACrB,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,gBAAgB,IAAI,CAAC,SAAS,EAAE;aAC/D,CAAC,CAAC;YAEH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACpB,IAAI,EAAE,kBAAkB,IAAI,CAAC,WAAW,EAAE;gBAC1C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC;aACnC,CAAC,CAAC;YAEH,kBAAkB;YAClB,MAAM,cAAc,GAAG,SAAS,CAAC,SAAS,CAAC;gBAC1C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;aAClC,CAAC,CAAC;YAEH,cAAc,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAE3D,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACjD,WAAW,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,kDAAkD,EAAE,CAAC,CAAC;YACrF,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACzB,IAAI,EAAE,oBAAoB;gBAC1B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;aAC/B,CAAC,CAAC;YACH,WAAW,CAAC,UAAU,CAAC;gBACtB,IAAI,EAAE,gEAAgE;aACtE,CAAC,CAAC;YAEH,wBAAwB;YACxB,MAAM,eAAe,GAAG,SAAS,CAAC,SAAS,CAAC;gBAC3C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC;aACnC,CAAC,CAAC;YAEH,eAAe,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;YAE1D,cAAc;YACd,MAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAChD,SAAS,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACnD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACvB,IAAI,EAAE,sCAAsC;gBAC5C,IAAI,EAAE,MAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,mCAAI,uBAAuB,CAAC,KAAK;aAC9D,CAAC,CAAC;YACH,SAAS,CAAC,UAAU,CAAC;gBACpB,IAAI,EAAE,yCAAyC;aAC/C,CAAC,CAAC;YAEH,kBAAkB;YAClB,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAClD,WAAW,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC,CAAC;YACxD,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACzB,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,MAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,mCAAI,uBAAuB,CAAC,OAAO;aAClE,CAAC,CAAC;YACH,WAAW,CAAC,UAAU,CAAC;gBACtB,IAAI,EAAE,gDAAgD;aACtD,CAAC,CAAC;YAEH,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAEzB,oBAAoB;YACpB,MAAM,iBAAiB,GAAG,iBAAiB,CAC1C,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAC5B,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,SAAS,CACd,CAAC;YAEF,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpC,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE;oBACvB,IAAI,EAAE,8CAA8C;oBACpD,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC;iBAChC,CAAC,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACP,MAAM,kBAAkB,GAAG,SAAS,CAAC,SAAS,CAAC;oBAC9C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;iBAClC,CAAC,CAAC;gBAEH,MAAM,eAAe,GAAG,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;gBAEnE,MAAM,gBAAgB,CAAC,MAAM,CAC5B,IAAI,CAAC,GAAG,EACR,eAAe,EACf,kBAAkB,EAClB,GAAG,EACH,IAAI,CAAC,MAAM,CACX,CAAC;gBAEF,gCAAgC;gBAChC,IAAI,CAAC,0BAA0B,CAAC,kBAAkB,CAAC,CAAC;YACrD,CAAC;YAED,iBAAiB;YACjB,MAAM,eAAe,GAAG,SAAS,CAAC,SAAS,CAAC;gBAC3C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;aAClC,CAAC,CAAC;YAEH,wBAAwB;YACxB,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBACvD,IAAI,EAAE,gBAAgB;aACtB,CAAC,CAAC;YACH,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAC3C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;YAEH,uBAAuB;YACvB,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBAClD,IAAI,EAAE,eAAe;aACrB,CAAC,CAAC;YACH,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBACtC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;YACxD,CAAC,CAAC,CAAC;YAEH,eAAe;YACf,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBACnD,IAAI,EAAE,UAAU;aAChB,CAAC,CAAC;YACH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;;gBACvC,MAAM,CAAC,IAAI,CAAC,MAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,mCAAI,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACjF,CAAC,CAAC,CAAC;YAEH,iBAAiB;YACjB,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBACrD,IAAI,EAAE,iBAAiB;aACvB,CAAC,CAAC;YACH,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;;gBACzC,MAAM,CAAC,IAAI,CAAC,MAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,mCAAI,uBAAuB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACrF,CAAC,CAAC,CAAC;YAEH,gCAAgC;YAChC,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBACnD,IAAI,EAAE,OAAO;gBACb,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;aACxB,CAAC,CAAC;YACH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC;KAAA;IAED,OAAO;QACN,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CACD","sourcesContent":["import type { App, Plugin } from \"obsidian\";\nimport { MarkdownRenderer, Modal } from \"obsidian\";\nimport { formatChangelogSections, getChangelogSince } from \"../string/changelog-parser\";\n\n/**\n * Default URLs for the What's New modal.\n * These can be overridden in the config.\n */\nexport const DEFAULT_WHATS_NEW_LINKS = {\n\t/**\n\t * Default tools page showcasing all plugins and productivity software.\n\t */\n\tTOOLS: \"https://matejvavroproductivity.com/tools/\",\n\n\t/**\n\t * Default YouTube channel with Obsidian tutorials and productivity tips.\n\t * Includes subscription confirmation parameter.\n\t */\n\tYOUTUBE: \"https://www.youtube.com/@MatejVavroProductivity?sub_confirmation=1\",\n} as const;\n\nexport interface WhatsNewModalConfig {\n\t/**\n\t * The CSS class prefix/suffix to use for styling.\n\t * Example: \"custom-calendar\" will generate classes like \"custom-calendar-whats-new-modal\"\n\t */\n\tcssPrefix: string;\n\n\t/**\n\t * Display name of the plugin.\n\t * Example: \"Custom Calendar\"\n\t */\n\tpluginName: string;\n\n\t/**\n\t * Raw changelog markdown content to parse.\n\t */\n\tchangelogContent: string;\n\n\t/**\n\t * Links to external resources.\n\t */\n\tlinks: {\n\t\t/**\n\t\t * URL to support/donate page.\n\t\t */\n\t\tsupport: string;\n\n\t\t/**\n\t\t * URL to full changelog page.\n\t\t */\n\t\tchangelog: string;\n\n\t\t/**\n\t\t * Base URL for documentation (used to resolve relative links in changelog).\n\t\t * Example: \"https://docs.example.com\" or \"https://docs.example.com/\"\n\t\t */\n\t\tdocumentation: string;\n\n\t\t/**\n\t\t * URL to tools page showcasing all plugins and productivity tools.\n\t\t * Defaults to DEFAULT_WHATS_NEW_LINKS.TOOLS if not provided.\n\t\t */\n\t\ttools?: string;\n\n\t\t/**\n\t\t * URL to YouTube channel with tutorials and productivity tips.\n\t\t * Defaults to DEFAULT_WHATS_NEW_LINKS.YOUTUBE if not provided.\n\t\t */\n\t\tyoutube?: string;\n\t};\n}\n\n/**\n * Generic \"What's New\" modal that displays changelog entries between versions.\n * Supports custom CSS prefixes, plugin names, and configurable links.\n */\nexport class WhatsNewModal extends Modal {\n\tconstructor(\n\t\tapp: App,\n\t\tprivate plugin: Plugin,\n\t\tprivate config: WhatsNewModalConfig,\n\t\tprivate fromVersion: string,\n\t\tprivate toVersion: string\n\t) {\n\t\tsuper(app);\n\t}\n\n\t/**\n\t * Helper to create CSS class names with the configured prefix.\n\t */\n\tprivate cls(suffix: string): string {\n\t\treturn `${this.config.cssPrefix}-${suffix}`;\n\t}\n\n\t/**\n\t * Helper to add CSS class to an element.\n\t */\n\tprivate addCls(el: HTMLElement, suffix: string): void {\n\t\tel.classList.add(this.cls(suffix));\n\t}\n\n\t/**\n\t * Makes external links in rendered markdown clickable by adding click handlers.\n\t * Handles both absolute URLs (http/https) and relative URLs (starting with /).\n\t * Relative URLs are resolved against the documentation base URL.\n\t */\n\tprivate makeExternalLinksClickable(container: HTMLElement): void {\n\t\tconst links = container.querySelectorAll<HTMLAnchorElement>(\"a[href]\");\n\n\t\t// Convert NodeList to Array for iteration\n\t\tArray.from(links).forEach((link) => {\n\t\t\tconst href = link.getAttribute(\"href\");\n\t\t\tif (!href) return;\n\n\t\t\tlet finalUrl: string | null = null;\n\n\t\t\t// Handle absolute HTTP(S) links\n\t\t\tif (href.startsWith(\"http://\") || href.startsWith(\"https://\")) {\n\t\t\t\tfinalUrl = href;\n\t\t\t}\n\t\t\t// Handle relative links (starting with /)\n\t\t\telse if (href.startsWith(\"/\")) {\n\t\t\t\t// Get base documentation URL and ensure proper slash handling\n\t\t\t\tconst baseUrl = this.config.links.documentation;\n\t\t\t\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\t\t\t\tfinalUrl = `${normalizedBase}${href}`;\n\t\t\t}\n\n\t\t\t// Add click handler for external links\n\t\t\tif (finalUrl) {\n\t\t\t\tlink.addEventListener(\"click\", (event: MouseEvent) => {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\twindow.open(finalUrl, \"_blank\");\n\t\t\t\t});\n\n\t\t\t\t// Add visual indicator that it's an external link\n\t\t\t\tlink.classList.add(\"external-link\");\n\t\t\t}\n\t\t});\n\t}\n\n\tasync onOpen() {\n\t\tconst { contentEl } = this;\n\t\tcontentEl.empty();\n\n\t\tthis.addCls(contentEl, \"whats-new-modal\");\n\n\t\t// Header section\n\t\tconst header = contentEl.createDiv({ cls: this.cls(\"whats-new-header\") });\n\t\theader.createEl(\"h2\", {\n\t\t\ttext: `${this.config.pluginName} updated to v${this.toVersion}`,\n\t\t});\n\n\t\theader.createEl(\"p\", {\n\t\t\ttext: `Changes since v${this.fromVersion}`,\n\t\t\tcls: this.cls(\"whats-new-subtitle\"),\n\t\t});\n\n\t\t// Support section\n\t\tconst supportSection = contentEl.createDiv({\n\t\t\tcls: this.cls(\"whats-new-support\"),\n\t\t});\n\n\t\tsupportSection.createEl(\"h3\", { text: \"Support My Work\" });\n\n\t\tconst supportText = supportSection.createEl(\"p\");\n\t\tsupportText.createSpan({ text: \"If you enjoy using this plugin, please consider \" });\n\t\tsupportText.createEl(\"a\", {\n\t\t\ttext: \"supporting my work\",\n\t\t\thref: this.config.links.support,\n\t\t});\n\t\tsupportText.createSpan({\n\t\t\ttext: \". Your support helps keep this plugin maintained and improved!\",\n\t\t});\n\n\t\t// Discover more section\n\t\tconst discoverSection = contentEl.createDiv({\n\t\t\tcls: this.cls(\"whats-new-discover\"),\n\t\t});\n\n\t\tdiscoverSection.createEl(\"h3\", { text: \"Discover More\" });\n\n\t\t// Other tools\n\t\tconst toolsText = discoverSection.createEl(\"p\");\n\t\ttoolsText.createSpan({ text: \"🔧 Check out my \" });\n\t\ttoolsText.createEl(\"a\", {\n\t\t\ttext: \"other plugins and productivity tools\",\n\t\t\thref: this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS,\n\t\t});\n\t\ttoolsText.createSpan({\n\t\t\ttext: \" to enhance your workflow even further.\",\n\t\t});\n\n\t\t// YouTube channel\n\t\tconst youtubeText = discoverSection.createEl(\"p\");\n\t\tyoutubeText.createSpan({ text: \"📺 Subscribe to my \" });\n\t\tyoutubeText.createEl(\"a\", {\n\t\t\ttext: \"YouTube channel\",\n\t\t\thref: this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE,\n\t\t});\n\t\tyoutubeText.createSpan({\n\t\t\ttext: \" for Obsidian tutorials and productivity tips!\",\n\t\t});\n\n\t\tcontentEl.createEl(\"hr\");\n\n\t\t// Changelog content\n\t\tconst changelogSections = getChangelogSince(\n\t\t\tthis.config.changelogContent,\n\t\t\tthis.fromVersion,\n\t\t\tthis.toVersion\n\t\t);\n\n\t\tif (changelogSections.length === 0) {\n\t\t\tcontentEl.createEl(\"p\", {\n\t\t\t\ttext: \"No significant changes found in this update.\",\n\t\t\t\tcls: this.cls(\"whats-new-empty\"),\n\t\t\t});\n\t\t} else {\n\t\t\tconst changelogContainer = contentEl.createDiv({\n\t\t\t\tcls: this.cls(\"whats-new-content\"),\n\t\t\t});\n\n\t\t\tconst markdownContent = formatChangelogSections(changelogSections);\n\n\t\t\tawait MarkdownRenderer.render(\n\t\t\t\tthis.app,\n\t\t\t\tmarkdownContent,\n\t\t\t\tchangelogContainer,\n\t\t\t\t\"/\",\n\t\t\t\tthis.plugin\n\t\t\t);\n\n\t\t\t// Make external links clickable\n\t\t\tthis.makeExternalLinksClickable(changelogContainer);\n\t\t}\n\n\t\t// Action buttons\n\t\tconst buttonContainer = contentEl.createDiv({\n\t\t\tcls: this.cls(\"whats-new-buttons\"),\n\t\t});\n\n\t\t// Full changelog button\n\t\tconst changelogBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Full Changelog\",\n\t\t});\n\t\tchangelogBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.changelog, \"_blank\");\n\t\t});\n\n\t\t// Documentation button\n\t\tconst docsBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Documentation\",\n\t\t});\n\t\tdocsBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.documentation, \"_blank\");\n\t\t});\n\n\t\t// Tools button\n\t\tconst toolsBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"My Tools\",\n\t\t});\n\t\ttoolsBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS, \"_blank\");\n\t\t});\n\n\t\t// YouTube button\n\t\tconst youtubeBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"YouTube Channel\",\n\t\t});\n\t\tyoutubeBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE, \"_blank\");\n\t\t});\n\n\t\t// Close button (always present)\n\t\tconst closeBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Close\",\n\t\t\tcls: this.cls(\"mod-cta\"),\n\t\t});\n\t\tcloseBtn.addEventListener(\"click\", () => this.close());\n\t}\n\n\tonClose() {\n\t\tthis.contentEl.empty();\n\t}\n}\n"]}
1
+ {"version":3,"file":"whats-new-modal.js","sourceRoot":"","sources":["../../src/components/whats-new-modal.ts"],"names":[],"mappings":"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","sourcesContent":["import { formatChangelogSections, getChangelogSince } from \"@real1ty-obsidian-plugins/utils\";\nimport type { App, Plugin } from \"obsidian\";\nimport { MarkdownRenderer, Modal } from \"obsidian\";\n\n/**\n * Default URLs for the What's New modal.\n * These can be overridden in the config.\n */\nexport const DEFAULT_WHATS_NEW_LINKS = {\n\t/**\n\t * Default tools page showcasing all plugins and productivity software.\n\t */\n\tTOOLS: \"https://matejvavroproductivity.com/tools/\",\n\n\t/**\n\t * Default YouTube channel with Obsidian tutorials and productivity tips.\n\t * Includes subscription confirmation parameter.\n\t */\n\tYOUTUBE: \"https://www.youtube.com/@MatejVavroProductivity?sub_confirmation=1\",\n} as const;\n\nexport interface WhatsNewModalConfig {\n\t/**\n\t * The CSS class prefix/suffix to use for styling.\n\t * Example: \"custom-calendar\" will generate classes like \"custom-calendar-whats-new-modal\"\n\t */\n\tcssPrefix: string;\n\n\t/**\n\t * Display name of the plugin.\n\t * Example: \"Custom Calendar\"\n\t */\n\tpluginName: string;\n\n\t/**\n\t * Raw changelog markdown content to parse.\n\t */\n\tchangelogContent: string;\n\n\t/**\n\t * Links to external resources.\n\t */\n\tlinks: {\n\t\t/**\n\t\t * URL to support/donate page.\n\t\t */\n\t\tsupport: string;\n\n\t\t/**\n\t\t * URL to full changelog page.\n\t\t */\n\t\tchangelog: string;\n\n\t\t/**\n\t\t * Base URL for documentation (used to resolve relative links in changelog).\n\t\t * Example: \"https://docs.example.com\" or \"https://docs.example.com/\"\n\t\t */\n\t\tdocumentation: string;\n\n\t\t/**\n\t\t * URL to GitHub repository.\n\t\t */\n\t\tgithub: string;\n\n\t\t/**\n\t\t * URL to tools page showcasing all plugins and productivity tools.\n\t\t * Defaults to DEFAULT_WHATS_NEW_LINKS.TOOLS if not provided.\n\t\t */\n\t\ttools?: string;\n\n\t\t/**\n\t\t * URL to YouTube channel with tutorials and productivity tips.\n\t\t * Defaults to DEFAULT_WHATS_NEW_LINKS.YOUTUBE if not provided.\n\t\t */\n\t\tyoutube?: string;\n\t};\n}\n\n/**\n * Generic \"What's New\" modal that displays changelog entries between versions.\n * Supports custom CSS prefixes, plugin names, and configurable links.\n *\n * ## CSS Classes\n *\n * This modal uses the following CSS classes (with your custom prefix).\n * Replace `{prefix}` with your `cssPrefix` value (e.g., \"my-plugin\").\n *\n * ### Main Container\n * - `.{prefix}-whats-new-modal` - Applied to the main content element\n * - `.{prefix}-whats-new-modal .modal` - Modal dialog styling (max-width, width)\n *\n * ### Title and Subtitle\n * - Modal title is set via `setTitle()` - Obsidian handles the styling and X close button\n * - `.{prefix}-whats-new-subtitle` - Subtitle text (\"Changes since vX.X.X\")\n *\n * ### Support Section\n * - `.{prefix}-whats-new-support` - Support section container\n * - Contains donation, tools, and YouTube links\n * - Should have background, padding, border-radius\n * - `.{prefix}-whats-new-support h3` - Support section heading\n * - `.{prefix}-whats-new-support p` - Support section paragraph text (one per row)\n * - `.{prefix}-whats-new-support a` - Links in support section (consistent styling)\n * - `.{prefix}-whats-new-support a:hover` - Link hover state\n *\n * ### Changelog Content\n * - `.{prefix}-whats-new-content` - Changelog content container\n * - Should have max-height, overflow-y: auto for scrolling\n * - `.{prefix}-whats-new-content h2` - Version headings in changelog\n * - `.{prefix}-whats-new-content h3` - Section headings in changelog\n * - `.{prefix}-whats-new-content ul` - Changelog lists\n * - `.{prefix}-whats-new-content li` - Changelog list items\n * - `.{prefix}-whats-new-content code` - Inline code in changelog\n * - `.{prefix}-whats-new-content pre` - Code blocks in changelog\n * - `.{prefix}-whats-new-content a.external-link` - External links (auto-added)\n * - `.{prefix}-whats-new-empty` - Empty state message\n *\n * ### Sticky Footer\n * - `.{prefix}-whats-new-sticky-footer` - Footer container (should be sticky)\n * - Has border-top to separate from content\n * - `.{prefix}-whats-new-buttons` - Button container\n * - `.{prefix}-whats-new-buttons button` - Individual buttons\n *\n * ## Example CSS Implementation\n *\n * ```css\n * // Main Container\n * .my-plugin-whats-new-modal .modal {\n * max-width: 800px;\n * width: 90%;\n * }\n *\n * // Plugin Name Link (in title)\n * .my-plugin-whats-new-plugin-name {\n * color: var(--link-color);\n * text-decoration: none;\n * transition: all 0.2s ease;\n * position: relative;\n * font-weight: 600;\n * }\n *\n * .my-plugin-whats-new-plugin-name:hover {\n * color: var(--link-color-hover);\n * text-decoration: none;\n * }\n *\n * .my-plugin-whats-new-plugin-name::after {\n * content: '';\n * position: absolute;\n * bottom: -2px;\n * left: 0;\n * width: 0;\n * height: 2px;\n * background-color: var(--interactive-accent);\n * transition: width 0.3s ease;\n * }\n *\n * .my-plugin-whats-new-plugin-name:hover::after {\n * width: 100%;\n * }\n *\n * // Subtitle\n * .my-plugin-whats-new-subtitle {\n * color: var(--text-muted);\n * font-size: 0.9rem;\n * margin: 0 0 1rem 0;\n * }\n *\n * // Support Section (with donation, tools, and YouTube links)\n * .my-plugin-whats-new-support {\n * margin: 0 0 1rem 0;\n * padding: 1rem;\n * background-color: var(--background-secondary);\n * border-radius: 8px;\n * }\n *\n * .my-plugin-whats-new-support h3 {\n * margin-top: 0;\n * margin-bottom: 0.5rem;\n * font-size: 1rem;\n * }\n *\n * .my-plugin-whats-new-support p {\n * margin: 0.5rem 0;\n * color: var(--text-normal);\n * }\n *\n * .my-plugin-whats-new-support a {\n * color: var(--link-color);\n * text-decoration: none;\n * transition: all 0.2s ease;\n * position: relative;\n * }\n *\n * .my-plugin-whats-new-support a:hover {\n * color: var(--link-color-hover);\n * text-decoration: none;\n * }\n *\n * .my-plugin-whats-new-support a::after {\n * content: '';\n * position: absolute;\n * bottom: -2px;\n * left: 0;\n * width: 0;\n * height: 2px;\n * background-color: var(--interactive-accent);\n * transition: width 0.3s ease;\n * }\n *\n * .my-plugin-whats-new-support a:hover::after {\n * width: 100%;\n * }\n *\n * // Changelog Content (Scrollable Area)\n * .my-plugin-whats-new-content {\n * max-height: 400px;\n * overflow-y: auto;\n * margin-bottom: 1rem;\n * padding-right: 0.5rem;\n * border-radius: 8px;\n * }\n *\n * .my-plugin-whats-new-content h2 {\n * font-size: 1.3rem;\n * margin-top: 1.5rem;\n * margin-bottom: 0.5rem;\n * color: var(--text-accent);\n * }\n *\n * .my-plugin-whats-new-content h3 {\n * font-size: 1.1rem;\n * margin-top: 1rem;\n * margin-bottom: 0.5rem;\n * }\n *\n * .my-plugin-whats-new-content ul {\n * padding-left: 1.5rem;\n * }\n *\n * .my-plugin-whats-new-content li {\n * margin-bottom: 0.5rem;\n * line-height: 1.6;\n * }\n *\n * .my-plugin-whats-new-content code {\n * background: var(--code-background);\n * padding: 0.2em 0.4em;\n * border-radius: 3px;\n * font-size: 0.9em;\n * }\n *\n * .my-plugin-whats-new-content pre {\n * background: var(--code-background);\n * padding: 1rem;\n * border-radius: 6px;\n * overflow-x: auto;\n * }\n *\n * .my-plugin-whats-new-content a.external-link {\n * color: var(--link-external-color);\n * }\n *\n * .my-plugin-whats-new-content a.external-link::after {\n * content: \"↗\";\n * margin-left: 0.2em;\n * font-size: 0.8em;\n * }\n *\n * .my-plugin-whats-new-empty {\n * text-align: center;\n * color: var(--text-muted);\n * padding: 2rem;\n * font-style: italic;\n * }\n *\n * // Sticky Footer\n * .my-plugin-whats-new-sticky-footer {\n * position: sticky;\n * bottom: 0;\n * background: var(--background-primary);\n * padding-top: 0.75rem;\n * margin-top: 0;\n * z-index: 10;\n * border-top: 1px solid var(--background-modifier-border);\n * }\n *\n * .my-plugin-whats-new-buttons {\n * display: flex;\n * gap: 0.5rem;\n * justify-content: space-between;\n * flex-wrap: wrap;\n * padding-bottom: 0.5rem;\n * width: 100%;\n * }\n *\n * .my-plugin-whats-new-buttons button {\n * flex: 1;\n * min-width: 0;\n * padding: 0.5rem 1rem;\n * border-radius: 4px;\n * cursor: pointer;\n * border: 1px solid var(--background-modifier-border);\n * background: var(--interactive-normal);\n * color: var(--text-normal);\n * transition: all 0.2s ease;\n * text-align: center;\n * }\n *\n * .my-plugin-whats-new-buttons button:hover {\n * background: var(--interactive-hover);\n * border-color: var(--interactive-accent);\n * transform: translateY(-1px);\n * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n * }\n *\n * .my-plugin-whats-new-buttons button:active {\n * transform: translateY(0);\n * box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);\n * }\n * ```\n *\n * @example\n * ```typescript\n * const modal = new WhatsNewModal(app, plugin, {\n * cssPrefix: \"my-plugin\",\n * pluginName: \"My Plugin\",\n * changelogContent: rawChangelog,\n * links: {\n * support: \"https://...\",\n * changelog: \"https://...\",\n * documentation: \"https://...\"\n * }\n * }, \"1.0.0\", \"2.0.0\");\n * modal.open();\n * ```\n */\nexport class WhatsNewModal extends Modal {\n\tconstructor(\n\t\tapp: App,\n\t\tprivate plugin: Plugin,\n\t\tprivate config: WhatsNewModalConfig,\n\t\tprivate fromVersion: string,\n\t\tprivate toVersion: string\n\t) {\n\t\tsuper(app);\n\t}\n\n\t/**\n\t * Helper to create CSS class names with the configured prefix.\n\t */\n\tprivate cls(suffix: string): string {\n\t\treturn `${this.config.cssPrefix}-${suffix}`;\n\t}\n\n\t/**\n\t * Helper to add CSS class to an element.\n\t */\n\tprivate addCls(el: HTMLElement, suffix: string): void {\n\t\tel.classList.add(this.cls(suffix));\n\t}\n\n\t/**\n\t * Makes external links in rendered markdown clickable by adding click handlers.\n\t * Handles both absolute URLs (http/https) and relative URLs (starting with /).\n\t * Relative URLs are resolved against the documentation base URL.\n\t */\n\tprivate makeExternalLinksClickable(container: HTMLElement): void {\n\t\tconst links = container.querySelectorAll<HTMLAnchorElement>(\"a[href]\");\n\n\t\t// Convert NodeList to Array for iteration\n\t\tArray.from(links).forEach((link) => {\n\t\t\tconst href = link.getAttribute(\"href\");\n\t\t\tif (!href) return;\n\n\t\t\tlet finalUrl: string | null = null;\n\n\t\t\t// Handle absolute HTTP(S) links\n\t\t\tif (href.startsWith(\"http://\") || href.startsWith(\"https://\")) {\n\t\t\t\tfinalUrl = href;\n\t\t\t}\n\t\t\t// Handle relative links (starting with /)\n\t\t\telse if (href.startsWith(\"/\")) {\n\t\t\t\t// Get base documentation URL and ensure proper slash handling\n\t\t\t\tconst baseUrl = this.config.links.documentation;\n\t\t\t\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\t\t\t\tfinalUrl = `${normalizedBase}${href}`;\n\t\t\t}\n\n\t\t\t// Add click handler for external links\n\t\t\tif (finalUrl) {\n\t\t\t\tlink.addEventListener(\"click\", (event: MouseEvent) => {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\twindow.open(finalUrl, \"_blank\");\n\t\t\t\t});\n\n\t\t\t\t// Add visual indicator that it's an external link\n\t\t\t\tlink.classList.add(\"external-link\");\n\t\t\t}\n\t\t});\n\t}\n\n\tasync onOpen() {\n\t\tconst { contentEl } = this;\n\t\tcontentEl.empty();\n\n\t\tthis.addCls(contentEl, \"whats-new-modal\");\n\n\t\tthis.setTitle(\"\");\n\n\t\tconst titleEl = this.titleEl;\n\t\ttitleEl.empty();\n\n\t\tconst pluginNameLink = titleEl.createEl(\"a\", {\n\t\t\ttext: this.config.pluginName,\n\t\t\tcls: this.cls(\"whats-new-plugin-name\"),\n\t\t\thref: \"#\",\n\t\t});\n\n\t\tpluginNameLink.addEventListener(\"click\", (e) => {\n\t\t\te.preventDefault();\n\t\t\twindow.open(this.config.links.github, \"_blank\");\n\t\t});\n\n\t\ttitleEl.createSpan({\n\t\t\ttext: ` updated to v${this.toVersion}`,\n\t\t});\n\n\t\t// Subtitle\n\t\tcontentEl.createEl(\"p\", {\n\t\t\ttext: `Changes since v${this.fromVersion}`,\n\t\t\tcls: this.cls(\"whats-new-subtitle\"),\n\t\t});\n\n\t\t// Support section\n\t\tconst supportSection = contentEl.createDiv({\n\t\t\tcls: this.cls(\"whats-new-support\"),\n\t\t});\n\n\t\tsupportSection.createEl(\"h3\", { text: \"Support the development of this plugin\" });\n\n\t\tconst introText = supportSection.createEl(\"p\");\n\t\tintroText.setText(\n\t\t\t\"If this plugin saves you time or improves how you work in Obsidian, consider supporting its development. Your support helps fund ongoing maintenance, new features, and long-term stability.\"\n\t\t);\n\n\t\tconst supportLinkText = supportSection.createEl(\"p\");\n\t\tsupportLinkText.createSpan({ text: \"👉 \" });\n\t\tsupportLinkText.createEl(\"a\", {\n\t\t\ttext: \"Support my work\",\n\t\t\thref: this.config.links.support,\n\t\t});\n\n\t\tconst exploreText = supportSection.createEl(\"p\");\n\t\texploreText.createSpan({ text: \"You can also explore my \" });\n\t\texploreText.createEl(\"a\", {\n\t\t\ttext: \"other Obsidian plugins and productivity tools\",\n\t\t\thref: this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS,\n\t\t});\n\t\texploreText.createSpan({ text: \", or follow my \" });\n\t\texploreText.createEl(\"a\", {\n\t\t\ttext: \"YouTube channel\",\n\t\t\thref: this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE,\n\t\t});\n\t\texploreText.createSpan({\n\t\t\ttext: \" for in-depth tutorials and workflow ideas.\",\n\t\t});\n\n\t\t// Changelog content\n\t\tconst changelogSections = getChangelogSince(\n\t\t\tthis.config.changelogContent,\n\t\t\tthis.fromVersion,\n\t\t\tthis.toVersion\n\t\t);\n\n\t\tif (changelogSections.length === 0) {\n\t\t\tcontentEl.createEl(\"p\", {\n\t\t\t\ttext: \"No significant changes found in this update.\",\n\t\t\t\tcls: this.cls(\"whats-new-empty\"),\n\t\t\t});\n\t\t} else {\n\t\t\tconst changelogContainer = contentEl.createDiv({\n\t\t\t\tcls: this.cls(\"whats-new-content\"),\n\t\t\t});\n\n\t\t\tconst markdownContent = formatChangelogSections(changelogSections);\n\n\t\t\tawait MarkdownRenderer.render(\n\t\t\t\tthis.app,\n\t\t\t\tmarkdownContent,\n\t\t\t\tchangelogContainer,\n\t\t\t\t\"/\",\n\t\t\t\tthis.plugin\n\t\t\t);\n\n\t\t\t// Make external links clickable\n\t\t\tthis.makeExternalLinksClickable(changelogContainer);\n\t\t}\n\n\t\t// Sticky footer section (hr + buttons)\n\t\tconst stickyFooter = contentEl.createDiv({\n\t\t\tcls: this.cls(\"whats-new-sticky-footer\"),\n\t\t});\n\n\t\t// Action buttons\n\t\tconst buttonContainer = stickyFooter.createDiv({\n\t\t\tcls: this.cls(\"whats-new-buttons\"),\n\t\t});\n\n\t\t// GitHub button\n\t\tconst githubBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"GitHub\",\n\t\t});\n\t\tgithubBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.github, \"_blank\");\n\t\t});\n\n\t\t// Full changelog button\n\t\tconst changelogBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Changelog\",\n\t\t});\n\t\tchangelogBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.changelog, \"_blank\");\n\t\t});\n\n\t\t// Documentation button\n\t\tconst docsBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Documentation\",\n\t\t});\n\t\tdocsBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.documentation, \"_blank\");\n\t\t});\n\n\t\t// Tools button\n\t\tconst toolsBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Other Plugins\",\n\t\t});\n\t\ttoolsBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS, \"_blank\");\n\t\t});\n\n\t\t// YouTube button\n\t\tconst youtubeBtn = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"YouTube\",\n\t\t});\n\t\tyoutubeBtn.addEventListener(\"click\", () => {\n\t\t\twindow.open(this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE, \"_blank\");\n\t\t});\n\t}\n\n\tonClose() {\n\t\tthis.contentEl.empty();\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real1ty-obsidian-plugins/utils",
3
- "version": "2.31.0",
3
+ "version": "2.33.0",
4
4
  "description": "Shared utilities for Obsidian plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,5 @@
1
1
  import type { App, Plugin } from "obsidian";
2
2
  import { MarkdownRenderer, Modal } from "obsidian";
3
- import { formatChangelogSections, getChangelogSince } from "../string/changelog-parser";
4
3
 
5
4
  /**
6
5
  * Default URLs for the What's New modal.
@@ -57,6 +56,11 @@ export interface WhatsNewModalConfig {
57
56
  */
58
57
  documentation: string;
59
58
 
59
+ /**
60
+ * URL to GitHub repository.
61
+ */
62
+ github: string;
63
+
60
64
  /**
61
65
  * URL to tools page showcasing all plugins and productivity tools.
62
66
  * Defaults to DEFAULT_WHATS_NEW_LINKS.TOOLS if not provided.
@@ -74,6 +78,260 @@ export interface WhatsNewModalConfig {
74
78
  /**
75
79
  * Generic "What's New" modal that displays changelog entries between versions.
76
80
  * Supports custom CSS prefixes, plugin names, and configurable links.
81
+ *
82
+ * ## CSS Classes
83
+ *
84
+ * This modal uses the following CSS classes (with your custom prefix).
85
+ * Replace `{prefix}` with your `cssPrefix` value (e.g., "my-plugin").
86
+ *
87
+ * ### Main Container
88
+ * - `.{prefix}-whats-new-modal` - Applied to the main content element
89
+ * - `.{prefix}-whats-new-modal .modal` - Modal dialog styling (max-width, width)
90
+ *
91
+ * ### Title and Subtitle
92
+ * - Modal title is set via `setTitle()` - Obsidian handles the styling and X close button
93
+ * - `.{prefix}-whats-new-subtitle` - Subtitle text ("Changes since vX.X.X")
94
+ *
95
+ * ### Support Section
96
+ * - `.{prefix}-whats-new-support` - Support section container
97
+ * - Contains donation, tools, and YouTube links
98
+ * - Should have background, padding, border-radius
99
+ * - `.{prefix}-whats-new-support h3` - Support section heading
100
+ * - `.{prefix}-whats-new-support p` - Support section paragraph text (one per row)
101
+ * - `.{prefix}-whats-new-support a` - Links in support section (consistent styling)
102
+ * - `.{prefix}-whats-new-support a:hover` - Link hover state
103
+ *
104
+ * ### Changelog Content
105
+ * - `.{prefix}-whats-new-content` - Changelog content container
106
+ * - Should have max-height, overflow-y: auto for scrolling
107
+ * - `.{prefix}-whats-new-content h2` - Version headings in changelog
108
+ * - `.{prefix}-whats-new-content h3` - Section headings in changelog
109
+ * - `.{prefix}-whats-new-content ul` - Changelog lists
110
+ * - `.{prefix}-whats-new-content li` - Changelog list items
111
+ * - `.{prefix}-whats-new-content code` - Inline code in changelog
112
+ * - `.{prefix}-whats-new-content pre` - Code blocks in changelog
113
+ * - `.{prefix}-whats-new-content a.external-link` - External links (auto-added)
114
+ * - `.{prefix}-whats-new-empty` - Empty state message
115
+ *
116
+ * ### Sticky Footer
117
+ * - `.{prefix}-whats-new-sticky-footer` - Footer container (should be sticky)
118
+ * - Has border-top to separate from content
119
+ * - `.{prefix}-whats-new-buttons` - Button container
120
+ * - `.{prefix}-whats-new-buttons button` - Individual buttons
121
+ *
122
+ * ## Example CSS Implementation
123
+ *
124
+ * ```css
125
+ * // Main Container
126
+ * .my-plugin-whats-new-modal .modal {
127
+ * max-width: 800px;
128
+ * width: 90%;
129
+ * }
130
+ *
131
+ * // Plugin Name Link (in title)
132
+ * .my-plugin-whats-new-plugin-name {
133
+ * color: var(--link-color);
134
+ * text-decoration: none;
135
+ * transition: all 0.2s ease;
136
+ * position: relative;
137
+ * font-weight: 600;
138
+ * }
139
+ *
140
+ * .my-plugin-whats-new-plugin-name:hover {
141
+ * color: var(--link-color-hover);
142
+ * text-decoration: none;
143
+ * }
144
+ *
145
+ * .my-plugin-whats-new-plugin-name::after {
146
+ * content: '';
147
+ * position: absolute;
148
+ * bottom: -2px;
149
+ * left: 0;
150
+ * width: 0;
151
+ * height: 2px;
152
+ * background-color: var(--interactive-accent);
153
+ * transition: width 0.3s ease;
154
+ * }
155
+ *
156
+ * .my-plugin-whats-new-plugin-name:hover::after {
157
+ * width: 100%;
158
+ * }
159
+ *
160
+ * // Subtitle
161
+ * .my-plugin-whats-new-subtitle {
162
+ * color: var(--text-muted);
163
+ * font-size: 0.9rem;
164
+ * margin: 0 0 1rem 0;
165
+ * }
166
+ *
167
+ * // Support Section (with donation, tools, and YouTube links)
168
+ * .my-plugin-whats-new-support {
169
+ * margin: 0 0 1rem 0;
170
+ * padding: 1rem;
171
+ * background-color: var(--background-secondary);
172
+ * border-radius: 8px;
173
+ * }
174
+ *
175
+ * .my-plugin-whats-new-support h3 {
176
+ * margin-top: 0;
177
+ * margin-bottom: 0.5rem;
178
+ * font-size: 1rem;
179
+ * }
180
+ *
181
+ * .my-plugin-whats-new-support p {
182
+ * margin: 0.5rem 0;
183
+ * color: var(--text-normal);
184
+ * }
185
+ *
186
+ * .my-plugin-whats-new-support a {
187
+ * color: var(--link-color);
188
+ * text-decoration: none;
189
+ * transition: all 0.2s ease;
190
+ * position: relative;
191
+ * }
192
+ *
193
+ * .my-plugin-whats-new-support a:hover {
194
+ * color: var(--link-color-hover);
195
+ * text-decoration: none;
196
+ * }
197
+ *
198
+ * .my-plugin-whats-new-support a::after {
199
+ * content: '';
200
+ * position: absolute;
201
+ * bottom: -2px;
202
+ * left: 0;
203
+ * width: 0;
204
+ * height: 2px;
205
+ * background-color: var(--interactive-accent);
206
+ * transition: width 0.3s ease;
207
+ * }
208
+ *
209
+ * .my-plugin-whats-new-support a:hover::after {
210
+ * width: 100%;
211
+ * }
212
+ *
213
+ * // Changelog Content (Scrollable Area)
214
+ * .my-plugin-whats-new-content {
215
+ * max-height: 400px;
216
+ * overflow-y: auto;
217
+ * margin-bottom: 1rem;
218
+ * padding-right: 0.5rem;
219
+ * border-radius: 8px;
220
+ * }
221
+ *
222
+ * .my-plugin-whats-new-content h2 {
223
+ * font-size: 1.3rem;
224
+ * margin-top: 1.5rem;
225
+ * margin-bottom: 0.5rem;
226
+ * color: var(--text-accent);
227
+ * }
228
+ *
229
+ * .my-plugin-whats-new-content h3 {
230
+ * font-size: 1.1rem;
231
+ * margin-top: 1rem;
232
+ * margin-bottom: 0.5rem;
233
+ * }
234
+ *
235
+ * .my-plugin-whats-new-content ul {
236
+ * padding-left: 1.5rem;
237
+ * }
238
+ *
239
+ * .my-plugin-whats-new-content li {
240
+ * margin-bottom: 0.5rem;
241
+ * line-height: 1.6;
242
+ * }
243
+ *
244
+ * .my-plugin-whats-new-content code {
245
+ * background: var(--code-background);
246
+ * padding: 0.2em 0.4em;
247
+ * border-radius: 3px;
248
+ * font-size: 0.9em;
249
+ * }
250
+ *
251
+ * .my-plugin-whats-new-content pre {
252
+ * background: var(--code-background);
253
+ * padding: 1rem;
254
+ * border-radius: 6px;
255
+ * overflow-x: auto;
256
+ * }
257
+ *
258
+ * .my-plugin-whats-new-content a.external-link {
259
+ * color: var(--link-external-color);
260
+ * }
261
+ *
262
+ * .my-plugin-whats-new-content a.external-link::after {
263
+ * content: "↗";
264
+ * margin-left: 0.2em;
265
+ * font-size: 0.8em;
266
+ * }
267
+ *
268
+ * .my-plugin-whats-new-empty {
269
+ * text-align: center;
270
+ * color: var(--text-muted);
271
+ * padding: 2rem;
272
+ * font-style: italic;
273
+ * }
274
+ *
275
+ * // Sticky Footer
276
+ * .my-plugin-whats-new-sticky-footer {
277
+ * position: sticky;
278
+ * bottom: 0;
279
+ * background: var(--background-primary);
280
+ * padding-top: 0.75rem;
281
+ * margin-top: 0;
282
+ * z-index: 10;
283
+ * border-top: 1px solid var(--background-modifier-border);
284
+ * }
285
+ *
286
+ * .my-plugin-whats-new-buttons {
287
+ * display: flex;
288
+ * gap: 0.5rem;
289
+ * justify-content: space-between;
290
+ * flex-wrap: wrap;
291
+ * padding-bottom: 0.5rem;
292
+ * width: 100%;
293
+ * }
294
+ *
295
+ * .my-plugin-whats-new-buttons button {
296
+ * flex: 1;
297
+ * min-width: 0;
298
+ * padding: 0.5rem 1rem;
299
+ * border-radius: 4px;
300
+ * cursor: pointer;
301
+ * border: 1px solid var(--background-modifier-border);
302
+ * background: var(--interactive-normal);
303
+ * color: var(--text-normal);
304
+ * transition: all 0.2s ease;
305
+ * text-align: center;
306
+ * }
307
+ *
308
+ * .my-plugin-whats-new-buttons button:hover {
309
+ * background: var(--interactive-hover);
310
+ * border-color: var(--interactive-accent);
311
+ * transform: translateY(-1px);
312
+ * box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
313
+ * }
314
+ *
315
+ * .my-plugin-whats-new-buttons button:active {
316
+ * transform: translateY(0);
317
+ * box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
318
+ * }
319
+ * ```
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const modal = new WhatsNewModal(app, plugin, {
324
+ * cssPrefix: "my-plugin",
325
+ * pluginName: "My Plugin",
326
+ * changelogContent: rawChangelog,
327
+ * links: {
328
+ * support: "https://...",
329
+ * changelog: "https://...",
330
+ * documentation: "https://..."
331
+ * }
332
+ * }, "1.0.0", "2.0.0");
333
+ * modal.open();
334
+ * ```
77
335
  */
78
336
  export class WhatsNewModal extends Modal {
79
337
  constructor(
@@ -146,13 +404,28 @@ export class WhatsNewModal extends Modal {
146
404
 
147
405
  this.addCls(contentEl, "whats-new-modal");
148
406
 
149
- // Header section
150
- const header = contentEl.createDiv({ cls: this.cls("whats-new-header") });
151
- header.createEl("h2", {
152
- text: `${this.config.pluginName} updated to v${this.toVersion}`,
407
+ this.setTitle("");
408
+
409
+ const titleEl = this.titleEl;
410
+ titleEl.empty();
411
+
412
+ const pluginNameLink = titleEl.createEl("a", {
413
+ text: this.config.pluginName,
414
+ cls: this.cls("whats-new-plugin-name"),
415
+ href: "#",
416
+ });
417
+
418
+ pluginNameLink.addEventListener("click", (e) => {
419
+ e.preventDefault();
420
+ window.open(this.config.links.github, "_blank");
153
421
  });
154
422
 
155
- header.createEl("p", {
423
+ titleEl.createSpan({
424
+ text: ` updated to v${this.toVersion}`,
425
+ });
426
+
427
+ // Subtitle
428
+ contentEl.createEl("p", {
156
429
  text: `Changes since v${this.fromVersion}`,
157
430
  cls: this.cls("whats-new-subtitle"),
158
431
  });
@@ -162,49 +435,35 @@ export class WhatsNewModal extends Modal {
162
435
  cls: this.cls("whats-new-support"),
163
436
  });
164
437
 
165
- supportSection.createEl("h3", { text: "Support My Work" });
438
+ supportSection.createEl("h3", { text: "Support the development of this plugin" });
166
439
 
167
- const supportText = supportSection.createEl("p");
168
- supportText.createSpan({ text: "If you enjoy using this plugin, please consider " });
169
- supportText.createEl("a", {
170
- text: "supporting my work",
171
- href: this.config.links.support,
172
- });
173
- supportText.createSpan({
174
- text: ". Your support helps keep this plugin maintained and improved!",
175
- });
440
+ const introText = supportSection.createEl("p");
441
+ introText.setText(
442
+ "If this plugin saves you time or improves how you work in Obsidian, consider supporting its development. Your support helps fund ongoing maintenance, new features, and long-term stability."
443
+ );
176
444
 
177
- // Discover more section
178
- const discoverSection = contentEl.createDiv({
179
- cls: this.cls("whats-new-discover"),
445
+ const supportLinkText = supportSection.createEl("p");
446
+ supportLinkText.createSpan({ text: "👉 " });
447
+ supportLinkText.createEl("a", {
448
+ text: "Support my work",
449
+ href: this.config.links.support,
180
450
  });
181
451
 
182
- discoverSection.createEl("h3", { text: "Discover More" });
183
-
184
- // Other tools
185
- const toolsText = discoverSection.createEl("p");
186
- toolsText.createSpan({ text: "🔧 Check out my " });
187
- toolsText.createEl("a", {
188
- text: "other plugins and productivity tools",
452
+ const exploreText = supportSection.createEl("p");
453
+ exploreText.createSpan({ text: "You can also explore my " });
454
+ exploreText.createEl("a", {
455
+ text: "other Obsidian plugins and productivity tools",
189
456
  href: this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS,
190
457
  });
191
- toolsText.createSpan({
192
- text: " to enhance your workflow even further.",
193
- });
194
-
195
- // YouTube channel
196
- const youtubeText = discoverSection.createEl("p");
197
- youtubeText.createSpan({ text: "📺 Subscribe to my " });
198
- youtubeText.createEl("a", {
458
+ exploreText.createSpan({ text: ", or follow my " });
459
+ exploreText.createEl("a", {
199
460
  text: "YouTube channel",
200
461
  href: this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE,
201
462
  });
202
- youtubeText.createSpan({
203
- text: " for Obsidian tutorials and productivity tips!",
463
+ exploreText.createSpan({
464
+ text: " for in-depth tutorials and workflow ideas.",
204
465
  });
205
466
 
206
- contentEl.createEl("hr");
207
-
208
467
  // Changelog content
209
468
  const changelogSections = getChangelogSince(
210
469
  this.config.changelogContent,
@@ -236,14 +495,27 @@ export class WhatsNewModal extends Modal {
236
495
  this.makeExternalLinksClickable(changelogContainer);
237
496
  }
238
497
 
498
+ // Sticky footer section (hr + buttons)
499
+ const stickyFooter = contentEl.createDiv({
500
+ cls: this.cls("whats-new-sticky-footer"),
501
+ });
502
+
239
503
  // Action buttons
240
- const buttonContainer = contentEl.createDiv({
504
+ const buttonContainer = stickyFooter.createDiv({
241
505
  cls: this.cls("whats-new-buttons"),
242
506
  });
243
507
 
508
+ // GitHub button
509
+ const githubBtn = buttonContainer.createEl("button", {
510
+ text: "GitHub",
511
+ });
512
+ githubBtn.addEventListener("click", () => {
513
+ window.open(this.config.links.github, "_blank");
514
+ });
515
+
244
516
  // Full changelog button
245
517
  const changelogBtn = buttonContainer.createEl("button", {
246
- text: "Full Changelog",
518
+ text: "Changelog",
247
519
  });
248
520
  changelogBtn.addEventListener("click", () => {
249
521
  window.open(this.config.links.changelog, "_blank");
@@ -259,7 +531,7 @@ export class WhatsNewModal extends Modal {
259
531
 
260
532
  // Tools button
261
533
  const toolsBtn = buttonContainer.createEl("button", {
262
- text: "My Tools",
534
+ text: "Other Plugins",
263
535
  });
264
536
  toolsBtn.addEventListener("click", () => {
265
537
  window.open(this.config.links.tools ?? DEFAULT_WHATS_NEW_LINKS.TOOLS, "_blank");
@@ -267,18 +539,11 @@ export class WhatsNewModal extends Modal {
267
539
 
268
540
  // YouTube button
269
541
  const youtubeBtn = buttonContainer.createEl("button", {
270
- text: "YouTube Channel",
542
+ text: "YouTube",
271
543
  });
272
544
  youtubeBtn.addEventListener("click", () => {
273
545
  window.open(this.config.links.youtube ?? DEFAULT_WHATS_NEW_LINKS.YOUTUBE, "_blank");
274
546
  });
275
-
276
- // Close button (always present)
277
- const closeBtn = buttonContainer.createEl("button", {
278
- text: "Close",
279
- cls: this.cls("mod-cta"),
280
- });
281
- closeBtn.addEventListener("click", () => this.close());
282
547
  }
283
548
 
284
549
  onClose() {