Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions example-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Example external CSS file - Custom markdown editor styles */

/* Set editor background color */
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: #ffffff !important;
}

/* Title Style */
h1, h2, h3, h4, h5, h6 {
color: #1fd700 !important;
border-bottom: 2px solid #ffd700 !important;
padding-bottom: 5px !important;
}

/* Code block styles */
pre {
background: rgba(0, 0, 0, 0.3) !important;
border: 1px solid #ffd700 !important;
border-radius: 8px !important;
padding: 15px !important;
}

code {
background: rgba(255, 215, 0, 0.2) !important;
color: #ffd700 !important;
padding: 2px 4px !important;
border-radius: 3px !important;
}

/* Link styles */
a {
color: #00ff7f !important;
text-decoration: none !important;
}

a:hover {
color: #32cd32 !important;
text-decoration: underline !important;
}

/* Table styles */
table {
border-collapse: collapse !important;
background: rgba(255, 255, 255, 0.1) !important;
}

th, td {
border: 1px solid #ffd700 !important;
padding: 8px 12px !important;
}

th {
background: rgba(255, 215, 0, 0.3) !important;
color: #ffffff !important;
font-weight: bold !important;
}

/* Quote block style */
blockquote {
border-left: 4px solid #ffd700 !important;
background: rgba(255, 215, 0, 0.1) !important;
padding: 10px 15px !important;
margin: 10px 0 !important;
}

/* list styles */
ul li::marker {
color: #ffd700 !important;
}

ol li::marker {
color: #ffd700 !important;
}
17 changes: 16 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@
},
"markdown-editor.customCss": {
"type": "string",
"default": ""
"default": "",
"description": "Inline CSS styles to apply to the markdown editor."
},
"markdown-editor.externalCssFiles": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Array of external CSS file paths or URLs to load into the markdown editor. Supports local file paths, workspace relative paths, and HTTP/HTTPS URLs."
},
"markdown-editor.cssLoadOrder": {
"type": "string",
"enum": ["external-first", "custom-first"],
"default": "external-first",
"description": "Order to load CSS: 'external-first' loads external CSS files before custom CSS, 'custom-first' loads custom CSS before external files."
}
}
},
Expand Down
100 changes: 89 additions & 11 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode'
import * as NodePath from 'path'
import * as fs from 'fs'
const KeyVditorOptions = 'vditor.options'

function debug(...args: any[]) {
Expand Down Expand Up @@ -58,9 +59,9 @@ class EditorPanel {
return
}
let doc: undefined | vscode.TextDocument
// from context menu : 从当前打开的 textEditor 中寻找 是否有当前 markdown editor, 有的话则绑定 document
// from context menu : Search for the current markdown editor from the currently opened textEditor, and bind the document if found.
if (uri) {
// 从右键打开文件,先打开文档然后开启自动同步,不然没法保存文件和同步到已经打开的document
// To open a file from the right-click menu, first open the document and then enable auto-sync. Otherwise, you won't be able to save the file or sync it to the already opened document.
doc = await vscode.workspace.openTextDocument(uri)
} else {
doc = vscode.window.activeTextEditor?.document
Expand Down Expand Up @@ -127,8 +128,8 @@ class EditorPanel {
private readonly _context: vscode.ExtensionContext,
private readonly _panel: vscode.WebviewPanel,
private readonly _extensionUri: vscode.Uri,
public _document: vscode.TextDocument, // 当前有 markdown 编辑器
public _uri = _document.uri // 从资源管理器打开,只有 uri 没有 _document
public _document: vscode.TextDocument,
public _uri = _document.uri
) {
// Set the webview's initial html content

Expand All @@ -149,7 +150,6 @@ class EditorPanel {
if (e.document.fileName !== this._document.fileName) {
return
}
// 当 webview panel 激活时不将由 webview编辑导致的 vsc 编辑器更新同步回 webview
// don't change webview panel when webview panel is focus
if (this._panel.active) {
return
Expand All @@ -160,6 +160,16 @@ class EditorPanel {
this._updateEditTitle()
}, 300)
}, this._disposables)

// Monitor configuration changes and reload the webview when CSS-related configurations are modified.
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('markdown-editor.externalCssFiles') ||
e.affectsConfiguration('markdown-editor.customCss') ||
e.affectsConfiguration('markdown-editor.cssLoadOrder')) {
// Regenerate HTML to apply new CSS configuration
this._panel.webview.html = this._getHtmlForWebview(this._panel.webview)
}
}, this._disposables)
// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(
async (message) => {
Expand Down Expand Up @@ -208,7 +218,7 @@ class EditorPanel {
showError(message.content)
break
case 'edit': {
// 只有当 webview 处于编辑状态时才同步到 vsc 编辑器,避免重复刷新
// Only synchronize to the VSC editor when the webview is in edit mode to avoid repeated refreshing.
if (this._panel.active) {
await syncToEditor()
this._updateEditTitle()
Expand Down Expand Up @@ -356,6 +366,19 @@ class EditorPanel {
const JsFiles = ['main.js'].map(toMediaPath).map(toUri)
const CssFiles = ['main.css'].map(toMediaPath).map(toUri)

// Generate external CSS links
const externalCssLinks = this._generateExternalCssLinks(webview)
const customCss = EditorPanel.config.get<string>('customCss') || ''
const cssLoadOrder = EditorPanel.config.get<string>('cssLoadOrder') || 'external-first'

// Determines CSS loading order based on configuration
let cssContent = ''
if (cssLoadOrder === 'external-first') {
cssContent = externalCssLinks + (customCss ? `\n<style>${customCss}</style>` : '')
} else {
cssContent = (customCss ? `<style>${customCss}</style>\n` : '') + externalCssLinks
}

return (
`<!DOCTYPE html>
<html lang="en">
Expand All @@ -365,21 +388,76 @@ class EditorPanel {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="${baseHref}" />


${CssFiles.map((f) => `<link href="${f}" rel="stylesheet">`).join('\n')}
${cssContent}

<title>markdown editor</title>
<style>` +
EditorPanel.config.get<string>('customCss') +
`</style>
</head>
<body>
<div id="app"></div>


${JsFiles.map((f) => `<script src="${f}"></script>`).join('\n')}
</body>
</html>`
)
}

/**
* Generate external CSS links
*/
private _generateExternalCssLinks(webview: vscode.Webview): string {
const externalCssFiles = EditorPanel.config.get<string[]>('externalCssFiles') || []

return externalCssFiles.map(cssFile => {
// Handling different types of CSS paths
if (this._isHttpUrl(cssFile)) {
// HTTP/HTTPS URL
return `<link href="${cssFile}" rel="stylesheet" crossorigin="anonymous">`
} else if (NodePath.isAbsolute(cssFile)) {
// absolute path
try {
const cssUri = webview.asWebviewUri(vscode.Uri.file(cssFile))
return `<link href="${cssUri}" rel="stylesheet">`
} catch (error) {
console.warn(`Failed to load CSS file: ${cssFile}`, error)
return `<!-- Failed to load CSS: ${cssFile} -->`
}
} else {
// Relative path (relative to the workspace or markdown file)
try {
let resolvedPath: string

// Try parsing relative to the current markdown file
const markdownDir = NodePath.dirname(this._fsPath)
const relativeToMarkdown = NodePath.resolve(markdownDir, cssFile)

// Check if the file exists
if (fs.existsSync(relativeToMarkdown)) {
resolvedPath = relativeToMarkdown
} else {
// Try to resolve relative to the workspace root directory
const workspaceFolder = vscode.workspace.getWorkspaceFolder(this._uri)
if (workspaceFolder) {
resolvedPath = NodePath.resolve(workspaceFolder.uri.fsPath, cssFile)
} else {
resolvedPath = relativeToMarkdown // Downgrade relative to the markdown file
}
}

const cssUri = webview.asWebviewUri(vscode.Uri.file(resolvedPath))
return `<link href="${cssUri}" rel="stylesheet">`
} catch (error) {
console.warn(`Failed to resolve CSS file: ${cssFile}`, error)
return `<!-- Failed to resolve CSS: ${cssFile} -->`
}
}
}).join('\n')
}

/**
* Check if it is an HTTP/HTTPS URL
*/
private _isHttpUrl(url: string): boolean {
return /^https?:\/\//i.test(url)
}
}