@salesforcedevs/docs-components
Advanced tools
Comparing version 0.14.3 to 0.15.0-alpha.0
{ | ||
"name": "@salesforcedevs/docs-components", | ||
"version": "0.14.3", | ||
"version": "0.15.0-alpha.0", | ||
"description": "Docs Lightning web components for DSC", | ||
@@ -17,3 +17,3 @@ "license": "MIT", | ||
}, | ||
"gitHead": "5f2e8f24d38615ea586617396722792cf8a42f20" | ||
"gitHead": "ccf65c5d3c7390f1eb3cff119a800adafe89a2ed" | ||
} |
@@ -0,1 +1,2 @@ | ||
import CodeBlock from "dx/codeBlock"; | ||
import { createRenderComponent } from "utils/tests"; | ||
@@ -37,5 +38,4 @@ import Content from "../content"; | ||
}); | ||
const empty_dx_buttons = c_no_buttons.shadowRoot.querySelectorAll( | ||
"dx-button" | ||
); | ||
const empty_dx_buttons = | ||
c_no_buttons.shadowRoot.querySelectorAll("dx-button"); | ||
expect(empty_dx_buttons.length).toEqual(0); | ||
@@ -56,5 +56,4 @@ }); | ||
expect(images.length).toEqual(3); | ||
const contentMediaEls = component.shadowRoot.querySelectorAll( | ||
"doc-content-media" | ||
); | ||
const contentMediaEls = | ||
component.shadowRoot.querySelectorAll("doc-content-media"); | ||
expect(contentMediaEls.length).toEqual(0); | ||
@@ -75,5 +74,4 @@ }); | ||
expect(images.length).toEqual(0); | ||
const contentMediaEls = component.shadowRoot.querySelectorAll( | ||
"doc-content-media" | ||
); | ||
const contentMediaEls = | ||
component.shadowRoot.querySelectorAll("doc-content-media"); | ||
expect(contentMediaEls.length).toEqual(2); | ||
@@ -123,2 +121,196 @@ contentMediaEls.forEach((contentMedia) => { | ||
}); | ||
it("highlights text that matches search criteria", () => { | ||
const component = render({ | ||
docsData: mockContent.content, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
expect(contentEl.querySelectorAll("mark")).toHaveLength(0); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: "apex", | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
expect(contentEl.querySelectorAll("mark")).toHaveLength(14); | ||
}); | ||
it("doesn't highlight elements that contains dx/docs components", () => { | ||
const searchTerm = "customreport"; | ||
const component = render({ | ||
docsData: mockContent.sampleCodeBlockAndNote, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
expect(contentEl.querySelectorAll("mark")).toHaveLength(0); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: searchTerm, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const marks: Array<HTMLElement> = contentEl.querySelectorAll("mark"); | ||
expect(marks).toHaveLength(0); | ||
const code: CodeBlock = contentEl.querySelector("dx-code-block"); | ||
expect(code).not.toBeNull(); | ||
expect(code.codeBlock).toBeTruthy(); | ||
expect(contentEl.querySelector("doc-content-callout")).not.toBeNull(); | ||
}); | ||
it("doesn't highlight terms separated by html tags", () => { | ||
const searchTerm = "loadData within"; | ||
const component = render({ | ||
docsData: mockContent.sampleCodeBlockAndNote, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: searchTerm, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const marks: Array<HTMLElement> = | ||
component.shadowRoot.querySelectorAll("mark"); | ||
expect(marks).toHaveLength(0); | ||
}); | ||
it("escapes regex especial characters", () => { | ||
const searchTerm = "used.+"; | ||
const component = render({ | ||
docsData: mockContent.sampleCodeBlockAndNote, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: searchTerm, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const marks: Array<HTMLElement> = | ||
component.shadowRoot.querySelectorAll("mark"); | ||
expect(marks).toHaveLength(0); | ||
}); | ||
it("cleans previous search result before searching for a new term", () => { | ||
const firstSearch = "verify"; | ||
const secondSearch = "sObject"; | ||
const component = render({ | ||
docsData: mockContent.sampleCodeBlockAndNote, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: firstSearch, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const firstMarks: Array<HTMLElement> = | ||
component.shadowRoot.querySelectorAll("mark"); | ||
expect(firstMarks).toHaveLength(2); | ||
firstMarks.forEach(({ textContent }) => | ||
expect(textContent).toBe(firstSearch) | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: secondSearch, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const secondMarks: Array<HTMLElement> = | ||
component.shadowRoot.querySelectorAll("mark"); | ||
expect(secondMarks).toHaveLength(1); | ||
secondMarks.forEach(({ textContent }) => | ||
expect(textContent).toBe(secondSearch) | ||
); | ||
}); | ||
it("cleans all marks if the search term is empty string", () => { | ||
const firstSearch = "verify"; | ||
const secondSearch = ""; | ||
const component = render({ | ||
docsData: mockContent.sampleCodeBlockAndNote, | ||
pageReference: mockPageReference, | ||
isStorybook: true | ||
}); | ||
const contentEl = component.shadowRoot.querySelector( | ||
'[data-name="content"]' | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: firstSearch, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
const firstMarks: Array<HTMLElement> = | ||
component.shadowRoot.querySelectorAll("mark"); | ||
expect(firstMarks).toHaveLength(2); | ||
firstMarks.forEach(({ textContent }) => | ||
expect(textContent).toBe(firstSearch) | ||
); | ||
contentEl.dispatchEvent( | ||
new CustomEvent("highlightedtermchange", { | ||
detail: secondSearch, | ||
composed: true, | ||
bubbles: true | ||
}) | ||
); | ||
expect(component.shadowRoot.querySelectorAll("mark")).toHaveLength(0); | ||
}); | ||
}); |
@@ -316,2 +316,29 @@ export const content = `<div><a name='apex_dev_guide'><!-- --></a><h1 class='helpHead1'>Apex Developer Guide</h1><div class='shortdesc'>Salesforce has changed the way organizations do business by moving enterprise | ||
export const sampleCodeBlockAndNote = ` | ||
<h1 class='helpHead1'>Using the instanceof Keyword</h1> | ||
<p class="p">If you need to verify at run time whether an object is actually an instance of a particular class, use the | ||
<samp class="codeph apex_code"><span class="statement">instanceof</span></samp> keyword. | ||
The <samp class="codeph apex_code"><span class="statement">instanceof</span></samp> keyword can only be used to | ||
verify if the target type in the expression on the right of the keyword is a viable | ||
alternative for the declared type of the expression on the left. | ||
</p> | ||
<div class="p">You could add the following check to the <samp class="codeph apex_code">Report</samp>class in the <a class="xref" href="atlas.en-us.apexcode.meta/apexcode/apex_classes_casting.htm" title="In general, all type information is available at run time. This means that Apex enables casting, that is, a data type of one class can be assigned to a data type of another class, but only if one class is a subclass of the other class. Use casting when you want to convert an object from one data type to another.">classes and casting example</a> before you cast the item back into a <samp class="codeph apex_code">CustomReport</samp> object. | ||
<div class="codeSection apex_code"> | ||
<pre class="codeblock brush:apex">If (Reports.get(0) <span class="statement">instanceof</span> CustomReport) {\n <span class="onelineComment">// Can safely cast it back to a custom report object</span>\n CustomReport c = (CustomReport) Reports.get(0);\n} Else {\n <span class="onelineComment">// Do something with the non-custom-report.</span>\n}</pre> | ||
</div> | ||
</div> | ||
${noteWithOrderedList} | ||
<div class="p"> | ||
Follow these steps: | ||
<ol class="ol enumList"> | ||
<li class="li">Add the data in a .csv file.</li> | ||
<li class="li">Create a static resource for this file.</li> | ||
<li class="li"> | ||
Call <samp class="codeph apex_code">Test.loadData</samp> within your test | ||
method and passing it the sObject type token and the static resource name. | ||
</li> | ||
</ol> | ||
</div> | ||
`; | ||
export const withNotes = `<div> | ||
@@ -318,0 +345,0 @@ ${noteWithTable} |
import * as mockDocContent from "./__tests__/mockDocContent"; | ||
import mockPageReference from "./__tests__/mockPageReference"; | ||
import mockSidebar from "./__tests__/mockSidebar"; | ||
import { html } from "lit-html"; | ||
@@ -109,1 +110,40 @@ | ||
}; | ||
export const HighlightedTerm = ({ docsData, trees }: any) => html` | ||
<style> | ||
.sb-show-main.sb-main-padded { | ||
padding: 0; | ||
} | ||
.container { | ||
display: flex; | ||
} | ||
dx-sidebar { | ||
--dx-c-sidebar-min-height: 100vh; | ||
margin-right: 16px; | ||
} | ||
doc-content { | ||
margin: 16px 16px; | ||
} | ||
</style> | ||
<div class="container"> | ||
<dx-sidebar | ||
header="Guides" | ||
trees="${JSON.stringify(trees)}" | ||
></dx-sidebar> | ||
<doc-content | ||
is-storybook="true" | ||
page-reference="${JSON.stringify(mockPageReference)}" | ||
docs-data="${docsData}" | ||
></doc-content> | ||
</div> | ||
`; | ||
HighlightedTerm.args = { | ||
...defaultArgs, | ||
docsData: mockDocContent.sampleCodeBlockAndNote, | ||
trees: mockSidebar | ||
}; |
/* eslint-disable @lwc/lwc/no-inner-html */ | ||
import { createElement, LightningElement, api, track } from "lwc"; | ||
import { DocContent, PageReference } from "../../../../../../../typings/custom"; | ||
import { DocContent, PageReference } from "typings/custom"; | ||
import ContentCallout from "doc/contentCallout"; | ||
@@ -8,3 +8,20 @@ import CodeBlock from "dx/codeBlock"; | ||
import Button from "dx/button"; | ||
import { highlightTerms } from "utils/highlight"; | ||
const ALLOWED_ELEMENTS = [ | ||
"p", | ||
".p", | ||
".shortdesc", | ||
"h1", | ||
"h2", | ||
"h3", | ||
"h4", | ||
"h5", | ||
"h6", | ||
"li", | ||
"dl", | ||
"th", | ||
"td" | ||
]; | ||
const LANGUAGE_MAP: { [key: string]: string } = { | ||
@@ -19,11 +36,13 @@ js: "javascript" | ||
@api showPaginationButtons: boolean = false; | ||
@api | ||
set docsData(value) { | ||
this._docRendered = false; | ||
this.docContent = value; | ||
this.docContent = (value && value.trim()) || ""; | ||
} | ||
get docsData() { | ||
return this.docContent; | ||
} | ||
_codeBlockTheme: string = "dark"; | ||
@api | ||
@@ -33,2 +52,3 @@ set codeBlockTheme(value) { | ||
} | ||
get codeBlockTheme() { | ||
@@ -39,4 +59,6 @@ return this._codeBlockTheme; | ||
@track docContent: DocContent = ""; | ||
_codeBlockTheme: string = "dark"; | ||
_docRendered: boolean = false; | ||
originalCodeBlockThemeValue: String = ""; | ||
connectedCallback() { | ||
@@ -47,4 +69,16 @@ this.template.addEventListener( | ||
); | ||
window.addEventListener( | ||
"highlightedtermchange", | ||
this.updateHighlighted | ||
); | ||
} | ||
disconnectedCallback(): void { | ||
window.removeEventListener( | ||
"highlightedtermchange", | ||
this.updateHighlighted | ||
); | ||
} | ||
updateTheme() { | ||
@@ -80,15 +114,13 @@ this._codeBlockTheme = | ||
// field to the template. Hence we manipulate the DOM manually. | ||
insertDocHtml() { | ||
const divEl = this.template.querySelector("div"); | ||
insertDocHtml(docContent?: string) { | ||
const divEl = this.getCleanContainer(); | ||
// Some simple data mutation to make Prism work on-the-fly with the existing datasource | ||
const templateEl = document.createElement("template"); | ||
this.docContent = this.docContent.trim(); | ||
// eslint-disable-next-line no-use-before-define | ||
templateEl.innerHTML = this.docContent; | ||
templateEl.innerHTML = docContent || this.docContent; | ||
// Query the code blocks and create a dx-code-block component that contains the code | ||
const codeBlockEls = templateEl.content.querySelectorAll( | ||
".codeSection" | ||
); | ||
const codeBlockEls = | ||
templateEl.content.querySelectorAll(".codeSection"); | ||
codeBlockEls.forEach((codeBlockEl) => { | ||
@@ -145,5 +177,4 @@ codeBlockEl.setAttribute("lwc:dom", "manual"); | ||
// Modify links to work with any domain, links that start with "#" are excluded | ||
const anchorEls = templateEl.content.querySelectorAll( | ||
"a:not([href^='#'])" | ||
); | ||
const anchorEls = | ||
templateEl.content.querySelectorAll("a:not([href^='#'])"); | ||
anchorEls.forEach((anchorEl) => { | ||
@@ -263,2 +294,11 @@ if ( | ||
private getCleanContainer(): HTMLElement | null { | ||
const divEl = this.template.querySelector("div"); | ||
if (divEl?.hasChildNodes()) { | ||
divEl.removeChild(divEl.firstChild!); | ||
} | ||
return divEl; | ||
} | ||
isSamePage(reference: PageReference): boolean { | ||
@@ -278,5 +318,4 @@ return ( | ||
const target = event.currentTarget.dataset.id; | ||
const [page, docId, deliverable, tempContentDocumentId] = target.split( | ||
"/" | ||
); | ||
const [page, docId, deliverable, tempContentDocumentId] = | ||
target.split("/"); | ||
const [contentDocumentId, hash] = tempContentDocumentId.split("#"); | ||
@@ -305,2 +344,10 @@ const newPageReference = { | ||
updateHighlighted = (event: any) => | ||
highlightTerms( | ||
Array.from( | ||
this.template.querySelectorAll(ALLOWED_ELEMENTS.join(",")) | ||
), | ||
event.detail | ||
); | ||
@api | ||
@@ -307,0 +354,0 @@ public navigateToHash(hash: String) { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
171100
62
4366