* Rewrite one body-tag occurrence in place, by its parsed span. The span is * re-validated against current file content first, so an edit that raced a * change to the note refuses to write (with a Notice) rather than corrupt * unrelated text. `newValue` is the full replacement token c
(property: Partial<Property>, newValue: unknown, file: TFile)
| 631 | * unrelated text. `newValue` is the full replacement token computed by the UI. |
| 632 | */ |
| 633 | private async writeTagOccurrence(property: Partial<Property>, newValue: unknown, file: TFile): Promise<void> { |
| 634 | // The UI passes a full token (`#new`, `#a/leaf`, `#tag:value`); a bare |
| 635 | // value from the API (`"done"`) is normalised to `#done`. Reject anything |
| 636 | // that is not a single valid tag rather than splice invalid text into the |
| 637 | // note (e.g. `#meeting notes`, which Obsidian would parse as `#meeting`). |
| 638 | const newToken = normalizeTagToken(String(newValue)); |
| 639 | if (!isValidTagToken(newToken)) { |
| 640 | throw new Error(`'${String(newValue)}' is not a valid tag name.`); |
| 641 | } |
| 642 | |
| 643 | const content = await this.app.vault.read(file); |
| 644 | const updated = spliceTag(content, property.position, property.key ?? "", newToken); |
| 645 | |
| 646 | if (updated === null) { |
| 647 | throw new Error( |
| 648 | `could not locate the tag '${property.key}' to edit - the note may have changed since it was opened. Reopen and try again.`, |
| 649 | ); |
| 650 | } |
| 651 | |
| 652 | await this.app.vault.modify(file, updated); |
| 653 | } |
| 654 | |
| 655 | private escapeSpecialCharacters(text: string): string{ |
| 656 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); |
no test coverage detected