diff --git a/client/calendar.coffee b/client/calendar.coffee
deleted file mode 100644
index 68788e0..0000000
--- a/client/calendar.coffee
+++ /dev/null
@@ -1,130 +0,0 @@
-###
- * Federated Wiki : Calendar Plugin
- *
- * Licensed under the MIT license.
- * https://github.com/fedwiki/wiki-plugin-calendar/blob/master/LICENSE.txt
-###
-
-months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
-spans = ['EARLY', 'LATE', 'DECADE', 'DAY', 'MONTH', 'YEAR']
-
-span = (result, span) ->
- if (m = spans.indexOf result.span) < 0
- result.span = span
- else if (spans.indexOf span) < m
- result.span = span
-
-parse = (text) ->
- rows = []
- for line in text.split /\n/
- result = {}
- words = line.match /\S+/g
- for word, i in words
- if word.match /^\d\d\d\d$/
- result.year = +word
- span result, 'YEAR'
- else if m = word.match /^(\d0)S$/
- result.year = +m[1]+1900
- span result, 'DECADE'
- else if (m = spans.indexOf word) >= 0
- result.span = spans[m]
- else if (m = months.indexOf word[0..2]) >= 0
- result.month = m+1
- span result, 'MONTH'
- else if m = word.match /^([1-3]?[0-9])$/
- result.day = +m[1]
- span result, 'DAY'
- else
- result.label = words[i..999].join ' '
- break
- rows.push result
- rows
-
-apply = (input, output, date, rows) ->
- result = []
- for row in rows
- if input[row.label]?.date?
- date = input[row.label].date
- if output[row.label]?.date?
- date = output[row.label].date
- if row.year?
- date = new Date row.year, 1-1
- if row.month?
- date = new Date date.getYear()+1900, row.month-1
- if row.day?
- date = new Date date.getYear()+1900, date.getMonth(), row.day
- if row.label?
- output[row.label] = {date}
- output[row.label].span = row.span if row.span?
- row.date = date
- radarValue = dateAsValue(row.date, row.span)
- row.units = radarValue.units
- row.value = radarValue.value
- row.precision = radarValue.precision
- result.push row
- result
-
-show = (date, span) ->
- switch span
- when 'YEAR' then date.getFullYear()
- when 'DECADE' then "#{date.getFullYear()}'S"
- when 'EARLY' then "Early #{date.getFullYear()}'S"
- when 'LATE' then "Late #{date.getFullYear()}'S"
- when 'MONTH' then "#{months[date.getMonth()]} #{date.getFullYear()}"
- else "#{date.getDate()} #{months[date.getMonth()]} #{date.getFullYear()}"
-
-
-format = (rows) ->
- for row in rows
- """
#{show row.date, row.span} | #{row.label}"""
-
-precisionFor =
- DAY: 1000 * 60 * 60 * 24
- MONTH: 1000 * 60 * 60 * 24 * 365.25 / 12
- YEAR: 1000 * 60 * 60 * 24 * 365.25
- DECADE: 1000 * 60 * 60 * 24 * 365.25 * 10
- EARLY: 1000 * 60 * 60 * 24 * 365.25 * 10
- LATE: 1000 * 60 * 60 * 24 * 365.25 * 10
-
-unitsFor =
- DAY: 'day'
- MONTH: 'month'
- YEAR: 'year'
- DECADE: 'decade'
- EARLY: 'decade'
- LATE: 'decade'
-
-dateAsValue = (date, span) ->
- precisionInMilliseconds = precisionFor[span] ? precisionFor.DAY
- units : [unitsFor[span] ? unitsFor.DAY]
- value : (Math.floor(date.getTime() / precisionInMilliseconds))
- precision : precisionInMilliseconds
-
-radarSource = ($item, results) ->
- data = {}
- for row in results
- data[row.label] =
- units: row.units
- value: row.value
- precision: row.precision
-
- $item.addClass 'radar-source'
- $item.get(0).radarData = -> data
-
-module.exports = {parse, apply, format, radarSource} if module?
-
-
-emit = (div, item) ->
- rows = parse item.text
- # wiki.log 'calendar rows', rows
- results = apply {}, {}, new Date(), rows
- # wiki.log 'calendar results', results
- radarSource div, results
- div.append """
- #{format(results).join ''}
- """
-
-bind = (div, item) ->
- div.on 'dblclick', () -> wiki.textEditor div, item
-
-window.plugins.calendar = {emit, bind} if window?
diff --git a/src/client/calendar.js b/src/client/calendar.js
new file mode 100644
index 0000000..d6fcc2d
--- /dev/null
+++ b/src/client/calendar.js
@@ -0,0 +1,163 @@
+/*
+ * Federated Wiki : Calendar Plugin
+ *
+ * Licensed under the MIT license.
+ * https://github.com/fedwiki/wiki-plugin-calendar/blob/master/LICENSE.txt
+ */
+
+const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
+const spans = ['EARLY', 'LATE', 'DECADE', 'DAY', 'MONTH', 'YEAR']
+
+const span = function (result, span) {
+ let m
+ if ((m = spans.indexOf(result.span)) < 0) {
+ return (result.span = span)
+ } else if (spans.indexOf(span) < m) {
+ return (result.span = span)
+ }
+}
+
+const parse = function (text) {
+ const rows = []
+ for (var line of text.split(/\n/)) {
+ var result = {}
+ var words = line.match(/\S+/g)
+ for (var i = 0; i < words.length; i++) {
+ var m
+ var word = words[i]
+ if (word.match(/^\d\d\d\d$/)) {
+ result.year = +word
+ span(result, 'YEAR')
+ } else if ((m = word.match(/^(\d0)S$/))) {
+ result.year = +m[1] + 1900
+ span(result, 'DECADE')
+ } else if ((m = spans.indexOf(word)) >= 0) {
+ result.span = spans[m]
+ } else if ((m = months.indexOf(word.slice(0, 3))) >= 0) {
+ result.month = m + 1
+ span(result, 'MONTH')
+ } else if ((m = word.match(/^([1-3]?[0-9])$/))) {
+ result.day = +m[1]
+ span(result, 'DAY')
+ } else {
+ result.label = words.slice(i, 1000).join(' ')
+ break
+ }
+ }
+ rows.push(result)
+ }
+ return rows
+}
+
+const apply = function (input, output, date, rows) {
+ const result = []
+ for (var row of rows) {
+ if (input[row.label]?.date) {
+ date = input[row.label].date
+ }
+ if (output[row.label]?.date) {
+ date = output[row.label].date
+ }
+ if (row.year) {
+ date = new Date(row.year, 1 - 1)
+ }
+ if (row.month) {
+ date = new Date(date.getYear() + 1900, row.month - 1)
+ }
+ if (row.day) {
+ date = new Date(date.getYear() + 1900, date.getMonth(), row.day)
+ }
+ if (row.label) {
+ output[row.label] = { date }
+ if (row.span) {
+ output[row.label].span = row.span
+ }
+ }
+ row.date = date
+ var radarValue = dateAsValue(row.date, row.span)
+ row.units = radarValue.units
+ row.value = radarValue.value
+ row.precision = radarValue.precision
+ result.push(row)
+ }
+ return result
+}
+
+const show = function (date, span) {
+ switch (span) {
+ case 'YEAR':
+ return date.getFullYear()
+ case 'DECADE':
+ return `${date.getFullYear()}'S`
+ case 'EARLY':
+ return `Early ${date.getFullYear()}'S`
+ case 'LATE':
+ return `Late ${date.getFullYear()}'S`
+ case 'MONTH':
+ return `${months[date.getMonth()]} ${date.getFullYear()}`
+ default:
+ return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`
+ }
+}
+
+const format = rows => rows.map(row => ` |
${show(row.date, row.span)} | ${row.label}`)
+
+const precisionFor = {
+ DAY: 1000 * 60 * 60 * 24,
+ MONTH: (1000 * 60 * 60 * 24 * 365.25) / 12,
+ YEAR: 1000 * 60 * 60 * 24 * 365.25,
+ DECADE: 1000 * 60 * 60 * 24 * 365.25 * 10,
+ EARLY: 1000 * 60 * 60 * 24 * 365.25 * 10,
+ LATE: 1000 * 60 * 60 * 24 * 365.25 * 10,
+}
+
+const unitsFor = {
+ DAY: 'day',
+ MONTH: 'month',
+ YEAR: 'year',
+ DECADE: 'decade',
+ EARLY: 'decade',
+ LATE: 'decade',
+}
+
+var dateAsValue = function (date, span) {
+ const precisionInMilliseconds = precisionFor[span] != null ? precisionFor[span] : precisionFor.DAY
+ return {
+ units: [unitsFor[span] != null ? unitsFor[span] : unitsFor.DAY],
+ value: Math.floor(date.getTime() / precisionInMilliseconds),
+ precision: precisionInMilliseconds,
+ }
+}
+
+const radarSource = function ($item, results) {
+ const data = {}
+ for (var row of results) {
+ data[row.label] = {
+ units: row.units,
+ value: row.value,
+ precision: row.precision,
+ }
+ }
+
+ $item.addClass('radar-source')
+ return ($item.get(0).radarData = () => data)
+}
+
+const emit = function (div, item) {
+ const rows = parse(item.text)
+ // wiki.log 'calendar rows', rows
+ const results = apply({}, {}, new Date(), rows)
+ // wiki.log 'calendar results', results
+ radarSource(div, results)
+ div.append(`\
+${format(results).join('')} \
+`)
+}
+
+const bind = (div, item) => div.on('dblclick', () => wiki.textEditor(div, item))
+
+if (typeof window !== 'undefined' && window !== null) {
+ window.plugins.calendar = { emit, bind }
+}
+
+export const calendar = typeof window == 'undefined' ? { parse, apply, format, radarSource } : undefined
diff --git a/test/test.coffee b/test/test.coffee
deleted file mode 100644
index d23b737..0000000
--- a/test/test.coffee
+++ /dev/null
@@ -1,118 +0,0 @@
-report = require '../client/calendar'
-expect = require 'expect.js'
-
-describe 'calendar plugin', ->
-
- describe 'parsing', ->
-
- it 'recognizes decades', ->
- expect(report.parse "1960 DECADE").to.eql [{year: 1960, span:'DECADE'}]
- expect(report.parse "DECADE 1960").to.eql [{year: 1960, span:'DECADE'}]
- expect(report.parse "60S").to.eql [{year: 1960, span:'DECADE'}]
-
- it 'recognizes half decades', ->
- expect(report.parse "60S EARLY").to.eql [{year: 1960, span:'EARLY'}]
- expect(report.parse "EARLY 60S").to.eql [{year: 1960, span:'EARLY'}]
- expect(report.parse "LATE 60S").to.eql [{year: 1960, span:'LATE'}]
-
- it 'recognizes years', ->
- expect(report.parse "1960").to.eql [{year: 1960, span:'YEAR'}]
-
- it 'recognizes months', ->
- expect(report.parse "1960 MAR").to.eql [{year: 1960, month:3, span:'MONTH'}]
- expect(report.parse "MAR 1960").to.eql [{year: 1960, month:3, span:'MONTH'}]
- expect(report.parse "MARCH 1960").to.eql [{year: 1960, month:3, span:'MONTH'}]
-
- it 'recognizes days', ->
- expect(report.parse "MAR 5 1960").to.eql [{year: 1960, month:3, day: 5, span:'DAY'}]
- expect(report.parse "1960 MAR 5").to.eql [{year: 1960, month:3, day: 5, span:'DAY'}]
- expect(report.parse "5 MAR 1960").to.eql [{year: 1960, month:3, day: 5, span:'DAY'}]
-
- it 'recognizes labels', ->
- expect(report.parse "Ward's CHM Interview").to.eql [{label: "Ward's CHM Interview"}]
- expect(report.parse "APRIL 24 2006 Ward's CHM Interview").to.eql [{year: 2006, month:4, day: 24, span:'DAY', label: "Ward's CHM Interview"}]
- expect(report.parse " APRIL 24 2006\tWard's CHM Interview ").to.eql [{year: 2006, month:4, day: 24, span:'DAY', label: "Ward's CHM Interview"}]
-
- describe 'applying', ->
-
- today = new Date 2013, 2-1, 3
- interview = new Date 2006, 4-1, 24
- oneDayInMS = 24*60*60*1000
-
- it 'recalls input', ->
- input = {interview: {date: interview}}
- output = {}
- rows = report.parse "interview"
- expect(report.apply input, output, today, rows).to.eql [
- date: interview
- label:'interview'
- units: ['day']
- value: Math.floor(interview.getTime() / oneDayInMS)
- precision: oneDayInMS
- ]
-
- it 'extends today', ->
- input = {}
- output = {}
- rows = report.parse "APRIL 1 April Fools Day"
- results = report.apply input, output, today, rows
- expect(results).to.eql [
- date: new Date(2013, 4-1, 1)
- month: 4
- day: 1
- span:'DAY'
- label: 'April Fools Day'
- units: ['day']
- value: Math.floor(new Date(2013, 4-1, 1).getTime() / oneDayInMS)
- precision: oneDayInMS
- ]
- expect(output).to.eql {'April Fools Day': {date: new Date(2013, 4-1, 1), span:'DAY'}}
-
- describe 'radarSource', ->
- mock = {}
- beforeEach ->
- mock.el = {}
- mock.$el =
- addClass : (c) -> mock.actualClass = c
- get : (n) -> mock.el
- results = report.apply {}, {}, new Date(), report.parse('''
- 2015 SEP 1 Starts Now
- LATE 90S Some languages were born
- ''')
- report.radarSource(mock.$el, results)
-
- it 'calls addClass with "radar-source"', ->
- expect(mock.actualClass).to.be 'radar-source'
-
- it 'adds radarData() to the DOM element', ->
- expect(mock.el).to.have.key 'radarData'
-
- it 'uses the labels as keys in the radarData', ->
- expect(mock.el.radarData()).to.have.key 'Starts Now'
-
- it 'puts the distance from the Epoch into the values in the radarData', ->
- data = mock.el.radarData()
- daysSinceEpoch = Math.floor(new Date(2015, 8, 1).getTime() / (24 * 60 * 60 * 1000) ) # 16,679
- expect(data['Starts Now']).to.have.key 'value'
- expect(data['Starts Now'].value).to.eql daysSinceEpoch
-
- it 'specifies units & precision with values in the radarData', ->
- data = mock.el.radarData()
- oneDayInMS = 24 * 60 * 60 * 1000 # 86,400,000
- expect(data['Starts Now']).to.have.key 'units'
- expect(data['Starts Now'].units).to.eql ['day']
- expect(data['Starts Now']).to.have.key 'precision'
- expect(data['Starts Now'].precision).to.eql oneDayInMS
-
- it 'chooses units & precision to match the parsed span of the date', ->
- data = mock.el.radarData()
- oneDecadeInMS = 10 * 365.25 * 24 * 60 * 60 * 1000 # 315,576,000,000
- expect(data['Some languages were born']).to.have.key 'units'
- expect(data['Some languages were born'].units).to.eql ['decade']
- expect(data['Some languages were born']).to.have.key 'precision'
- expect(data['Some languages were born'].precision).to.eql oneDecadeInMS
-
- # describe 'formatting', ->
- # it 'returns an array of strings', ->
- # rows = report.format report.parse ""
- # expect(rows).to.eql []
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..dab2c8d
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,144 @@
+import { calendar as report } from '../src/client/calendar.js'
+import expect from 'expect.js'
+
+describe('calendar plugin', function () {
+ describe('parsing', function () {
+ it('recognizes decades', function () {
+ expect(report.parse('1960 DECADE')).to.eql([{ year: 1960, span: 'DECADE' }])
+ expect(report.parse('DECADE 1960')).to.eql([{ year: 1960, span: 'DECADE' }])
+ expect(report.parse('60S')).to.eql([{ year: 1960, span: 'DECADE' }])
+ })
+
+ it('recognizes half decades', function () {
+ expect(report.parse('60S EARLY')).to.eql([{ year: 1960, span: 'EARLY' }])
+ expect(report.parse('EARLY 60S')).to.eql([{ year: 1960, span: 'EARLY' }])
+ expect(report.parse('LATE 60S')).to.eql([{ year: 1960, span: 'LATE' }])
+ })
+
+ it('recognizes years', () => expect(report.parse('1960')).to.eql([{ year: 1960, span: 'YEAR' }]))
+
+ it('recognizes months', function () {
+ expect(report.parse('1960 MAR')).to.eql([{ year: 1960, month: 3, span: 'MONTH' }])
+ expect(report.parse('MAR 1960')).to.eql([{ year: 1960, month: 3, span: 'MONTH' }])
+ expect(report.parse('MARCH 1960')).to.eql([{ year: 1960, month: 3, span: 'MONTH' }])
+ })
+
+ it('recognizes days', function () {
+ expect(report.parse('MAR 5 1960')).to.eql([{ year: 1960, month: 3, day: 5, span: 'DAY' }])
+ expect(report.parse('1960 MAR 5')).to.eql([{ year: 1960, month: 3, day: 5, span: 'DAY' }])
+ expect(report.parse('5 MAR 1960')).to.eql([{ year: 1960, month: 3, day: 5, span: 'DAY' }])
+ })
+
+ return it('recognizes labels', function () {
+ expect(report.parse("Ward's CHM Interview")).to.eql([{ label: "Ward's CHM Interview" }])
+ expect(report.parse("APRIL 24 2006 Ward's CHM Interview")).to.eql([
+ { year: 2006, month: 4, day: 24, span: 'DAY', label: "Ward's CHM Interview" },
+ ])
+ expect(report.parse(" APRIL 24 2006\tWard's CHM Interview ")).to.eql([
+ { year: 2006, month: 4, day: 24, span: 'DAY', label: "Ward's CHM Interview" },
+ ])
+ })
+ })
+
+ describe('applying', function () {
+ const today = new Date(2013, 2 - 1, 3)
+ const interview = new Date(2006, 4 - 1, 24)
+ const oneDayInMS = 24 * 60 * 60 * 1000
+
+ it('recalls input', function () {
+ const input = { interview: { date: interview } }
+ const output = {}
+ const rows = report.parse('interview')
+ expect(report.apply(input, output, today, rows)).to.eql([
+ {
+ date: interview,
+ label: 'interview',
+ units: ['day'],
+ value: Math.floor(interview.getTime() / oneDayInMS),
+ precision: oneDayInMS,
+ },
+ ])
+ })
+
+ return it('extends today', function () {
+ const input = {}
+ const output = {}
+ const rows = report.parse('APRIL 1 April Fools Day')
+ const results = report.apply(input, output, today, rows)
+ expect(results).to.eql([
+ {
+ date: new Date(2013, 4 - 1, 1),
+ month: 4,
+ day: 1,
+ span: 'DAY',
+ label: 'April Fools Day',
+ units: ['day'],
+ value: Math.floor(new Date(2013, 4 - 1, 1).getTime() / oneDayInMS),
+ precision: oneDayInMS,
+ },
+ ])
+ expect(output).to.eql({ 'April Fools Day': { date: new Date(2013, 4 - 1, 1), span: 'DAY' } })
+ })
+ })
+
+ return describe('radarSource', function () {
+ const mock = {}
+ beforeEach(function () {
+ mock.el = {}
+ mock.$el = {
+ addClass(c) {
+ return (mock.actualClass = c)
+ },
+ get(n) {
+ return mock.el
+ },
+ }
+ const results = report.apply(
+ {},
+ {},
+ new Date(),
+ report.parse(`\
+ 2015 SEP 1 Starts Now
+ LATE 90S Some languages were born\
+`),
+ )
+ return report.radarSource(mock.$el, results)
+ })
+
+ it('calls addClass with "radar-source"', () => expect(mock.actualClass).to.be('radar-source'))
+
+ it('adds radarData() to the DOM element', () => expect(mock.el).to.have.key('radarData'))
+
+ it('uses the labels as keys in the radarData', () => expect(mock.el.radarData()).to.have.key('Starts Now'))
+
+ it('puts the distance from the Epoch into the values in the radarData', function () {
+ const data = mock.el.radarData()
+ const daysSinceEpoch = Math.floor(new Date(2015, 8, 1).getTime() / (24 * 60 * 60 * 1000)) // 16,679
+ expect(data['Starts Now']).to.have.key('value')
+ return expect(data['Starts Now'].value).to.eql(daysSinceEpoch)
+ })
+
+ it('specifies units & precision with values in the radarData', function () {
+ const data = mock.el.radarData()
+ const oneDayInMS = 24 * 60 * 60 * 1000 // 86,400,000
+ expect(data['Starts Now']).to.have.key('units')
+ expect(data['Starts Now'].units).to.eql(['day'])
+ expect(data['Starts Now']).to.have.key('precision')
+ expect(data['Starts Now'].precision).to.eql(oneDayInMS)
+ })
+
+ return it('chooses units & precision to match the report.parsed span of the date', function () {
+ const data = mock.el.radarData()
+ const oneDecadeInMS = 10 * 365.25 * 24 * 60 * 60 * 1000 // 315,576,000,000
+ expect(data['Some languages were born']).to.have.key('units')
+ expect(data['Some languages were born'].units).to.eql(['decade'])
+ expect(data['Some languages were born']).to.have.key('precision')
+ expect(data['Some languages were born'].precision).to.eql(oneDecadeInMS)
+ })
+ })
+})
+
+// describe 'formatting', ->
+// it 'returns an array of strings', ->
+// rows = report.format report.parse ""
+// expect(rows).to.eql []
|