diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f48fca --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.DS_Store +# npm +node_modules +package-lock.json + +# build +main.js +*.js.map +*.zip +dist/ +coverage/ +.nyc_output/ +test-vault/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..67d9eb1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via an [issue](https://github.com/lynchjames/obsidian-day-planner/issues), or with the maintainer on the [Obsidian Forum](https://forum.obsidian.md/u/j_l/summary) before making a change. + +I have included a code of conduct, please follow it in all your interactions with the development of this plugin. + +## Pull Request Process + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Where appropriate, update the README.md with details of changes to the plugin, this includes additions and changes to configuration + settings, plugin commands, useful file locations and additional installation instructions. +3. If you can, please include tests in your Pull Request, particularly if you are making significant changes or additions to the behaviour of the plugin. +4. A CI Test Github Action workflow will run when a new Pull Request is made. The Pull Request cannot be completed until that workflow is passing with the plugin successfully building and all tests passing. +5. The repository maintainer will be responsible for increasing the version numbers in files and the README.md to the new version that this Pull Request would represent once it has been completed and merged. The versioning scheme used is [SemVer](http://semver.org/). + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Attribution + +This Code of Conduct is based on and adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..75f18a3 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Day Planner + + + + +This repository contains a plugin for [Obsidian](https://obsidian.md/) for day planning and managing pomodoro timers in Markdown. + + +## Usage + +## Commands + + +## Configuration + +### Timezone +<<
>> + +## Compatibility + +Custom plugins are only available for Obsidian v0.9.7+. + +The current API of this repo targets Obsidian **v0.9.10**. + +## Installing + +As of version [0.9.7 of Obsidian](https://forum.obsidian.md/t/obsidian-release-v0-9-7-insider-build/7628), this plugin is available to be installed directly from within the app. The plugin can be found in the Community Plugins directory which can be accessed from the Settings pane under Third Party Plugins. + +## Manual installation + +1. Download the [latest release](https://github.com/lynchjames/obsidian-day-planner/releases/latest) +1. Extract the obsidian-day-planner folder from the zip to your vault's plugins folder: `/.obsidian/plugins/` +Note: On some machines the `.obsidian` folder may be hidden. On MacOS you should be able to press `Command+Shift+Dot` to show the folder in Finder. +1. Reload Obsidian +1. If prompted about Safe Mode, you can disable safe mode and enable the plugin. + +## Credits + +TBC + +## For developers +Pull requests are both weclcome and appreciated. 😀 + +If you would like to contribute to the development of this plugin, please follow the guidelines provided in [CONTRIBUTING.md](CONTRIBUTING.md). + +## Donating + +This plugin is provided free of charge. If you would like to donate something to me, you can via [PayPal](https://paypal.me/lynchjames2020). Thank you! diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..7449d14 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "obsidian-day-planner", + "name": "Day Planner", + "version": "0.0.1", + "description": "A plugin to help you plan your day and setup pomodoro timers", + "isDesktopOnly": false, + "js": "main.js", + "css": "style.css" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c7722c2 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "obsidian-day-planner", + "version": "0.1.0", + "description": "A plugin to help you plan your day and setup pomodoro timers", + "main": "main.js", + "scripts": { + "dev": "rollup --config rollup.config.js -w", + "build": "rollup --config rollup.config.js", + "test": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' mocha -r ts-node/register -r ignore-styles -r jsdom-global/register tests/**/*.test.ts", + "coverage": "nyc -r lcov -e .ts -x \"*.test.ts\" npm run test" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "@rollup/plugin-commonjs": "^15.1.0", + "@rollup/plugin-node-resolve": "^9.0.0", + "@rollup/plugin-typescript": "^6.0.0", + "@types/chai": "^4.2.14", + "@types/mocha": "^8.0.3", + "@types/node": "^14.14.2", + "chai": "^4.2.0", + "cross-env": "^7.0.2", + "ignore-styles": "^5.0.1", + "jsdom": "^16.4.0", + "jsdom-global": "^3.0.2", + "mocha": "^8.2.0", + "nyc": "^15.1.0", + "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master", + "rollup": "^2.32.1", + "rollup-plugin-copy": "^3.3.0", + "ts-node": "^9.0.0", + "tslib": "^2.0.3", + "typescript": "^4.0.5" + }, + "dependencies": { + "moment": "^2.29.1" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..edb932e --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,27 @@ +import typescript from '@rollup/plugin-typescript'; +import {nodeResolve} from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import copy from 'rollup-plugin-copy'; +const TEST_VAULT = 'test-vault/.obsidian/plugins/day-planner'; + +export default { + input: 'src/main.ts', + output: { + dir: 'dist/', + sourcemap: 'inline', + format: 'cjs', + exports: 'default' + }, + external: ['obsidian'], + plugins: [ + typescript(), + nodeResolve({browser: true}), + commonjs(), + copy({ + targets: [ + { src: 'dist/main.js', dest: TEST_VAULT }, + { src: ['manifest.json', 'styles.css'], dest: TEST_VAULT } + ], flatten: true + }) + ] +}; \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..ae00a6e --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,9 @@ +export const HEADING_REGEX = /^[#\s-]*/; +export const HEADING_FORMAT = '#'; + +export const DEFAULT_DATE_FORMAT = 'YYYYMMDDHHmm'; +export const DATE_REGEX = /(?{{date:?(?[^}]*)}})/g; + +export const DAY_PLANNER_FILENAME = 'Day Planner.md'; +export const PLAN_PARSER_REGEX = +/^-?[\s]*\[?(?[x ]*)\]?(\d.)?\s*?(?\d{2}):(?\d{2})(?.*)$/gmi; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..cc0c59d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,106 @@ +import { + MarkdownView, + Plugin, + Vault, + DataAdapter, + TFile, + EventRef +} from 'obsidian'; +import MomentDateRegex from './moment-date-regex'; +import moment from 'moment'; +import { DayPlannerSettingsTab } from './settings-tab'; +import { DayPlannerSettings } from './settings'; +import { DAY_PLANNER_FILENAME } from './constants'; +import Parser, { PlanItem, PlanSummaryData } from './parser'; + +export default class DayPlanner extends Plugin { + settings: DayPlannerSettings; + momentDateRegex: MomentDateRegex; + parser: Parser; + vault: Vault; + statusBar: HTMLElement + statusBarAdded: boolean; + statusBarText: HTMLSpanElement; + statusBarProgress: HTMLDivElement; + statusBarCurrentProgress: HTMLDivElement; + + + + onInit() {} + + async onload() { + console.log("Loading Day Planner plugin"); + this.settings = (await this.loadData()) || new DayPlannerSettings(); + this.vault = this.app.vault; + this.statusBar = this.addStatusBarItem() + this.momentDateRegex = new MomentDateRegex(); + this.parser = new Parser(this.app.vault); + + this.linkToDayPlanBlock(); + // this.registerEvent(this.app.workspace.on('file-open', this.parseDayPlanner)); + this.registerEvent(this.app.on("codemirror", this.codeMirror)); + + this.addSettingTab(new DayPlannerSettingsTab(this.app, this)); + this.parseDayPlanner(); + + this.registerInterval( + window.setInterval(() => this.refreshStatusBar(), 2500) + ); + } + + async refreshStatusBar() { + const planSummary = await this.parseDayPlanner(); + const current = planSummary.current(); + this.updateProgress(current.current, current.next); + } + + updateProgress(current: PlanItem, next: PlanItem) { + if(!current || !next){ + return; + } + const nowMoment = moment(new Date()); + const currentMoment = moment(current.time); + const nextMoment = moment(next.time); + const diff = moment.duration(nextMoment.diff(currentMoment)); + const fromStart = moment.duration(nowMoment.diff(currentMoment)); + const fromNext = moment.duration(nextMoment.diff(nowMoment)); + let percentageComplete = (fromStart.asMinutes()/diff.asMinutes())*100; + console.log(fromStart.asMinutes(), fromNext.asMinutes(), diff.asMinutes(), percentageComplete); + this.statusBarCurrentProgress.style.width = `${percentageComplete.toFixed(0)}%`; + this.statusBarText.innerText = `${fromNext.asMinutes().toFixed(0)} mins left`; + } + + async parseDayPlanner():Promise { + const fileContent = await this.vault.adapter.read(DAY_PLANNER_FILENAME); + const planData = await this.parser.parseMarkdown(fileContent); + console.log('Current Task', planData.current()); + return planData; + } + + linkToDayPlanBlock() { + if(this.statusBarAdded) { + return; + } + let minutes = new Date().getMinutes(); + let left = 60 - minutes; + // let statusBarContent = new Array(count + 1).join('='); + let status = this.statusBar.createEl('div', { cls: 'day-planner', 'title': 'View the Day Planner', prepend: true}); + this.statusBarText = status.createEl('span', { cls: ['status-bar-item-segment', 'day-planner-status-bar-text']}); + this.statusBarProgress = status.createEl('div', { cls: ['status-bar-item-segment', 'day-planner-progress-bar']}); + this.statusBarCurrentProgress = this.statusBarProgress.createEl('div', { cls: 'day-planner-progress-value'}); + status.onClickEvent((ev: any) => { + this.app.workspace.openLinkText('Day Planner', '', false); + }); + + this.statusBarAdded = true; + } + + codeMirror = (cm: any) => { + console.log(cm); + } + + onunload() { + console.log("Unloading Day Planner plugin"); + } + +} \ No newline at end of file diff --git a/src/moment-date-regex.ts b/src/moment-date-regex.ts new file mode 100644 index 0000000..66f7d87 --- /dev/null +++ b/src/moment-date-regex.ts @@ -0,0 +1,45 @@ +import { DEFAULT_DATE_FORMAT, DATE_REGEX } from './constants'; +import * as moment from 'moment'; + +export default class MomentDateRegex { + replace(input: string): string { + //A regex to capture multiple matches, each with a target group ({date:YYMMDD}) and date group (YYMMDD) + const dateRegex = DATE_REGEX; + const customFolderString = input; + //Iterate through the matches to collect them in a single array + const matches = []; + let match; + while(match = dateRegex.exec(customFolderString)){ + matches.push(match) + } + //Return the custom folder setting value if no dates are found + if(!matches || matches.length === 0){ + return input; + } + const now = new Date(); + //Transform date matches into moment formatted dates + const formattedDates = matches.map(m => { + //Default to YYYYMMDDHHmm if {{date}} is used + const dateFormat = m.groups.date === '' ? DEFAULT_DATE_FORMAT : m.groups.date; + return [m.groups.target, + this.getMoment(now, dateFormat)]; + }); + + //Check to see if any date formatting is needed. If not return the unformatted setting text. + let output = customFolderString; + formattedDates.forEach(fd => { + output = output.replace(fd[0], fd[1]); + }) + return output; + } + + getMoment(now: Date, dateFormat: string) { + if((window as any).moment) { + return (window as any) + .moment(now) + .format(dateFormat) + } else { + return moment(now).format(dateFormat); + } + } +} \ No newline at end of file diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..233e34f --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,113 @@ +import { TFile, Vault } from 'obsidian'; +import { PLAN_PARSER_REGEX } from './constants'; + + +export class PlanSummaryData { + empty: boolean; + invalid: boolean; + items: PlanItem[]; + + constructor(items: PlanItem[]){ + this.empty = items.length < 1; + this.invalid = false; + this.items = items; + } + + current(): {current: PlanItem, next: PlanItem} { + const now = new Date(); + let result = null; + this.items.forEach((item, i) => { + const next = this.items[i+1]; + if(item.time < now && (!next || now < next.time)){ + result = {current: item, next: next}; + } + }); + return result; + } +} + +export class PlanRenderData { + + + constructor(){ + + } +} + +export class PlanItem { + matchIndex: number; + charIndex: number; + completed: boolean; + time: Date; + text: string; + + constructor(matchIndex: number, charIndex: number, completed: boolean, time: Date, text: string){ + this.matchIndex = matchIndex; + this.charIndex = charIndex; + this.completed = completed; + this.time = time; + this.text = text; + } +} + +export default class Parser { + vault: Vault; + + constructor(vault: Vault) { + this.vault = vault; + } + + async parseMarkdown(fileContent: string): Promise { + // if(file.basename !== 'Day Planner'){ + // return this.empty(); + // } + // if(file.extension !== 'md'){ + // return this.invalid(); + // } + const parsed = this.parse(fileContent); + const transformed = this.transform(parsed); + this.renderProgressInEditor(fileContent, transformed); + return new PlanSummaryData(transformed); + } + + private parse(input: string): RegExpExecArray[] { + const matches = []; + let match; + while(match = PLAN_PARSER_REGEX.exec(input)){ + matches.push(match) + } + return matches; + } + + private transform(regexMatches: RegExpExecArray[]): PlanItem[]{ + const results = regexMatches.map((value:RegExpMatchArray, index) => { + try { + const completed = value.groups.completion.trim().toLocaleLowerCase() === 'x'; + const time = new Date(); + time.setHours(parseInt(value.groups.hours)) + time.setMinutes(parseInt(value.groups.minutes)) + return new PlanItem(index, value.index, completed, time, value.groups.text.trim()); + } catch (error) { + + } + }); + return results; + } + + private renderProgressInEditor(fileContent:string, items:PlanItem[]){ + + } + + private empty(): PlanSummaryData { + const planData = new PlanSummaryData() + planData.empty= true; + return planData; + } + + private invalid(): PlanSummaryData { + const planData = new PlanSummaryData() + planData.invalid = true; + return planData; + } + +} diff --git a/src/settings-tab.ts b/src/settings-tab.ts new file mode 100644 index 0000000..ea034dd --- /dev/null +++ b/src/settings-tab.ts @@ -0,0 +1,38 @@ +import { + App, + PluginSettingTab, + Setting +} from 'obsidian'; +import { TimeZone } from './settings'; +import MomentDateRegex from './moment-date-regex'; +import DayPlanner from './main'; + + export class DayPlannerSettingsTab extends PluginSettingTab { + momentDateRegex = new MomentDateRegex(); + plugin: DayPlanner; + constructor(app: App, plugin: DayPlanner) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + + containerEl.empty(); + + new Setting(containerEl) + .setName('Default location for new notes') + .setDesc('Where newly created notes are placed. Plugin settings will override this.') + .addDropdown(dropDown => + dropDown + .addOption(TimeZone[TimeZone.GMT], "Time zone") + .setValue(TimeZone[this.plugin.settings.timeZone] || TimeZone.GMT.toString()) + .onChange((value:string) => { + this.plugin.settings.timeZone = TimeZone[value as keyof typeof TimeZone]; + this.plugin.saveData(this.plugin.settings); + this.display(); + })); + + + } + } \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..b29978c --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,7 @@ +export class DayPlannerSettings { + timeZone: TimeZone = TimeZone.GMT; +} + +export enum TimeZone { + GMT +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..6d03673 --- /dev/null +++ b/styles.css @@ -0,0 +1,90 @@ +.day-planner { + position: relative; +} + +.day-planner .status-bar-item-segment:hover { + cursor: pointer; +} + +.day-planner-progress-bar { + margin-top: 2px; + background-color: lightgray; + border-radius: 4px; + min-width: 150px; + height: 16px; + float: right; +} + +.day-planner-progress-value { + background-color: var(--interactive-accent); + transition: 0.3s all linear; + border-radius: 4px; + height: 16px; + display: inline-block; + /* animation: progress 3s ease-in-out forwards; + -webkit-animation: progress 3s ease-in-out forwards; */ +} + +.day-planner-progress-value.green { + background-color: #4CAF50; + animation: progress-3 3s ease-in-out forwards; + -webkit-animation: progress-3 3s ease-in-out forwards; +} +.day-planner-progress-value.yellow { + background-color: #ff9800; + animation: progress-2 3s ease-in-out forwards; + -webkit-animation: progress-2 3s ease-in-out forwards; +} + +.day-planner-status-bar-text { +} + +/* animation */ + @keyframes progress { + from { + width: 0; + } + to { + width: 55%; + } +} + @-webkit-keyframes progress { + from { + width: 0; + } + to { + width: 55%; + } +} + @keyframes progress-2 { + from { + width: 0; + } + to { + width: 70%; + } +} + @-webkit-keyframes progress-2 { + from { + width: 0; + } + to { + width: 70%; + } +} + @keyframes progress-3 { + from { + width: 0; + } + to { + width: 90%; + } +} + @-webkit-keyframes progress-3 { + from { + width: 0; + } + to { + width: 90%; + } +} diff --git a/tests/mocks/date.ts b/tests/mocks/date.ts new file mode 100644 index 0000000..68b12ec --- /dev/null +++ b/tests/mocks/date.ts @@ -0,0 +1,30 @@ +/** + * https://kimpers.com/mocking-date-and-time-in-tests-with-typescript-and-jest + * @param {Date} expected The date to which we want to freeze time + * @returns {Function} Call to remove Date mocking + */ +export const mockDate = (expected: Date) => { + const _Date = Date + + // If any Date or number is passed to the constructor + // use that instead of our mocked date + function MockDate(mockOverride?: Date | number) { + return new _Date(mockOverride || expected) + } + + MockDate.UTC = _Date.UTC + MockDate.parse = _Date.parse + MockDate.now = () => expected.getTime() + // Give our mock Date has the same prototype as Date + // Some libraries rely on this to identify Date objects + MockDate.prototype = _Date.prototype + + // Our mock is not a full implementation of Date + // Types will not match but it's good enough for our tests + global.Date = MockDate as any + + // Callback function to remove the Date mock + return () => { + global.Date = _Date + } + } \ No newline at end of file diff --git a/tests/moment-date-regex.test.ts b/tests/moment-date-regex.test.ts new file mode 100644 index 0000000..8c07df4 --- /dev/null +++ b/tests/moment-date-regex.test.ts @@ -0,0 +1,84 @@ +import 'mocha'; +import { assert } from 'chai'; +import { mockDate } from './mocks/date' +import MomentDateRegex from '../src/moment-date-regex'; +const date = new Date(2020, 9, 31, 14, 25, 15); +const momentRegex = new MomentDateRegex(); + +describe("Date formatting", () => { + let resetDateMock:() => void; + + before(async () => { + resetDateMock = mockDate(date); + }); + + it("No date format formatting using standad format", () => { + const input = 'Zettels/{{date}}'; + const expectedOuput = 'Zettels/202010311425'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("YYYYMMDD format", () => { + const input = 'Zettels/{{date:YYYYMMDD}}'; + const expectedOuput = 'Zettels/20201031'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Multiple dates", () => { + const input = 'Zettels/{{date:YYYY}}/{{date:MMM}}/{{date:DD_ddd}}'; + const expectedOuput = 'Zettels/2020/Oct/31_Sat'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Date path prefixing", () => { + const input = '{{date:YYYY}}/{{date:MM}}/My Notes'; + const expectedOuput = '2020/10/My Notes'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Text between date targets", () => { + const input = '{{date:YYYY}}/Zettels/{{date:MMMM}}'; + const expectedOuput = '2020/Zettels/October'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Date file name prefixing", () => { + const input = '{{date:YYYYMMDDHHmm}}-My New Note'; + const expectedOuput = '202010311425-My New Note'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + after(() => { + resetDateMock(); + }); +}); + +describe("Non-date input", () => { + + it("Input without dates", () => { + const input = 'Inbox/New'; + const expectedOuput = 'Inbox/New'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Input with date format without date target", () => { + const input = 'Inbox/YYYY'; + const expectedOuput = 'Inbox/YYYY'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); + + it("Input with date format and partial date target", () => { + const input = 'Inbox/{{date:YYYY'; + const expectedOuput = 'Inbox/{{date:YYYY'; + + assert.equal(momentRegex.replace(input), expectedOuput); + }); +}); \ No newline at end of file diff --git a/tests/setings.test.ts b/tests/setings.test.ts new file mode 100644 index 0000000..7650cc2 --- /dev/null +++ b/tests/setings.test.ts @@ -0,0 +1,11 @@ +import 'mocha'; +import { assert } from 'chai'; +import { TimeZone, DayPlannerSettings } from '../src/settings'; + +const settings = new DayPlannerSettings(); +describe("Day Planner Settings defaults", () => { + + it("Time zone ", () => { + assert.equal(settings.timeZone, TimeZone.GMT); + }); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4891e75 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "es5", + "allowJs": true, + "noImplicitAny": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "importHelpers": true, + "lib": [ + "dom", + "es5", + "scripthost", + "es2015" + ] + }, + "include": [ + "**/*.ts" + ] +}