feat(workspace): add resizable markdown editor/preview split with draggable handle and markdown outlet styling/demo
This commit is contained in:
BIN
.playwright-mcp/markdown-outlet-bright-bottom.png
Normal file
BIN
.playwright-mcp/markdown-outlet-bright-bottom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
.playwright-mcp/markdown-outlet-bright.png
Normal file
BIN
.playwright-mcp/markdown-outlet-bright.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
.playwright-mcp/markdown-outlet-dark.png
Normal file
BIN
.playwright-mcp/markdown-outlet-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
.playwright-mcp/markdown-outlet-full.png
Normal file
BIN
.playwright-mcp/markdown-outlet-full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-31 - 3.22.0 - feat(workspace)
|
||||
add resizable markdown editor/preview split with draggable handle and markdown outlet styling/demo
|
||||
|
||||
- Introduce a flexible split layout for the workspace markdown editor with a draggable resize handle (splitRatio, minPanelSize, dragging state, mouse handlers and cleanup).
|
||||
- Enhance dees-workspace-markdown: switch from grid to flex, add resize handle UI, prevent pointer selection while dragging, notify Monaco on resize.
|
||||
- Add comprehensive styling and demo content for dees-workspace-markdownoutlet (dark/light themed markdown styles, syntax highlighting classes, and demo scenarios).
|
||||
- Fix typescript-intellisense monaco model update: only set model value when content actually changed to avoid cursor resets.
|
||||
- Add markdown outlet demo helper and numerous screenshot/image assets (.playwright-mcp) for demos/documentation.
|
||||
|
||||
## 2025-12-31 - 3.21.0 - feat(terminal)
|
||||
add dynamic bright/dark theming for terminal components and terminal preview
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.21.0',
|
||||
version: '3.22.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
domtools
|
||||
domtools,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { DeesWorkspaceMonaco } from '../dees-workspace-monaco/dees-workspace-monaco.js';
|
||||
@@ -27,31 +28,113 @@ export class DeesWorkspaceMarkdown extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
.gridcontainer {
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.splitContainer {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 60% 40%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.editorContainer {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-width: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.resizeHandle {
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#30363d')};
|
||||
cursor: col-resize;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resizeHandle:hover,
|
||||
.resizeHandle.dragging {
|
||||
background: ${cssManager.bdTheme('#3b82f6', '#58a6ff')};
|
||||
}
|
||||
|
||||
.resizeHandle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2px;
|
||||
height: 32px;
|
||||
background: ${cssManager.bdTheme('#9ca3af', '#6e7681')};
|
||||
border-radius: 1px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.resizeHandle:hover::after,
|
||||
.resizeHandle.dragging::after {
|
||||
background: ${cssManager.bdTheme('#ffffff', '#ffffff')};
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.outletContainer {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
font-family: 'Roboto Slab';
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-width: 100px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0d1117')};
|
||||
color: ${cssManager.bdTheme('#24292f', '#e6edf3')};
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Prevent text selection while dragging */
|
||||
.splitContainer.dragging {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.splitContainer.dragging .editorContainer,
|
||||
.splitContainer.dragging .outletContainer {
|
||||
pointer-events: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Initial split ratio for the editor (left) panel.
|
||||
* Value from 0 to 100 representing percentage width.
|
||||
* Default is 50 (50/50 split).
|
||||
*/
|
||||
@property({ type: Number })
|
||||
accessor splitRatio: number = 50;
|
||||
|
||||
/**
|
||||
* Minimum width percentage for either panel.
|
||||
*/
|
||||
@property({ type: Number })
|
||||
accessor minPanelSize: number = 10;
|
||||
|
||||
@state()
|
||||
accessor currentSplitRatio: number = 50;
|
||||
|
||||
@state()
|
||||
accessor isDragging: boolean = false;
|
||||
|
||||
private resizeHandleElement: HTMLElement;
|
||||
private containerElement: HTMLElement;
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<div class="gridcontainer">
|
||||
<div class="editorContainer">
|
||||
<div class="splitContainer ${this.isDragging ? 'dragging' : ''}">
|
||||
<div class="editorContainer" style="width: ${this.currentSplitRatio}%">
|
||||
<dees-workspace-monaco
|
||||
.language=${'markdown'}
|
||||
.content=${`# a test content
|
||||
@@ -78,7 +161,11 @@ const hello = 'yes'
|
||||
wordWrap="bounded"
|
||||
></dees-workspace-monaco>
|
||||
</div>
|
||||
<div class="outletContainer">
|
||||
<div
|
||||
class="resizeHandle ${this.isDragging ? 'dragging' : ''}"
|
||||
@mousedown=${this.handleMouseDown}
|
||||
></div>
|
||||
<div class="outletContainer" style="width: ${100 - this.currentSplitRatio}%">
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,9 +174,17 @@ const hello = 'yes'
|
||||
|
||||
public async firstUpdated(_changedPropertiesArg) {
|
||||
await super.firstUpdated(_changedPropertiesArg);
|
||||
|
||||
// Initialize current ratio from property
|
||||
this.currentSplitRatio = this.splitRatio;
|
||||
|
||||
// Cache elements
|
||||
this.containerElement = this.shadowRoot.querySelector('.splitContainer');
|
||||
this.resizeHandleElement = this.shadowRoot.querySelector('.resizeHandle');
|
||||
|
||||
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
|
||||
// lets care about wiring the markdown stuff.
|
||||
// Wire up markdown rendering
|
||||
const markdownOutlet = this.shadowRoot.querySelector('dees-workspace-markdownoutlet');
|
||||
const smartmarkdownInstance = new domtools.plugins.smartmarkdown.SmartMarkdown();
|
||||
const mdParsedResult = await smartmarkdownInstance.getMdParsedResultFromMarkdown('loading...')
|
||||
@@ -97,6 +192,64 @@ const hello = 'yes'
|
||||
await mdParsedResult.updateFromMarkdownString(contentArg)
|
||||
const html = mdParsedResult.html;
|
||||
markdownOutlet.updateHtmlText(html);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private handleMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.isDragging = true;
|
||||
|
||||
document.addEventListener('mousemove', this.handleMouseMove);
|
||||
document.addEventListener('mouseup', this.handleMouseUp);
|
||||
};
|
||||
|
||||
private handleMouseMove = (e: MouseEvent) => {
|
||||
if (!this.isDragging || !this.containerElement) return;
|
||||
|
||||
const containerRect = this.containerElement.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width;
|
||||
const mouseX = e.clientX - containerRect.left;
|
||||
|
||||
// Calculate percentage, accounting for the resize handle width (6px)
|
||||
let newRatio = (mouseX / containerWidth) * 100;
|
||||
|
||||
// Clamp to min/max
|
||||
newRatio = Math.max(this.minPanelSize, Math.min(100 - this.minPanelSize, newRatio));
|
||||
|
||||
this.currentSplitRatio = newRatio;
|
||||
};
|
||||
|
||||
private handleMouseUp = () => {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
|
||||
// Trigger resize on monaco editor
|
||||
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
if (editor) {
|
||||
// Monaco needs to be notified of size changes
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
};
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
// Clean up event listeners
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically set the split ratio
|
||||
*/
|
||||
public setSplitRatio(ratio: number) {
|
||||
this.currentSplitRatio = Math.max(this.minPanelSize, Math.min(100 - this.minPanelSize, ratio));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to initial split ratio
|
||||
*/
|
||||
public resetSplitRatio() {
|
||||
this.currentSplitRatio = this.splitRatio;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
import { html, css } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import '../../dees-panel/dees-panel.js';
|
||||
import type { DeesWorkspaceMarkdownoutlet } from './dees-workspace-markdownoutlet.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
${css`
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div class="demo-container">
|
||||
<!-- Demo 1: Headings -->
|
||||
<dees-panel
|
||||
.title=${'Headings'}
|
||||
.subtitle=${'All heading levels from H1 to H6'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<h1>Heading Level 1</h1>
|
||||
<h2>Heading Level 2</h2>
|
||||
<h3>Heading Level 3</h3>
|
||||
<h4>Heading Level 4</h4>
|
||||
<h5>Heading Level 5</h5>
|
||||
<h6>Heading Level 6</h6>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 2: Text Formatting -->
|
||||
<dees-panel
|
||||
.title=${'Text Formatting'}
|
||||
.subtitle=${'Bold, italic, links, code, and keyboard input'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<p>This is a paragraph with <strong>bold text</strong> and <em>italic text</em>.</p>
|
||||
<p>You can also use <strong><em>bold italic</em></strong> for emphasis.</p>
|
||||
<p>Here's a <a href="#">link example</a> and some <code>inline code</code>.</p>
|
||||
<p>Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy text.</p>
|
||||
<hr>
|
||||
<p>Above is a horizontal rule separator.</p>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 3: Lists -->
|
||||
<dees-panel
|
||||
.title=${'Lists'}
|
||||
.subtitle=${'Ordered, unordered, and task lists'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<h3>Unordered List</h3>
|
||||
<ul>
|
||||
<li>First item</li>
|
||||
<li>Second item
|
||||
<ul>
|
||||
<li>Nested item 1</li>
|
||||
<li>Nested item 2</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Third item</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ordered List</h3>
|
||||
<ol>
|
||||
<li>Step one</li>
|
||||
<li>Step two</li>
|
||||
<li>Step three</li>
|
||||
</ol>
|
||||
|
||||
<h3>Task List</h3>
|
||||
<ul>
|
||||
<li class="task-list-item"><input type="checkbox" checked disabled> Completed task</li>
|
||||
<li class="task-list-item"><input type="checkbox" disabled> Pending task</li>
|
||||
<li class="task-list-item"><input type="checkbox" disabled> Another task</li>
|
||||
</ul>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 4: Code Blocks -->
|
||||
<dees-panel
|
||||
.title=${'Code Blocks'}
|
||||
.subtitle=${'Syntax highlighted code examples'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<p>Inline code: <code>const greeting = "Hello, World!";</code></p>
|
||||
|
||||
<h3>TypeScript Example</h3>
|
||||
<pre><code><span class="hljs-keyword">import</span> { html, css } <span class="hljs-keyword">from</span> <span class="hljs-string">'@design.estate/dees-element'</span>;
|
||||
|
||||
<span class="hljs-keyword">interface</span> <span class="hljs-title">IUser</span> {
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
|
||||
<span class="hljs-attr">email</span>: <span class="hljs-built_in">string</span>;
|
||||
<span class="hljs-attr">age</span>?: <span class="hljs-built_in">number</span>;
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-title">getUser</span> = <span class="hljs-keyword">async</span> (<span class="hljs-attr">id</span>: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span><IUser> => {
|
||||
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(\`/api/users/\${id}\`);
|
||||
<span class="hljs-keyword">return</span> response.json();
|
||||
};</code></pre>
|
||||
|
||||
<h3>JSON Example</h3>
|
||||
<pre><code>{
|
||||
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"dees-catalog"</span>,
|
||||
<span class="hljs-attr">"version"</span>: <span class="hljs-string">"3.0.0"</span>,
|
||||
<span class="hljs-attr">"dependencies"</span>: {
|
||||
<span class="hljs-attr">"@design.estate/dees-element"</span>: <span class="hljs-string">"^2.0.0"</span>
|
||||
}
|
||||
}</code></pre>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 5: Tables -->
|
||||
<dees-panel
|
||||
.title=${'Tables'}
|
||||
.subtitle=${'Styled tables with alternating rows'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Status</th>
|
||||
<th>Priority</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Dark mode support</td>
|
||||
<td>Completed</td>
|
||||
<td>High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Responsive design</td>
|
||||
<td>In Progress</td>
|
||||
<td>Medium</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Accessibility</td>
|
||||
<td>Planned</td>
|
||||
<td>High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Documentation</td>
|
||||
<td>In Progress</td>
|
||||
<td>Low</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 6: Blockquotes & Alerts -->
|
||||
<dees-panel
|
||||
.title=${'Blockquotes & Alerts'}
|
||||
.subtitle=${'Quotes and GitHub-style alerts'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<blockquote>
|
||||
<p>This is a standard blockquote. It's great for highlighting important information or quotes from other sources.</p>
|
||||
</blockquote>
|
||||
|
||||
<div class="markdown-alert markdown-alert-note">
|
||||
<p><strong>Note:</strong> This is an informational note to draw attention to important details.</p>
|
||||
</div>
|
||||
|
||||
<div class="markdown-alert markdown-alert-tip">
|
||||
<p><strong>Tip:</strong> Here's a helpful tip to improve your workflow.</p>
|
||||
</div>
|
||||
|
||||
<div class="markdown-alert markdown-alert-important">
|
||||
<p><strong>Important:</strong> This information is crucial for understanding the topic.</p>
|
||||
</div>
|
||||
|
||||
<div class="markdown-alert markdown-alert-warning">
|
||||
<p><strong>Warning:</strong> Be careful when performing this action.</p>
|
||||
</div>
|
||||
|
||||
<div class="markdown-alert markdown-alert-caution">
|
||||
<p><strong>Caution:</strong> This action may have unintended consequences.</p>
|
||||
</div>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
|
||||
<!-- Demo 7: Full Document -->
|
||||
<dees-panel
|
||||
.title=${'Full Document'}
|
||||
.subtitle=${'A complete markdown document example'}
|
||||
.runAfterRender=${async (panelEl: HTMLElement) => {
|
||||
const outlet = panelEl.querySelector('dees-workspace-markdownoutlet') as DeesWorkspaceMarkdownoutlet;
|
||||
await outlet?.updateHtmlText(`
|
||||
<h1>Getting Started Guide</h1>
|
||||
<p>Welcome to the <strong>dees-catalog</strong> component library. This guide will help you get up and running quickly.</p>
|
||||
|
||||
<h2>Installation</h2>
|
||||
<p>Install the package using your preferred package manager:</p>
|
||||
<pre><code><span class="hljs-comment"># Using pnpm (recommended)</span>
|
||||
pnpm add @design.estate/dees-catalog
|
||||
|
||||
<span class="hljs-comment"># Using npm</span>
|
||||
npm install @design.estate/dees-catalog</code></pre>
|
||||
|
||||
<h2>Basic Usage</h2>
|
||||
<p>Import and use components in your TypeScript files:</p>
|
||||
<pre><code><span class="hljs-keyword">import</span> { DeesButton } <span class="hljs-keyword">from</span> <span class="hljs-string">'@design.estate/dees-catalog'</span>;
|
||||
|
||||
<span class="hljs-comment">// Use in your templates</span>
|
||||
html\`<dees-button>Click me</dees-button>\`</code></pre>
|
||||
|
||||
<div class="markdown-alert markdown-alert-tip">
|
||||
<p><strong>Tip:</strong> Check the demo showcase for live examples of all components.</p>
|
||||
</div>
|
||||
|
||||
<h2>Available Components</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Component</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>dees-button</code></td>
|
||||
<td>Primary button component</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dees-input-text</code></td>
|
||||
<td>Text input field</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dees-table</code></td>
|
||||
<td>Data table with sorting</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dees-modal</code></td>
|
||||
<td>Modal dialog</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li>Explore the <a href="#">component documentation</a></li>
|
||||
<li>Check out the <a href="#">theming guide</a></li>
|
||||
<li>Join our <a href="#">community Discord</a></li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
<p>"The best component library is one that gets out of your way." — Design Systems Team</p>
|
||||
</blockquote>
|
||||
`);
|
||||
}}
|
||||
>
|
||||
<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>
|
||||
</dees-panel>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
@@ -1,4 +1,13 @@
|
||||
import { customElement, DeesElement, html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import {
|
||||
customElement,
|
||||
DeesElement,
|
||||
html,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { demoFunc } from './dees-workspace-markdownoutlet.demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -9,27 +18,382 @@ declare global {
|
||||
@customElement('dees-workspace-markdownoutlet')
|
||||
export class DeesWorkspaceMarkdownoutlet extends DeesElement {
|
||||
// DEMO
|
||||
public static demo = () => html`<dees-workspace-markdownoutlet></dees-workspace-markdownoutlet>`;
|
||||
public static demo = demoFunc;
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: ${cssManager.bdTheme('#24292f', '#e6edf3')};
|
||||
}
|
||||
|
||||
.outlet {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
color: ${cssManager.bdTheme('#1f2328', '#f0f6fc')};
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.85em;
|
||||
color: ${cssManager.bdTheme('#656d76', '#8b949e')};
|
||||
}
|
||||
|
||||
/* Paragraphs and text */
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: ${cssManager.bdTheme('#0969da', '#58a6ff')};
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Strong and emphasis */
|
||||
strong {
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#1f2328', '#f0f6fc')};
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
ul, ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
ul ul, ul ol, ol ul, ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
blockquote {
|
||||
margin: 0 0 16px 0;
|
||||
padding: 0 1em;
|
||||
color: ${cssManager.bdTheme('#656d76', '#8b949e')};
|
||||
border-left: 4px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
}
|
||||
|
||||
blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
||||
background-color: ${cssManager.bdTheme('rgba(175, 184, 193, 0.2)', 'rgba(110, 118, 129, 0.4)')};
|
||||
border-radius: 6px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
||||
background-color: ${cssManager.bdTheme('#f6f8fa', '#161b22')};
|
||||
border-radius: 6px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: 600;
|
||||
padding: 6px 13px;
|
||||
border: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
background-color: ${cssManager.bdTheme('#f6f8fa', '#161b22')};
|
||||
color: ${cssManager.bdTheme('#1f2328', '#e6edf3')};
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
color: ${cssManager.bdTheme('#1f2328', '#e6edf3')};
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: ${cssManager.bdTheme('#ffffff', '#0d1117')};
|
||||
border-top: 1px solid ${cssManager.bdTheme('#d1d9e0', '#21262d')};
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: ${cssManager.bdTheme('#f6f8fa', '#161b22')};
|
||||
}
|
||||
|
||||
/* Horizontal rules */
|
||||
hr {
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
img {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Task lists */
|
||||
.task-list-item {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.task-list-item input {
|
||||
margin: 0 0.2em 0.25em -1.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Definition lists */
|
||||
dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
padding: 0;
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Keyboard input */
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font-size: 11px;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
||||
line-height: 10px;
|
||||
color: ${cssManager.bdTheme('#1f2328', '#e6edf3')};
|
||||
vertical-align: middle;
|
||||
background-color: ${cssManager.bdTheme('#f6f8fa', '#161b22')};
|
||||
border: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 -1px 0 ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
}
|
||||
|
||||
/* Footnotes */
|
||||
.footnotes {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#656d76', '#8b949e')};
|
||||
border-top: 1px solid ${cssManager.bdTheme('#d1d9e0', '#30363d')};
|
||||
padding-top: 16px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
/* Alerts/Admonitions */
|
||||
.markdown-alert {
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 16px;
|
||||
border-left: 4px solid;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.markdown-alert-note {
|
||||
border-color: ${cssManager.bdTheme('#0969da', '#58a6ff')};
|
||||
background-color: ${cssManager.bdTheme('rgba(9, 105, 218, 0.1)', 'rgba(56, 139, 253, 0.1)')};
|
||||
}
|
||||
|
||||
.markdown-alert-warning {
|
||||
border-color: ${cssManager.bdTheme('#bf8700', '#d29922')};
|
||||
background-color: ${cssManager.bdTheme('rgba(191, 135, 0, 0.1)', 'rgba(187, 128, 9, 0.1)')};
|
||||
}
|
||||
|
||||
.markdown-alert-important {
|
||||
border-color: ${cssManager.bdTheme('#8250df', '#a371f7')};
|
||||
background-color: ${cssManager.bdTheme('rgba(130, 80, 223, 0.1)', 'rgba(163, 113, 247, 0.1)')};
|
||||
}
|
||||
|
||||
.markdown-alert-caution {
|
||||
border-color: ${cssManager.bdTheme('#cf222e', '#f85149')};
|
||||
background-color: ${cssManager.bdTheme('rgba(207, 34, 46, 0.1)', 'rgba(248, 81, 73, 0.1)')};
|
||||
}
|
||||
|
||||
.markdown-alert-tip {
|
||||
border-color: ${cssManager.bdTheme('#1a7f37', '#3fb950')};
|
||||
background-color: ${cssManager.bdTheme('rgba(26, 127, 55, 0.1)', 'rgba(46, 160, 67, 0.1)')};
|
||||
}
|
||||
|
||||
/* Syntax highlighting for code blocks */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: ${cssManager.bdTheme('#6a737d', '#8b949e')};
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-addition {
|
||||
color: ${cssManager.bdTheme('#d73a49', '#ff7b72')};
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-meta-string,
|
||||
.hljs-literal,
|
||||
.hljs-doctag,
|
||||
.hljs-regexp {
|
||||
color: ${cssManager.bdTheme('#032f62', '#a5d6ff')};
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: ${cssManager.bdTheme('#6f42c1', '#d2a8ff')};
|
||||
}
|
||||
|
||||
.hljs-attribute,
|
||||
.hljs-attr,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-type {
|
||||
color: ${cssManager.bdTheme('#005cc5', '#79c0ff')};
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-subst,
|
||||
.hljs-meta,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-link {
|
||||
color: ${cssManager.bdTheme('#e36209', '#ffa657')};
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-deletion {
|
||||
color: ${cssManager.bdTheme('#b31d28', '#ffa198')};
|
||||
}
|
||||
|
||||
.hljs-formula {
|
||||
background-color: ${cssManager.bdTheme('#f6f8fa', '#161b22')};
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
// INSTANCE
|
||||
private outlet: HTMLElement;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="outlet markdown-body">
|
||||
<h1>Hi there</h1>
|
||||
<div class="outlet">
|
||||
<h1>Markdown Preview</h1>
|
||||
<p>Content will appear here when markdown is parsed.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
||||
await super.firstUpdated(_changedProperties);
|
||||
const styleElement = document.createElement('style');
|
||||
const cssText = await (
|
||||
await fetch('https://unpkg.com/github-markdown-css@5.1.0/github-markdown-dark.css')
|
||||
).text();
|
||||
styleElement.textContent = cssText;
|
||||
this.shadowRoot.append(styleElement);
|
||||
this.outlet = this.shadowRoot.querySelector('.outlet');
|
||||
}
|
||||
|
||||
public async updateHtmlText(htmlTextArg: string) {
|
||||
|
||||
@@ -378,7 +378,10 @@ export class TypeScriptIntelliSenseManager {
|
||||
const existingModel = this.monacoInstance.editor.getModel(uri);
|
||||
|
||||
if (existingModel) {
|
||||
existingModel.setValue(content);
|
||||
// Only update if content actually changed (prevents cursor reset)
|
||||
if (existingModel.getValue() !== content) {
|
||||
existingModel.setValue(content);
|
||||
}
|
||||
} else {
|
||||
const language = this.getLanguageFromPath(path);
|
||||
this.monacoInstance.editor.createModel(content, language, uri);
|
||||
|
||||
Reference in New Issue
Block a user