obsidian-dev-utils 98.0.0 → 100.0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/integration-test-plugin/main.js +132 -17
  3. package/dist/lib/cjs/__merged.cjs +11 -1
  4. package/dist/lib/cjs/__merged.d.cts +3 -3
  5. package/dist/lib/cjs/generated-during-build.cjs +1 -1
  6. package/dist/lib/cjs/obsidian/attachment-path.cjs +6 -1
  7. package/dist/lib/cjs/obsidian/attachment-path.d.cts +29 -0
  8. package/dist/lib/cjs/obsidian/attachment-unit-folder.cjs +7 -2
  9. package/dist/lib/cjs/obsidian/attachment-unit-folder.d.cts +32 -2
  10. package/dist/lib/cjs/obsidian/components/plugin-settings-component.cjs +36 -1
  11. package/dist/lib/cjs/obsidian/components/plugin-settings-component.d.cts +21 -0
  12. package/dist/lib/cjs/obsidian/components/plugin-suggestion-component.cjs +20 -6
  13. package/dist/lib/cjs/obsidian/components/plugin-suggestion-component.d.cts +26 -4
  14. package/dist/lib/cjs/obsidian/file-change.cjs +6 -6
  15. package/dist/lib/cjs/obsidian/link.cjs +33 -6
  16. package/dist/lib/cjs/obsidian/link.d.cts +35 -0
  17. package/dist/lib/cjs/obsidian/reference.cjs +9 -1
  18. package/dist/lib/cjs/obsidian/reference.d.cts +33 -0
  19. package/dist/lib/cjs/obsidian/validation.cjs +22 -3
  20. package/dist/lib/cjs/obsidian/validation.d.cts +37 -0
  21. package/dist/lib/cjs/obsidian/vault.cjs +4 -1
  22. package/dist/lib/cjs/obsidian/vault.d.cts +8 -1
  23. package/dist/lib/cjs/script-utils/nano-staged-config.cjs +7 -5
  24. package/dist/lib/cjs/script-utils/nano-staged-config.d.cts +5 -2
  25. package/dist/lib/cjs/script-utils/package-manager.cjs +113 -30
  26. package/dist/lib/cjs/script-utils/package-manager.d.cts +20 -4
  27. package/dist/lib/esm/__merged.d.mts +3 -3
  28. package/dist/lib/esm/__merged.mjs +11 -1
  29. package/dist/lib/esm/generated-during-build.mjs +1 -1
  30. package/dist/lib/esm/obsidian/attachment-path.d.mts +29 -0
  31. package/dist/lib/esm/obsidian/attachment-path.mjs +5 -1
  32. package/dist/lib/esm/obsidian/attachment-unit-folder.d.mts +32 -2
  33. package/dist/lib/esm/obsidian/attachment-unit-folder.mjs +7 -2
  34. package/dist/lib/esm/obsidian/components/plugin-settings-component.d.mts +21 -0
  35. package/dist/lib/esm/obsidian/components/plugin-settings-component.mjs +36 -1
  36. package/dist/lib/esm/obsidian/components/plugin-suggestion-component.d.mts +26 -4
  37. package/dist/lib/esm/obsidian/components/plugin-suggestion-component.mjs +20 -6
  38. package/dist/lib/esm/obsidian/file-change.mjs +6 -6
  39. package/dist/lib/esm/obsidian/link.d.mts +35 -0
  40. package/dist/lib/esm/obsidian/link.mjs +38 -7
  41. package/dist/lib/esm/obsidian/reference.d.mts +33 -0
  42. package/dist/lib/esm/obsidian/reference.mjs +8 -1
  43. package/dist/lib/esm/obsidian/validation.d.mts +37 -0
  44. package/dist/lib/esm/obsidian/validation.mjs +21 -2
  45. package/dist/lib/esm/obsidian/vault.d.mts +8 -1
  46. package/dist/lib/esm/obsidian/vault.mjs +4 -1
  47. package/dist/lib/esm/script-utils/nano-staged-config.d.mts +5 -2
  48. package/dist/lib/esm/script-utils/nano-staged-config.mjs +7 -5
  49. package/dist/lib/esm/script-utils/package-manager.d.mts +20 -4
  50. package/dist/lib/esm/script-utils/package-manager.mjs +113 -30
  51. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 100.0.0
4
+
5
+ - feat(attachment-unit-folder)!: own the designation, not just the walker
6
+
7
+ ## 99.0.0
8
+
9
+ - fix(plugin-suggestion)!: decide the notice after the host settings load
10
+ - fix(vault): complete the operation when there is nothing to write
11
+ - feat(validation): own the Windows reserved-name and trailing-character rules
12
+ - feat(link): scope link editing to an offset range
13
+ - fix(package-manager): collect every signal before picking the manager
14
+ - fix(script-utils): route the remaining npm run hops through the detected package manager
15
+
3
16
  ## 98.0.0
4
17
 
5
18
  - feat(deps)!: require obsidian-integration-testing ^12 as a peer, so OIT 11 no longer resolves
@@ -10574,6 +10574,7 @@ __export(merged_exports, {
10574
10574
  getCacheSafe: () => getCacheSafe,
10575
10575
  getCanvasReferences: () => getCanvasReferences,
10576
10576
  getCaseInsensitiveFileIndex: () => getCaseInsensitiveFileIndex,
10577
+ getCheckIsAttachmentUnitFolderFunction: () => getCheckIsAttachmentUnitFolderFunction,
10577
10578
  getCodeBlockMarkdownInfo: () => getCodeBlockMarkdownInfo,
10578
10579
  getCommunityPluginRepo: () => getCommunityPluginRepo,
10579
10580
  getDebugController: () => getDebugController,
@@ -10639,6 +10640,7 @@ __export(merged_exports, {
10639
10640
  hasPatchToken: () => hasPatchToken,
10640
10641
  hasSingleOccurrence: () => hasSingleOccurrence,
10641
10642
  hasWikilinkSyntax: () => hasWikilinkSyntax,
10643
+ hasWindowsTrailingChars: () => hasWindowsTrailingChars,
10642
10644
  hoverElement: () => hoverElement3,
10643
10645
  ignoreError: () => ignoreError,
10644
10646
  indent: () => indent,
@@ -10693,6 +10695,7 @@ __export(merged_exports, {
10693
10695
  isNote: () => isNote,
10694
10696
  isParseLinkFrontmatterReference: () => isParseLinkFrontmatterReference,
10695
10697
  isParseLinkReference: () => isParseLinkReference,
10698
+ isReferenceInOffsetRange: () => isReferenceInOffsetRange,
10696
10699
  isResourceLockedForPath: () => isResourceLockedForPath,
10697
10700
  isResourceLockedForPathByAncestor: () => isResourceLockedForPathByAncestor,
10698
10701
  isSpellcheckEnabled: () => isSpellcheckEnabled,
@@ -10700,6 +10703,7 @@ __export(merged_exports, {
10700
10703
  isUrl: () => isUrl,
10701
10704
  isValidRegExp: () => isValidRegExp,
10702
10705
  isValidationMessageHolder: () => isValidationMessageHolder,
10706
+ isWindowsReservedName: () => isWindowsReservedName,
10703
10707
  join: () => join,
10704
10708
  listSafe: () => listSafe,
10705
10709
  lockResourceForPath: () => lockResourceForPath,
@@ -10844,6 +10848,7 @@ __export(merged_exports, {
10844
10848
  trimEnd: () => trimEnd,
10845
10849
  trimMarkdownExtension: () => trimMarkdownExtension,
10846
10850
  trimStart: () => trimStart,
10851
+ trimWindowsTrailingChars: () => trimWindowsTrailingChars,
10847
10852
  typeIntoEditor: () => typeIntoEditor3,
10848
10853
  unescape: () => unescape,
10849
10854
  unescapeAlias: () => unescapeAlias,
@@ -11560,6 +11565,7 @@ __export(attachment_path_exports, {
11560
11565
  getAttachmentFolderPath: () => getAttachmentFolderPath,
11561
11566
  getAttachmentFolderPathSyncOrNull: () => getAttachmentFolderPathSyncOrNull,
11562
11567
  getAvailablePathForAttachments: () => getAvailablePathForAttachments,
11568
+ getCheckIsAttachmentUnitFolderFunction: () => getCheckIsAttachmentUnitFolderFunction,
11563
11569
  hasOwnAttachmentFolder: () => hasOwnAttachmentFolder,
11564
11570
  isAtProperAttachmentPath: () => isAtProperAttachmentPath
11565
11571
  });
@@ -12572,6 +12578,9 @@ async function getAvailablePathForAttachments(params) {
12572
12578
  }
12573
12579
  return getAvailablePathForAttachmentsSync(params);
12574
12580
  }
12581
+ function getCheckIsAttachmentUnitFolderFunction(app) {
12582
+ return app.vault.getAvailablePathForAttachments.checkIsAttachmentUnitFolder;
12583
+ }
12575
12584
  async function hasOwnAttachmentFolder(params) {
12576
12585
  const {
12577
12586
  app,
@@ -12719,10 +12728,14 @@ function findAttachmentUnitFolderPath(params) {
12719
12728
  if (!parentFolderPath2 || parentFolderPath2 === "." || parentFolderPath2 === "/") {
12720
12729
  return null;
12721
12730
  }
12731
+ const checkIsAttachmentUnitFolder = "checkIsAttachmentUnitFolder" in params ? params.checkIsAttachmentUnitFolder : getCheckIsAttachmentUnitFolderFunction(params.app);
12732
+ if (!checkIsAttachmentUnitFolder) {
12733
+ return null;
12734
+ }
12722
12735
  const segments = parentFolderPath2.split("/").filter(Boolean);
12723
12736
  for (let count = 1; count <= segments.length; count++) {
12724
12737
  const candidate = segments.slice(0, count).join("/");
12725
- if (params.checkIsAttachmentUnitFolder(candidate)) {
12738
+ if (checkIsAttachmentUnitFolder(candidate)) {
12726
12739
  return candidate;
12727
12740
  }
12728
12741
  }
@@ -13286,6 +13299,7 @@ __export(link_exports, {
13286
13299
  });
13287
13300
  var import_obsidian102 = require("obsidian");
13288
13301
  init_abort_controller();
13302
+ init_debug();
13289
13303
  init_object_utils();
13290
13304
  init_obsidian_dev_utils_state();
13291
13305
  init_path();
@@ -13497,6 +13511,7 @@ __export(reference_exports, {
13497
13511
  isCanvasFileNodeReference: () => isCanvasFileNodeReference,
13498
13512
  isCanvasReference: () => isCanvasReference,
13499
13513
  isCanvasTextNodeReference: () => isCanvasTextNodeReference,
13514
+ isReferenceInOffsetRange: () => isReferenceInOffsetRange,
13500
13515
  referenceToFileChange: () => referenceToFileChange,
13501
13516
  sortReferences: () => sortReferences
13502
13517
  });
@@ -13509,6 +13524,12 @@ function isCanvasReference(reference) {
13509
13524
  function isCanvasTextNodeReference(reference) {
13510
13525
  return isCanvasReference(reference) && reference.type === "text";
13511
13526
  }
13527
+ function isReferenceInOffsetRange(reference, offsetRange) {
13528
+ if (!isReferenceCache(reference)) {
13529
+ return false;
13530
+ }
13531
+ return offsetRange.startOffset <= reference.position.start.offset && reference.position.end.offset <= offsetRange.endOffset;
13532
+ }
13512
13533
  function referenceToFileChange(reference, newContent) {
13513
13534
  if (isFrontmatterLinkCacheWithOffsets(reference)) {
13514
13535
  return {
@@ -13887,6 +13908,9 @@ async function process2(params) {
13887
13908
  if (newContent === null) {
13888
13909
  return false;
13889
13910
  }
13911
+ if (newContent === oldContent) {
13912
+ return true;
13913
+ }
13890
13914
  let isSuccess = true;
13891
13915
  const result = await invokeFileActionSafe({
13892
13916
  app,
@@ -14123,7 +14147,7 @@ async function applyCanvasChanges(params) {
14123
14147
  const canvasData = parseJsonSafe(content3);
14124
14148
  if (!Array.isArray(canvasData.nodes) || !Array.isArray(canvasData.edges)) {
14125
14149
  getLibDebugger("FileChange:applyCanvasChanges")("Not a valid canvas; skipping rewrite", { path });
14126
- return null;
14150
+ return content3;
14127
14151
  }
14128
14152
  const canvasTextChanges = /* @__PURE__ */ new Map();
14129
14153
  for (const change of changes) {
@@ -14142,7 +14166,7 @@ async function applyCanvasChanges(params) {
14142
14166
  nodeIndex: change.reference.nodeIndex,
14143
14167
  path
14144
14168
  });
14145
- return null;
14169
+ return content3;
14146
14170
  }
14147
14171
  if (isCanvasFileNodeChange(change)) {
14148
14172
  if (node2.file !== change.oldContent) {
@@ -14153,7 +14177,7 @@ async function applyCanvasChanges(params) {
14153
14177
  path,
14154
14178
  type: "file"
14155
14179
  });
14156
- return null;
14180
+ return content3;
14157
14181
  }
14158
14182
  node2.file = change.newContent;
14159
14183
  } else if (isCanvasTextNodeChange(change)) {
@@ -14173,7 +14197,7 @@ async function applyCanvasChanges(params) {
14173
14197
  nodeIndex,
14174
14198
  path
14175
14199
  });
14176
- return null;
14200
+ return content3;
14177
14201
  }
14178
14202
  if (typeof node2.text !== "string") {
14179
14203
  const message = "Node text is not a string";
@@ -14181,7 +14205,7 @@ async function applyCanvasChanges(params) {
14181
14205
  nodeIndex,
14182
14206
  path
14183
14207
  });
14184
- return null;
14208
+ return content3;
14185
14209
  }
14186
14210
  const contentChanges = canvasTextChangesForNode.map((change) => {
14187
14211
  const newContent = change.reference.originalReference.original.includes(ESCAPED_WIKILINK_DIVIDER) ? change.newContent.replaceAll(UNESCAPED_WIKILINK_DIVIDER_REGEXP, () => ESCAPED_WIKILINK_DIVIDER) : change.newContent;
@@ -25309,12 +25333,16 @@ async function editLinks(params) {
25309
25333
  const {
25310
25334
  app,
25311
25335
  linkConverter,
25336
+ offsetRange,
25312
25337
  pathOrFile,
25313
25338
  shouldEditExternalLinks = false,
25314
25339
  shouldEditFrontmatterExternalLinks = false,
25315
25340
  shouldEditMultiValueFrontmatterExternalLinks = false,
25316
25341
  ...options
25317
25342
  } = params;
25343
+ if (offsetRange !== void 0) {
25344
+ validateOffsetRange(offsetRange);
25345
+ }
25318
25346
  await applyFileChanges({
25319
25347
  app,
25320
25348
  changesProvider: async ({ abortSignal, content: content3 }) => {
@@ -25330,25 +25358,29 @@ async function editLinks(params) {
25330
25358
  if (content3 !== cachedContent) {
25331
25359
  return null;
25332
25360
  }
25333
- return await getFileChanges({
25361
+ return await getFileChanges(normalizeOptionalProperties({
25334
25362
  abortSignal,
25335
25363
  cache,
25336
25364
  isCanvasFileCache: isCanvasFile(pathOrFile),
25337
25365
  linkConverter,
25366
+ offsetRange,
25338
25367
  shouldIncludeExternalLinks: shouldEditExternalLinks,
25339
25368
  shouldIncludeFrontmatterExternalLinks: shouldEditFrontmatterExternalLinks,
25340
25369
  shouldIncludeMultiValueFrontmatterExternalLinks: shouldEditMultiValueFrontmatterExternalLinks
25341
- });
25370
+ }));
25342
25371
  },
25343
25372
  pathOrFile,
25344
25373
  ...options
25345
25374
  });
25346
25375
  }
25347
25376
  async function editLinksInContent(params) {
25348
- const { app, content: content3, linkConverter, shouldEditExternalLinks = false, shouldEditFrontmatterExternalLinks = false, shouldEditMultiValueFrontmatterExternalLinks = false } = params;
25377
+ const { app, content: content3, linkConverter, offsetRange, shouldEditExternalLinks = false, shouldEditFrontmatterExternalLinks = false, shouldEditMultiValueFrontmatterExternalLinks = false } = params;
25349
25378
  let { abortSignal } = params;
25350
25379
  abortSignal ??= abortSignalNever();
25351
25380
  abortSignal.throwIfAborted();
25381
+ if (offsetRange !== void 0) {
25382
+ validateOffsetRange(offsetRange);
25383
+ }
25352
25384
  const newContent = await applyContentChanges({
25353
25385
  abortSignal,
25354
25386
  changesProvider: async () => {
@@ -25358,15 +25390,16 @@ async function editLinksInContent(params) {
25358
25390
  shouldParseMultiValueFrontmatterExternalLinks: shouldEditMultiValueFrontmatterExternalLinks
25359
25391
  });
25360
25392
  abortSignal.throwIfAborted();
25361
- const changes = await getFileChanges({
25393
+ const changes = await getFileChanges(normalizeOptionalProperties({
25362
25394
  abortSignal,
25363
25395
  cache,
25364
25396
  isCanvasFileCache: false,
25365
25397
  linkConverter,
25398
+ offsetRange,
25366
25399
  shouldIncludeExternalLinks: shouldEditExternalLinks,
25367
25400
  shouldIncludeFrontmatterExternalLinks: shouldEditFrontmatterExternalLinks,
25368
25401
  shouldIncludeMultiValueFrontmatterExternalLinks: shouldEditMultiValueFrontmatterExternalLinks
25369
- });
25402
+ }));
25370
25403
  abortSignal.throwIfAborted();
25371
25404
  return changes;
25372
25405
  },
@@ -25811,6 +25844,7 @@ async function getFileChanges(params) {
25811
25844
  cache,
25812
25845
  isCanvasFileCache,
25813
25846
  linkConverter,
25847
+ offsetRange,
25814
25848
  shouldIncludeExternalLinks = false,
25815
25849
  shouldIncludeFrontmatterExternalLinks = false,
25816
25850
  shouldIncludeMultiValueFrontmatterExternalLinks = false
@@ -25826,8 +25860,16 @@ async function getFileChanges(params) {
25826
25860
  end: section.position.end.offset,
25827
25861
  start: section.position.start.offset
25828
25862
  }));
25863
+ const getFileChangesDebugger = getLibDebugger("Link:getFileChanges");
25829
25864
  for (const link2 of getLinks({ cache, shouldIncludeExternalLinks, shouldIncludeFrontmatterExternalLinks, shouldIncludeMultiValueFrontmatterExternalLinks })) {
25830
25865
  abortSignal.throwIfAborted();
25866
+ if (offsetRange !== void 0 && !isReferenceInOffsetRange(link2, offsetRange)) {
25867
+ getFileChangesDebugger(
25868
+ isReferenceCache(link2) ? "Skipping a link that is not fully contained in the offset range" : "Skipping a link that carries no position within the content, so it can never be in the offset range",
25869
+ { link: link2.original, offsetRange }
25870
+ );
25871
+ continue;
25872
+ }
25831
25873
  const newContent = await linkConverter(link2, abortSignal);
25832
25874
  abortSignal.throwIfAborted();
25833
25875
  if (newContent === void 0) {
@@ -25954,6 +25996,14 @@ function shouldUseWikilinkStyle(params) {
25954
25996
  }
25955
25997
  }
25956
25998
  }
25999
+ function validateOffsetRange(offsetRange) {
26000
+ if (offsetRange.startOffset < 0) {
26001
+ throw new Error(`Invalid offset range: the start offset ${String(offsetRange.startOffset)} must not be negative.`);
26002
+ }
26003
+ if (offsetRange.endOffset < offsetRange.startOffset) {
26004
+ throw new Error(`Invalid offset range: the end offset ${String(offsetRange.endOffset)} must not be less than the start offset ${String(offsetRange.startOffset)}.`);
26005
+ }
26006
+ }
25957
26007
 
25958
26008
  // src/obsidian/backlink.ts
25959
26009
  async function renderBacklinksTable(dv, pathOrFiles) {
@@ -28818,8 +28868,10 @@ var PluginSettingsComponentBase = class extends ComponentEx {
28818
28868
  pluginSettingsClass;
28819
28869
  asyncEvents = new AsyncEvents();
28820
28870
  currentState;
28871
+ isLoadedFromFile = false;
28821
28872
  lastSavedState;
28822
28873
  legacySettingsConverters = [];
28874
+ loadedFromFileResolvers = [];
28823
28875
  propertyNames;
28824
28876
  validators = /* @__PURE__ */ new Map();
28825
28877
  /**
@@ -28917,6 +28969,7 @@ var PluginSettingsComponentBase = class extends ComponentEx {
28917
28969
  await this.saveToFileImpl();
28918
28970
  }
28919
28971
  } finally {
28972
+ this.markLoadedFromFile();
28920
28973
  await this.asyncEvents.triggerAsync("loadSettings", this.currentState, isInitialLoad);
28921
28974
  }
28922
28975
  }
@@ -29086,6 +29139,31 @@ var PluginSettingsComponentBase = class extends ComponentEx {
29086
29139
  }
29087
29140
  return result;
29088
29141
  }
29142
+ /**
29143
+ * Waits until the settings have been read from `data.json` at least once.
29144
+ *
29145
+ * Anything that reads a setting from outside this component has to wait for this, because until the first
29146
+ * {@link loadFromFile} completes {@link settings} still holds the DEFAULTS — reading it earlier answers
29147
+ * with a default rather than with what the user actually stored. That window is easy to miss, since it is
29148
+ * empty on a cold start (the workspace layout is not ready yet, so nothing has run) but wide open on a
29149
+ * runtime enable from the Community Plugins tab or a re-enable after an update, where a layout-ready
29150
+ * handler fires immediately while this component's own async load is still in flight.
29151
+ *
29152
+ * Resolves immediately once the settings have loaded, so a caller that arrives late is not stranded
29153
+ * waiting for an event that has already been raised. Later reloads (an external `data.json` change) do
29154
+ * not reset it — the question it answers is whether the settings have EVER been read, not whether the
29155
+ * most recent read has finished.
29156
+ *
29157
+ * @returns A {@link Promise} that resolves once the settings have been loaded from the file.
29158
+ */
29159
+ whenLoadedFromFile() {
29160
+ if (this.isLoadedFromFile) {
29161
+ return noopAsync();
29162
+ }
29163
+ return new Promise((resolve2) => {
29164
+ this.loadedFromFileResolvers.push(resolve2);
29165
+ });
29166
+ }
29089
29167
  /**
29090
29168
  * Gets the transformer.
29091
29169
  *
@@ -29188,6 +29266,13 @@ var PluginSettingsComponentBase = class extends ComponentEx {
29188
29266
  }
29189
29267
  return this.propertyNames.includes(property);
29190
29268
  }
29269
+ markLoadedFromFile() {
29270
+ this.isLoadedFromFile = true;
29271
+ const resolvers = this.loadedFromFileResolvers.splice(0);
29272
+ for (const resolve2 of resolvers) {
29273
+ resolve2();
29274
+ }
29275
+ }
29191
29276
  async rawRecordToSettings(rawRecord) {
29192
29277
  rawRecord = this.getTransformer().transformObjectRecursively(rawRecord);
29193
29278
  await this.onLoadRecord(rawRecord);
@@ -29292,6 +29377,7 @@ var PluginSuggestionComponent = class extends ComponentEx {
29292
29377
  app;
29293
29378
  isSuggestionDeclined;
29294
29379
  pluginNoticeComponent;
29380
+ pluginSettingsComponent;
29295
29381
  reason;
29296
29382
  setSuggestionDeclined;
29297
29383
  suggestedPluginId;
@@ -29305,6 +29391,7 @@ var PluginSuggestionComponent = class extends ComponentEx {
29305
29391
  super();
29306
29392
  this.app = params.app;
29307
29393
  this.pluginNoticeComponent = params.pluginNoticeComponent;
29394
+ this.pluginSettingsComponent = params.pluginSettingsComponent;
29308
29395
  this.reason = params.reason;
29309
29396
  this.suggestedPluginId = params.suggestedPluginId;
29310
29397
  this.suggestedPluginName = params.suggestedPluginName;
@@ -29357,15 +29444,27 @@ var PluginSuggestionComponent = class extends ComponentEx {
29357
29444
  }));
29358
29445
  }
29359
29446
  /**
29360
- * Loads the component, showing the suggestion notice once the layout is ready.
29447
+ * Loads the component, showing the suggestion notice once the layout is ready AND the host's settings
29448
+ * have been read from disk.
29449
+ *
29450
+ * Both waits matter, and neither implies the other:
29361
29451
  *
29362
- * The wait matters: plugins load in an unspecified order, so the suggested plugin may well be enabled
29363
- * a moment after this one. Checking before the layout is ready would suggest installing something the
29364
- * user already has.
29452
+ * - Layout-ready, because plugins load in an unspecified order, so the suggested plugin may well be
29453
+ * enabled a moment after this one. Checking earlier would suggest installing something the user
29454
+ * already has.
29455
+ * - Settings-loaded, because {@link PluginSuggestionComponentConstructorParams.isSuggestionDeclined}
29456
+ * reads a setting of the host, and the host's settings component is a SIBLING whose own async load is
29457
+ * still in flight here. On a layout that is already ready — a runtime enable from the Community
29458
+ * Plugins tab, or a re-enable after an update — the layout-ready callback fires immediately, so the
29459
+ * read would see the default `false` and ask again a user who has already declined.
29365
29460
  */
29366
29461
  onload() {
29367
29462
  this.addChild(
29368
- new CallbackLayoutReadyComponent(this.app, () => {
29463
+ new CallbackLayoutReadyComponent(this.app, async () => {
29464
+ await this.pluginSettingsComponent.whenLoadedFromFile();
29465
+ if (this.isUnloaded()) {
29466
+ return;
29467
+ }
29369
29468
  this.showSuggestionNotice();
29370
29469
  })
29371
29470
  );
@@ -37106,9 +37205,13 @@ __export(validation_exports, {
37106
37205
  WINDOWS_UNSAFE_PATH_CHARS: () => WINDOWS_UNSAFE_PATH_CHARS,
37107
37206
  getOsAndObsidianUnsafePathCharsRegExp: () => getOsAndObsidianUnsafePathCharsRegExp,
37108
37207
  getOsUnsafePathCharsRegExp: () => getOsUnsafePathCharsRegExp,
37109
- isValidationMessageHolder: () => isValidationMessageHolder
37208
+ hasWindowsTrailingChars: () => hasWindowsTrailingChars,
37209
+ isValidationMessageHolder: () => isValidationMessageHolder,
37210
+ isWindowsReservedName: () => isWindowsReservedName,
37211
+ trimWindowsTrailingChars: () => trimWindowsTrailingChars
37110
37212
  });
37111
37213
  var import_obsidian154 = require("obsidian");
37214
+ init_path();
37112
37215
  init_reg_exp();
37113
37216
  function isValidationMessageHolder(value) {
37114
37217
  return value.validationMessage !== void 0;
@@ -37116,6 +37219,8 @@ function isValidationMessageHolder(value) {
37116
37219
  var OBSIDIAN_UNSAFE_FILENAME_CHARS = /[#^[\]|]/g;
37117
37220
  var WINDOWS_UNSAFE_PATH_CHARS = /[*\\/<>:|?"]/g;
37118
37221
  var UNIX_UNSAFE_PATH_CHARS = /[\0/]/g;
37222
+ var WINDOWS_RESERVED_NAME_REG_EXP = /^(?:AUX|COM[1-9]|CON|LPT[1-9]|NUL|PRN)$/i;
37223
+ var WINDOWS_TRAILING_CHARS_REG_EXP = /[ .]+$/;
37119
37224
  function getOsAndObsidianUnsafePathCharsRegExp(isWindows) {
37120
37225
  return oneOf([
37121
37226
  getOsUnsafePathCharsRegExp(isWindows),
@@ -37126,6 +37231,16 @@ function getOsUnsafePathCharsRegExp(isWindows) {
37126
37231
  isWindows ??= import_obsidian154.Platform.isWin;
37127
37232
  return isWindows ? WINDOWS_UNSAFE_PATH_CHARS : UNIX_UNSAFE_PATH_CHARS;
37128
37233
  }
37234
+ function hasWindowsTrailingChars(name) {
37235
+ return WINDOWS_TRAILING_CHARS_REG_EXP.test(name);
37236
+ }
37237
+ function isWindowsReservedName(name) {
37238
+ const trimmedName = trimWindowsTrailingChars(name);
37239
+ return WINDOWS_RESERVED_NAME_REG_EXP.test(basename(trimmedName, extname(trimmedName)));
37240
+ }
37241
+ function trimWindowsTrailingChars(name) {
37242
+ return name.replace(WINDOWS_TRAILING_CHARS_REG_EXP, "");
37243
+ }
37129
37244
 
37130
37245
  // src/obsidian/plugin/plugin-settings-tab.ts
37131
37246
  init_plugin_context();