-
Notifications
You must be signed in to change notification settings - Fork 9
Closed
Description
Below is a sample script for generating a book mode cover and each book mode HackMD note. Book mode is a Markdown note that contains links to each note page.
- Make it more generic by placing constants at the beginning of the file, and add comments and a README.
- Implement it in TypeScript and install the tsx package to allow users to run it without tsc.
- Generate a sample sessions.json file based on the script.
- Add a detailed README.
'use strict'
// Load environment variables from .env file in project root
require('dotenv').config()
const _ = require('lodash')
const moment = require('moment')
const { API } = require('@hackmd/api')
const annoncementNote = '@DevOpsDay/rkO2jyLMlg'
const teamPath = 'DevOpsDay'
// Define permission constants (since we can't import the enums)
const NotePermissionRole = {
OWNER: 'owner',
SIGNED_IN: 'signed_in',
GUEST: 'guest'
}
// Load and process session data
let sessionList = require('./sessions.json')
.filter(s => s.session_type && s.session_type !== null) // Filter out null session types
.map(s => {
const speakers = s.speaker.map(speaker => {
return speaker.speaker.public_name
}).join(' & ')
return {
id: s.id,
title: s.title + (speakers ? " - " + speakers : ""),
tags: s.tags || [],
startDate: moment(s.started_at).valueOf(),
day: moment(s.started_at).format('MM/DD'),
startTime: moment(s.started_at).format('HH:mm'),
endTime: moment(s.finished_at).format('HH:mm'),
sessionType: s.session_type,
classroom: s.classroom?.tw_name || s.classroom?.en_name || 'TBD',
language: s.language || 'en',
difficulty: s.difficulty || 'General'
}
})
.sort((a, b) => (a.startDate - b.startDate))
function nest(seq, keys) {
if (!keys.length)
return seq;
const [first, ...rest] = keys
return _.mapValues(_.groupBy(seq, first), function (value) {
return nest(value, rest)
});
}
const api = new API(process.env.HACKMD_ACCESS_TOKEN, process.env.HACKMD_API_ENDPOINT)
// Extract the host from the API endpoint for generating correct URLs
function getHackMDHost() {
const apiEndpoint = process.env.HACKMD_API_ENDPOINT || 'https://hackmd.io'
try {
const url = new URL(apiEndpoint)
return `${url.protocol}//${url.host}`
} catch (error) {
console.warn('Failed to parse HACKMD_API_ENDPOINT, falling back to https://hackmd.io')
return 'https://hackmd.io'
}
}
async function main() {
console.log(`Processing ${sessionList.length} sessions...`)
// Create individual session notes
for (let data of sessionList) {
const noteData = {
title: `${data.title}`,
content: `# ${data.title}
{%hackmd ${annoncementNote} %}
## 筆記區
> 從這開始記錄你的筆記
## 討論區
> 歡迎在此進行討論
## 相關連結
- [DevOpsDays Taipei 2025 官方網站](https://devopsdays.tw/)
###### tags: \`DevOpsDays Taipei 2025\`
`,
readPermission: NotePermissionRole.GUEST,
writePermission: NotePermissionRole.SIGNED_IN
}
try {
const note = await api.createTeamNote(teamPath, noteData)
data.noteUrl = note.shortId
console.log(`Created note for: ${data.title}`)
} catch (error) {
console.error(`Failed to create note for ${data.title}:`, error.message)
data.noteUrl = 'error'
}
}
// Output session URLs for reference
const hackmdHost = getHackMDHost()
const sessionUrls = sessionList
.filter(s => s.noteUrl !== 'error')
.map(s => ({
id: s.id,
url: `${hackmdHost}/${s.noteUrl}`,
title: s.title
}))
console.log('\n=== Session URLs ===')
console.log(JSON.stringify(sessionUrls, null, 2))
// Create nested structure for the main book
const nestedSessions = nest(sessionList.filter(s => s.noteUrl !== 'error'), ['day', 'startTime'])
const bookContent = generateBookContent(nestedSessions, 1)
// Create main conference book
const mainBookContent = `DevOpsDays Taipei 2025 共同筆記
===
## 歡迎來到 DevOpsDays Taipei 2025!
- [歡迎來到 DevOpsDays!](/@DevOpsDay/ry9DnJIfel)
- [DevOpsDays 2025 官方網站](https://devopsdays.tw/) [target=_blank]
- [HackMD 快速入門](https://hackmd.io/s/BJvtP4zGX)
- [HackMD 會議功能介紹](https://hackmd.io/s/BJHWlNQMX)
## 議程筆記
${bookContent}
## 相關資源
- [DevOps Taiwan Community](https://www.facebook.com/groups/DevOpsTaiwan/)
- [活動照片分享區](#)
- [問題回饋](#)
###### tags: \`DevOpsDays Taipei 2025\`
`
try {
const mainBook = await api.createTeamNote(teamPath, {
title: 'DevOpsDays Taipei 2025 共同筆記',
content: mainBookContent,
readPermission: NotePermissionRole.GUEST,
writePermission: NotePermissionRole.SIGNED_IN
})
console.log('\n=== Main Conference Book ===')
console.log(`${hackmdHost}/${mainBook.shortId}`)
} catch (error) {
console.error('Failed to create main book:', error.message)
}
}
function generateBookContent(sessions, layer) {
const days = Object.keys(sessions).sort()
let content = ""
if (Array.isArray(sessions[days[0]])) {
// This is the leaf level (sessions) - flatten all sessions and sort chronologically
let allSessions = []
for (let timeSlot of days) {
allSessions = allSessions.concat(sessions[timeSlot])
}
// Sort all sessions by start time
const sortedSessions = _.sortBy(allSessions, ['startTime'])
for (let session of sortedSessions) {
if (session.noteUrl && session.noteUrl !== 'error') {
content += `- ${session.startTime} ~ ${session.endTime} [${session.title}](/${session.noteUrl}) (${session.classroom})\n`
}
}
return content
} else {
// This is a grouping level
for (let day of days) {
content += `${new Array(layer).fill("#").join("")} ${day}\n\n`
content += generateBookContent(sessions[day], layer + 1)
}
return content
}
}
// Run the script
if (require.main === module) {
main().catch(console.error)
}
module.exports = { main, generateBookContent }
Copilot
Metadata
Metadata
Assignees
Labels
No labels