Skip to content

Commit de205fd

Browse files
committed
np.Preview v0.5 preview any note; improve CSS for tables
1 parent 21231b5 commit de205fd

5 files changed

Lines changed: 137 additions & 22 deletions

File tree

‎helpers/HTMLView.js‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,36 @@ export async function getNoteContentAsHTML(content: string, note: TNote): Promis
132132
// Change frontmatter for this note (if present)
133133
// In particular remove trigger line
134134
if (hasFrontmatter) {
135-
let titleAsMD = ''
135+
let fmTitleText = ''
136136
// look for 2nd '---' and double it, because of showdown bug
137137
for (let i = 1; i < lines.length; i++) {
138-
if (lines[i].match(/^title:\s/)) {
139-
titleAsMD = lines[i].replace('title:', '#')
138+
const titleMatch = lines[i].match(/^title:\s*(.*)$/)
139+
if (titleMatch) {
140+
// Strip optional YAML quotes around the value
141+
fmTitleText = titleMatch[1].trim().replace(/^["'](.*)["']$/, '$1').trim()
140142
logDebug('getNoteContentAsHTML', `removing title line ${String(i)}`)
141143
lines.splice(i, 1)
142144
}
143-
if (lines[i].trim() === '---') {
145+
if (lines[i] != null && lines[i].trim() === '---') {
144146
lines.splice(i, 0, '') // add a blank before second HR to stop it acting as an ATX header line
145-
lines.splice(i + 2, 0, titleAsMD) // add the title (as MD)
147+
// After this splice: blank at i, closing --- at i+1; body starts at i+2
148+
// If the first H1 in the body already matches the frontmatter title, do not inject a second H1
149+
let bodyHasSameTitleH1 = false
150+
if (fmTitleText !== '') {
151+
for (let j = i + 2; j < lines.length; j++) {
152+
const h1Match = lines[j].match(/^#\s+(.+)$/)
153+
if (h1Match) {
154+
if (h1Match[1].trim() === fmTitleText) {
155+
bodyHasSameTitleH1 = true
156+
logDebug('getNoteContentAsHTML', `skipping frontmatter title injection; body already has matching first H1 '${fmTitleText}'`)
157+
}
158+
break
159+
}
160+
}
161+
}
162+
if (fmTitleText !== '' && !bodyHasSameTitleH1) {
163+
lines.splice(i + 2, 0, `# ${fmTitleText}`) // add the title as an H1 once
164+
}
146165
break
147166
}
148167
}

‎np.Preview/CHANGELOG.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# What's Changed in 🖥️ Previews plugin?
22
See [website README for more details](https://github.com/NotePlan/plugins/tree/main/np.Preview), and how to configure it.
33

4+
## [0.5.0] - 2026-08-04
5+
- tables: 1px solid grey borders; left- and top-aligned cells; bold thead and tfoot only (not tbody)
6+
- fix: do not show the title twice when frontmatter `title:` matches the first body H1
7+
- Preview can be called for a specified note (filename, title, relative date, or calendar identifier) via x-callback / other plugins; Command Bar still previews the open Editor note
8+
- Mermaid theme auto-selects light/dark from the NotePlan theme unless overridden as arg1
9+
410
## [0.4.6] - 2026-08-04
5-
- dev: Mermaid loads latest 11.x from CDN when online; falls back to shipped offline UMD snapshot (11.16.1)
11+
- Mermaid loads latest 11.x from CDN when online; falls back to shipped offline UMD snapshot (11.16.1)
612

713
## [0.4.5] - 2025-03-14
814
- upgraded to use Mermaid v11.x

‎np.Preview/README.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# 🖥️ Preview Plugin
2-
This plugin provides the **/preview note** and **/start live preview** commands that renders the current note to HTML including:
2+
This plugin provides the **/preview note** and **/start live preview** commands that render a note to HTML including:
33
- standard Markdown conversion (including referenced images)
44
- [Mermaid diagrams](https://mermaid.js.org) (e.g. flowcharts, gantt charts, sequence diagrams ...)
55
- [MathJax](https://www.mathjax.org/) fragments or lines (for mathematical equations and notation)
@@ -24,6 +24,27 @@ triggers: onEditorWillSave => np.Preview.updatePreview
2424
2525
It deliberately updates the Preview window without giving it focus, so that you can continue editing.
2626
27+
## Preview a specified note (x-callback / other plugins)
28+
29+
From the Command Bar, **/preview note** always previews the **currently open Editor note**.
30+
31+
Other plugins and automations can **preview any note** (without opening it in the Editor first) by passing a note identifier as `arg0`:
32+
33+
```
34+
noteplan://x-callback-url/runPlugin?pluginID=np.Preview&command=preview%20note&arg0=<encoded filename or identifier>
35+
```
36+
37+
Examples of `arg0`:
38+
39+
- Filename: `Projects/My%20Note.md`
40+
- Note title: `My%20Note`
41+
- Relative date: `today`, `yesterday`, `this%20week`
42+
- Calendar date string: `20260804`
43+
44+
Optional `arg1` sets the Mermaid theme (e.g. `default`, `dark`, `forest`). If omitted, the plugin picks `default` or `dark` from the current NotePlan theme.
45+
46+
If the specified note is open in the Editor, unsaved Editor content is used. Live preview (**start live preview**) still applies only to the note that has the trigger in the open Editor.
47+
2748
## Mermaid charts
2849
<img src="kanban-mermaid@2x.png" alt="Example of Kanban board in Mermaid charts" />
2950
Mermaid is a third-party library that makes a wide variety of diagrams (including Flowcharts, Gantt, Kanban, state transition etc.) and some simple charts, using markdown-ish definitions. These definitions are placed in one or more fenced code blocks, like this:

‎np.Preview/plugin.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"macOS.minVersion": "10.13.0",
44
"plugin.id": "np.Preview",
55
"plugin.name": "🖥️ Preview",
6-
"plugin.description": "Shows HTML preview of the current note, including Mermaid diagrams and MathJax equations. Requires NP v3.9.2.",
6+
"plugin.description": "Shows HTML preview of a note, including Mermaid diagrams and MathJax equations. Runs on current Editor, or a specified note via x-callback. Requires NP v3.9.2.",
77
"plugin.icon": "",
88
"plugin.author": "Jonathan Clark",
99
"plugin.url": "https://github.com/NotePlan/plugins/tree/main/np.Preview/",
10-
"plugin.version": "0.4.6",
11-
"plugin.lastUpdateInfo": "v0.4.6: Mermaid via CDN latest 11.x, with offline UMD fallback 11.16.1.\nv0.4.5: updated to use Mermaid v11.",
10+
"plugin.version": "0.5.0",
11+
"plugin.lastUpdateInfo": "v0.5.0: Add basic table styling (bold thead/tfoot only; left/top-aligned cells; grey borders).\nDon't show title twice when frontmatter title and first H1 match.\nAdd support for x-callback to preview a specified note, rather than just the current note.\nv0.4.6: Update offline support for Mermaid to v11.16.1 (latest).\n",
1212
"plugin.changelog": "https://github.com/NotePlan/plugins/blob/main/np.Preview/CHANGELOG.md",
1313
"plugin.dependencies": [],
1414
"plugin.requiredFiles": [
@@ -24,7 +24,7 @@
2424
"alias": [
2525
"pn"
2626
],
27-
"description": "Preview note in HTML window",
27+
"description": "Preview current (or specified) note in HTML window",
2828
"jsFunction": "previewNote"
2929
},
3030
{

‎np.Preview/src/previewMain.js‎

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
//--------------------------------------------------------------
44
// Main rendering function for Preview
5-
// by Jonathan Clark, last updated 2026-08-04 for v0.4.6
5+
// by Jonathan Clark, last updated 2026-08-04 for v0.5.0
66
//--------------------------------------------------------------
77

88

@@ -11,6 +11,7 @@ import pluginJson from '../plugin.json'
1111
import { logDebug, logError, logWarn } from '@helpers/dev'
1212
import { addTrigger } from '@helpers/NPFrontMatter'
1313
import { displayTitle } from '@helpers/general'
14+
import { getNoteFromFilename, getNoteFromIdentifier } from '@helpers/NPnote'
1415

1516
import {
1617
getNoteContentAsHTML,
@@ -19,6 +20,56 @@ import {
1920
} from '@helpers/HTMLView'
2021
import { showMessageYesNo } from '@helpers/userInput'
2122

23+
/**
24+
* Resolved note + markdown content for preview.
25+
* Prefer Editor content when the target file is open (includes unsaved edits).
26+
*/
27+
type PreviewNoteAndContent = {
28+
note: TNote,
29+
content: string,
30+
}
31+
32+
/**
33+
* Resolve which note and content to preview.
34+
* - Empty/null noteSpec => Editor.note + Editor.content
35+
* - TNote => that note
36+
* - string => filename (teamspace-aware) then identifier (title / relative date / calendar)
37+
* If Editor is open on the same filename, use Editor.content for unsaved text.
38+
* @param {string | TNote | null | void} noteSpec
39+
* @returns {PreviewNoteAndContent}
40+
* @throws {Error} if no note or content can be resolved
41+
*/
42+
function resolveNoteAndContent(noteSpec?: string | TNote | null): PreviewNoteAndContent {
43+
let note: ?TNote = null
44+
45+
if (noteSpec == null || noteSpec === '') {
46+
note = Editor.note
47+
if (!note) {
48+
throw new Error('No note or content found in Editor. Stopping.')
49+
}
50+
} else if (typeof noteSpec === 'object' && noteSpec.filename) {
51+
note = noteSpec
52+
} else if (typeof noteSpec === 'string') {
53+
note = getNoteFromFilename(noteSpec) ?? getNoteFromIdentifier(noteSpec)
54+
if (!note) {
55+
throw new Error(`Cannot find note for '${noteSpec}'. Stopping.`)
56+
}
57+
} else {
58+
throw new Error('Invalid note specifier for preview. Stopping.')
59+
}
60+
61+
// Prefer open Editor buffer when it is the same file (unsaved edits)
62+
if (Editor.note?.filename === note.filename && Editor.content != null) {
63+
return { note: Editor.note, content: Editor.content }
64+
}
65+
66+
const content = note.content
67+
if (content == null) {
68+
throw new Error(`No content found for note '${displayTitle(note)}'. Stopping.`)
69+
}
70+
return { note, content }
71+
}
72+
2273
//--------------------------------------------------------------
2374

2475
// Constants
@@ -88,6 +139,23 @@ Button a { text-decoration: none; font-size: 0.9rem; }
88139
padding: 0rem 0.4rem;
89140
background-color: var(--bg-alt-color);
90141
}
142+
table {
143+
border-collapse: collapse;
144+
border: 1px solid grey;
145+
}
146+
th, td {
147+
border: 1px solid grey;
148+
text-align: left;
149+
vertical-align: top;
150+
padding: 0.2rem 0.4rem;
151+
font-weight: normal;
152+
}
153+
table thead th,
154+
table thead td,
155+
table tfoot th,
156+
table tfoot td {
157+
font-weight: bold;
158+
}
91159
@media print {
92160
.nonPrinting {
93161
display: none;
@@ -96,20 +164,21 @@ Button a { text-decoration: none; font-size: 0.9rem; }
96164
`
97165

98166
/**
99-
* Preview current Editor note to HTML window, covering:
167+
* Preview a note to HTML window, covering:
100168
* - Mermaid diagrams
101169
* - MathJax fragments or lines
102170
* - other standard Markdown conversion (supplied by 'showdown' library)
103171
* - some non-standard Markdown conversion (e.g. tables) (also supplied by 'showdown' library)
172+
* Command Bar with no args previews the open Editor note.
173+
* From x-callback / other plugins, pass a filename, title, relative date, or TNote as arg0.
104174
* @author @jgclark
105-
* @param {string?} mermaidTheme name (optional)
175+
* @param {string | TNote | null | void} noteSpec - optional filename/identifier or TNote; omit for Editor
176+
* @param {string?} mermaidTheme - optional Mermaid theme override (otherwise default/dark from NP theme)
106177
*/
107-
export async function previewNote(mermaidTheme?: string = "green"): Promise<void> {
178+
export async function previewNote(noteSpec?: string | TNote | null, mermaidTheme?: string): Promise<void> {
108179
try {
109-
const { note, content } = Editor
110-
if (!note || !content) {
111-
throw new Error('No note or content found in Editor. Stopping.')
112-
}
180+
const { note, content } = resolveNoteAndContent(noteSpec)
181+
logDebug(pluginJson, `previewNote: '${displayTitle(note)}' (${note.filename})`)
113182
let lines = content.split('\n')
114183
lines = lines.filter(l => l !== 'triggers: onEditorWillSave => np.Preview.updatePreview')
115184
// Update mermaid fenced code blocks to suitable <divs>
@@ -144,7 +213,7 @@ export async function previewNote(mermaidTheme?: string = "green"): Promise<void
144213
<meta name="date" content="${new Date().toISOString()}">`
145214

146215
const windowOpts: HtmlWindowOptions = {
147-
windowTitle: `${displayTitle(Editor)} Preview`,
216+
windowTitle: `${displayTitle(note)} Preview`,
148217
headerTags: headerTags,
149218
generalCSSIn: '', // get general CSS set automatically
150219
bodyOptions: '',
@@ -172,8 +241,8 @@ export async function previewNote(mermaidTheme?: string = "green"): Promise<void
172241
*/
173242
export async function openPreviewNoteInBrowser(): Promise<void> {
174243
try {
175-
// Call preview note function with 'default' theme (best for printing)
176-
await previewNote('default')
244+
// Editor note with 'default' Mermaid theme (best for printing)
245+
await previewNote(undefined, 'default')
177246
logDebug(pluginJson, `openPreviewNoteInBrowser: preview created; now will try to open in browser`)
178247
// FIXME: the following doesn't work -- something to do with imports and builtins
179248
// await open(savedFilename)

0 commit comments

Comments
 (0)