diff --git a/README.md b/README.md index ddd9322..1690237 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,83 @@ I'm just a footer But other than that, you can use all the basic [tags](https://liquidjs.com/tags/overview.html) and [filters](https://liquidjs.com/filters/overview.html) +## Custom Filters + +For now I'll keep the documentation of the filters here, when they become more I'll move it somewhere more convenient + +## `date` filter + +The LiquidJS [built-in `date` filter](https://liquidjs.com/filters/date.html) has been replaced with a custom one. +For now, if there is a `%` character in the date format the old filter is used, otherwise the new one will take place. + +The new `date` filter uses [date-fns](https://date-fns.org/) as library to handle dates, and the format strings to use are showed here: [date-fns formats](https://date-fns.org/v2.21.3/docs/format) + +There are also some special words that can be used with this filter: + +**"now"**
+`{{ "now" | date: "yyyy-MM-dd" }}`
+Use the date from `new Date` and formatted with the given format
+ + +**"today"**
+`{{ "today" | date: "yyyy-MM-dd" }}`
+same as `now`
+ + +**"yesterday"**
+`{{ "yesterday" | date: "yyyy-MM-dd" }}`
+use the [`subDays`](https://date-fns.org/v2.21.3/docs/subDays) function to subtract `1` from `Date.now()` and formatted with the given format
+ + +**"tomorrow"**
+`{{ "tomorrow" | date: "yyyy-MM-dd" }}`
+use the [`addDays`](https://date-fns.org/v2.21.3/docs/addDays) function to add `1` to `Date.now()` and formatted with the given format
+ + +### `date` Default format + +Now you can use the "Date Format" you defined inside the plugin settings in a easier way: + +**Before**: `{{ "now" | date: default_date_format }}` + +**Now**: `{{ "now" | date }}` the format you specified in the settings is now used as default + +## `days_ago` filter + +A simple filter that uses the [`subDays`](https://date-fns.org/v2.21.3/docs/subDays) to subtract days from the current date (`Date.now()`) + +Keep in mind that this filter returns a date that needs to be formatted, so needs to be used with the `date` filter, like this: + +- `{{ 1 | days_ago | date }}` same as `{{ "yesterday" | date }}` +- `{{ 2 | days_ago | date }}` return the date from two days ago + +This can be used for a "Weekly Review" template, something like: + +``` +- [[{{ 7 | days_ago | date }}]] +- [[{{ 6 | days_ago | date }}]] +- [[{{ 5 | days_ago | date }}]] +- [[{{ 4 | days_ago | date }}]] +- [[{{ 3 | days_ago | date }}]] +- [[{{ 2 | days_ago | date }}]] +- [[{{ 1 | days_ago | date }}]] +``` + +## `days_after` filter + +A simple filter that uses the [`addDays`](https://date-fns.org/v2.21.3/docs/addDays) to add days to the current date (`Date.now()`) + +Keep in mind that this filter returns a date that needs to be formatted, so needs to be used with the `date` filter, like this: + +- `{{ 1 | days_after | date }}` same as `{{ "tomorrow" | date }}` +- `{{ 2 | days_after | date }}` return the date from two days from now + ## Template context Other than the classic, `date`, `time` and `title` variable you also have: -| Name | Description | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Name | Description | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | default_date_format | The date format that you have specified in the plugin settings and can be used like this: {{ "now" | date: default_date_format }} | | default_time_format | The time format that you have specified in the plugin settings and can be used like this: {{ "now" | date: default_time_format }} | @@ -73,5 +144,5 @@ For now, this plugin just includes the basic version of LiquidJS, but I want to - [ ] Add autocomplete to the template folder, excluded folder settings - [ ] Implement a fuzzy finder for the autocomplete -- [ ] Implement/install a filter that allows you to write `{{ 1 | days_ago | date: default_date_format }}` -- [ ] Parse a selected template string, something like you select `{{ "dQw4w9WgXcQ" | youtube_iframe }}` run a command and end up with the parsed result, in this case, the youtube iframe. (the `youtube_iframe` tag does not exist yet) \ No newline at end of file +- [x] Implement/install a filter that allows you to write `{{ 1 | days_ago | date: default_date_format }}` +- [ ] Parse a selected template string, something like you select `{{ "dQw4w9WgXcQ" | youtube_iframe }}` run a command and end up with the parsed result, in this case, the youtube iframe. (the `youtube_iframe` tag does not exist yet) diff --git a/manifest.json b/manifest.json index 94a3076..cfb757d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "liquid-templates", "name": "Liquid Templates", - "version": "0.1.5", + "version": "0.2.0", "minAppVersion": "0.12.3", "description": "Empower your template with LiquidJS tags", "author": "diomedet", diff --git a/package.json b/package.json index c8d1123..dea2a3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "liquid-templates", - "version": "0.1.5", + "version": "0.2.0", "description": "Empower your template with LiquidJS tags", "main": "main.js", "scripts": { @@ -22,6 +22,7 @@ "typescript": "^4.2.4" }, "dependencies": { + "date-fns": "^2.21.3", "liquidjs": "^9.25.0", "lodash": "^4.17.21" } diff --git a/rollup.config.js b/rollup.config.js index dd4d041..80651fd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -4,7 +4,7 @@ import commonjs from '@rollup/plugin-commonjs'; const isProd = (process.env.BUILD === 'production'); -const banner = +const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ROLLUP if you want to view the source visit the plugins github repository @@ -12,7 +12,7 @@ if you want to view the source visit the plugins github repository `; export default { - input: 'main.ts', + input: 'src/main.ts', output: { dir: '.', sourcemap: 'inline', @@ -27,4 +27,4 @@ export default { nodeResolve({browser: true}), commonjs(), ] -}; \ No newline at end of file +}; diff --git a/src/engine/filters/base_filter.ts b/src/engine/filters/base_filter.ts new file mode 100644 index 0000000..ac0c4aa --- /dev/null +++ b/src/engine/filters/base_filter.ts @@ -0,0 +1,26 @@ +import { Liquid, FilterImplOptions } from 'liquidjs'; + +import LiquidTemplates from '../../main'; + +export interface BaseFilterProps { + engine: Liquid, + plugin: LiquidTemplates, + filterName?: string; +} + +export class BaseFilter { + readonly plugin: LiquidTemplates; + readonly engine: Liquid; + readonly filterName: string; + handler: FilterImplOptions; + + constructor({ plugin, engine, filterName }: BaseFilterProps) { + this.plugin = plugin; + this.engine = engine; + this.filterName = filterName; + } + + register(): void { + this.engine.registerFilter(this.filterName, this.handler); + } +} diff --git a/src/engine/filters/date.ts b/src/engine/filters/date.ts new file mode 100644 index 0000000..a5f1312 --- /dev/null +++ b/src/engine/filters/date.ts @@ -0,0 +1,41 @@ +import { addDays, format, subDays } from 'date-fns'; + +import { BaseFilter, BaseFilterProps } from './base_filter'; + +export default class DateFiler extends BaseFilter { + originalFilter: Function; + + constructor(props: BaseFilterProps) { + super({ ...props, filterName: 'date' }); + this.originalFilter = this.engine.filters.get('date'); + } + + handler = (givenValue: number | string, dateFormat?: string): string | number => { + const dateToFormat = (typeof givenValue === 'number') + ? givenValue + : this.parseDate(givenValue) + const formatToUse = dateFormat || this.plugin.settings.dateFormat; + + // TODO: improve or remove me: keep backwards compatibility with the current 0.1.5 version + if (formatToUse.includes('%')) { + // TODO: send a notification when using this function + return this.originalFilter(givenValue, dateFormat); + } + + try { + return format(dateToFormat, formatToUse) + } catch (e) { + if (!(e instanceof RangeError)) throw e; + } + + return givenValue; + } + + parseDate = (stringToParse: string): Date => { + if(['now', 'today'].includes(stringToParse)) return new Date; + if(stringToParse === 'yesterday') return subDays(Date.now(), 1); + if(stringToParse === 'tomorrow') return addDays(Date.now(), 1); + // try to parse the given string + return new Date(Date.parse(stringToParse)); + } +} diff --git a/src/engine/filters/days_after.ts b/src/engine/filters/days_after.ts new file mode 100644 index 0000000..56e3321 --- /dev/null +++ b/src/engine/filters/days_after.ts @@ -0,0 +1,11 @@ +import { addDays } from 'date-fns'; + +import { BaseFilter, BaseFilterProps } from './base_filter'; + +export default class DaysAfter extends BaseFilter { + constructor(props: BaseFilterProps) { + super({ ...props, filterName: 'days_after' }); + } + + handler = (daysToSub: number): Date => addDays(Date.now(), daysToSub); +} diff --git a/src/engine/filters/days_ago.ts b/src/engine/filters/days_ago.ts new file mode 100644 index 0000000..758e8a7 --- /dev/null +++ b/src/engine/filters/days_ago.ts @@ -0,0 +1,11 @@ +import { subDays } from 'date-fns'; + +import { BaseFilter, BaseFilterProps } from './base_filter'; + +export default class DaysAgo extends BaseFilter { + constructor(props: BaseFilterProps) { + super({ ...props, filterName: 'days_ago' }); + } + + handler = (daysToSub: number): Date => subDays(Date.now(), daysToSub); +} diff --git a/src/engine/index.ts b/src/engine/index.ts new file mode 100644 index 0000000..46cb557 --- /dev/null +++ b/src/engine/index.ts @@ -0,0 +1,48 @@ +import { App } from 'obsidian'; +import { Liquid } from 'liquidjs'; + +import LiquidTemplates from '../main'; +import { getTFilesFromFolder } from 'src/utils'; + +import DateFilter from './filters/date'; +import DaysAgo from './filters/days_ago'; +import DaysAfter from './filters/days_after'; + +export function applyCustomFilters(engine: Liquid, plugin: LiquidTemplates) { + [ + DateFilter, + DaysAgo, + DaysAfter + ].forEach(filter => new filter({ plugin, engine }).register()) +} + +export function initEngine(app: App, plugin: LiquidTemplates): Liquid { + const engine = new Liquid({ + fs: { + readFileSync: (file) => { + // TODO: implement me + return ""; + }, + readFile: async (file) => { + const [fileName, ...folder] = file.split('/').reverse() + const folderToCheck = [plugin.settings.templatesFolder, ...folder.reverse()].join('/') + // TODO: find a better way to do this + const fileObj = getTFilesFromFolder(app, folderToCheck).find(f => f.basename === fileName); + return app.vault.read(fileObj); + }, + existsSync: () => { + return true + }, + exists: async () => { + return true + }, + resolve: (_root, file, _ext) => { + return file + } + }, + extname: '.md', + greedy: true, + }); + applyCustomFilters(engine, plugin); + return engine; +} diff --git a/main.ts b/src/main.ts similarity index 56% rename from main.ts rename to src/main.ts index 2dbfada..f909dbd 100644 --- a/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ +import { get, isEmpty } from "lodash"; import { Plugin } from "obsidian"; import { LiquidTemplatesSettings, DEFAULT_SETTINGS, LiquidTemplatesSettingsTab } from "./settings"; @@ -40,11 +41,29 @@ export default class LiquidTemplates extends Plugin { }; async loadSettings(): Promise { - this.settings = Object.assign({ ...DEFAULT_SETTINGS }, await this.loadData()); + this.settings = this.settingsWithDefault(await this.loadData()); } async saveSettings(): Promise { + // the templates folder can change this.setupAutosuggest(); await this.saveData(this.settings); } + + settingsWithDefault(savedSettings: LiquidTemplatesSettings): LiquidTemplatesSettings { + // TODO: improve me + return { + templatesFolder: this.getSettingOrDefault(savedSettings, 'templatesFolder'), + excludeFolders: this.getSettingOrDefault(savedSettings, 'excludeFolders'), + dateFormat: this.getSettingOrDefault(savedSettings, 'dateFormat'), + timeFormat: this.getSettingOrDefault(savedSettings, 'timeFormat'), + autocompleteTrigger: this.getSettingOrDefault(savedSettings, 'autocompleteTrigger'), + } + } + + getSettingOrDefault(savedSettings: LiquidTemplatesSettings, settingKey: string, defaultValue?: any): any { + let currentValue = get(savedSettings, settingKey) + if (!isEmpty(currentValue)) return currentValue; + return get(DEFAULT_SETTINGS, settingKey, defaultValue); + } } diff --git a/settings.ts b/src/settings.ts similarity index 72% rename from settings.ts rename to src/settings.ts index 24a641b..5a0a2d5 100644 --- a/settings.ts +++ b/src/settings.ts @@ -1,6 +1,6 @@ -import { get, isEmpty } from "lodash"; import { App, PluginSettingTab, Setting } from "obsidian"; import LiquidTemplates from "./main"; +import { format } from 'date-fns'; export interface LiquidTemplatesSettings { templatesFolder: string; @@ -13,8 +13,8 @@ export interface LiquidTemplatesSettings { export const DEFAULT_SETTINGS: LiquidTemplatesSettings = { templatesFolder: 'templates', excludeFolders: "", - dateFormat: '%Y-%m-%d', - timeFormat: '%H:%M', + dateFormat: 'yyyy-MM-dd', + timeFormat: 'k:m', autocompleteTrigger: ';;' }; @@ -60,9 +60,26 @@ export class LiquidTemplatesSettingsTab extends PluginSettingTab { }) ); + const dateFormatDesc = document.createDocumentFragment(); + dateFormatDesc.append( + 'Output format for render dates.', + dateFormatDesc.createEl('br'), + 'For more syntax, refer to ', + dateFormatDesc.createEl("a", { + href: "https://date-fns.org/v2.21.3/docs/format", + text: "format reference", + }), + dateFormatDesc.createEl('br'), + 'current format: ', + dateFormatDesc.createEl('b', { + cls: 'u-pop', + text: format(new Date, this.plugin.settings.dateFormat) + }) + ); + new Setting(containerEl) .setName('Date format') - .setDesc('Output format for render dates') + .setDesc(dateFormatDesc) .addText((text) => text .setPlaceholder(DEFAULT_SETTINGS.dateFormat) @@ -73,9 +90,27 @@ export class LiquidTemplatesSettingsTab extends PluginSettingTab { }) ); + + const timeFormatDesc = document.createDocumentFragment(); + timeFormatDesc.append( + 'Output format for render time.', + timeFormatDesc.createEl('br'), + 'For more syntax, refer to ', + timeFormatDesc.createEl("a", { + href: "https://date-fns.org/v2.21.3/docs/format", + text: "format reference", + }), + timeFormatDesc.createEl('br'), + 'current format: ', + timeFormatDesc.createEl('b', { + cls: 'u-pop', + text: format(new Date, this.plugin.settings.timeFormat) + }) + ); + new Setting(containerEl) .setName('Time format') - .setDesc('Output format for render time') + .setDesc(timeFormatDesc) .addText((text) => text .setPlaceholder(DEFAULT_SETTINGS.timeFormat) diff --git a/suggest/codemirror-suggest.ts b/src/suggest/codemirror-suggest.ts similarity index 100% rename from suggest/codemirror-suggest.ts rename to src/suggest/codemirror-suggest.ts diff --git a/suggest/suggest.ts b/src/suggest/suggest.ts similarity index 100% rename from suggest/suggest.ts rename to src/suggest/suggest.ts diff --git a/suggest/template-suggest.ts b/src/suggest/template-suggest.ts similarity index 64% rename from suggest/template-suggest.ts rename to src/suggest/template-suggest.ts index ff9a0c1..423638d 100644 --- a/suggest/template-suggest.ts +++ b/src/suggest/template-suggest.ts @@ -1,8 +1,11 @@ import { App, TFile } from "obsidian"; import { Liquid } from "liquidjs"; -import type PoweredTemplates from "../main"; +import { format } from "date-fns"; + import CodeMirrorSuggest from "./codemirror-suggest"; -import { getTFilesFromFolder } from "utils"; +import type PoweredTemplates from "../main"; +import { getTFilesFromFolder } from "../utils"; +import { initEngine } from "src/engine"; interface ITemplateCompletion { label: string; @@ -20,34 +23,7 @@ export default class TemplateSuggest extends CodeMirrorSuggest { - // TODO: implement me - return ""; - }, - readFile: async (file) => { - const [fileName, ...folder] = file.split('/').reverse() - const { templatesFolder } = this.plugin.settings; - const folderToCheck = [templatesFolder, ...folder.reverse()].join('/') - // TODO: find a better way to do this - const fileObj = getTFilesFromFolder(app, folderToCheck).find(f => f.basename === fileName); - return app.vault.read(fileObj); - }, - existsSync: () => { - return true - }, - exists: async () => { - return true - }, - resolve: (_root, file, _ext) => { - return file - } - }, - extname: '.md', - greedy: true, - }); + this.engine = initEngine(app, plugin); } open(): void { @@ -83,13 +59,18 @@ export default class TemplateSuggest extends CodeMirrorSuggest