From 255872b40cac7f7ac738dac71200b263c49fa4f0 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Wed, 1 Jun 2022 22:15:59 -0400 Subject: [PATCH 1/9] add dep: readability --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 06e83d6..49006f5 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@mozilla/readability": "^0.4.2", "@react-spring/web": "^9.4.5", "@types/jest": "^27.5.1", "@types/node": "^17.0.35", diff --git a/yarn.lock b/yarn.lock index 5e36dd5..7071ace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -83,6 +83,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@mozilla/readability@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@mozilla/readability/-/readability-0.4.2.tgz#a425f3dccdbe777d45a4e791bd703ebe633ebb87" + integrity sha512-48MJXzi4Dhy2fJ3lGjmwdEJKoMmn3oiYew9n/1OW6cZy78hAzRIyDJDBCGrg4PBFDyY4xos+H4LCFn5QVRDcfw== + "@next/env@12.1.6": version "12.1.6" resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.6.tgz#5f44823a78335355f00f1687cfc4f1dafa3eca08" From 669f5586add73a13b2ed814003b864698eed3539 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 01:09:31 -0400 Subject: [PATCH 2/9] Setup readability serverless function --- lib/readwise/index.ts | 17 +- package.json | 3 + pages/api/reader.js | 4 - pages/api/reader.ts | 40 +++++ yarn.lock | 376 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 424 insertions(+), 16 deletions(-) delete mode 100644 pages/api/reader.js create mode 100644 pages/api/reader.ts diff --git a/lib/readwise/index.ts b/lib/readwise/index.ts index 300790a..2f0fef9 100644 --- a/lib/readwise/index.ts +++ b/lib/readwise/index.ts @@ -102,7 +102,12 @@ export function useHighlights() { const { data, error, isValidating } = useSWR, any>( id ? 'v2/highlights' : null, - () => fetchHighlights({ id, token: token as string }) + () => fetchHighlights({ id, token: token as string }), + { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } ); useEffect(() => { @@ -164,8 +169,14 @@ export function useBooks(token: string) { // mutate('v2/books'); // }, [token]); - const { data, error, isValidating } = useSWR('v2/books', () => - fetchBooks({ token: token }) + const { data, error, isValidating } = useSWR( + 'v2/books', + () => fetchBooks({ token: token }), + { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } ); useEffect(() => { diff --git a/package.json b/package.json index 49006f5..88ed85e 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@mozilla/readability": "^0.4.2", "@react-spring/web": "^9.4.5", "@types/jest": "^27.5.1", + "@types/jsdom": "^16.2.14", "@types/node": "^17.0.35", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.5", @@ -17,6 +18,8 @@ "framer-motion": "^6.3.4", "fuse.js": "^6.6.2", "husky": "^8.0.1", + "interweave": "^13.0.0", + "jsdom": "^19.0.0", "lint-staged": "^12.4.2", "luxon": "^2.4.0", "next": "^12.1.6", diff --git a/pages/api/reader.js b/pages/api/reader.js deleted file mode 100644 index fe8ac32..0000000 --- a/pages/api/reader.js +++ /dev/null @@ -1,4 +0,0 @@ -export default (request, response) => { - const { name } = request.query; - response.status(200).send(`Hellooooo ${name}!`); -}; diff --git a/pages/api/reader.ts b/pages/api/reader.ts new file mode 100644 index 0000000..d9475cf --- /dev/null +++ b/pages/api/reader.ts @@ -0,0 +1,40 @@ +import { Readability } from '@mozilla/readability'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { JSDOM } from 'jsdom'; + +type Data = { + /** article title */ + title: string; + /** author metadata */ + byline: string; + /** content direction */ + dir: string; + /** HTML of processed article content */ + content: T; + /** text content of the article (all HTML removed) */ + textContent: string; + /** length of an article, in characters */ + length: number; + /** article description, or short excerpt from the content */ + excerpt: string; + siteName: string; +}; + +export default async ( + request: NextApiRequest, + response: NextApiResponse +) => { + const url = request.query.url as string; + const article = await fetch(url); + const markup = await article.text(); + + const doc = new JSDOM(markup, { + url: url, + includeNodeLocations: true, + storageQuota: 10000000, + }); + + const reader = new Readability(doc.window.document).parse(); + + response.status(200).send(reader); +}; diff --git a/yarn.lock b/yarn.lock index 7071ace..8b62eb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,11 @@ "@react-spring/shared" "~9.4.5" "@react-spring/types" "~9.4.5" +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@types/eslint-scope@^3.7.3": version "3.7.3" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" @@ -249,6 +254,15 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/jsdom@^16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.14.tgz#26fe9da6a8870715b154bb84cd3b2e53433d8720" + integrity sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w== + dependencies: + "@types/node" "*" + "@types/parse5" "*" + "@types/tough-cookie" "*" + "@types/json-schema@*", "@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -259,6 +273,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.36.tgz#c0d5f2fe76b47b63e0e0efc3d2049a9970d68794" integrity sha512-V3orv+ggDsWVHP99K3JlwtH20R7J4IhI1Kksgc+64q5VxgfRkQG8Ws3MFm/FZOKDYGy9feGFlZ70/HpCNe9QaA== +"@types/parse5@*": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" + integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== + "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -285,6 +304,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + "@use-gesture/core@10.2.15": version "10.2.15" resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.15.tgz#ed55e85211eb6129a7979f177f1831d078087d22" @@ -443,6 +467,19 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" @@ -462,12 +499,12 @@ acorn-node@^1.8.2: acorn-walk "^7.0.0" xtend "^4.0.2" -acorn-walk@^7.0.0: +acorn-walk@^7.0.0, acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^7.0.0: +acorn@^7.0.0, acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -477,6 +514,13 @@ acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -562,6 +606,11 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + autoprefixer@^10.4.7: version "10.4.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" @@ -599,6 +648,11 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browserslist@^4.14.5, browserslist@^4.20.3: version "4.20.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" @@ -708,6 +762,13 @@ colorette@^2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -737,19 +798,50 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== -debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +data-urls@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + +debug@4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -deep-is@^0.1.3: +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -764,6 +856,11 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detective@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" @@ -800,6 +897,13 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -838,11 +942,28 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -926,6 +1047,11 @@ espree@^9.3.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -996,7 +1122,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -1040,6 +1166,15 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + formik@^2.2.9: version "2.2.9" resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" @@ -1175,6 +1310,30 @@ hoist-non-react-statics@^3.3.0: dependencies: react-is "^16.7.0" +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -1185,6 +1344,13 @@ husky@^8.0.1: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -1226,6 +1392,13 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +interweave@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/interweave/-/interweave-13.0.0.tgz#68fdaa6bde65cf296ba74829bb3746d2a5455a2b" + integrity sha512-Mckwj+ix/VtrZu1bRBIIohwrsXj12ZTvJCoYUMZlJmgtvIaQCj0i77eSZ63ckbA1TsPrz2VOvLW9/kTgm5d+mw== + dependencies: + escape-html "^1.0.3" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1267,6 +1440,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -1323,6 +1501,39 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" + integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -1346,6 +1557,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lilconfig@2.0.5, lilconfig@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" @@ -1460,7 +1679,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27: +mime-types@^2.1.12, mime-types@^2.1.27: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1549,6 +1768,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + object-hash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" @@ -1573,6 +1797,18 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -1599,6 +1835,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -1702,6 +1943,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + prettier@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" @@ -1716,7 +1962,12 @@ pretty-format@^27.0.0, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" -punycode@^2.1.0: +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -1870,6 +2121,11 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + sass@^1.52.1: version "1.52.1" resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.1.tgz#554693da808543031f9423911d62c60a1acf7889" @@ -1879,6 +2135,13 @@ sass@^1.52.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8" @@ -1963,7 +2226,7 @@ source-map-support@^0.5.17, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -2057,6 +2320,11 @@ swr@^1.3.0: resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + tailwindcss@^3.0.24: version "3.0.24" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d" @@ -2132,6 +2400,22 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + ts-node@8.9.1: version "8.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" @@ -2160,6 +2444,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -2180,6 +2471,11 @@ typescript@^4.7.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2197,6 +2493,20 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + watchpack@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" @@ -2210,6 +2520,11 @@ web-vitals@^2.1.4: resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c" integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" @@ -2245,6 +2560,34 @@ webpack@^5.72.1: watchpack "^2.3.1" webpack-sources "^3.2.3" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -2252,7 +2595,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -2280,6 +2623,21 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^8.2.3: + version "8.7.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.7.0.tgz#eaf9d874b433aa00c0e0d8752532444875db3957" + integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From 5d7806890230e7770f27eccc85c74ad7ef6f7756 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 01:09:52 -0400 Subject: [PATCH 3/9] Render highlights in readable article via intertweave --- components/Page.tsx | 58 +++++++++++++++++++++++++++++++---------- pages/library/[id].tsx | 59 +++++++++++++++++++++++++++++++----------- styles/_global.scss | 8 ++++++ 3 files changed, 96 insertions(+), 29 deletions(-) diff --git a/components/Page.tsx b/components/Page.tsx index da35639..f7df97a 100644 --- a/components/Page.tsx +++ b/components/Page.tsx @@ -1,7 +1,8 @@ import { animate as framer, AnimationControls, motion } from 'framer-motion'; +import { Interweave, match, MatcherInterface, MatchResponse } from 'interweave'; import { FC, useEffect, useRef } from 'react'; -import language from '../constants/language'; +import language from '../constants/language'; import { Book, Highlight } from '../lib/readwise/types'; interface Props { @@ -12,6 +13,7 @@ interface Props { pan: boolean; bookmark: Highlight; token: string; + article: any; } const Page: FC = ({ @@ -21,22 +23,52 @@ const Page: FC = ({ custom, pan, bookmark, + article, }) => { const pageRef = useRef(); const highlightRef = useRef(null); - useEffect(() => { - let topPos = - highlightRef.current.offsetTop + highlightRef.current.clientHeight - 8; - console.log(topPos); + const matcher: MatcherInterface = { + inverseName: 'noFoo', + propName: 'foo', + match(string): MatchResponse { + const result = string.match(bookmark.text); - highlightRef.current.className = 'bg-moss/20'; + if (!result) { + return null; + } + + return { + index: result.index!, + length: result[0].length, + match: result[0], + className: 'highlight', + valid: true, + ref: highlightRef, + }; + }, + createElement(children, props) { + return {children}; + }, + asTag() { + return 'span'; + }, + }; + + useEffect(() => { + if (highlightRef.current) { + { + let topPos = + highlightRef.current.offsetTop + highlightRef.current.clientHeight; - if (pan) { - framer(pageRef.current.scrollTop, topPos, { - onUpdate: (top) => - pageRef.current.scrollTo({ top, behavior: 'smooth' }), - }); + highlightRef.current.className = 'bg-moss/20'; + if (pan) { + framer(pageRef.current.scrollTop, topPos, { + onUpdate: (top) => + pageRef.current.scrollTo({ top, behavior: 'smooth' }), + }); + } + } } }, [pan]); @@ -53,9 +85,7 @@ const Page: FC = ({ {language.article.highlight.eyebrow(pageIndex + 1)}
-

- {bookmark.text} -

+ ;
); diff --git a/pages/library/[id].tsx b/pages/library/[id].tsx index c46ce1f..192bb8f 100644 --- a/pages/library/[id].tsx +++ b/pages/library/[id].tsx @@ -4,11 +4,26 @@ import { useLocalstorage } from 'rooks'; import { useGesture } from '@use-gesture/react'; +import { useEffect } from 'react'; import Page from '../../components/Page'; -import { useBooks, useHighlights } from '../../lib/readwise'; -import { usePreventGestureDefault } from '../../utils'; import language from '../../constants/language'; import { READWISE_TOKEN_LOCALSTORAGE_KEY } from '../../constants/values'; +import { useBooks, useHighlights } from '../../lib/readwise'; +import { Book } from '../../lib/readwise/types'; +import { usePreventGestureDefault } from '../../utils'; + +const fetchArticle = async (book: Book) => { + const reader = await fetch(`/api/reader?url=${book.source_url}`); + + const markup = await reader.json(); + // const dom = new JSDOM(markup.content, { + // url: book.source_url, + // includeNodeLocations: true, + // storageQuota: 10000000, + // }); + + return markup.content; +}; function Article() { const { value: token } = useLocalstorage(READWISE_TOKEN_LOCALSTORAGE_KEY); @@ -20,6 +35,17 @@ function Article() { const [pan, setPan] = useState(false); const controls = useAnimation(); + const [article, setArticle] = useState(null); + + useEffect(() => { + if (book) { + fetchArticle(book).then((b) => { + // console.log(b); + setArticle(b); + }); + } + }, [book]); + usePreventGestureDefault(); const bind = useGesture( @@ -47,7 +73,7 @@ function Article() { } ); - return loading || loadingBooks ? ( + return loading || loadingBooks || !article ? (
Loading…
) : (
@@ -74,18 +100,21 @@ function Article() { />
- {bookmarks.map((bookmark, i) => ( - - ))} + {bookmarks.map((bookmark, i) => { + return i < 5 ? ( + + ) : null; + })} ); } diff --git a/styles/_global.scss b/styles/_global.scss index ac2c2f9..937fd07 100644 --- a/styles/_global.scss +++ b/styles/_global.scss @@ -22,3 +22,11 @@ main a { .highlight { @apply bg-moss/20; } + +.page { + @apply text-base; + @apply leading-6; + p { + @apply mb-4; + } +} From 785a3a13f5532a9b933529b9f41b2ea7b32c399c Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 10:48:16 -0400 Subject: [PATCH 4/9] Remove jsdom, fix type error --- package.json | 2 -- pages/api/reader.ts | 4 ++-- pages/library/[id].tsx | 5 ----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 88ed85e..3717a09 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "@mozilla/readability": "^0.4.2", "@react-spring/web": "^9.4.5", "@types/jest": "^27.5.1", - "@types/jsdom": "^16.2.14", "@types/node": "^17.0.35", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.5", @@ -19,7 +18,6 @@ "fuse.js": "^6.6.2", "husky": "^8.0.1", "interweave": "^13.0.0", - "jsdom": "^19.0.0", "lint-staged": "^12.4.2", "luxon": "^2.4.0", "next": "^12.1.6", diff --git a/pages/api/reader.ts b/pages/api/reader.ts index d9475cf..99ae54c 100644 --- a/pages/api/reader.ts +++ b/pages/api/reader.ts @@ -2,7 +2,7 @@ import { Readability } from '@mozilla/readability'; import { NextApiRequest, NextApiResponse } from 'next'; import { JSDOM } from 'jsdom'; -type Data = { +type Data = { /** article title */ title: string; /** author metadata */ @@ -22,7 +22,7 @@ type Data = { export default async ( request: NextApiRequest, - response: NextApiResponse + response: NextApiResponse> ) => { const url = request.query.url as string; const article = await fetch(url); diff --git a/pages/library/[id].tsx b/pages/library/[id].tsx index 192bb8f..90eba11 100644 --- a/pages/library/[id].tsx +++ b/pages/library/[id].tsx @@ -16,11 +16,6 @@ const fetchArticle = async (book: Book) => { const reader = await fetch(`/api/reader?url=${book.source_url}`); const markup = await reader.json(); - // const dom = new JSDOM(markup.content, { - // url: book.source_url, - // includeNodeLocations: true, - // storageQuota: 10000000, - // }); return markup.content; }; From 802278a13e8fe6707bb5fde714127406c988bdbb Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 13:36:22 -0400 Subject: [PATCH 5/9] Minify and clean readable article HTML --- package.json | 3 +++ pages/api/reader.ts | 40 +++++++++++++++++++++++++++++++++------- pages/library/[id].tsx | 7 ++++--- yarn.lock | 10 ++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 3717a09..7a781d4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@mozilla/readability": "^0.4.2", "@react-spring/web": "^9.4.5", "@types/jest": "^27.5.1", + "@types/jsdom": "^16.2.14", "@types/node": "^17.0.35", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.5", @@ -16,8 +17,10 @@ "formik": "^2.2.9", "framer-motion": "^6.3.4", "fuse.js": "^6.6.2", + "htmlclean": "^3.0.8", "husky": "^8.0.1", "interweave": "^13.0.0", + "jsdom": "^19.0.0", "lint-staged": "^12.4.2", "luxon": "^2.4.0", "next": "^12.1.6", diff --git a/pages/api/reader.ts b/pages/api/reader.ts index 99ae54c..aa5c3d7 100644 --- a/pages/api/reader.ts +++ b/pages/api/reader.ts @@ -1,6 +1,8 @@ +import whitespace from 'dom-whitespace'; import { Readability } from '@mozilla/readability'; import { NextApiRequest, NextApiResponse } from 'next'; import { JSDOM } from 'jsdom'; +import htmlclean from 'htmlclean'; type Data = { /** article title */ @@ -22,19 +24,43 @@ type Data = { export default async ( request: NextApiRequest, - response: NextApiResponse> + response: NextApiResponse ) => { + /** + * Fetch the article HTML as text from source URL + */ const url = request.query.url as string; const article = await fetch(url); const markup = await article.text(); - const doc = new JSDOM(markup, { - url: url, - includeNodeLocations: true, - storageQuota: 10000000, - }); + /** + * Create a JSDOM object, which provides a virtual DOM interface to the HTML. + * The document object will be accessible via `doc.window.document`. + */ + const doc = new JSDOM(markup); + /** + * Create a Readability object, which will parse the HTML and extract the + * most likely article content. + * `reader.content` will return stringified HTML, which can be used to + * render the article content as markup. + * `reader.textContent` will return the text itself, with all HTML stripped. + */ const reader = new Readability(doc.window.document).parse(); - response.status(200).send(reader); + /** + * Convert the Readability HTML back to a JSDOM object for manipulation. + */ + const readerDoc = new JSDOM(reader.content); + + /** + * Passing the document fragment which contains our article content, + * clean it in order to strip errant whitespace, such as newlines, + * hard-coded indents and spaces, and space between HTML tags. + */ + const minifiedMarkup: string = htmlclean( + readerDoc.window.document.body.innerHTML + ); + + response.status(200).send(minifiedMarkup); }; diff --git a/pages/library/[id].tsx b/pages/library/[id].tsx index 90eba11..411746b 100644 --- a/pages/library/[id].tsx +++ b/pages/library/[id].tsx @@ -14,10 +14,9 @@ import { usePreventGestureDefault } from '../../utils'; const fetchArticle = async (book: Book) => { const reader = await fetch(`/api/reader?url=${book.source_url}`); + const markup = await reader.text(); - const markup = await reader.json(); - - return markup.content; + return markup; }; function Article() { @@ -35,6 +34,8 @@ function Article() { useEffect(() => { if (book) { fetchArticle(book).then((b) => { + console.log(b); + // console.log(b); setArticle(b); }); diff --git a/yarn.lock b/yarn.lock index 8b62eb3..e94bcbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -897,6 +897,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-whitespace@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-whitespace/-/dom-whitespace-0.1.2.tgz#b35b2dad9d81f2d985a8e3c251d3d252fcc6c006" + integrity sha512-cZOgQgWdqSyoVZCsdK/gMy0Nxi+u0p0WiyMi9VkQzyd+2skJwxyi8Wct7U0om5tOqToMaPdznKcDUnSps7zdag== + domexception@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -1317,6 +1322,11 @@ html-encoding-sniffer@^3.0.0: dependencies: whatwg-encoding "^2.0.0" +htmlclean@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/htmlclean/-/htmlclean-3.0.8.tgz#cea451cf5399d4018386a57129489f2d630e62b0" + integrity sha512-pxe6KHAQFvn407iNVNs8jpQ43BSy0w2VJ7DOUrbl/wOOy33RgDR1IcOplYqseQBBcdJLEozzeL9RziGCdK2Zsg== + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" From 8982c59bc50dca7ede8e2c5920c07aefe3fde61a Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 14:47:20 -0400 Subject: [PATCH 6/9] Match highlights against content and add a class --- pages/api/reader.ts | 36 +++++++---------- pages/library/[id].tsx | 87 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/pages/api/reader.ts b/pages/api/reader.ts index aa5c3d7..7141164 100644 --- a/pages/api/reader.ts +++ b/pages/api/reader.ts @@ -1,30 +1,18 @@ -import whitespace from 'dom-whitespace'; -import { Readability } from '@mozilla/readability'; -import { NextApiRequest, NextApiResponse } from 'next'; -import { JSDOM } from 'jsdom'; import htmlclean from 'htmlclean'; +import { JSDOM } from 'jsdom'; +import { NextApiRequest, NextApiResponse } from 'next'; + +import { Readability } from '@mozilla/readability'; -type Data = { - /** article title */ - title: string; - /** author metadata */ - byline: string; - /** content direction */ - dir: string; - /** HTML of processed article content */ - content: T; - /** text content of the article (all HTML removed) */ - textContent: string; - /** length of an article, in characters */ - length: number; - /** article description, or short excerpt from the content */ - excerpt: string; - siteName: string; +export type ReaderResponse = { + markup: string; + text: string; + source: string; }; export default async ( request: NextApiRequest, - response: NextApiResponse + response: NextApiResponse ) => { /** * Fetch the article HTML as text from source URL @@ -62,5 +50,9 @@ export default async ( readerDoc.window.document.body.innerHTML ); - response.status(200).send(minifiedMarkup); + response.status(200).send({ + markup: minifiedMarkup, + text: reader.textContent, + source: reader.content, + }); }; diff --git a/pages/library/[id].tsx b/pages/library/[id].tsx index 411746b..15d46fc 100644 --- a/pages/library/[id].tsx +++ b/pages/library/[id].tsx @@ -9,12 +9,16 @@ import Page from '../../components/Page'; import language from '../../constants/language'; import { READWISE_TOKEN_LOCALSTORAGE_KEY } from '../../constants/values'; import { useBooks, useHighlights } from '../../lib/readwise'; -import { Book } from '../../lib/readwise/types'; +import { Book, Highlight } from '../../lib/readwise/types'; import { usePreventGestureDefault } from '../../utils'; +import { Interweave, MatcherInterface, MatchResponse } from 'interweave'; +import { ReaderResponse } from '../api/reader'; +import { useRef } from 'react'; -const fetchArticle = async (book: Book) => { +const fetchArticle = async (book: Book): Promise => { const reader = await fetch(`/api/reader?url=${book.source_url}`); - const markup = await reader.text(); + const markup = await reader.json(); + console.log(markup); return markup; }; @@ -23,21 +27,83 @@ function Article() { const { value: token } = useLocalstorage(READWISE_TOKEN_LOCALSTORAGE_KEY); const { bookmarks, loading } = useHighlights(); const { books, loading: loadingBooks, error } = useBooks(token); + const highlightRefs = useRef([]); + + const matcher = (bookmark: Highlight): MatcherInterface => { + return { + inverseName: 'noFoo', + propName: 'foo', + match(string): MatchResponse { + // if ( + // bookmark.text.includes('This captures something') && + // string.includes('This captures something') + // ) { + // console.log({ + // string: string, + // highlight: bookmark.text, + // }); + // } + + function escapeRegExp(stringToGoIntoTheRegex) { + return stringToGoIntoTheRegex.replace( + /[-\/\\^$*+?.()|[\]{}]/g, + '\\$&' + ); + } + + const regex = new RegExp(escapeRegExp(bookmark.text)); + + console.log(regex); + + const result = string.match(regex); + + if (!result) { + return null; + } + + console.log(result); + + return { + index: result.index!, + length: result[0].length, + match: result[0], + className: 'highlight', + valid: true, + }; + }, + createElement(children, props) { + return ( + + (highlightRefs.current[highlightRefs.current.length + 1] = el) + } + > + {children} + + ); + }, + asTag() { + return 'span'; + }, + }; + }; + + const matchers = bookmarks.map((bookmark, i) => { + return matcher(bookmark); + }); const book = books.find(({ id }) => id === bookmarks[0].book_id); const [pan, setPan] = useState(false); const controls = useAnimation(); - const [article, setArticle] = useState(null); + const [article, setArticle] = useState(null); useEffect(() => { if (book) { fetchArticle(book).then((b) => { - console.log(b); - - // console.log(b); - setArticle(b); + setArticle(b.markup); }); } }, [book]); @@ -96,7 +162,8 @@ function Article() { /> - {bookmarks.map((bookmark, i) => { + ; + {/* {bookmarks.map((bookmark, i) => { return i < 5 ? ( ) : null; - })} + })} */} ); } From fced9bc345397f7055492482b6d58cd249743596 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 18:26:34 -0400 Subject: [PATCH 7/9] Revise readwise types --- lib/readwise/types.ts | 84 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/lib/readwise/types.ts b/lib/readwise/types.ts index 3e092d8..09d2ffb 100644 --- a/lib/readwise/types.ts +++ b/lib/readwise/types.ts @@ -1,6 +1,34 @@ -export interface Highlight { - id: number; +/** + * The following are type definitions for Readwise. + * + * [API Documentation](https://readwise.io/api_deets) + */ + +// A const for a type definition extending RawBook. Omit the `text` field. +// export interface Book extends Omit { + +type GenericId = string | number; + +/** + * A tag can be applied to highlights and books. + */ +export interface Tag { + id: GenericId; + name: string; +} + +/** + * Stub for a highlight. Detailed highlights should use the `Highlight` type. + */ +export interface RawHighlight { + id: GenericId; text: string; +} + +/** + * Detailed highlight object. Extends `RawHighlight`. + */ +export interface Highlight extends RawHighlight { note: string; location: string; location_type: string; @@ -8,17 +36,26 @@ export interface Highlight { url: string; color: string; updated: string; - book_id: number; + book_id: GenericId; tags: Tag[]; } -export interface Tag { - id: number; - name: string; +/** + * Stub for a book. Detailed books should use the `Book` type. + */ +export interface RawBook { + id: GenericId; + text: string; } -export interface Book { - id: number; +/** + * Detailed book object. Extends `RawBook`. + * + * *This type is consumed by various "source types", which are not always webpages.* + */ + +export interface Book extends Omit { + id: GenericId; title: string; author: string; category: string; @@ -33,12 +70,31 @@ export interface Book { tags: Tag[]; } -export interface RawHighlight { - id: string; - text: string; +/** + * `GET` request body for the Readwise Highlights API. + */ +export interface FetchBookmarksRequest { + id: GenericId; + token: string; } -export interface RawBook { - id: string; - text: string; +/** + * `GET` response body for the Readwise Highlights API. + */ +export interface FetchBookmarksResponse { + list: Record; +} + +/** + * `GET` request body for the Readwise Books API. + */ +export interface FetchBooksRequest { + token: string; +} + +/** + * `GET` response body for the Readwise Books API. + */ +export interface FetchBooksResponse { + list: Record; } From a0be2f4f702c63b5e0087a1918d638c68ba04c76 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 18:26:47 -0400 Subject: [PATCH 8/9] Minor refactoring and cleanup --- components/Page.tsx | 8 +-- constants/language.tsx | 4 ++ constants/values.ts | 1 + lib/readwise/index.ts | 148 ++++++++++++++++++----------------------- pages/library/[id].tsx | 35 ++-------- utils/index.ts | 16 +++++ 6 files changed, 96 insertions(+), 116 deletions(-) diff --git a/components/Page.tsx b/components/Page.tsx index f7df97a..08fe4e5 100644 --- a/components/Page.tsx +++ b/components/Page.tsx @@ -1,9 +1,9 @@ import { animate as framer, AnimationControls, motion } from 'framer-motion'; -import { Interweave, match, MatcherInterface, MatchResponse } from 'interweave'; +import { Interweave, MatcherInterface, MatchResponse } from 'interweave'; import { FC, useEffect, useRef } from 'react'; import language from '../constants/language'; -import { Book, Highlight } from '../lib/readwise/types'; +import { Highlight } from '../lib/readwise/types'; interface Props { pageIndex: number; @@ -29,8 +29,8 @@ const Page: FC = ({ const highlightRef = useRef(null); const matcher: MatcherInterface = { - inverseName: 'noFoo', - propName: 'foo', + inverseName: 'noMark', + propName: 'mark', match(string): MatchResponse { const result = string.match(bookmark.text); diff --git a/constants/language.tsx b/constants/language.tsx index f7d7f7f..9caa8f8 100644 --- a/constants/language.tsx +++ b/constants/language.tsx @@ -25,6 +25,10 @@ const language = { ), + coverImage: { + alt: (title: string) => + `A featured image related to the link titled ${title}`, + }, readwiseLink: 'View on Readwise', highlight: { eyebrow: (index: number) => `${index}`, diff --git a/constants/values.ts b/constants/values.ts index 93fae05..c1c422f 100644 --- a/constants/values.ts +++ b/constants/values.ts @@ -1,3 +1,4 @@ export const THEME_COLOR = '#295122'; export const READWISE_TOKEN_LOCALSTORAGE_KEY = 'g:readwise_token'; +export const READWISE_API_BASE_URL = 'https://readwise.io/api/'; diff --git a/lib/readwise/index.ts b/lib/readwise/index.ts index 2f0fef9..79f7afa 100644 --- a/lib/readwise/index.ts +++ b/lib/readwise/index.ts @@ -1,80 +1,74 @@ -import { READWISE_TOKEN_LOCALSTORAGE_KEY } from './../../constants/values'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useLocalstorage } from 'rooks'; import useSWR, { mutate } from 'swr'; -import { Book, Highlight, RawBook, RawHighlight } from './types'; - -interface FetchBookmarksRequest { - name?: string; - state?: string; - count?: number; - id?: number | string; - token?: string; -} - -interface FetchBookmarksResponse { - list: Record; -} - -interface FetchBooksRequest { - name?: string; - state?: string; - count?: number; - token?: string; -} - -interface FetchBooksResponse { - list: Record; -} - -const BASE_URL = 'https://readwise.io/api/'; - -export const request = async ( - url, - method: 'POST' | 'GET', +import { + READWISE_API_BASE_URL, + READWISE_TOKEN_LOCALSTORAGE_KEY, +} from '../../constants/values'; +import { + Book, + FetchBookmarksRequest, + FetchBookmarksResponse, + FetchBooksRequest, + FetchBooksResponse, + Highlight, +} from './types'; + +/** + * Fetches data from the Readwie API. + * @param url The Readwise API endpoint to hit. Omit the leading slash. + * @param method The method to use for the request. + * @param token The Readwise user access token to use for the request. + * @param options Additional options to pass to the request. + * @returns The response body as a JSON object. + */ +const request = async ( + url: string, + method: 'POST' | 'GET' | 'DELETE', token: string, - options?: object -) => { - return fetch(BASE_URL + url, { + options?: RequestInit +): Promise => { + const response = await fetch(READWISE_API_BASE_URL + url, { method: method, headers: { Authorization: `Token ${token}`, 'content-type': 'application/json', + ...options.headers, }, ...options, }); -}; -export const auth = async (token: string) => - await fetch('https://readwise.io/api/v2/auth', { - method: 'GET', - headers: { - Authorization: `Token ${token}`, - 'content-type': 'application/json', - }, - }); + const json = await response.json(); + return json; +}; + +/** + * Verify the that saved Readwise token is valid. + * @param token The user's Readwise access token. + * @returns unknown + */ +// TODO: Figure out what the response is and type the return value accoringly. +export const verifyAuth = async (token: string) => + await request('v2/auth', 'GET', token); + +/** + * Fetch all highlights from a given book. + * @param args.id The book ID to fetch highlights from. + * @param args.token The user's Readwise access token. + * @returns `Highlight[]` – The user's highlights. + */ export const fetchHighlights = async ({ - name, - state, - count, id, token, -}: FetchBookmarksRequest = {}): Promise> => { - const response = await fetch( - BASE_URL + `v2/highlights?page_size=1000&book_id=${id}`, - { - method: 'GET', - headers: { - Authorization: `Token ${token}`, - 'content-type': 'application/json', - }, - } +}: FetchBookmarksRequest): Promise => { + const result = await request( + `v2/highlights?page_size=1000&book_id=${id}`, + 'GET', + token ); - - const result = await response.json(); const results = result.results as FetchBookmarksResponse; const bookmarks: Array = Object.values(results).map((item) => ({ @@ -94,15 +88,21 @@ export const fetchHighlights = async ({ return bookmarks; }; +/** + * React hook to fetch the user's Readwise bookmarks. + * @returns An object with the data and loading state. + */ export function useHighlights() { - const { value: token } = useLocalstorage(READWISE_TOKEN_LOCALSTORAGE_KEY); + const { value: token }: { value: string } = useLocalstorage( + READWISE_TOKEN_LOCALSTORAGE_KEY + ); const router = useRouter(); const id = router.query.id as string; const { data, error, isValidating } = useSWR, any>( id ? 'v2/highlights' : null, - () => fetchHighlights({ id, token: token as string }), + () => fetchHighlights({ id, token }), { revalidateIfStale: false, revalidateOnFocus: false, @@ -130,20 +130,16 @@ export function useHighlights() { }; } -export async function fetchBooks({ token }: FetchBooksRequest = {}): Promise< - Array -> { - const response = await request( +export async function fetchBooks({ + token, +}: FetchBooksRequest): Promise> { + const response = await request( 'v2/books?category=articles&page_size=500', 'GET', token ); - const result = await response.json(); - - const results = result.results as FetchBooksResponse; - - const books: Array = Object.values(results).map((item) => ({ + const books: Array = Object.values(response).map((item) => ({ id: item.id, title: item.title, author: item.author, @@ -163,12 +159,6 @@ export async function fetchBooks({ token }: FetchBooksRequest = {}): Promise< } export function useBooks(token: string) { - // const { value: token } = useLocalstorage('g:readwise_token'); - - // useEffect(() => { - // mutate('v2/books'); - // }, [token]); - const { data, error, isValidating } = useSWR( 'v2/books', () => fetchBooks({ token: token }), @@ -195,9 +185,3 @@ export function useBooks(token: string) { error: error, }; } - -export const CmsClient: { - fetchHighlights(): Promise>; -} = { - fetchHighlights, -}; diff --git a/pages/library/[id].tsx b/pages/library/[id].tsx index 15d46fc..289da73 100644 --- a/pages/library/[id].tsx +++ b/pages/library/[id].tsx @@ -1,19 +1,16 @@ import { useAnimation } from 'framer-motion'; -import { useState } from 'react'; +import { Interweave, MatcherInterface, MatchResponse } from 'interweave'; +import { useEffect, useRef, useState } from 'react'; import { useLocalstorage } from 'rooks'; import { useGesture } from '@use-gesture/react'; -import { useEffect } from 'react'; -import Page from '../../components/Page'; import language from '../../constants/language'; import { READWISE_TOKEN_LOCALSTORAGE_KEY } from '../../constants/values'; import { useBooks, useHighlights } from '../../lib/readwise'; import { Book, Highlight } from '../../lib/readwise/types'; -import { usePreventGestureDefault } from '../../utils'; -import { Interweave, MatcherInterface, MatchResponse } from 'interweave'; +import { escapeForRegExp, usePreventGestureDefault } from '../../utils'; import { ReaderResponse } from '../api/reader'; -import { useRef } from 'react'; const fetchArticle = async (book: Book): Promise => { const reader = await fetch(`/api/reader?url=${book.source_url}`); @@ -34,35 +31,13 @@ function Article() { inverseName: 'noFoo', propName: 'foo', match(string): MatchResponse { - // if ( - // bookmark.text.includes('This captures something') && - // string.includes('This captures something') - // ) { - // console.log({ - // string: string, - // highlight: bookmark.text, - // }); - // } - - function escapeRegExp(stringToGoIntoTheRegex) { - return stringToGoIntoTheRegex.replace( - /[-\/\\^$*+?.()|[\]{}]/g, - '\\$&' - ); - } - - const regex = new RegExp(escapeRegExp(bookmark.text)); - - console.log(regex); - + const regex = new RegExp(escapeForRegExp(bookmark.text)); const result = string.match(regex); if (!result) { return null; } - console.log(result); - return { index: result.index!, length: result[0].length, @@ -157,7 +132,7 @@ function Article() {
diff --git a/utils/index.ts b/utils/index.ts index 3eb35a9..1ae6f4b 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,8 +1,15 @@ import { useEffect } from 'react'; +/** + * Determine whether the current environment is development or production. + * @returns boolean: `true` if development, `false` if production + */ export const isDevelopment = () => !process.env.NODE_ENV || process.env.NODE_ENV === 'development'; +/** + * Hook to prevent the browser from registering default gesture events. + */ export const usePreventGestureDefault = () => { useEffect(() => { const handler = (e) => e.preventDefault(); @@ -16,3 +23,12 @@ export const usePreventGestureDefault = () => { }; }, []); }; + +/** + * Escape special characters used by RegExp, in order to safely + * use regex against the supplied string. + * @param string The string to escape for RegExp. + * @returns The escaped string. + */ +export const escapeForRegExp = (string: string) => + string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); From d5c3ea32b668383973878ed0f83ce03325e7f118 Mon Sep 17 00:00:00 2001 From: Devin Halladay Date: Thu, 2 Jun 2022 23:46:02 -0600 Subject: [PATCH 9/9] Fix type definition for FetchBooksResponse --- lib/readwise/index.ts | 8 ++++---- lib/readwise/types.ts | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/readwise/index.ts b/lib/readwise/index.ts index 79f7afa..6fb0c5e 100644 --- a/lib/readwise/index.ts +++ b/lib/readwise/index.ts @@ -35,7 +35,7 @@ const request = async ( headers: { Authorization: `Token ${token}`, 'content-type': 'application/json', - ...options.headers, + ...options?.headers, }, ...options, }); @@ -133,20 +133,20 @@ export function useHighlights() { export async function fetchBooks({ token, }: FetchBooksRequest): Promise> { - const response = await request( + const { results } = await request( 'v2/books?category=articles&page_size=500', 'GET', token ); - const books: Array = Object.values(response).map((item) => ({ + const books: Array = Object.values(results).map((item) => ({ id: item.id, title: item.title, author: item.author, category: item.category, source: item.source, num_highlights: item.num_highlights, - last_highlight_at: item.last_highlighted_at, + last_highlight_at: item.last_highlight_at, updated: item.updated, cover_image_url: item.cover_image_url, highlights_url: item.highlights_url, diff --git a/lib/readwise/types.ts b/lib/readwise/types.ts index 09d2ffb..4ae300e 100644 --- a/lib/readwise/types.ts +++ b/lib/readwise/types.ts @@ -96,5 +96,8 @@ export interface FetchBooksRequest { * `GET` response body for the Readwise Books API. */ export interface FetchBooksResponse { - list: Record; + count: number; + next: any; + previous: any; + results: Book[]; }