From ec8685eddc10e190afee0db73108e37af5b68d6a Mon Sep 17 00:00:00 2001 From: Keath Milligan Date: Tue, 25 Nov 2025 19:19:17 -0600 Subject: [PATCH] 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 --- manifest.json | 2 +- .../proposal.md | 22 +++++ .../specs/markdown-transformations/spec.md | 78 ++++++++++++++++++ .../tasks.md | 30 +++++++ .../specs/markdown-transformations/spec.md | 80 +++++++++++++++++++ package.json | 2 +- src/main.ts | 21 +++++ src/markdownTransformer.ts | 16 ++++ styles.css | 11 +++ versions.json | 3 +- 10 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 openspec/changes/archive/2025-11-26-add-single-spacing-transformer/proposal.md create mode 100644 openspec/changes/archive/2025-11-26-add-single-spacing-transformer/specs/markdown-transformations/spec.md create mode 100644 openspec/changes/archive/2025-11-26-add-single-spacing-transformer/tasks.md create mode 100644 openspec/specs/markdown-transformations/spec.md diff --git a/manifest.json b/manifest.json index 85cf30f..0529171 100644 --- a/manifest.json +++ b/manifest.json @@ -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", diff --git a/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/proposal.md b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/proposal.md new file mode 100644 index 0000000..098a1d8 --- /dev/null +++ b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/proposal.md @@ -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 diff --git a/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/specs/markdown-transformations/spec.md b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/specs/markdown-transformations/spec.md new file mode 100644 index 0000000..efe8111 --- /dev/null +++ b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/specs/markdown-transformations/spec.md @@ -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 diff --git a/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/tasks.md b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/tasks.md new file mode 100644 index 0000000..d075af5 --- /dev/null +++ b/openspec/changes/archive/2025-11-26-add-single-spacing-transformer/tasks.md @@ -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) diff --git a/openspec/specs/markdown-transformations/spec.md b/openspec/specs/markdown-transformations/spec.md new file mode 100644 index 0000000..bb95cbb --- /dev/null +++ b/openspec/specs/markdown-transformations/spec.md @@ -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 + diff --git a/package.json b/package.json index 1552c75..cbe891e 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/main.ts b/src/main.ts index 64ffafe..c501c34 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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) diff --git a/src/markdownTransformer.ts b/src/markdownTransformer.ts index 605add8..fbec27f 100644 --- a/src/markdownTransformer.ts +++ b/src/markdownTransformer.ts @@ -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 diff --git a/styles.css b/styles.css index 5f942d6..6f1113f 100644 --- a/styles.css +++ b/styles.css @@ -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); +} diff --git a/versions.json b/versions.json index dc3780c..bce5541 100644 --- a/versions.json +++ b/versions.json @@ -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" } \ No newline at end of file