diff --git a/package.json b/package.json index 8b76551..35f4793 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "lodash": "^4.17.21", "luxon": "^3.2.1", "markdown-escape": "^1.1.0", - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "out-of-character": "^1.2.1" }, "devDependencies": { "@semantic-release/changelog": "^6.0.1", diff --git a/src/api.ts b/src/api.ts index c737840..eceeb05 100644 --- a/src/api.ts +++ b/src/api.ts @@ -72,9 +72,9 @@ export enum HighlightType { export interface Highlight { id: string - quote: string - annotation: string - patch: string + quote: string | null + annotation: string | null + patch: string | null updatedAt: string labels?: Label[] type: HighlightType diff --git a/src/index.ts b/src/index.ts index 4100166..144a9fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import { isBlockPropertiesChanged, parseBlockProperties, parseDateTime, + replaceIllegalChars, } from './util' const isValidCurrentGraph = async (): Promise => { @@ -250,10 +251,8 @@ const fetchOmnivore = async (inBackground = false) => { for (const article of articles) { if (!isSinglePage) { // create a new page for each article - pageName = renderPageName( - article, - pageNameTemplate, - preferredDateFormat + pageName = replaceIllegalChars( + renderPageName(article, pageNameTemplate, preferredDateFormat) ) targetBlockId = (await getOmnivoreBlock(pageName, blockTitle)).uuid } diff --git a/src/settings/template.ts b/src/settings/template.ts index ecd37fd..83c673e 100644 --- a/src/settings/template.ts +++ b/src/settings/template.ts @@ -152,7 +152,7 @@ const createArticleView = ( dateSaved, content: article.content, datePublished, - note: note?.annotation, + note: note?.annotation ?? undefined, type: article.pageType, rawDatePublished, rawDateRead, @@ -189,7 +189,7 @@ export const renderHighlightContent = ( highlightUrl: `https://omnivore.app/me/${article.slug}#${highlight.id}`, dateHighlighted, rawDateHighlighted, - note: highlight.annotation, + note: highlight.annotation ?? undefined, ...functionMap, } return Mustache.render(template, highlightView) diff --git a/src/util.ts b/src/util.ts index b5e19b9..8297ade 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,6 +2,7 @@ import { format } from 'date-fns' import { diff_match_patch } from 'diff-match-patch' import { DateTime } from 'luxon' import escape from 'markdown-escape' +import outOfCharacter from 'out-of-character' import { Highlight } from './api' export interface HighlightPoint { @@ -11,14 +12,25 @@ export interface HighlightPoint { 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 getHighlightLocation = (patch: string): number => { +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 const getHighlightLocation = (patch: string | null): number => { + if (!patch) { + return 0 + } const dmp = new diff_match_patch() const patches = dmp.patch_fromText(patch) return patches[0].start1 || 0 } -export const getHighlightPoint = (patch: string): HighlightPoint => { +export const getHighlightPoint = (patch: string | null): HighlightPoint => { + if (!patch) { + return { left: 0, top: 0 } + } const { bbox } = JSON.parse(patch) as { bbox: number[] } if (!bbox || bbox.length !== 4) { return { left: 0, top: 0 } @@ -87,9 +99,13 @@ export const siteNameFromUrl = (originalArticleUrl: string): string => { export const delay = (t = 100) => new Promise((r) => setTimeout(r, t)) export const formatHighlightQuote = ( - quote: string, + quote: string | null, template: string ): string => { + if (!quote) { + return '' + } + if (template.startsWith('>')) { // replace all empty lines with blockquote '>' to preserve paragraphs quote = quote.replace(/^(?=\n)$|^\s*?\n/gm, '> ') @@ -162,3 +178,11 @@ export const isBlockPropertiesChanged = ( export const escapeQuotes = (str: string): string => { return str.replace(/"/g, '\\"') } + +const removeInvisibleChars = (str: string): string => { + return outOfCharacter.replace(str) +} + +export const replaceIllegalChars = (str: string): string => { + return removeInvisibleChars(str.replace(ILLEGAL_CHAR_REGEX, REPLACEMENT_CHAR)) +} diff --git a/yarn.lock b/yarn.lock index 3891435..ab66ce5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1825,6 +1825,11 @@ color-support@^1.1.3: resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colorette@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + columnify@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz" @@ -2639,6 +2644,18 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -4077,6 +4094,14 @@ ordered-binary@^1.2.4: resolved "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.2.5.tgz" integrity sha512-djRmZoEpOGvIRW7ufsCDHtvcUa18UC9TxnPbHhSVFZHsoyg0dtut1bWtBZ/fmxdPN62oWXrV6adM7NoWU+CneA== +out-of-character@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/out-of-character/-/out-of-character-1.2.1.tgz#383bf683e58f654ee1f348029367edf5c79ba87d" + integrity sha512-clqCX4lNQa5Qr7urMNej2DbKVt+SO5CZ9Sq4XYEJ04qzvDHA4yFMhz0F+vLrfxO89640x8W56D7wtNoAgCCcAA== + dependencies: + colorette "1.2.2" + glob "7.1.7" + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz"