@salesforcedevs/docs-components 1.29.0-llm-alpha2 → 1.29.0-newct-alpha1

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/lwc.config.json CHANGED
@@ -5,7 +5,6 @@
5
5
  { "npm": "@salesforcedevs/dw-components" }
6
6
  ],
7
7
  "expose": [
8
- "doc/aiToolbar",
9
8
  "doc/amfReference",
10
9
  "doc/banner",
11
10
  "doc/localeBanner",
@@ -18,6 +17,7 @@
18
17
  "doc/contentMedia",
19
18
  "doc/docXmlContent",
20
19
  "doc/lwcContentLayout",
20
+ "doc/unifiedContentLayout",
21
21
  "doc/header",
22
22
  "doc/heading",
23
23
  "doc/headingAnchor",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "1.29.0-llm-alpha2",
3
+ "version": "1.29.0-newct-alpha1",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -1,15 +1,11 @@
1
1
  /* eslint-disable @lwc/lwc/no-document-query */
2
- import { LightningElement, api, createElement, track } from "lwc";
3
- import { closest, querySelector } from "kagekiri";
2
+ import { LightningElement, api, track } from "lwc";
3
+ import { closest } from "kagekiri";
4
4
  import { toJson, normalizeBoolean } from "dxUtils/normalizers";
5
5
  import { highlightTerms } from "dxUtils/highlight";
6
6
  import { SearchSyncer } from "docUtils/searchSyncer";
7
7
  import type { OptionWithLink } from "typings/custom";
8
8
  import { buildDocLinkClickHandler } from "dxUtils/analytics";
9
- import AiToolbar from "doc/aiToolbar";
10
-
11
- const AI_TOOLBAR_TAG = "doc-ai-toolbar";
12
- const PAGE_HEADING_SELECTOR = "h1";
13
9
 
14
10
  type AnchorMap = { [key: string]: { intersect: boolean; id: string } };
15
11
 
@@ -99,17 +95,6 @@ export default class ContentLayout extends LightningElement {
99
95
  /** Optional origin URL for the footer MFE (e.g. wp-json endpoint). Passed through to dx-footer. */
100
96
  @api origin: string | null = null;
101
97
 
102
- /** Controls whether the AI toolbar is displayed. */
103
- @api
104
- get showAiToolbar() {
105
- return this._showAiToolbar;
106
- }
107
- set showAiToolbar(value) {
108
- this._showAiToolbar = normalizeBoolean(value);
109
- this.updateAiToolbar();
110
- }
111
- private _showAiToolbar = false;
112
-
113
98
  @api
114
99
  get breadcrumbs() {
115
100
  return this._breadcrumbs;
@@ -167,7 +152,6 @@ export default class ContentLayout extends LightningElement {
167
152
  protected hasRendered: boolean = false;
168
153
  protected contentLoaded: boolean = false;
169
154
  protected sidebarOpen: boolean = false;
170
- protected aiToolbarElement: HTMLElement | null = null;
171
155
 
172
156
  get shouldDisplayFeedback() {
173
157
  return this.contentLoaded && typeof Sprig !== "undefined";
@@ -273,8 +257,6 @@ export default class ContentLayout extends LightningElement {
273
257
  window.addEventListener("scroll", this.adjustNavPosition);
274
258
  window.addEventListener("resize", this.adjustNavPosition);
275
259
 
276
- this.updateAiToolbar();
277
-
278
260
  if (!this.hasRendered) {
279
261
  this.hasRendered = true;
280
262
  this.restoreScroll();
@@ -284,47 +266,6 @@ export default class ContentLayout extends LightningElement {
284
266
  }
285
267
  }
286
268
 
287
- /**
288
- * Inserts the AI toolbar into the slotted content immediately after the
289
- * first H1 found inside this layout. The H1 typically lives deep in
290
- * `doc-content`'s shadow (rendered via `lwc:dom="manual"`), so we use
291
- * kagekiri's shadow-piercing query to locate it from here.
292
- */
293
- protected updateAiToolbar(): void {
294
- if (!this.showAiToolbar) {
295
- this.removeAiToolbar();
296
- return;
297
- }
298
-
299
- const heading = querySelector(
300
- PAGE_HEADING_SELECTOR,
301
- this.template.host
302
- ) as HTMLElement | null;
303
-
304
- if (!heading) {
305
- return;
306
- }
307
-
308
- if (this.aiToolbarElement?.previousElementSibling === heading) {
309
- return;
310
- }
311
-
312
- this.removeAiToolbar();
313
-
314
- const toolbar = createElement(AI_TOOLBAR_TAG, {
315
- is: AiToolbar
316
- }) as unknown as HTMLElement;
317
- heading.parentNode?.insertBefore(toolbar, heading.nextSibling);
318
- this.aiToolbarElement = toolbar;
319
- }
320
-
321
- protected removeAiToolbar(): void {
322
- if (this.aiToolbarElement) {
323
- this.aiToolbarElement.remove();
324
- this.aiToolbarElement = null;
325
- }
326
- }
327
-
328
269
  disconnectedCallback(): void {
329
270
  this.disconnectObserver();
330
271
  window.removeEventListener(
@@ -340,8 +281,6 @@ export default class ContentLayout extends LightningElement {
340
281
 
341
282
  // Remove link click handler
342
283
  this.template.removeEventListener("click", this.handleLinkClick);
343
-
344
- this.removeAiToolbar();
345
284
  }
346
285
 
347
286
  restoreScroll() {
@@ -584,7 +523,6 @@ export default class ContentLayout extends LightningElement {
584
523
 
585
524
  onSlotChange(): void {
586
525
  this.updateRNB();
587
- this.updateAiToolbar();
588
526
  this.contentLoaded = true;
589
527
  }
590
528
 
@@ -0,0 +1,3 @@
1
+ :host {
2
+ display: block;
3
+ }
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <doc-content-layout
3
+ class="content-type content-type-markdown content-type-docs"
4
+ breadcrumbs={breadcrumbs}
5
+ share-title={shareTitle}
6
+ share-twitter-via={twitterVia}
7
+ sidebar-header={sidebarHeader}
8
+ sidebar-value={sidebarValue}
9
+ sidebar-content={sidebarContent}
10
+ toc-title={tocTitle}
11
+ toc-options={tocOptions}
12
+ toc-aria-level={tocAriaLevel}
13
+ enable-slot-change="true"
14
+ languages={languages}
15
+ language={language}
16
+ show-footer={enableFooter}
17
+ origin={origin}
18
+ >
19
+ <doc-phase
20
+ slot="doc-phase"
21
+ lwc:if={docPhaseInfo}
22
+ doc-phase-info={docPhaseInfo}
23
+ ></doc-phase>
24
+
25
+ <template lwc:if={isMarkdownTopic}>
26
+ <slot></slot>
27
+ </template>
28
+ <!--
29
+ TODO(W-22340752): render OAS specs via doc-redoc-reference when
30
+ topicType === "spec". The docs content-type parser currently 404s
31
+ spec topics until that path lands.
32
+ -->
33
+ </doc-content-layout>
34
+ </template>
@@ -0,0 +1,85 @@
1
+ import { LightningElement, api } from "lwc";
2
+ import { normalizeBoolean } from "dxUtils/normalizers";
3
+ import type { OptionWithLink } from "typings/custom";
4
+
5
+ /**
6
+ * Topic types emitted by the docs content-type parser
7
+ * (see @salesforcedevs/sfdocs-doc-framework: `TopicTypeEnum`).
8
+ */
9
+ const TOPIC_TYPE_MARKDOWN = "markdown";
10
+ const TOPIC_TYPE_SPEC = "spec";
11
+
12
+ /**
13
+ * Wrapper around `doc-content-layout` for the "docs" content type emitted by
14
+ * the `DocsContentTypeParser`.
15
+ *
16
+ * Mirrors the role that `doc-amf-reference` plays for the "reference" content
17
+ * type: it owns content-type-aware concerns (markdown body vs. OAS spec via
18
+ * Redoc) and forwards the chrome (sidebar, breadcrumbs, TOC, footer) to
19
+ * `doc-content-layout`.
20
+ *
21
+ * Only the markdown topic type is wired up today. OAS spec rendering via Redoc
22
+ * is tracked separately (W-22340752) and currently 404s upstream in the parser.
23
+ */
24
+ export default class UnifiedContentLayout extends LightningElement {
25
+ @api breadcrumbs: string | null = null;
26
+ @api sidebarHeader?: string;
27
+ @api sidebarValue?: string;
28
+ @api sidebarContent?: string;
29
+ @api tocTitle?: string;
30
+ @api tocOptions?: string;
31
+ @api tocAriaLevel?: string;
32
+ @api languages?: OptionWithLink[];
33
+ @api language?: string;
34
+
35
+ /** Optional origin URL for the footer MFE (e.g. wp-json endpoint). */
36
+ @api origin: string | null = null;
37
+
38
+ /** Article name from breadcrumbs, used as share title (e.g. for social share). */
39
+ @api shareTitle: string | null = null;
40
+
41
+ /** Optional Twitter "via" handle (e.g. SalesforceDevs) for social share. */
42
+ @api twitterVia: string | null = null;
43
+
44
+ @api hideFooter = false;
45
+
46
+ /**
47
+ * Topic type forwarded from the layout template. The docs parser supports
48
+ * `markdown` today and `spec` (OAS via Redoc) in a follow-up.
49
+ */
50
+ @api topicType: string = TOPIC_TYPE_MARKDOWN;
51
+
52
+ private _docPhaseInfo: string | null = null;
53
+
54
+ @api
55
+ get docPhaseInfo(): string | null {
56
+ return this._docPhaseInfo;
57
+ }
58
+
59
+ set docPhaseInfo(value: string | null) {
60
+ this._docPhaseInfo = value || null;
61
+ }
62
+
63
+ private _expandChildren = false;
64
+
65
+ @api
66
+ get expandChildren(): boolean {
67
+ return this._expandChildren;
68
+ }
69
+
70
+ set expandChildren(value: boolean | string) {
71
+ this._expandChildren = normalizeBoolean(value);
72
+ }
73
+
74
+ get isMarkdownTopic(): boolean {
75
+ return this.topicType === TOPIC_TYPE_MARKDOWN;
76
+ }
77
+
78
+ get isSpecTopic(): boolean {
79
+ return this.topicType === TOPIC_TYPE_SPEC;
80
+ }
81
+
82
+ private get enableFooter(): boolean {
83
+ return !this.hideFooter;
84
+ }
85
+ }
@@ -1,40 +0,0 @@
1
- @import "dxHelpers/reset";
2
-
3
- :host {
4
- display: inline-flex;
5
- }
6
-
7
- .ai-toolbar {
8
- display: flex;
9
- align-items: center;
10
- gap: var(--dx-g-spacing-smd);
11
- padding-bottom: var(--dx-g-spacing-sm);
12
- }
13
-
14
- .toolbar-button {
15
- --dx-g-button-inline-color: var(--dx-g-blue-vibrant-50);
16
- --dx-g-button-inline-color-hover: var(--dx-g-blue-vibrant-30);
17
- --dx-c-button-font-size: var(--dx-g-text-sm);
18
- --dx-c-button-horizontal-spacing: 0;
19
- --dx-c-button-icon-vertical-align: middle;
20
- }
21
-
22
- .toolbar-button::part(content) {
23
- font-family: var(--dx-g-font-display);
24
- font-weight: var(--dx-g-font-demi);
25
- line-height: var(--dx-g-spacing-mlg);
26
- letter-spacing: 0.07px;
27
- }
28
-
29
- .divider {
30
- width: 1px;
31
- height: var(--dx-g-spacing-md);
32
- background-color: var(--dx-g-gray-70);
33
- }
34
-
35
- @media screen and (max-width: 480px) {
36
- .toolbar-button_copy-url,
37
- .divider_copy-url {
38
- display: none;
39
- }
40
- }
@@ -1,53 +0,0 @@
1
- <template>
2
- <div class="ai-toolbar">
3
- <dx-tooltip placement="top-right" label={copyMarkdownLabel}>
4
- <dx-button
5
- class="toolbar-button"
6
- variant="inline"
7
- size="small"
8
- icon-sprite="utility"
9
- icon-symbol="copy"
10
- icon-size="medium"
11
- icon-position="right"
12
- aria-label="Copy as Markdown"
13
- onclick={handleCopyMarkdown}
14
- >
15
- Copy as Markdown
16
- </dx-button>
17
- </dx-tooltip>
18
-
19
- <div class="divider"></div>
20
-
21
- <dx-button
22
- class="toolbar-button"
23
- variant="inline"
24
- size="small"
25
- icon-sprite="utility"
26
- icon-symbol="new_window"
27
- icon-size="medium"
28
- icon-position="right"
29
- aria-label="View as Markdown"
30
- onclick={handleViewMarkdown}
31
- >
32
- View as Markdown
33
- </dx-button>
34
-
35
- <div class="divider divider_copy-url"></div>
36
-
37
- <dx-tooltip placement="top-right" label={copyUrlLabel}>
38
- <dx-button
39
- class="toolbar-button toolbar-button_copy-url"
40
- variant="inline"
41
- size="small"
42
- icon-sprite="utility"
43
- icon-symbol="link"
44
- icon-size="medium"
45
- icon-position="right"
46
- aria-label="Copy URL to Markdown"
47
- onclick={handleCopyUrl}
48
- >
49
- Copy URL to Markdown
50
- </dx-button>
51
- </dx-tooltip>
52
- </div>
53
- </template>
@@ -1,83 +0,0 @@
1
- import { LightningElement } from "lwc";
2
-
3
- const DEFAULT_COPY_TOOLTIP_LABEL = "Click to copy";
4
- const COPIED_TOOLTIP_LABEL = "Copied!";
5
- const COPIED_TOOLTIP_RESET_MS = 2000;
6
-
7
- export default class AiToolbar extends LightningElement {
8
- copyMarkdownLabel: string = DEFAULT_COPY_TOOLTIP_LABEL;
9
- copyUrlLabel: string = DEFAULT_COPY_TOOLTIP_LABEL;
10
-
11
- private copyTooltipResetTimeout: number | null = null;
12
-
13
- async handleCopyMarkdown() {
14
- const markdownUrl = this.getMarkdownUrl();
15
- if (!markdownUrl) {
16
- return;
17
- }
18
-
19
- try {
20
- const response = await fetch(markdownUrl);
21
- if (!response.ok) {
22
- return;
23
- }
24
- const markdown = await response.text();
25
- await navigator.clipboard.writeText(markdown);
26
- this.flashCopied("copyMarkdownLabel");
27
- } catch (error) {
28
- console.error(error);
29
- }
30
- }
31
-
32
- handleViewMarkdown() {
33
- const markdownUrl = this.getMarkdownUrl();
34
- if (!markdownUrl) {
35
- return;
36
- }
37
- window.open(markdownUrl, "_blank", "noopener,noreferrer");
38
- }
39
-
40
- async handleCopyUrl() {
41
- const markdownUrl = this.getMarkdownUrl();
42
- if (!markdownUrl) {
43
- return;
44
- }
45
-
46
- try {
47
- await navigator.clipboard.writeText(markdownUrl);
48
- this.flashCopied("copyUrlLabel");
49
- } catch (error) {
50
- console.error(error);
51
- }
52
- }
53
-
54
- /**
55
- * Returns the `.md` equivalent of the current page URL with any hash and
56
- * query string stripped, or `null` when the current page does not end
57
- * with `.html`.
58
- */
59
- private getMarkdownUrl(): string | null {
60
- const url = new URL(window.location.href);
61
- url.hash = "";
62
- url.search = "";
63
-
64
- if (!url.pathname.endsWith(".html")) {
65
- return null;
66
- }
67
-
68
- url.pathname = url.pathname.replace(/\.html$/, ".md");
69
- return url.toString();
70
- }
71
-
72
- private flashCopied(labelKey: "copyMarkdownLabel" | "copyUrlLabel") {
73
- if (this.copyTooltipResetTimeout !== null) {
74
- window.clearTimeout(this.copyTooltipResetTimeout);
75
- }
76
- this[labelKey] = COPIED_TOOLTIP_LABEL;
77
- this.copyTooltipResetTimeout = window.setTimeout(() => {
78
- this.copyMarkdownLabel = DEFAULT_COPY_TOOLTIP_LABEL;
79
- this.copyUrlLabel = DEFAULT_COPY_TOOLTIP_LABEL;
80
- this.copyTooltipResetTimeout = null;
81
- }, COPIED_TOOLTIP_RESET_MS);
82
- }
83
- }
@@ -1,48 +0,0 @@
1
- import { http, HttpResponse } from "msw";
2
-
3
- /**
4
- * Mocks for the AI toolbar so stories never hit the real
5
- * docs backend. Any story rendering `doc-ai-toolbar` (directly or via
6
- * `doc-content-layout`) must register `aiToolbarMswHandlers` and call
7
- * `interceptWindowOpenForAiToolbar()`.
8
- */
9
- export const DUMMY_MARKDOWN_CONTENT = `# Dummy Markdown
10
-
11
- Storybook serves this placeholder content in place of the real markdown
12
- that the docs backend would return for the current page. It exists so
13
- the "Copied" tooltip, the "View as Markdown" new tab, and the
14
- "Copy URL to Markdown" clipboard behavior are all exercisable in
15
- storybook without hitting the live backend.
16
-
17
- - Item 1
18
- - Item 2
19
- - Item 3
20
- `;
21
-
22
- export const DUMMY_MARKDOWN_DATA_URL = `data:text/markdown;charset=utf-8,${encodeURIComponent(
23
- DUMMY_MARKDOWN_CONTENT
24
- )}`;
25
-
26
- /** Intercepts any `.md` GET request and returns the dummy markdown. */
27
- export const aiToolbarMswHandlers = [
28
- http.get(/\.md(\?.*)?$/, () => HttpResponse.text(DUMMY_MARKDOWN_CONTENT))
29
- ];
30
-
31
- let windowOpenIntercepted = false;
32
-
33
- /** Redirects `window.open` calls for `.md` URLs to the dummy markdown data URL (MSW does not cover new tabs). */
34
- export function interceptWindowOpenForAiToolbar() {
35
- if (windowOpenIntercepted) {
36
- return;
37
- }
38
- windowOpenIntercepted = true;
39
-
40
- const originalOpen = window.open.bind(window);
41
- window.open = ((url?: string | URL, target?: string, features?: string) => {
42
- const stringUrl = url?.toString() ?? "";
43
- if (stringUrl.endsWith(".md")) {
44
- return originalOpen(DUMMY_MARKDOWN_DATA_URL, target, features);
45
- }
46
- return originalOpen(url ?? "", target, features);
47
- }) as typeof window.open;
48
- }