diff --git a/.eslintrc b/.eslintrc
index 734c3f3..bab550a 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -5,11 +5,11 @@
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
- "plugin:@typescript-eslint/eslint-recommended",
- "plugin:@typescript-eslint/recommended"
+ "plugin:@typescript-eslint/recommended",
+ "plugin:prettier/recommended"
],
"parserOptions": {
- "sourceType": "module"
+ "project": "./tsconfig.json"
},
"rules": {
"no-unused-vars": "off",
diff --git a/.prettierrc b/.prettierrc
index 1ccc975..6467b11 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,4 +1,5 @@
{
"semi": false,
- "singleQuote": true
+ "singleQuote": true,
+ "endOfLine": "auto"
}
diff --git a/package.json b/package.json
index 8497187..fea74bc 100644
--- a/package.json
+++ b/package.json
@@ -37,11 +37,13 @@
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"eslint": "^8.53.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.3",
"jest": "^29.4.3",
"jest-cli": "^29.4.3",
"jest-junit-reporter": "^1.1.0",
"obsidian": "^1.2.8",
- "prettier": "^2.8.1",
+ "prettier": "^3.2.5",
"semantic-release": "^19.0.5",
"ts-jest": "^29.0.5",
"tslib": "2.4.0",
diff --git a/src/__tests__/formatDate.spec.ts b/src/__tests__/formatDate.spec.ts
index 22525aa..10e6978 100644
--- a/src/__tests__/formatDate.spec.ts
+++ b/src/__tests__/formatDate.spec.ts
@@ -75,8 +75,8 @@ function generateRandomISODateStrings(quantity: number): string[] {
Math.floor(Math.random() * 24),
Math.floor(Math.random() * 60),
Math.floor(Math.random() * 60),
- Math.floor(Math.random() * 1000)
- )
+ Math.floor(Math.random() * 1000),
+ ),
)
// Randomly select a timezone from the available time zones
@@ -86,7 +86,7 @@ function generateRandomISODateStrings(quantity: number): string[] {
// Convert the generated date to the randomly selected timezone
// const dateTimeWithZone = DateTime.fromJSDate(date, { zone: randomTimeZone }).toUTC();
const jsDateTimeWithZone = new Date(
- date.toLocaleString('en-US', { timeZone: randomTimeZone })
+ date.toLocaleString('en-US', { timeZone: randomTimeZone }),
)
const luxonDate = DateTime.fromJSDate(jsDateTimeWithZone)
randomISODateStrings.push(luxonDate.toISO() as string)
@@ -102,13 +102,13 @@ describe('formatDate on random dates', () => {
const result = formatDate(date, 'yyyy-MM-dd HH:mm:ss')
// test with regex to ensure the format is correct
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)
- }
+ },
)
})
function getCasesWithRandomDates(
testFormats: string[],
- quantity = 10
+ quantity = 10,
): {
date: string
luxonFormat: string
@@ -117,7 +117,7 @@ function getCasesWithRandomDates(
generateRandomISODateStrings(quantity).map((date) => ({
date,
luxonFormat,
- }))
+ })),
)
}
@@ -135,7 +135,7 @@ describe('round trip on random dates', () => {
const result = formatDate(testCase.date, testCase.luxonFormat)
const result2 = formatDate(result, testCase.luxonFormat)
expect(result2).toBe(result)
- }
+ },
)
const atypicalFormats = [
@@ -148,9 +148,9 @@ describe('round trip on random dates', () => {
const formattedDate = formatDate(testCase.date, testCase.luxonFormat)
const parsedDate = DateTime.fromFormat(
formattedDate,
- testCase.luxonFormat
+ testCase.luxonFormat,
)
expect(parsedDate.isValid).toBe(true)
- }
+ },
)
})
diff --git a/src/__tests__/path_validation.spec.ts b/src/__tests__/path_validation.spec.ts
index 1454553..d5a3067 100644
--- a/src/__tests__/path_validation.spec.ts
+++ b/src/__tests__/path_validation.spec.ts
@@ -29,7 +29,7 @@ describe('replaceIllegalChars() removes all expected characters', () => {
const input = `this${character}string`
const output = replaceIllegalChars(input)
expect(output).not.toContain(character)
- }
+ },
)
})
@@ -41,7 +41,7 @@ describe('replaceIllegalChars() function replaces illegal characters with replac
const expectedOutput = `this${REPLACEMENT_CHAR}string`
const output = replaceIllegalChars(input)
expect(output).toEqual(expectedOutput)
- }
+ },
)
})
@@ -51,7 +51,7 @@ describe('replaceIllegalChars() function does not modify string without illegal
(input) => {
const output = replaceIllegalChars(input)
expect(output).toEqual(input)
- }
+ },
)
})
@@ -72,14 +72,14 @@ describe('replaceIllegalChars() function replaces all occurrences of illegal cha
const output = replaceIllegalChars(input)
expect(output).toEqual(expectedOutput)
expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull()
- }
+ },
)
})
describe('file system behavior with non-alphanumeric characters not in the illegal character list', () => {
const nonAlphanumericCharactersWithoutIllegal: string[] = Array.from(
{ length: 127 - 32 },
- (_, i) => String.fromCharCode(i + 32)
+ (_, i) => String.fromCharCode(i + 32),
)
.filter((char) => !/^[a-zA-Z0-9]+$/.test(char))
.map(replaceIllegalChars)
@@ -97,7 +97,7 @@ describe('file system behavior with non-alphanumeric characters not in the illeg
fs.unlinkSync(input)
// verify the file has been deleted
expect(fs.existsSync(input)).toBe(false)
- }
+ },
)
})
@@ -110,6 +110,6 @@ describe('replaceIllegalChars() function removes all occurrences of invisible ch
const output = replaceIllegalChars(input)
expect(output).toEqual(expectedOutput)
expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull()
- }
+ },
)
})
diff --git a/src/api.ts b/src/api.ts
index 9669ed6..894b938 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -95,7 +95,7 @@ export const loadArticles = async (
updatedAt = '',
query = '',
includeContent = false,
- format = 'html'
+ format = 'html',
): Promise<[Article[], boolean]> => {
const res = await requestUrl({
url: endpoint,
@@ -177,7 +177,7 @@ export const loadArticles = async (
export const deleteArticleById = async (
endpoint: string,
apiKey: string,
- articleId: string
+ articleId: string,
) => {
const res = await requestUrl({
url: endpoint,
diff --git a/src/main.ts b/src/main.ts
index 4ca6b50..4630854 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,4 +1,4 @@
-import { DateTime } from "luxon"
+import { DateTime } from 'luxon'
import {
addIcon,
App,
@@ -11,16 +11,16 @@ import {
stringifyYaml,
TFile,
TFolder,
-} from "obsidian"
-import { Article, deleteArticleById, loadArticles, PageType } from "./api"
+} from 'obsidian'
+import { Article, deleteArticleById, loadArticles, PageType } from './api'
import {
DEFAULT_SETTINGS,
Filter,
FRONT_MATTER_VARIABLES,
HighlightOrder,
OmnivoreSettings,
-} from "./settings"
-import { FolderSuggest } from "./settings/file-suggest"
+} from './settings'
+import { FolderSuggest } from './settings/file-suggest'
import {
preParseTemplate,
render,
@@ -49,7 +49,7 @@ export default class OmnivorePlugin extends Plugin {
const currentVersion = this.settings.version
if (latestVersion !== currentVersion) {
this.settings.version = latestVersion
- this.saveSettings()
+ await this.saveSettings()
// show release notes
const releaseNotes = `Omnivore plugin is upgraded to ${latestVersion}.
@@ -61,27 +61,27 @@ export default class OmnivorePlugin extends Plugin {
this.addCommand({
id: 'sync',
name: 'Sync new changes',
- callback: () => {
- this.fetchOmnivore()
+ callback: async () => {
+ await this.fetchOmnivore()
},
})
this.addCommand({
id: 'deleteArticle',
name: 'Delete Current Article from Omnivore',
- callback: () => {
- this.deleteCurrentArticle(this.app.workspace.getActiveFile())
+ callback: async () => {
+ await this.deleteCurrentArticle(this.app.workspace.getActiveFile())
},
})
this.addCommand({
id: 'resync',
name: 'Resync all articles',
- callback: () => {
+ callback: async () => {
this.settings.syncAt = ''
- this.saveSettings()
+ await this.saveSettings()
new Notice('Omnivore Last Sync reset')
- this.fetchOmnivore()
+ await this.fetchOmnivore()
},
})
@@ -89,7 +89,7 @@ export default class OmnivorePlugin extends Plugin {
// add icon
addIcon(
iconId,
- ``
+ ``,
)
// This creates an icon in the left ribbon.
@@ -103,8 +103,8 @@ export default class OmnivorePlugin extends Plugin {
this.scheduleSync()
- // sync when the app is loaded if frequency is greater than zero
- if (this.settings.frequency > 0) {
+ // sync when the app is loaded if syncOnStart is true
+ if (this.settings.syncOnStart) {
await this.fetchOmnivore()
}
}
@@ -113,13 +113,38 @@ export default class OmnivorePlugin extends Plugin {
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
+
+ // for backward compatibility, replace advanced filter with all filter
+ if (this.settings.filter === 'ADVANCED') {
+ this.settings.filter = 'ALL'
+ console.log(
+ 'obsidian-omnivore: advanced filter is replaced with all filter',
+ )
+ const customQuery = this.settings.customQuery
+ this.settings.customQuery = `in:all ${
+ customQuery ? `(${customQuery})` : ''
+ }`
+ console.log(
+ `obsidian-omnivore: custom query is set to ${this.settings.customQuery}`,
+ )
+ this.saveSettings()
+ }
+
+ // for backward compatibility, set custom query from filter
+ if (!this.settings.customQuery) {
+ this.settings.customQuery = getQueryFromFilter(this.settings.filter)
+ console.log(
+ `obsidian-omnivore: custom query is set to ${this.settings.customQuery}`,
+ )
+ this.saveSettings()
+ }
}
async saveSettings() {
await this.saveData(this.settings)
}
- scheduleSync() {
+ async scheduleSync() {
// clear previous interval
if (this.settings.intervalId > 0) {
window.clearInterval(this.settings.intervalId)
@@ -127,12 +152,15 @@ export default class OmnivorePlugin extends Plugin {
const frequency = this.settings.frequency
if (frequency > 0) {
// schedule new interval
- const intervalId = window.setInterval(async () => {
- await this.fetchOmnivore(false)
- }, frequency * 60 * 1000)
+ const intervalId = window.setInterval(
+ async () => {
+ await this.fetchOmnivore(false)
+ },
+ frequency * 60 * 1000,
+ )
// save new interval id
this.settings.intervalId = intervalId
- this.saveSettings()
+ await this.saveSettings()
// clear interval when plugin is unloaded
this.registerInterval(intervalId)
}
@@ -149,8 +177,8 @@ export default class OmnivorePlugin extends Plugin {
render(
article,
this.settings.attachmentFolder,
- this.settings.folderDateFormat
- )
+ this.settings.folderDateFormat,
+ ),
)
const folder = app.vault.getAbstractFileByPath(folderName)
if (!(folder instanceof TFolder)) {
@@ -161,7 +189,7 @@ export default class OmnivorePlugin extends Plugin {
if (!(file instanceof TFile)) {
const newFile = await app.vault.createBinary(
fileName,
- response.arrayBuffer
+ response.arrayBuffer,
)
return newFile.path
}
@@ -172,7 +200,6 @@ export default class OmnivorePlugin extends Plugin {
const {
syncAt,
apiKey,
- filter,
customQuery,
highlightOrder,
syncing,
@@ -207,10 +234,10 @@ export default class OmnivorePlugin extends Plugin {
const templateSpans = preParseTemplate(template)
// check if we need to include content or file attachment
const includeContent = templateSpans.some(
- (templateSpan) => templateSpan[1] === 'content'
+ (templateSpan) => templateSpan[1] === 'content',
)
const includeFileAttachment = templateSpans.some(
- (templateSpan) => templateSpan[1] === 'fileAttachment'
+ (templateSpan) => templateSpan[1] === 'fileAttachment',
)
const size = 50
@@ -225,14 +252,14 @@ export default class OmnivorePlugin extends Plugin {
after,
size,
parseDateTime(syncAt).toISO() || undefined,
- getQueryFromFilter(filter, customQuery),
+ customQuery,
includeContent,
- 'highlightedMarkdown'
+ 'highlightedMarkdown',
)
for (const article of articles) {
const folderName = normalizePath(
- render(article, folder, this.settings.folderDateFormat)
+ render(article, folder, this.settings.folderDateFormat),
)
const omnivoreFolder =
this.app.vault.getAbstractFileByPath(folderName)
@@ -252,11 +279,11 @@ export default class OmnivorePlugin extends Plugin {
isSingleFile,
frontMatterVariables,
frontMatterTemplate,
- fileAttachment
+ fileAttachment,
)
// use the custom filename
const customFilename = replaceIllegalChars(
- renderFilename(article, filename, this.settings.filenameDateFormat)
+ renderFilename(article, filename, this.settings.filenameDateFormat),
)
const pageName = `${folderName}/${customFilename}.md`
const normalizedPath = normalizePath(pageName)
@@ -292,7 +319,7 @@ export default class OmnivorePlugin extends Plugin {
// find the front matter with the same id
const frontMatterIdx = findFrontMatterIndex(
existingFrontMatter,
- article.id
+ article.id,
)
if (frontMatterIdx >= 0) {
// this article already exists in the file
@@ -302,12 +329,12 @@ export default class OmnivorePlugin extends Plugin {
const sectionEnd = `%%${article.id}_end%%`
const existingContentRegex = new RegExp(
`${sectionStart}.*?${sectionEnd}`,
- 's'
+ 's',
)
newContentWithoutFrontMatter =
existingContentWithoutFrontmatter.replace(
existingContentRegex,
- contentWithoutFrontmatter
+ contentWithoutFrontmatter,
)
existingFrontMatter[frontMatterIdx] = newFrontMatter[0]
@@ -320,12 +347,12 @@ export default class OmnivorePlugin extends Plugin {
}
const newFrontMatterStr = `---\n${stringifyYaml(
- existingFrontMatter
+ existingFrontMatter,
)}---`
await this.app.vault.modify(
omnivoreFile,
- `${newFrontMatterStr}\n\n${newContentWithoutFrontMatter}`
+ `${newFrontMatterStr}\n\n${newContentWithoutFrontMatter}`,
)
continue
}
@@ -342,9 +369,8 @@ export default class OmnivorePlugin extends Plugin {
this.app.vault.getAbstractFileByPath(newNormalizedPath)
if (newOmnivoreFile instanceof TFile) {
// a file with the same name and id already exists, so we need to update it
- const existingContent = await this.app.vault.read(
- newOmnivoreFile
- )
+ const existingContent =
+ await this.app.vault.read(newOmnivoreFile)
if (existingContent !== content) {
await this.app.vault.modify(newOmnivoreFile, content)
}
@@ -359,7 +385,7 @@ export default class OmnivorePlugin extends Plugin {
if (existingContent !== content) {
await this.app.vault.modify(omnivoreFile, content)
}
- }
+ },
)
continue
}
@@ -369,7 +395,7 @@ export default class OmnivorePlugin extends Plugin {
} catch (error) {
if (error.toString().includes('File already exists')) {
new Notice(
- `Skipping file creation: ${normalizedPath}. Please check if you have duplicated article titles and delete the file if needed.`
+ `Skipping file creation: ${normalizedPath}. Please check if you have duplicated article titles and delete the file if needed.`,
)
} else {
throw error
@@ -403,7 +429,7 @@ export default class OmnivorePlugin extends Plugin {
const isDeleted = deleteArticleById(
this.settings.endpoint,
this.settings.apiKey,
- articleId
+ articleId,
)
if (!isDeleted) {
new Notice('Failed to delete article in Omnivore')
@@ -436,90 +462,95 @@ class OmnivoreSettingTab extends PluginSettingTab {
containerEl.empty()
- containerEl.createEl("h2", { text: "Settings for Omnivore plugin" })
+ containerEl.createEl('h2', { text: 'Settings for Omnivore plugin' })
new Setting(containerEl)
- .setName("API Key")
+ .setName('API Key')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "You can create an API key at ",
- fragment.createEl("a", {
- text: "https://omnivore.app/settings/api",
- href: "https://omnivore.app/settings/api",
- })
+ 'You can create an API key at ',
+ fragment.createEl('a', {
+ text: 'https://omnivore.app/settings/api',
+ href: 'https://omnivore.app/settings/api',
+ }),
)
- })
+ }),
)
.addText((text) =>
text
- .setPlaceholder("Enter your Omnivore Api Key")
+ .setPlaceholder('Enter your Omnivore Api Key')
.setValue(this.plugin.settings.apiKey)
.onChange(async (value) => {
this.plugin.settings.apiKey = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Filter")
- .setDesc("Select an Omnivore search filter type. Changing this would reset the 'Last sync' timestamp")
+ .setName('Filter')
+ .setDesc(
+ "Select an Omnivore search filter type. Changing this would update the 'Custom Query' accordingly and reset the 'Last sync' timestamp",
+ )
.addDropdown((dropdown) => {
dropdown.addOptions(Filter)
dropdown
.setValue(this.plugin.settings.filter)
.onChange(async (value) => {
this.plugin.settings.filter = value
- this.plugin.settings.syncAt = ""
+ this.plugin.settings.customQuery = getQueryFromFilter(value)
+ this.plugin.settings.syncAt = ''
await this.plugin.saveSettings()
+ this.display()
})
})
new Setting(containerEl)
- .setName("Custom query")
+ .setName('Custom Query')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "See ",
- fragment.createEl("a", {
- text: "https://docs.omnivore.app/using/search",
- href: "https://docs.omnivore.app/using/search",
+ 'See ',
+ fragment.createEl('a', {
+ text: 'https://docs.omnivore.app/using/search',
+ href: 'https://docs.omnivore.app/using/search',
}),
- " for more info on search query syntax. Make sure your Filter (in the section above) is set to advanced when using a custom query.",
- " Changing this would reset the 'Last Sync' timestamp"
+ " for more info on search query syntax. Changing this would reset the 'Last Sync' timestamp",
)
- })
+ }),
)
.addText((text) =>
text
.setPlaceholder(
- "Enter an Omnivore custom search query if advanced filter is selected"
+ 'Enter an Omnivore custom search query if advanced filter is selected',
)
.setValue(this.plugin.settings.customQuery)
.onChange(async (value) => {
this.plugin.settings.customQuery = value
- this.plugin.settings.syncAt = ""
+ this.plugin.settings.syncAt = ''
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Last Sync")
- .setDesc("Last time the plugin synced with Omnivore. The 'Sync' command fetches articles updated after this timestamp")
+ .setName('Last Sync')
+ .setDesc(
+ "Last time the plugin synced with Omnivore. The 'Sync' command fetches articles updated after this timestamp",
+ )
.addMomentFormat((momentFormat) =>
momentFormat
- .setPlaceholder("Last Sync")
+ .setPlaceholder('Last Sync')
.setValue(this.plugin.settings.syncAt)
.setDefaultFormat("yyyy-MM-dd'T'HH:mm:ss")
.onChange(async (value) => {
this.plugin.settings.syncAt = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Highlight Order")
- .setDesc("Select the order in which highlights are applied")
+ .setName('Highlight Order')
+ .setDesc('Select the order in which highlights are applied')
.addDropdown((dropdown) => {
dropdown.addOptions(HighlightOrder)
dropdown
@@ -531,63 +562,63 @@ class OmnivoreSettingTab extends PluginSettingTab {
})
new Setting(containerEl)
- .setName("Front Matter")
+ .setName('Front Matter')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "Enter the metadata to be used in your note separated by commas. You can also use custom aliases in the format of metatdata::alias, e.g. date_saved::date. ",
- fragment.createEl("br"),
- fragment.createEl("br"),
- "Available metadata can be found at ",
- fragment.createEl("a", {
- text: "Reference",
- href: "https://docs.omnivore.app/integrations/obsidian.html#front-matter",
+ 'Enter the metadata to be used in your note separated by commas. You can also use custom aliases in the format of metatdata::alias, e.g. date_saved::date. ',
+ fragment.createEl('br'),
+ fragment.createEl('br'),
+ 'Available metadata can be found at ',
+ fragment.createEl('a', {
+ text: 'Reference',
+ href: 'https://docs.omnivore.app/integrations/obsidian.html#front-matter',
}),
- fragment.createEl("br"),
- fragment.createEl("br"),
- "If you want to use a custom front matter template, you can enter it below under the advanced settings"
+ fragment.createEl('br'),
+ fragment.createEl('br'),
+ 'If you want to use a custom front matter template, you can enter it below under the advanced settings',
)
- })
+ }),
)
.addTextArea((text) => {
text
- .setPlaceholder("Enter the metadata")
- .setValue(this.plugin.settings.frontMatterVariables.join(","))
+ .setPlaceholder('Enter the metadata')
+ .setValue(this.plugin.settings.frontMatterVariables.join(','))
.onChange(async (value) => {
// validate front matter variables and deduplicate
this.plugin.settings.frontMatterVariables = value
- .split(",")
+ .split(',')
.map((v) => v.trim())
.filter(
(v, i, a) =>
- FRONT_MATTER_VARIABLES.includes(v.split("::")[0]) &&
- a.indexOf(v) === i
+ FRONT_MATTER_VARIABLES.includes(v.split('::')[0]) &&
+ a.indexOf(v) === i,
)
await this.plugin.saveSettings()
})
- text.inputEl.setAttr("rows", 4)
- text.inputEl.setAttr("cols", 30)
+ text.inputEl.setAttr('rows', 4)
+ text.inputEl.setAttr('cols', 30)
})
new Setting(containerEl)
- .setName("Article Template")
+ .setName('Article Template')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "Enter template to render articles with ",
- fragment.createEl("a", {
- text: "Reference",
- href: "https://docs.omnivore.app/integrations/obsidian.html#controlling-the-layout-of-the-data-imported-to-obsidian",
+ 'Enter template to render articles with ',
+ fragment.createEl('a', {
+ text: 'Reference',
+ href: 'https://docs.omnivore.app/integrations/obsidian.html#controlling-the-layout-of-the-data-imported-to-obsidian',
}),
- fragment.createEl("br"),
- fragment.createEl("br"),
- "If you want to use a custom front matter template, you can enter it below under the advanced settings"
+ fragment.createEl('br'),
+ fragment.createEl('br'),
+ 'If you want to use a custom front matter template, you can enter it below under the advanced settings',
)
- })
+ }),
)
.addTextArea((text) => {
text
- .setPlaceholder("Enter the template")
+ .setPlaceholder('Enter the template')
.setValue(this.plugin.settings.template)
.onChange(async (value) => {
// if template is empty, use default template
@@ -596,36 +627,50 @@ class OmnivoreSettingTab extends PluginSettingTab {
: DEFAULT_SETTINGS.template
await this.plugin.saveSettings()
})
- text.inputEl.setAttr("rows", 25)
- text.inputEl.setAttr("cols", 50)
+ text.inputEl.setAttr('rows', 25)
+ text.inputEl.setAttr('cols', 50)
})
.addExtraButton((button) => {
// add a button to reset template
button
- .setIcon("reset")
- .setTooltip("Reset template")
+ .setIcon('reset')
+ .setTooltip('Reset template')
.onClick(async () => {
this.plugin.settings.template = DEFAULT_SETTINGS.template
await this.plugin.saveSettings()
this.display()
- new Notice("Template reset")
+ new Notice('Template reset')
})
})
new Setting(containerEl)
- .setName("Frequency")
+ .setName('Sync on Start')
+ .setDesc(
+ 'Check this box if you want to sync with Omnivore when the app is loaded',
+ )
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.syncOnStart)
+ .onChange(async (value) => {
+ this.plugin.settings.syncOnStart = value
+ await this.plugin.saveSettings()
+ }),
+ )
+
+ new Setting(containerEl)
+ .setName('Frequency')
.setDesc(
- "Enter the frequency in minutes to sync with Omnivore automatically. 0 means manual sync"
+ 'Enter the frequency in minutes to sync with Omnivore automatically. 0 means manual sync',
)
.addText((text) =>
text
- .setPlaceholder("Enter the frequency")
+ .setPlaceholder('Enter the frequency')
.setValue(this.plugin.settings.frequency.toString())
.onChange(async (value) => {
// validate frequency
const frequency = parseInt(value)
if (isNaN(frequency)) {
- new Notice("Frequency must be a positive integer")
+ new Notice('Frequency must be a positive integer')
return
}
// save frequency
@@ -633,18 +678,18 @@ class OmnivoreSettingTab extends PluginSettingTab {
await this.plugin.saveSettings()
this.plugin.scheduleSync()
- })
+ }),
)
new Setting(containerEl)
- .setName("Folder")
+ .setName('Folder')
.setDesc(
- "Enter the folder where the data will be stored. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name"
+ 'Enter the folder where the data will be stored. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name',
)
.addSearch((search) => {
new FolderSuggest(this.app, search.inputEl)
search
- .setPlaceholder("Enter the folder")
+ .setPlaceholder('Enter the folder')
.setValue(this.plugin.settings.folder)
.onChange(async (value) => {
this.plugin.settings.folder = value
@@ -652,14 +697,14 @@ class OmnivoreSettingTab extends PluginSettingTab {
})
})
new Setting(containerEl)
- .setName("Attachment Folder")
+ .setName('Attachment Folder')
.setDesc(
- "Enter the folder where the attachment will be downloaded to. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name"
+ 'Enter the folder where the attachment will be downloaded to. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name',
)
.addSearch((search) => {
new FolderSuggest(this.app, search.inputEl)
search
- .setPlaceholder("Enter the attachment folder")
+ .setPlaceholder('Enter the attachment folder')
.setValue(this.plugin.settings.attachmentFolder)
.onChange(async (value) => {
this.plugin.settings.attachmentFolder = value
@@ -668,9 +713,9 @@ class OmnivoreSettingTab extends PluginSettingTab {
})
new Setting(containerEl)
- .setName("Is Single File")
+ .setName('Is Single File')
.setDesc(
- "Check this box if you want to save all articles in a single file"
+ 'Check this box if you want to save all articles in a single file',
)
.addToggle((toggle) =>
toggle
@@ -678,73 +723,73 @@ class OmnivoreSettingTab extends PluginSettingTab {
.onChange(async (value) => {
this.plugin.settings.isSingleFile = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Filename")
+ .setName('Filename')
.setDesc(
- "Enter the filename where the data will be stored. {{id}}, {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the filename"
+ 'Enter the filename where the data will be stored. {{id}}, {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the filename',
)
.addText((text) =>
text
- .setPlaceholder("Enter the filename")
+ .setPlaceholder('Enter the filename')
.setValue(this.plugin.settings.filename)
.onChange(async (value) => {
this.plugin.settings.filename = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Filename Date Format")
+ .setName('Filename Date Format')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "Enter the format date for use in rendered filename. Format ",
- fragment.createEl("a", {
- text: "reference",
- href: "https://moment.github.io/luxon/#/formatting?id=table-of-tokens",
- })
+ 'Enter the format date for use in rendered filename. Format ',
+ fragment.createEl('a', {
+ text: 'reference',
+ href: 'https://moment.github.io/luxon/#/formatting?id=table-of-tokens',
+ }),
)
- })
+ }),
)
.addText((text) =>
text
- .setPlaceholder("yyyy-MM-dd")
+ .setPlaceholder('yyyy-MM-dd')
.setValue(this.plugin.settings.filenameDateFormat)
.onChange(async (value) => {
this.plugin.settings.filenameDateFormat = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Folder Date Format")
+ .setName('Folder Date Format')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "Enter the format date for use in rendered folder name. Format ",
- fragment.createEl("a", {
- text: "reference",
- href: "https://moment.github.io/luxon/#/formatting?id=table-of-tokens",
- })
+ 'Enter the format date for use in rendered folder name. Format ',
+ fragment.createEl('a', {
+ text: 'reference',
+ href: 'https://moment.github.io/luxon/#/formatting?id=table-of-tokens',
+ }),
)
- })
+ }),
)
.addText((text) =>
text
- .setPlaceholder("yyyy-MM-dd")
+ .setPlaceholder('yyyy-MM-dd')
.setValue(this.plugin.settings.folderDateFormat)
.onChange(async (value) => {
this.plugin.settings.folderDateFormat = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Date Saved Format")
+ .setName('Date Saved Format')
.setDesc(
- "Enter the date format for dateSaved variable in rendered template"
+ 'Enter the date format for dateSaved variable in rendered template',
)
.addText((text) =>
text
@@ -753,105 +798,105 @@ class OmnivoreSettingTab extends PluginSettingTab {
.onChange(async (value) => {
this.plugin.settings.dateSavedFormat = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(containerEl)
- .setName("Date Highlighted Format")
+ .setName('Date Highlighted Format')
.setDesc(
- "Enter the date format for dateHighlighted variable in rendered template"
+ 'Enter the date format for dateHighlighted variable in rendered template',
)
.addText((text) =>
text
- .setPlaceholder("Date Highlighted Format")
+ .setPlaceholder('Date Highlighted Format')
.setValue(this.plugin.settings.dateHighlightedFormat)
.onChange(async (value) => {
this.plugin.settings.dateHighlightedFormat = value
await this.plugin.saveSettings()
- })
+ }),
)
- containerEl.createEl("h5", {
- cls: "omnivore-collapsible",
- text: "Advanced Settings",
+ containerEl.createEl('h5', {
+ cls: 'omnivore-collapsible',
+ text: 'Advanced Settings',
})
- const advancedSettings = containerEl.createEl("div", {
- cls: "omnivore-content",
+ const advancedSettings = containerEl.createEl('div', {
+ cls: 'omnivore-content',
})
new Setting(advancedSettings)
- .setName("API Endpoint")
+ .setName('API Endpoint')
.setDesc("Enter the Omnivore server's API endpoint")
.addText((text) =>
text
- .setPlaceholder("API endpoint")
+ .setPlaceholder('API endpoint')
.setValue(this.plugin.settings.endpoint)
.onChange(async (value) => {
this.plugin.settings.endpoint = value
await this.plugin.saveSettings()
- })
+ }),
)
new Setting(advancedSettings)
- .setName("Front Matter Template")
+ .setName('Front Matter Template')
.setDesc(
createFragment((fragment) => {
fragment.append(
- "Enter YAML template to render the front matter with ",
- fragment.createEl("a", {
- text: "Reference",
- href: "https://docs.omnivore.app/integrations/obsidian.html#front-matter-template",
+ 'Enter YAML template to render the front matter with ',
+ fragment.createEl('a', {
+ text: 'Reference',
+ href: 'https://docs.omnivore.app/integrations/obsidian.html#front-matter-template',
}),
- fragment.createEl("br"),
- fragment.createEl("br"),
- "We recommend you to use Front Matter section under the basic settings to define the metadata.",
- fragment.createEl("br"),
- fragment.createEl("br"),
- "If this template is set, it will override the Front Matter so please make sure your template is a valid YAML."
+ fragment.createEl('br'),
+ fragment.createEl('br'),
+ 'We recommend you to use Front Matter section under the basic settings to define the metadata.',
+ fragment.createEl('br'),
+ fragment.createEl('br'),
+ 'If this template is set, it will override the Front Matter so please make sure your template is a valid YAML.',
)
- })
+ }),
)
.addTextArea((text) => {
text
- .setPlaceholder("Enter the template")
+ .setPlaceholder('Enter the template')
.setValue(this.plugin.settings.frontMatterTemplate)
.onChange(async (value) => {
this.plugin.settings.frontMatterTemplate = value
await this.plugin.saveSettings()
})
- text.inputEl.setAttr("rows", 10)
- text.inputEl.setAttr("cols", 30)
+ text.inputEl.setAttr('rows', 10)
+ text.inputEl.setAttr('cols', 30)
})
.addExtraButton((button) => {
// add a button to reset template
button
- .setIcon("reset")
- .setTooltip("Reset front matter template")
+ .setIcon('reset')
+ .setTooltip('Reset front matter template')
.onClick(async () => {
this.plugin.settings.frontMatterTemplate =
DEFAULT_SETTINGS.frontMatterTemplate
await this.plugin.saveSettings()
this.display()
- new Notice("Front matter template reset")
+ new Notice('Front matter template reset')
})
})
- const help = containerEl.createEl("p")
+ const help = containerEl.createEl('p')
help.innerHTML = `For more information, please visit our GitHub page, email us at feedback@omnivore.app or join our Discord server.`
// script to make collapsible sections
- const coll = document.getElementsByClassName("omnivore-collapsible")
+ const coll = document.getElementsByClassName('omnivore-collapsible')
let i
for (i = 0; i < coll.length; i++) {
- coll[i].addEventListener("click", function () {
- this.classList.toggle("omnivore-active")
+ coll[i].addEventListener('click', function () {
+ this.classList.toggle('omnivore-active')
const content = this.nextElementSibling
if (content.style.maxHeight) {
content.style.maxHeight = null
} else {
- content.style.maxHeight = "fit-content"
+ content.style.maxHeight = 'fit-content'
}
})
}
diff --git a/src/settings/index.ts b/src/settings/index.ts
index 773e7a5..2c4f63a 100644
--- a/src/settings/index.ts
+++ b/src/settings/index.ts
@@ -17,7 +17,7 @@ export const FRONT_MATTER_VARIABLES = [
'read_length',
'state',
'date_archived',
- 'image'
+ 'image',
]
export const DEFAULT_SETTINGS: OmnivoreSettings = {
@@ -42,12 +42,14 @@ export const DEFAULT_SETTINGS: OmnivoreSettings = {
intervalId: 0,
frontMatterVariables: [],
frontMatterTemplate: '',
+ syncOnStart: true,
}
export enum Filter {
- ALL = 'import all my articles',
- HIGHLIGHTS = 'import just highlights',
- ADVANCED = 'advanced',
+ ALL = 'Sync all the items',
+ LIBRARY = 'Sync only the library items',
+ ARCHIVED = 'Sync only the archived items',
+ HIGHLIGHTS = 'Sync only the highlighted items',
}
export enum HighlightOrder {
@@ -77,4 +79,5 @@ export interface OmnivoreSettings {
frontMatterVariables: string[]
frontMatterTemplate: string
filenameDateFormat: string
+ syncOnStart: boolean
}
diff --git a/src/settings/suggest.ts b/src/settings/suggest.ts
index d2def24..430bce6 100644
--- a/src/settings/suggest.ts
+++ b/src/settings/suggest.ts
@@ -18,12 +18,12 @@ class Suggest {
containerEl.on(
'click',
'.suggestion-item',
- this.onSuggestionClick.bind(this)
+ this.onSuggestionClick.bind(this),
)
containerEl.on(
'mousemove',
'.suggestion-item',
- this.onSuggestionMouseover.bind(this)
+ this.onSuggestionMouseover.bind(this),
)
scope.register([], 'ArrowUp', (event) => {
@@ -127,7 +127,7 @@ export abstract class TextInputSuggest implements ISuggestOwner {
'.suggestion-container',
(event: MouseEvent) => {
event.preventDefault()
- }
+ },
)
}
diff --git a/src/settings/template.ts b/src/settings/template.ts
index 04dd135..156a4b9 100644
--- a/src/settings/template.ts
+++ b/src/settings/template.ts
@@ -15,7 +15,7 @@ import {
type FunctionMap = {
[key: string]: () => (
text: string,
- render: (text: string) => string
+ render: (text: string) => string,
) => string
}
@@ -81,301 +81,300 @@ export type ArticleView =
}
| FunctionMap
- export type View =
- | {
- id: string
- title: string
- omnivoreUrl: string
- siteName: string
- originalUrl: string
- author: string
- date: string
- dateSaved: string
- datePublished?: string
- type: PageType
- dateRead?: string
- state: string
- dateArchived?: string
- }
- | FunctionMap
-
- enum ArticleState {
- Inbox = 'INBOX',
- Reading = 'READING',
- Completed = 'COMPLETED',
- Archived = 'ARCHIVED',
- }
-
- const getArticleState = (article: Article): string => {
- if (article.isArchived) {
- return ArticleState.Archived
- }
- if (article.readingProgressPercent > 0) {
- return article.readingProgressPercent === 100
- ? ArticleState.Completed
- : ArticleState.Reading
+export type View =
+ | {
+ id: string
+ title: string
+ omnivoreUrl: string
+ siteName: string
+ originalUrl: string
+ author: string
+ date: string
+ dateSaved: string
+ datePublished?: string
+ type: PageType
+ dateRead?: string
+ state: string
+ dateArchived?: string
}
+ | FunctionMap
- return ArticleState.Inbox
- }
+enum ArticleState {
+ Inbox = 'INBOX',
+ Reading = 'READING',
+ Completed = 'COMPLETED',
+ Archived = 'ARCHIVED',
+}
- function lowerCase() {
- return function (text: string, render: (text: string) => string) {
- return render(text).toLowerCase()
- }
+const getArticleState = (article: Article): string => {
+ if (article.isArchived) {
+ return ArticleState.Archived
}
-
- function upperCase() {
- return function (text: string, render: (text: string) => string) {
- return render(text).toUpperCase()
- }
+ if (article.readingProgressPercent > 0) {
+ return article.readingProgressPercent === 100
+ ? ArticleState.Completed
+ : ArticleState.Reading
}
- function upperCaseFirst() {
- return function (text: string, render: (text: string) => string) {
- const str = render(text)
- return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
- }
+ return ArticleState.Inbox
+}
+
+function lowerCase() {
+ return function (text: string, render: (text: string) => string) {
+ return render(text).toLowerCase()
}
+}
- function formatDateFunc() {
- return function (text: string, render: (text: string) => string) {
- // get the date and format from the text
- const [dateVariable, format] = text.split(',', 2)
- const date = render(dateVariable)
- if (!date) {
- return ''
- }
- // format the date
- return formatDate(date, format)
- }
+function upperCase() {
+ return function (text: string, render: (text: string) => string) {
+ return render(text).toUpperCase()
}
+}
- const functionMap: FunctionMap = {
- lowerCase,
- upperCase,
- upperCaseFirst,
- formatDate: formatDateFunc,
+function upperCaseFirst() {
+ return function (text: string, render: (text: string) => string) {
+ const str = render(text)
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}
+}
- const getOmnivoreUrl = (article: Article) => {
- return `https://omnivore.app/me/${article.slug}`
+function formatDateFunc() {
+ return function (text: string, render: (text: string) => string) {
+ // get the date and format from the text
+ const [dateVariable, format] = text.split(',', 2)
+ const date = render(dateVariable)
+ if (!date) {
+ return ''
+ }
+ // format the date
+ return formatDate(date, format)
}
+}
- export const renderFilename = (
- article: Article,
- filename: string,
- dateFormat: string
- ) => {
- const renderedFilename = render(article, filename, dateFormat)
+const functionMap: FunctionMap = {
+ lowerCase,
+ upperCase,
+ upperCaseFirst,
+ formatDate: formatDateFunc,
+}
- // truncate the filename to 100 characters
- return truncate(renderedFilename, {
- length: 100,
- })
- }
+const getOmnivoreUrl = (article: Article) => {
+ return `https://omnivore.app/me/${article.slug}`
+}
- export const renderLabels = (labels?: LabelView[]) => {
- return labels?.map((l) => ({
- // replace spaces with underscores because Obsidian doesn't allow spaces in tags
- name: l.name.replaceAll(' ', '_'),
- }))
- }
+export const renderFilename = (
+ article: Article,
+ filename: string,
+ dateFormat: string,
+) => {
+ const renderedFilename = render(article, filename, dateFormat)
+
+ // truncate the filename to 100 characters
+ return truncate(renderedFilename, {
+ length: 100,
+ })
+}
+
+export const renderLabels = (labels?: LabelView[]) => {
+ return labels?.map((l) => ({
+ // replace spaces with underscores because Obsidian doesn't allow spaces in tags
+ name: l.name.replaceAll(' ', '_'),
+ }))
+}
- export const renderArticleContnet = async (
- article: Article,
- template: string,
- highlightOrder: string,
- dateHighlightedFormat: string,
- dateSavedFormat: string,
- isSingleFile: boolean,
- frontMatterVariables: string[],
- frontMatterTemplate: string,
- fileAttachment?: string
- ) => {
- // filter out notes and redactions
- const articleHighlights =
- article.highlights?.filter((h) => h.type === HighlightType.Highlight) ||
- []
- // sort highlights by location if selected in options
- if (highlightOrder === 'LOCATION') {
- articleHighlights.sort((a, b) => {
- try {
- if (article.pageType === PageType.File) {
- // sort by location in file
- return compareHighlightsInFile(a, b)
- }
- // for web page, sort by location in the page
- return getHighlightLocation(a.patch) - getHighlightLocation(b.patch)
- } catch (e) {
- console.error(e)
+export const renderArticleContnet = async (
+ article: Article,
+ template: string,
+ highlightOrder: string,
+ dateHighlightedFormat: string,
+ dateSavedFormat: string,
+ isSingleFile: boolean,
+ frontMatterVariables: string[],
+ frontMatterTemplate: string,
+ fileAttachment?: string,
+) => {
+ // filter out notes and redactions
+ const articleHighlights =
+ article.highlights?.filter((h) => h.type === HighlightType.Highlight) || []
+ // sort highlights by location if selected in options
+ if (highlightOrder === 'LOCATION') {
+ articleHighlights.sort((a, b) => {
+ try {
+ if (article.pageType === PageType.File) {
+ // sort by location in file
return compareHighlightsInFile(a, b)
}
- })
- }
- const highlights: HighlightView[] = articleHighlights.map((highlight) => {
- return {
- text: formatHighlightQuote(highlight.quote, template),
- highlightUrl: `https://omnivore.app/me/${article.slug}#${highlight.id}`,
- highlightID: highlight.id.slice(0, 8),
- dateHighlighted: formatDate(highlight.updatedAt, dateHighlightedFormat),
- note: highlight.annotation ?? undefined,
- labels: renderLabels(highlight.labels),
- color: highlight.color ?? 'yellow',
- positionPercent: highlight.highlightPositionPercent,
- positionAnchorIndex: highlight.highlightPositionAnchorIndex + 1, // PDF page numbers start at 1
+ // for web page, sort by location in the page
+ return getHighlightLocation(a.patch) - getHighlightLocation(b.patch)
+ } catch (e) {
+ console.error(e)
+ return compareHighlightsInFile(a, b)
}
})
- const dateSaved = formatDate(article.savedAt, dateSavedFormat)
- const siteName =
- article.siteName || siteNameFromUrl(article.originalArticleUrl)
- const publishedAt = article.publishedAt
- const datePublished = publishedAt
- ? formatDate(publishedAt, dateSavedFormat).trim()
- : undefined
- const articleNote = article.highlights?.find(
- (h) => h.type === HighlightType.Note
- )
- const dateRead = article.readAt
- ? formatDate(article.readAt, dateSavedFormat).trim()
- : undefined
- const wordsCount = article.wordsCount
- const readLength = wordsCount
- ? Math.round(Math.max(1, wordsCount / 235))
- : undefined
- const articleView: ArticleView = {
- id: article.id,
- title: article.title,
- omnivoreUrl: `https://omnivore.app/me/${article.slug}`,
- siteName,
- originalUrl: article.originalArticleUrl,
- author: article.author,
- labels: renderLabels(article.labels),
- dateSaved,
- highlights,
- content: article.contentReader === 'WEB' ? article.content : undefined,
- datePublished,
- fileAttachment,
- description: article.description,
- note: articleNote?.annotation ?? undefined,
- type: article.pageType,
- dateRead,
- wordsCount,
- readLength,
- state: getArticleState(article),
- dateArchived: article.archivedAt,
- image: article.image,
- updatedAt: article.updatedAt,
- ...functionMap,
+ }
+ const highlights: HighlightView[] = articleHighlights.map((highlight) => {
+ return {
+ text: formatHighlightQuote(highlight.quote, template),
+ highlightUrl: `https://omnivore.app/me/${article.slug}#${highlight.id}`,
+ highlightID: highlight.id.slice(0, 8),
+ dateHighlighted: formatDate(highlight.updatedAt, dateHighlightedFormat),
+ note: highlight.annotation ?? undefined,
+ labels: renderLabels(highlight.labels),
+ color: highlight.color ?? 'yellow',
+ positionPercent: highlight.highlightPositionPercent,
+ positionAnchorIndex: highlight.highlightPositionAnchorIndex + 1, // PDF page numbers start at 1
}
+ })
+ const dateSaved = formatDate(article.savedAt, dateSavedFormat)
+ const siteName =
+ article.siteName || siteNameFromUrl(article.originalArticleUrl)
+ const publishedAt = article.publishedAt
+ const datePublished = publishedAt
+ ? formatDate(publishedAt, dateSavedFormat).trim()
+ : undefined
+ const articleNote = article.highlights?.find(
+ (h) => h.type === HighlightType.Note,
+ )
+ const dateRead = article.readAt
+ ? formatDate(article.readAt, dateSavedFormat).trim()
+ : undefined
+ const wordsCount = article.wordsCount
+ const readLength = wordsCount
+ ? Math.round(Math.max(1, wordsCount / 235))
+ : undefined
+ const articleView: ArticleView = {
+ id: article.id,
+ title: article.title,
+ omnivoreUrl: `https://omnivore.app/me/${article.slug}`,
+ siteName,
+ originalUrl: article.originalArticleUrl,
+ author: article.author,
+ labels: renderLabels(article.labels),
+ dateSaved,
+ highlights,
+ content: article.contentReader === 'WEB' ? article.content : undefined,
+ datePublished,
+ fileAttachment,
+ description: article.description,
+ note: articleNote?.annotation ?? undefined,
+ type: article.pageType,
+ dateRead,
+ wordsCount,
+ readLength,
+ state: getArticleState(article),
+ dateArchived: article.archivedAt,
+ image: article.image,
+ updatedAt: article.updatedAt,
+ ...functionMap,
+ }
- let frontMatter: { [id: string]: unknown } = {
- id: article.id, // id is required for deduplication
- }
+ let frontMatter: { [id: string]: unknown } = {
+ id: article.id, // id is required for deduplication
+ }
- // if the front matter template is set, use it
- if (frontMatterTemplate) {
- const frontMatterTemplateRendered = Mustache.render(
- frontMatterTemplate,
- articleView
- )
- try {
- // parse the front matter template as yaml
- const frontMatterParsed = parseYaml(frontMatterTemplateRendered)
+ // if the front matter template is set, use it
+ if (frontMatterTemplate) {
+ const frontMatterTemplateRendered = Mustache.render(
+ frontMatterTemplate,
+ articleView,
+ )
+ try {
+ // parse the front matter template as yaml
+ const frontMatterParsed = parseYaml(frontMatterTemplateRendered)
- frontMatter = {
- ...frontMatterParsed,
- ...frontMatter,
- }
- } catch (error) {
- // if there's an error parsing the front matter template, log it
- console.error('Error parsing front matter template', error)
- // and add the error to the front matter
- frontMatter = {
- ...frontMatter,
- omnivore_error:
- 'There was an error parsing the front matter template. See console for details.',
- }
+ frontMatter = {
+ ...frontMatterParsed,
+ ...frontMatter,
}
- } else {
- // otherwise, use the front matter variables
- for (const item of frontMatterVariables) {
- // split the item into variable and alias
- const aliasedVariables = item.split('::')
- const variable = aliasedVariables[0]
- // we use snake case for variables in the front matter
- const articleVariable = snakeToCamelCase(variable)
- // use alias if available, otherwise use variable
- const key = aliasedVariables[1] || variable
- if (
- variable === 'tags' &&
- articleView.labels &&
- articleView.labels.length > 0
- ) {
- // tags are handled separately
- // use label names as tags
- frontMatter[key] = articleView.labels.map((l) => l.name)
- continue
- }
-
- const value = (articleView as any)[articleVariable]
- if (value) {
- // if variable is in article, use it
- frontMatter[key] = value
- }
+ } catch (error) {
+ // if there's an error parsing the front matter template, log it
+ console.error('Error parsing front matter template', error)
+ // and add the error to the front matter
+ frontMatter = {
+ ...frontMatter,
+ omnivore_error:
+ 'There was an error parsing the front matter template. See console for details.',
}
}
+ } else {
+ // otherwise, use the front matter variables
+ for (const item of frontMatterVariables) {
+ // split the item into variable and alias
+ const aliasedVariables = item.split('::')
+ const variable = aliasedVariables[0]
+ // we use snake case for variables in the front matter
+ const articleVariable = snakeToCamelCase(variable)
+ // use alias if available, otherwise use variable
+ const key = aliasedVariables[1] || variable
+ if (
+ variable === 'tags' &&
+ articleView.labels &&
+ articleView.labels.length > 0
+ ) {
+ // tags are handled separately
+ // use label names as tags
+ frontMatter[key] = articleView.labels.map((l) => l.name)
+ continue
+ }
- // Build content string based on template
- const content = Mustache.render(template, articleView)
- let contentWithoutFrontMatter = removeFrontMatterFromContent(content)
- let frontMatterYaml = stringifyYaml(frontMatter)
- if (isSingleFile) {
- // wrap the content without front matter in comments
- const sectionStart = `%%${article.id}_start%%`
- const sectionEnd = `%%${article.id}_end%%`
- contentWithoutFrontMatter = `${sectionStart}\n${contentWithoutFrontMatter}\n${sectionEnd}`
-
- // if single file, wrap the front matter in an array
- frontMatterYaml = stringifyYaml([frontMatter])
+ const value = (articleView as any)[articleVariable]
+ if (value) {
+ // if variable is in article, use it
+ frontMatter[key] = value
+ }
}
+ }
- const frontMatterStr = `---\n${frontMatterYaml}---`
-
- return `${frontMatterStr}\n\n${contentWithoutFrontMatter}`
+ // Build content string based on template
+ const content = Mustache.render(template, articleView)
+ let contentWithoutFrontMatter = removeFrontMatterFromContent(content)
+ let frontMatterYaml = stringifyYaml(frontMatter)
+ if (isSingleFile) {
+ // wrap the content without front matter in comments
+ const sectionStart = `%%${article.id}_start%%`
+ const sectionEnd = `%%${article.id}_end%%`
+ contentWithoutFrontMatter = `${sectionStart}\n${contentWithoutFrontMatter}\n${sectionEnd}`
+
+ // if single file, wrap the front matter in an array
+ frontMatterYaml = stringifyYaml([frontMatter])
}
- export const render = (
- article: Article,
- template: string,
- dateFormat: string
- ) => {
- const dateSaved = formatDate(article.savedAt, dateFormat)
- const datePublished = article.publishedAt
- ? formatDate(article.publishedAt, dateFormat).trim()
- : undefined
- const dateArchived = article.archivedAt
- ? formatDate(article.archivedAt, dateFormat).trim()
- : undefined
- const dateRead = article.readAt
- ? formatDate(article.readAt, dateFormat).trim()
- : undefined
- const view: View = {
- ...article,
- author: article.author || 'unknown-author',
- omnivoreUrl: getOmnivoreUrl(article),
- originalUrl: article.originalArticleUrl,
- date: dateSaved,
- dateSaved,
- datePublished,
- dateArchived,
- dateRead,
- type: article.pageType,
- state: getArticleState(article),
- ...functionMap,
- }
- return Mustache.render(template, view)
+ const frontMatterStr = `---\n${frontMatterYaml}---`
+
+ return `${frontMatterStr}\n\n${contentWithoutFrontMatter}`
+}
+
+export const render = (
+ article: Article,
+ template: string,
+ dateFormat: string,
+) => {
+ const dateSaved = formatDate(article.savedAt, dateFormat)
+ const datePublished = article.publishedAt
+ ? formatDate(article.publishedAt, dateFormat).trim()
+ : undefined
+ const dateArchived = article.archivedAt
+ ? formatDate(article.archivedAt, dateFormat).trim()
+ : undefined
+ const dateRead = article.readAt
+ ? formatDate(article.readAt, dateFormat).trim()
+ : undefined
+ const view: View = {
+ ...article,
+ author: article.author || 'unknown-author',
+ omnivoreUrl: getOmnivoreUrl(article),
+ originalUrl: article.originalArticleUrl,
+ date: dateSaved,
+ dateSaved,
+ datePublished,
+ dateArchived,
+ dateRead,
+ type: article.pageType,
+ state: getArticleState(article),
+ ...functionMap,
}
+ return Mustache.render(template, view)
+}
export const preParseTemplate = (template: string) => {
return Mustache.parse(template)
diff --git a/src/util.ts b/src/util.ts
index 510cf80..1b762e9 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,21 +1,21 @@
-import { diff_match_patch } from "diff-match-patch"
-import { DateTime } from "luxon"
-import escape from "markdown-escape"
-import { parseYaml } from "obsidian"
-import outOfCharacter from "out-of-character"
-import { Highlight } from "./api"
+import { diff_match_patch } from 'diff-match-patch'
+import { DateTime } from 'luxon'
+import escape from 'markdown-escape'
+import { parseYaml } from 'obsidian'
+import outOfCharacter from 'out-of-character'
+import { Highlight } from './api'
export const DATE_FORMAT_W_OUT_SECONDS = "yyyy-MM-dd'T'HH:mm"
export const DATE_FORMAT = `${DATE_FORMAT_W_OUT_SECONDS}:ss`
-export const REPLACEMENT_CHAR = "-"
+export const REPLACEMENT_CHAR = '-'
// On Unix-like systems / is reserved and <>:"/\|?* as well as non-printable characters \u0000-\u001F on Windows
// credit: https://github.com/sindresorhus/filename-reserved-regex
// eslint-disable-next-line no-control-regex
export const ILLEGAL_CHAR_REGEX = /[<>:"/\\|?*\u0000-\u001F]/g
export interface HighlightPoint {
- left: number;
- top: number;
+ left: number
+ top: number
}
export const getHighlightLocation = (patch: string | null): number => {
@@ -54,7 +54,7 @@ export const markdownEscape = (text: string): string => {
try {
return escape(text)
} catch (e) {
- console.error("markdownEscape error", e)
+ console.error('markdownEscape error', e)
return text
}
}
@@ -78,29 +78,27 @@ export const wrapAround = (value: number, size: number): number => {
export const unicodeSlug = (str: string, savedAt: string) => {
return (
str
- .normalize("NFKD") // using NFKD method returns the Unicode Normalization Form of a given string.
- .replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
+ .normalize('NFKD') // using NFKD method returns the Unicode Normalization Form of a given string.
+ .replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
.trim()
.toLowerCase()
.replace(
/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,
- ""
+ '',
) // replace all the symbols with -
- .replace(/\s+/g, "-") // collapse whitespace and replace by -
- .replace(/_/g, "-") // replace _ with -
- .replace(/-+/g, "-") // collapse dashes
+ .replace(/\s+/g, '-') // collapse whitespace and replace by -
+ .replace(/_/g, '-') // replace _ with -
+ .replace(/-+/g, '-') // collapse dashes
// remove trailing -
- .replace(/-$/g, "")
+ .replace(/-$/g, '')
.substring(0, 64) +
- "-" +
+ '-' +
new Date(savedAt).getTime().toString(16)
)
}
export const replaceIllegalChars = (str: string): string => {
- return removeInvisibleChars(
- str.replace(ILLEGAL_CHAR_REGEX, REPLACEMENT_CHAR)
- )
+ return removeInvisibleChars(str.replace(ILLEGAL_CHAR_REGEX, REPLACEMENT_CHAR))
}
export function formatDate(date: string, format: string): string {
@@ -110,42 +108,41 @@ export function formatDate(date: string, format: string): string {
return DateTime.fromJSDate(new Date(date)).toFormat(format)
}
-export const getQueryFromFilter = (
- filter: string,
- customQuery: string
-): string => {
+export const getQueryFromFilter = (filter: string): string => {
switch (filter) {
- case "ALL":
- return "in:all"
- case "HIGHLIGHTS":
- return `has:highlights in:all`
- case "ADVANCED":
- return customQuery
+ case 'ALL':
+ return 'in:all'
+ case 'HIGHLIGHTS':
+ return `in:all has:highlights`
+ case 'ARCHIVED':
+ return `in:archive`
+ case 'LIBRARY':
+ return `in:library`
default:
- return ""
+ return 'in:all'
}
}
export const siteNameFromUrl = (originalArticleUrl: string): string => {
try {
- return new URL(originalArticleUrl).hostname.replace(/^www\./, "")
+ return new URL(originalArticleUrl).hostname.replace(/^www\./, '')
} catch {
- return ""
+ return ''
}
}
export const formatHighlightQuote = (
quote: string | null,
- template: string
+ template: string,
): string => {
if (!quote) {
- return ""
+ return ''
}
// if the template has highlights, we need to preserve paragraphs
const regex = /{{#highlights}}(\n)*>/gm
if (regex.test(template)) {
// replace all empty lines with blockquote '>' to preserve paragraphs
- quote = quote.replaceAll(">", ">").replaceAll(/\n/gm, "\n> ")
+ quote = quote.replaceAll('>', '>').replaceAll(/\n/gm, '\n> ')
}
return quote
@@ -153,7 +150,7 @@ export const formatHighlightQuote = (
export const findFrontMatterIndex = (
frontMatter: any[],
- id: string
+ id: string,
): number => {
// find index of front matter with matching id
return frontMatter.findIndex((fm) => fm.id == id)
@@ -172,11 +169,11 @@ export const parseFrontMatterFromContent = (content: string) => {
export const removeFrontMatterFromContent = (content: string): string => {
const frontMatterRegex = /^---.*?---\n*/s
- return content.replace(frontMatterRegex, "")
+ return content.replace(frontMatterRegex, '')
}
export const snakeToCamelCase = (str: string) =>
- str.replace(/(_[a-z])/g, (group) => group.toUpperCase().replace("_", ""))
+ str.replace(/(_[a-z])/g, (group) => group.toUpperCase().replace('_', ''))
const removeInvisibleChars = (str: string): string => {
return outOfCharacter.replace(str)
diff --git a/yarn.lock b/yarn.lock
index f21bbc0..d2427dd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -997,6 +997,11 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+"@pkgr/core@^0.1.0":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
+ integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
+
"@pnpm/config.env-replace@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
@@ -2439,6 +2444,19 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+eslint-config-prettier@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f"
+ integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==
+
+eslint-plugin-prettier@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1"
+ integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==
+ dependencies:
+ prettier-linter-helpers "^1.0.0"
+ synckit "^0.8.6"
+
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
@@ -2605,6 +2623,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-diff@^1.1.2:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
+ integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
+
fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
@@ -4913,10 +4936,17 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-prettier@^2.8.1:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
- integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+prettier-linter-helpers@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+ integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+ dependencies:
+ fast-diff "^1.1.2"
+
+prettier@^3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
+ integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
pretty-format@^29.0.0, pretty-format@^29.5.0:
version "29.5.0"
@@ -5563,6 +5593,14 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+synckit@^0.8.6:
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7"
+ integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==
+ dependencies:
+ "@pkgr/core" "^0.1.0"
+ tslib "^2.6.2"
+
tar@^6.1.0, tar@^6.1.11, tar@^6.1.2:
version "6.1.15"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69"
@@ -5696,6 +5734,11 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+tslib@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+ integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"