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
97 changes: 97 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Markdown Live Preview - AI Coding Agent Instructions

## Project Overview

Single-page web application for live Markdown editing and preview using Monaco Editor, marked.js parser, and DOMPurify sanitization. Deployed to Firebase Hosting via Vite bundler.

## Architecture

### Core Components

- **[src/main.js](../src/main.js)**: Single-file application with modular functions
- Monaco Editor initialization (lines 88-111)
- Markdown conversion pipeline: `marked.parse()` → `DOMPurify.sanitize()` (lines 135-141)
- State persistence via `storehouse-js` localStorage wrapper (lines 241-257)
- Split-pane divider with proportional resizing (lines 259-339)

### Key Dependencies

- `monaco-editor`: CDN ESM import (`https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/+esm`)
- `storehouse-js`: Custom fork from GitHub (`github:tanabe/Storehouse-js`)
- `marked` + `DOMPurify`: Markdown parsing and XSS protection pipeline

## Development Workflow

### Commands (prefer Makefile over npm)

```bash
make setup # npm install
make dev # Vite dev server (HMR enabled)
make build # Production build to dist/
make deploy # Firebase deploy (requires firebase-tools)
```

### Build System

- **Vite**: No configuration file present; uses defaults
- **Output**: `dist/` directory (configured in [firebase.json](../firebase.json))
- **Entry point**: [index.html](../index.html) with module script `<script type="module" src="/src/main.js">`

## Code Patterns

### State Management

```javascript
// LocalStorage wrapper with namespace + expiration
Storehouse.setItem(localStorageNamespace, key, value, expiredAt);
Storehouse.getItem(localStorageNamespace, key);
```

- Namespace: `com.markdownlivepreview`
- Keys: `last_state` (editor content), `scroll_bar_settings` (sync toggle)

### Editor-Preview Sync

- **Scroll sync**: Calculate ratio from Monaco scroll event, apply to preview element
- **Content sync**: Triggered on `onDidChangeModelContent` event
- **Manual reset**: Confirms if content differs from `defaultInput` template

### Monaco Editor Configuration

```javascript
// Minimal config: no minimap, no hover, no suggestions
monaco.editor.create(element, {
language: "markdown",
minimap: { enabled: false },
wordWrap: "on",
hover: { enabled: false },
quickSuggestions: false,
});
```

## Project-Specific Conventions

### HTML Structure

- Split-pane layout: `#edit` (editor) | `#split-divider` | `#preview` (rendered output)
- GitHub Markdown CSS applied to output: `class="markdown-body"`

### Styling

- Custom split-pane styles in [public/css/style.css](../public/css/style.css)
- GitHub-flavored Markdown styles from `github-markdown-css` package

### Default Template

- Comprehensive Markdown syntax guide embedded in [src/main.js](../src/main.js) (lines 17-83)
- Loaded on first visit; persisted content takes precedence

## Deployment Notes

- Firebase Hosting serves `dist/` directory
- No server-side logic; pure client-side rendering
- Cache busting via query params: `?v=1.10.1` in [index.html](../index.html)

## Testing

No test framework configured. Manual testing via `make dev`.
56 changes: 28 additions & 28 deletions dist/assets/index-DXVBtf7Z.js → dist/assets/index-_bLmAPh3.js

Large diffs are not rendered by default.

141 changes: 122 additions & 19 deletions dist/css/style.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
html, body {
html,
body {
color: #333;
overflow-x: hidden;
margin: 0;
Expand All @@ -13,10 +14,16 @@ html, body {
body {
display: flex;
flex-direction: column;
height:100vh;
height: 100vh;
}

h1, h2, h3, h4, h5, h6, p {
h1,
h2,
h3,
h4,
h5,
h6,
p {
font-size: 1em;
margin: 0;
padding: 0;
Expand All @@ -29,8 +36,7 @@ p {

a:link,
a:visited,
a:hover
a:active {
a:hover a:active {
color: #333;
text-decoration: none;
}
Expand All @@ -50,7 +56,10 @@ header {
color: #fff;
}

header a:link, header a:hover, header a:visited, header a:active {
header a:link,
header a:hover,
header a:visited,
header a:active {
color: #fff;
}

Expand All @@ -59,33 +68,41 @@ header a:hover {
}

#menu-items {
display: flex;
display: flex;
}

#reset-button {
margin-left: 16px;
margin-left: 16px;
}

#copy-button {
margin-left: 16px;
margin-left: 16px;
}

#sync-button {
margin-left: 16px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin-left: 16px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

#theme-button {
margin-left: 16px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

header input[type="checkbox"] {
vertical-align: middle;
margin-right: 4px;
margin-top: 0px;
vertical-align: middle;
margin-right: 4px;
margin-top: 0px;
}

#github {
padding-right: 32px;
padding-right: 32px;
}

#github img {
Expand Down Expand Up @@ -169,4 +186,90 @@ footer {

.split-divider.active {
background: #666;
}
}

/* Dark mode styles */
body.dark-mode {
background-color: #1e1e1e;
color: #d4d4d4;
}

body.dark-mode a:link,
body.dark-mode a:visited,
body.dark-mode a:hover,
body.dark-mode a:active {
color: #d4d4d4;
}

body.dark-mode #container {
background-color: #1e1e1e;
border-bottom: 1px solid #3e3e42;
}

body.dark-mode footer {
background-color: #1e1e1e;
}

body.dark-mode .split-divider {
background: #3e3e42;
}

body.dark-mode .split-divider.hover {
background: #5a5a5f;
}

body.dark-mode .split-divider.active {
background: #707070;
}

/* Dark mode markdown output styles */
#output.dark-mode {
background-color: #1e1e1e;
color: #d4d4d4;
}

#output.dark-mode h1,
#output.dark-mode h2,
#output.dark-mode h3,
#output.dark-mode h4,
#output.dark-mode h5,
#output.dark-mode h6 {
color: #d4d4d4;
border-bottom-color: #3e3e42;
}

#output.dark-mode a {
color: #58a6ff;
}

#output.dark-mode code {
background-color: rgba(110, 118, 129, 0.4);
color: #d4d4d4;
}

#output.dark-mode pre {
background-color: #161b22;
}

#output.dark-mode blockquote {
color: #8b949e;
border-left-color: #3e3e42;
}

#output.dark-mode table tr {
background-color: #1e1e1e;
border-top-color: #3e3e42;
}

#output.dark-mode table tr:nth-child(2n) {
background-color: #161b22;
}

#output.dark-mode table th,
#output.dark-mode table td {
border-color: #3e3e42;
}

#output.dark-mode img {
background-color: transparent;
}
Loading