From ebe934fab6c082c37d4af665ef61d1caab822612 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Fri, 25 Apr 2025 00:14:50 -0400 Subject: [PATCH 1/9] add a show/hide link to code snippet use node attr to persist toggle --- plugins/official/stack-snippets/src/schema.ts | 1 + .../stack-snippets/src/snippet-view.ts | 59 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index 20c4dde4..913a6970 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -170,6 +170,7 @@ export const stackSnippetRichTextNodeSpec: { [name: string]: NodeSpec } = { babel: { default: "null" }, babelPresetReact: { default: "null" }, babelPresetTS: { default: "null" }, + showCode: { default: true } }, }, stack_snippet_lang: { diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index 319f1174..fb3edaf3 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -19,6 +19,9 @@ export class StackSnippetView implements NodeView { this.getPos = getPos; this.snippetMetadata = getSnippetMetadata(node); + const codeIsShown: boolean = typeof node.attrs.showCode === "boolean" + ? node.attrs.showCode + : true; //We want to render the language blocks in the middle of some content, // so we need to custom-render stuff here ("holes" must be last) @@ -26,12 +29,51 @@ export class StackSnippetView implements NodeView { this.dom = snippetContainer; snippetContainer.className = "snippet"; + let toggleContainer: HTMLDivElement; + + if (this.snippetMetadata.hide === "true") { + // Create the show/hide link container + toggleContainer = document.createElement("div"); + toggleContainer.className = "snippet-toggle-container"; + + // Create the arrow span + const arrowSpan = document.createElement("span"); + arrowSpan.className = codeIsShown + ? "svg-icon-bg iconArrowDownSm va-middle" + : "svg-icon-bg iconArrowRightSm va-middle"; + toggleContainer.appendChild(arrowSpan); + + // Create the show/hide link + const toggleLink = document.createElement("a"); + toggleLink.href = "#"; + toggleLink.className = "snippet-toggle fs-body1"; + toggleLink.textContent = codeIsShown ? "Hide Code" : "Show Code"; + toggleContainer.appendChild(toggleLink); + + snippetContainer.appendChild(toggleContainer); + } + //This is the div where we're going to render any language blocks const snippetCode = document.createElement("div"); snippetCode.className = "snippet-code"; + snippetCode.style.display = codeIsShown ? "" : "none"; snippetContainer.appendChild(snippetCode); this.contentDOM = snippetCode; + if (this.snippetMetadata.hide === "true") { + toggleContainer.addEventListener("click", (e) => { + e.preventDefault(); + const isVisible = snippetCode.style.display !== "none"; +e + this.view.dispatch( + this.view.state.tr.setNodeMarkup(this.getPos(), null, { + ...node.attrs, + showCode: !isVisible, + }) + ); + }); + } + //And this is where we stash our CTAs and results, which are statically rendered. const snippetResult = document.createElement("div"); snippetResult.className = "snippet-result"; @@ -122,6 +164,19 @@ export class StackSnippetView implements NodeView { JSON.stringify(this.snippetMetadata); this.snippetMetadata = updatedMeta; + // Update the visibility of the snippet-code div and toggle link + const snippetCode = this.contentDOM; + const toggleLink = this.dom.querySelector(".snippet-toggle"); + const arrowSpan = this.dom.querySelector(".svg-icon-bg"); + + const isVisible = node.attrs.showCode as boolean; + snippetCode.style.display = isVisible ? "" : "none"; + toggleLink.textContent = isVisible ? "Hide Code" : "Show Code"; + arrowSpan.className = isVisible + ? "svg-icon-bg iconArrowDownSm va-middle" + : "svg-icon-bg iconArrowRightSm va-middle"; + + // Update the result container if metadata has changed const content = this.contentNode; if (metaChanged && content) { //Clear the node @@ -134,7 +189,7 @@ export class StackSnippetView implements NodeView { "allow-forms allow-modals allow-scripts" ); if (content.nodeType === Node.DOCUMENT_NODE) { - const document = content; + const document = content as Document; iframe.srcdoc = document.documentElement.innerHTML; } this.resultContainer.appendChild(iframe); @@ -148,6 +203,6 @@ export class StackSnippetView implements NodeView { private contentNode: Node; private getPos: () => number; resultContainer: HTMLDivElement; - dom: Node; + dom: HTMLElement; contentDOM: HTMLElement; } From bc06cd0d9ba38595ecc08d90f51de5e5b97cb219 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Fri, 25 Apr 2025 00:49:15 -0400 Subject: [PATCH 2/9] fix copy --- plugins/official/stack-snippets/src/snippet-view.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index fb3edaf3..9b9bbac2 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -47,7 +47,7 @@ export class StackSnippetView implements NodeView { const toggleLink = document.createElement("a"); toggleLink.href = "#"; toggleLink.className = "snippet-toggle fs-body1"; - toggleLink.textContent = codeIsShown ? "Hide Code" : "Show Code"; + toggleLink.textContent = codeIsShown ? "Hide code snippet" : "Show code snippet"; toggleContainer.appendChild(toggleLink); snippetContainer.appendChild(toggleContainer); @@ -171,7 +171,7 @@ e const isVisible = node.attrs.showCode as boolean; snippetCode.style.display = isVisible ? "" : "none"; - toggleLink.textContent = isVisible ? "Hide Code" : "Show Code"; + toggleLink.textContent = isVisible ? "Hide code snippet" : "Show code snippet"; arrowSpan.className = isVisible ? "svg-icon-bg iconArrowDownSm va-middle" : "svg-icon-bg iconArrowRightSm va-middle"; From 16290949ce1ce1504f27fa2d56da6d53e3dac40e Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Fri, 25 Apr 2025 00:53:00 -0400 Subject: [PATCH 3/9] add tests --- .../stack-snippets/test/snippet-view.test.ts | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/plugins/official/stack-snippets/test/snippet-view.test.ts b/plugins/official/stack-snippets/test/snippet-view.test.ts index a2928e45..33dd2187 100644 --- a/plugins/official/stack-snippets/test/snippet-view.test.ts +++ b/plugins/official/stack-snippets/test/snippet-view.test.ts @@ -14,19 +14,20 @@ describe("StackSnippetView", () => { { language: "js" }, schema.text("console.log('test');") ); - const validSnippet = schema.nodes.stack_snippet.createChecked( - { - id: "1234", - babel: "true", - babelPresetReact: "true", - babelPresetTS: "null", - console: "true", - hide: "false", - }, - langNode - ); + const validSnippet = (hide: string) => + schema.nodes.stack_snippet.createChecked( + { + id: "1234", + babel: "true", + babelPresetReact: "true", + babelPresetTS: "null", + console: "true", + hide: hide, + }, + langNode + ); - const buildView = (options?: StackSnippetOptions): EditorView => { + const buildView = (options?: StackSnippetOptions, hide: string = "false"): EditorView => { const state = EditorState.create({ schema: schema, plugins: [stackSnippetPasteHandler], @@ -48,7 +49,7 @@ describe("StackSnippetView", () => { view.state.tr.replaceRangeWith( 0, view.state.doc.nodeSize - 2, - validSnippet + validSnippet(hide) ) ); @@ -128,4 +129,39 @@ describe("StackSnippetView", () => { const [resultDiv] = resultDoc.getElementsByTagName("div"); expect(resultDiv.textContent).toBe("test!"); }); + + it("should render show/hide link if hide attr is true", () => { + const view = buildView(undefined, "true"); + const toggleLink = view.dom.querySelectorAll(".snippet-toggle"); + + expect(toggleLink).toHaveLength(1); + expect(toggleLink[0].textContent).toBe("Hide code snippet"); + }); + + it("should not render show/hide link if hide attr is false", () => { + const view = buildView(undefined, "false"); + const toggleLink = view.dom.querySelectorAll(".snippet-toggle"); + + expect(toggleLink).toHaveLength(0); + }); + + it("should toggle visibility of code snippet when show/hide link is clicked", () => { + const view = buildView(undefined, "true"); + const toggleLink = view.dom.querySelector(".snippet-toggle"); + const snippetCode = view.dom.querySelector(".snippet-code"); + + // Initial state: Code is visible + expect((snippetCode).style.display).toBe(""); + expect(toggleLink.textContent).toBe("Hide code snippet"); + + // Click to hide + (toggleLink).click(); + expect((snippetCode).style.display).toBe("none"); + expect(toggleLink.textContent).toBe("Show code snippet"); + + // Click to show + (toggleLink).click(); + expect((snippetCode).style.display).toBe(""); + expect(toggleLink.textContent).toBe("Hide code snippet"); + }); }); From fe999426f2adbee9b1e474404483b4a81e95f6da Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Fri, 25 Apr 2025 00:56:00 -0400 Subject: [PATCH 4/9] format --- plugins/official/stack-snippets/src/schema.ts | 2 +- .../stack-snippets/src/snippet-view.ts | 18 +++++++++++------- .../stack-snippets/test/snippet-view.test.ts | 5 ++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index 913a6970..ff403a0f 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -170,7 +170,7 @@ export const stackSnippetRichTextNodeSpec: { [name: string]: NodeSpec } = { babel: { default: "null" }, babelPresetReact: { default: "null" }, babelPresetTS: { default: "null" }, - showCode: { default: true } + showCode: { default: true }, }, }, stack_snippet_lang: { diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index 9b9bbac2..0e4965ba 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -19,9 +19,10 @@ export class StackSnippetView implements NodeView { this.getPos = getPos; this.snippetMetadata = getSnippetMetadata(node); - const codeIsShown: boolean = typeof node.attrs.showCode === "boolean" - ? node.attrs.showCode - : true; + const codeIsShown: boolean = + typeof node.attrs.showCode === "boolean" + ? node.attrs.showCode + : true; //We want to render the language blocks in the middle of some content, // so we need to custom-render stuff here ("holes" must be last) @@ -47,7 +48,9 @@ export class StackSnippetView implements NodeView { const toggleLink = document.createElement("a"); toggleLink.href = "#"; toggleLink.className = "snippet-toggle fs-body1"; - toggleLink.textContent = codeIsShown ? "Hide code snippet" : "Show code snippet"; + toggleLink.textContent = codeIsShown + ? "Hide code snippet" + : "Show code snippet"; toggleContainer.appendChild(toggleLink); snippetContainer.appendChild(toggleContainer); @@ -56,7 +59,7 @@ export class StackSnippetView implements NodeView { //This is the div where we're going to render any language blocks const snippetCode = document.createElement("div"); snippetCode.className = "snippet-code"; - snippetCode.style.display = codeIsShown ? "" : "none"; + snippetCode.style.display = codeIsShown ? "" : "none"; snippetContainer.appendChild(snippetCode); this.contentDOM = snippetCode; @@ -64,7 +67,6 @@ export class StackSnippetView implements NodeView { toggleContainer.addEventListener("click", (e) => { e.preventDefault(); const isVisible = snippetCode.style.display !== "none"; -e this.view.dispatch( this.view.state.tr.setNodeMarkup(this.getPos(), null, { ...node.attrs, @@ -171,7 +173,9 @@ e const isVisible = node.attrs.showCode as boolean; snippetCode.style.display = isVisible ? "" : "none"; - toggleLink.textContent = isVisible ? "Hide code snippet" : "Show code snippet"; + toggleLink.textContent = isVisible + ? "Hide code snippet" + : "Show code snippet"; arrowSpan.className = isVisible ? "svg-icon-bg iconArrowDownSm va-middle" : "svg-icon-bg iconArrowRightSm va-middle"; diff --git a/plugins/official/stack-snippets/test/snippet-view.test.ts b/plugins/official/stack-snippets/test/snippet-view.test.ts index 33dd2187..bf2e142a 100644 --- a/plugins/official/stack-snippets/test/snippet-view.test.ts +++ b/plugins/official/stack-snippets/test/snippet-view.test.ts @@ -27,7 +27,10 @@ describe("StackSnippetView", () => { langNode ); - const buildView = (options?: StackSnippetOptions, hide: string = "false"): EditorView => { + const buildView = ( + options?: StackSnippetOptions, + hide: string = "false" + ): EditorView => { const state = EditorState.create({ schema: schema, plugins: [stackSnippetPasteHandler], From b9836d3c08660aa215e8c3c0947730aafe857b77 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Mon, 28 Apr 2025 12:10:11 -0400 Subject: [PATCH 5/9] fix test and icon --- .../stack-snippets/src/snippet-view.ts | 29 ++++++++++--------- src/styles/icons.css | 6 ++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index 0e4965ba..763020e5 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -166,19 +166,22 @@ export class StackSnippetView implements NodeView { JSON.stringify(this.snippetMetadata); this.snippetMetadata = updatedMeta; - // Update the visibility of the snippet-code div and toggle link - const snippetCode = this.contentDOM; - const toggleLink = this.dom.querySelector(".snippet-toggle"); - const arrowSpan = this.dom.querySelector(".svg-icon-bg"); - - const isVisible = node.attrs.showCode as boolean; - snippetCode.style.display = isVisible ? "" : "none"; - toggleLink.textContent = isVisible - ? "Hide code snippet" - : "Show code snippet"; - arrowSpan.className = isVisible - ? "svg-icon-bg iconArrowDownSm va-middle" - : "svg-icon-bg iconArrowRightSm va-middle"; + if (this.snippetMetadata.hide === "true") { + + // Update the visibility of the snippet-code div and toggle link + const snippetCode = this.contentDOM; + const toggleLink = this.dom.querySelector(".snippet-toggle"); + const arrowSpan = this.dom.querySelector(".svg-icon-bg"); + + const isVisible = node.attrs.showCode as boolean; + snippetCode.style.display = isVisible ? "" : "none"; + toggleLink.textContent = isVisible + ? "Hide code snippet" + : "Show code snippet"; + arrowSpan.className = isVisible + ? "svg-icon-bg iconArrowDownSm va-middle" + : "svg-icon-bg iconArrowRightSm va-middle"; + } // Update the result container if metadata has changed const content = this.contentNode; diff --git a/src/styles/icons.css b/src/styles/icons.css index 957685ab..5d702699 100644 --- a/src/styles/icons.css +++ b/src/styles/icons.css @@ -13,3 +13,9 @@ .svg-icon-bg.iconEllipsisHorizontal { --bg-icon: url("~@stackoverflow/stacks-icons/src/Icon/EllipsisHorizontal.svg"); } +.svg-icon-bg.iconArrowDownSm { + --bg-icon: url("~@stackoverflow/stacks-icons/src/Icon/ArrowDownSm.svg"); +} +.svg-icon-bg.iconArrowRightSm { + --bg-icon: url("~@stackoverflow/stacks-icons/src/Icon/ArrowRightSm.svg"); +} From 14170dff42086691e2694b242f4168b14a8cf817 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Mon, 28 Apr 2025 13:14:46 -0400 Subject: [PATCH 6/9] format --- plugins/official/stack-snippets/src/snippet-view.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index 763020e5..329b42d2 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -167,7 +167,6 @@ export class StackSnippetView implements NodeView { this.snippetMetadata = updatedMeta; if (this.snippetMetadata.hide === "true") { - // Update the visibility of the snippet-code div and toggle link const snippetCode = this.contentDOM; const toggleLink = this.dom.querySelector(".snippet-toggle"); From 46398864bdbca49fcfeeeb562a0c4cc638ba1b08 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Mon, 28 Apr 2025 13:23:12 -0400 Subject: [PATCH 7/9] add changeset --- .changeset/fifty-tips-listen.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fifty-tips-listen.md diff --git a/.changeset/fifty-tips-listen.md b/.changeset/fifty-tips-listen.md new file mode 100644 index 00000000..245b7764 --- /dev/null +++ b/.changeset/fifty-tips-listen.md @@ -0,0 +1,5 @@ +--- +"@stackoverflow/stacks-editor": minor +--- + +Adds show/hide functionality to Snippets From 78ab895e9960ce05296c5a8b0970b28152a52eab Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Tue, 29 Apr 2025 10:22:41 -0400 Subject: [PATCH 8/9] Update plugins/official/stack-snippets/src/snippet-view.ts Co-authored-by: Dan Cormier --- plugins/official/stack-snippets/src/snippet-view.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index 329b42d2..e38d192b 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -178,8 +178,8 @@ export class StackSnippetView implements NodeView { ? "Hide code snippet" : "Show code snippet"; arrowSpan.className = isVisible - ? "svg-icon-bg iconArrowDownSm va-middle" - : "svg-icon-bg iconArrowRightSm va-middle"; + ? "svg-icon-bg iconArrowDownSm" + : "svg-icon-bg iconArrowRightSm"; } // Update the result container if metadata has changed From c2da4c4f34316a5f10ef0fb5f95485af4a424606 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Tue, 29 Apr 2025 10:22:58 -0400 Subject: [PATCH 9/9] Update plugins/official/stack-snippets/src/snippet-view.ts Co-authored-by: Dan Cormier --- plugins/official/stack-snippets/src/snippet-view.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/official/stack-snippets/src/snippet-view.ts b/plugins/official/stack-snippets/src/snippet-view.ts index e38d192b..f27bdd2d 100644 --- a/plugins/official/stack-snippets/src/snippet-view.ts +++ b/plugins/official/stack-snippets/src/snippet-view.ts @@ -35,13 +35,14 @@ export class StackSnippetView implements NodeView { if (this.snippetMetadata.hide === "true") { // Create the show/hide link container toggleContainer = document.createElement("div"); - toggleContainer.className = "snippet-toggle-container"; + toggleContainer.className = + "snippet-toggle-container d-inline-flex ai-center g2"; // Create the arrow span const arrowSpan = document.createElement("span"); arrowSpan.className = codeIsShown - ? "svg-icon-bg iconArrowDownSm va-middle" - : "svg-icon-bg iconArrowRightSm va-middle"; + ? "svg-icon-bg iconArrowDownSm" + : "svg-icon-bg iconArrowRightSm"; toggleContainer.appendChild(arrowSpan); // Create the show/hide link