### Setup Speaker Timer
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/speaker-view.html
Initializes the timer and clock UI elements and starts the update interval.
```javascript
/**
* Create the timer and clock and start updating them
* at an interval.
*/
function setupTimer() {
var start = new Date(),
timeEl = document.querySelector( '.speaker-controls-time' ),
clockEl = timeEl.querySelector( '.clock-value' ),
hoursEl = timeEl.querySelector( '.hours-value' ),
minutesEl = timeEl.querySelector( '.minutes-value' ),
secondsEl = timeEl.querySelector( '.seconds-value' ),
pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
pacingEl = timeEl.querySelector( '.pacing' ),
pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
var timings = null;
getTimings( function ( _timings ) {
timings = _timings;
if (_timings !== null) {
pacingTitleEl.style.removeProperty('display');
pacingEl.style.removeProperty('display');
}
// Update once directly
_updateTimer();
// Then update every second
setInterval( _updateTimer, 1000 );
} );
function _resetTimer() {
if (timings == null) {
start = new Date();
_updateTimer();
} else {
// Reset timer to beginning of current slide
getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
var slideEndTiming = slideEndTimingSeconds * 1000;
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
var currentSlideTiming = timings[currentSlide] * 1000;
var previousSlidesTiming = slideEndTiming - currentSlideTiming;
var now = new Date();
start = new Date(now.getTime() - previo
```
--------------------------------
### Install Project Dependencies
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Installs project dependencies using pnpm. Run from the shell after cloning the project.
```shell
pnpm install
```
--------------------------------
### Setup Notes UI
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Locates the DOM elements required for displaying speaker notes.
```javascript
/** * Setup the notes UI. */ function setupNotes() { notes = document.querySelector( '.speaker-controls-notes' ); notesValue = document.querySelector( '.speaker-controls-notes .value' ); }
```
--------------------------------
### Install Crossnote using npm, pnpm, or yarn
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Choose the appropriate command based on your package manager to install the crossnote library.
```sh
# If you are using npm
$ npm install --save crossnote
```
```sh
# If you are using pnpm
$ pnpm add crossnote
```
```sh
# If you are using yarn
$ yarn add crossnote
```
--------------------------------
### Initialize Notebook and Export Notes with Crossnote
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Demonstrates initializing a notebook, getting a markdown engine, and performing various export operations like HTML, PDF, and ebook. Ensure correct paths for Chrome if using puppeteer export.
```javascript
// CJS
const { Notebook } = require('crossnote');
// ESM
// import { Notebook } from "crossnote"
async function main() {
const notebook = await Notebook.init({
notebookPath: '/absolute/path/to/your/notebook',
config: {
previewTheme: 'github-light.css',
mathRenderingOption: 'KaTeX',
codeBlockTheme: 'github.css',
printBackground: true,
enableScriptExecution: true, // <= For running code chunks.
chromePath: '/path/to/chrome', // <= For puppeteer export and open in browser locally.
// Recommended to use the absolute path of Chrome executable.
},
});
// Get the markdown engine for a specific note file in your notebook.
const engine = notebook.getNoteMarkdownEngine('README.md');
// open in browser
await engine.openInBrowser({ runAllCodeChunks: true });
// html export
await engine.htmlExport({ offline: false, runAllCodeChunks: true });
// chrome (puppeteer) export
await engine.chromeExport({ fileType: 'pdf', runAllCodeChunks: true }); // fileType = 'pdf'|'png'|'jpeg'
// prince export
await engine.princeExport({ runAllCodeChunks: true });
// ebook export
await engine.eBookExport({ fileType: 'epub' }); // fileType = 'epub'|'pdf'|'mobi'|'html'
// pandoc export
await engine.pandocExport({ runAllCodeChunks: true });
// markdown(gfm) export
await engine.markdownExport({ runAllCodeChunks: true });
return process.exit();
}
main();
```
--------------------------------
### Setup Speaker View Layout
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Initializes the speaker layout dropdown and sets the initial layout. It renders available layouts and adds an event listener for changes.
```javascript
function setupLayout() {
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
layoutLabel = document.querySelector( '.speaker-layout-label' );
// Render the list of available layouts
for( var id in SPEAKER_LAYOUTS ) {
var option = document.createElement( 'option' );
option.setAttribute( 'value', id );
option.textContent = SPEAKER_LAYOUTS[ id ];
layoutDropdown.appendChild( option );
}
// Monitor the dropdown for changes
layoutDropdown.addEventListener( 'change', function( event ) {
setLayout( layoutDropdown.value );
}, false );
// Restore any currently persisted layout
setLayout( getLayout() );
}
```
--------------------------------
### Initialize Speaker Notes and Slide Previews
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/notes.html
Handles the connection between the main window and the speaker view, including iframe setup and message event listeners.
```javascript
(function() { var notes, notesValue, currentState, currentSlide, upcomingSlide, layoutLabel, layoutDropdown, connected = false; var SPEAKER_LAYOUTS = { 'default': 'Default', 'wide': 'Wide', 'tall': 'Tall', 'notes-only': 'Notes only' }; setupLayout(); var connectionStatus = document.querySelector( '#connection-status' ); var connectionTimeout = setTimeout( function() { connectionStatus.innerHTML = 'Error connecting to main window.
Please try closing and reopening the speaker view.'; }, 5000 ); window.addEventListener( 'message', function( event ) { clearTimeout( connectionTimeout ); connectionStatus.style.display = 'none'; var data = JSON.parse( event.data ); // The overview mode is only useful to the reveal.js instance // where navigation occurs so we don't sync it if( data.state ) delete data.state.overview; // Messages sent by the notes plugin inside of the main window if( data && data.namespace === 'reveal-notes' ) { if( data.type === 'connect' ) { handleConnectMessage( data ); } else if( data.type === 'state' ) { handleStateMessage( data ); } } // Messages sent by the reveal.js inside of the current slide preview else if( data && data.namespace === 'reveal' ) { if( /ready/.test( data.eventName ) ) { // Send a message back to notify that the handshake is complete window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' ); } else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) { window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' ); } } } ); /** * Called when the main window is trying to establish a * connection. */ function handleConnectMessage( data ) { if( connected === false ) { connected = true; setupIframes( data ); setupKeyboard(); setupNotes(); setupTimer(); } } /** * Called when the main window sends an updated state. */ function handleStateMessage( data ) { // Store the most recently set state to avoid circular loops // applying the same state currentState = JSON.stringify( data.state ); // No need for updating the notes in case of fragment changes if ( data.notes ) { notes.classList.remove( 'hidden' ); notesValue.style.whiteSpace = data.whitespace; if( data.markdown ) { notesValue.innerHTML = marked( data.notes ); } else { notesValue.innerHTML = data.notes; } } else { notes.classList.add( 'hidden' ); } // Update the note slides currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' ); upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' ); upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' ); } // Limit to max one state update per X ms handleStateMessage = debounce( handleStateMessage, 200 ); /** * Forward keyboard events to the current slide window. * This enables keyboard events to work even if focus * isn't set on the current slide iframe. * * Block F5 default handling, it reloads and disconnects * the speaker notes window. */ function setupKeyboard() { document.addEventListener( 'keydown', function( event ) { if( event.keyCode === 116 || ( event.metaKey && event.keyCode === 82 ) ) { event.preventDefault(); return false; } currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' ); } ); } /** * Creates the preview iframes. */ function setupIframes( data ) { var params = [ 'receiver', 'progress=false', 'history=false', 'transition=none', 'autoSlide=0', 'backgroundTransition=none' ].join( '&' ); var urlSeparator = /\?/.test(data.url) ? '&' : '?'; var hash = '#/' + data.state.indexh + '/' + data.state.indexv; var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash; var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash; currentSlide = document.createElement( 'iframe' ); currentSlide.setAttribute( 'width', 1280 ); currentSlide.setAttribute( 'height', 1024 ); currentSlide.setAttribute( 'src', currentURL ); document.querySelector( '#current-slide' ).appendChild( currentSlide ); upcomingSlide = document.createElement( 'iframe' ); upcomingSlide.setAttribute( 'width', 640 ); upcomingSlide.setAttribute( 'height', 512 ); upcomingSlide.setAttribute( 'src', upcomingURL ); document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide ); } /** * Setup the notes UI. */ function setupNotes() { notes = document.querySelector( '.speaker-controls-notes' ); notesValue = document.querySelector( '.speaker-controls-notes .value' ); } function getTimings() { var slides = Reveal.getSlides(); var defaultTiming = Reveal.getConfig().defaultTiming; if (defaultTiming == null) { return null; } var timings = []; for ( var i in slides ) { var slide = slides[i]; var timing = defaultTiming; if( slide.hasAttribute( 'data-timing' )) { var t = slide.getAtt
```
--------------------------------
### Initialize Presentation Timer
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Starts a timer and clock display that updates every second.
```javascript
/** * Create the timer and clock and start updating them * at an interval. */ function setupTimer() { var start = new Date(), timeEl = document.querySelector( '.speaker-controls-time' ), clockEl = timeEl.querySelector( '.clock-value' ), hoursEl = timeEl.querySelector( '.hours-value' ), minutesEl = timeEl.querySelector( '.minutes-value' ), secondsEl = timeEl.querySelector( '.seconds-value' ); function _updateTimer() { var diff, hours, minutes, seconds, now = new Date(); diff = now.getTime() - start.getTime(); hours = Math.floor( diff / ( 1000 * 60 * 60 ) ); minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 ); seconds = Math.floor( ( diff / 1000 ) % 60 ); clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } ); hoursEl.innerHTML = zeroPadInteger( hours ); hoursEl.className = hours > 0 ? '' : 'mute'; minutesEl.innerHTML = ':' + zeroPadInteger( minutes ); minutesEl.className = minutes > 0 ? '' : 'mute'; secondsEl.innerHTML = ':' + zeroPadInteger( seconds ); } // Update once directly _updateTimer(); // Then update every second setInterval( _updateTimer, 1000 ); timeEl.addEventListener( 'click', function() { start = new Date(); _updateTimer(); return false; } ); }
```
--------------------------------
### Python Code Block
Source: https://github.com/shd101wyy/crossnote/blob/develop/test/markdown/test-files/test8.expect.md
Python code examples demonstrating function definition.
```python
def hello():
println("Hello, world")
```
```python
def hello():
println("Hello, world")
```
--------------------------------
### Build Project in Watch Mode
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Starts the build process in watch mode using pnpm. This command is useful for development.
```shell
pnpm build:watch
```
--------------------------------
### Manage Notes with Notebook Class
Source: https://context7.com/shd101wyy/crossnote/llms.txt
Initializes a Notebook instance and demonstrates methods for refreshing all notes, getting a specific note, retrieving backlinks, and deleting notes. Ensure the notebook path is correctly set.
```javascript
const { Notebook } = require('crossnote');
async function manageNotes() {
const notebook = await Notebook.init({
notebookPath: '/path/to/notebook',
config: {
enableWikiLinkSyntax: true,
markdownFileExtensions: ['.md', '.markdown', '.mdx'],
},
});
// Refresh all notes in the notebook
const notes = await notebook.refreshNotes({
dir: './',
includeSubdirectories: true,
refreshRelations: true,
});
console.log('Loaded notes:', Object.keys(notes));
// Get a specific note
const note = await notebook.getNote('docs/intro.md', true);
if (note) {
console.log('Note title:', note.title);
console.log('Note path:', note.filePath);
console.log('Created at:', note.config.createdAt);
console.log('Modified at:', note.config.modifiedAt);
console.log('Mentions:', note.mentions);
}
// Get backlinks for a note
const backlinks = await notebook.getNoteBacklinks('docs/intro.md');
console.log('Backlinks:');
backlinks.forEach(backlink => {
console.log(` - ${backlink.note.title} (${backlink.note.filePath})`);
console.log(' References:', backlink.references.length);
});
// Get notes that link to this note
const backlinkedNotes = await notebook.getBacklinkedNotes('docs/intro.md');
console.log('Notes linking here:', Object.keys(backlinkedNotes));
// Delete a note
await notebook.deleteNote('old-note.md');
return notes;
}
manageNotes();
```
--------------------------------
### Setup Speaker View Layout
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/notes.html
Initializes the speaker view layout dropdown and label. Renders available layouts and sets up event listeners for layout changes.
```javascript
function setupLayout() {
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
layoutLabel = document.querySelector( '.speaker-layout-label' );
// Render the list of available layouts
for( var id in SPEAKER_LAYOUTS ) {
var option = document.createElement( 'option' );
option.setAttribute( 'value', id );
option.textContent = SPEAKER_LAYOUTS[ id ];
layoutDropdown.appendChild( option );
}
// Monitor the dropdown for changes
layoutDropdown.addEventListener( 'change', function( event ) {
setLayout( layoutDropdown.value );
}, false );
// Restore any currently persisted layout
setLayout( getLayout() );
}
```
--------------------------------
### Interactive Vega-Lite with Data Files
Source: https://github.com/shd101wyy/crossnote/blob/develop/test/integration/fixtures/interactive-diagrams.md
Examples of using local and remote CSV data files for interactive visualizations.
```vega-lite
{
"$schema": "https://vega.github.io/schema/vega-lite/v3.json",
"data": {"url": "data/sp500.csv"},
"vconcat": [{
"width": 480,
"mark": "area",
"encoding": {
"x": {
"field": "date",
"type": "temporal",
"scale": {"domain": {"selection": "brush"}},
"axis": {"title": ""}
},
"y": {"field": "price","type": "quantitative"}
}
}, {
"width": 480,
"height": 60,
"mark": "area",
"selection": {
"brush": {"type": "interval", "encodings": ["x"]}
},
"encoding": {
"x": {
"field": "date",
"type": "temporal",
"axis": {"format": "%Y"}
},
"y": {
"field": "price",
"type": "quantitative",
"axis": {"tickCount": 3, "grid": false}
}
}
}]
}
```
```vega-lite
{
"$schema": "https://vega.github.io/schema/vega-lite/v3.json",
"data": {"url": "https://vega.github.io/vega-lite/data/sp500.csv"},
"vconcat": [{
"width": 480,
"mark": "area",
"encoding": {
"x": {
"field": "date",
"type": "temporal",
"scale": {"domain": {"selection": "brush"}},
"axis": {"title": ""}
},
"y": {"field": "price","type": "quantitative"}
}
}, {
"width": 480,
"height": 60,
"mark": "area",
"selection": {
"brush": {"type": "interval", "encodings": ["x"]}
},
"encoding": {
"x": {
"field": "date",
"type": "temporal",
"axis": {"format": "%Y"}
},
"y": {
"field": "price",
"type": "quantitative",
"axis": {"tickCount": 3, "grid": false}
}
}
}]
}
```
--------------------------------
### Setup Speaker View Layout
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/speaker-view.html
Initializes the speaker view layout by populating a dropdown with available layouts and setting up an event listener for changes. It also restores the previously selected layout.
```javascript
function setupLayout() {
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
layoutLabel = document.querySelector( '.speaker-layout-label' );
// Render the list of available layouts for( var id in SPEAKER_LAYOUTS ) {
var option = document.createElement( 'option' );
option.setAttribute( 'value', id );
option.textContent = SPEAKER_LAYOUTS[ id ];
layoutDropdown.appendChild( option );
}
// Monitor the dropdown for changes
layoutDropdown.addEventListener( 'change', function( event ) {
setLayout( layoutDropdown.value );
}, false );
// Restore any currently persisted layout
setLayout( getLayout() );
}
```
--------------------------------
### Allow Nix Direnv
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Allows direnv to manage environment variables for the project if Nix is installed. Run once in the project directory.
```shell
direnv allow
```
--------------------------------
### Setup Keyboard Event Forwarding
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Forwards keyboard events to the slide iframe to ensure controls work regardless of focus.
```javascript
/** * Forward keyboard events to the current slide window. * This enables keyboard events to work even if focus * isn't set on the current slide iframe. */ function setupKeyboard() { document.addEventListener( 'keydown', function( event ) { currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' ); } ); }
```
--------------------------------
### Export to PDF using PrinceXML
Source: https://context7.com/shd101wyy/crossnote/llms.txt
Use this method for high-quality PDF generation with CSS print styles. Ensure PrinceXML is installed and configured.
```javascript
const { Notebook } = require('crossnote');
async function exportWithPrince() {
const notebook = await Notebook.init({
notebookPath: '/path/to/notebook',
config: {
previewTheme: 'github-light.css',
printBackground: true,
},
});
const engine = notebook.getNoteMarkdownEngine('document.md');
const pdfPath = await engine.princeExport({
runAllCodeChunks: true,
openFileAfterGeneration: false,
});
console.log('Prince PDF exported to:', pdfPath);
return pdfPath;
}
exportWithPrince();
```
--------------------------------
### PlantUML Server Configuration
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Specifies the PlantUML server URL for rendering diagrams. Leave empty to use a local plantuml.jar. Example: 'http://localhost:8080/svg/'.
```javascript
plantumlServer: "http://localhost:8080/svg/",
```
--------------------------------
### jsDelivr CDN Host
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Specifies the jsDelivr CDN host for asset delivery. Example values include 'cdn.jsdelivr.net', 'fastly.jsdelivr.net'.
```javascript
jsdelivrCdnHost: "cdn.jsdelivr.net",
```
--------------------------------
### Setup and Update Speaker Timer
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/notes.html
Initializes the speaker timer, clock, and pacing indicators. Updates the display every second and includes functionality to reset the timer.
```javascript
function setupTimer() {
var start = new Date(),
timeEl = document.querySelector( '.speaker-controls-time' ),
clockEl = timeEl.querySelector( '.clock-value' ),
hoursEl = timeEl.querySelector( '.hours-value' ),
minutesEl = timeEl.querySelector( '.minutes-value' ),
secondsEl = timeEl.querySelector( '.seconds-value' ),
pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
pacingEl = timeEl.querySelector( '.pacing' ),
pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
var timings = getTimings();
if (timings !== null) {
pacingTitleEl.style.removeProperty('display');
pacingEl.style.removeProperty('display');
}
function _displayTime( hrEl, minEl, secEl, time) {
var sign = Math.sign(time) == -1 ? "-" : "";
time = Math.abs(Math.round(time / 1000));
var seconds = time % 60;
var minutes = Math.floor( time / 60 ) % 60 ;
var hours = Math.floor( time / ( 60 * 60 )) ;
hrEl.innerHTML = sign + zeroPadInteger( hours );
if (hours == 0) {
hrEl.classList.add( 'mute' );
} else {
hrEl.classList.remove( 'mute' );
}
minEl.innerHTML = ':' + zeroPadInteger( minutes );
if (hours == 0 && minutes == 0) {
minEl.classList.add( 'mute' );
} else {
minEl.classList.remove( 'mute' );
}
secEl.innerHTML = ':' + zeroPadInteger( seconds );
}
function _updateTimer() {
var diff, hours, minutes, seconds, now = new Date();
diff = now.getTime() - start.getTime();
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
_displayTime( hoursEl, minutesEl, secondsEl, diff );
if (timings !== null) {
_updatePacing(diff);
}
}
function _updatePacing(diff) {
var slideEndTiming = getTimeAllocated(timings) * 1000;
var currentSlide = Reveal.getSlidePastCount();
var currentSlideTiming = timings[currentSlide] * 1000;
var timeLeftCurrentSlide = slideEndTiming - diff;
if (timeLeftCurrentSlide < 0) {
pacingEl.className = 'pacing behind';
} else if (timeLeftCurrentSlide < currentSlideTiming) {
pacingEl.className = 'pacing on-track';
} else {
pacingEl.className = 'pacing ahead';
}
_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
}
// Update once directly
_updateTimer();
// Then update every second
setInterval( _updateTimer, 1000 );
function _resetTimer() {
if (timings == null) {
start = new Date();
} else {
// Reset timer to beginning of current slide
var slideEndTiming = getTimeAllocated(timings) * 1000;
var currentSlide = Reveal.getSlidePastCount();
var currentSlideTiming = timings[currentSlide] * 1000;
var previousSlidesTiming = slideEndTiming - currentSlideTiming;
var now = new Date();
start = new Date(now.getTime() - previousSlidesTiming);
}
_updateTimer();
}
timeEl.addEventListener( 'click', function() {
_resetTimer();
return false;
} );
}
```
--------------------------------
### Configure Default LaTeX Engine
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Set the default LaTeX engine for Pandoc export and LaTeX code chunks. Example: 'pdflatex'.
```javascript
latexEngine: 'pdflatex',
```
--------------------------------
### Vega-Lite Geospatial Chart with External Data
Source: https://github.com/shd101wyy/crossnote/blob/develop/test/integration/fixtures/diagrams.md
A Vega-Lite example for creating a geospatial chart using TopoJSON and unemployment data. It requires external data files for rendering.
```json
{
"$schema": "https://vega.github.io/schema/vega-lite/v2.1.json",
"width": 500,
"height": 300,
"data": {
"url": "data/us-10m.json",
"format": {
"type": "topojson",
"feature": "counties"
}
},
"transform": [{
"lookup": "id",
"from": {
"data": {
"url": "data/unemployment.tsv"
},
"key": "id",
"fields": ["rate"]
}
}],
"projection": {
"type": "albersUsa"
},
"mark": "geoshape",
"encoding": {
"color": {
"field": "rate",
"type": "quantitative"
}
}
}
```
--------------------------------
### Get Speaker View Layout
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/speaker-view.html
Retrieves the currently set speaker view layout from local storage. If no layout is stored, it returns the default layout.
```javascript
function getLayout() {
if( supportsLocalStorage() ) {
var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
if( layout ) {
return layout;
}
}
// Default to the first record in the layouts hash
for( var id in SPEAKER_LAYOUTS ) {
return id;
}
}
```
--------------------------------
### Get Speaker View Layout
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Retrieves the previously set speaker view layout from local storage. If no layout is found or local storage is unavailable, it returns the default layout.
```javascript
function getLayout() {
if( window.localStorage ) {
var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
if( layout ) {
return layout;
}
}
// Default to the first record in the layouts hash
for( var id in SPEAKER_LAYOUTS ) {
return id;
}
}
```
--------------------------------
### Get Markdown Engine for a Note
Source: https://context7.com/shd101wyy/crossnote/llms.txt
Retrieves a MarkdownEngine instance for a specific note file within the initialized notebook. This engine is used for parsing markdown content and exporting it. Caches can be cleared using `clearAllNoteMarkdownEngineCaches`.
```javascript
const { Notebook } = require('crossnote');
async function processNote() {
const notebook = await Notebook.init({
notebookPath: '/path/to/notebook',
config: {
enableScriptExecution: true,
mathRenderingOption: 'KaTeX',
},
});
// Get markdown engine for a specific note
const engine = notebook.getNoteMarkdownEngine('README.md');
// You can also use relative paths
const docsEngine = notebook.getNoteMarkdownEngine('docs/guide.md');
// Get all markdown engines
const allEngines = notebook.getNoteMarkdownEngines();
console.log('Engines for notes:', Object.keys(allEngines));
// Clear all engine caches
notebook.clearAllNoteMarkdownEngineCaches();
return engine;
}
processNote();
```
--------------------------------
### Initialize Speaker Notes and Presentation Sync
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes-server/notes.html
Sets up socket connections, iframe communication, and event listeners for state changes in the presentation.
```javascript
(function() { var notes, notesValue, currentState, currentSlide, upcomingSlide, layoutLabel, layoutDropdown, connected = false; var socket = io.connect( window.location.origin ), socketId = '{{socketId}}'; var SPEAKER_LAYOUTS = { 'default': 'Default', 'wide': 'Wide', 'tall': 'Tall', 'notes-only': 'Notes only' }; socket.on( 'statechanged', function( data ) { // ignore data from sockets that aren't ours if( data.socketId !== socketId ) { return; } if( connected === false ) { connected = true; setupKeyboard(); setupNotes(); setupTimer(); } handleStateMessage( data ); } ); setupLayout(); // Load our presentation iframes setupIframes(); // Once the iframes have loaded, emit a signal saying there's // a new subscriber which will trigger a 'statechanged' // message to be sent back window.addEventListener( 'message', function( event ) { var data = JSON.parse( event.data ); if( data && data.namespace === 'reveal' ) { if( /ready/.test( data.eventName ) ) { socket.emit( 'new-subscriber', { socketId: socketId } ); } } // Messages sent by reveal.js inside of the current slide preview if( data && data.namespace === 'reveal' ) { if( /slidechanged|fragmentshown|fragmenthidden|overviewshown|overviewhidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) { socket.emit( 'statechanged-speaker', { state: data.state } ); } } } );
```
--------------------------------
### Get Allocated Time
Source: https://github.com/shd101wyy/crossnote/blob/develop/dependencies/reveal/plugin/notes/speaker-view.html
Calculates the total seconds allocated for all slides up to the current slide index.
```javascript
/**
* Return the number of seconds allocated for presenting
* all slides up to and including this one.
*/
function getTimeAllocated( timings, callback ) {
callRevealApi( 'getSlidePastCount', [], function ( currentSlide ) {
var allocated = 0;
for (var i in timings.slice(0, currentSlide + 1)) {
allocated += timings[i];
}
callback( allocated );
} );
}
```
--------------------------------
### Build Project
Source: https://github.com/shd101wyy/crossnote/blob/develop/README.md
Builds the project using pnpm. Run from the shell.
```shell
pnpm build
```
--------------------------------
### Load Configurations from .crossnote Directory
Source: https://context7.com/shd101wyy/crossnote/llms.txt
Loads configuration files (like CSS, Mermaid, KaTeX settings) from a specified .crossnote directory. The function can optionally create the directory if it doesn't exist. The loaded configurations are then used to initialize the Notebook.
```javascript
const { loadConfigsInDirectory, wrapNodeFSAsApi, Notebook } = require('crossnote');
async function loadCustomConfigs() {
const fs = wrapNodeFSAsApi();
// Load configs from .crossnote directory
const configs = await loadConfigsInDirectory(
'/path/to/notebook/.crossnote',
fs,
true, // Create directory if not exists
);
console.log('Loaded configs:', configs);
console.log('Global CSS:', configs.globalCss?.substring(0, 100) + '...');
console.log('Mermaid config:', configs.mermaidConfig);
console.log('KaTeX config:', configs.katexConfig);
// The .crossnote directory structure:
// .crossnote/
// ├── config.js - KaTeX, MathJax, Mermaid configs
// ├── head.html - Custom HTML for