create-marp-presentation 1.2.1 → 1.2.2
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/docs/plans/2026-02-28-conflict-resolution-fix.md +62 -0
- package/lib/add-themes-command.js +5 -1
- package/lib/prompts.js +22 -5
- package/lib/theme-manager.js +8 -2
- package/lib/theme-resolver.js +20 -2
- package/package.json +1 -1
- package/themes/beam/beam.css +1 -0
- package/themes/default-clean/default-clean.css +1 -1
- package/themes/gaia-dark/gaia-dark.css +1 -1
- package/themes/marpx/marpx.css +2 -1
- package/themes/marpx/socrates.css +1 -0
- package/themes/uncover-minimal/uncover-minimal.css +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Conflict Resolution Bug Fix Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-02-28
|
|
4
|
+
**Status:** Designed
|
|
5
|
+
**Issue:** Dialog repeats when resolving single theme conflict
|
|
6
|
+
|
|
7
|
+
## Problem Description
|
|
8
|
+
|
|
9
|
+
When adding themes to an existing project, if a user selects an already existing theme, a conflict dialog appears. However, when the user chooses an action (Skip/Overwrite), the dialog repeats instead of resolving the conflict.
|
|
10
|
+
|
|
11
|
+
## Root Cause
|
|
12
|
+
|
|
13
|
+
In `lib/add-themes-command.js`, the `_resolveConflictsInteractive` method doesn't handle the return values `'skip'` and `'overwrite'` from `promptConflictResolution`.
|
|
14
|
+
|
|
15
|
+
When there's a single conflict:
|
|
16
|
+
1. `promptConflictResolution` returns `'skip'`, `'overwrite'`, or `'cancel'`
|
|
17
|
+
2. The code only checks for `'skip-all'`, `'overwrite-all'`, and `'cancel'`
|
|
18
|
+
3. When result is `'skip'` or `'overwrite'`, none of the conditions match
|
|
19
|
+
4. Code falls through to the `for` loop
|
|
20
|
+
5. User gets prompted again → **dialog repeats**
|
|
21
|
+
|
|
22
|
+
## Solution
|
|
23
|
+
|
|
24
|
+
Add explicit handling for `'skip'` and `'overwrite'` values in `_resolveConflictsInteractive`.
|
|
25
|
+
|
|
26
|
+
### Code Changes
|
|
27
|
+
|
|
28
|
+
**File:** `lib/add-themes-command.js`
|
|
29
|
+
**Method:** `_resolveConflictsInteractive` (lines 303-333)
|
|
30
|
+
|
|
31
|
+
Add after line 318 (after `if (result === 'cancel')`):
|
|
32
|
+
```javascript
|
|
33
|
+
// Handle single conflict responses
|
|
34
|
+
if (result === 'skip') return { overwrite: [] };
|
|
35
|
+
if (result === 'overwrite') return { overwrite: conflicts };
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Updated Logic Flow
|
|
39
|
+
|
|
40
|
+
| Return Value | Behavior |
|
|
41
|
+
|--------------|----------|
|
|
42
|
+
| `skip-all` | Skip all conflicts |
|
|
43
|
+
| `overwrite-all` | Overwrite all conflicts |
|
|
44
|
+
| `cancel` | Cancel operation |
|
|
45
|
+
| `skip` | Skip single conflict |
|
|
46
|
+
| `overwrite` | Overwrite single conflict |
|
|
47
|
+
| `choose-each` | Prompt for each conflict individually |
|
|
48
|
+
|
|
49
|
+
## Test Scenarios
|
|
50
|
+
|
|
51
|
+
1. **Single conflict - Skip**: Theme skipped, no dialog repeat
|
|
52
|
+
2. **Single conflict - Overwrite**: Theme overwritten, no dialog repeat
|
|
53
|
+
3. **Single conflict - Cancel**: Operation cancelled
|
|
54
|
+
4. **Multiple conflicts - choose-each**: Individual prompts for each
|
|
55
|
+
5. **Multiple conflicts - skip-all/overwrite-all**: Existing behavior verified
|
|
56
|
+
|
|
57
|
+
## Backward Compatibility
|
|
58
|
+
|
|
59
|
+
- No changes to public API
|
|
60
|
+
- Return values remain consistent
|
|
61
|
+
- Existing tests should pass without modification
|
|
62
|
+
- Custom prompt handlers still work via `this.options.prompts?.resolveConflicts`
|
|
@@ -319,7 +319,11 @@ class AddThemesCommand {
|
|
|
319
319
|
if (result === 'overwrite-all') return { overwrite: conflicts };
|
|
320
320
|
if (result === 'cancel') return { overwrite: [] };
|
|
321
321
|
|
|
322
|
-
// Handle
|
|
322
|
+
// Handle single conflict responses
|
|
323
|
+
if (result === 'skip') return { overwrite: [] };
|
|
324
|
+
if (result === 'overwrite') return { overwrite: conflicts };
|
|
325
|
+
|
|
326
|
+
// Handle 'choose-each' - prompt for each conflict individually
|
|
323
327
|
const overwrite = [];
|
|
324
328
|
for (const conflict of conflicts) {
|
|
325
329
|
const choice = await Prompts.promptSingleConflict(conflict);
|
package/lib/prompts.js
CHANGED
|
@@ -17,11 +17,16 @@ class Prompts {
|
|
|
17
17
|
return [];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const choices = availableThemes.map(theme =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const choices = availableThemes.map(theme => {
|
|
21
|
+
const displayName = theme.description
|
|
22
|
+
? `${theme.name} - ${theme.description}`
|
|
23
|
+
: theme.name;
|
|
24
|
+
return {
|
|
25
|
+
name: displayName,
|
|
26
|
+
value: theme.name,
|
|
27
|
+
checked: false
|
|
28
|
+
};
|
|
29
|
+
});
|
|
25
30
|
|
|
26
31
|
return await inquirer.checkbox({
|
|
27
32
|
message: 'Select themes to add:',
|
|
@@ -152,6 +157,18 @@ class Prompts {
|
|
|
152
157
|
});
|
|
153
158
|
}
|
|
154
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Prompt user for new theme description
|
|
162
|
+
*
|
|
163
|
+
* @returns {Promise<string|null>} Theme description or null if skipped
|
|
164
|
+
*/
|
|
165
|
+
static async promptNewThemeDescription() {
|
|
166
|
+
return await inquirer.input({
|
|
167
|
+
message: 'Theme description (optional - press Enter to skip):',
|
|
168
|
+
default: ''
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
155
172
|
/**
|
|
156
173
|
* Prompt user for conflict resolution
|
|
157
174
|
*
|
package/lib/theme-manager.js
CHANGED
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const { ThemeResolver } = require('./theme-resolver');
|
|
5
5
|
const { VSCodeIntegration } = require('./vscode-integration');
|
|
6
6
|
const { Frontmatter } = require('./frontmatter');
|
|
7
|
+
const { Prompts } = require('./prompts');
|
|
7
8
|
const {
|
|
8
9
|
ThemeNotFoundError,
|
|
9
10
|
ThemeAlreadyExistsError,
|
|
@@ -173,10 +174,11 @@ class ThemeManager {
|
|
|
173
174
|
* @param {string|null} parent - Parent theme name or null
|
|
174
175
|
* @param {string} location - 'root', 'existing' folder name, or 'new'
|
|
175
176
|
* @param {string} newFolderName - Required if location is 'new'
|
|
177
|
+
* @param {string|null} description - Optional theme description
|
|
176
178
|
* @throws {ThemeAlreadyExistsError} If theme with same name already exists
|
|
177
179
|
* @throws {Error} If location is 'new' but newFolderName not provided
|
|
178
180
|
*/
|
|
179
|
-
createTheme(name, parent, location, newFolderName = null) {
|
|
181
|
+
createTheme(name, parent, location, newFolderName = null, description = null) {
|
|
180
182
|
// Check for duplicate
|
|
181
183
|
if (this.getTheme(name)) {
|
|
182
184
|
throw new ThemeAlreadyExistsError(name);
|
|
@@ -198,7 +200,11 @@ class ThemeManager {
|
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
// Generate CSS content
|
|
201
|
-
let css = `/* @theme ${name}
|
|
203
|
+
let css = `/* @theme ${name}`;
|
|
204
|
+
if (description && description.trim()) {
|
|
205
|
+
css += `\n * @description ${description.trim()}`;
|
|
206
|
+
}
|
|
207
|
+
css += ` */\n\n`;
|
|
202
208
|
|
|
203
209
|
if (parent) {
|
|
204
210
|
css += `@import "${parent}";\n\n`;
|
package/lib/theme-resolver.js
CHANGED
|
@@ -7,11 +7,12 @@ const path = require('path');
|
|
|
7
7
|
* Represents a Marp theme
|
|
8
8
|
*/
|
|
9
9
|
class Theme {
|
|
10
|
-
constructor(name, cssPath, css, dependencies = []) {
|
|
10
|
+
constructor(name, cssPath, css, dependencies = [], description = null) {
|
|
11
11
|
this.name = name;
|
|
12
12
|
this.path = cssPath;
|
|
13
13
|
this.css = css;
|
|
14
14
|
this.dependencies = dependencies;
|
|
15
|
+
this.description = description;
|
|
15
16
|
this.isSystem = ['default', 'gaia', 'uncover'].includes(name);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -81,6 +82,22 @@ class ThemeResolver {
|
|
|
81
82
|
return dependencies;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Extract @description from CSS comment directive
|
|
87
|
+
* Pattern: /* @description text *\/
|
|
88
|
+
*
|
|
89
|
+
* @param {string} cssContent - CSS file content
|
|
90
|
+
* @returns {string|null} Description text or null if not found
|
|
91
|
+
*/
|
|
92
|
+
static extractDescription(cssContent) {
|
|
93
|
+
// Match /* @description text */ - supports multi-line comments
|
|
94
|
+
// The [\s\S]*? allows matching across newlines (non-greedy)
|
|
95
|
+
const descRegex = /\/\*[\s\S]*?@description\s+([^\r\n*]+)[\s\S]*?\*\//;
|
|
96
|
+
const match = cssContent.match(descRegex);
|
|
97
|
+
|
|
98
|
+
return match ? match[1].trim() : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
84
101
|
/**
|
|
85
102
|
* Resolve theme from a CSS file
|
|
86
103
|
*
|
|
@@ -97,8 +114,9 @@ class ThemeResolver {
|
|
|
97
114
|
const filename = path.basename(cssPath);
|
|
98
115
|
const name = this.extractThemeName(css, filename) || filename;
|
|
99
116
|
const dependencies = this.extractDependencies(css);
|
|
117
|
+
const description = this.extractDescription(css);
|
|
100
118
|
|
|
101
|
-
return new Theme(name, cssPath, css, dependencies);
|
|
119
|
+
return new Theme(name, cssPath, css, dependencies, description);
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
/**
|
package/package.json
CHANGED
package/themes/beam/beam.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* @theme beam */
|
|
2
|
+
/* @description Beamer-inspired theme with clean headers and footer bars */
|
|
2
3
|
/* Author: rnd195 https://github.com/rnd195/ */
|
|
3
4
|
/* beam license - GNU GPLv3 https://github.com/rnd195/my-marp-themes/blob/live/licenses/LICENSE_beam */
|
|
4
5
|
/* License of beamer which inspired this theme - GNU GPLv2 https://github.com/rnd195/my-marp-themes/blob/live/licenses/LICENSE_GPLv2 */
|
package/themes/marpx/marpx.css
CHANGED