commit 795fe162c5e78b326c41f2e5ab8810278c7612e6 Author: Finn van Reenen Date: Tue Nov 28 09:32:35 2023 +0100 inital commit diff --git a/main.js b/main.js new file mode 100644 index 0000000..54dba9a --- /dev/null +++ b/main.js @@ -0,0 +1,523 @@ +const obsidian = require('obsidian'); +const Plugin = obsidian.Plugin; +const ItemView = obsidian.ItemView; + +const DateFormat = { + week: "y[0-9]{2}w[0-9]{2}", + days: ["zo", "ma", "di", "wo", "do", "vrij", "za"], + time: "[0-9]{2}:[0-9]{2}[ap]m", + date: "" +} +DateFormat.date = "(?:" + DateFormat.week + ")?(?: ?(?:" + DateFormat.days.join('|') + ") ?)?(?:" + DateFormat.time + ")?"; + +function parseSingleDate(d, defal) +{ + let year = -1, week = -1, dow = -1, time = defal; + + d = d.toLowerCase(); + + // year + let res = d.match(/y([0-9]{2})/); + if (res !== null) + { + year = parseInt(res[1]) + 2000; + } + else + { + year = defal.isoWeekYear(); + } + + // week + res = d.match(/w([0-9]{2})/); + if (res !== null) + { + week = parseInt(res[1]); + } + else + { + week = defal.isoWeek(); + } + + // day of week + res = d.split(' '); + for (const p in res) + { + let i = DateFormat.days.indexOf(res[p]); + if (i != -1){ + dow = i; + break; + } + } + if (dow == -1) + { + dow = defal.day() + } + + // time + res = d.match(/([0-9]{2}:[0-9]{2}[ap]m)/); + if (res !== null) + { + time = moment(res[1], "hh:mma"); + } + + // combine + time.day(dow); + time.isoWeek(week); + time.isoWeekYear(year); + return time; +} + +function stringifySingleDate(date) +{ + let week = date.format("[y]GG[w]WW"); + let dow = DateFormat.days[date.day()]; + let time = date.format("hh:mma"); + return [week, dow, time].join(' '); +} + +function stringifyDate(date) +{ + let res = stringifySingleDate(date[0]); + if (date[1].diff(date[0]) > 1000) + { + res += "-" + stringifySingleDate(date[1]) + } + return res; +} + +function parseDate(d, defal) +{ + let start, end; + let res = d.match(/([^-]+)-([^-]+)/); + if (res !== null) + { + start = parseSingleDate(res[1], defal); + end = parseSingleDate(res[2], start); + } + else + { + start = parseSingleDate(d, defal); + end = start; + } + return [start, end] +} + +function findDate(line, defal) +{ + let res = line.match("\\[(" + DateFormat.date + "(?:-" + DateFormat.date + ")?)\\]"); + if (res !== null) + { + res[1] = parseDate(res[1], defal); + } + return { + "line": line, + "dateRemoved": (res === null) ? line : line.replace(res[0], ''), + 'date': (res === null) ? null : res[1] + } +} + +function scanFile(editor, data) +{ + let lineNum = 0, lineCount = editor.lineCount(); + let defal = parseSingleDate("00:00am", moment()); + + while ((lineNum < lineCount)) + { + let res, line = editor.getLine(lineNum); + + res = line.match("weekboek: (" + DateFormat.date + ")"); + if (res != null) + { + defal = parseSingleDate(res[1], defal); + } + + res = [...line.matchAll("\\[(" + DateFormat.date + "(?:-" + DateFormat.date + ")?)\\]")]; + if (res.length > 0) + { + if ((res[0] == "[-]" && res.length > 1) || res[0] != "[-]") + { + if (res[0][0] == "[-]") + { + res[0] = res[1]; + } + res = res[0] + res[1] = parseDate(res[1], defal); + let procesed = processLine(line, line.replace(res[0], ''), res[1], data); + data = procesed['data']; + editor.setLine(lineNum, procesed['line']); + } + } + + lineNum++; + } + return data; +} + +function processLine(line, noDate, date, data) +{ + let res; + let item = { + "id": null, + "date": stringifyDate(date), + "title": "Untitled", + "group": "default", + "location": null, + "state": "reminder" + }; + + // id + res = noDate.match(/<([a-z0-9]+)\/>/); + if (res != null) + { + item["id"] = res[1]; + noDate = noDate.replace(res[0], ''); + } + else + { + item["id"] = generateId(); + line += " <" + item["id"] + "/>"; + } + + // location + res = noDate.match(/\{([^\}\{]+)\}/); + if (res != null) + { + item["location"] = res[1]; + noDate = noDate.replace(res[0], ''); + } + + // state (checkbox) + res = noDate.match(/^[ \t]*- \[(.)\] /); + if (res != null) + { + switch (res[1]) + { + case ' ': + item["state"] = "task"; + break; + case 'x': + item["state"] = "done"; + break; + case '-': + item["state"] = "canceled"; + break; + case '>': + item["state"] = "moved"; + break; + case '/': + item["state"] = "partly finished"; + break; + default: + item['state'] = res[1]; + break; + } + noDate = noDate.replace('[' + res[1] + '] ', ''); + } + + noDate = noDate.replace(/[ \t][ \t]+/g, ' '); // remve multeple spaces/tabs in a row + + // title and group + res = noDate.match(/^ ?- ([^\[\]<>{}]*) - ([^\[\]<>{}]*)/); + if (res != null) + { + item["group"] = res[1]; + item["title"] = res[2]; + } + else + { + // no group look for only title + res = noDate.match(/^ ?- ([^\[\]<>\{\}]*)/); + if (res != null) + { + item["title"] = res[1]; + } + else + { + item["title"] = noDate; + } + } + // remove leading and ending spaces + item["title"] = item['title'].replaceAll(/(^ +)?( +$)?/g, '') + item["group"] = item['group'].replaceAll(/(^ +)?( +$)?/g, '') + + // add/update to database + if (data == null) data = {}; + data[item["id"]] = item; + + return { line: line, data: data}; +} + +function generateId() +{ + let id = ''; + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let l = 0; + while (l < 10) { + id += chars.charAt(Math.floor(Math.random() * chars.length)); + l += 1; + } + return id; +} + +const VIEW_TYPE_CALENDAR = "fr-calendar" + +class CalendarView extends ItemView +{ + constructor(leaf, plugin) + { + super(leaf); + this.plugin = plugin; + this.week = stringifySingleDate(moment()).match(DateFormat.week)[0] + } + + getViewType() + { + return VIEW_TYPE_CALENDAR; + } + + getDisplayText() + { + return "Calender " + this.week; + } + + async onOpen() + { + const container = this.containerEl.children[1]; + container.empty(); + + // month + this.monthEl = container.createEl("div", { cls: "frcal__month" }); + + this.monthEl.createEl("div", { cls: "frcal__time" }); + this.mountFirstEl = this.monthEl.createEl("div", { cls: "frcal__month_first", attr: { style: "flex: 70" } }); + this.mountFirstSpanEl = this.mountFirstEl.createEl("span"); + this.mountSecondEl = this.monthEl.createEl("div", { cls: "frcal__month_second", attr: { style: "flex: 0" } }); + this.mountSecondSpanEl = this.mountSecondEl.createEl("span"); + + // day header + this.headEl = container.createEl("div", { cls: "frcal__days frcal__day_head" }); + + this.headEl.createEl("div", { cls: "frcal__time" }); + this.dayHeads = [null, null, null, null, null, null, null]; + for (let i = 0; i < 7; i++) + { + let dayEl = this.headEl.createEl("div", { cls: "frcal__day frcal__day_head_item" }); + this.dayHeads[i] = dayEl.createEl("span"); + } + + // all day events + this.alldayEl = container.createEl("div", { cls: "frcal__days frcal__day_allday" }); + + this.alldayEl.createEl("div", { cls: "frcal__time" }); + this.allday = [null, null, null, null, null, null, null]; + for (let i = 0; i < 7; i++) + { + this.allday[i] = this.alldayEl.createEl("div", { cls: "frcal__day frcal__day_allday_item" }); + } + + // timed events + this.timedEl = container.createEl("div", { cls: "frcal__days frcal__day_timed" }); + + let timed = this.timedEl.createEl("div", { cls: "frcal__time" }); + let median = "am" + for (let i = 1; i <= 12; i++) + { + timed.createEl("div", { cls: "frcal__hour " + median, text: i.toString() }); + if (i == 12 && median == "am"){ + i = 0; + median = "pm"; + } + } + this.timed = [null, null, null, null, null, null, null]; + for (let i = 0; i < 7; i++) + { + this.timed[i] = this.timedEl.createEl("div", { cls: "frcal__day frcal__timed_item" }); + } + + this.addEvents(); + this.updateWeek(); + } + + async onClose() + { + // Nothing to clean up. + } + + addEvents() + { + this.backButtonEl.ariaDisabled = "false"; + this.backButtonEl.addEventListener('click', this.gotoPreviusWeek.bind(this)); + this.forwardButtonEl.ariaDisabled = "false"; + this.forwardButtonEl.addEventListener('click', this.gotoNextWeek.bind(this)); + } + + gotoPreviusWeek() + { + let date = this.week.match(/y([0-9]{2})w([0-9]{2})/); + this.week = "y" + date[1] + "w" + (parseInt(date[2]) - 1).toString(); + this.updateWeek(); + } + + gotoNextWeek() + { + let date = this.week.match(/y([0-9]{2})w([0-9]{2})/); + this.week = "y" + date[1] + "w" + (parseInt(date[2]) + 1).toString(); + this.updateWeek(); + } + + updateWeek() + { + let months = ["januarie", "februarie", "maart", "april", "mei", "juni", "juli", "augustis", "september", "oktober", "november", "december"]; + let monthF = parseSingleDate(this.week + " ma 00:00am",).month(); + this.mountFirstSpanEl.innerText = (monthF+1).toString() + " - " + months[monthF]; + + let zoDate = parseSingleDate(this.week + " zo 00:00am"); + let monthS = zoDate.month(); + if (monthF != monthS) + { + this.mountSecondSpanEl.innerText = (monthS+1).toString() + " - " + months[monthS]; + + let dow = zoDate.date(); + + this.mountFirstEl.setAttr("style", "flex: " + ((7-dow)*10).toString()); + this.mountSecondEl.setAttr("style", "flex: " + ((dow)*10).toString()) + } + else + { + this.mountSecondSpanEl.innerText = ""; + this.mountFirstEl.setAttr("style", "flex: 70") + this.mountSecondEl.setAttr("style", "flex: 0") + } + + + let days = ["Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"]; + for (let day = 0; day < 7; day++) + { + let date = parseSingleDate(this.week + " " + DateFormat.days[(day + 1) % 7] + " 12:00pm"); + this.dayHeads[day].innerHTML = days[day] + "
" + date.date().toString(); + } + + this.updateEvents(); + } + + updateEvents() + { + // let item = { + // "id": null, + // "date": stringifyDate(date), + // "allDay": false, + // "title": "Untitled", + // "group": "default", + // "location": null, + // "state": "reminder" + // }; + + // clear events + for (let day = 0; day < 7; day++) + { + this.allday[day].innerHTML = ""; + this.timed[day].innerHTML = ""; + } + + let start = parseSingleDate(this.week + " ma 00:00am"); + let end = parseSingleDate(this.week + " zo 11:59pm"); + for (let event in this.plugin.data) + { + event = this.plugin.data[event]; + let date = parseDate(event['date']) + if ( + (date[0] > start && date[0] < end) + || (date[1] > start && date[1] < end) + ) + { + let day = date[0].day() - 1 + day = (day == -1) ? 6 : day; + if (event.date.match(DateFormat.time) == '12:00am') + { + this.renderEvent(event, this.allday[day]); + } + else + { + this.renderEvent(event, this.timed[day]); + } + } + } + } + + renderEvent(event, container) + { + el = container.createEl('div', { + cls: 'frcal__event', + id: "fr_event_" + event.id, + attr: { + 'data-group': event.group, + 'date-state': event.state + } + }); + el.createEl('span', { text: event.title }); + return el; + } +} + + +class FRCalander extends Plugin +{ + async onload() + { + this.registerView(VIEW_TYPE_CALENDAR, (leaf) => { return new CalendarView(leaf, this) }); + + this.addCommand({ + id: 'calnder-scan', + name: 'scan active file', + editorCallback: (editor) => { + //TODO: check if editor is valid + if (this.data == null) this.data = this.loadData(); + this.data = scanFile(editor, this.data); + this.saveData(this.data); + } + }); + //TODO: scan folder; with DataAdapter + // this.addCommand({ + // id: 'calnder-save', + // name: 'save the calander', + // editorCallback: () => { + // this.saveData(data); + // } + // }); + // this.addCommand({ + // id: 'calnder-load', + // name: 'save the calander', + // editorCallback: () => { + // data = this.loadData(); + // } + // }); + + + this.addRibbonIcon("calendar", "show fr calender", () => { + let leaf = app.workspace.getLeavesOfType(VIEW_TYPE_CALENDAR); + if (leaf.length > 0) + { + leaf = leaf[0]; + } + else + { + leaf = app.workspace.getLeavesOfType('empty'); + if (leaf.length > 0) + { + leaf = leaf[0]; + } + else + { + leaf = app.workspace.getLeaf('tab'); + } + } + + leaf.setViewState({ type: VIEW_TYPE_CALENDAR }); + app.workspace.setActiveLeaf(leaf); + }); + + this.data = this.loadData(); + } +} + +module.exports = FRCalander; diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..7eaec3b --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "fr-calander", + "name": "calendar thingimegig", + "version": "0.0.1", + "minAppVersion": "1.3.0", + "description": "creates a calender from notes", + "author": "Finn van Reenen", + "authorUrl": "https://github.com/freenen", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..be52864 --- /dev/null +++ b/style.css @@ -0,0 +1,96 @@ + +.workspace-leaf-content[data-type="fr-calendar"] span { + padding: 5px 10px; + display: block; +} + +.frcal__time { + flex: 2; + border-right: var(--color-base-40) 1px solid; +} + +.frcal__month { + width: 100%; + display: flex; + /*position: fixed;*/ +} + +.frcal__month_first { + background-color: var(--background-primary); + border-top: var(--color-base-40) 1px solid; + border-right: var(--color-base-40) 1px solid; + border-bottom: var(--color-base-40) 1px solid; +} + +.frcal__month_second { + background-color: var(--background-secondary); + border-top: var(--color-base-40) 1px solid; + border-right: var(--color-base-40) 1px solid; + border-bottom: var(--color-base-40) 1px solid; +} + +.frcal__days { + display: flex; +} +.frcal__day { + flex: 10; + border-right: var(--color-base-40) 1px solid; +} +.frcal__day:nth-child(even) { + background-color: var(--background-primary); +} +.frcal__day:nth-child(odd) { + background-color: var(--background-primary-alt); +} + +.frcal__day_head { + text-align: center; + /*position: fixed;*/ + width: 100%; + /*top: 30px;*/ +} + +.frcal__day_head .frcal__day { + border-bottom: var(--color-base-40) 1px solid; +} + +.frcal__day_allday { + width: 100%; + /*margin-top: 60px*/ + + /*position: fixed;*/ + /*top: 60px;*/ +} + +.frcal__day_allday .frcal__day { + min-height: 30px; + border-bottom: var(--color-base-40) 1px solid; + text-align: left; +} + +.frcal__day_timed { + border-bottom: var(--color-base-40) 1px solid; + text-align: left; + align-items: stretch; + /*margin-top: 60px*/ +} + +.frcal__hour { + height: 30px; + border-bottom: var(--color-base-40) 1px solid; + padding: 3px; +} +.frcal__hour.am{ + color: var(--text-muted); +} +.frcal__hour.pm{ + color: var(--text-normal); +} + +.frcal__event span { + background-color: var(--color-accent); + border-radius: 5px; + padding: 5px; + margin: 5px 3px; + display: block; +} \ No newline at end of file