conf-cal
Version:
Convenient calendar for conferences
242 lines (218 loc) • 7.62 kB
JavaScript
const moment = require('moment-timezone')
function renderHeader (options, context) {
context.columns = [''].concat(context.data.rooms.map(options.quickEscape))
const headerA = options.renderRow(options, context)
let headerB = ''
if (options.headerSeperator) {
context.columns = context.columns.map(() => options.headerSeperator)
headerB = options.renderRow(options, context)
}
delete context.columns
return `${headerA}${headerB}`
}
function renderRow (options, context) {
return `${options.rowHeader}${context.columns.join(options.columnSeperator)}${options.rowFooter}${options.rowSeperator}`
}
function renderFullBreak (options, context) {
const br = options.renderBreak(options, context)
return br + new Array(context.data.rooms.length).join(`${options.columnSeperator}${br}`)
}
function renderBreak (options, context) {
return `[${options.breakWord}]`
}
function escape (options, context) {
return String(context.string).replace(/\|/g, '|').replace(/\n/gm, '<br/>')
}
function renderLang (options, context) {
if (!context.roomEntry || !context.roomEntry.lang) {
return ''
}
return ` ${options.lang} ${context.roomEntry.lang}`
}
function renderBy (options, context) {
if (!context.roomEntry || !context.roomEntry.person) {
return ''
}
context.string = context.roomEntry.person
const result = ` _${options.by} ${options.escape(options, context)}_`
delete context.string
return result
}
function renderRoomListEntry (options, context) {
return `${options.listBullet}${context.roomListContent}`
}
function renderRoomListContent (options, context) {
const ctx = Object.assign({}, context, {
roomEntry: context.roomListEntry,
roomList: ''
})
ctx.roomPerson = options.renderBy(options, ctx)
ctx.roomLang = options.renderLang(options, ctx)
return options.renderRoom(options, ctx)
}
function renderRoomList (options, context) {
if (!context.roomEntry.entries) {
return ''
}
const roomEntries = context.roomEntry.entries.map((roomListEntry) => {
context.roomListEntry = roomListEntry
context.roomListContent = options.renderRoomListContent(options, context)
return options.renderRoomListEntry(options, context)
})
delete context.roomListContent
delete context.roomListEntry
return `${options.listHeader}${roomEntries.join(options.listSeperator)}${options.listFooter}`
}
function renderRoom (options, context) {
if (!context.roomEntry) {
return options.cont
}
if (context.roomEntry.summary === null) {
return options.renderBreak(options, context)
}
return `${options.quickEscape(context.roomEntry.summary)}${context.roomPerson}${context.roomLang}${context.roomList}`
}
function renderSingleRoom (options, context) {
let found = false
return context.data.rooms.map(room => {
if (context.slotEntry.room === room) {
found = true
return options.renderRoom(options, context)
}
return found ? options.left : options.right
}).join(options.columnSeperator)
}
function renderRooms (options, context) {
const slotEntry = context.slotEntry
if (slotEntry.entry) {
if (!slotEntry.room) {
return options.renderFullBreak(options, context)
}
context.room = slotEntry.room
context.roomEntry = slotEntry.entry
context.roomPerson = options.renderBy(options, context)
context.roomLang = options.renderLang(options, context)
context.roomList = options.renderRoomList(options, context)
let result
if (context.data.rooms.length === 1) {
result = options.renderRoom(options, context)
} else {
result = options.renderSingleRoom(options, context)
}
delete context.room
delete context.roomEntry
delete context.roomPerson
delete context.roomList
delete context.roomLang
return result
}
return context.data.rooms.map(room => {
context.room = room
context.roomEntry = slotEntry.entries[room]
if (context.roomEntry) {
context.roomPerson = options.renderBy(options, context)
context.roomLang = options.renderLang(options, context)
context.roomList = options.renderRoomList(options, context)
}
const result = options.renderRoom(options, context)
delete context.room
delete context.roomEntry
delete context.roomPerson
delete context.roomList
delete context.roomLang
return result
}).join(options.columnSeperator)
}
function renderTime (options, context) {
return `${context.slotStart}-${context.slotEnd}`
}
function renderSlot (options, context) {
const time = options.renderTime(options, context)
if (time) {
context.columns = [time].concat(context.roomEntries)
} else {
context.columns = context.roomEntries
}
const result = options.renderRow(options, context)
delete context.columns
return result
}
function renderSlots (options, context) {
return context.data.slots
.map(slotEntry => {
context.slotStart = moment(slotEntry.start).tz(context.data.tz).format('H:mm')
context.slotEnd = moment(slotEntry.end).tz(context.data.tz).format('H:mm')
context.slotEntry = slotEntry
context.roomEntries = options.renderRooms(options, context)
const result = options.renderSlot(options, context)
delete context.slotEntry
delete context.slotStart
delete context.slotEnd
delete context.roomEntries
return result
})
.join('')
}
function render (options, context) {
return `${options.header}${options.renderHeader(options, context)}${options.renderSlots(options, context)}${options.footer}`
}
function quickEscape (options, context, string) {
context.string = string
const result = options.escape(options, context)
delete context.string
return result
}
const defaults = {
header: '\n', // Header to be prepended to the list
footer: '', // Footer to be appended on the list
breakWord: 'Break', // Keyword to be inserted in a break
by: 'by', // Keywork to be used to prefix the presenter
lang: 'in', // Keyword to be used to prefix the language
cont: '⤓', // If the former entry continues in this slot
left: '←', // Single room: for empty rooms, where the full room is on the left
right: '→', // Single room: for empty rooms, where the full room is on the right
rowHeader: '| ', // prefix for a row
rowFooter: ' |', // suffix for a row
columnSeperator: ' | ', // seperator between each column
rowSeperator: '\n', // seperator between each row
headerSeperator: '---', // seperator between head and body
listHeader: '<br/><ul>', // header before a list of entries in an entry
listSeperator: '</li>', // suffix to be added to an list entry
listBullet: '<li>', // prefix to be added to an list entry
listFooter: '</li></ul>', // suffix to be added after the list
//
// All render methods are called with (options, context)
//
// where 'options' is this passed-in options and context
// is transformed before each call. The context always
// contains a 'data' property that contains the slotData
//
render,
renderRow,
renderHeader,
renderSlots,
renderSlot,
renderTime,
renderRooms,
renderRoom,
renderSingleRoom,
renderRoomList,
renderRoomListEntry,
renderRoomListContent,
renderBreak,
renderFullBreak,
renderBy,
renderLang,
escape
}
module.exports = (options, slotData) => {
options = Object.assign({}, defaults, options)
options.defaults = defaults
const context = {
data: slotData
}
if (!options.quickEscape) {
options.quickEscape = quickEscape.bind(null, options, context)
}
return options.render(options, context)
}