feat: add single-spacing transformer option

- Add convertToSingleSpaced setting to collapse multiple
  consecutive blank lines into single blank lines
- Add "Convert to single-spaced" toggle in settings UI
  positioned above "Remove empty lines"
- Implement conditional disabling when "Remove empty
  lines" is enabled with visual indication
- Add transformer logic that processes before empty line
  removal with optimization to skip when not needed
- Add OpenSpec documentation for markdown-transformations
  capability
- Update version to 1.3.0
This commit is contained in:
Keath Milligan 2025-11-25 19:19:17 -06:00
parent 7c4cc6dec9
commit ec8685eddc
No known key found for this signature in database
GPG key ID: D98D37E8D3A8825C
10 changed files with 262 additions and 3 deletions

View file

@ -1,7 +1,7 @@
{
"id": "paste-reformatter",
"name": "Paste Reformatter",
"version": "1.2.2",
"version": "1.3.0",
"minAppVersion": "0.15.0",
"description": "Reformat pasted text for precise control.",
"author": "Keath Milligan",

View file

@ -0,0 +1,22 @@
# Change: Add single-spacing transformer for markdown
## Why
Users may want to collapse multiple consecutive blank lines in pasted content into single blank lines without removing all blank lines entirely. This provides a middle-ground option between preserving all blank lines as-is and removing them completely.
## What Changes
- Add a new markdown transformer that collapses multiple consecutive blank lines (2+) into a single blank line
- Add a new setting "Convert to single-spaced" that appears above "Remove empty lines" in the Markdown transformations section
- The setting defaults to Off (false) to preserve existing behavior
- When "Remove empty lines" is enabled, the "Convert to single-spaced" option is disabled (not applicable) since all blank lines are removed anyway
- The transformer is applied in the markdown transformation pipeline before empty line removal
## Impact
- Affected specs: `markdown-transformations`
- Affected code:
- `src/markdownTransformer.ts` - Add single-spacing logic
- `src/main.ts` - Add `convertToSingleSpaced` setting and UI toggle with conditional disabling
- No breaking changes
- Backward compatible: defaults to Off, existing behavior unchanged

View file

@ -0,0 +1,78 @@
# Markdown Transformations
## ADDED Requirements
### Requirement: Single-spacing transformation
The system SHALL provide an option to collapse multiple consecutive blank lines into a single blank line in the markdown output.
#### Scenario: Multiple blank lines collapsed to single
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **AND** the markdown content contains 2 or more consecutive blank lines
- **THEN** the consecutive blank lines SHALL be replaced with exactly 1 blank line
- **AND** the `appliedTransformations` flag SHALL be set to true
#### Scenario: Single blank lines preserved
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **AND** the markdown content contains single blank lines (not consecutive)
- **THEN** those single blank lines SHALL be preserved as-is
- **AND** no transformation is applied to those lines
#### Scenario: Non-blank lines unaffected
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **THEN** all non-blank lines SHALL remain unchanged
#### Scenario: Feature disabled preserves original behavior
- **WHEN** `convertToSingleSpaced` is disabled (false)
- **THEN** all blank lines SHALL remain unchanged regardless of how many are consecutive
- **AND** the `appliedTransformations` flag SHALL NOT be set based on spacing
#### Scenario: Interaction with removeEmptyLines
- **WHEN** `removeEmptyLines` is enabled (true)
- **THEN** the single-spacing transformer SHALL be skipped (not applied)
- **AND** the empty line removal logic SHALL be applied instead
### Requirement: Single-spacing configuration
The system SHALL provide a setting to enable or disable the single-spacing transformation.
#### Scenario: Setting defaults to disabled
- **WHEN** the plugin is initialized with no prior settings
- **THEN** the `convertToSingleSpaced` setting SHALL default to false
#### Scenario: Setting persists across sessions
- **WHEN** a user enables or disables the `convertToSingleSpaced` setting
- **AND** the settings are saved
- **THEN** the setting value SHALL be persisted and restored on plugin reload
### Requirement: Single-spacing UI control
The system SHALL provide a user interface control for the single-spacing transformation setting.
#### Scenario: Toggle positioned above "Remove empty lines"
- **WHEN** the settings UI is displayed
- **THEN** the "Convert to single-spaced" toggle SHALL appear in the Markdown transformations section
- **AND** it SHALL be positioned above the "Remove empty lines" toggle
#### Scenario: Toggle description
- **WHEN** the "Convert to single-spaced" toggle is displayed
- **THEN** the description SHALL read "Collapse multiple consecutive blank lines into a single blank line"
#### Scenario: Toggle disabled when removeEmptyLines is enabled
- **WHEN** the "Remove empty lines" setting is enabled (true)
- **THEN** the "Convert to single-spaced" toggle SHALL be disabled (non-interactive)
- **AND** it SHALL be visually indicated as disabled (grayed out or similar)
#### Scenario: Toggle enabled when removeEmptyLines is disabled
- **WHEN** the "Remove empty lines" setting is disabled (false)
- **THEN** the "Convert to single-spaced" toggle SHALL be enabled (interactive)
- **AND** users SHALL be able to toggle it on or off
#### Scenario: UI updates when removeEmptyLines changes
- **WHEN** the "Remove empty lines" setting is toggled
- **THEN** the disabled/enabled state of the "Convert to single-spaced" toggle SHALL update accordingly
### Requirement: Transform pipeline order
The system SHALL apply transformations in a defined order to ensure consistent behavior.
#### Scenario: Single-spacing before empty line removal
- **WHEN** both `convertToSingleSpaced` and `removeEmptyLines` are configured (regardless of values)
- **THEN** the single-spacing transformation logic SHALL be evaluated before the empty line removal logic in the code
- **AND** if `removeEmptyLines` is true, single-spacing SHALL be skipped as an optimization

View file

@ -0,0 +1,30 @@
# Implementation Tasks
## 1. Update settings interface and defaults
- [x] 1.1 Add `convertToSingleSpaced: boolean` property to `PasteReformmatterSettings` interface in `src/main.ts`
- [x] 1.2 Add `convertToSingleSpaced: false` to `DEFAULT_SETTINGS` in `src/main.ts`
## 2. Implement single-spacing transformer
- [x] 2.1 Add `convertToSingleSpaced` parameter to `transformMarkdown` function signature in `src/markdownTransformer.ts`
- [x] 2.2 Implement single-spacing logic that collapses 2+ consecutive blank lines into 1 blank line
- [x] 2.3 Apply transformer before the `removeEmptyLines` logic in the transformation pipeline
- [x] 2.4 Skip single-spacing transformer if `removeEmptyLines` is enabled (optimization - no point processing if lines will be removed)
- [x] 2.5 Set `appliedTransformations` flag when single-spacing makes changes
## 3. Add settings UI
- [x] 3.1 Add "Convert to single-spaced" toggle in settings UI, positioned above "Remove empty lines" setting
- [x] 3.2 Set description text: "Collapse multiple consecutive blank lines into a single blank line"
- [x] 3.3 Implement conditional disabling: when `removeEmptyLines` is true, disable the toggle and show it as disabled/grayed
- [x] 3.4 Update onChange handler to save settings
- [x] 3.5 When `removeEmptyLines` changes, refresh display to update disabled state of "Convert to single-spaced" toggle
## 4. Integration
- [x] 4.1 Pass `convertToSingleSpaced` setting to `transformMarkdown` call in `src/main.ts:doPaste()`
- [x] 4.2 Verify backward compatibility with existing settings (defaults to false)
## 5. Testing
- [x] 5.1 Build project: `npm run build`
- [x] 5.2 Test single-spacing with text containing 2+ consecutive blank lines
- [x] 5.3 Test that single-spacing is skipped when "Remove empty lines" is enabled
- [x] 5.4 Test UI toggle behavior and disabled state interaction
- [x] 5.5 Test that existing behavior is preserved when feature is disabled (default)

View file

@ -0,0 +1,80 @@
# markdown-transformations Specification
## Purpose
TBD - created by archiving change add-single-spacing-transformer. Update Purpose after archive.
## Requirements
### Requirement: Single-spacing transformation
The system SHALL provide an option to collapse multiple consecutive blank lines into a single blank line in the markdown output.
#### Scenario: Multiple blank lines collapsed to single
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **AND** the markdown content contains 2 or more consecutive blank lines
- **THEN** the consecutive blank lines SHALL be replaced with exactly 1 blank line
- **AND** the `appliedTransformations` flag SHALL be set to true
#### Scenario: Single blank lines preserved
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **AND** the markdown content contains single blank lines (not consecutive)
- **THEN** those single blank lines SHALL be preserved as-is
- **AND** no transformation is applied to those lines
#### Scenario: Non-blank lines unaffected
- **WHEN** `convertToSingleSpaced` is enabled (true)
- **THEN** all non-blank lines SHALL remain unchanged
#### Scenario: Feature disabled preserves original behavior
- **WHEN** `convertToSingleSpaced` is disabled (false)
- **THEN** all blank lines SHALL remain unchanged regardless of how many are consecutive
- **AND** the `appliedTransformations` flag SHALL NOT be set based on spacing
#### Scenario: Interaction with removeEmptyLines
- **WHEN** `removeEmptyLines` is enabled (true)
- **THEN** the single-spacing transformer SHALL be skipped (not applied)
- **AND** the empty line removal logic SHALL be applied instead
### Requirement: Single-spacing configuration
The system SHALL provide a setting to enable or disable the single-spacing transformation.
#### Scenario: Setting defaults to disabled
- **WHEN** the plugin is initialized with no prior settings
- **THEN** the `convertToSingleSpaced` setting SHALL default to false
#### Scenario: Setting persists across sessions
- **WHEN** a user enables or disables the `convertToSingleSpaced` setting
- **AND** the settings are saved
- **THEN** the setting value SHALL be persisted and restored on plugin reload
### Requirement: Single-spacing UI control
The system SHALL provide a user interface control for the single-spacing transformation setting.
#### Scenario: Toggle positioned above "Remove empty lines"
- **WHEN** the settings UI is displayed
- **THEN** the "Convert to single-spaced" toggle SHALL appear in the Markdown transformations section
- **AND** it SHALL be positioned above the "Remove empty lines" toggle
#### Scenario: Toggle description
- **WHEN** the "Convert to single-spaced" toggle is displayed
- **THEN** the description SHALL read "Collapse multiple consecutive blank lines into a single blank line"
#### Scenario: Toggle disabled when removeEmptyLines is enabled
- **WHEN** the "Remove empty lines" setting is enabled (true)
- **THEN** the "Convert to single-spaced" toggle SHALL be disabled (non-interactive)
- **AND** it SHALL be visually indicated as disabled (grayed out or similar)
#### Scenario: Toggle enabled when removeEmptyLines is disabled
- **WHEN** the "Remove empty lines" setting is disabled (false)
- **THEN** the "Convert to single-spaced" toggle SHALL be enabled (interactive)
- **AND** users SHALL be able to toggle it on or off
#### Scenario: UI updates when removeEmptyLines changes
- **WHEN** the "Remove empty lines" setting is toggled
- **THEN** the disabled/enabled state of the "Convert to single-spaced" toggle SHALL update accordingly
### Requirement: Transform pipeline order
The system SHALL apply transformations in a defined order to ensure consistent behavior.
#### Scenario: Single-spacing before empty line removal
- **WHEN** both `convertToSingleSpaced` and `removeEmptyLines` are configured (regardless of values)
- **THEN** the single-spacing transformation logic SHALL be evaluated before the empty line removal logic in the code
- **AND** if `removeEmptyLines` is true, single-spacing SHALL be skipped as an optimization

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-sample-plugin",
"version": "1.2.2",
"version": "1.3.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"main": "dist/main.js",
"scripts": {

View file

@ -17,6 +17,7 @@ interface PasteReformmatterSettings {
cascadeHeadingLevels: boolean; // Whether to cascade heading levels (e.g., H1→H2→H3 becomes H2→H3→H4 when max level is H2)
contextualCascade: boolean; // Whether to cascade headings based on the current context (e.g., if cursor is in an H2 section, headings will start from H3)
stripLineBreaks: boolean; // Whether to strip hard line breaks (br tags) when reformatting pasted content
convertToSingleSpaced: boolean; // Whether to collapse multiple consecutive blank lines into a single blank line
removeEmptyLines: boolean; // Whether to remove blank lines in the Markdown output
htmlRegexReplacements: RegexReplacement[]; // Regular expression replacements to apply to the HTML content before converting to Markdown
markdownRegexReplacements: RegexReplacement[]; // Regular expression replacements to apply to the Markdown content after HTML conversion
@ -29,6 +30,7 @@ const DEFAULT_SETTINGS: PasteReformmatterSettings = {
cascadeHeadingLevels: true,
contextualCascade: true,
stripLineBreaks: false,
convertToSingleSpaced: false,
removeEmptyLines: false,
htmlRegexReplacements: [],
markdownRegexReplacements: []
@ -525,6 +527,22 @@ class PasteReformmatterSettingsTab extends PluginSettingTab {
await this.plugin.saveSettings();
}));
const singleSpacedSetting = new Setting(containerEl)
.setName('Convert to single-spaced')
.setDesc('Collapse multiple consecutive blank lines into a single blank line')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.convertToSingleSpaced)
.setDisabled(this.plugin.settings.removeEmptyLines)
.onChange(async (value) => {
this.plugin.settings.convertToSingleSpaced = value;
await this.plugin.saveSettings();
}));
// Add visual indication when disabled
if (this.plugin.settings.removeEmptyLines) {
singleSpacedSetting.settingEl.addClass('paste-reformatter-disabled-setting');
}
new Setting(containerEl)
.setName('Remove empty lines')
.setDesc('Remove blank lines in the Markdown output')
@ -533,6 +551,9 @@ class PasteReformmatterSettingsTab extends PluginSettingTab {
.onChange(async (value) => {
this.plugin.settings.removeEmptyLines = value;
await this.plugin.saveSettings();
// Refresh the display to update the disabled state of "Convert to single-spaced"
this.display();
}));
new Setting(containerEl)

View file

@ -16,6 +16,7 @@ export function transformMarkdown(
maxHeadingLevel: number,
cascadeHeadingLevels: boolean,
stripLineBreaks: boolean,
convertToSingleSpaced: boolean,
removeEmptyLines: boolean
},
contextLevel: number = 0,
@ -146,6 +147,21 @@ export function transformMarkdown(
// First handle the special line break markers
let preserveLineBreaks = !settings.stripLineBreaks;
// Convert multiple consecutive blank lines to single blank line if enabled
// Skip this if removeEmptyLines is enabled (optimization - lines will be removed anyway)
if (settings.convertToSingleSpaced && !settings.removeEmptyLines) {
// Normalize line endings to ensure consistent processing
markdown = markdown.replace(/\r\n/g, '\n');
// Replace 2 or more consecutive newlines with exactly 2 newlines (1 blank line)
const originalMarkdown = markdown;
markdown = markdown.replace(/\n{3,}/g, '\n\n');
if (originalMarkdown !== markdown) {
appliedTransformations = true;
}
}
// Process empty lines with a sliding window approach
if (settings.removeEmptyLines) {
// First, normalize line endings to ensure consistent processing

View file

@ -115,3 +115,14 @@ If your plugin does not need CSS, delete this file.
outline: 2px solid var(--interactive-accent);
outline-offset: 2px;
} */
/* Disabled Setting Styles */
.paste-reformatter-disabled-setting {
opacity: 0.5;
pointer-events: none;
}
.paste-reformatter-disabled-setting .setting-item-name,
.paste-reformatter-disabled-setting .setting-item-description {
color: var(--text-muted);
}

View file

@ -4,5 +4,6 @@
"1.1.1": "0.15.0",
"1.2.0": "0.15.0",
"1.2.1": "0.15.0",
"1.2.2": "0.15.0"
"1.2.2": "0.15.0",
"1.3.0": "0.15.0"
}