From 10a21adc335122a5c654a2b816ff38daf04b183e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 27 Nov 2023 22:54:39 +0800 Subject: [PATCH 001/278] detect language from content if locale not found in metadata --- packages/readabilityjs/Readability.js | 35 +++++++++++++------ packages/readabilityjs/package.json | 1 + yarn.lock | 48 +++++++++++++++++---------- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 2934841bd..6c9403e03 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -23,6 +23,7 @@ var parseSrcset = require('parse-srcset'); var htmlEntities = require('html-entities') const axios = require("axios"); +const cld = require('cld'); /** Checks whether an element is a wrapper for tweet */ const hasTweetInChildren = element => { @@ -3029,17 +3030,26 @@ Readability.prototype = { return false; }, - _getLanguage: function(code) { - if (!code) { - // Default to English - return 'English'; - } - + _getLanguage: async function(locale, content, languageCode) { try { + if (!locale) { + // detect language from the html content + if (content) { + const languages = (await cld.detect(content, { isHTML: true })).languages; + console.log('Detected languages: ', languages); + if (languages.length > 0) { + return languages[0].name; + } + } + } + + console.log('Trying to get language name from locale and language code: ', locale, languageCode); let lang = new Intl.DisplayNames(['en'], {type: 'language'}); - return lang.of(code.split('_')[0]); - } catch { - return 'English' + const code = locale || languageCode.replace('_', '-') || 'en'; + return lang.of(code); + } catch (error) { + console.error('Failed to get language', error); + return 'English'; } }, @@ -3112,12 +3122,15 @@ Readability.prototype = { } var textContent = articleContent.textContent; + const content = this._serializer(articleContent); + console.log('language code', metadata.locale, this._languageCode); + const language = await this._getLanguage(metadata.locale, content, this._languageCode); return { title: this._articleTitle, // remove \n and extra spaces and trim the string byline: author ? author.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim() : null, dir: this._articleDir, - content: this._serializer(articleContent), + content, textContent: textContent, length: textContent.length, excerpt: metadata.excerpt, @@ -3125,7 +3138,7 @@ Readability.prototype = { siteIcon: metadata.siteIcon, previewImage: metadata.previewImage, publishedDate, - language: this._getLanguage(metadata.locale || this._languageCode), + language, }; } }; diff --git a/packages/readabilityjs/package.json b/packages/readabilityjs/package.json index 41c440cdc..0cccc78f5 100644 --- a/packages/readabilityjs/package.json +++ b/packages/readabilityjs/package.json @@ -39,6 +39,7 @@ "sinon": "^7.3.2" }, "dependencies": { + "cld": "^2.9.1", "html-entities": "^2.3.2", "parse-srcset": "^1.0.2" }, diff --git a/yarn.lock b/yarn.lock index 1091ebb97..7347c7690 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10811,6 +10811,15 @@ classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +cld@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/cld/-/cld-2.9.1.tgz#0c6685672d9f4612dfeb75eabfdd17bf282a87a6" + integrity sha512-GfNd5TM2He+pCsEhJWBgnLAoB0pnn6jCoYom7pzv04wpF2d54C0nasHZ8BW59KijwnVRmEBcI+imRnVbj5fVjw== + dependencies: + glob "7" + node-addon-api "*" + underscore "^1.12.1" + clean-css@^4.2.3: version "4.2.4" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" @@ -15130,6 +15139,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@7, glob@^7.0.5: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" @@ -15189,18 +15210,6 @@ glob@^10.2.2: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" -glob@^7.0.5: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^8.0.0: version "8.0.3" resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" @@ -20814,6 +20823,11 @@ node-abort-controller@^3.0.1: resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e" integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw== +node-addon-api@*: + version "7.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" + integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== + node-addon-api@^1.2.0: version "1.7.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" @@ -27285,16 +27299,16 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +underscore@^1.12.1, underscore@^1.13.6, underscore@~1.13.2: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + underscore@^1.13.4, underscore@^1.9.1: version "1.13.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== -underscore@^1.13.6, underscore@~1.13.2: - version "1.13.6" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" - integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== - undici@^4.9.3: version "4.14.1" resolved "https://registry.yarnpkg.com/undici/-/undici-4.14.1.tgz#7633b143a8a10d6d63335e00511d071e8d52a1d9" From 77110640a13270b01b2132cf3202493c915d7685 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 27 Nov 2023 23:09:20 +0800 Subject: [PATCH 002/278] detect language from content --- packages/readabilityjs/Readability.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/readabilityjs/Readability.js b/packages/readabilityjs/Readability.js index 6c9403e03..d2f671311 100644 --- a/packages/readabilityjs/Readability.js +++ b/packages/readabilityjs/Readability.js @@ -3030,22 +3030,19 @@ Readability.prototype = { return false; }, - _getLanguage: async function(locale, content, languageCode) { + _getLanguage: async function(content, locale, languageCode) { try { - if (!locale) { - // detect language from the html content - if (content) { - const languages = (await cld.detect(content, { isHTML: true })).languages; - console.log('Detected languages: ', languages); - if (languages.length > 0) { - return languages[0].name; - } - } + let code = locale || languageCode.replace('_', '-') || 'en'; + + // detect language from the html content + const languages = (await cld.detect(content, { isHTML: true })).languages; + console.log('Detected languages: ', languages); + if (languages.length > 0) { + code = languages[0].code; } - console.log('Trying to get language name from locale and language code: ', locale, languageCode); + console.log('Getting language name from code: ', code); let lang = new Intl.DisplayNames(['en'], {type: 'language'}); - const code = locale || languageCode.replace('_', '-') || 'en'; return lang.of(code); } catch (error) { console.error('Failed to get language', error); @@ -3123,8 +3120,7 @@ Readability.prototype = { var textContent = articleContent.textContent; const content = this._serializer(articleContent); - console.log('language code', metadata.locale, this._languageCode); - const language = await this._getLanguage(metadata.locale, content, this._languageCode); + const language = await this._getLanguage(content, metadata.locale, this._languageCode); return { title: this._articleTitle, // remove \n and extra spaces and trim the string From 8e77c047bd6ae7cc43851f06786aad316849f6ef Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 19 Dec 2023 14:25:55 +0800 Subject: [PATCH 003/278] add endpoint to prune items from folder older than some days --- packages/api/src/routers/svc/links.ts | 61 +++++++++++++++++++++++ packages/api/src/services/library_item.ts | 13 ++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/api/src/routers/svc/links.ts b/packages/api/src/routers/svc/links.ts index fcf06d34d..7d6e1d0db 100644 --- a/packages/api/src/routers/svc/links.ts +++ b/packages/api/src/routers/svc/links.ts @@ -2,8 +2,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import express from 'express' +import { LessThan } from 'typeorm' +import { LibraryItemState } from '../../entity/library_item' import { readPushSubscription } from '../../pubsub' import { createPageSaveRequest } from '../../services/create_page_save_request' +import { deleteLibraryItemsByAdmin } from '../../services/library_item' import { logger } from '../../utils/logger' interface CreateLinkRequestMessage { @@ -11,6 +14,31 @@ interface CreateLinkRequestMessage { userId: string } +type PruneMessage = { + expireInDays: number + folder?: string + state?: LibraryItemState +} + +const isPruneMessage = (obj: any): obj is PruneMessage => 'expireInDays' in obj + +const getPruneMessage = (msgStr: string): PruneMessage => { + try { + const obj = JSON.parse(msgStr) as unknown + if (isPruneMessage(obj)) { + return obj + } + } catch (err) { + console.log('error deserializing event: ', { msgStr, err }) + } + + // default to prune following folder items older than 30 days + return { + folder: 'following', + expireInDays: 30, + } +} + export function linkServiceRouter() { const router = express.Router() @@ -52,5 +80,38 @@ export function linkServiceRouter() { } }) + router.post('/prune', async (req, res) => { + logger.info('prune expired items in folder') + + const { message: msgStr, expired } = readPushSubscription(req) + + if (!msgStr) { + return res.status(200).send('Bad Request') + } + + if (expired) { + logger.info('discarding expired message') + return res.status(200).send('Expired') + } + + const pruneMessage = getPruneMessage(msgStr) + const expireTime = pruneMessage.expireInDays * 1000 * 60 * 60 * 24 // convert days to milliseconds + + try { + const result = await deleteLibraryItemsByAdmin({ + folder: pruneMessage.folder, + state: pruneMessage.state, + updatedAt: LessThan(new Date(Date.now() - expireTime)), + }) + logger.info('prune result', result) + + return res.sendStatus(200) + } catch (error) { + logger.error('error prune items', error) + + return res.sendStatus(500) + } + }) + return router } diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 4864055da..6f9eafa80 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -1,6 +1,6 @@ import { ExpressionToken, LiqeQuery } from '@omnivore/liqe' import { DateTime } from 'luxon' -import { DeepPartial, ObjectLiteral } from 'typeorm' +import { DeepPartial, FindOptionsWhere, ObjectLiteral } from 'typeorm' import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity' import { EntityLabel } from '../entity/entity_label' import { Highlight } from '../entity/highlight' @@ -1062,3 +1062,14 @@ export const deleteLibraryItemsByUserId = async (userId: string) => { userId ) } + +export const deleteLibraryItemsByAdmin = async ( + criteria: FindOptionsWhere +) => { + return authTrx( + async (tx) => tx.withRepository(libraryItemRepository).delete(criteria), + undefined, + undefined, + 'admin' + ) +} From 1386e97396994c8499ca71c321712f1dd9bf51ae Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Dec 2023 19:36:08 -0500 Subject: [PATCH 004/278] Add documentation for highlight and fix bug Issue: https://github.com/omnivore-app/omnivore/issues/2996 --- .../web/lib/highlights/highlightGenerator.ts | 48 +++++++++- packages/web/lib/highlights/useSelection.tsx | 93 ++++++++++++++++++- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/packages/web/lib/highlights/highlightGenerator.ts b/packages/web/lib/highlights/highlightGenerator.ts index c852d67af..19d1131bb 100644 --- a/packages/web/lib/highlights/highlightGenerator.ts +++ b/packages/web/lib/highlights/highlightGenerator.ts @@ -24,6 +24,13 @@ const maxDeepPatchDistance = 4000 const maxDeepPatchThreshhold = 0.5 const maxSurroundingTextLength = 2000 +/** + * Wrapper for text node + * + * @property startIndex - offset from the start of article for which the text begins + * @property node - the text node + * @property startsParagraph - whether a new paragraph is started + */ type TextNode = { startIndex: number node: Node @@ -35,12 +42,18 @@ type ArticleTextContent = { articleText: string } +/** + * Location of a highlight as starting/ending offset from the start of article. The end offset is non-inclusive + */ export type HighlightLocation = { id: string start: number end: number } +/** + * Relevant attributes of a highlight node created in DOM + */ export type HighlightNodeAttributes = { prefix: string suffix: string @@ -73,6 +86,16 @@ function nodeAttributesFromHighlight( return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip) } +/** + * Make a highlight on the highlight selection and return its attributes + * + * @param patch - {@link generateDiffPatch|patch} of the highlight location + * @param id - highlight id + * @param withNote - whether highlight has notes + * @param customColor - color of highlight + * @param tooltip + * @returns relevant highlight attributes + */ export function makeHighlightNodeAttributes( patch: string, id: string, @@ -184,6 +207,15 @@ export function makeHighlightNodeAttributes( } } +/** + * Given a text selection by user, annotate the article around the selection and + * produce a {@link https://github.com/google/diff-match-patch | diff patch} + * + * The diff patch is used for identifying the selection/highlight location + * + * @param range text selection range + * @returns diff patch + */ export function generateDiffPatch(range: Range): string { const articleContentElement = document.getElementById(articleContainerId) if (!articleContentElement) @@ -217,7 +249,12 @@ export function generateDiffPatch(range: Range): string { return patch } -export function wrapHighlightTagAroundRange(range: Range): [number, number] { +/** + * Retrieve starting and ending offsets to the highlight selection + * @param range highlight selection + * @returns starting offset and ending offset (non-inclusive) + */ +export function retrieveOffsetsForSelection(range: Range): [number, number] { const patch = generateDiffPatch(range) const { highlightTextStart, highlightTextEnd } = selectionOffsetsFromPatch(patch) @@ -265,6 +302,15 @@ const getArticleTextNodes = ( return { textNodes, articleText } } +/** + * Return the offsets to the selection/highlight + * + * @param patch {@link generateDiffPatch|diff patch} identifying a selection/highlight location + * @returns + * - highlightTextStart - The start of highlight, offset from the start of article by characters + * - highlightTextEnd - The end of highlight (non-inclusive), offset from the start of article by characters + * - matchingHighlightContent - the matched highlight + */ const selectionOffsetsFromPatch = ( patch: string ): { diff --git a/packages/web/lib/highlights/useSelection.tsx b/packages/web/lib/highlights/useSelection.tsx index fcb02d0dc..c32db86d2 100644 --- a/packages/web/lib/highlights/useSelection.tsx +++ b/packages/web/lib/highlights/useSelection.tsx @@ -1,11 +1,21 @@ import { useCallback, useEffect, useState } from 'react' import { - wrapHighlightTagAroundRange, + retrieveOffsetsForSelection, getHighlightElements, HighlightLocation, } from './highlightGenerator' import type { SelectionAttributes } from './highlightHelpers' +/** + * Get the range of text with {@link SelectionAttributes} that user has selected + * + * Event Handlers for detecting/using new highlight selection are registered + * + * If the new highlight selection overlaps with existing highlights, the new selection is merged. + * + * @param highlightLocations existing highlights + * @returns selection range and its setter + */ export function useSelection( highlightLocations: HighlightLocation[] ): [SelectionAttributes | null, (x: SelectionAttributes | null) => void] { @@ -55,7 +65,7 @@ export function useSelection( } const { range, isReverseSelected, selection } = result - const [selectionStart, selectionEnd] = wrapHighlightTagAroundRange(range) + const [selectionStart, selectionEnd] = retrieveOffsetsForSelection(range) const rangeRect = rangeToPos(range, isReverseSelected) let shouldCancelSelection = false @@ -82,8 +92,8 @@ export function useSelection( } if ( - selectionStart <= highlightLocation.end && - highlightLocation.start <= selectionEnd + selectionStart < highlightLocation.end && + highlightLocation.start < selectionEnd ) { overlapHighlights.push(highlightLocation) } @@ -234,9 +244,77 @@ async function makeSelectionRange(): Promise< range.startContainer === selection.focusNode && range.endOffset === selection.anchorOffset + /** + * Edge case: + * If the selection ends on range endContainer (or startContainer in reverse select) but no text is selected (i.e. selection ends at + * an empty area), the preceding text is highlighted due to range normalizing. + * This is a visual bug and would sometimes lead to weird highlight behavior during removal. + */ + const selectionEndNode = selection.focusNode + const selectionEndOffset = selection.focusOffset + const selectionStartNode = isReverseSelected ? range.endContainer : range.startContainer + + if (selectionEndNode?.nodeType === Node.TEXT_NODE) { + const selectionEndNodeEdgeIndex = isReverseSelected ? selectionEndNode.textContent?.length : 0 + + if (selectionStartNode !== selectionEndNode && + selectionEndOffset == selectionEndNodeEdgeIndex) { + clipRangeToNearestAnchor(range, selectionEndNode, isReverseSelected) + } + } + return isRangeAllowed ? { range, isReverseSelected, selection } : undefined } +/** + * Clip selection range to the beginning/end of the adjacent anchor element + * + * @param range selection range + * @param selectionEndNode the node where the selection ended at + * @param isReverseSelected + */ +const clipRangeToNearestAnchor = ( + range: Range, + selectionEndNode: Node, + isReverseSelected: boolean +) => { + let nearestAnchorElement = selectionEndNode.parentElement + while (nearestAnchorElement !== null && !nearestAnchorElement.hasAttribute('data-omnivore-anchor-idx')) { + nearestAnchorElement = nearestAnchorElement.parentElement; + } + if (!nearestAnchorElement) { + throw Error('Unable to find nearest anchor element for node: ' + selectionEndNode) + } + let anchorId = Number(nearestAnchorElement.getAttribute('data-omnivore-anchor-idx')!) + let adjacentAnchorId, adjacentAnchor, adjacentAnchorOffset + if (isReverseSelected) { + // move down to find adjacent anchor node and clip at its beginning + adjacentAnchorId = anchorId + 1 + adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0] + adjacentAnchorOffset = 0 + range.setStart(adjacentAnchor, adjacentAnchorOffset) + } else { + // move up to find adjacent anchor node and clip at its end + do { + adjacentAnchorId = --anchorId + adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0] + } while (adjacentAnchor.contains(selectionEndNode)) + if (adjacentAnchor.textContent) { + let lastTextNodeChild = adjacentAnchor.lastChild + while (!!lastTextNodeChild && lastTextNodeChild.nodeType !== Node.TEXT_NODE) { + lastTextNodeChild = lastTextNodeChild.previousSibling; + } + adjacentAnchor = lastTextNodeChild + adjacentAnchorOffset = adjacentAnchor?.nodeValue?.length ?? 0 + } else { + adjacentAnchorOffset = 0 + } + if (adjacentAnchor) { + range.setEnd(adjacentAnchor, adjacentAnchorOffset) + } + } +} + export type RangeEndPos = { left: number top: number @@ -246,6 +324,13 @@ export type RangeEndPos = { height: number } +/** + * Return coordinates of the screen area occupied by the last line of user selection + * + * @param range range of user selection + * @param getFirst whether to get first line of user selection. Get last if false (default) + * @returns {RangeEndPos} selection coordinates + */ const rangeToPos = (range: Range, getFirst = false): RangeEndPos => { if (typeof window === 'undefined' || !range) { return { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 } From 59961ece07dc6a19b0bfb502b07d398ade6ba0ee Mon Sep 17 00:00:00 2001 From: cdev147 <154508819+cdev147@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:56:47 +0530 Subject: [PATCH 005/278] Update useKeyboardShortcuts.ts fixed issue #1879 "Keyboard shortcuts modal is ugly" -based on the labels modal --- .../keyboardShortcuts/useKeyboardShortcuts.ts | 169 +++++++++--------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts index c91c65a8e..604e151f4 100644 --- a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts @@ -1,6 +1,6 @@ -import { useEffect, useCallback, useReducer, useMemo } from 'react' +import { useEffect, useCallback, useReducer, useMemo } from 'react'; -const disabledTargets = ['INPUT', 'TEXTAREA'] +const disabledTargets = ['INPUT', 'TEXTAREA']; /* Usage: commands: KeyboardCommand[] = [{ @@ -10,19 +10,19 @@ const disabledTargets = ['INPUT', 'TEXTAREA'] ] */ -const KBAR_KEYS = ['control', 'meta', 'v', 'i', 's', 'n', '[', ']'] +const KBAR_KEYS = ['control', 'meta', 'v', 'i', 's', 'n', '[', ']']; type KeyPressed = { - [name: string]: boolean -} + [name: string]: boolean; +}; export type KeyboardCommand = { - shortcutKeys: string[] - callback: () => void - actionDescription: string - shortcutKeyDescription: string - ignoreDisabledTargets?: boolean -} + shortcutKeys: string[]; + callback: () => void; + actionDescription: string; + shortcutKeyDescription: string; + ignoreDisabledTargets?: boolean; +}; enum ActionType { SET_KEY_DOWN, @@ -35,128 +35,131 @@ const keysReducer = ( ): KeyPressed => { switch (action.type) { case ActionType.SET_KEY_DOWN: - return { ...state, [action.key]: true } + return { ...state, [action.key]: true }; case ActionType.SET_KEY_UP: - return { ...state, [action.key]: false } + return { ...state, [action.key]: false }; default: - return state + return state; } -} +}; + +// Labels +const useInitialKeyMapping = (commands: KeyboardCommand[]): KeyPressed => { + const currentKeys: KeyPressed = {}; + commands.forEach((command) => { + command.shortcutKeys.forEach((key) => { + const aliases = key.split('|'); + aliases.forEach((k) => { + currentKeys[k.toLowerCase()] = false; + }); + }); + }); + + KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)); + return currentKeys; +}; export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { - const initalKeyMapping = useMemo(() => { - const currentKeys: KeyPressed = {} - commands.forEach((command) => { - command.shortcutKeys.forEach((key) => { - const aliases = key.split('|') - aliases.forEach((k) => { - currentKeys[k.toLowerCase()] = false - }) - }) - }) + const initalKeyMapping = useMemo(() => useInitialKeyMapping(commands), [commands]); - KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)) - return currentKeys - }, [commands]) - - const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping) + const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping); const metaPressed = useCallback(() => { - return keys['meta'] === true - }, [keys]) + return keys['meta'] === true; + }, [keys]); const applyCommands = useCallback( (updatedKeys: KeyPressed, disabled?: boolean) => { - let commandApplied = false + let commandApplied = false; commands.forEach((command) => { if (disabled && !command.ignoreDisabledTargets) { - return + return; } - const tempState = { ...updatedKeys } + const tempState = { ...updatedKeys }; const arePressed = command.shortcutKeys.every((key) => { - const aliases = key.split('|') + const aliases = key.split('|'); for (const k of aliases) { if (tempState[k]) { - delete tempState[k] - return true + delete tempState[k]; + return true; } } - return false - }) + return false; + }); // apply callback if 1)corresponding buttons are pressed 2) no other buttons are pressed if (arePressed && !Object.values(tempState).some((k) => k)) { - command.callback() - commandApplied = true + command.callback(); + commandApplied = true; // remove applied keys from state to prevent Key Lifetime smashes command.shortcutKeys.forEach((key, index) => { // don't remove a base key to allow non-stop actions - if (index === 0 && command.shortcutKeys.length > 1) return - const aliases = key.split('|') + if (index === 0 && command.shortcutKeys.length > 1) return; + const aliases = key.split('|'); for (const l of aliases) { - setKeys({ type: ActionType.SET_KEY_UP, key: l }) + setKeys({ type: ActionType.SET_KEY_UP, key: l }); } - }) + }); } - }) - return commandApplied + }); + return commandApplied; }, [commands] - ) + ); const keydownListener = useCallback( (keydownEvent: any) => { - const { target } = keydownEvent - if (!keydownEvent.key) return - const key = keydownEvent.key.toLowerCase() - if (keys[key] === undefined) return + const { target } = keydownEvent; + if (!keydownEvent.key) return; + const key = keydownEvent.key.toLowerCase(); + if (keys[key] === undefined) return; if (keys[key] === false) { if (key === 'k' && metaPressed()) { // not setting the K value because meta is already pressed - return + return; } - setKeys({ type: ActionType.SET_KEY_DOWN, key }) + setKeys({ type: ActionType.SET_KEY_DOWN, key }); } const commandApplied = applyCommands( { ...keys, [key]: true }, disabledTargets.includes(target.tagName) - ) + ); if (commandApplied) { - keydownEvent.preventDefault() + keydownEvent.preventDefault(); } }, [applyCommands, keys, metaPressed] - ) + ); const keyupListener = useCallback( (keyupEvent: any) => { - if (!keyupEvent.key) return - const key = keyupEvent.key.toLowerCase() - if (keys[key] === undefined) return + if (!keyupEvent.key) return; + const key = keyupEvent.key.toLowerCase(); + if (keys[key] === undefined) return; if (keys[key] === true) { - setTimeout(() => setKeys({ type: ActionType.SET_KEY_UP, key }), 800) + setTimeout(() => setKeys({ type: ActionType.SET_KEY_UP, key }), 800); } }, [keys] - ) + ); useEffect(() => { if (typeof window === 'undefined') { - return + return; } - window.addEventListener('keydown', keydownListener, true) - return () => window.removeEventListener('keydown', keydownListener, true) - }, [keydownListener]) + window.addEventListener('keydown', keydownListener, true); + return () => window.removeEventListener('keydown', keydownListener, true); + }, [keydownListener]); useEffect(() => { if (typeof window === 'undefined') { - return + return; } - window.addEventListener('keyup', keyupListener, true) - return () => window.removeEventListener('keyup', keyupListener, true) - }, [keyupListener]) -} + window.addEventListener('keyup', keyupListener, true); + return () => window.removeEventListener('keyup', keyupListener, true); + }, [keyupListener]); +}; export const useConfirmListener = ( onConfirm?: () => void, @@ -165,28 +168,28 @@ export const useConfirmListener = ( ): void => { const keyHandleConfirm = useCallback( (e: KeyboardEvent) => { - const key = e.key.toLowerCase() + const key = e.key.toLowerCase(); if (onConfirm && key === 'enter') { // if control key is required we make sure CTRL on // meta is pressed when pressing enter. if (ctrlOnConfirm && !e.metaKey && !e.ctrlKey) { - return + return; } - onConfirm() + onConfirm(); } else if (onCancel && key === 'escape') { - onCancel() + onCancel(); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [onCancel, onConfirm] - ) + ); useEffect(() => { - if (typeof window === 'undefined') return + if (typeof window === 'undefined') return; - window.addEventListener('keydown', keyHandleConfirm) + window.addEventListener('keydown', keyHandleConfirm); return () => { - window.removeEventListener('keydown', keyHandleConfirm) - } - }, [keyHandleConfirm]) -} + window.removeEventListener('keydown', keyHandleConfirm); + }; + }, [keyHandleConfirm]); +}; From b23ddcb6bcb026fa70b2916d7c9d01f4fc4ce6eb Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 Dec 2023 19:36:08 -0500 Subject: [PATCH 006/278] Add documentation for highlight and fix bug Issue: https://github.com/omnivore-app/omnivore/issues/2996 --- .../web/lib/highlights/highlightGenerator.ts | 48 +++++++++- packages/web/lib/highlights/useSelection.tsx | 94 ++++++++++++++++++- 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/packages/web/lib/highlights/highlightGenerator.ts b/packages/web/lib/highlights/highlightGenerator.ts index c852d67af..19d1131bb 100644 --- a/packages/web/lib/highlights/highlightGenerator.ts +++ b/packages/web/lib/highlights/highlightGenerator.ts @@ -24,6 +24,13 @@ const maxDeepPatchDistance = 4000 const maxDeepPatchThreshhold = 0.5 const maxSurroundingTextLength = 2000 +/** + * Wrapper for text node + * + * @property startIndex - offset from the start of article for which the text begins + * @property node - the text node + * @property startsParagraph - whether a new paragraph is started + */ type TextNode = { startIndex: number node: Node @@ -35,12 +42,18 @@ type ArticleTextContent = { articleText: string } +/** + * Location of a highlight as starting/ending offset from the start of article. The end offset is non-inclusive + */ export type HighlightLocation = { id: string start: number end: number } +/** + * Relevant attributes of a highlight node created in DOM + */ export type HighlightNodeAttributes = { prefix: string suffix: string @@ -73,6 +86,16 @@ function nodeAttributesFromHighlight( return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip) } +/** + * Make a highlight on the highlight selection and return its attributes + * + * @param patch - {@link generateDiffPatch|patch} of the highlight location + * @param id - highlight id + * @param withNote - whether highlight has notes + * @param customColor - color of highlight + * @param tooltip + * @returns relevant highlight attributes + */ export function makeHighlightNodeAttributes( patch: string, id: string, @@ -184,6 +207,15 @@ export function makeHighlightNodeAttributes( } } +/** + * Given a text selection by user, annotate the article around the selection and + * produce a {@link https://github.com/google/diff-match-patch | diff patch} + * + * The diff patch is used for identifying the selection/highlight location + * + * @param range text selection range + * @returns diff patch + */ export function generateDiffPatch(range: Range): string { const articleContentElement = document.getElementById(articleContainerId) if (!articleContentElement) @@ -217,7 +249,12 @@ export function generateDiffPatch(range: Range): string { return patch } -export function wrapHighlightTagAroundRange(range: Range): [number, number] { +/** + * Retrieve starting and ending offsets to the highlight selection + * @param range highlight selection + * @returns starting offset and ending offset (non-inclusive) + */ +export function retrieveOffsetsForSelection(range: Range): [number, number] { const patch = generateDiffPatch(range) const { highlightTextStart, highlightTextEnd } = selectionOffsetsFromPatch(patch) @@ -265,6 +302,15 @@ const getArticleTextNodes = ( return { textNodes, articleText } } +/** + * Return the offsets to the selection/highlight + * + * @param patch {@link generateDiffPatch|diff patch} identifying a selection/highlight location + * @returns + * - highlightTextStart - The start of highlight, offset from the start of article by characters + * - highlightTextEnd - The end of highlight (non-inclusive), offset from the start of article by characters + * - matchingHighlightContent - the matched highlight + */ const selectionOffsetsFromPatch = ( patch: string ): { diff --git a/packages/web/lib/highlights/useSelection.tsx b/packages/web/lib/highlights/useSelection.tsx index fcb02d0dc..f1de3563a 100644 --- a/packages/web/lib/highlights/useSelection.tsx +++ b/packages/web/lib/highlights/useSelection.tsx @@ -1,11 +1,21 @@ import { useCallback, useEffect, useState } from 'react' import { - wrapHighlightTagAroundRange, + retrieveOffsetsForSelection, getHighlightElements, HighlightLocation, } from './highlightGenerator' import type { SelectionAttributes } from './highlightHelpers' +/** + * Get the range of text with {@link SelectionAttributes} that user has selected + * + * Event Handlers for detecting/using selection are registered + * + * If the selection overlaps with existing highlights + * + * @param highlightLocations existing highlights + * @returns selection range and its setter + */ export function useSelection( highlightLocations: HighlightLocation[] ): [SelectionAttributes | null, (x: SelectionAttributes | null) => void] { @@ -55,7 +65,7 @@ export function useSelection( } const { range, isReverseSelected, selection } = result - const [selectionStart, selectionEnd] = wrapHighlightTagAroundRange(range) + const [selectionStart, selectionEnd] = retrieveOffsetsForSelection(range) const rangeRect = rangeToPos(range, isReverseSelected) let shouldCancelSelection = false @@ -82,8 +92,8 @@ export function useSelection( } if ( - selectionStart <= highlightLocation.end && - highlightLocation.start <= selectionEnd + selectionStart < highlightLocation.end && + highlightLocation.start < selectionEnd ) { overlapHighlights.push(highlightLocation) } @@ -234,9 +244,78 @@ async function makeSelectionRange(): Promise< range.startContainer === selection.focusNode && range.endOffset === selection.anchorOffset + /** + * Edge case: + * If the selection ends on range endContainer (or startContainer in reverse select) but no text is selected (i.e. selection ends at + * an empty area), the preceding text is highlighted due to range normalizing. + * This is a visual bug and would sometimes lead to weird highlight behavior during removal. + * See X for more detail + */ + const selectionEndNode = selection.focusNode + const selectionEndOffset = selection.focusOffset + const selectionStartNode = isReverseSelected ? range.endContainer : range.startContainer + + if (selectionEndNode?.nodeType === Node.TEXT_NODE) { + const selectionEndNodeEdgeIndex = isReverseSelected ? selectionEndNode.textContent?.length : 0 + + if (selectionStartNode !== selectionEndNode && + selectionEndOffset == selectionEndNodeEdgeIndex) { + clipRangeToNearestAnchor(range, selectionEndNode, isReverseSelected) + } + } + return isRangeAllowed ? { range, isReverseSelected, selection } : undefined } +/** + * Clip selection range to the beginning/end of the adjacent anchor element + * + * @param range selection range + * @param selectionEndNode the node where the selection ended at + * @param isReverseSelected + */ +const clipRangeToNearestAnchor = ( + range: Range, + selectionEndNode: Node, + isReverseSelected: boolean +) => { + let nearestAnchorElement = selectionEndNode.parentElement + while (nearestAnchorElement !== null && !nearestAnchorElement.hasAttribute('data-omnivore-anchor-idx')) { + nearestAnchorElement = nearestAnchorElement.parentElement; + } + if (!nearestAnchorElement) { + throw Error('Unable to find nearest anchor element for node: ' + selectionEndNode) + } + let anchorId = Number(nearestAnchorElement.getAttribute('data-omnivore-anchor-idx')!) + let adjacentAnchorId, adjacentAnchor, adjacentAnchorOffset + if (isReverseSelected) { + // move down to find adjacent anchor node and clip at its beginning + adjacentAnchorId = anchorId + 1 + adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0] + adjacentAnchorOffset = 0 + range.setStart(adjacentAnchor, adjacentAnchorOffset) + } else { + // move up to find adjacent anchor node and clip at its end + do { + adjacentAnchorId = --anchorId + adjacentAnchor = document.querySelectorAll(`[data-omnivore-anchor-idx='${adjacentAnchorId}']`)[0] + } while (adjacentAnchor.contains(selectionEndNode)) + if (adjacentAnchor.textContent) { + let lastTextNodeChild = adjacentAnchor.lastChild + while (!!lastTextNodeChild && lastTextNodeChild.nodeType !== Node.TEXT_NODE) { + lastTextNodeChild = lastTextNodeChild.previousSibling; + } + adjacentAnchor = lastTextNodeChild + adjacentAnchorOffset = adjacentAnchor?.nodeValue?.length ?? 0 + } else { + adjacentAnchorOffset = 0 + } + if (adjacentAnchor) { + range.setEnd(adjacentAnchor, adjacentAnchorOffset) + } + } +} + export type RangeEndPos = { left: number top: number @@ -246,6 +325,13 @@ export type RangeEndPos = { height: number } +/** + * Return coordinates of the screen area occupied by the last line of user selection + * + * @param range range of user selection + * @param getFirst whether to get first line of user selection. Get last if false (default) + * @returns {RangeEndPos} selection coordinates + */ const rangeToPos = (range: Range, getFirst = false): RangeEndPos => { if (typeof window === 'undefined' || !range) { return { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 } From 45add1643cfb8df95b3e644824eefc0a6a50df40 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 2 Jan 2024 02:00:08 -0500 Subject: [PATCH 007/278] small fix --- packages/web/lib/highlights/useSelection.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/web/lib/highlights/useSelection.tsx b/packages/web/lib/highlights/useSelection.tsx index afdd50a90..c32db86d2 100644 --- a/packages/web/lib/highlights/useSelection.tsx +++ b/packages/web/lib/highlights/useSelection.tsx @@ -249,10 +249,6 @@ async function makeSelectionRange(): Promise< * If the selection ends on range endContainer (or startContainer in reverse select) but no text is selected (i.e. selection ends at * an empty area), the preceding text is highlighted due to range normalizing. * This is a visual bug and would sometimes lead to weird highlight behavior during removal. -<<<<<<< HEAD -======= - * See X for more detail ->>>>>>> main */ const selectionEndNode = selection.focusNode const selectionEndOffset = selection.focusOffset From fbabb4ff7ea5e6202b03ebc0449b4987ad4a1e67 Mon Sep 17 00:00:00 2001 From: LennardSchwarz Date: Wed, 3 Jan 2024 08:23:11 +0100 Subject: [PATCH 008/278] Add german translation iOS --- .../Resources/de.lproj/Localizable.strings | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 apple/OmnivoreKit/Sources/Views/Resources/de.lproj/Localizable.strings diff --git a/apple/OmnivoreKit/Sources/Views/Resources/de.lproj/Localizable.strings b/apple/OmnivoreKit/Sources/Views/Resources/de.lproj/Localizable.strings new file mode 100644 index 000000000..28d4c831c --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Resources/de.lproj/Localizable.strings @@ -0,0 +1,208 @@ +// Unit test Entry -- Do not remove this or add entries before this one. +// This allows us to check for syntax errors in this file with a unit test +"unitTestLeadingEntry" = "Nur zu Testzwecken."; + +// share extension +"saveArticleSavedState" = "In Omnivore gespeichert"; +"saveArticleProcessingState" = "Wird in Omnivore gespeichert"; +"extensionAppUnauthorized" = "Bitte melde dich in der App bei Omnivore an, bevor du deinen ersten Link speicherst."; +"saveToOmnivore" = "In Omnivore speichern"; + +// audio player +"audioPlayerReplay" = "Wiederholen"; + +// Highlights List Card +"highlightCardHighlightByOther" = "Markierung von "; +"highlightCardNoHighlightsOnPage" = "Du hast keine Markierungen auf dieser Seite hinzugefügt."; + +// Labels View +"labelsViewAssignNameColor" = "Weise einen Namen und eine Farbe zu."; +"createLabelMessage" = "Erstelle ein neues Label"; +"labelsPurposeDescription" = "Nutze Labels, um Sammlungen von Links zu erstellen."; +"labelNamePlaceholder" = "Label Name"; + +// Manage Account View +"manageAccountDelete" = "Konto löschen"; +"manageAccountResetCache" = "Cache zurücksetzen"; +"manageAccountConfirmDeleteMessage" = "Bist du sicher, dass du dein Konto löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden."; + +// Newsletter Emails View +"newsletterEmailsExisting" = "Vorhandene E-Mails (Tippen zum Kopieren)"; +"createNewEmailMessage" = "Erstelle eine neue E-Mail-Adresse"; +"newslettersDescription" = "Füge PDFs zu deiner Bibliothek hinzu oder abonniere Newsletter mit einer Omnivore-E-Mail-Adresse."; +"noCurrentSubscriptionsMessage" = "Du hast aktuell keine Abonnements."; + +// Profile View +"profileConfirmLogoutMessage" = "Bist du sicher, dass du dich abmelden möchtest?"; + +// Devices View +"devicesTokensTitle" = "Registrierte Geräte-Tokens (wischen zum Entfernen)"; +"devicesCreated" = "Erstellt: "; + +// Push Notification Settings +"notificationsEnabled" = "Benachrichtigungen aktiviert"; +"notificationsExplainer" = "Das Aktivieren von Push-Benachrichtigungen gibt Omnivore die Erlaubnis, Benachrichtigungen zu senden,\ndu entscheidest jedoch, welche Benachrichtigungen gesendet werden."; +"notificationsTriggerExplainer" = "Push-Benachrichtigungen werden durch deine \n[Kontoregeln](https://omnivore.app/settings/rules) ausgelöst, die du online bearbeiten kannst."; +"notificationsEnable" = "Push-Benachrichtigungen aktivieren?"; +"notificationsGeneralExplainer" = "Erhalte Benachrichtigungen, wenn Newsletter-Links in deinem Posteingang eintreffen. Oder erhalte Erinnerungen, die du über unsere Erweiterung festgelegt hast."; +"notificationsOptionDeny" = "Nein, danke"; +"notificationsOptionEnable" = "Ja, bitte"; + +// Community Modal +"communityHeadline" = "Hilf mit, die Omnivore-Community aufzubauen"; +"communityAppstoreReview" = "Bewerte uns im AppStore"; +"communityTweet" = "Tweete über Omnivore"; +"communityFollowTwitter" = "Folge uns auf Twitter"; +"communityJoinDiscord" = "Tritt unserem Discord bei"; +"communityStarGithub" = "Gib uns ein Stern auf GitHub"; + +// Clubs View +"clubsLearnTitle" = "Erfahre mehr über Clubs"; +"clubsName" = "Club Name"; +"clubsCreate" = "Erstelle einen neuen Club"; +"clubsYours" = "Deine Clubs"; +"clubsNotAMemberMessage" = "Du bist kein Mitglied eines Clubs.\nErstelle einen neuen Club und sende den Einladungslink an deine Freunde, um loszulegen.\n\nWährend der Beta bist du darauf beschränkt, drei Clubs zu erstellen, und jeder Club\nkann maximal zwölf Benutzer haben."; +"clubsErrorCopying" = "Fehler beim Kopieren des Einladungs-Links"; +"clubsAdminDenyViewing" = "Der Admin dieses Clubs erlaubt es nicht, alle Mitglieder einzusehen."; +"clubsNoMembers" = "Dieser Club hat keine Mitglieder. Füge Benutzer zu deinem Club hinzu, indem du\nihnen den Einladungslink sendest."; +"clubsLeave" = "Club verlassen"; +"clubsLeaveConfirm" = "Bist du sicher, dass du diesen Club verlassen möchtest? Es werden keine Daten gelöscht, aber du wirst keine Empfehlungen mehr von diesem Club erhalten."; +"clubsNoneJoined" = "Du bist keinem Club beigetreten, in dem du posten kannst.\nTritt einem Club bei oder erstelle deinen eigenen, um Artikel zu empfehlen."; + +// Subscriptions +"subscriptionsErrorRetrieving" = "Entschuldigung, wir konnten deine Abonnements nicht abrufen."; +"subscriptionsNone" = "Du hast aktuell keine Abonnements."; +//"subscriptions.error.retrieving" = "Zuletzt erhalten: (updatedDate.formatted())"; // unused for now + +// Text to Speech +"texttospeechLanguageDefault" = "Standardsprache"; +"texttospeechSettingsAudio" = "Audio Einstellungen"; +"texttospeechSettingsEnablePrefetch" = "Audio Vorladen aktivieren"; +"texttospeechBetaSignupInProcess" = "Anmeldung zur Beta läuft"; +"texttospeechBetaRealisticVoiceLimit" = "Du nimmst an der Beta für ultra-realistische Stimmen teil. Während der Beta kannst du 10.000 Wörter Audio pro Tag anhören."; +"texttospeechBetaRequestReceived" = "Deine Anfrage, an der Demo für ultra-realistische Stimmen teilzunehmen, wurde erhalten. Du wirst per E-Mail informiert, wenn ein Platz verfügbar ist."; +"texttospeechBetaWaitlist" = "Ultra-realistische Stimmen sind derzeit in einer begrenzten Beta und nur für Englisch verfügbar. Das Aktivieren der Funktion wird dich zur Beta-Warteliste hinzufügen."; + +// Sign in/up +"registrationNoAccount" = "Du hast noch kein Konto?"; +"registrationForgotPassword" = "Passwort vergessen?"; +"registrationStatusCheck" = "Status überprüfen"; +"registrationUseDifferentEmail" = "Eine andere E-Mail verwenden?"; +"registrationFullName" = "Vollständiger Name"; +"registrationUsername" = "Benutzername"; +"registrationAlreadyHaveAccount" = "Du hast bereits ein Konto?"; +"registrationBio" = "Biografie (optional)"; +"registrationWelcome" = "Willkommen bei Omnivore!"; +"registrationUsernameAssignedPrefix" = "Dein Benutzername lautet:"; +"registrationChangeUsername" = "Benutzername ändern"; +"registrationEdit" = "Bearbeiten"; +"googleAuthButton" = "Mit Google fortfahren"; +"registrationViewSignUpHeadline" = "Registrieren"; +"loginErrorInvalidCreds" = "Die angegebenen Anmeldeinformationen sind ungültig."; + +// Recommendation +"recommendationToPrefix" = "An:"; +"recommendationAddNote" = "Eine Notiz hinzufügen (optional)"; +//"recommendationToPrefix" = "Füge deine (viewModel.highlightCount) Markierung(viewModel.highlightCount > 1 ? "en" : """; // unused for now +"recommendationError" = "Fehler beim Empfehlen dieser Seite"; + +// Web Reader +"readerCopyLink" = "Link kopieren"; +"readerSave" = "In Omnivore speichern"; +"readerError" = "Ein Fehler ist aufgetreten"; + +// Debug Menu +"menuDebugTitle" = "Debuggin Menü"; +"menuDebugApiEnv" = "API Umgebung:"; + +// Navigation +"navigationSelectLink" = "Wähle einen Link aus deiner Bibliothek"; +"navigationSelectSidebarToggle" = "Seitenleiste umschalten"; + +// Welcome View +"welcomeTitle" = "Read-it-later für anspruchsvolle Leser."; +"welcomeLearnMore" = "Mehr erfahren"; +"welcomeSignupAgreement" = "Mit deiner Anmeldung stimmst du den\n"; +"welcomeTitleTermsOfService" = "Nutzungsbedingungen"; +"welcomeTitleAndJoiner" = " und "; +"welcomeTitleEmailContinue" = "Mit E-Mail fortfahren"; + +// Keyboard Commands +"keyboardCommandDecreaseFont" = "Schriftgröße verkleinern"; +"keyboardCommandIncreaseFont" = "Schriftgröße vergrößern"; +"keyboardCommandDecreaseMargin" = "Rand verkleinern"; +"keyboardCommandIncreaseMargin" = "Rand vergrößern"; +"keyboardCommandDecreaseLineSpacing" = "Zeilenabstand verkleinern"; +"keyboardCommandIncreaseLineSpacing" = "Zeilenabstand vergrößern"; + +// Library +//"library.by.author.suffix" = "von (author)" // unused +//"Recommended by (byStr) in (inStr)" // unused + +// Generic +"genericSnooze" = "Schlummern"; +"genericClose" = "Schließen"; +"genericCreate" = "Erstellen"; +"genericConfirm" = "Bestätigen"; +"genericProfile" = "Profil"; +"genericNext" = "Weiter"; +"genericName" = "Name"; +"genericOk" = "Ok"; +"genericRetry" = "Erneut versuchen"; +"genericEmail" = "E-Mail"; +"genericPassword" = "Passwort"; +"genericSubmit" = "Absenden"; +"genericContinue" = "Fortfahren"; +"genericSend" = "Senden"; +"genericOptions" = "Optionen"; +"genericOpen" = "Öffnen"; +"genericChangeApply" = "Änderungen anwenden"; +"genericTitle" = "Titel"; +"genericAuthor" = "Autor"; +"genericDescription" = "Beschreibung"; +"genericSave" = "Speichern"; +"genericLoading" = "Lädt..."; +"genericFontFamily" = "Schriftart"; +"genericHighContrastText" = "Text in hohem Kontrast"; +"enableHighlightOnReleaseText" = "Automatisches Markieren aktivieren"; +"enableJustifyText" = "Text ausrichten"; +"genericFont" = "Schrift"; +"genericHighlight" = "Hervorheben"; +"labelsGeneric" = "Labels"; +"emailsGeneric" = "E-Mails"; +"subscriptionsGeneric" = "Abonnements"; +"textToSpeechGeneric" = "Text in Sprache"; +"privacyPolicyGeneric" = "Datenschutzrichtlinie"; +"termsAndConditionsGeneric" = "Geschäftsbedingungen"; +"feedbackGeneric" = "Feedback"; +"manageAccountGeneric" = "Konto verwalten"; +"logoutGeneric" = "Abmelden"; +"doneGeneric" = "Fertig"; +"cancelGeneric" = "Abbrechen"; +"exportGeneric" = "Exportieren"; +"inboxGeneric" = "Posteingang"; +"readLaterGeneric" = "Später lesen"; +"newslettersGeneric" = "Newsletter"; +"allGeneric" = "Alle"; +"archivedGeneric" = "Archiviert"; +"highlightedGeneric" = "Markiert"; +"filesGeneric" = "Dateien"; +"newestGeneric" = "Neueste"; +"oldestGeneric" = "Älteste"; +"longestGeneric" = "Längste"; +"shortestGeneric" = "Kürzeste"; +"recentlyReadGeneric" = "Kürzlich gelesen"; +"recentlyPublishedGeneric" = "Kürzlich veröffentlicht"; +"clubsGeneric" = "Clubs"; +"filterGeneric" = "Filter"; +"errorGeneric" = "Etwas ist schiefgelaufen, bitte versuche es erneut."; +"pushNotificationsGeneric" = "Push-Benachrichtigungen"; +"dismissButton" = "Verwerfen"; +"errorNetwork" = "Wir haben Probleme, eine Verbindung zum Internet herzustellen."; +"documentationGeneric" = "Dokumentation"; + +// TODO: search navigationTitle, toggle, section, button, Label, title: ", CreateProfileViewModel, TextField, .keyboardShortcut + +// Unit test Entry -- Do not remove this or add entries after this one. +// This allows us to check for syntax errors in this file with a unit test +"unitTestTrailingEntry" = "Nur zu Testzwecken."; \ No newline at end of file From 6e3cb7de9743d7bb52027f2ab5b1cb8761935378 Mon Sep 17 00:00:00 2001 From: LennardSchwarz Date: Wed, 3 Jan 2024 08:44:42 +0100 Subject: [PATCH 009/278] Add Android localization strings; Add LaunchScreen file for iOS --- .../app/src/main/res/values-de/strings.xml | 233 ++++++++++++++++++ apple/Resources/de.lproj/LaunchScreen.strings | 0 2 files changed, 233 insertions(+) create mode 100644 android/Omnivore/app/src/main/res/values-de/strings.xml create mode 100644 apple/Resources/de.lproj/LaunchScreen.strings diff --git a/android/Omnivore/app/src/main/res/values-de/strings.xml b/android/Omnivore/app/src/main/res/values-de/strings.xml new file mode 100644 index 000000000..afaefdef8 --- /dev/null +++ b/android/Omnivore/app/src/main/res/values-de/strings.xml @@ -0,0 +1,233 @@ + + Omnivore + Verpasse nie wieder eine großartige Lektüre + Mehr erfahren + Speichere Artikel und lies sie später in unserem ablenkungsfreien Reader. + Markieren + Kopieren + Anmerken + Entfernen + Markieren + Kopieren + Notiz + Kopieren + Notiz + + + Mit Apple fortfahren + Anmeldung... + + + Erstelle dein Profil + Laden... + Anmeldung abbrechen + Absenden + Name + Name + Benutzername + Benutzername + Bitte gib einen gültigen Namen und Benutzernamen ein. + + + Laden... + Zurück zum Social Login + Du hast noch kein Konto? + Passwort vergessen? + Anmelden + benutzer@email.com + E-Mail + Passwort + Passwort + Bitte gib eine E-Mail-Adresse und ein Passwort ein. + + + Wir haben eine Verifizierungs-E-Mail an %1$s gesendet. Bitte bestätige deine E-Mail und tippe dann auf den unten stehenden Knopf. + Status prüfen + Eine andere E-Mail verwenden? + Laden... + Zurück zu Social Login + Du hast bereits ein Konto? + Registrieren + benutzer@email.com + E-Mail + Passwort + Passwort + Name + Name + Name + Name + Bitte fülle alle Felder aus. + + + Mit Google fortfahren + Anmeldung... + + + Einstellungen für Self-Hosting aktualisiert. + Einstellungen für Self-Hosting zurückgesetzt. + Benutzername muss zwischen 4 und 15 Zeichen lang sein. + Benutzername darf nur Buchstaben und Zahlen enthalten. + Dieser Benutzername ist nicht verfügbar. + Entschuldigung, wir haben Probleme, eine Verbindung zum Server herzustellen. + Etwas ist schiefgelaufen. Bitte überprüfe deine E-Mail und dein Passwort und versuche es erneut. + Etwas ist schiefgelaufen. Bitte überprüfe deine Anmeldeinformationen und versuche es erneut. + Authentifizierung mit Google fehlgeschlagen. + Kein Authentifizierungstoken gefunden. + + + Laden... + Zurücksetzen + Zurück + Speichern + Mehr über Self-Hosting von Omnivore erfahren + API-Server + Webserver + Bitte gib die Adressen des API-Servers und des Webservers ein. + + + Schließen + Mit E-Mail fortfahren + Self-Hosting Optionen + + + Neues Label erstellen + Weise einen Namen und eine Farbe zu. + Erstellen + Abbrechen + Label-Name + + + Nach Label filtern + Labels setzen + Abbrechen + Suchen + Speichern + Erstelle ein neues Label mit dem Namen \"%1$s\" + Der angegebene Name ist zu lang (muss %1$d Zeichen oder weniger sein) + + + Labels + + + Bibliothek + + Suchen + + + Labels aktualisiert + Labels konnten nicht gesetzt werden + + + Notizbuch + Kopieren + Notizbuch kopiert + + + Notiz + Speichern + Abbrechen + + + Artikelnotizen + Notizen hinzufügen... + + + Hervorhebungen + Kopieren + Hervorhebung kopiert + Notiz hinzufügen... + Du hast dieser Seite keine Hervorhebungen hinzugefügt. + + + Schriftgröße: + Rand + Zeilenabstand + Thema: + Automatisch + Hoher Textkontrast + Text ausrichten + + + Wir konnten deinen Inhalt nicht abrufen. + Lese-Einstellungen + Notizbuch + Info bearbeiten + Notizbuch + Link öffnen + + + Im Browser öffnen + In Omnivore speichern + Link kopieren + Abbrechen + + + Link gespeichert + Fehler beim Speichern des Links + Link kopiert + + + Speichern + Jetzt lesen + Später lesen + Schließen + + + Speichern in Omnivore... + Du bist nicht angemeldet. Bitte melde dich an, bevor du speicherst. + Seite gespeichert + Fehler beim Speichern deiner Seite + + + Info bearbeiten + Labels bearbeiten + Archivieren + Aus dem Archiv wiederherstellen + Original teilen + Element entfernen + + + Abmelden + Bist du sicher, dass du dich abmelden möchtest? + Bestätigen + Abbrechen + + + Konto verwalten + Cache zurücksetzen + + + Einstellungen + + + Einstellungen + Dokumentation + Feedback + Datenschutzerklärung + Nutzungsbedingungen + Konto verwalten + Abmelden + + + Link hinzufügen + Link hinzufügen + Hinzufügen + Abbrechen + Aus Zwischenablage holen + Ungültiger Link + Fehler beim Speichern des Links! + Link erfolgreich gespeichert! + + + Du bist nicht angemeldet. Bitte melde dich an, bevor du speicherst. + + + Info bearbeiten + Titel + Autor + Beschreibung + Speichern + Abbrechen + Fehler beim Bearbeiten des Artikels! + Artikelinformationen erfolgreich aktualisiert! + \ No newline at end of file diff --git a/apple/Resources/de.lproj/LaunchScreen.strings b/apple/Resources/de.lproj/LaunchScreen.strings new file mode 100644 index 000000000..e69de29bb From 6e73b10771262ce4f12abdecb977bbbf59689fec Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 3 Jan 2024 17:38:12 +0800 Subject: [PATCH 010/278] if the query contains includes:, we want to search in all folders --- packages/api/src/utils/search.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/api/src/utils/search.ts b/packages/api/src/utils/search.ts index 4c068c4f2..871c1d984 100644 --- a/packages/api/src/utils/search.ts +++ b/packages/api/src/utils/search.ts @@ -1,7 +1,7 @@ import { LiqeQuery, parse } from '@omnivore/liqe' export const parseSearchQuery = (query: string): LiqeQuery => { - const searchQuery = query + let searchQuery = query .replace(/\W\s":/g, '') .replace('in:subscription', 'has:subscriptions') // compatibility with old search .replace('in:library', 'no:subscription') // compatibility with old search @@ -10,9 +10,14 @@ export const parseSearchQuery = (query: string): LiqeQuery => { // remove any quotes that are in the array value for example: label:"test","test2" -> label:"test,test2" .replace(/","/g, ',') + // if the query contains includes:, we want to search in all folders + if (searchQuery.includes('includes:')) { + searchQuery = `(${searchQuery}) in:all` + } + // by default we want to search in the inbox if no in: is specified if (!searchQuery.includes('in:')) { - return parse(`(${searchQuery}) in:inbox`) + searchQuery = `(${searchQuery}) in:inbox` } return parse(searchQuery) From 6237c9df5161d661d8765a489169b50788e40e63 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 3 Jan 2024 18:14:51 +0800 Subject: [PATCH 011/278] iOS: clear old database when running new version, better alignment of reader icons --- apple/Omnivore.xcodeproj/project.pbxproj | 8 ++--- .../Views/WebReader/WebReaderContainer.swift | 12 ++++--- .../Models/CoreData/StorageProvider.swift | 16 +++++++--- .../Services/DataService/DataService.swift | 31 ++++++++++--------- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index 6e53e68b2..a928eedd5 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1388,7 +1388,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.40.0; + MARKETING_VERSION = 1.41.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; @@ -1423,7 +1423,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.40.0; + MARKETING_VERSION = 1.41.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1478,7 +1478,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.40.0; + MARKETING_VERSION = 1.41.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1819,7 +1819,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.40.0; + MARKETING_VERSION = 1.41.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index a7535d14e..52af57555 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -152,19 +152,21 @@ struct WebReaderContainerView: View { label: { textToSpeechButtonImage } - ).buttonStyle(.plain) + ) + .buttonStyle(.plain) + .padding(.trailing, 4) ) } } var textToSpeechButtonImage: some View { if audioController.playbackError || audioController.state == .stopped || audioController.itemAudioProperties?.itemID != self.item.id { - return AnyView(Image.audioPlay.frame(width: 48, height: 48)) + return Image.audioPlay } if audioController.isPlayingItem(itemID: item.unwrappedID) { - return AnyView(Image.audioPause.frame(width: 48, height: 48)) + return Image.audioPause } - return AnyView(Image.audioPlay.frame(width: 48, height: 48)) + return Image.audioPlay } #endif @@ -299,7 +301,7 @@ struct WebReaderContainerView: View { } ) .buttonStyle(.plain) - .padding(.horizontal, 5) + .padding(.trailing, 5) .popover(isPresented: $showPreferencesPopover) { webPreferencesPopoverView .frame(maxWidth: 400, maxHeight: 475) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift index 558282ea5..30e8e9429 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -7,11 +7,9 @@ let logger = Logger(subsystem: "app.omnivore", category: "models") /// An `NSPersistentContainer` subclass that lives in the `Models` package so that /// the data model is looked for in the same package bundle (rather than the main bundle) public class PersistentContainer: NSPersistentContainer { - public static func make() -> PersistentContainer { - let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! - let model = NSManagedObjectModel(contentsOf: modelURL)! - let container = PersistentContainer(name: "DataModel", managedObjectModel: model) + public static var DatabaseFile = "store.sqlite" + public static func path() -> URL? { // Store the sqlite file in the app group container. // This allows shared access for app and app extensions. #if os(iOS) @@ -20,7 +18,15 @@ public class PersistentContainer: NSPersistentContainer { let appGroupID = "QJF2XZ86HB.app.omnivore.app" #endif let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) - let appGroupContainerURL = appGroupContainer?.appendingPathComponent("store.sqlite") + return appGroupContainer?.appendingPathComponent(DatabaseFile) + } + + public static func make() -> PersistentContainer { + let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! + let model = NSManagedObjectModel(contentsOf: modelURL)! + let container = PersistentContainer(name: "DataModel", managedObjectModel: model) + let appGroupContainerURL = path() + logger.debug("starting with sqlite container \(appGroupContainerURL?.absoluteString ?? "(nil)")") container.persistentStoreDescriptions.first!.url = appGroupContainerURL diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 54edeea16..a6743b4e0 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -53,6 +53,10 @@ public final class DataService: ObservableObject { } public init(appEnvironment: AppEnvironment, networker: Networker) { + if Self.isFirstTimeRunningNewAppBuild() { + Self.deleteLocalStorage() + } + self.appEnvironment = appEnvironment self.networker = networker self.persistentContainer = PersistentContainer.make() @@ -61,13 +65,9 @@ public final class DataService: ObservableObject { backgroundContext.automaticallyMergesChangesFromParent = true backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - if isFirstTimeRunningNewAppBuild() { - resetLocalStorage() - } else { - persistentContainer.loadPersistentStores { _, error in - if let error = error { - fatalError("Core Data store failed to load with error: \(error)") - } + persistentContainer.loadPersistentStores { _, error in + if let error = error { + fatalError("Core Data store failed to load with error: \(error)") } } } @@ -190,16 +190,17 @@ public final class DataService: ObservableObject { } } - public func resetLocalStorage() { - viewContext.perform { - // We want to specify the order of deleting items to better handle relationships - let entities = ["LibraryItem", "Viewer", "Filter", "Highlight", "NewsletterEmail", - "LinkedItemLabel", "RecentSearchItem", "Recommendation", "RecommendationGroup", "UserProfile"] - entities.forEach { entityName in - self.deleteAllEntities(entityName: entityName, inContext: self.viewContext) + public static func deleteLocalStorage() { + do { + if let storeURL = PersistentContainer.path() { + try FileManager.default.removeItem(atPath: storeURL.path) } + } catch { + print(error.localizedDescription) } + } + public func resetLocalStorage() { UserDefaults.standard.set(nil, forKey: UserDefaultKey.lastSelectedTabItem.rawValue) lastItemSyncTime = Date(timeIntervalSinceReferenceDate: 0) @@ -216,7 +217,7 @@ public final class DataService: ObservableObject { backgroundContext = persistentContainer.newBackgroundContext() } - private func isFirstTimeRunningNewAppBuild() -> Bool { + private static func isFirstTimeRunningNewAppBuild() -> Bool { let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String From cba83c159392c28792f5b74959cae2332e114b76 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 3 Jan 2024 12:56:26 +0800 Subject: [PATCH 012/278] Update the default Highlights query This query is more of what people want, all their created highlights, not just ones in their current inbox. Since a normal workflow would be: read + highlight, archive, review highlights. --- .../0152.do.update_highlights_saved_search.sql | 12 ++++++++++++ .../0152.undo.update_highlights_saved_search.sql | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100755 packages/db/migrations/0152.do.update_highlights_saved_search.sql create mode 100755 packages/db/migrations/0152.undo.update_highlights_saved_search.sql diff --git a/packages/db/migrations/0152.do.update_highlights_saved_search.sql b/packages/db/migrations/0152.do.update_highlights_saved_search.sql new file mode 100755 index 000000000..29fe3586e --- /dev/null +++ b/packages/db/migrations/0152.do.update_highlights_saved_search.sql @@ -0,0 +1,12 @@ +-- Type: DO +-- Name: update_highlights_saved_search +-- Description: Update highlights saved search to use all instead of inbox + +BEGIN; + +UPDATE omnivore.filters + SET filter = 'in:all has:highlights mode:highlights' + WHERE name = 'Highlights' + AND filter = 'has:highlights mode:highlights' ; + +COMMIT; diff --git a/packages/db/migrations/0152.undo.update_highlights_saved_search.sql b/packages/db/migrations/0152.undo.update_highlights_saved_search.sql new file mode 100755 index 000000000..a842c252d --- /dev/null +++ b/packages/db/migrations/0152.undo.update_highlights_saved_search.sql @@ -0,0 +1,12 @@ +-- Type: UNDO +-- Name: update_highlights_saved_search +-- Description: Update highlights saved search to use all instead of inbox + +BEGIN; + +UPDATE omnivore.filters + SET filter = 'has:highlights mode:highlights' + WHERE name = 'Highlights' + AND filter = 'in:all has:highlights mode:highlights' ; + +COMMIT; From a1ea20b964b7b8a7b75934e8876558c780c70557 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 3 Jan 2024 18:21:13 +0800 Subject: [PATCH 013/278] Update highlights query for new users --- packages/api/src/services/create_user.ts | 2 +- packages/web/components/templates/homeFeed/EmptyLibrary.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/src/services/create_user.ts b/packages/api/src/services/create_user.ts index a04ff8164..fdeb188c2 100644 --- a/packages/api/src/services/create_user.ts +++ b/packages/api/src/services/create_user.ts @@ -160,7 +160,7 @@ const createDefaultFiltersForUser = filter: 'in:inbox sort:read-desc is:reading', }, { name: 'Non-Feed Items', filter: 'no:subscription' }, - { name: 'Highlights', filter: 'has:highlights mode:highlights' }, + { name: 'Highlights', filter: 'in:all has:highlights mode:highlights' }, { name: 'Unlabeled', filter: 'no:label' }, { name: 'Oldest First', filter: 'sort:saved-asc' }, { name: 'Files', filter: 'type:file' }, diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index 23dfe3d7b..dc7ca5777 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -171,7 +171,9 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { return 'continue' case 'no:subscription': return 'non-feed' + // Handle both cases while we migrate case 'has:highlights mode:highlights': + case 'in:all has:highlights mode:highlights': return 'highlight' case 'no:label': return 'unlabeled' From 5eb19cda71c3bc6acabbd5c1190aaf9561601d5a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 2 Jan 2024 13:37:50 +0800 Subject: [PATCH 014/278] prune users in batch --- packages/api/src/routers/svc/user.ts | 4 +-- packages/api/src/services/user.ts | 44 +++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/api/src/routers/svc/user.ts b/packages/api/src/routers/svc/user.ts index ca6e9dbea..0c7fc5991 100644 --- a/packages/api/src/routers/svc/user.ts +++ b/packages/api/src/routers/svc/user.ts @@ -5,7 +5,7 @@ import express from 'express' import { LessThan } from 'typeorm' import { StatusType } from '../../entity/user' import { readPushSubscription } from '../../pubsub' -import { deleteUsers } from '../../services/user' +import { batchDeleteUsers } from '../../services/user' import { corsConfig } from '../../utils/corsConfig' import { logger } from '../../utils/logger' @@ -52,7 +52,7 @@ export function userServiceRouter() { const subTime = cleanupMessage.subDays * 1000 * 60 * 60 * 24 // convert days to milliseconds try { - const result = await deleteUsers({ + const result = await batchDeleteUsers({ status: StatusType.Deleted, updatedAt: LessThan(new Date(Date.now() - subTime)), // subDays ago }) diff --git a/packages/api/src/services/user.ts b/packages/api/src/services/user.ts index f3d01e489..848c12b0c 100644 --- a/packages/api/src/services/user.ts +++ b/packages/api/src/services/user.ts @@ -1,6 +1,6 @@ import { DeepPartial, FindOptionsWhere, In } from 'typeorm' import { StatusType, User } from '../entity/user' -import { authTrx } from '../repository' +import { authTrx, getRepository, queryBuilderToRawSql } from '../repository' import { userRepository } from '../repository/user' import { SetClaimsRole } from '../utils/dictionary' @@ -47,3 +47,45 @@ export const createUsers = async (users: DeepPartial[]) => { SetClaimsRole.ADMIN ) } + +export const batchDeleteUsers = async (criteria: FindOptionsWhere) => { + const userQb = getRepository(User).createQueryBuilder().where(criteria) + const userCountSql = queryBuilderToRawSql(userQb.select('COUNT(1)')) + const userSubQuery = queryBuilderToRawSql(userQb.select('id INTO user_ids')) + + const batchSize = 1000 + const start = new Date().toISOString() + const sql = ` + -- Set batch size + DO $$ + DECLARE + batch_size INT := ${batchSize}; + user_ids UUID[]; + BEGIN + -- Loop through batches of users + FOR i IN 0..CEIL((${userCountSql})) * 1.0 / batch_size) - 1 LOOP + -- GET batch of user ids + ${userSubQuery} LIMIT ${batchSize} OFFSET i * batch_size; + + -- Loop through batches of items + FOR j IN 0..CEIL((SELECT COUNT(1) FROM omnivore.library_item WHERE user_id = ANY(user_ids))) * 1.0 / batch_size) - 1 LOOP + -- Delete batch of items + DELETE FROM omnivore.library_item + WHERE user_id = ANY(user_ids) + AND updated_at < '${start}' + LIMIT ${batchSize}; + END LOOP; + + -- Delete the batch of users + DELETE FROM omnivore.user WHERE id = ANY(user_ids); + END LOOP; + END $$ + ` + + return authTrx( + async (t) => t.query(sql), + undefined, + undefined, + SetClaimsRole.ADMIN + ) +} From d31c520a904a5039bd28bd0c3d0c8bf78f78dd31 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 2 Jan 2024 14:08:41 +0800 Subject: [PATCH 015/278] add empty trash api to prune soft deleted items in trash --- packages/api/src/generated/graphql.ts | 42 +++++++++++++++++++++ packages/api/src/generated/schema.graphql | 15 ++++++++ packages/api/src/resolvers/article/index.ts | 24 ++++++++++++ packages/api/src/routers/svc/user.ts | 4 +- packages/api/src/schema.ts | 15 ++++++++ packages/api/src/services/library_item.ts | 27 +++++++++++++ packages/api/src/services/user.ts | 14 ++++--- 7 files changed, 133 insertions(+), 8 deletions(-) diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index 62ba7be07..c68e340ee 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -727,6 +727,22 @@ export type DeviceTokensSuccess = { deviceTokens: Array; }; +export type EmptyTrashError = { + __typename?: 'EmptyTrashError'; + errorCodes: Array; +}; + +export enum EmptyTrashErrorCode { + Unauthorized = 'UNAUTHORIZED' +} + +export type EmptyTrashResult = EmptyTrashError | EmptyTrashSuccess; + +export type EmptyTrashSuccess = { + __typename?: 'EmptyTrashSuccess'; + success?: Maybe; +}; + export type Feature = { __typename?: 'Feature'; createdAt: Scalars['Date']; @@ -1352,6 +1368,7 @@ export type Mutation = { deleteNewsletterEmail: DeleteNewsletterEmailResult; deleteRule: DeleteRuleResult; deleteWebhook: DeleteWebhookResult; + emptyTrash: EmptyTrashResult; fetchContent: FetchContentResult; generateApiKey: GenerateApiKeyResult; googleLogin: LoginResult; @@ -3663,6 +3680,10 @@ export type ResolversTypes = { DeviceTokensErrorCode: DeviceTokensErrorCode; DeviceTokensResult: ResolversTypes['DeviceTokensError'] | ResolversTypes['DeviceTokensSuccess']; DeviceTokensSuccess: ResolverTypeWrapper; + EmptyTrashError: ResolverTypeWrapper; + EmptyTrashErrorCode: EmptyTrashErrorCode; + EmptyTrashResult: ResolversTypes['EmptyTrashError'] | ResolversTypes['EmptyTrashSuccess']; + EmptyTrashSuccess: ResolverTypeWrapper; Feature: ResolverTypeWrapper; Feed: ResolverTypeWrapper; FeedArticle: ResolverTypeWrapper; @@ -4169,6 +4190,9 @@ export type ResolversParentTypes = { DeviceTokensError: DeviceTokensError; DeviceTokensResult: ResolversParentTypes['DeviceTokensError'] | ResolversParentTypes['DeviceTokensSuccess']; DeviceTokensSuccess: DeviceTokensSuccess; + EmptyTrashError: EmptyTrashError; + EmptyTrashResult: ResolversParentTypes['EmptyTrashError'] | ResolversParentTypes['EmptyTrashSuccess']; + EmptyTrashSuccess: EmptyTrashSuccess; Feature: Feature; Feed: Feed; FeedArticle: FeedArticle; @@ -4975,6 +4999,20 @@ export type DeviceTokensSuccessResolvers; }; +export type EmptyTrashErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type EmptyTrashResultResolvers = { + __resolveType: TypeResolveFn<'EmptyTrashError' | 'EmptyTrashSuccess', ParentType, ContextType>; +}; + +export type EmptyTrashSuccessResolvers = { + success?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeatureResolvers = { createdAt?: Resolver; expiresAt?: Resolver, ParentType, ContextType>; @@ -5460,6 +5498,7 @@ export type MutationResolvers>; deleteRule?: Resolver>; deleteWebhook?: Resolver>; + emptyTrash?: Resolver; fetchContent?: Resolver>; generateApiKey?: Resolver>; googleLogin?: Resolver>; @@ -6647,6 +6686,9 @@ export type Resolvers = { DeviceTokensError?: DeviceTokensErrorResolvers; DeviceTokensResult?: DeviceTokensResultResolvers; DeviceTokensSuccess?: DeviceTokensSuccessResolvers; + EmptyTrashError?: EmptyTrashErrorResolvers; + EmptyTrashResult?: EmptyTrashResultResolvers; + EmptyTrashSuccess?: EmptyTrashSuccessResolvers; Feature?: FeatureResolvers; Feed?: FeedResolvers; FeedArticle?: FeedArticleResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index cbcdc1808..5c2431d8a 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -644,6 +644,20 @@ type DeviceTokensSuccess { deviceTokens: [DeviceToken!]! } +type EmptyTrashError { + errorCodes: [EmptyTrashErrorCode!]! +} + +enum EmptyTrashErrorCode { + UNAUTHORIZED +} + +union EmptyTrashResult = EmptyTrashError | EmptyTrashSuccess + +type EmptyTrashSuccess { + success: Boolean +} + type Feature { createdAt: Date! expiresAt: Date @@ -1213,6 +1227,7 @@ type Mutation { deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult! deleteRule(id: ID!): DeleteRuleResult! deleteWebhook(id: ID!): DeleteWebhookResult! + emptyTrash: EmptyTrashResult! fetchContent(id: ID!): FetchContentResult! generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult! googleLogin(input: GoogleLoginInput!): LoginResult! diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 5d698fc81..a1b95ee92 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -21,6 +21,8 @@ import { CreateArticleError, CreateArticleErrorCode, CreateArticleSuccess, + EmptyTrashError, + EmptyTrashSuccess, FetchContentError, FetchContentErrorCode, FetchContentSuccess, @@ -71,6 +73,7 @@ import { findOrCreateLabels, } from '../../services/labels' import { + batchDelete, batchUpdateLibraryItems, createLibraryItem, findLibraryItemById, @@ -1035,6 +1038,27 @@ export const fetchContentResolver = authorized< } }) +export const emptyTrashResolver = authorized< + EmptyTrashSuccess, + EmptyTrashError +>(async (_, __, { uid }) => { + analytics.track({ + userId: uid, + event: 'empty_trash', + }) + + await batchDelete({ + state: LibraryItemState.Deleted, + user: { + id: uid, + }, + }) + + return { + success: true, + } +}) + const getUpdateReason = (libraryItem: LibraryItem, since: Date) => { if (libraryItem.deletedAt) { return UpdateReason.Deleted diff --git a/packages/api/src/routers/svc/user.ts b/packages/api/src/routers/svc/user.ts index 0c7fc5991..cf9519c69 100644 --- a/packages/api/src/routers/svc/user.ts +++ b/packages/api/src/routers/svc/user.ts @@ -5,7 +5,7 @@ import express from 'express' import { LessThan } from 'typeorm' import { StatusType } from '../../entity/user' import { readPushSubscription } from '../../pubsub' -import { batchDeleteUsers } from '../../services/user' +import { batchDelete } from '../../services/user' import { corsConfig } from '../../utils/corsConfig' import { logger } from '../../utils/logger' @@ -52,7 +52,7 @@ export function userServiceRouter() { const subTime = cleanupMessage.subDays * 1000 * 60 * 60 * 24 // convert days to milliseconds try { - const result = await batchDeleteUsers({ + const result = await batchDelete({ status: StatusType.Deleted, updatedAt: LessThan(new Date(Date.now() - subTime)), // subDays ago }) diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 4df9a5b35..47720b676 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2761,6 +2761,20 @@ const schema = gql` BAD_REQUEST } + union EmptyTrashResult = EmptyTrashSuccess | EmptyTrashError + + type EmptyTrashSuccess { + success: Boolean + } + + type EmptyTrashError { + errorCodes: [EmptyTrashErrorCode!]! + } + + enum EmptyTrashErrorCode { + UNAUTHORIZED + } + # Mutations type Mutation { googleLogin(input: GoogleLoginInput!): LoginResult! @@ -2872,6 +2886,7 @@ const schema = gql` updateNewsletterEmail( input: UpdateNewsletterEmailInput! ): UpdateNewsletterEmailResult! + emptyTrash: EmptyTrashResult! } # FIXME: remove sort from feedArticles after all cached tabs are closed diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index fdfb4e962..9aca2c098 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -1073,3 +1073,30 @@ export const deleteLibraryItemsByAdmin = async ( 'admin' ) } + +export const batchDelete = async (criteria: FindOptionsWhere) => { + const batchSize = 1000 + + const qb = libraryItemRepository.createQueryBuilder().where(criteria) + const countSql = queryBuilderToRawSql(qb.select('COUNT(1)')) + const subQuery = queryBuilderToRawSql(qb.select('id').limit(batchSize)) + + const sql = ` + -- Set batch size + DO $$ + DECLARE + batch_size INT := ${batchSize}; + BEGIN + -- Loop through batches + FOR i IN 0..CEIL((${countSql})) * 1.0 / batch_size) - 1 LOOP + -- Delete batch + DELETE FROM omnivore.library_item + WHERE id = ANY( + ${subQuery} + ); + END LOOP; + END $$ + ` + + return authTrx(async (t) => t.query(sql)) +} diff --git a/packages/api/src/services/user.ts b/packages/api/src/services/user.ts index 848c12b0c..4c8cabbd7 100644 --- a/packages/api/src/services/user.ts +++ b/packages/api/src/services/user.ts @@ -48,13 +48,12 @@ export const createUsers = async (users: DeepPartial[]) => { ) } -export const batchDeleteUsers = async (criteria: FindOptionsWhere) => { +export const batchDelete = async (criteria: FindOptionsWhere) => { const userQb = getRepository(User).createQueryBuilder().where(criteria) const userCountSql = queryBuilderToRawSql(userQb.select('COUNT(1)')) const userSubQuery = queryBuilderToRawSql(userQb.select('id INTO user_ids')) const batchSize = 1000 - const start = new Date().toISOString() const sql = ` -- Set batch size DO $$ @@ -65,15 +64,18 @@ export const batchDeleteUsers = async (criteria: FindOptionsWhere) => { -- Loop through batches of users FOR i IN 0..CEIL((${userCountSql})) * 1.0 / batch_size) - 1 LOOP -- GET batch of user ids - ${userSubQuery} LIMIT ${batchSize} OFFSET i * batch_size; + ${userSubQuery} LIMIT batch_size OFFSET i * batch_size; -- Loop through batches of items FOR j IN 0..CEIL((SELECT COUNT(1) FROM omnivore.library_item WHERE user_id = ANY(user_ids))) * 1.0 / batch_size) - 1 LOOP -- Delete batch of items DELETE FROM omnivore.library_item - WHERE user_id = ANY(user_ids) - AND updated_at < '${start}' - LIMIT ${batchSize}; + WHERE id = ANY( + SELECT id + FROM omnivore.library_item + WHERE user_id = ANY(user_ids) + LIMIT batch_size + ); END LOOP; -- Delete the batch of users From 554c4cee12fc83868d17111a34517642fdad0c0f Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 3 Jan 2024 12:40:46 +0800 Subject: [PATCH 016/278] add test cases --- .../api/src/resolvers/function_resolvers.ts | 3 ++ packages/api/src/services/library_item.ts | 2 +- packages/api/src/services/user.ts | 10 ++-- packages/api/test/resolvers/article.test.ts | 54 +++++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 7c4616ea1..6cff177ca 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -38,6 +38,7 @@ import { generateDownloadSignedUrl, generateUploadFilePathName, } from '../utils/uploads' +import { emptyTrashResolver } from './article' import { optInFeatureResolver } from './features' import { uploadImportFileResolver } from './importers/uploadImportFileResolver' import { @@ -231,6 +232,7 @@ export const functionResolvers = { updateEmail: updateEmailResolver, moveToFolder: moveToFolderResolver, updateNewsletterEmail: updateNewsletterEmailResolver, + emptyTrash: emptyTrashResolver, }, Query: { me: getMeUserResolver, @@ -578,4 +580,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('ScanFeeds'), ...resultResolveTypeResolver('MoveToFolder'), ...resultResolveTypeResolver('UpdateNewsletterEmail'), + ...resultResolveTypeResolver('EmptyTrash'), } diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 9aca2c098..5fabc2828 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -1088,7 +1088,7 @@ export const batchDelete = async (criteria: FindOptionsWhere) => { batch_size INT := ${batchSize}; BEGIN -- Loop through batches - FOR i IN 0..CEIL((${countSql})) * 1.0 / batch_size) - 1 LOOP + FOR i IN 0..CEIL((${countSql}) * 1.0 / batch_size) - 1 LOOP -- Delete batch DELETE FROM omnivore.library_item WHERE id = ANY( diff --git a/packages/api/src/services/user.ts b/packages/api/src/services/user.ts index 4c8cabbd7..d7f9fa0d3 100644 --- a/packages/api/src/services/user.ts +++ b/packages/api/src/services/user.ts @@ -51,7 +51,9 @@ export const createUsers = async (users: DeepPartial[]) => { export const batchDelete = async (criteria: FindOptionsWhere) => { const userQb = getRepository(User).createQueryBuilder().where(criteria) const userCountSql = queryBuilderToRawSql(userQb.select('COUNT(1)')) - const userSubQuery = queryBuilderToRawSql(userQb.select('id INTO user_ids')) + const userSubQuery = queryBuilderToRawSql( + userQb.select('array_agg(id::UUID) into user_ids') + ) const batchSize = 1000 const sql = ` @@ -62,12 +64,12 @@ export const batchDelete = async (criteria: FindOptionsWhere) => { user_ids UUID[]; BEGIN -- Loop through batches of users - FOR i IN 0..CEIL((${userCountSql})) * 1.0 / batch_size) - 1 LOOP + FOR i IN 0..CEIL((${userCountSql}) * 1.0 / batch_size) - 1 LOOP -- GET batch of user ids - ${userSubQuery} LIMIT batch_size OFFSET i * batch_size; + ${userSubQuery} LIMIT batch_size; -- Loop through batches of items - FOR j IN 0..CEIL((SELECT COUNT(1) FROM omnivore.library_item WHERE user_id = ANY(user_ids))) * 1.0 / batch_size) - 1 LOOP + FOR j IN 0..CEIL((SELECT COUNT(1) FROM omnivore.library_item WHERE user_id = ANY(user_ids)) * 1.0 / batch_size) - 1 LOOP -- Delete batch of items DELETE FROM omnivore.library_item WHERE id = ANY( diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index b6e781597..090e31208 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -2285,4 +2285,58 @@ describe('Article API', () => { expect(item?.labels?.map((l) => l.name)).to.eql(['Favorites']) }) }) + + describe('EmptyTrash API', () => { + const emptyTrashQuery = () => ` + mutation { + emptyTrash { + ... on EmptyTrashSuccess { + success + } + ... on EmptyTrashError { + errorCodes + } + } + }` + + let items: LibraryItem[] = [] + + before(async () => { + // Create some test items + for (let i = 0; i < 5; i++) { + const itemToSave: DeepPartial = { + user, + title: 'test item', + readableContent: '

test

', + slug: '', + originalUrl: `https://blog.omnivore.app/p/empty-trash-${i}`, + deletedAt: new Date(), + state: LibraryItemState.Deleted, + } + const item = await createLibraryItem(itemToSave, user.id) + items.push(item) + } + }) + + after(async () => { + // Delete all items + await deleteLibraryItemsByUserId(user.id) + }) + + it('empties the trash', async () => { + let response = await graphqlRequest( + searchQuery('in:trash'), + authToken + ).expect(200) + expect(response.body.data.search.pageInfo.totalCount).to.eql(5) + + await graphqlRequest(emptyTrashQuery(), authToken).expect(200) + + response = await graphqlRequest( + searchQuery('in:trash'), + authToken + ).expect(200) + expect(response.body.data.search.pageInfo.totalCount).to.eql(0) + }) + }) }) From 8a288ffe5fdc9598d2d22371c67a7b3adb071c8b Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 06:09:22 +0800 Subject: [PATCH 017/278] Delay loading of the background context --- .../Sources/Services/DataService/DataService.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index a6743b4e0..f113f72d4 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -60,16 +60,16 @@ public final class DataService: ObservableObject { self.appEnvironment = appEnvironment self.networker = networker self.persistentContainer = PersistentContainer.make() - self.backgroundContext = persistentContainer.newBackgroundContext() - - backgroundContext.automaticallyMergesChangesFromParent = true - backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump persistentContainer.loadPersistentStores { _, error in if let error = error { fatalError("Core Data store failed to load with error: \(error)") } } + self.backgroundContext = persistentContainer.newBackgroundContext() + + backgroundContext.automaticallyMergesChangesFromParent = true + backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump } public func cleanupDeletedItems(in context: NSManagedObjectContext) { From bf26d01cbb166c0f29fee694e56311bf4eaeacc0 Mon Sep 17 00:00:00 2001 From: Gannon Date: Wed, 3 Jan 2024 21:52:21 -0500 Subject: [PATCH 018/278] Update app/build.gradle file to sync without the requirement for a keystore.properties This change will only effect fresh imports and people who don't setup a keystore.properties When building debug, there is not a strict reason why we need a custom keystore. This change will allow people to pull the code and start developing right away without configuring keystores --- android/Omnivore/app/build.gradle | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index b158b6f07..f849d699b 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -8,7 +8,9 @@ plugins { def keystorePropertiesFile = rootProject.file("app/external/keystore.properties"); def keystoreProperties = new Properties() -keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} android { compileSdk 33 @@ -26,24 +28,28 @@ android { } } - signingConfigs{ - release{ + signingConfigs { + release { keyAlias 'key0' storeFile file('external/omnivore-prod.keystore') storePassword keystoreProperties['prodStorePassword'] keyPassword keystoreProperties['prodKeyPassword'] } - debug{ - keyAlias 'androiddebugkey' - storeFile file('external/omnivore-demo.keystore') - storePassword keystoreProperties['demoStorePassword'] - keyPassword keystoreProperties['demoKeyPassword'] + debug { + if (keystoreProperties['demoStorePassword'] && keystoreProperties['demoKeyPassword']) { + keyAlias 'androiddebugkey' + storeFile file('external/omnivore-demo.keystore') + storePassword keystoreProperties['demoStorePassword'] + keyPassword keystoreProperties['demoKeyPassword'] + } } } buildTypes { - debug{ - signingConfig signingConfigs.debug + debug { + if (signingConfigs.debug.storeFile) { + signingConfig signingConfigs.debug + } buildConfigField("String", "OMNIVORE_API_URL", "\"https://api-demo.omnivore.app\"") buildConfigField("String", "OMNIVORE_WEB_URL", "\"https://demo.omnivore.app\"") buildConfigField("String", "OMNIVORE_GAUTH_SERVER_CLIENT_ID", "\"267918240109-eu2ar09unac3lqqigluknhk7t0021b54.apps.googleusercontent.com\"") From 4689b5325f9a1c9afbc0d1995f95d16eca4f6bd4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 10:59:36 +0800 Subject: [PATCH 019/278] Ensure app environment is configured This can happen if a user sets a custom self hosting environment, then deletes and re-installs the app. --- .../Sources/App/Views/RootView/RootView.swift | 2 +- apple/OmnivoreKit/Sources/Models/AppEnvironment.swift | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift index 62a77584a..7260e6bab 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift @@ -49,7 +49,7 @@ struct InnerRootView: View { @ObservedObject var viewModel: RootViewModel @ViewBuilder private var innerBody: some View { - if authenticator.isLoggedIn { + if authenticator.isLoggedIn, dataService.appEnvironment.environmentConfigured { PrimaryContentView() .task { try? await dataService.syncOfflineItemsWithServerIfNeeded() diff --git a/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift b/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift index 854059f41..b57f6bf81 100644 --- a/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift +++ b/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift @@ -54,6 +54,17 @@ public extension AppEnvironment { ) } + var environmentConfigured: Bool { + if self == .custom { + if let str = UserDefaults.standard.string(forKey: AppEnvironmentUserDefaultKey.webAppBaseURL.rawValue), let url = URL(string: str) { + return true + } else { + return false + } + } + return true + } + var graphqlPath: String { "\(serverBaseURL.absoluteString)/api/graphql" } From 863a486a4afd0257d805a55b6b4d1d4c2eb373fc Mon Sep 17 00:00:00 2001 From: Gannon Date: Wed, 3 Jan 2024 22:06:53 -0500 Subject: [PATCH 020/278] Update build.gradle --- android/Omnivore/app/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index f849d699b..28088e19e 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -47,9 +47,7 @@ android { buildTypes { debug { - if (signingConfigs.debug.storeFile) { - signingConfig signingConfigs.debug - } + signingConfig signingConfigs.debug buildConfigField("String", "OMNIVORE_API_URL", "\"https://api-demo.omnivore.app\"") buildConfigField("String", "OMNIVORE_WEB_URL", "\"https://demo.omnivore.app\"") buildConfigField("String", "OMNIVORE_GAUTH_SERVER_CLIENT_ID", "\"267918240109-eu2ar09unac3lqqigluknhk7t0021b54.apps.googleusercontent.com\"") From ab004caad7e0684f2ba421cca6646ba5556293d7 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 12:20:40 +0800 Subject: [PATCH 021/278] Add toolbar spacing on iPad --- .../Sources/App/Views/Home/HomeFeedViewIOS.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 24b1c6b61..5d8433de2 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -376,6 +376,7 @@ struct AnimatingCellHeight: AnimatableModifier { .foregroundColor(Color.toolbarItemForeground) } ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) } if enableGrid { Button( @@ -385,6 +386,7 @@ struct AnimatingCellHeight: AnimatableModifier { .foregroundColor(Color.toolbarItemForeground) } ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) } Button( @@ -400,6 +402,7 @@ struct AnimatingCellHeight: AnimatableModifier { .foregroundColor(Color.toolbarItemForeground) } ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) Button( action: { @@ -412,6 +415,7 @@ struct AnimatingCellHeight: AnimatableModifier { .foregroundColor(Color.toolbarItemForeground) } ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) } ToolbarItemGroup(placement: .bottomBar) { @@ -419,18 +423,14 @@ struct AnimatingCellHeight: AnimatableModifier { Button(action: { viewModel.bulkAction(dataService: dataService, action: .delete, items: Array(selection)) isEditMode = .inactive - }, label: { Image(systemName: "trash") }) - .alignmentGuide(HorizontalAlignment.center, computeValue: { dim in - dim[HorizontalAlignment.center] - }) + }, label: { Image.toolbarTrash }) + .padding(.horizontal, 10) Button(action: { viewModel.bulkAction(dataService: dataService, action: .archive, items: Array(selection)) isEditMode = .inactive - }, label: { Image(systemName: "archivebox") }) - .alignmentGuide(HorizontalAlignment.center, computeValue: { dim in - dim[HorizontalAlignment.center] - }) + }, label: { Image.toolbarArchive.padding(.top, 4) }) + .padding(.horizontal, 10) Spacer() Text("\(selection.count) selected").font(.footnote) From 486ec1993727ad84f4f8d872317964cb4b57f388 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 12:20:57 +0800 Subject: [PATCH 022/278] Improve logged out handler of extension, create new local db path --- .../Share/ShareExtensionScene.swift | 16 +++++++++++++ .../Share/ShareExtensionViewModel.swift | 2 +- .../Share/Views/ShareExtensionView.swift | 23 +++++++++++++++++++ .../Models/CoreData/StorageProvider.swift | 3 ++- .../Services/DataService/DataService.swift | 5 +--- .../DataService/Queries/LabelsPublisher.swift | 2 +- .../ShareExtensionViewController.swift | 10 ++++++++ 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift index 2437a21ed..24e631a21 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift @@ -22,4 +22,20 @@ public extension PlatformViewController { #endif return hostingController } + + static func makeLoggedOutShareExtensionController( + extensionContext: NSExtensionContext? + ) -> PlatformViewController { + registerFonts() + + let hostingController = PlatformHostingController( + rootView: LoggedOutShareExtensionView(extensionContext: extensionContext) + ) + #if os(iOS) + hostingController.view.layer.cornerRadius = 12 + hostingController.view.layer.masksToBounds = true + hostingController.view.layer.isOpaque = false + #endif + return hostingController + } } diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift index 7f5ad4aff..f8a29c7ba 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift @@ -17,7 +17,7 @@ public class ShareExtensionViewModel: ObservableObject { @Published var debugText: String? @Published var noteText: String = "" - let services = Services() + public let services = Services() let queue = OperationQueue() public init() {} diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift index b1b282a8e..4e0731b9d 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift @@ -470,3 +470,26 @@ public struct ShareExtensionView: View { } } } + +public struct LoggedOutShareExtensionView: View { + let extensionContext: NSExtensionContext? + + public init( + extensionContext: NSExtensionContext?) + { + self.extensionContext = extensionContext + } + + public var body: some View { + VStack(spacing: 20) { + Text("You are not logged in. Please open the Omnivore app and log in to start saving items to your library") + Button(action: { + if let extensionContext = extensionContext { + extensionContext.completeRequest(returningItems: nil) + } + }, label: { Text("Dismiss") }) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(20) + } +} diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift index 30e8e9429..409cb5813 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -7,7 +7,8 @@ let logger = Logger(subsystem: "app.omnivore", category: "models") /// An `NSPersistentContainer` subclass that lives in the `Models` package so that /// the data model is looked for in the same package bundle (rather than the main bundle) public class PersistentContainer: NSPersistentContainer { - public static var DatabaseFile = "store.sqlite" + public static var DatabaseVersion = "002" + public static var DatabaseFile = "store-v\(DatabaseVersion).sqlite" public static func path() -> URL? { // Store the sqlite file in the app group container. diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index f113f72d4..70e798db2 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -53,10 +53,6 @@ public final class DataService: ObservableObject { } public init(appEnvironment: AppEnvironment, networker: Networker) { - if Self.isFirstTimeRunningNewAppBuild() { - Self.deleteLocalStorage() - } - self.appEnvironment = appEnvironment self.networker = networker self.persistentContainer = PersistentContainer.make() @@ -197,6 +193,7 @@ public final class DataService: ObservableObject { } } catch { print(error.localizedDescription) + print("ERROR: ", error) } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift index b6b920a25..1ad3d62f9 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift @@ -44,7 +44,7 @@ public extension DataService { continuation.resume(throwing: BasicError.message(messageText: "CoreData error")) } case .error: - continuation.resume(throwing: BasicError.message(messageText: "Newsletter Email fetch error")) + continuation.resume(throwing: BasicError.message(messageText: "labels fetch error")) } } } diff --git a/apple/Sources/ShareExtension/ShareExtensionViewController.swift b/apple/Sources/ShareExtension/ShareExtensionViewController.swift index bbf6dd0d6..c1efb133d 100644 --- a/apple/Sources/ShareExtension/ShareExtensionViewController.swift +++ b/apple/Sources/ShareExtension/ShareExtensionViewController.swift @@ -16,6 +16,16 @@ import Views super.viewDidLoad() view.backgroundColor = .clear + if !viewModel.services.authenticator.isLoggedIn, + !viewModel.services.dataService.appEnvironment.environmentConfigured + { + embed( + childViewController: UIViewController.makeLoggedOutShareExtensionController(extensionContext: extensionContext), + heightRatio: 0.60 + ) + return + } + NotificationCenter.default.addObserver( forName: Notification.Name("ShowAddNoteSheet"), object: nil, From de0686d0b161e30526957d1097ba7e5942733c1f Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 12:40:47 +0800 Subject: [PATCH 023/278] More spacing on toolbar items --- .../Sources/App/Views/Home/HomeFeedViewIOS.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 5d8433de2..cca806ff0 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -424,13 +424,13 @@ struct AnimatingCellHeight: AnimatableModifier { viewModel.bulkAction(dataService: dataService, action: .delete, items: Array(selection)) isEditMode = .inactive }, label: { Image.toolbarTrash }) - .padding(.horizontal, 10) + .padding(.horizontal, UIDevice.isIPad ? 10 : 5) Button(action: { viewModel.bulkAction(dataService: dataService, action: .archive, items: Array(selection)) isEditMode = .inactive - }, label: { Image.toolbarArchive.padding(.top, 4) }) - .padding(.horizontal, 10) + }, label: { Image.toolbarArchive }) + .padding(.horizontal, UIDevice.isIPad ? 10 : 5) Spacer() Text("\(selection.count) selected").font(.footnote) From 168df439bdb5590169178b2afdfe93d038885b49 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 4 Jan 2024 12:51:49 +0800 Subject: [PATCH 024/278] Move cancel button to top of the nav bar in multiselect --- .../App/Views/Home/HomeFeedViewIOS.swift | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index cca806ff0..991879fee 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -367,55 +367,59 @@ struct AnimatingCellHeight: AnimatableModifier { } ToolbarItemGroup(placement: .barTrailing) { - if prefersListLayout { + if isEditMode == .active { + Button(action: { isEditMode = .inactive }, label: { Text("Cancel") }) + } else { + if prefersListLayout { + Button( + action: { isEditMode = isEditMode == .active ? .inactive : .active }, + label: { + Image + .selectMultiple + .foregroundColor(Color.toolbarItemForeground) + } + ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) + } + if enableGrid { + Button( + action: { prefersListLayout.toggle() }, + label: { + Image(systemName: prefersListLayout ? "square.grid.2x2" : "list.bullet") + .foregroundColor(Color.toolbarItemForeground) + } + ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) + } + Button( - action: { isEditMode = isEditMode == .active ? .inactive : .active }, + action: { + if viewModel.currentFolder == "inbox" { + showAddLinkView = true + } else if viewModel.currentFolder == "following" { + viewModel.showAddFeedView = true + } + }, + label: { + Image.addLink + .foregroundColor(Color.toolbarItemForeground) + } + ).buttonStyle(.plain) + .padding(.horizontal, UIDevice.isIPad ? 5 : 0) + + Button( + action: { + searchPresented = true + isEditMode = .inactive + }, label: { Image - .selectMultiple + .magnifyingGlass .foregroundColor(Color.toolbarItemForeground) } ).buttonStyle(.plain) .padding(.horizontal, UIDevice.isIPad ? 5 : 0) } - if enableGrid { - Button( - action: { prefersListLayout.toggle() }, - label: { - Image(systemName: prefersListLayout ? "square.grid.2x2" : "list.bullet") - .foregroundColor(Color.toolbarItemForeground) - } - ).buttonStyle(.plain) - .padding(.horizontal, UIDevice.isIPad ? 5 : 0) - } - - Button( - action: { - if viewModel.currentFolder == "inbox" { - showAddLinkView = true - } else if viewModel.currentFolder == "following" { - viewModel.showAddFeedView = true - } - }, - label: { - Image.addLink - .foregroundColor(Color.toolbarItemForeground) - } - ).buttonStyle(.plain) - .padding(.horizontal, UIDevice.isIPad ? 5 : 0) - - Button( - action: { - searchPresented = true - isEditMode = .inactive - }, - label: { - Image - .magnifyingGlass - .foregroundColor(Color.toolbarItemForeground) - } - ).buttonStyle(.plain) - .padding(.horizontal, UIDevice.isIPad ? 5 : 0) } ToolbarItemGroup(placement: .bottomBar) { @@ -424,19 +428,19 @@ struct AnimatingCellHeight: AnimatableModifier { viewModel.bulkAction(dataService: dataService, action: .delete, items: Array(selection)) isEditMode = .inactive }, label: { Image.toolbarTrash }) - .padding(.horizontal, UIDevice.isIPad ? 10 : 5) - - Button(action: { - viewModel.bulkAction(dataService: dataService, action: .archive, items: Array(selection)) - isEditMode = .inactive - }, label: { Image.toolbarArchive }) + .disabled(selection.count < 1) .padding(.horizontal, UIDevice.isIPad ? 10 : 5) Spacer() Text("\(selection.count) selected").font(.footnote) Spacer() - Button(action: { isEditMode = .inactive }, label: { Text("Cancel") }) + Button(action: { + viewModel.bulkAction(dataService: dataService, action: .archive, items: Array(selection)) + isEditMode = .inactive + }, label: { Image.toolbarArchive }) + .disabled(selection.count < 1) + .padding(.horizontal, UIDevice.isIPad ? 10 : 5) } } } From d31eb7a88b67d75fcea2b07c0840a08319dfacdd Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 15:06:31 +0800 Subject: [PATCH 025/278] skip sending item update event for reading progress update --- packages/api/src/resolvers/article/index.ts | 3 +-- packages/api/src/services/library_item.ts | 16 +--------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 5d698fc81..6f8ff71e0 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -643,8 +643,7 @@ export const saveArticleReadingProgressResolver = authorized< uid, readingProgressPercent, readingProgressTopPercent, - readingProgressAnchorIndex, - pubsub + readingProgressAnchorIndex ) if (!updatedItem) { return { errorCodes: [SaveArticleReadingProgressErrorCode.BadData] } diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index fdfb4e962..c09411943 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -739,8 +739,7 @@ export const updateLibraryItemReadingProgress = async ( userId: string, bottomPercent: number, topPercent: number | null = null, - anchorIndex: number | null = null, - pubsub = createPubSubClient() + anchorIndex: number | null = null ): Promise => { // If we have a top percent, we only save it if it's greater than the current top percent // or set to zero if the top percent is zero. @@ -787,19 +786,6 @@ export const updateLibraryItemReadingProgress = async ( } const updatedItem = result[0][0] - await pubsub.entityUpdated>( - EntityType.PAGE, - { - id, - readingProgressBottomPercent: updatedItem.readingProgressBottomPercent, - readingProgressTopPercent: updatedItem.readingProgressTopPercent, - readingProgressHighestReadAnchor: - updatedItem.readingProgressHighestReadAnchor, - readAt: updatedItem.readAt, - }, - userId - ) - return updatedItem } From 0f774b6ae42ee945d11e444580ce7ad9d4c742be Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 17:36:12 +0800 Subject: [PATCH 026/278] skip recently saved rss item --- packages/rss-handler/package.json | 1 + packages/rss-handler/src/index.ts | 104 +++++++++++++++++++++--------- packages/rss-handler/src/redis.ts | 26 ++++++++ 3 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 packages/rss-handler/src/redis.ts diff --git a/packages/rss-handler/package.json b/packages/rss-handler/package.json index 49452e22e..728cd9b82 100644 --- a/packages/rss-handler/package.json +++ b/packages/rss-handler/package.json @@ -29,6 +29,7 @@ "dotenv": "^16.0.1", "jsonwebtoken": "^8.5.1", "linkedom": "^0.16.4", + "redis": "^4.3.1", "rss-parser": "^3.13.0" }, "volta": { diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index a615fa675..2da93f3f7 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -4,11 +4,15 @@ import crypto from 'crypto' import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import import * as jwt from 'jsonwebtoken' import { parseHTML } from 'linkedom' +import { createClient } from 'redis' import Parser, { Item } from 'rss-parser' import { promisify } from 'util' +import { createRedisClient } from './redis' import { CONTENT_FETCH_URL, createCloudTask } from './task' type FolderType = 'following' | 'inbox' +// explicitly create the return type of RedisClient +type RedisClient = ReturnType interface RssFeedRequest { subscriptionIds: string[] @@ -42,6 +46,7 @@ type RssFeedItemMedia = { type RssFeedItem = Item & { 'media:thumbnail'?: RssFeedItemMedia 'media:content'?: RssFeedItemMedia[] + link: string } export const isOldItem = (item: RssFeedItem, lastFetchedAt: number) => { @@ -214,13 +219,34 @@ const sendUpdateSubscriptionMutation = async ( } } +const isItemRecentlySaved = async ( + redisClient: RedisClient, + userId: string, + url: string +) => { + const key = `rss-recent-save:${userId}:${url}` + const result = await redisClient.get(key) + return !!result +} + const createTask = async ( userId: string, feedUrl: string, item: RssFeedItem, fetchContent: boolean, - folder: FolderType + folder: FolderType, + redisClient: RedisClient ) => { + const isRecentlySaved = await isItemRecentlySaved( + redisClient, + userId, + item.link + ) + if (isRecentlySaved) { + console.log('Item recently saved', item.link) + return true + } + if (folder === 'following' && !fetchContent) { return createItemWithPreviewContent(userId, feedUrl, item) } @@ -363,7 +389,7 @@ const getUpdatePeriodInHours = (feed: RssFeed) => { } // get link following the order of preference: via, alternate, self -const getLink = (links: RssFeedItemLink[]) => { +const getLink = (links: RssFeedItemLink[]): string | undefined => { // sort links by preference const sortedLinks: string[] = [] @@ -398,10 +424,11 @@ const processSubscription = async ( lastFetchedChecksum: string, fetchContent: boolean, folder: FolderType, - feed: RssFeed + feed: RssFeed, + redisClient: RedisClient ) => { let lastItemFetchedAt: Date | null = null - let lastValidItem: Item | null = null + let lastValidItem: RssFeedItem | null = null if (fetchResult.checksum === lastFetchedChecksum) { console.log('feed has not been updated', feedUrl, lastFetchedChecksum) @@ -425,7 +452,7 @@ const processSubscription = async ( // save each item in the feed for (const item of feed.items) { // use published or updated if isoDate is not available for atom feeds - item.isoDate = + const isoDate = item.isoDate || item.published || item.updated || item.created console.log('Processing feed item', item.links, item.isoDate, feed.feedUrl) @@ -434,21 +461,28 @@ const processSubscription = async ( continue } - item.link = getLink(item.links) - if (!item.link) { + const link = getLink(item.links) + if (!link) { console.log('Invalid feed item links', item.links) continue } - console.log('Fetching feed item', item.link) + console.log('Fetching feed item', link) + const feedItem = { + ...item, + isoDate, + link, + } - const publishedAt = item.isoDate ? new Date(item.isoDate) : new Date() + const publishedAt = feedItem.isoDate + ? new Date(feedItem.isoDate) + : new Date() // remember the last valid item if ( !lastValidItem || (lastValidItem.isoDate && publishedAt > new Date(lastValidItem.isoDate)) ) { - lastValidItem = item + lastValidItem = feedItem } // Max limit per-feed update @@ -457,20 +491,21 @@ const processSubscription = async ( } // skip old items - if (isOldItem(item, lastFetchedAt)) { - console.log('Skipping old feed item', item.link) + if (isOldItem(feedItem, lastFetchedAt)) { + console.log('Skipping old feed item', feedItem.link) continue } const created = await createTask( userId, feedUrl, - item, + feedItem, fetchContent, - folder + folder, + redisClient ) if (!created) { - console.error('Failed to create task for feed item', item.link) + console.error('Failed to create task for feed item', feedItem.link) continue } @@ -496,7 +531,8 @@ const processSubscription = async ( feedUrl, lastValidItem, fetchContent, - folder + folder, + redisClient ) if (!created) { console.error('Failed to create task for feed item', lastValidItem.link) @@ -536,6 +572,12 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( return res.status(400).send('INVALID_REQUEST_BODY') } + // create redis client + const redisClient = await createRedisClient( + process.env.REDIS_URL, + process.env.REDIS_CERT + ) + const { feedUrl, subscriptionIds, @@ -563,22 +605,22 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( console.log('Fetched feed', feed.title, new Date()) - await Promise.all( - subscriptionIds.map((_, i) => - processSubscription( - subscriptionIds[i], - userIds[i], - feedUrl, - fetchResult, - lastFetchedTimestamps[i], - scheduledTimestamps[i], - lastFetchedChecksums[i], - fetchContents[i] && allowFetchContent, - folders[i], - feed - ) + // process each subscription sequentially + for (let i = 0; i < subscriptionIds.length; i++) { + await processSubscription( + subscriptionIds[i], + userIds[i], + feedUrl, + fetchResult, + lastFetchedTimestamps[i], + scheduledTimestamps[i], + lastFetchedChecksums[i], + fetchContents[i] && allowFetchContent, + folders[i], + feed, + redisClient ) - ) + } res.send('ok') } catch (e) { diff --git a/packages/rss-handler/src/redis.ts b/packages/rss-handler/src/redis.ts new file mode 100644 index 000000000..350cb09f6 --- /dev/null +++ b/packages/rss-handler/src/redis.ts @@ -0,0 +1,26 @@ +import { createClient } from 'redis' + +export const createRedisClient = async (url?: string, cert?: string) => { + const redisClient = createClient({ + url, + socket: { + tls: url?.startsWith('rediss://'), // rediss:// is the protocol for TLS + cert: cert?.replace(/\\n/g, '\n'), // replace \n with new line + rejectUnauthorized: false, // for self-signed certs + connectTimeout: 10000, // 10 seconds + reconnectStrategy(retries: number): number | Error { + if (retries > 10) { + return new Error('Retries exhausted') + } + return 1000 + }, + }, + }) + + redisClient.on('error', (err) => console.error('Redis Client Error', err)) + + await redisClient.connect() + console.log('Redis Client Connected:', url) + + return redisClient +} From 08c4624fceb856cfa12c2836f38f2eb39384b75f Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 18:03:34 +0800 Subject: [PATCH 027/278] save the url in redis for 8 hours so rss-feeder will not try to re-save it --- packages/api/package.json | 1 + packages/api/src/redis.ts | 18 ++++++++++++++++++ packages/api/src/server.ts | 3 +++ packages/api/src/services/save_page.ts | 9 +++++++++ packages/api/src/util.ts | 11 +++++++++++ packages/rss-handler/src/index.ts | 2 +- 6 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/redis.ts diff --git a/packages/api/package.json b/packages/api/package.json index 34d9b24fb..75cecca83 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -82,6 +82,7 @@ "pg": "^8.3.3", "postgrator": "^4.2.0", "private-ip": "^2.3.3", + "redis": "^4.3.1", "rss-parser": "^3.13.0", "sanitize-html": "^2.3.2", "sax": "^1.3.0", diff --git a/packages/api/src/redis.ts b/packages/api/src/redis.ts new file mode 100644 index 000000000..14559c0be --- /dev/null +++ b/packages/api/src/redis.ts @@ -0,0 +1,18 @@ +import { createClient } from 'redis' +import { env } from './env' + +export const redisClient = createClient({ + url: env.redis.url, + socket: { + tls: env.redis.url?.startsWith('rediss://'), // rediss:// is the protocol for TLS + cert: env.redis.cert?.replace(/\\n/g, '\n'), // replace \n with new line + rejectUnauthorized: false, // for self-signed certs + connectTimeout: 10000, // 10 seconds + reconnectStrategy(retries: number): number | Error { + if (retries > 10) { + return new Error('Retries exhausted') + } + return 1000 + }, + }, +}) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 1bb75a085..2380e3c37 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -15,6 +15,7 @@ import { config, loggers } from 'winston' import { makeApolloServer } from './apollo' import { appDataSource } from './data_source' import { env } from './env' +import { redisClient } from './redis' import { articleRouter } from './routers/article_router' import { authRouter } from './routers/auth/auth_router' import { mobileAuthRouter } from './routers/auth/mobile/mobile_auth_router' @@ -157,6 +158,8 @@ const main = async (): Promise => { // as healthy. await appDataSource.initialize() + await redisClient.connect() + const { app, apollo, httpServer } = createApp() await apollo.start() diff --git a/packages/api/src/services/save_page.ts b/packages/api/src/services/save_page.ts index 442dcea93..a49f9ac86 100644 --- a/packages/api/src/services/save_page.ts +++ b/packages/api/src/services/save_page.ts @@ -13,6 +13,7 @@ import { SavePageInput, SaveResult, } from '../generated/graphql' +import { redisClient } from '../redis' import { authTrx } from '../repository' import { enqueueThumbnailTask } from '../utils/createTask' import { @@ -194,6 +195,14 @@ export const savePage = async ( } } + // save the url in redis for 8 hours so rss-feeder won't try to re-save it + const redisKey = `recent-saved-item:${user.id}:${itemToSave.originalUrl}` + const expireInSeconds = 60 * 60 * 8 + await redisClient.set(redisKey, 1, { + EX: expireInSeconds, + NX: true, + }) + return { clientRequestId, url: `${homePageURL()}/${user.profile.username}/${slug}`, diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts index c056d17cd..f37b51587 100755 --- a/packages/api/src/util.ts +++ b/packages/api/src/util.ts @@ -109,6 +109,10 @@ interface BackendEnv { max: number } } + redis: { + url?: string + cert?: string + } } /*** @@ -173,6 +177,8 @@ const nullableEnvVars = [ 'INTEGRATION_EXPORTER_URL', 'INTEGRATION_IMPORTER_URL', 'SUBSCRIPTION_FEED_MAX', + 'REDIS_URL', + 'REDIS_CERT', ] // Allow some vars to be null/empty /* If not in GAE and Prod/QA/Demo env (f.e. on localhost/dev env), allow following env vars to be null */ @@ -316,6 +322,10 @@ export function getEnv(): BackendEnv { : 256, // default to 256 }, } + const redis = { + url: parse('REDIS_URL'), + cert: parse('REDIS_CERT'), + } return { pg, @@ -338,6 +348,7 @@ export function getEnv(): BackendEnv { gcp, pocket, subscription, + redis, } } diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index 2da93f3f7..4604690ef 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -224,7 +224,7 @@ const isItemRecentlySaved = async ( userId: string, url: string ) => { - const key = `rss-recent-save:${userId}:${url}` + const key = `recent-saved-item:${userId}:${url}` const result = await redisClient.get(key) return !!result } From bcd05cc7fc8e93bd9cc1e7ca9445b54bd983f1fa Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 18:10:14 +0800 Subject: [PATCH 028/278] set the url in redis when an item is inserted into db --- packages/api/src/services/library_item.ts | 4 +++- packages/api/src/services/save_page.ts | 8 -------- packages/api/src/utils/helpers.ts | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index d6944af12..c68e6526d 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -15,7 +15,7 @@ import { valuesToRawSql, } from '../repository' import { libraryItemRepository } from '../repository/library_item' -import { wordsCount } from '../utils/helpers' +import { setRecentlySavedItemInRedis, wordsCount } from '../utils/helpers' import { parseSearchQuery } from '../utils/search' enum ReadFilter { @@ -818,6 +818,8 @@ export const createLibraryItem = async ( userId ) + await setRecentlySavedItemInRedis(userId, newLibraryItem.originalUrl) + if (skipPubSub) { return newLibraryItem } diff --git a/packages/api/src/services/save_page.ts b/packages/api/src/services/save_page.ts index a49f9ac86..0cded9a52 100644 --- a/packages/api/src/services/save_page.ts +++ b/packages/api/src/services/save_page.ts @@ -195,14 +195,6 @@ export const savePage = async ( } } - // save the url in redis for 8 hours so rss-feeder won't try to re-save it - const redisKey = `recent-saved-item:${user.id}:${itemToSave.originalUrl}` - const expireInSeconds = 60 * 60 * 8 - await redisClient.set(redisKey, 1, { - EX: expireInSeconds, - NX: true, - }) - return { clientRequestId, url: `${homePageURL()}/${user.profile.username}/${slug}`, diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index df6537996..48437b29e 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -25,6 +25,7 @@ import { SearchItem, } from '../generated/graphql' import { createPubSubClient } from '../pubsub' +import { redisClient } from '../redis' import { Claims, WithDataSourcesContext } from '../resolvers/types' import { validateUrl } from '../services/create_page_save_request' import { updateLibraryItem } from '../services/library_item' @@ -407,3 +408,16 @@ export const isRelativeUrl = (url: string): boolean => { export const getAbsoluteUrl = (url: string, baseUrl: string): string => { return new URL(url, baseUrl).href } + +export const setRecentlySavedItemInRedis = async ( + userId: string, + url: string +) => { + // save the url in redis for 8 hours so rss-feeder won't try to re-save it + const redisKey = `recent-saved-item:${userId}:${url}` + const ttlInSeconds = 60 * 60 * 8 + return redisClient.set(redisKey, 1, { + EX: ttlInSeconds, + NX: true, + }) +} From 8137a74f5139faf830f39bb0ba920f4fade6ee73 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 18:14:50 +0800 Subject: [PATCH 029/278] add redis back to docker compose --- docker-compose.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a66ee9f61..a42a6931c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,14 @@ services: depends_on: postgres: condition: service_healthy + + redis: + image: "redis:6.0.9-alpine" + container_name: "omnivore-redis" + ports: + - "6379:6379" + expose: + - 6379 api: build: @@ -61,6 +69,8 @@ services: depends_on: migrate: condition: service_completed_successfully + redis: + condition: service_healthy web: build: From 0cf7054ce67c655229466cf579ece280283106e6 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 18:17:16 +0800 Subject: [PATCH 030/278] add redis url to env var --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a42a6931c..75aff218e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,10 @@ services: container_name: "omnivore-redis" ports: - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 15s + timeout: 90s expose: - 6379 @@ -66,6 +70,7 @@ services: - CLIENT_URL=http://localhost:3000 - GATEWAY_URL=http://localhost:8080/api - CONTENT_FETCH_URL=http://content-fetch:8080/?token=some_token + - REDIS_URL=redis://redis:6379 depends_on: migrate: condition: service_completed_successfully From feeb25588334780e50c7d56f099178c627fb861c Mon Sep 17 00:00:00 2001 From: Remy Chantenay Date: Thu, 4 Jan 2024 11:41:48 +0100 Subject: [PATCH 031/278] android: Fix OpenLinkView title for dark mode Signed-off-by: Remy Chantenay --- .../java/app/omnivore/omnivore/ui/reader/OpenLinkView.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/OpenLinkView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/OpenLinkView.kt index 4ac6f8545..c658d4b27 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/OpenLinkView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/OpenLinkView.kt @@ -1,5 +1,6 @@ package app.omnivore.omnivore.ui.reader +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,6 +22,7 @@ import app.omnivore.omnivore.R @Composable fun OpenLinkView(webReaderViewModel: WebReaderViewModel) { val context = LocalContext.current + val isDarkMode = isSystemInDarkTheme() Column(modifier = Modifier .padding(top = 25.dp) @@ -28,8 +30,8 @@ fun OpenLinkView(webReaderViewModel: WebReaderViewModel) { Row { Text(webReaderViewModel.currentLink.toString(), fontWeight = FontWeight.Light, - color = Color.DarkGray, - fontSize = 12.sp, + color = if (isDarkMode) Color.White else Color.DarkGray, + fontSize = 13.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, ) From 32a58e49f9d43fbaf042e2e7d1fe63ab30b1f47a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 21:55:41 +0800 Subject: [PATCH 032/278] make redis optional for backend --- docker-compose.yml | 12 ------------ packages/api/src/server.ts | 5 ++++- packages/api/src/services/library_item.ts | 6 +++++- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 75aff218e..fb807fa66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,18 +31,6 @@ services: depends_on: postgres: condition: service_healthy - - redis: - image: "redis:6.0.9-alpine" - container_name: "omnivore-redis" - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 15s - timeout: 90s - expose: - - 6379 api: build: diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 2380e3c37..0cf7cad88 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -158,7 +158,10 @@ const main = async (): Promise => { // as healthy. await appDataSource.initialize() - await redisClient.connect() + // redis is optional + if (env.redis.url) { + await redisClient.connect() + } const { app, apollo, httpServer } = createApp() diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index c68e6526d..8478f33f3 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -6,6 +6,7 @@ import { EntityLabel } from '../entity/entity_label' import { Highlight } from '../entity/highlight' import { Label } from '../entity/label' import { LibraryItem, LibraryItemState } from '../entity/library_item' +import { env } from '../env' import { BulkActionType, InputMaybe, SortParams } from '../generated/graphql' import { createPubSubClient, EntityType } from '../pubsub' import { @@ -818,7 +819,10 @@ export const createLibraryItem = async ( userId ) - await setRecentlySavedItemInRedis(userId, newLibraryItem.originalUrl) + // set recently saved item in redis if redis is enabled + if (env.redis.url) { + await setRecentlySavedItemInRedis(userId, newLibraryItem.originalUrl) + } if (skipPubSub) { return newLibraryItem From f4ff4b9fcda8891996798e5425390bebedb5ce43 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 22:27:31 +0800 Subject: [PATCH 033/278] fix tests --- packages/api/test/global-setup.ts | 7 +++++++ packages/api/test/global-teardown.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/api/test/global-setup.ts b/packages/api/test/global-setup.ts index d4a56e120..b36cfa15b 100644 --- a/packages/api/test/global-setup.ts +++ b/packages/api/test/global-setup.ts @@ -1,3 +1,5 @@ +import { env } from '../src/env' +import { redisClient } from '../src/redis' import { createTestConnection } from './db' import { startApolloServer } from './util' @@ -5,6 +7,11 @@ export const mochaGlobalSetup = async () => { await createTestConnection() console.log('db connection created') + if (env.redis.url) { + await redisClient.connect() + console.log('redis connection created') + } + await startApolloServer() console.log('apollo server started') } diff --git a/packages/api/test/global-teardown.ts b/packages/api/test/global-teardown.ts index ec201d2db..2d11b2a62 100644 --- a/packages/api/test/global-teardown.ts +++ b/packages/api/test/global-teardown.ts @@ -1,4 +1,6 @@ import { appDataSource } from '../src/data_source' +import { env } from '../src/env' +import { redisClient } from '../src/redis' import { stopApolloServer } from './util' export const mochaGlobalTeardown = async () => { @@ -7,4 +9,9 @@ export const mochaGlobalTeardown = async () => { await appDataSource.destroy() console.log('db connection closed') + + if (env.redis.url) { + await redisClient.disconnect() + console.log('redis connection closed') + } } From 6d48816c22e7e164c66bc419fb1efbc166618baf Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 22:42:31 +0800 Subject: [PATCH 034/278] fix tests --- packages/rss-handler/src/index.ts | 2 +- packages/rss-handler/test/index.test.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index 4604690ef..248c6b4a9 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -43,7 +43,7 @@ type RssFeed = Parser.Output<{ type RssFeedItemMedia = { $: { url: string; width?: string; height?: string; medium?: string } } -type RssFeedItem = Item & { +export type RssFeedItem = Item & { 'media:thumbnail'?: RssFeedItemMedia 'media:content'?: RssFeedItemMedia[] link: string diff --git a/packages/rss-handler/test/index.test.ts b/packages/rss-handler/test/index.test.ts index 5abf0b77c..b43cf7973 100644 --- a/packages/rss-handler/test/index.test.ts +++ b/packages/rss-handler/test/index.test.ts @@ -1,13 +1,12 @@ import { expect } from 'chai' import 'mocha' -import { Item } from 'rss-parser' -import { isOldItem } from '../src' +import { isOldItem, RssFeedItem } from '../src' describe('isOldItem', () => { it('returns true if item is older than 1 day', () => { const item = { pubDate: '2020-01-01', - } as Item + } as RssFeedItem const lastFetchedAt = Date.now() expect(isOldItem(item, lastFetchedAt)).to.be.true @@ -17,7 +16,7 @@ describe('isOldItem', () => { const lastFetchedAt = Date.now() const item = { pubDate: new Date(lastFetchedAt).toISOString(), - } as Item + } as RssFeedItem expect(isOldItem(item, lastFetchedAt)).to.be.true }) From b54b696fab9bab0920d2407c80379cfba005c447 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 4 Jan 2024 23:06:37 +0800 Subject: [PATCH 035/278] skip parsing blocked feeds stored in redis --- packages/rss-handler/src/index.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index 248c6b4a9..df8e53761 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -58,6 +58,30 @@ export const isOldItem = (item: RssFeedItem, lastFetchedAt: number) => { ) } +const feedFetchFailedRedisKey = (feedUrl: string) => + `feed-fetch-failure:${feedUrl}` + +const isFeedBlocked = async (feedUrl: string, redisClient: RedisClient) => { + const key = feedFetchFailedRedisKey(feedUrl) + const result = await redisClient.get(key) + // if the feed has failed to fetch more than certain times, block it + const maxFailures = parseInt(process.env.MAX_FEED_FETCH_FAILURES ?? '10') + if (result && parseInt(result) > maxFailures) { + return true + } + + return false +} + +const blockFeed = async (feedUrl: string, redisClient: RedisClient) => { + const key = feedFetchFailedRedisKey(feedUrl) + const result = await redisClient.incr(key) + // expire the key in 1 day + await redisClient.expire(key, 24 * 60 * 60, 'NX') + + return result +} + export const isContentFetchBlocked = (feedUrl: string) => { if (feedUrl.startsWith('https://arxiv.org/')) { return true @@ -590,10 +614,17 @@ export const rssHandler = Sentry.GCPFunction.wrapHttpFunction( } = req.body console.log('Processing feed', feedUrl) + const isBlocked = await isFeedBlocked(feedUrl, redisClient) + if (isBlocked) { + console.log('feed is blocked: ', feedUrl) + return res.sendStatus(200) + } + const fetchResult = await fetchAndChecksum(feedUrl) const feed = await parseFeed(feedUrl, fetchResult.content) if (!feed) { console.error('Failed to parse RSS feed', feedUrl) + await blockFeed(feedUrl, redisClient) return res.status(500).send('INVALID_RSS_FEED') } From 75d9206262cecf34018969f71e2d70ee751cf55f Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 5 Jan 2024 11:01:15 +0800 Subject: [PATCH 036/278] remove redis from docker-compose --- docker-compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fb807fa66..a66ee9f61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,12 +58,9 @@ services: - CLIENT_URL=http://localhost:3000 - GATEWAY_URL=http://localhost:8080/api - CONTENT_FETCH_URL=http://content-fetch:8080/?token=some_token - - REDIS_URL=redis://redis:6379 depends_on: migrate: condition: service_completed_successfully - redis: - condition: service_healthy web: build: From b7fd8a63268b55570c6819f1adb708e0a81ad12d Mon Sep 17 00:00:00 2001 From: Gannon Date: Thu, 4 Jan 2024 22:11:43 -0500 Subject: [PATCH 037/278] Update LibraryView to use ModalBottomSheet using the modalBottomSheet will support back action, tapping off the bottomsheet, and swiping down --- .../app/omnivore/omnivore/EventTracker.kt | 11 +- .../omnivore/ui/auth/LoginViewModel.kt | 2 +- .../ui/library/LibraryBottomSheetState.kt | 8 + .../omnivore/ui/library/LibraryFilterBar.kt | 2 +- .../omnivore/ui/library/LibraryView.kt | 731 +++++++++--------- .../omnivore/ui/library/LibraryViewModel.kt | 643 +++++++-------- 6 files changed, 731 insertions(+), 666 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryBottomSheetState.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt index e5ce9fbcc..38abeeda0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt @@ -24,12 +24,15 @@ class EventTracker @Inject constructor(val app: Context) { PostHog.setSingletonInstance(posthog) } - fun registerUser(userID: String, intercomHash: String?) { + fun registerUser(userID: String, intercomHash: String?, isDebug: Boolean) { posthog.identify(userID) - Intercom.client().loginIdentifiedUser(Registration.create().withUserId(userID)) - intercomHash?.let { intercomHash -> - Intercom.client().setUserHash(intercomHash) + if (!isDebug) { + Intercom.client().loginIdentifiedUser(Registration.create().withUserId(userID)) + intercomHash?.let { intercomHash -> + Intercom.client().setUserHash(intercomHash) + } } + } fun track(eventName: String, properties: Properties = Properties()) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index 462e606e7..ff9b40868 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -130,7 +130,7 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { val viewer = networker.viewer() viewer?.let { - eventTracker.registerUser(viewer.userID, viewer.intercomHash) + eventTracker.registerUser(viewer.userID, viewer.intercomHash, BuildConfig.DEBUG) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryBottomSheetState.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryBottomSheetState.kt new file mode 100644 index 000000000..4bd866b45 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryBottomSheetState.kt @@ -0,0 +1,8 @@ +package app.omnivore.omnivore.ui.library + +enum class LibraryBottomSheetState { + HIDDEN, + ADD_LINK, + LABEL, + EDIT +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt index 06099ea77..576b21e9d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt @@ -63,7 +63,7 @@ fun LibraryFilterBar(viewModel: LibraryViewModel) { modifier = Modifier.padding(end = 6.dp) ) AssistChip( - onClick = { viewModel.showLabelsSelectionSheetLiveData.value = true }, + onClick = { viewModel.bottomSheetState.value = LibraryBottomSheetState.LABEL }, label = { Text(stringResource(R.string.library_filter_bar_label_labels)) }, trailingIcon = { Icon( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 6086a1e8e..76850d9af 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -3,37 +3,50 @@ package app.omnivore.omnivore.ui.library import android.content.Intent import android.util.Log import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.FloatTweenSpec import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* import androidx.compose.material.DismissDirection import androidx.compose.material.DismissState -import androidx.compose.material.FractionalThreshold import androidx.compose.material.DismissValue +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.FractionalThreshold +import androidx.compose.material.Icon +import androidx.compose.material.Scaffold +import androidx.compose.material.ScaffoldState +import androidx.compose.material.SwipeToDismiss +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Archive +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Unarchive import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material.SwipeToDismiss -import androidx.compose.material.Icon -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Archive -import androidx.compose.material.icons.filled.Unarchive -import androidx.compose.material3.* +import androidx.compose.material.rememberDismissState +import androidx.compose.material.rememberScaffoldState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -47,370 +60,386 @@ import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent import app.omnivore.omnivore.ui.components.LabelsViewModel import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel -import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import app.omnivore.omnivore.ui.reader.PDFReaderActivity import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity import app.omnivore.omnivore.ui.save.SaveState import app.omnivore.omnivore.ui.save.SaveViewModel +import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @Composable fun LibraryView( - libraryViewModel: LibraryViewModel, - labelsViewModel: LabelsViewModel, - saveViewModel: SaveViewModel, - editInfoViewModel: EditInfoViewModel, - navController: NavHostController + libraryViewModel: LibraryViewModel, + labelsViewModel: LabelsViewModel, + saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, + navController: NavHostController ) { - val scaffoldState: ScaffoldState = rememberScaffoldState() - val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false) - val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false) - val showEditInfoSheet: Boolean by libraryViewModel.showEditInfoSheetLiveData.observeAsState(false) + val scaffoldState: ScaffoldState = rememberScaffoldState() - val coroutineScope = rememberCoroutineScope() - val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, - confirmValueChange = { it != ModalBottomSheetValue.Hidden } - ) + val coroutineScope = rememberCoroutineScope() - if (showLabelsSelectionSheet || showAddLinkSheet || showEditInfoSheet) { - coroutineScope.launch { - modalBottomSheetState.show() - } - } else { - coroutineScope.launch { - modalBottomSheetState.hide() - } - } - - libraryViewModel.snackbarMessage?.let { - coroutineScope.launch { - scaffoldState.snackbarHostState.showSnackbar(it) - libraryViewModel.clearSnackbarMessage() - } - } - - ModalBottomSheetLayout( - sheetBackgroundColor = Color.Transparent, - sheetState = modalBottomSheetState, - sheetContent = { - BottomSheetContent(libraryViewModel, labelsViewModel, saveViewModel,editInfoViewModel) - Spacer(modifier = Modifier.weight(1.0F)) - } - ) { - Scaffold( - scaffoldState = scaffoldState, - topBar = { - LibraryNavigationBar( - savedItemViewModel = libraryViewModel, - onSearchClicked = { navController.navigate(Routes.Search.route) }, - onAddLinkClicked = { libraryViewModel.showAddLinkSheetLiveData.value = true }, - onSettingsIconClick = { navController.navigate(Routes.Settings.route) } - ) - }, - ) { paddingValues -> - LibraryViewContent( - libraryViewModel, - modifier = Modifier - .padding(top = paddingValues.calculateTopPadding()) - ) - } - } -} - -@Composable -fun BottomSheetContent(libraryViewModel: LibraryViewModel, - labelsViewModel: LabelsViewModel, - saveViewModel: SaveViewModel, - editInfoViewModel: EditInfoViewModel -) { - val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false) - val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false) - val showEditInfoSheet: Boolean by libraryViewModel.showEditInfoSheetLiveData.observeAsState(false) - val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() - val labels: List by libraryViewModel.savedItemLabelsLiveData.observeAsState(listOf()) - - if (showLabelsSelectionSheet) { - BottomSheetUI { - if (currentSavedItemData != null) { - LabelsSelectionSheetContent( - labels = labels, - labelsViewModel = labelsViewModel, - initialSelectedLabels = currentSavedItemData.labels, - onCancel = { - libraryViewModel.showLabelsSelectionSheetLiveData.value = false - libraryViewModel.currentItemLiveData.value = null - }, - isLibraryMode = false, - onSave = { - if (it != labels) { - libraryViewModel.updateSavedItemLabels( - savedItemID = currentSavedItemData.savedItem.savedItemId, - labels = it - ) - } - libraryViewModel.currentItemLiveData.value = null - libraryViewModel.showLabelsSelectionSheetLiveData.value = false - }, - onCreateLabel = { newLabelName, labelHexValue -> - libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue) - } - ) - } else { // Is used in library mode - LabelsSelectionSheetContent( - labels = labels, - labelsViewModel = labelsViewModel, - initialSelectedLabels = libraryViewModel.activeLabelsLiveData.value ?: listOf(), - onCancel = { libraryViewModel.showLabelsSelectionSheetLiveData.value = false }, - isLibraryMode = true, - onSave = { - libraryViewModel.updateAppliedLabels(it) - libraryViewModel.currentItemLiveData.value = null - libraryViewModel.showLabelsSelectionSheetLiveData.value = false - }, - onCreateLabel = { newLabelName, labelHexValue -> - libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue) - } - ) - } - } - } else if (showAddLinkSheet) { - BottomSheetUI { - AddLinkSheetContent( - viewModel = saveViewModel, - onCancel = { - libraryViewModel.showAddLinkSheetLiveData.value = false - saveViewModel.state.value = SaveState.DEFAULT - }, - onLinkAdded = { - libraryViewModel.showAddLinkSheetLiveData.value = false - saveViewModel.state.value = SaveState.DEFAULT - } - ) - } - } else if (showEditInfoSheet) { - BottomSheetUI { - EditInfoSheetContent( - savedItemId = currentSavedItemData?.savedItem?.savedItemId, - title = currentSavedItemData?.savedItem?.title, - author = currentSavedItemData?.savedItem?.author, - description = currentSavedItemData?.savedItem?.descriptionText, - viewModel = editInfoViewModel, - onCancel = { - libraryViewModel.showEditInfoSheetLiveData.value = false - libraryViewModel.currentItemLiveData.value = null - }, - onUpdated = { - libraryViewModel.showEditInfoSheetLiveData.value = false - libraryViewModel.currentItemLiveData.value = null - libraryViewModel.refresh() - } - ) - } - } -} - -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) -@Composable -fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) { - val context = LocalContext.current - val listState = rememberLazyListState() - - val pullRefreshState = rememberPullRefreshState( - refreshing = libraryViewModel.isRefreshing, - onRefresh = { libraryViewModel.refresh() } - ) - - val selectedItem: SavedItemWithLabelsAndHighlights? by libraryViewModel.actionsMenuItemLiveData.observeAsState() - val cardsData: List by libraryViewModel.itemsLiveData.observeAsState( - listOf() - ) - - Box( - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - ) { - - LazyColumn( - state = listState, - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize() - .padding(horizontal = 6.dp) - ) { - item { - LibraryFilterBar(libraryViewModel) - } - items( - items = cardsData, - key = { item -> item.savedItem.savedItemId } - ) { cardDataWithLabels -> - val swipeThreshold = 0.40f - - val currentThresholdFraction = remember { mutableStateOf(0f) } - val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem) - val swipeState = rememberDismissState( - confirmStateChange = { - if (it == DismissValue.DismissedToEnd || - currentThresholdFraction.value < swipeThreshold || - currentThresholdFraction.value > 1.0f) { - false - } - - if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving. - if (currentItem.isArchived) { - libraryViewModel.unarchiveSavedItem(currentItem.savedItemId) - } else { - libraryViewModel.archiveSavedItem(currentItem.savedItemId) - } - } else if (it == DismissValue.DismissedToStart) { // Deleting. - libraryViewModel.deleteSavedItem(currentItem.savedItemId) - } - - true - } - ) - SwipeToDismiss( - state = swipeState, - modifier = Modifier.padding(vertical = 4.dp), - directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart), - dismissThresholds = { FractionalThreshold(swipeThreshold) }, - background = { - val direction = swipeState.dismissDirection ?: return@SwipeToDismiss - val color by animateColorAsState( - when (swipeState.targetValue) { - DismissValue.Default -> Color.LightGray - DismissValue.DismissedToEnd -> Color.Green - DismissValue.DismissedToStart -> Color.Red - }, label = "backgroundColor" - ) - val alignment = when (direction) { - DismissDirection.StartToEnd -> Alignment.CenterStart - DismissDirection.EndToStart -> Alignment.CenterEnd - } - val icon = when (direction) { - DismissDirection.StartToEnd -> if (currentItem.isArchived) Icons.Default.Unarchive else Icons.Default.Archive - DismissDirection.EndToStart -> Icons.Default.Delete - } - val scale by animateFloatAsState( - if (swipeState.targetValue == DismissValue.Default) 0.75f else 1f, - label = "scaleAnimation" - ) - - Box( - Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp), - contentAlignment = alignment - ) { - currentThresholdFraction.value = swipeState.progress.fraction - Icon( - icon, - contentDescription = null, - modifier = Modifier.scale(scale) - ) - } - }, - dismissContent = { - val selected = currentItem.savedItemId == selectedItem?.savedItem?.savedItemId - SavedItemCard( - selected = selected, - savedItemViewModel = libraryViewModel, - savedItem = cardDataWithLabels, - onClickHandler = { - libraryViewModel.actionsMenuItemLiveData.postValue(null) - val activityClass = - if (currentItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java - val intent = Intent(context, activityClass) - intent.putExtra("SAVED_ITEM_SLUG", currentItem.slug) - context.startActivity(intent) - }, - actionHandler = { - libraryViewModel.handleSavedItemAction( - currentItem.savedItemId, - it - ) - } - ) - }, - ) - when { - swipeState.isDismissed(DismissDirection.EndToStart) -> Reset(state = swipeState) - swipeState.isDismissed(DismissDirection.StartToEnd) -> Reset(state = swipeState) - } - } - } - - InfiniteListHandler(listState = listState) { - if (cardsData.isEmpty()) { - Log.d("sync", "loading with load func") - libraryViewModel.initialLoad() - } else { - Log.d("sync", "loading with search api") - libraryViewModel.loadUsingSearchAPI() - } - } - - PullRefreshIndicator( - refreshing = libraryViewModel.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter) + val showBottomSheet: LibraryBottomSheetState by libraryViewModel.bottomSheetState.observeAsState( + LibraryBottomSheetState.HIDDEN ) - // LabelsSelectionSheet(viewModel = libraryViewModel) - } + libraryViewModel.snackbarMessage?.let { + coroutineScope.launch { + scaffoldState.snackbarHostState.showSnackbar(it) + libraryViewModel.clearSnackbarMessage() + } + } + + when (showBottomSheet) { + LibraryBottomSheetState.ADD_LINK -> { + AddLinkBottomSheet(saveViewModel) { + libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN + } + } + + LibraryBottomSheetState.LABEL -> { + LabelBottomSheet( + libraryViewModel, + labelsViewModel + ) { + libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN + } + } + + LibraryBottomSheetState.EDIT -> { + EditBottomSheet( + editInfoViewModel, + libraryViewModel + ) { + libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN + } + } + + LibraryBottomSheetState.HIDDEN -> { + } + } + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + LibraryNavigationBar( + savedItemViewModel = libraryViewModel, + onSearchClicked = { navController.navigate(Routes.Search.route) }, + onAddLinkClicked = { showAddLinkBottomSheet(libraryViewModel) }, + onSettingsIconClick = { navController.navigate(Routes.Settings.route) } + ) + }, + ) { paddingValues -> + LibraryViewContent( + libraryViewModel, + modifier = Modifier + .padding(top = paddingValues.calculateTopPadding()) + ) + } +} + +fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) { + libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LabelBottomSheet( + libraryViewModel: LibraryViewModel, + labelsViewModel: LabelsViewModel, + onDismiss: () -> Unit = {} +) { + ModalBottomSheet(onDismissRequest = { onDismiss() }) { + val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() + val labels: List by libraryViewModel.savedItemLabelsLiveData.observeAsState( + listOf() + ) + if (currentSavedItemData != null) { + LabelsSelectionSheetContent( + labels = labels, + labelsViewModel = labelsViewModel, + initialSelectedLabels = currentSavedItemData.labels, + onCancel = { + libraryViewModel.currentItemLiveData.value = null + onDismiss() + }, + isLibraryMode = false, + onSave = { + if (it != labels) { + libraryViewModel.updateSavedItemLabels( + savedItemID = currentSavedItemData.savedItem.savedItemId, + labels = it + ) + } + libraryViewModel.currentItemLiveData.value = null + onDismiss() + }, + onCreateLabel = { newLabelName, labelHexValue -> + libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue) + } + ) + } else { // Is used in library mode + LabelsSelectionSheetContent( + labels = labels, + labelsViewModel = labelsViewModel, + initialSelectedLabels = libraryViewModel.activeLabelsLiveData.value ?: listOf(), + onCancel = { onDismiss() }, + isLibraryMode = true, + onSave = { + libraryViewModel.updateAppliedLabels(it) + libraryViewModel.currentItemLiveData.value = null + onDismiss() + }, + onCreateLabel = { newLabelName, labelHexValue -> + libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue) + } + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddLinkBottomSheet( + saveViewModel: SaveViewModel, + onDismiss: () -> Unit = {} +) { + ModalBottomSheet(onDismissRequest = { onDismiss() }) { + AddLinkSheetContent( + viewModel = saveViewModel, + onCancel = { + saveViewModel.state.value = SaveState.DEFAULT + onDismiss() + }, + onLinkAdded = { + saveViewModel.state.value = SaveState.DEFAULT + onDismiss() + } + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditBottomSheet( + editInfoViewModel: EditInfoViewModel, + libraryViewModel: LibraryViewModel, + onDismiss: () -> Unit = {} +) { + ModalBottomSheet(onDismissRequest = { onDismiss() }) { + val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() + EditInfoSheetContent( + savedItemId = currentSavedItemData?.savedItem?.savedItemId, + title = currentSavedItemData?.savedItem?.title, + author = currentSavedItemData?.savedItem?.author, + description = currentSavedItemData?.savedItem?.descriptionText, + viewModel = editInfoViewModel, + onCancel = { + libraryViewModel.currentItemLiveData.value = null + onDismiss() + }, + onUpdated = { + libraryViewModel.currentItemLiveData.value = null + libraryViewModel.refresh() + onDismiss() + } + ) + } +} + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) { + val context = LocalContext.current + val listState = rememberLazyListState() + + val pullRefreshState = rememberPullRefreshState( + refreshing = libraryViewModel.isRefreshing, + onRefresh = { libraryViewModel.refresh() } + ) + + val selectedItem: SavedItemWithLabelsAndHighlights? by libraryViewModel.actionsMenuItemLiveData.observeAsState() + val cardsData: List by libraryViewModel.itemsLiveData.observeAsState( + listOf() + ) + + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + + LazyColumn( + state = listState, + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .padding(horizontal = 6.dp) + ) { + item { + LibraryFilterBar(libraryViewModel) + } + items( + items = cardsData, + key = { item -> item.savedItem.savedItemId } + ) { cardDataWithLabels -> + val swipeThreshold = 0.40f + + val currentThresholdFraction = remember { mutableStateOf(0f) } + val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem) + val swipeState = rememberDismissState( + confirmStateChange = { + if (it == DismissValue.DismissedToEnd || + currentThresholdFraction.value < swipeThreshold || + currentThresholdFraction.value > 1.0f + ) { + false + } + + if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving. + if (currentItem.isArchived) { + libraryViewModel.unarchiveSavedItem(currentItem.savedItemId) + } else { + libraryViewModel.archiveSavedItem(currentItem.savedItemId) + } + } else if (it == DismissValue.DismissedToStart) { // Deleting. + libraryViewModel.deleteSavedItem(currentItem.savedItemId) + } + + true + } + ) + SwipeToDismiss( + state = swipeState, + modifier = Modifier.padding(vertical = 4.dp), + directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart), + dismissThresholds = { FractionalThreshold(swipeThreshold) }, + background = { + val direction = swipeState.dismissDirection ?: return@SwipeToDismiss + val color by animateColorAsState( + when (swipeState.targetValue) { + DismissValue.Default -> Color.LightGray + DismissValue.DismissedToEnd -> Color.Green + DismissValue.DismissedToStart -> Color.Red + }, label = "backgroundColor" + ) + val alignment = when (direction) { + DismissDirection.StartToEnd -> Alignment.CenterStart + DismissDirection.EndToStart -> Alignment.CenterEnd + } + val icon = when (direction) { + DismissDirection.StartToEnd -> if (currentItem.isArchived) Icons.Default.Unarchive else Icons.Default.Archive + DismissDirection.EndToStart -> Icons.Default.Delete + } + val scale by animateFloatAsState( + if (swipeState.targetValue == DismissValue.Default) 0.75f else 1f, + label = "scaleAnimation" + ) + + Box( + Modifier + .fillMaxSize() + .background(color) + .padding(horizontal = 20.dp), + contentAlignment = alignment + ) { + currentThresholdFraction.value = swipeState.progress.fraction + Icon( + icon, + contentDescription = null, + modifier = Modifier.scale(scale) + ) + } + }, + dismissContent = { + val selected = + currentItem.savedItemId == selectedItem?.savedItem?.savedItemId + SavedItemCard( + selected = selected, + savedItemViewModel = libraryViewModel, + savedItem = cardDataWithLabels, + onClickHandler = { + libraryViewModel.actionsMenuItemLiveData.postValue(null) + val activityClass = + if (currentItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java + val intent = Intent(context, activityClass) + intent.putExtra("SAVED_ITEM_SLUG", currentItem.slug) + context.startActivity(intent) + }, + actionHandler = { + libraryViewModel.handleSavedItemAction( + currentItem.savedItemId, + it + ) + } + ) + }, + ) + when { + swipeState.isDismissed(DismissDirection.EndToStart) -> Reset(state = swipeState) + swipeState.isDismissed(DismissDirection.StartToEnd) -> Reset(state = swipeState) + } + } + } + + InfiniteListHandler(listState = listState) { + if (cardsData.isEmpty()) { + Log.d("sync", "loading with load func") + libraryViewModel.initialLoad() + } else { + Log.d("sync", "loading with search api") + libraryViewModel.loadUsingSearchAPI() + } + } + + PullRefreshIndicator( + refreshing = libraryViewModel.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + + // LabelsSelectionSheet(viewModel = libraryViewModel) + } } @OptIn(ExperimentalMaterialApi::class) @Composable private fun Reset(state: DismissState) { - val scope = rememberCoroutineScope() - LaunchedEffect(key1 = state.dismissDirection) { - scope.launch { - state.reset() - state.animateTo(DismissValue.Default, FloatTweenSpec(duration= 0, delay = 0)) + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = state.dismissDirection) { + scope.launch { + state.reset() + state.animateTo(DismissValue.Default, FloatTweenSpec(duration = 0, delay = 0)) + } } - } } -@Composable -private fun BottomSheetUI(content: @Composable () -> Unit) { - Box( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) - .background(Color.White) - .statusBarsPadding() - ) { - content() - } -} - - @Composable fun InfiniteListHandler( - listState: LazyListState, - buffer: Int = 2, - onLoadMore: () -> Unit + listState: LazyListState, + buffer: Int = 2, + onLoadMore: () -> Unit ) { - val loadMore = remember { - derivedStateOf { - val layoutInfo = listState.layoutInfo - val totalItemsNumber = layoutInfo.totalItemsCount - val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 + val loadMore = remember { + derivedStateOf { + val layoutInfo = listState.layoutInfo + val totalItemsNumber = layoutInfo.totalItemsCount + val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 - lastVisibleItemIndex > (totalItemsNumber - buffer) + lastVisibleItemIndex > (totalItemsNumber - buffer) + } } - } - LaunchedEffect(loadMore) { - snapshotFlow { loadMore.value } - .distinctUntilChanged() - .collect { - onLoadMore() - } - } + LaunchedEffect(loadMore) { + snapshotFlow { loadMore.value } + .distinctUntilChanged() + .collect { + onLoadMore() + } + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index a7983cdfb..02c4742f8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -20,155 +20,160 @@ import javax.inject.Inject @HiltViewModel class LibraryViewModel @Inject constructor( - private val networker: Networker, - private val dataService: DataService, - private val datastoreRepo: DatastoreRepository, - private val resourceProvider: ResourceProvider -): ViewModel(), SavedItemViewModel { - private val contentRequestChannel = Channel(capacity = Channel.UNLIMITED) + private val networker: Networker, + private val dataService: DataService, + private val datastoreRepo: DatastoreRepository, + private val resourceProvider: ResourceProvider +) : ViewModel(), SavedItemViewModel { + private val contentRequestChannel = Channel(capacity = Channel.UNLIMITED) - private var cursor: String? = null - private var librarySearchCursor: String? = null + private var cursor: String? = null + private var librarySearchCursor: String? = null - // These are used to make sure we handle search result - // responses in the right order - private var searchIdx = 0 - private var receivedIdx = 0 + // These are used to make sure we handle search result + // responses in the right order + private var searchIdx = 0 + private var receivedIdx = 0 - var snackbarMessage by mutableStateOf(null) - private set + var snackbarMessage by mutableStateOf(null) + private set - // Live Data - private var itemsLiveDataInternal = dataService.db.savedItemDao().filteredLibraryData( - allowedArchiveStates = listOf(0), - sortKey = "newest", - requiredLabels = listOf(), - excludedLabels = listOf(), - allowedContentReaders = listOf("WEB", "PDF", "EPUB") - ) - val itemsLiveData = MediatorLiveData>() - val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX) - val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST) - val showLabelsSelectionSheetLiveData = MutableLiveData(false) - val showEditInfoSheetLiveData = MutableLiveData(false) - val showAddLinkSheetLiveData = MutableLiveData(false) - val currentItemLiveData = MutableLiveData(null) - val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() - val activeLabelsLiveData = MutableLiveData>(listOf()) + // Live Data + private var itemsLiveDataInternal = dataService.db.savedItemDao().filteredLibraryData( + allowedArchiveStates = listOf(0), + sortKey = "newest", + requiredLabels = listOf(), + excludedLabels = listOf(), + allowedContentReaders = listOf("WEB", "PDF", "EPUB") + ) + val itemsLiveData = MediatorLiveData>() + val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX) + val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST) + val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN) + val currentItemLiveData = MutableLiveData(null) + val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() + val activeLabelsLiveData = MutableLiveData>(listOf()) - override val actionsMenuItemLiveData = MutableLiveData(null) + override val actionsMenuItemLiveData = MutableLiveData(null) - var isRefreshing by mutableStateOf(false) - private var hasLoadedInitialFilters = false + var isRefreshing by mutableStateOf(false) + private var hasLoadedInitialFilters = false - private fun loadInitialFilterValues() { - if (hasLoadedInitialFilters) { return } - hasLoadedInitialFilters = false - - viewModelScope.launch { - withContext(Dispatchers.IO) { - dataService.syncLabels() - } - } - - viewModelScope.launch { - handleFilterChanges() - for (slug in contentRequestChannel) { - CoroutineScope(Dispatchers.IO).launch { - dataService.fetchSavedItemContent(slug) + private fun loadInitialFilterValues() { + if (hasLoadedInitialFilters) { + return } - } - } - } + hasLoadedInitialFilters = false - fun clearSnackbarMessage() { - snackbarMessage = null - } - - fun refresh() { - cursor = null - librarySearchCursor = null - isRefreshing = true - load() - } - - private fun getLastSyncTime(): Instant? = runBlocking { - datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let { - try { - return@let Instant.parse(it) - } catch (e: Exception) { - return@let null - } - } - } - - fun initialLoad() { - if (getLastSyncTime() == null) { - hasLoadedInitialFilters = false - cursor = null - librarySearchCursor = null - searchIdx = 0 - receivedIdx = 0 - } - - if (hasLoadedInitialFilters) { return } - load() - } - - fun load() { - loadInitialFilterValues() - - viewModelScope.launch { - syncItems() - loadUsingSearchAPI() - } - } - - fun loadUsingSearchAPI() { - viewModelScope.launch { - withContext(Dispatchers.IO) { - val result = dataService.librarySearch(cursor = librarySearchCursor, query = searchQueryString()) - result.cursor?.let { - librarySearchCursor = it - } - CoroutineScope(Dispatchers.Main).launch { - isRefreshing = false + viewModelScope.launch { + withContext(Dispatchers.IO) { + dataService.syncLabels() + } } - result.savedItems.map { - val isSavedInDB = dataService.isSavedItemContentStoredInDB(it.savedItem.slug) - - if (!isSavedInDB) { - delay(2000) - contentRequestChannel.send(it.savedItem.slug) - } + viewModelScope.launch { + handleFilterChanges() + for (slug in contentRequestChannel) { + CoroutineScope(Dispatchers.IO).launch { + dataService.fetchSavedItemContent(slug) + } + } } - } } - } - fun updateSavedItemFilter(filter: SavedItemFilter) { - viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue) - appliedFilterLiveData.value = filter - handleFilterChanges() + fun clearSnackbarMessage() { + snackbarMessage = null } - } - fun updateSavedItemSortFilter(filter: SavedItemSortFilter) { - viewModelScope.launch { - datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemSortFilter, filter.rawValue) - appliedSortFilterLiveData.value = filter - handleFilterChanges() + fun refresh() { + cursor = null + librarySearchCursor = null + isRefreshing = true + load() } - } - fun updateAppliedLabels(labels: List) { - viewModelScope.launch { - activeLabelsLiveData.value = labels - handleFilterChanges() + private fun getLastSyncTime(): Instant? = runBlocking { + datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let { + try { + return@let Instant.parse(it) + } catch (e: Exception) { + return@let null + } + } + } + + fun initialLoad() { + if (getLastSyncTime() == null) { + hasLoadedInitialFilters = false + cursor = null + librarySearchCursor = null + searchIdx = 0 + receivedIdx = 0 + } + + if (hasLoadedInitialFilters) { + return + } + load() + } + + fun load() { + loadInitialFilterValues() + + viewModelScope.launch { + syncItems() + loadUsingSearchAPI() + } + } + + fun loadUsingSearchAPI() { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val result = dataService.librarySearch( + cursor = librarySearchCursor, + query = searchQueryString() + ) + result.cursor?.let { + librarySearchCursor = it + } + CoroutineScope(Dispatchers.Main).launch { + isRefreshing = false + } + + result.savedItems.map { + val isSavedInDB = dataService.isSavedItemContentStoredInDB(it.savedItem.slug) + + if (!isSavedInDB) { + delay(2000) + contentRequestChannel.send(it.savedItem.slug) + } + } + } + } + } + + fun updateSavedItemFilter(filter: SavedItemFilter) { + viewModelScope.launch { + datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue) + appliedFilterLiveData.value = filter + handleFilterChanges() + } + } + + fun updateSavedItemSortFilter(filter: SavedItemSortFilter) { + viewModelScope.launch { + datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemSortFilter, filter.rawValue) + appliedSortFilterLiveData.value = filter + handleFilterChanges() + } + } + + fun updateAppliedLabels(labels: List) { + viewModelScope.launch { + activeLabelsLiveData.value = labels + handleFilterChanges() + } } - } // fun sortKey(appliedSortKey: String) { // when(appliedSortKey) { @@ -176,208 +181,228 @@ class LibraryViewModel @Inject constructor( // } // } - private fun handleFilterChanges() { - librarySearchCursor = null + private fun handleFilterChanges() { + librarySearchCursor = null - if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) { - val sortKey = when (appliedSortFilterLiveData.value) { - SavedItemSortFilter.NEWEST -> "newest" - SavedItemSortFilter.OLDEST -> "oldest" - SavedItemSortFilter.RECENTLY_READ -> "recentlyRead" - SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished" - else -> "newest" - } + if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) { + val sortKey = when (appliedSortFilterLiveData.value) { + SavedItemSortFilter.NEWEST -> "newest" + SavedItemSortFilter.OLDEST -> "oldest" + SavedItemSortFilter.RECENTLY_READ -> "recentlyRead" + SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished" + else -> "newest" + } - val allowedArchiveStates = when (appliedFilterLiveData.value) { - SavedItemFilter.ALL -> listOf(0, 1) - SavedItemFilter.ARCHIVED -> listOf(1) - else -> listOf(0) - } + val allowedArchiveStates = when (appliedFilterLiveData.value) { + SavedItemFilter.ALL -> listOf(0, 1) + SavedItemFilter.ARCHIVED -> listOf(1) + else -> listOf(0) + } - val allowedContentReaders = when(appliedFilterLiveData.value) { - SavedItemFilter.FILES -> listOf("PDF", "EPUB") - else -> listOf("WEB", "PDF", "EPUB") - } + val allowedContentReaders = when (appliedFilterLiveData.value) { + SavedItemFilter.FILES -> listOf("PDF", "EPUB") + else -> listOf("WEB", "PDF", "EPUB") + } - var requiredLabels = when(appliedFilterLiveData.value) { - SavedItemFilter.NEWSLETTERS -> listOf("Newsletter") - SavedItemFilter.FEEDS -> listOf("RSS") - else -> (activeLabelsLiveData.value ?: listOf()).map { it.name } - } + var requiredLabels = when (appliedFilterLiveData.value) { + SavedItemFilter.NEWSLETTERS -> listOf("Newsletter") + SavedItemFilter.FEEDS -> listOf("RSS") + else -> (activeLabelsLiveData.value ?: listOf()).map { it.name } + } - activeLabelsLiveData.value?.let { it -> - requiredLabels = requiredLabels + it.map { it.name } - } + activeLabelsLiveData.value?.let { it -> + requiredLabels = requiredLabels + it.map { it.name } + } - val excludeLabels = when(appliedFilterLiveData.value) { - SavedItemFilter.READ_LATER -> listOf("Newsletter", "RSS") - else -> listOf() - } + val excludeLabels = when (appliedFilterLiveData.value) { + SavedItemFilter.READ_LATER -> listOf("Newsletter", "RSS") + else -> listOf() + } - val newData = dataService.db.savedItemDao().filteredLibraryData( - allowedArchiveStates = allowedArchiveStates, - sortKey = sortKey, - requiredLabels = requiredLabels, - excludedLabels = excludeLabels, - allowedContentReaders = allowedContentReaders - ) + val newData = dataService.db.savedItemDao().filteredLibraryData( + allowedArchiveStates = allowedArchiveStates, + sortKey = sortKey, + requiredLabels = requiredLabels, + excludedLabels = excludeLabels, + allowedContentReaders = allowedContentReaders + ) - itemsLiveData.removeSource(itemsLiveDataInternal) - itemsLiveDataInternal = newData - itemsLiveData.addSource(itemsLiveDataInternal, itemsLiveData::setValue) - } - } - - private suspend fun syncItems() { - val syncStart = Instant.now() - val lastSyncDate = getLastSyncTime() ?: Instant.MIN - - withContext(Dispatchers.IO) { - performItemSync(cursor = null, since = lastSyncDate.toString(), count = 0, startTime = syncStart.toString()) - CoroutineScope(Dispatchers.Main).launch { - isRefreshing = false - } - } - } - - private suspend fun performItemSync(cursor: String?, since: String, count: Int, startTime: String, isInitialBatch: Boolean = true) { - dataService.syncOfflineItemsWithServerIfNeeded() - val result = dataService.sync(since = since, cursor = cursor, limit = 20) - - // Fetch content for the initial batch only - if (isInitialBatch) { - for (slug in result.savedItemSlugs) { - delay(250) - contentRequestChannel.send(slug) - } + itemsLiveData.removeSource(itemsLiveDataInternal) + itemsLiveDataInternal = newData + itemsLiveData.addSource(itemsLiveDataInternal, itemsLiveData::setValue) + } } - val totalCount = count + result.count + private suspend fun syncItems() { + val syncStart = Instant.now() + val lastSyncDate = getLastSyncTime() ?: Instant.MIN - if (!result.hasError && result.hasMoreItems && result.cursor != null) { - performItemSync( - cursor = result.cursor, - since = since, - count = totalCount, - startTime = startTime, - isInitialBatch = false - ) - } else { - datastoreRepo.putString(DatastoreKeys.libraryLastSyncTimestamp, startTime) + withContext(Dispatchers.IO) { + performItemSync( + cursor = null, + since = lastSyncDate.toString(), + count = 0, + startTime = syncStart.toString() + ) + CoroutineScope(Dispatchers.Main).launch { + isRefreshing = false + } + } } - } - override fun handleSavedItemAction(itemID: String, action: SavedItemAction) { - when (action) { - SavedItemAction.Delete -> { - deleteSavedItem(itemID) - } - SavedItemAction.Archive -> { - archiveSavedItem(itemID) - } - SavedItemAction.Unarchive -> { - unarchiveSavedItem(itemID) - } - SavedItemAction.EditLabels -> { - currentItemLiveData.value = itemID - showLabelsSelectionSheetLiveData.value = true - } - SavedItemAction.EditInfo -> { - currentItemLiveData.value = itemID - showEditInfoSheetLiveData.value = true - } - } - actionsMenuItemLiveData.postValue(null) - } + private suspend fun performItemSync( + cursor: String?, + since: String, + count: Int, + startTime: String, + isInitialBatch: Boolean = true + ) { + dataService.syncOfflineItemsWithServerIfNeeded() + val result = dataService.sync(since = since, cursor = cursor, limit = 20) - fun deleteSavedItem(itemID: String) { - viewModelScope.launch { - dataService.deleteSavedItem(itemID) - } - } + // Fetch content for the initial batch only + if (isInitialBatch) { + for (slug in result.savedItemSlugs) { + delay(250) + contentRequestChannel.send(slug) + } + } - fun archiveSavedItem(itemID: String) { - viewModelScope.launch { - dataService.archiveSavedItem(itemID) - } - } + val totalCount = count + result.count - fun unarchiveSavedItem(itemID: String) { - viewModelScope.launch { - dataService.unarchiveSavedItem(itemID) - } - } - - fun updateSavedItemLabels(savedItemID: String, labels: List) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - val result = setSavedItemLabels( - networker = networker, - dataService = dataService, - savedItemID = savedItemID, - labels = labels - ) - - snackbarMessage = if (result) { - resourceProvider.getString(R.string.library_view_model_snackbar_success) + if (!result.hasError && result.hasMoreItems && result.cursor != null) { + performItemSync( + cursor = result.cursor, + since = since, + count = totalCount, + startTime = startTime, + isInitialBatch = false + ) } else { - resourceProvider.getString(R.string.library_view_model_snackbar_error) + datastoreRepo.putString(DatastoreKeys.libraryLastSyncTimestamp, startTime) + } + } + + override fun handleSavedItemAction(itemID: String, action: SavedItemAction) { + when (action) { + SavedItemAction.Delete -> { + deleteSavedItem(itemID) + } + + SavedItemAction.Archive -> { + archiveSavedItem(itemID) + } + + SavedItemAction.Unarchive -> { + unarchiveSavedItem(itemID) + } + + SavedItemAction.EditLabels -> { + currentItemLiveData.value = itemID + bottomSheetState.value = LibraryBottomSheetState.EDIT + } + + SavedItemAction.EditInfo -> { + currentItemLiveData.value = itemID + bottomSheetState.value = LibraryBottomSheetState.EDIT + } + } + actionsMenuItemLiveData.postValue(null) + } + + fun deleteSavedItem(itemID: String) { + viewModelScope.launch { + dataService.deleteSavedItem(itemID) + } + } + + fun archiveSavedItem(itemID: String) { + viewModelScope.launch { + dataService.archiveSavedItem(itemID) + } + } + + fun unarchiveSavedItem(itemID: String) { + viewModelScope.launch { + dataService.unarchiveSavedItem(itemID) + } + } + + fun updateSavedItemLabels(savedItemID: String, labels: List) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val result = setSavedItemLabels( + networker = networker, + dataService = dataService, + savedItemID = savedItemID, + labels = labels + ) + + snackbarMessage = if (result) { + resourceProvider.getString(R.string.library_view_model_snackbar_success) + } else { + resourceProvider.getString(R.string.library_view_model_snackbar_error) + } + + CoroutineScope(Dispatchers.Main).launch { + handleFilterChanges() + } + } + } + } + + fun createNewSavedItemLabel(labelName: String, hexColorValue: String) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val newLabel = networker.createNewLabel( + CreateLabelInput( + color = Optional.presentIfNotNull(hexColorValue), name = labelName + ) + ) + + newLabel?.let { + val savedItemLabel = SavedItemLabel( + savedItemLabelId = it.id, + name = it.name, + color = it.color, + createdAt = it.createdAt as String?, + labelDescription = it.description + ) + + dataService.db.savedItemLabelDao().insertAll(listOf(savedItemLabel)) + } + } + } + } + + fun currentSavedItemUnderEdit(): SavedItemWithLabelsAndHighlights? { + currentItemLiveData.value?.let { itemID -> + return itemsLiveData.value?.first { it.savedItem.savedItemId == itemID } } - CoroutineScope(Dispatchers.Main).launch { - handleFilterChanges() + return null + } + + private fun searchQueryString(): String { + var query = + "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}" + + activeLabelsLiveData.value?.let { + if (it.isNotEmpty()) { + query += " label:" + query += it.joinToString { label -> label.name } + } } - } + + return query } - } - - fun createNewSavedItemLabel(labelName: String, hexColorValue: String) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - val newLabel = networker.createNewLabel(CreateLabelInput(color = Optional.presentIfNotNull(hexColorValue), name = labelName)) - - newLabel?.let { - val savedItemLabel = SavedItemLabel( - savedItemLabelId = it.id, - name = it.name, - color = it.color, - createdAt = it.createdAt as String?, - labelDescription = it.description - ) - - dataService.db.savedItemLabelDao().insertAll(listOf(savedItemLabel)) - } - } - } - } - - fun currentSavedItemUnderEdit(): SavedItemWithLabelsAndHighlights? { - currentItemLiveData.value?.let { itemID -> - return itemsLiveData.value?.first { it.savedItem.savedItemId == itemID } - } - - return null - } - - private fun searchQueryString(): String { - var query = "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}" - - activeLabelsLiveData.value?.let { - if (it.isNotEmpty()) { - query += " label:" - query += it.joinToString { label -> label.name } - } - } - - return query - } } enum class SavedItemAction { - Delete, - Archive, - Unarchive, - EditLabels, - EditInfo, + Delete, + Archive, + Unarchive, + EditLabels, + EditInfo, } From 09dd9a62e3a9b5e5d354558a3ee5c48bae7d689c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 Jan 2024 14:38:48 +0800 Subject: [PATCH 038/278] Run prettier on updated file --- .../keyboardShortcuts/useKeyboardShortcuts.ts | 161 +++++++++--------- 1 file changed, 82 insertions(+), 79 deletions(-) diff --git a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts index 604e151f4..1caae5bbc 100644 --- a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts @@ -1,6 +1,6 @@ -import { useEffect, useCallback, useReducer, useMemo } from 'react'; +import { useEffect, useCallback, useReducer, useMemo } from 'react' -const disabledTargets = ['INPUT', 'TEXTAREA']; +const disabledTargets = ['INPUT', 'TEXTAREA'] /* Usage: commands: KeyboardCommand[] = [{ @@ -10,19 +10,19 @@ const disabledTargets = ['INPUT', 'TEXTAREA']; ] */ -const KBAR_KEYS = ['control', 'meta', 'v', 'i', 's', 'n', '[', ']']; +const KBAR_KEYS = ['control', 'meta', 'v', 'i', 's', 'n', '[', ']'] type KeyPressed = { - [name: string]: boolean; -}; + [name: string]: boolean +} export type KeyboardCommand = { - shortcutKeys: string[]; - callback: () => void; - actionDescription: string; - shortcutKeyDescription: string; - ignoreDisabledTargets?: boolean; -}; + shortcutKeys: string[] + callback: () => void + actionDescription: string + shortcutKeyDescription: string + ignoreDisabledTargets?: boolean +} enum ActionType { SET_KEY_DOWN, @@ -35,131 +35,134 @@ const keysReducer = ( ): KeyPressed => { switch (action.type) { case ActionType.SET_KEY_DOWN: - return { ...state, [action.key]: true }; + return { ...state, [action.key]: true } case ActionType.SET_KEY_UP: - return { ...state, [action.key]: false }; + return { ...state, [action.key]: false } default: - return state; + return state } -}; +} // Labels const useInitialKeyMapping = (commands: KeyboardCommand[]): KeyPressed => { - const currentKeys: KeyPressed = {}; + const currentKeys: KeyPressed = {} commands.forEach((command) => { command.shortcutKeys.forEach((key) => { - const aliases = key.split('|'); + const aliases = key.split('|') aliases.forEach((k) => { - currentKeys[k.toLowerCase()] = false; - }); - }); - }); + currentKeys[k.toLowerCase()] = false + }) + }) + }) - KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)); - return currentKeys; -}; + KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)) + return currentKeys +} export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { - const initalKeyMapping = useMemo(() => useInitialKeyMapping(commands), [commands]); + const initalKeyMapping = useMemo( + () => useInitialKeyMapping(commands), + [commands] + ) - const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping); + const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping) const metaPressed = useCallback(() => { - return keys['meta'] === true; - }, [keys]); + return keys['meta'] === true + }, [keys]) const applyCommands = useCallback( (updatedKeys: KeyPressed, disabled?: boolean) => { - let commandApplied = false; + let commandApplied = false commands.forEach((command) => { if (disabled && !command.ignoreDisabledTargets) { - return; + return } - const tempState = { ...updatedKeys }; + const tempState = { ...updatedKeys } const arePressed = command.shortcutKeys.every((key) => { - const aliases = key.split('|'); + const aliases = key.split('|') for (const k of aliases) { if (tempState[k]) { - delete tempState[k]; - return true; + delete tempState[k] + return true } } - return false; - }); + return false + }) // apply callback if 1)corresponding buttons are pressed 2) no other buttons are pressed if (arePressed && !Object.values(tempState).some((k) => k)) { - command.callback(); - commandApplied = true; + command.callback() + commandApplied = true // remove applied keys from state to prevent Key Lifetime smashes command.shortcutKeys.forEach((key, index) => { // don't remove a base key to allow non-stop actions - if (index === 0 && command.shortcutKeys.length > 1) return; - const aliases = key.split('|'); + if (index === 0 && command.shortcutKeys.length > 1) return + const aliases = key.split('|') for (const l of aliases) { - setKeys({ type: ActionType.SET_KEY_UP, key: l }); + setKeys({ type: ActionType.SET_KEY_UP, key: l }) } - }); + }) } - }); - return commandApplied; + }) + return commandApplied }, [commands] - ); + ) const keydownListener = useCallback( (keydownEvent: any) => { - const { target } = keydownEvent; - if (!keydownEvent.key) return; - const key = keydownEvent.key.toLowerCase(); - if (keys[key] === undefined) return; + const { target } = keydownEvent + if (!keydownEvent.key) return + const key = keydownEvent.key.toLowerCase() + if (keys[key] === undefined) return if (keys[key] === false) { if (key === 'k' && metaPressed()) { // not setting the K value because meta is already pressed - return; + return } - setKeys({ type: ActionType.SET_KEY_DOWN, key }); + setKeys({ type: ActionType.SET_KEY_DOWN, key }) } const commandApplied = applyCommands( { ...keys, [key]: true }, disabledTargets.includes(target.tagName) - ); + ) if (commandApplied) { - keydownEvent.preventDefault(); + keydownEvent.preventDefault() } }, [applyCommands, keys, metaPressed] - ); + ) const keyupListener = useCallback( (keyupEvent: any) => { - if (!keyupEvent.key) return; - const key = keyupEvent.key.toLowerCase(); - if (keys[key] === undefined) return; + if (!keyupEvent.key) return + const key = keyupEvent.key.toLowerCase() + if (keys[key] === undefined) return if (keys[key] === true) { - setTimeout(() => setKeys({ type: ActionType.SET_KEY_UP, key }), 800); + setTimeout(() => setKeys({ type: ActionType.SET_KEY_UP, key }), 800) } }, [keys] - ); + ) useEffect(() => { if (typeof window === 'undefined') { - return; + return } - window.addEventListener('keydown', keydownListener, true); - return () => window.removeEventListener('keydown', keydownListener, true); - }, [keydownListener]); + window.addEventListener('keydown', keydownListener, true) + return () => window.removeEventListener('keydown', keydownListener, true) + }, [keydownListener]) useEffect(() => { if (typeof window === 'undefined') { - return; + return } - window.addEventListener('keyup', keyupListener, true); - return () => window.removeEventListener('keyup', keyupListener, true); - }, [keyupListener]); -}; + window.addEventListener('keyup', keyupListener, true) + return () => window.removeEventListener('keyup', keyupListener, true) + }, [keyupListener]) +} export const useConfirmListener = ( onConfirm?: () => void, @@ -168,28 +171,28 @@ export const useConfirmListener = ( ): void => { const keyHandleConfirm = useCallback( (e: KeyboardEvent) => { - const key = e.key.toLowerCase(); + const key = e.key.toLowerCase() if (onConfirm && key === 'enter') { // if control key is required we make sure CTRL on // meta is pressed when pressing enter. if (ctrlOnConfirm && !e.metaKey && !e.ctrlKey) { - return; + return } - onConfirm(); + onConfirm() } else if (onCancel && key === 'escape') { - onCancel(); + onCancel() } }, // eslint-disable-next-line react-hooks/exhaustive-deps [onCancel, onConfirm] - ); + ) useEffect(() => { - if (typeof window === 'undefined') return; + if (typeof window === 'undefined') return - window.addEventListener('keydown', keyHandleConfirm); + window.addEventListener('keydown', keyHandleConfirm) return () => { - window.removeEventListener('keydown', keyHandleConfirm); - }; - }, [keyHandleConfirm]); -}; + window.removeEventListener('keydown', keyHandleConfirm) + } + }, [keyHandleConfirm]) +} From 7eeed4db8140d54528e350c911fd8b6abf66288d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 Jan 2024 14:57:48 +0800 Subject: [PATCH 039/278] Revert change as it had some issue with hooks --- .../keyboardShortcuts/useKeyboardShortcuts.ts | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts index 1caae5bbc..c91c65a8e 100644 --- a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts @@ -43,27 +43,21 @@ const keysReducer = ( } } -// Labels -const useInitialKeyMapping = (commands: KeyboardCommand[]): KeyPressed => { - const currentKeys: KeyPressed = {} - commands.forEach((command) => { - command.shortcutKeys.forEach((key) => { - const aliases = key.split('|') - aliases.forEach((k) => { - currentKeys[k.toLowerCase()] = false +export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { + const initalKeyMapping = useMemo(() => { + const currentKeys: KeyPressed = {} + commands.forEach((command) => { + command.shortcutKeys.forEach((key) => { + const aliases = key.split('|') + aliases.forEach((k) => { + currentKeys[k.toLowerCase()] = false + }) }) }) - }) - KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)) - return currentKeys -} - -export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { - const initalKeyMapping = useMemo( - () => useInitialKeyMapping(commands), - [commands] - ) + KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false)) + return currentKeys + }, [commands]) const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping) From ccd4f2d3f0208d344c6942d4358f01866fba0838 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 5 Jan 2024 15:20:42 +0800 Subject: [PATCH 040/278] catch redis client error and continue the process --- packages/api/src/server.ts | 5 +++++ packages/api/src/utils/helpers.ts | 15 +++++++++++---- packages/rss-handler/src/index.ts | 27 ++++++++++++++++++--------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 0cf7cad88..a6458730f 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -160,7 +160,12 @@ const main = async (): Promise => { // redis is optional if (env.redis.url) { + redisClient.on('error', (err) => { + console.error('Redis Client Error', err) + }) + await redisClient.connect() + console.log('Redis Client Connected:', env.redis.url) } const { app, apollo, httpServer } = createApp() diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index 48437b29e..cf179fd35 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -416,8 +416,15 @@ export const setRecentlySavedItemInRedis = async ( // save the url in redis for 8 hours so rss-feeder won't try to re-save it const redisKey = `recent-saved-item:${userId}:${url}` const ttlInSeconds = 60 * 60 * 8 - return redisClient.set(redisKey, 1, { - EX: ttlInSeconds, - NX: true, - }) + try { + return redisClient.set(redisKey, 1, { + EX: ttlInSeconds, + NX: true, + }) + } catch (error) { + logger.error('error setting recently saved item in redis', { + redisKey, + error, + }) + } } diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index df8e53761..1e36a7d02 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -63,11 +63,15 @@ const feedFetchFailedRedisKey = (feedUrl: string) => const isFeedBlocked = async (feedUrl: string, redisClient: RedisClient) => { const key = feedFetchFailedRedisKey(feedUrl) - const result = await redisClient.get(key) - // if the feed has failed to fetch more than certain times, block it - const maxFailures = parseInt(process.env.MAX_FEED_FETCH_FAILURES ?? '10') - if (result && parseInt(result) > maxFailures) { - return true + try { + const result = await redisClient.get(key) + // if the feed has failed to fetch more than certain times, block it + const maxFailures = parseInt(process.env.MAX_FEED_FETCH_FAILURES ?? '10') + if (result && parseInt(result) > maxFailures) { + return true + } + } catch (error) { + console.error('Failed to check feed block status', feedUrl, error) } return false @@ -75,11 +79,16 @@ const isFeedBlocked = async (feedUrl: string, redisClient: RedisClient) => { const blockFeed = async (feedUrl: string, redisClient: RedisClient) => { const key = feedFetchFailedRedisKey(feedUrl) - const result = await redisClient.incr(key) - // expire the key in 1 day - await redisClient.expire(key, 24 * 60 * 60, 'NX') + try { + const result = await redisClient.incr(key) + // expire the key in 1 day + await redisClient.expire(key, 24 * 60 * 60, 'NX') - return result + return result + } catch (error) { + console.error('Failed to block feed', feedUrl, error) + return null + } } export const isContentFetchBlocked = (feedUrl: string) => { From 44bed67e3554d9e700b93c0e405e6feb8ae51367 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 5 Jan 2024 15:41:01 +0800 Subject: [PATCH 041/278] add comment --- packages/rss-handler/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rss-handler/src/index.ts b/packages/rss-handler/src/index.ts index 1e36a7d02..b3636e41c 100644 --- a/packages/rss-handler/src/index.ts +++ b/packages/rss-handler/src/index.ts @@ -68,6 +68,7 @@ const isFeedBlocked = async (feedUrl: string, redisClient: RedisClient) => { // if the feed has failed to fetch more than certain times, block it const maxFailures = parseInt(process.env.MAX_FEED_FETCH_FAILURES ?? '10') if (result && parseInt(result) > maxFailures) { + console.log('feed is blocked: ', feedUrl) return true } } catch (error) { From 715ef167d3fb8b6461610186eab9da618fa0984a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 Jan 2024 18:11:24 +0800 Subject: [PATCH 042/278] Bump android build --- android/Omnivore/app/build.gradle | 4 ++-- .../app/src/main/res/values/strings.xml | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 28088e19e..eaa1ed48c 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 158 - versionName "0.0.158" + versionCode 160 + versionName "0.0.160" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml index 878d99cce..a9eb61336 100644 --- a/android/Omnivore/app/src/main/res/values/strings.xml +++ b/android/Omnivore/app/src/main/res/values/strings.xml @@ -139,6 +139,7 @@ You have not added any highlights to this page. + Font: Font Size: Margin Line Spacing @@ -208,6 +209,23 @@ Manage Account Logout + + Inbox + Non-Feed Items + Feeds + Newsletters + Recommended + All + Archived + Highlighted + Files + + + Newest + Oldest + Recently read + Recently published + Add Link Add Link From 6ac6473cee8d50d76efa1f88fc577bc3d4d5196e Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 Jan 2024 18:12:59 +0800 Subject: [PATCH 043/278] Bump Android version --- android/Omnivore/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index eaa1ed48c..f975ca5b0 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 160 - versionName "0.0.160" + versionCode 170 + versionName "0.0.170" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 1ec5b7affe1a1d05abe6ec526920251f08375741 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 5 Jan 2024 23:22:09 +0800 Subject: [PATCH 044/278] fix labels not saved when merging highlights --- packages/api/src/resolvers/highlight/index.ts | 2 +- packages/api/src/services/highlights.ts | 14 +++++++ packages/api/test/resolvers/highlight.test.ts | 41 +++++++++++++++++-- packages/api/test/util.ts | 7 +++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/api/src/resolvers/highlight/index.ts b/packages/api/src/resolvers/highlight/index.ts index 4f54f7aa4..82618990c 100644 --- a/packages/api/src/resolvers/highlight/index.ts +++ b/packages/api/src/resolvers/highlight/index.ts @@ -123,7 +123,6 @@ export const mergeHighlightResolver = authorized< ...newHighlightInput, annotation: mergedAnnotations.length > 0 ? mergedAnnotations.join('\n') : null, - labels: mergedLabels, color, user: { id: uid }, libraryItem: { id: input.articleId }, @@ -134,6 +133,7 @@ export const mergeHighlightResolver = authorized< const newHighlight = await mergeHighlights( overlapHighlightIdList, highlight, + mergedLabels, input.articleId, uid, pubsub diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index d5ab01732..2d062418f 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -1,7 +1,9 @@ import { diff_match_patch } from 'diff-match-patch' import { DeepPartial } from 'typeorm' import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity' +import { EntityLabel } from '../entity/entity_label' import { Highlight } from '../entity/highlight' +import { Label } from '../entity/label' import { homePageURL } from '../env' import { createPubSubClient, EntityType } from '../pubsub' import { authTrx } from '../repository' @@ -65,6 +67,7 @@ export const createHighlight = async ( export const mergeHighlights = async ( highlightsToRemove: string[], highlightToAdd: DeepPartial, + labels: Label[], libraryItemId: string, userId: string, pubsub = createPubSubClient() @@ -75,6 +78,17 @@ export const mergeHighlights = async ( await highlightRepo.delete(highlightsToRemove) const newHighlight = await highlightRepo.createAndSave(highlightToAdd) + + if (labels.length > 0) { + // save new labels + await tx.getRepository(EntityLabel).save( + labels.map((l) => ({ + labelId: l.id, + highlightId: newHighlight.id, + })) + ) + } + return highlightRepo.findOneOrFail({ where: { id: newHighlight.id }, relations: { diff --git a/packages/api/test/resolvers/highlight.test.ts b/packages/api/test/resolvers/highlight.test.ts index 6383f14d2..e0fe8a668 100644 --- a/packages/api/test/resolvers/highlight.test.ts +++ b/packages/api/test/resolvers/highlight.test.ts @@ -6,10 +6,12 @@ import { User } from '../../src/entity/user' import { createHighlight, deleteHighlightById, + findHighlightById, } from '../../src/services/highlights' +import { createLabel, saveLabelsInHighlight } from '../../src/services/labels' import { deleteUser } from '../../src/services/user' import { createTestLibraryItem, createTestUser } from '../db' -import { generateFakeUuid, graphqlRequest, request } from '../util' +import { generateFakeShortId, generateFakeUuid, graphqlRequest, request } from '../util' chai.use(chaiString) @@ -227,14 +229,18 @@ describe('Highlights API', () => { context('mergeHighlightMutation', () => { let highlightId: string - before(async () => { + beforeEach(async () => { // create test highlight highlightId = generateFakeUuid() - const shortHighlightId = '_short_id_1' + const shortHighlightId = generateFakeShortId() const query = createHighlightQuery(itemId, highlightId, shortHighlightId) await graphqlRequest(query, authToken).expect(200) }) + afterEach(async () => { + await deleteHighlightById(highlightId) + }) + it('should not fail', async () => { const newHighlightId = generateFakeUuid() const newShortHighlightId = '_short_id_2' @@ -257,6 +263,35 @@ describe('Highlights API', () => { expect( res.body.data.mergeHighlight.highlight.highlightPositionAnchorIndex ).to.eq(highlightPositionAnchorIndex) + + highlightId = newHighlightId + }) + + it('keeps the labels of the merged highlight', async () => { + // create label + const labelName = 'test label' + const labelColor = '#ff0000' + const label = await createLabel(labelName, labelColor, user.id) + + await saveLabelsInHighlight([label], highlightId, user.id) + + const newHighlightId = generateFakeUuid() + const newShortHighlightId = generateFakeShortId() + const query = mergeHighlightQuery( + itemId, + newHighlightId, + newShortHighlightId, + [highlightId], + ) + const res = await graphqlRequest(query, authToken).expect(200) + + expect(res.body.data.mergeHighlight.highlight.id).to.eq(newHighlightId) + + const highlight = await findHighlightById(newHighlightId, user.id) + expect(highlight.labels).to.have.lengthOf(1) + expect(highlight.labels?.[0]?.name).to.eq(labelName) + + highlightId = newHighlightId }) }) diff --git a/packages/api/test/util.ts b/packages/api/test/util.ts index 8268c1503..5d2567f83 100644 --- a/packages/api/test/util.ts +++ b/packages/api/test/util.ts @@ -1,3 +1,4 @@ +import { nanoid } from 'nanoid' import supertest from 'supertest' import { v4 } from 'uuid' import { createApp } from '../src/server' @@ -18,7 +19,7 @@ export const stopApolloServer = async () => { export const graphqlRequest = ( query: string, authToken: string, - variables?: Record, + variables?: Record ): supertest.Test => { return request .post(apollo.graphqlPath) @@ -31,3 +32,7 @@ export const graphqlRequest = ( export const generateFakeUuid = () => { return v4() } + +export const generateFakeShortId = () => { + return nanoid(8) +} From 5cac028c91744691ed8862ae7342bd29d1bf39f5 Mon Sep 17 00:00:00 2001 From: Gannon Date: Sat, 6 Jan 2024 11:00:33 -0500 Subject: [PATCH 045/278] Uncomment theme colors and use them in bottomsheet Uncomment all the colors. Also use theme instead of hardcoding colors for the bottomsheet content --- .../ui/components/LabelsSelectionSheet.kt | 555 ++++++++++-------- .../app/omnivore/omnivore/ui/theme/Color.kt | 116 ++-- .../app/omnivore/omnivore/ui/theme/Theme.kt | 84 ++- 3 files changed, 434 insertions(+), 321 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt index 347b47a31..3b4605df3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt @@ -3,15 +3,39 @@ package app.omnivore.omnivore.ui.components import android.widget.Toast -import androidx.compose.foundation.* +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddCircle -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -20,7 +44,10 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.* +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue @@ -31,9 +58,12 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import app.omnivore.omnivore.R import app.omnivore.omnivore.persistence.entities.SavedItemLabel -import com.dokar.chiptextfield.* +import com.dokar.chiptextfield.Chip +import com.dokar.chiptextfield.ChipTextField +import com.dokar.chiptextfield.ChipTextFieldDefaults +import com.dokar.chiptextfield.ChipTextFieldState +import com.dokar.chiptextfield.rememberChipTextFieldState import com.google.accompanist.flowlayout.FlowRow -import java.util.* //@Composable @@ -99,216 +129,229 @@ import java.util.* //} @Composable -fun CircleIcon(colorHex: String){ - val chipColors = LabelChipColors.fromHex(colorHex) - val viewConfiguration = LocalViewConfiguration.current - val viewConfigurationOverride = remember(viewConfiguration) { - ViewConfigurationOverride( - base = viewConfiguration, - minimumTouchTargetSize = DpSize(24.dp, 24.dp) - ) - } - - CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) { - Row( - modifier = Modifier - .padding(start = 10.dp, end = 2.dp) - .padding(vertical = 7.dp) - ) { - Canvas(modifier = Modifier.size(12.dp), onDraw = { - drawCircle(color = chipColors.containerColor) - }) +fun CircleIcon(colorHex: String) { + val chipColors = LabelChipColors.fromHex(colorHex) + val viewConfiguration = LocalViewConfiguration.current + val viewConfigurationOverride = remember(viewConfiguration) { + ViewConfigurationOverride( + base = viewConfiguration, + minimumTouchTargetSize = DpSize(24.dp, 24.dp) + ) + } + + CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) { + Row( + modifier = Modifier + .padding(start = 10.dp, end = 2.dp) + .padding(vertical = 7.dp) + ) { + Canvas(modifier = Modifier.size(12.dp), onDraw = { + drawCircle(color = chipColors.containerColor) + }) + } } - } } @Composable fun CloseButton( - state: ChipTextFieldState, - chip: T, - modifier: Modifier = Modifier, - backgroundColor: Color = Color.Transparent, - strokeColor: Color = Color.White, - startPadding: Dp = 0.dp, - endPadding: Dp = 4.dp + state: ChipTextFieldState, + chip: T, + modifier: Modifier = Modifier, + backgroundColor: Color = Color.Transparent, + strokeColor: Color = Color.White, + startPadding: Dp = 0.dp, + endPadding: Dp = 4.dp ) { - Row( - modifier = modifier - .padding(start = startPadding, end = endPadding) - ) { - CloseButtonImpl( - onClick = { state.removeChip(chip) }, - backgroundColor = backgroundColor, - strokeColor = strokeColor - ) - } + Row( + modifier = modifier + .padding(start = startPadding, end = endPadding) + ) { + CloseButtonImpl( + onClick = { state.removeChip(chip) }, + backgroundColor = backgroundColor, + strokeColor = strokeColor + ) + } } internal class ViewConfigurationOverride( - base: ViewConfiguration, - override val doubleTapMinTimeMillis: Long = base.doubleTapMinTimeMillis, - override val doubleTapTimeoutMillis: Long = base.doubleTapTimeoutMillis, - override val longPressTimeoutMillis: Long = base.longPressTimeoutMillis, - override val touchSlop: Float = base.touchSlop, - override val minimumTouchTargetSize: DpSize = base.minimumTouchTargetSize + base: ViewConfiguration, + override val doubleTapMinTimeMillis: Long = base.doubleTapMinTimeMillis, + override val doubleTapTimeoutMillis: Long = base.doubleTapTimeoutMillis, + override val longPressTimeoutMillis: Long = base.longPressTimeoutMillis, + override val touchSlop: Float = base.touchSlop, + override val minimumTouchTargetSize: DpSize = base.minimumTouchTargetSize ) : ViewConfiguration @Composable private fun CloseButtonImpl( - onClick: () -> Unit, - backgroundColor: Color, - strokeColor: Color, - modifier: Modifier = Modifier, + onClick: () -> Unit, + backgroundColor: Color, + strokeColor: Color, + modifier: Modifier = Modifier, ) { - val padding = with(LocalDensity.current) { 6.dp.toPx() } - val strokeWidth = with(LocalDensity.current) { 1.2.dp.toPx() } - val viewConfiguration = LocalViewConfiguration.current - val viewConfigurationOverride = remember(viewConfiguration) { - ViewConfigurationOverride( - base = viewConfiguration, - minimumTouchTargetSize = DpSize(24.dp, 24.dp) - ) - } - CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) { - Canvas( - modifier = modifier - .size(18.dp) - .clip(CircleShape) - .background(backgroundColor) - .clickable(onClick = onClick) - ) { - drawLine( - color = strokeColor, - start = Offset(padding, padding), - end = Offset(size.width - padding, size.height - padding), - strokeWidth = strokeWidth - ) - drawLine( - color = strokeColor, - start = Offset(padding, size.height - padding), - end = Offset(size.width - padding, padding), - strokeWidth = strokeWidth - ) + val padding = with(LocalDensity.current) { 6.dp.toPx() } + val strokeWidth = with(LocalDensity.current) { 1.2.dp.toPx() } + val viewConfiguration = LocalViewConfiguration.current + val viewConfigurationOverride = remember(viewConfiguration) { + ViewConfigurationOverride( + base = viewConfiguration, + minimumTouchTargetSize = DpSize(24.dp, 24.dp) + ) + } + CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) { + Canvas( + modifier = modifier + .size(18.dp) + .clip(CircleShape) + .background(backgroundColor) + .clickable(onClick = onClick) + ) { + drawLine( + color = strokeColor, + start = Offset(padding, padding), + end = Offset(size.width - padding, size.height - padding), + strokeWidth = strokeWidth + ) + drawLine( + color = strokeColor, + start = Offset(padding, size.height - padding), + end = Offset(size.width - padding, padding), + strokeWidth = strokeWidth + ) + } } - } } class LabelChipView(label: SavedItemLabel) : Chip(label.name) { - val label = label + val label = label } -fun findOrCreateLabel(labelsViewModel: LabelsViewModel, labels: List, name: String): SavedItemLabel { - val found = labels.find { it.name == name } - if (found != null) { - return found - } - return labelsViewModel.createNewSavedItemLabelWithTemp(name, LabelSwatchHelper.random()) +fun findOrCreateLabel( + labelsViewModel: LabelsViewModel, + labels: List, + name: String +): SavedItemLabel { + val found = labels.find { it.name == name } + if (found != null) { + return found + } + return labelsViewModel.createNewSavedItemLabelWithTemp(name, LabelSwatchHelper.random()) } @Composable -@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, - ExperimentalMaterial3Api::class +@OptIn( + ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, + ExperimentalMaterial3Api::class ) fun LabelsSelectionSheetContent( - isLibraryMode: Boolean, - labels: List, - initialSelectedLabels: List, - labelsViewModel: LabelsViewModel, - onCancel: () -> Unit, - onSave: (List) -> Unit, - onCreateLabel: (String, String) -> Unit + isLibraryMode: Boolean, + labels: List, + initialSelectedLabels: List, + labelsViewModel: LabelsViewModel, + onCancel: () -> Unit, + onSave: (List) -> Unit, + onCreateLabel: (String, String) -> Unit ) { - val interactionSource = remember { MutableInteractionSource() } + val interactionSource = remember { MutableInteractionSource() } - val state = rememberChipTextFieldState(initialSelectedLabels.map { - LabelChipView(it) - }) + val state = rememberChipTextFieldState(initialSelectedLabels.map { + LabelChipView(it) + }) - val focusRequester = remember { FocusRequester() } - var filterTextValue by remember { mutableStateOf(TextFieldValue()) } - val onFilterTextValueChange: (TextFieldValue) -> Unit = { filterTextValue = it } + val focusRequester = remember { FocusRequester() } + var filterTextValue by remember { mutableStateOf(TextFieldValue()) } + val onFilterTextValueChange: (TextFieldValue) -> Unit = { filterTextValue = it } - val filteredLabels = labels.filter { label -> - val text = filterTextValue.text.toLowerCase(Locale.current) - val result = (text.isEmpty() || label.name.toLowerCase(Locale.current).startsWith(text)) - val alreadySelected = state.chips.map { it.label.name }.contains(label.name) - result && !alreadySelected - } + val filteredLabels = labels.filter { label -> + val text = filterTextValue.text.toLowerCase(Locale.current) + val result = (text.isEmpty() || label.name.toLowerCase(Locale.current).startsWith(text)) + val alreadySelected = state.chips.map { it.label.name }.contains(label.name) + result && !alreadySelected + } - val currentLabel = labels.find { - val text = filterTextValue.text.toLowerCase(Locale.current) - it.name.toLowerCase(Locale.current) == text - } + val currentLabel = labels.find { + val text = filterTextValue.text.toLowerCase(Locale.current) + it.name.toLowerCase(Locale.current) == text + } - val titleText = if (isLibraryMode) - stringResource(R.string.label_selection_sheet_title) else - stringResource(R.string.label_selection_sheet_title_alt) + val titleText = if (isLibraryMode) + stringResource(R.string.label_selection_sheet_title) else + stringResource(R.string.label_selection_sheet_title_alt) - Surface( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background), - ) { - Column( - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 5.dp) + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), ) { + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 5.dp) + ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - ) { - TextButton(onClick = onCancel) { - Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) - } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + ) { + TextButton(onClick = onCancel) { + Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) + } - Text(titleText, fontWeight = FontWeight.ExtraBold) + Text(titleText, fontWeight = FontWeight.ExtraBold) - TextButton(onClick = { onSave(state.chips.map { it.label }) }) { - Text(text = if (isLibraryMode) - stringResource(R.string.label_selection_sheet_action_search) else - stringResource(R.string.label_selection_sheet_action_save)) - } - } - - ChipTextField( - state = state, - value = filterTextValue, - onValueChange = onFilterTextValueChange, - onSubmit = { - if (isLibraryMode) { - currentLabel?.let { - LabelChipView(it) + TextButton(onClick = { onSave(state.chips.map { it.label }) }) { + Text( + text = if (isLibraryMode) + stringResource(R.string.label_selection_sheet_action_search) else + stringResource(R.string.label_selection_sheet_action_save) + ) + } } - } else { - LabelChipView(findOrCreateLabel(labelsViewModel = labelsViewModel, labels = labels, name = it.text)) - } - }, - chipLeadingIcon = { chip -> CircleIcon(colorHex = chip.label.color) }, - chipTrailingIcon = { chip -> CloseButton(state, chip) }, - interactionSource = interactionSource, - chipStyle = ChipTextFieldDefaults.chipStyle( - shape = androidx.compose.material.MaterialTheme.shapes.medium, - unfocusedBorderWidth = 0.dp, - focusedTextColor = Color(0xFFAEAEAF), - focusedBorderColor = Color(0xFF2A2A2A), - focusedBackgroundColor = Color(0xFF2A2A2A) - ), - colors = androidx.compose.material.TextFieldDefaults.textFieldColors( - textColor = Color(0xFFAEAEAF), - backgroundColor = Color(0xFF3D3D3D) - ), - contentPadding = PaddingValues(10.dp), - modifier = Modifier - .defaultMinSize(minHeight = 45.dp) - .fillMaxWidth() - .padding(horizontal = 10.dp) - .focusRequester(focusRequester) + + ChipTextField( + state = state, + value = filterTextValue, + onValueChange = onFilterTextValueChange, + onSubmit = { + if (isLibraryMode) { + currentLabel?.let { + LabelChipView(it) + } + } else { + LabelChipView( + findOrCreateLabel( + labelsViewModel = labelsViewModel, + labels = labels, + name = it.text + ) + ) + } + }, + chipLeadingIcon = { chip -> CircleIcon(colorHex = chip.label.color) }, + chipTrailingIcon = { chip -> CloseButton(state, chip) }, + interactionSource = interactionSource, + chipStyle = ChipTextFieldDefaults.chipStyle( + shape = androidx.compose.material.MaterialTheme.shapes.medium, + unfocusedBorderWidth = 0.dp, + focusedTextColor = Color(0xFFAEAEAF), + focusedBorderColor = Color(0xFF2A2A2A), + focusedBackgroundColor = Color(0xFF2A2A2A) + ), + colors = TextFieldDefaults.textFieldColors( + textColor = MaterialTheme.colorScheme.onBackground, + backgroundColor = MaterialTheme.colorScheme.background + ), + contentPadding = PaddingValues(10.dp), + modifier = Modifier + .defaultMinSize(minHeight = 45.dp) + .fillMaxWidth() + .padding(horizontal = 10.dp) + .focusRequester(focusRequester) // .onFocusEvent { // val text = filterTextValue.text // if (it.hasFocus) { @@ -316,78 +359,88 @@ fun LabelsSelectionSheetContent( // onFilterTextValueChange(filterTextValue.copy(selection = TextRange(selection))) // } // } - ) - - if (!isLibraryMode && filterTextValue.text.isNotEmpty() && currentLabel == null) { - val context = LocalContext.current - Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable { - val labelName = filterTextValue.text.trim() - when(labelsViewModel.validateLabelName(labelName)) { - LabelsViewModel.Error.LabelNameTooLong -> { - Toast.makeText( - context, - context.getString(R.string.label_selection_sheet_label_too_long_error_msg, - labelsViewModel.labelNameMaxLength), - Toast.LENGTH_SHORT - ).show() - } - null -> { - val label = findOrCreateLabel( - labelsViewModel = labelsViewModel, - labels = labels, - name = labelName - ) - - state.addChip(LabelChipView(label)) - filterTextValue = TextFieldValue() - } - } - } - .padding(horizontal = 10.dp) - .padding(top = 10.dp, bottom = 5.dp) - ) - { - Icon( - imageVector = Icons.Filled.AddCircle, - contentDescription = null, - modifier = Modifier.padding(end = 8.dp) - ) - Text(text = stringResource(R.string.label_selection_sheet_text_create, filterTextValue.text.trim())) - } - } - - if (filteredLabels.isNotEmpty()) { - FlowRow( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(10.dp) - .padding(bottom = 55.dp) - ) { - filteredLabels.forEach { label -> - val chipColors = LabelChipColors.fromHex(label.color) - - LabelChip( - name = label.name, - colors = chipColors, - modifier = Modifier - .padding(end = 10.dp, bottom = 10.dp) - .clickable { - state.addChip(LabelChipView(label)) - filterTextValue = TextFieldValue() - } ) - } + + if (!isLibraryMode && filterTextValue.text.isNotEmpty() && currentLabel == null) { + val context = LocalContext.current + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + val labelName = filterTextValue.text.trim() + when (labelsViewModel.validateLabelName(labelName)) { + LabelsViewModel.Error.LabelNameTooLong -> { + Toast + .makeText( + context, + context.getString( + R.string.label_selection_sheet_label_too_long_error_msg, + labelsViewModel.labelNameMaxLength + ), + Toast.LENGTH_SHORT + ) + .show() + } + + null -> { + val label = findOrCreateLabel( + labelsViewModel = labelsViewModel, + labels = labels, + name = labelName + ) + + state.addChip(LabelChipView(label)) + filterTextValue = TextFieldValue() + } + } + } + .padding(horizontal = 10.dp) + .padding(top = 10.dp, bottom = 5.dp) + ) + { + Icon( + imageVector = Icons.Filled.AddCircle, + contentDescription = null, + modifier = Modifier.padding(end = 8.dp) + ) + Text( + text = stringResource( + R.string.label_selection_sheet_text_create, + filterTextValue.text.trim() + ) + ) + } + } + + if (filteredLabels.isNotEmpty()) { + FlowRow( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(10.dp) + .padding(bottom = 55.dp) + ) { + filteredLabels.forEach { label -> + val chipColors = LabelChipColors.fromHex(label.color) + + LabelChip( + name = label.name, + colors = chipColors, + modifier = Modifier + .padding(end = 10.dp, bottom = 10.dp) + .clickable { + state.addChip(LabelChipView(label)) + filterTextValue = TextFieldValue() + } + ) + } + } + } } - } } - } - LaunchedEffect(Unit) { - state.focusTextField() - } + LaunchedEffect(Unit) { + state.focusTextField() + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt index 83cb2675c..f7243c32b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt @@ -2,63 +2,67 @@ package app.omnivore.omnivore.ui.theme import androidx.compose.ui.graphics.Color -// val md_theme_light_primary = Color(0xFF745B00) val md_theme_light_onPrimary = Color(0xFFFFFFFF) -//val md_theme_light_primaryContainer = Color(0xFFFFE08C) -//val md_theme_light_onPrimaryContainer = Color(0xFF241A00) -//val md_theme_light_secondary = Color(0xFF6D5E00) -//val md_theme_light_onSecondary = Color(0xFFFFFFFF) -//val md_theme_light_secondaryContainer = Color(0xFFFCE365) -//val md_theme_light_onSecondaryContainer = Color(0xFF211B00) -//val md_theme_light_tertiary = Color(0xFF4B670A) -//val md_theme_light_onTertiary = Color(0xFFFFFFFF) -//val md_theme_light_tertiaryContainer = Color(0xFFCBEF86) -//val md_theme_light_onTertiaryContainer = Color(0xFF141F00) -//val md_theme_light_error = Color(0xFFBA1A1A) -//val md_theme_light_errorContainer = Color(0xFFFFDAD6) -//val md_theme_light_onError = Color(0xFFFFFFFF) -//val md_theme_light_onErrorContainer = Color(0xFF410002) -//val md_theme_light_background = Color(0xFFF7FFED) -//val md_theme_light_onBackground = Color(0xFF032100) -//val md_theme_light_surface = Color(0xFFF7FFED) -//val md_theme_light_onSurface = Color(0xFF032100) -//val md_theme_light_surfaceVariant = Color(0xFFEBE1CF) -//val md_theme_light_onSurfaceVariant = Color(0xFF4C4639) -//val md_theme_light_outline = Color(0xFF7E7667) -//val md_theme_light_inverseOnSurface = Color(0xFFCBFFB5) -//val md_theme_light_inverseSurface = Color(0xFF083900) -//val md_theme_light_inversePrimary = Color(0xFFEFC125) -//val md_theme_light_shadow = Color(0xFF000000) -//val md_theme_light_surfaceTint = Color(0xFF745B00) -// +val md_theme_light_primaryContainer = Color(0xFFFFDDB3) +val md_theme_light_onPrimaryContainer = Color(0xFF291800) +val md_theme_light_secondary = Color(0xFF6F5B40) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFFBDEBC) +val md_theme_light_onSecondaryContainer = Color(0xFF271904) +val md_theme_light_tertiary = Color(0xFF51643F) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFFD4EABB) +val md_theme_light_onTertiaryContainer = Color(0xFF102004) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFFFFBFF) +val md_theme_light_onBackground = Color(0xFF1F1B16) +val md_theme_light_surface = Color(0xFFFFFBFF) +val md_theme_light_onSurface = Color(0xFF1F1B16) +val md_theme_light_surfaceVariant = Color(0xFFF0E0CF) +val md_theme_light_onSurfaceVariant = Color(0xFF4F4539) +val md_theme_light_outline = Color(0xFF817567) +val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7) +val md_theme_light_inverseSurface = Color(0xFF34302A) +val md_theme_light_inversePrimary = Color(0xFFFFB951) +val md_theme_light_shadow = Color(0xFF000000) +val md_theme_light_surfaceTint = Color(0xFF825500) +val md_theme_light_outlineVariant = Color(0xFFD3C4B4) +val md_theme_light_scrim = Color(0xFF000000) + val md_theme_dark_primary = Color(0xFFEFC125) val md_theme_dark_onPrimary = Color(0xFF3D2F00) -//val md_theme_dark_primaryContainer = Color(0xFF584400) -//val md_theme_dark_onPrimaryContainer = Color(0xFFFFE08C) -//val md_theme_dark_secondary = Color(0xFFDEC64C) -//val md_theme_dark_onSecondary = Color(0xFF393000) -//val md_theme_dark_secondaryContainer = Color(0xFF524600) -//val md_theme_dark_onSecondaryContainer = Color(0xFFFCE365) -//val md_theme_dark_tertiary = Color(0xFFB0D36D) -//val md_theme_dark_onTertiary = Color(0xFF243600) -//val md_theme_dark_tertiaryContainer = Color(0xFF364E00) -//val md_theme_dark_onTertiaryContainer = Color(0xFFCBEF86) -//val md_theme_dark_error = Color(0xFFFFB4AB) -//val md_theme_dark_errorContainer = Color(0xFF93000A) -//val md_theme_dark_onError = Color(0xFF690005) -//val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) -//val md_theme_dark_background = Color(0xFF032100) -//val md_theme_dark_onBackground = Color(0xFFB4F39B) -//val md_theme_dark_surface = Color(0xFF032100) -//val md_theme_dark_onSurface = Color(0xFFB4F39B) -//val md_theme_dark_surfaceVariant = Color(0xFF4C4639) -//val md_theme_dark_onSurfaceVariant = Color(0xFFCFC5B4) -//val md_theme_dark_outline = Color(0xFF989080) -//val md_theme_dark_inverseOnSurface = Color(0xFF032100) -//val md_theme_dark_inverseSurface = Color(0xFFB4F39B) -//val md_theme_dark_inversePrimary = Color(0xFF745B00) -//val md_theme_dark_shadow = Color(0xFF000000) -//val md_theme_dark_surfaceTint = Color(0xFFEFC125) -// -//val seed = Color(0xFFE2B513) +val md_theme_dark_primaryContainer = Color(0xFF633F00) +val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3) +val md_theme_dark_secondary = Color(0xFFDDC2A1) +val md_theme_dark_onSecondary = Color(0xFF3E2D16) +val md_theme_dark_secondaryContainer = Color(0xFF56442A) +val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC) +val md_theme_dark_tertiary = Color(0xFFB8CEA1) +val md_theme_dark_onTertiary = Color(0xFF243515) +val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A) +val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF1F1B16) +val md_theme_dark_onBackground = Color(0xFFEAE1D9) +val md_theme_dark_surface = Color(0xFF1F1B16) +val md_theme_dark_onSurface = Color(0xFFEAE1D9) +val md_theme_dark_surfaceVariant = Color(0xFF4F4539) +val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4) +val md_theme_dark_outline = Color(0xFF9C8F80) +val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16) +val md_theme_dark_inverseSurface = Color(0xFFEAE1D9) +val md_theme_dark_inversePrimary = Color(0xFF825500) +val md_theme_dark_shadow = Color(0xFF000000) +val md_theme_dark_surfaceTint = Color(0xFFFFB951) +val md_theme_dark_outlineVariant = Color(0xFF4F4539) +val md_theme_dark_scrim = Color(0xFF000000) + + +val seed = Color(0xFF825500) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt index 471966a12..9384a2ce8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Theme.kt @@ -2,32 +2,88 @@ package app.omnivore.omnivore.ui.theme import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable private val LightColors = lightColorScheme( - primary = md_theme_light_primary, - onPrimary = md_theme_light_onPrimary, + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + outlineVariant = md_theme_light_outlineVariant, + scrim = md_theme_light_scrim, ) private val DarkColors = darkColorScheme( - primary = md_theme_dark_primary, - onPrimary = md_theme_dark_onPrimary, + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + outlineVariant = md_theme_dark_outlineVariant, + scrim = md_theme_dark_scrim, ) @Composable fun OmnivoreTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - useDynamicTheme: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S, - content: @Composable () -> Unit + darkTheme: Boolean = isSystemInDarkTheme(), + useDynamicTheme: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S, + content: @Composable () -> Unit ) { - val colorScheme = if (darkTheme) DarkColors else LightColors + val colorScheme = if (darkTheme) DarkColors else LightColors - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, // shapes = Shapes, - content = content - ) + content = content + ) } From 36c75af23f325892fce5e382e6f5c66944543108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20J=C3=A4ger?= Date: Sun, 7 Jan 2024 14:42:26 +0100 Subject: [PATCH 046/278] minor improvements --- docker-compose.yml | 4 ++-- packages/db/setup.sh | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a66ee9f61..d1bdacbe3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,9 +24,9 @@ services: command: '/bin/sh ./packages/db/setup.sh' # Also create a demo user with email: demo@omnivore.app, password: demo_password environment: - PGPASSWORD=postgres + - POSTGRES_USER=postgres - PG_HOST=postgres - - PG_USER=postgres - - PG_PASSWORD=postgres + - PG_PASSWORD=app_pass - PG_DB=omnivore depends_on: postgres: diff --git a/packages/db/setup.sh b/packages/db/setup.sh index 0a09d5428..217bc86ad 100755 --- a/packages/db/setup.sh +++ b/packages/db/setup.sh @@ -1,13 +1,20 @@ #!/bin/bash -psql --host $PG_HOST -U $PG_USER -d $PG_DB -c "CREATE USER app_user WITH PASSWORD 'app_pass';" || true +psql --host $PG_HOST --username $POSTGRES_USER --command "CREATE DATABASE $PG_DB;" || true +echo "create $PG_DB database" + +psql --host $PG_HOST --username $POSTGRES_USER --command "CREATE USER app_user WITH ENCRYPTED PASSWORD '$PG_PASSWORD';" || true echo "created app_user" -yarn workspace @omnivore/db migrate -psql --host $PG_HOST -U $PG_USER -d $PG_DB -c "GRANT omnivore_user TO app_user;" || true + +PG_USER=$POSTGRES_USER PG_PASSWORD=$PGPASSWORD yarn workspace @omnivore/db migrate + +psql --host $PG_HOST --username $POSTGRES_USER --dbname $PG_DB --command "GRANT omnivore_user TO app_user;" || true echo "granted omnivore_user to app_user" # create demo user with email: demo@omnivore.app, password: demo_password -USER_ID=$(uuidgen) -PASSWORD='$2a$10$41G6b1BDUdxNjH1QFPJYDOM29EE0C9nTdjD1FoseuQ8vZU1NWtrh6' -psql --host $PG_HOST -U $PG_USER -d $PG_DB -c "INSERT INTO omnivore.user (id, source, email, source_user_id, name, password) VALUES ('$USER_ID', 'EMAIL', 'demo@omnivore.app', 'demo@omnivore.app', 'Demo User', '$PASSWORD'); INSERT INTO omnivore.user_profile (user_id, username) VALUES ('$USER_ID', 'demo_user');" -echo "created demo user with email: demo@omnivore.app, password: demo_password" +if [ -z "${NO_DEMO_USER}" ]; then + USER_ID=$(uuidgen) + PASSWORD='$2a$10$41G6b1BDUdxNjH1QFPJYDOM29EE0C9nTdjD1FoseuQ8vZU1NWtrh6' + psql --host $PG_HOST --username $POSTGRES_USER --dbname $PG_DB --command "INSERT INTO omnivore.user (id, source, email, source_user_id, name, password) VALUES ('$USER_ID', 'EMAIL', 'demo@omnivore.app', 'demo@omnivore.app', 'Demo User', '$PASSWORD'); INSERT INTO omnivore.user_profile (user_id, username) VALUES ('$USER_ID', 'demo_user');" + echo "created demo user with email: demo@omnivore.app, password: demo_password" +fi \ No newline at end of file From 965012728f9c759427ac4298208baf6afd60555b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20J=C3=A4ger?= Date: Sun, 7 Jan 2024 14:43:38 +0100 Subject: [PATCH 047/278] remove selfhost.sh and fix Dockerfile --- self-hosting/Dockerfile | 11 +++++------ self-hosting/selfhost.sh | 16 ---------------- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 self-hosting/selfhost.sh diff --git a/self-hosting/Dockerfile b/self-hosting/Dockerfile index 831b8ceab..ff18d192c 100644 --- a/self-hosting/Dockerfile +++ b/self-hosting/Dockerfile @@ -11,12 +11,12 @@ COPY tsconfig.json . COPY .prettierrc . COPY .eslintrc . -COPY /self-hosting/selfhost.sh /app/selfhost.sh COPY /packages/readabilityjs/package.json ./packages/readabilityjs/package.json COPY /packages/api/package.json ./packages/api/package.json COPY /packages/text-to-speech/package.json ./packages/text-to-speech/package.json COPY /packages/content-handler/package.json ./packages/content-handler/package.json COPY /packages/db/package.json ./packages/db/package.json +COPY /packages/liqe/package.json ./packages/liqe/package.json RUN yarn install --pure-lockfile @@ -25,9 +25,11 @@ COPY /packages/api ./packages/api COPY /packages/text-to-speech ./packages/text-to-speech COPY /packages/content-handler ./packages/content-handler COPY /packages/db ./packages/db +COPY /packages/liqe ./packages/liqe RUN yarn workspace @omnivore/text-to-speech-handler build RUN yarn workspace @omnivore/content-handler build +RUN yarn workspace @omnivore/liqe build RUN yarn workspace @omnivore/api build RUN rm -rf /app/packages/api/node_modules @@ -50,14 +52,11 @@ COPY --from=builder /app/node_modules /app/node_modules COPY --from=builder /app/package.json /app/package.json COPY --from=builder /app/packages/text-to-speech/ /app/packages/text-to-speech/ COPY --from=builder /app/packages/content-handler/ /app/packages/content-handler/ +COPY --from=builder /app/packages/liqe/ /app/packages/liqe/ COPY --from=builder /app/packages/db/ /app/packages/db/ -COPY --from=builder /app/selfhost.sh /app/selfhost.sh RUN apk add --no-cache postgresql-client -RUN chmod 0755 /app/selfhost.sh EXPOSE 8080 -# Using a custom script to customize imageproxy startup and to pass -# signatureKey through env variable -ENTRYPOINT ["/app/selfhost.sh"] +CMD ["yarn", "workspace", "@omnivore/api", "start"] diff --git a/self-hosting/selfhost.sh b/self-hosting/selfhost.sh deleted file mode 100644 index c64b2b768..000000000 --- a/self-hosting/selfhost.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -echo "creating omnivore database with $(which psql) and user: $POSTGRES_USER" -psql --host $PG_HOST -c "CREATE DATABASE omnivore;" - -echo "creating app_user" -psql --host $PG_HOST -d $PG_DB -c "CREATE USER app_user WITH ENCRYPTED PASSWORD '$PG_PASSWORD';" -echo "created app_user" - -echo "running migrations as $POSTGRES_USER" -PG_USER=$POSTGRES_USER PG_PASSWORD=$POSTGRES_PASSWORD yarn workspace @omnivore/db migrate - -psql --host $PG_HOST -d $PG_DB -c "GRANT omnivore_user TO app_user;" -echo "granted omnivore_user to app_user" - -yarn workspace @omnivore/api start From 4fdb47bd9afb126b2bf7581e933544cafd635713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20J=C3=A4ger?= Date: Sun, 7 Jan 2024 14:44:14 +0100 Subject: [PATCH 048/278] helper script to build and push docker images --- self-hosting/build-and-push-images.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 self-hosting/build-and-push-images.sh diff --git a/self-hosting/build-and-push-images.sh b/self-hosting/build-and-push-images.sh new file mode 100755 index 000000000..3113462ef --- /dev/null +++ b/self-hosting/build-and-push-images.sh @@ -0,0 +1,18 @@ +: ${APP_ENV?Please set APP_ENV} +: ${BASE_URL?Please set BASE_URL, i.e. your public webserver URL, e.g. 'https://omnivore.example.com'} +: ${SERVER_BASE_URL?Please set SERVER_BASE_URL, i.e. your public API URL, e.g. 'https://api.omnivore.example.com'} +: ${HIGHLIGHTS_BASE_URL?Please set HIGHLIGHTS_BASE_URL, i.e. your public webserver URL, e.g. 'https://omnivore.example.com'} + +: ${DOCKER_HUB_USER?Please set DOCKER_HUB_USER} +: ${DOCKER_TAG?Please set DOCKER_TAG} + + +docker build -t ${DOCKER_HUB_USER}/omnivore-web:${DOCKER_TAG} --build-arg="APP_ENV=${APP_ENV}" --build-arg="BASE_URL=${BASE_URL}" --build-arg="SERVER_BASE_URL=${SERVER_BASE_URL}" --build-arg="HIGHLIGHTS_BASE_URL=${HIGHLIGHTS_BASE_URL}" -f ../packages/web/Dockerfile .. +docker build -t ${DOCKER_HUB_USER}/omnivore-content-fetch:${DOCKER_TAG} -f ../packages/content-fetch/Dockerfile .. +docker build -t ${DOCKER_HUB_USER}/omnivore-api:${DOCKER_TAG} -f Dockerfile .. +docker build -t ${DOCKER_HUB_USER}/omnivore-migrate:${DOCKER_TAG} -f ../packages/db/Dockerfile .. + +docker push ${DOCKER_HUB_USER}/omnivore-web:${DOCKER_TAG} +docker push ${DOCKER_HUB_USER}/omnivore-content-fetch:${DOCKER_TAG} +docker push ${DOCKER_HUB_USER}/omnivore-api:${DOCKER_TAG} +docker push ${DOCKER_HUB_USER}/omnivore-migrate:${DOCKER_TAG} \ No newline at end of file From 9d3cdcdd1a1b63bc1774a3d193ec98d462b7b73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20J=C3=A4ger?= Date: Sun, 7 Jan 2024 14:46:14 +0100 Subject: [PATCH 049/278] add values for bjw-s helm framework chart --- self-hosting/helm/README.md | 37 ++++++++++++ self-hosting/helm/values.yaml | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 self-hosting/helm/README.md create mode 100644 self-hosting/helm/values.yaml diff --git a/self-hosting/helm/README.md b/self-hosting/helm/README.md new file mode 100644 index 000000000..64ccf0f3c --- /dev/null +++ b/self-hosting/helm/README.md @@ -0,0 +1,37 @@ +# Deployment using Helm + +### Prerequisites + +Make sure the Helm repository is installed as follows: + +```console +helm repo add bjw-s https://bjw-s.github.io/helm-charts +helm repo update +``` + +### Deployment + +In order to deploy the manifest for this example, issue the +following command: + +```console +helm install omnivore bjw-s/app-template --namespace omnivore --values values.yaml +``` + +This will apply the rendered manifest(s) to your cluster. + + +## Notes + +- using `sejaeger` docker images. You can build your own using: `build-and-push-images.sh` +- don't change the following because also hard coded: `PG_DB`, `PG_USER` +- requires postgres (+vector extension!) and elasticsearch +- make sure to change the values according to your setup, especially: postgres hostname, elasticsearch URL, omnivore URL +- information about possible setup see [https://github.com/bjw-s/helm-charts/blob/main/charts/library/common/values.yaml](https://github.com/bjw-s/helm-charts/blob/main/charts/library/common/values.yaml) + + +# TODOs + +- health checks +- RSS feeds +- Docs \ No newline at end of file diff --git a/self-hosting/helm/values.yaml b/self-hosting/helm/values.yaml new file mode 100644 index 000000000..5cc2d0ff5 --- /dev/null +++ b/self-hosting/helm/values.yaml @@ -0,0 +1,103 @@ +controllers: + main: + enabled: false + api: + enabled: true + type: deployment + strategy: Recreate + containers: + api: + image: + repository: sejaeger/omnivore-api + tag: 54ccbd4 + pullPolicy: IfNotPresent + env: + API_ENV: local + PG_HOST: postgres.postgres.svc.cluster.local + PG_USER: app_user + PG_DB: omnivore + PG_PORT: 5432 + PG_POOL_MAX: 20 + CLIENT_URL: https://omnivore.example.com + GATEWAY_URL: http://omnivore-api:8080/api + ES_URL: http://elasticsearch.elasticsearch.svc.cluster.local:9200 + ES_USERNAME: elastic + envFrom: + - secretRef: + name: omnivore-image-proxy-secret + - secretRef: + name: omnivore-jwt-secret + - secretRef: + name: omnivore-sso-jwt-secret + - secretRef: + name: omnivore-pg-password + - secretRef: + name: elasticsearch-auth-secret + - secretRef: + name: omnivore-content-fetch-token + initContainers: + migrate: + image: + repository: sejaeger/omnivore-migrate + tag: 54ccbd4 + pullPolicy: IfNotPresent + command: ["/bin/sh", "-c", "--"] + args: ["./packages/db/setup.sh"] + env: + PG_HOST: postgres.postgres.svc.cluster.local + PG_USER: app_user + PG_DB: omnivore + NO_DEMO_USER: 1 + envFrom: + - secretRef: + name: omnivore-pg-password + - secretRef: + name: postgres-admin-user-and-password + + web: + enabled: true + type: deployment + strategy: Recreate + containers: + web: + image: + repository: sejaeger/omnivore-web + tag: 54ccbd4 + pullPolicy: IfNotPresent + + content-fetch: + enabled: true + type: deployment + strategy: Recreate + containers: + content-fetch: + image: + repository: sejaeger/omnivore-content-fetch + tag: 54ccbd4 + pullPolicy: IfNotPresent + env: + REST_BACKEND_ENDPOINT: http://omnivore-api:8080/api + envFrom: + - secretRef: + name: omnivore-jwt-secret + - secretRef: + name: omnivore-content-fetch-token + +service: + main: + enabled: false + api: + controller: api + ports: + http: + port: 8080 + web: + controller: web + ports: + http: + port: 8080 + content-fetch: + controller: content-fetch + ports: + http: + port: 8080 From 897f55c0f79ea30f7a0531f41989ba43aaba550f Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 10:28:55 +0800 Subject: [PATCH 050/278] Request sync objects for all folders on iOS --- .../DataService/Queries/LinkedItemNetworkQuery.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift index 57a65aca2..117e1922a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LinkedItemNetworkQuery.swift @@ -71,13 +71,16 @@ extension DataService { ) } - let sort = InputObjects.SortParams(by: .updatedTime, - order: OptionalArgument(descending ? .descending : .ascending)) + let sort = InputObjects.SortParams( + by: .updatedTime, + order: OptionalArgument(descending ? .descending : .ascending) + ) let query = Selection.Query { try $0.updatesSince( after: OptionalArgument(cursor), first: OptionalArgument(limit), + folder: OptionalArgument("all"), since: DateTime(from: since), sort: OptionalArgument(sort), selection: selection From c0f613486df7e08918b36a5aa9569d10b1312053 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 10:43:57 +0800 Subject: [PATCH 051/278] Bump iOS release version --- apple/Omnivore.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index a928eedd5..1bd97059d 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1388,7 +1388,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.41.0; + MARKETING_VERSION = 1.42.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; @@ -1423,7 +1423,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.41.0; + MARKETING_VERSION = 1.42.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1478,7 +1478,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.41.0; + MARKETING_VERSION = 1.42.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1819,7 +1819,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.41.0; + MARKETING_VERSION = 1.42.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; From 213af8edc6732612fb5116f6a4d61404aa7f9a10 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 10:58:23 +0800 Subject: [PATCH 052/278] Request sync objects for all folders on Android --- .../app/src/main/graphql/UpdatesSince.graphql | 9 +- .../app/src/main/graphql/schema.graphqls | 186 +++++++++++++++++- .../networking/SavedItemUpdatesQuery.kt | 1 + 3 files changed, 190 insertions(+), 6 deletions(-) diff --git a/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql b/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql index aa93b28d5..8a2c29013 100644 --- a/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql +++ b/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql @@ -1,5 +1,10 @@ -query UpdatesSince($after: String, $first: Int, $since: Date!) { - updatesSince(after: $after, first: $first, since: $since) { +query UpdatesSince( + $folder: String + $after: String + $first: Int + $since: Date! +) { + updatesSince(after: $after, first: $first, folder: $folder, since: $since) { ... on UpdatesSinceSuccess { edges { cursor diff --git a/android/Omnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls index 335c93409..5c2431d8a 100644 --- a/android/Omnivore/app/src/main/graphql/schema.graphqls +++ b/android/Omnivore/app/src/main/graphql/schema.graphqls @@ -69,6 +69,7 @@ type Article { contentReader: ContentReader! createdAt: Date! description: String + folder: String! hasContent: Boolean hash: String! highlights(input: ArticleHighlightsInput): [Highlight!]! @@ -154,6 +155,7 @@ union ArticleSavingRequestResult = ArticleSavingRequestError | ArticleSavingRequ enum ArticleSavingRequestStatus { ARCHIVED + CONTENT_NOT_FETCHED DELETED FAILED PROCESSING @@ -203,6 +205,7 @@ enum BulkActionType { ARCHIVE DELETE MARK_AS_READ + MOVE_TO_FOLDER } enum ContentReader { @@ -227,8 +230,12 @@ enum CreateArticleErrorCode { input CreateArticleInput { articleSavingRequestId: ID + folder: String labels: [CreateLabelInput!] preparedDocument: PreparedDocumentInput + publishedAt: Date + rssFeedUrl: String + savedAt: Date skipParsing: Boolean source: String state: ArticleSavingRequestStatus @@ -377,6 +384,12 @@ enum CreateNewsletterEmailErrorCode { UNAUTHORIZED } +input CreateNewsletterEmailInput { + description: String + folder: String + name: String +} + union CreateNewsletterEmailResult = CreateNewsletterEmailError | CreateNewsletterEmailSuccess type CreateNewsletterEmailSuccess { @@ -631,6 +644,20 @@ type DeviceTokensSuccess { deviceTokens: [DeviceToken!]! } +type EmptyTrashError { + errorCodes: [EmptyTrashErrorCode!]! +} + +enum EmptyTrashErrorCode { + UNAUTHORIZED +} + +union EmptyTrashResult = EmptyTrashError | EmptyTrashSuccess + +type EmptyTrashSuccess { + success: Boolean +} + type Feature { createdAt: Date! expiresAt: Date @@ -641,6 +668,19 @@ type Feature { updatedAt: Date } +type Feed { + author: String + createdAt: Date + description: String + id: ID + image: String + publishedAt: Date + title: String! + type: String + updatedAt: Date + url: String! +} + type FeedArticle { annotationsCount: Int article: Article! @@ -674,12 +714,56 @@ type FeedArticlesSuccess { pageInfo: PageInfo! } +type FeedEdge { + cursor: String! + node: Feed! +} + +type FeedsError { + errorCodes: [FeedsErrorCode!]! +} + +enum FeedsErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input FeedsInput { + after: String + first: Int + query: String + sort: SortParams +} + +union FeedsResult = FeedsError | FeedsSuccess + +type FeedsSuccess { + edges: [FeedEdge!]! + pageInfo: PageInfo! +} + +type FetchContentError { + errorCodes: [FetchContentErrorCode!]! +} + +enum FetchContentErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union FetchContentResult = FetchContentError | FetchContentSuccess + +type FetchContentSuccess { + success: Boolean! +} + type Filter { - category: String! + category: String createdAt: Date! defaultFilter: Boolean description: String filter: String! + folder: String id: ID! name: String! position: Int! @@ -901,6 +985,8 @@ type IntegrationsSuccess { integrations: [Integration!]! } +scalar JSON + type JoinGroupError { errorCodes: [JoinGroupErrorCode!]! } @@ -925,6 +1011,7 @@ type Label { internal: Boolean name: String! position: Int + source: String } type LabelsError { @@ -1107,15 +1194,31 @@ type MoveLabelSuccess { label: Label! } +type MoveToFolderError { + errorCodes: [MoveToFolderErrorCode!]! +} + +enum MoveToFolderErrorCode { + ALREADY_EXISTS + BAD_REQUEST + UNAUTHORIZED +} + +union MoveToFolderResult = MoveToFolderError | MoveToFolderSuccess + +type MoveToFolderSuccess { + success: Boolean! +} + type Mutation { addPopularRead(name: String!): AddPopularReadResult! - bulkAction(action: BulkActionType!, async: Boolean, expectedCount: Int, labelIds: [ID!], query: String!): BulkActionResult! + bulkAction(action: BulkActionType!, arguments: JSON, async: Boolean, expectedCount: Int, labelIds: [ID!], query: String!): BulkActionResult! createArticle(input: CreateArticleInput!): CreateArticleResult! createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult! createGroup(input: CreateGroupInput!): CreateGroupResult! createHighlight(input: CreateHighlightInput!): CreateHighlightResult! createLabel(input: CreateLabelInput!): CreateLabelResult! - createNewsletterEmail: CreateNewsletterEmailResult! + createNewsletterEmail(input: CreateNewsletterEmailInput): CreateNewsletterEmailResult! deleteAccount(userID: ID!): DeleteAccountResult! deleteFilter(id: ID!): DeleteFilterResult! deleteHighlight(highlightId: ID!): DeleteHighlightResult! @@ -1124,6 +1227,8 @@ type Mutation { deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult! deleteRule(id: ID!): DeleteRuleResult! deleteWebhook(id: ID!): DeleteWebhookResult! + emptyTrash: EmptyTrashResult! + fetchContent(id: ID!): FetchContentResult! generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult! googleLogin(input: GoogleLoginInput!): LoginResult! googleSignup(input: GoogleSignupInput!): GoogleSignupResult! @@ -1135,6 +1240,7 @@ type Mutation { mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult! moveFilter(input: MoveFilterInput!): MoveFilterResult! moveLabel(input: MoveLabelInput!): MoveLabelResult! + moveToFolder(folder: String!, id: ID!): MoveToFolderResult! optInFeature(input: OptInFeatureInput!): OptInFeatureResult! recommend(input: RecommendInput!): RecommendResult! recommendHighlights(input: RecommendHighlightsInput!): RecommendHighlightsResult! @@ -1161,6 +1267,7 @@ type Mutation { updateFilter(input: UpdateFilterInput!): UpdateFilterResult! updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult! updateLabel(input: UpdateLabelInput!): UpdateLabelResult! + updateNewsletterEmail(input: UpdateNewsletterEmailInput!): UpdateNewsletterEmailResult! updatePage(input: UpdatePageInput!): UpdatePageResult! updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionResult! updateUser(input: UpdateUserInput!): UpdateUserResult! @@ -1173,7 +1280,10 @@ type NewsletterEmail { address: String! confirmationCode: String createdAt: Date! + description: String + folder: String! id: ID! + name: String subscriptionCount: Int! } @@ -1292,6 +1402,7 @@ type Query { article(format: String, slug: String!, username: String!): ArticleResult! articleSavingRequest(id: ID, url: String): ArticleSavingRequestResult! deviceTokens: DeviceTokensResult! + feeds(input: FeedsInput!): FeedsResult! filters: FiltersResult! getUserPersonalization: GetUserPersonalizationResult! groups: GroupsResult! @@ -1303,11 +1414,12 @@ type Query { recentEmails: RecentEmailsResult! recentSearches: RecentSearchesResult! rules(enabled: Boolean): RulesResult! + scanFeeds(input: ScanFeedsInput!): ScanFeedsResult! search(after: String, first: Int, format: String, includeContent: Boolean, query: String): SearchResult! sendInstallInstructions: SendInstallInstructionsResult! subscriptions(sort: SortParams, type: SubscriptionType): SubscriptionsResult! typeaheadSearch(first: Int, query: String!): TypeaheadSearchResult! - updatesSince(after: String, first: Int, since: Date!, sort: SortParams): UpdatesSinceResult! + updatesSince(after: String, first: Int, folder: String, since: Date!, sort: SortParams): UpdatesSinceResult! user(userId: ID, username: String): UserResult! users: UsersResult! validateUsername(username: String!): Boolean! @@ -1604,6 +1716,7 @@ enum SaveErrorCode { input SaveFileInput { clientRequestId: ID! + folder: String labels: [CreateLabelInput!] source: String! state: ArticleSavingRequestStatus @@ -1625,6 +1738,7 @@ input SaveFilterInput { category: String description: String filter: String! + folder: String name: String! position: Int } @@ -1637,6 +1751,7 @@ type SaveFilterSuccess { input SavePageInput { clientRequestId: ID! + folder: String labels: [CreateLabelInput!] originalContent: String! parseResult: ParseResult @@ -1658,6 +1773,7 @@ type SaveSuccess { input SaveUrlInput { clientRequestId: ID! + folder: String labels: [CreateLabelInput!] locale: String publishedAt: Date @@ -1668,6 +1784,25 @@ input SaveUrlInput { url: String! } +type ScanFeedsError { + errorCodes: [ScanFeedsErrorCode!]! +} + +enum ScanFeedsErrorCode { + BAD_REQUEST +} + +input ScanFeedsInput { + opml: String + url: String +} + +union ScanFeedsResult = ScanFeedsError | ScanFeedsSuccess + +type ScanFeedsSuccess { + feeds: [Feed!]! +} + type SearchError { errorCodes: [SearchErrorCode!]! } @@ -1686,16 +1821,20 @@ type SearchItem { contentReader: ContentReader! createdAt: Date! description: String + folder: String! highlights: [Highlight!] id: ID! image: String isArchived: Boolean! labels: [Label!] language: String + links: JSON originalArticleUrl: String ownedByViewer: Boolean pageId: ID pageType: PageType! + previewContent: String + previewContentType: String publishedAt: Date quote: String readAt: Date @@ -1844,6 +1983,7 @@ input SetIntegrationInput { importItemState: ImportItemState name: String! syncedAt: Date + taskName: String token: String! type: IntegrationType } @@ -1874,6 +2014,7 @@ input SetLabelsInput { labelIds: [ID!] labels: [CreateLabelInput!] pageId: ID! + source: String } union SetLabelsResult = SetLabelsError | SetLabelsSuccess @@ -1963,6 +2104,7 @@ enum SetUserPersonalizationErrorCode { } input SetUserPersonalizationInput { + fields: JSON fontFamily: String fontSize: Int libraryLayoutType: String @@ -2068,6 +2210,10 @@ enum SubscribeErrorCode { } input SubscribeInput { + autoAddToLibrary: Boolean + fetchContent: Boolean + folder: String + isPrivate: Boolean subscriptionType: SubscriptionType url: String! } @@ -2079,11 +2225,15 @@ type SubscribeSuccess { } type Subscription { + autoAddToLibrary: Boolean count: Int! createdAt: Date! description: String + fetchContent: Boolean! + folder: String! icon: String id: ID! + isPrivate: Boolean lastFetchedAt: Date name: String! newsletterEmail: String @@ -2203,6 +2353,7 @@ input UpdateFilterInput { category: String description: String filter: String + folder: String id: String! name: String position: Int @@ -2307,6 +2458,28 @@ type UpdateLinkShareInfoSuccess { message: String! } +type UpdateNewsletterEmailError { + errorCodes: [UpdateNewsletterEmailErrorCode!]! +} + +enum UpdateNewsletterEmailErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input UpdateNewsletterEmailInput { + description: String + folder: String + id: ID! + name: String +} + +union UpdateNewsletterEmailResult = UpdateNewsletterEmailError | UpdateNewsletterEmailSuccess + +type UpdateNewsletterEmailSuccess { + newsletterEmail: NewsletterEmail! +} + type UpdatePageError { errorCodes: [UpdatePageErrorCode!]! } @@ -2397,8 +2570,12 @@ enum UpdateSubscriptionErrorCode { } input UpdateSubscriptionInput { + autoAddToLibrary: Boolean description: String + fetchContent: Boolean + folder: String id: ID! + isPrivate: Boolean lastFetchedAt: Date lastFetchedChecksum: String name: String @@ -2557,6 +2734,7 @@ enum UserErrorCode { } type UserPersonalization { + fields: JSON fontFamily: String fontSize: Int id: ID diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemUpdatesQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemUpdatesQuery.kt index a50c71ce7..783ff3656 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemUpdatesQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemUpdatesQuery.kt @@ -20,6 +20,7 @@ suspend fun Networker.savedItemUpdates( try { val result = authenticatedApolloClient().query( UpdatesSinceQuery( + folder = Optional.presentIfNotNull("all"), after = Optional.presentIfNotNull(cursor), first = Optional.presentIfNotNull(limit), since = since From 24a87d696dd5c4d289e0a3c2d1f9be4ac44b0bd4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 11:46:18 +0800 Subject: [PATCH 053/278] Open the labels sheet from the library on Android --- .../java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index 02c4742f8..3f7f54e34 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -300,7 +300,7 @@ class LibraryViewModel @Inject constructor( SavedItemAction.EditLabels -> { currentItemLiveData.value = itemID - bottomSheetState.value = LibraryBottomSheetState.EDIT + bottomSheetState.value = LibraryBottomSheetState.LABEL } SavedItemAction.EditInfo -> { From eafdd41ee18b6a27d8aa385bde34314ce834e535 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 8 Jan 2024 12:07:26 +0800 Subject: [PATCH 054/278] get all the items updated since the last sync including archived items --- packages/integration-handler/src/item.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integration-handler/src/item.ts b/packages/integration-handler/src/item.ts index df9da7725..e406f915a 100644 --- a/packages/integration-handler/src/item.ts +++ b/packages/integration-handler/src/item.ts @@ -47,7 +47,8 @@ export const search = async ( first = 50, after = '0' ): Promise => { - const query = `updated:${updatedSince.toISOString()} ${ + // get all the items updated since the last sync including archived items + const query = `in:all updated:${updatedSince.toISOString()} ${ highlightOnly ? 'has:highlights' : '' } sort:updated-asc` From 42a36f1820c6119921c62a44a9f062bc2caa9e22 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 8 Jan 2024 12:20:31 +0800 Subject: [PATCH 055/278] log when retrieved 0 items --- packages/integration-handler/src/index.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/integration-handler/src/index.ts b/packages/integration-handler/src/index.ts index f9d657297..0aebced4d 100644 --- a/packages/integration-handler/src/index.ts +++ b/packages/integration-handler/src/index.ts @@ -127,15 +127,16 @@ export const exporter = Sentry.GCPFunction.wrapHttpFunction( hasMore = response.data.search.pageInfo.hasNextPage after = response.data.search.pageInfo.endCursor const items = response.data.search.edges.map((edge) => edge.node) - if (items.length === 0) { - break - } - + const size = items.length console.log('exporting items...', { userId: claims.uid, - total: items.length, + size, hasMore, }) + + if (size === 0) { + break + } const synced = await client.export(claims.token, items) if (!synced) { console.error('failed to export item', { @@ -144,16 +145,17 @@ export const exporter = Sentry.GCPFunction.wrapHttpFunction( return res.status(400).send('Failed to sync') } + const lastItemUpdatedAt = items[size - 1].updatedAt console.log('updating integration...', { userId: claims.uid, integrationId, - syncedAt: items[items.length - 1].updatedAt, + syncedAt: lastItemUpdatedAt, }) // update integration syncedAt if successful const updated = await updateIntegration( REST_BACKEND_ENDPOINT, integrationId, - items[items.length - 1].updatedAt, + lastItemUpdatedAt, integrationName, claims.token, systemToken, From ceff6fea376972e32f49f2fe8e6858594d421121 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 13:46:33 +0800 Subject: [PATCH 056/278] Improve labels modal on Android --- .../ui/components/LabelsSelectionSheet.kt | 75 ++++++++++++------- .../omnivore/ui/library/LibraryView.kt | 13 +++- .../ui/reader/WebReaderLoadingContainer.kt | 3 +- .../app/omnivore/omnivore/ui/theme/Color.kt | 18 ++--- 4 files changed, 68 insertions(+), 41 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt index 3b4605df3..d6ec5c75f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -20,15 +21,22 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Scaffold import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -49,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.intl.Locale @@ -56,6 +65,7 @@ import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import app.omnivore.omnivore.R import app.omnivore.omnivore.persistence.entities.SavedItemLabel import com.dokar.chiptextfield.Chip @@ -279,40 +289,43 @@ fun LabelsSelectionSheetContent( stringResource(R.string.label_selection_sheet_title) else stringResource(R.string.label_selection_sheet_title_alt) - Surface( + Scaffold( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background), - ) { + .background(MaterialTheme.colorScheme.primaryContainer), + topBar = { + CenterAlignedTopAppBar( + title = { + Text(titleText, fontWeight = FontWeight.ExtraBold) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + navigationIcon = { + TextButton(onClick = onCancel) { + Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) + } + }, + actions = { + TextButton(onClick = { onSave(state.chips.map { it.label }) }) { + Text( + text = if (isLibraryMode) + stringResource(R.string.label_selection_sheet_action_search) else + stringResource(R.string.label_selection_sheet_action_save) + ) + } + } + ) + } + ) { paddingValues -> Column( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() - .padding(horizontal = 5.dp) + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = paddingValues.calculateTopPadding()) ) { - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - ) { - TextButton(onClick = onCancel) { - Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) - } - - Text(titleText, fontWeight = FontWeight.ExtraBold) - - TextButton(onClick = { onSave(state.chips.map { it.label }) }) { - Text( - text = if (isLibraryMode) - stringResource(R.string.label_selection_sheet_action_search) else - stringResource(R.string.label_selection_sheet_action_save) - ) - } - } - ChipTextField( state = state, value = filterTextValue, @@ -344,7 +357,7 @@ fun LabelsSelectionSheetContent( ), colors = TextFieldDefaults.textFieldColors( textColor = MaterialTheme.colorScheme.onBackground, - backgroundColor = MaterialTheme.colorScheme.background + backgroundColor = MaterialTheme.colorScheme.surface ), contentPadding = PaddingValues(10.dp), modifier = Modifier @@ -403,13 +416,17 @@ fun LabelsSelectionSheetContent( Icon( imageVector = Icons.Filled.AddCircle, contentDescription = null, - modifier = Modifier.padding(end = 8.dp) + modifier = Modifier.padding(end = 8.dp), + tint = MaterialTheme.colorScheme.onSurface ) Text( text = stringResource( R.string.label_selection_sheet_text_create, filterTextValue.text.trim() - ) + ), + style = TextStyle( + color = MaterialTheme.colorScheme.onSurface + ), ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 76850d9af..8c9068efe 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -20,6 +20,7 @@ import androidx.compose.material.DismissValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold import androidx.compose.material.Icon +import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Scaffold import androidx.compose.material.ScaffoldState import androidx.compose.material.SwipeToDismiss @@ -31,6 +32,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.rememberDismissState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material.rememberScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -144,14 +146,21 @@ fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) { libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK } -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun LabelBottomSheet( libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel, onDismiss: () -> Unit = {} ) { - ModalBottomSheet(onDismissRequest = { onDismiss() }) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + containerColor = Color.Transparent, + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ), + ) { + val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() val labels: List by libraryViewModel.savedItemLabelsLiveData.observeAsState( listOf() diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index abdfebfcd..15193a361 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* +import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Home @@ -197,7 +198,7 @@ fun WebReaderLoadingContainer(onLibraryIconTap: (() -> Unit)? = null, ModalBottomSheetLayout( modifier = Modifier .statusBarsPadding(), - sheetBackgroundColor = Color.Transparent, + sheetBackgroundColor = MaterialTheme.colors.primary, sheetState = modalBottomSheetState, sheetContent = { when (bottomSheetState) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt index f7243c32b..8939caf19 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/theme/Color.kt @@ -34,12 +34,12 @@ val md_theme_light_outlineVariant = Color(0xFFD3C4B4) val md_theme_light_scrim = Color(0xFF000000) val md_theme_dark_primary = Color(0xFFEFC125) -val md_theme_dark_onPrimary = Color(0xFF3D2F00) -val md_theme_dark_primaryContainer = Color(0xFF633F00) +val md_theme_dark_onPrimary = Color(0xFF212121) +val md_theme_dark_primaryContainer = Color(0xFF212121) val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3) val md_theme_dark_secondary = Color(0xFFDDC2A1) -val md_theme_dark_onSecondary = Color(0xFF3E2D16) -val md_theme_dark_secondaryContainer = Color(0xFF56442A) +val md_theme_dark_onSecondary = Color(0xFF212121) +val md_theme_dark_secondaryContainer = Color(0xFF283237) val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC) val md_theme_dark_tertiary = Color(0xFFB8CEA1) val md_theme_dark_onTertiary = Color(0xFF243515) @@ -49,20 +49,20 @@ val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_onError = Color(0xFF690005) val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) -val md_theme_dark_background = Color(0xFF1F1B16) +val md_theme_dark_background = Color(0xFF262626) val md_theme_dark_onBackground = Color(0xFFEAE1D9) val md_theme_dark_surface = Color(0xFF1F1B16) val md_theme_dark_onSurface = Color(0xFFEAE1D9) -val md_theme_dark_surfaceVariant = Color(0xFF4F4539) +val md_theme_dark_surfaceVariant = Color(0xFF212121) val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4) val md_theme_dark_outline = Color(0xFF9C8F80) val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16) val md_theme_dark_inverseSurface = Color(0xFFEAE1D9) -val md_theme_dark_inversePrimary = Color(0xFF825500) +val md_theme_dark_inversePrimary = Color(0xFF212121) val md_theme_dark_shadow = Color(0xFF000000) val md_theme_dark_surfaceTint = Color(0xFFFFB951) -val md_theme_dark_outlineVariant = Color(0xFF4F4539) +val md_theme_dark_outlineVariant = Color(0xFF424242) val md_theme_dark_scrim = Color(0xFF000000) -val seed = Color(0xFF825500) +// val seed = Color(0xFF825500) From 89d126734c25a391effcc0f2783a73075fe856ec Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 13:48:53 +0800 Subject: [PATCH 057/278] Transparent backgrounds for modal bottom sheets --- .../omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 15193a361..f3945e24c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -198,7 +198,7 @@ fun WebReaderLoadingContainer(onLibraryIconTap: (() -> Unit)? = null, ModalBottomSheetLayout( modifier = Modifier .statusBarsPadding(), - sheetBackgroundColor = MaterialTheme.colors.primary, + sheetBackgroundColor = Color.Transparent, sheetState = modalBottomSheetState, sheetContent = { when (bottomSheetState) { From 291a424c58d307aff33abbd70412ed25dae4d0dd Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 14:41:42 +0800 Subject: [PATCH 058/278] More consistency on modal bottom sheets colors --- .../omnivore/ui/components/AddLinkSheet.kt | 67 ++++++++++++------- .../ui/components/LabelsSelectionSheet.kt | 7 +- .../omnivore/ui/editinfo/EditInfoSheet.kt | 64 +++++++++--------- .../omnivore/ui/library/LibraryView.kt | 19 +++++- 4 files changed, 96 insertions(+), 61 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/AddLinkSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/AddLinkSheet.kt index 6ab9bd169..1174bb6c3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/AddLinkSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/AddLinkSheet.kt @@ -27,6 +27,7 @@ import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.save.SaveState import app.omnivore.omnivore.ui.save.SaveViewModel +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AddLinkSheetContent( viewModel: SaveViewModel, @@ -84,42 +85,47 @@ fun AddLinkSheetContent( viewModel.saveURL(url) } - Surface( + androidx.compose.material.Scaffold( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background), - ) { + .background(MaterialTheme.colorScheme.primaryContainer), + topBar = { + CenterAlignedTopAppBar( + title = { + Text(stringResource(R.string.add_link_sheet_title)) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + navigationIcon = { + TextButton(onClick = onCancel) { + Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) + } + }, + actions = { + TextButton(onClick = { addLink(textFieldValue.text) }) { + Text(stringResource(R.string.add_link_sheet_action_add_link)) + } + } + ) + } + ) { paddingValues -> Column( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() - .padding(horizontal = 5.dp) + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 10.dp) ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - ) { - TextButton(onClick = onCancel) { - Text(text = stringResource(R.string.add_link_sheet_action_cancel)) - } - - Text(stringResource(R.string.add_link_sheet_title), fontWeight = FontWeight.ExtraBold) - - TextButton(onClick = { addLink(textFieldValue.text) }) { - Text(stringResource(R.string.add_link_sheet_action_add_link)) - } - } - if (isSaving.value == true) { Spacer(modifier = Modifier.width(16.dp)) CircularProgressIndicator( modifier = Modifier .height(16.dp) .width(16.dp), + strokeWidth = 2.dp, color = MaterialTheme.colorScheme.primary ) @@ -129,18 +135,29 @@ fun AddLinkSheetContent( value = textFieldValue, placeholder = { Text(stringResource(R.string.add_link_sheet_text_field_placeholder)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri), - leadingIcon = { Icon(imageVector = Icons.Default.Link, contentDescription = "linkIcon") }, + leadingIcon = { + Icon( + imageVector = Icons.Default.Link, + contentDescription = "linkIcon" + ) + }, onValueChange = { textFieldValue = it }, - modifier = Modifier.focusRequester(focusRequester).padding(top = 24.dp).fillMaxWidth() + modifier = Modifier + .focusRequester(focusRequester) + .padding(top = 24.dp) + .padding(horizontal = 10.dp) + .fillMaxWidth() ) if (clipboardText != null) { Button( - modifier = Modifier.padding(top = 10.dp), + modifier = Modifier.padding(top = 10.dp) .padding(horizontal = 10.dp) + , onClick = { textFieldValue = TextFieldValue( text = clipboardText, - selection = TextRange(clipboardText.length)) + selection = TextRange(clipboardText.length) + ) } ) { Text(stringResource(R.string.add_link_sheet_action_paste_from_clipboard)) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt index d6ec5c75f..9f3ca3606 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt @@ -296,7 +296,7 @@ fun LabelsSelectionSheetContent( topBar = { CenterAlignedTopAppBar( title = { - Text(titleText, fontWeight = FontWeight.ExtraBold) + Text(titleText) }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background @@ -324,7 +324,7 @@ fun LabelsSelectionSheetContent( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - .padding(horizontal = paddingValues.calculateTopPadding()) + .padding(horizontal = 10.dp) ) { ChipTextField( state = state, @@ -363,6 +363,7 @@ fun LabelsSelectionSheetContent( modifier = Modifier .defaultMinSize(minHeight = 45.dp) .fillMaxWidth() + .padding(top = 24.dp) .padding(horizontal = 10.dp) .focusRequester(focusRequester) // .onFocusEvent { @@ -410,7 +411,7 @@ fun LabelsSelectionSheetContent( } } .padding(horizontal = 10.dp) - .padding(top = 10.dp, bottom = 5.dp) + .padding(top = 24.dp, bottom = 5.dp) ) { Icon( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt index fa7abe53b..ad2b04f1b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.MutableLiveData import app.omnivore.omnivore.R +@OptIn(ExperimentalMaterial3Api::class) @Composable fun EditInfoSheetContent( savedItemId: String?, @@ -68,44 +69,47 @@ fun EditInfoSheetContent( } } - Surface( + androidx.compose.material.Scaffold( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background), - ) { + .background(MaterialTheme.colorScheme.primaryContainer), + topBar = { + CenterAlignedTopAppBar( + title = { + Text(stringResource(R.string.edit_info_sheet_title)) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + navigationIcon = { + TextButton(onClick = onCancel) { + Text(text = stringResource(R.string.edit_info_sheet_action_cancel)) + } + }, + actions = { + TextButton(onClick = { + val newTitle = titleTextFieldValue.text + val newAuthor = authorTextFieldValue.text.ifEmpty { null } + val newDescription = descriptionTextFieldValue.text.ifEmpty { null } + + savedItemId?.let { + viewModel.editInfo(it, newTitle, newAuthor, newDescription) + } + }) { + Text(stringResource(R.string.edit_info_sheet_action_save)) + } + } + ) + } + ) { paddingValues -> Column( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp) + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 10.dp) ) { - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - ) { - TextButton(onClick = onCancel) { - Text(text = stringResource(R.string.edit_info_sheet_action_cancel)) - } - - Text(stringResource(R.string.edit_info_sheet_title), fontWeight = FontWeight.ExtraBold) - - TextButton(onClick = { - val newTitle = titleTextFieldValue.text - val newAuthor = authorTextFieldValue.text.ifEmpty { null } - val newDescription = descriptionTextFieldValue.text.ifEmpty { null } - - savedItemId?.let { - viewModel.editInfo(it, newTitle, newAuthor, newDescription) - } - }) { - Text(stringResource(R.string.edit_info_sheet_action_save)) - } - } - if (isUpdating.value == true) { Spacer(modifier = Modifier.width(16.dp)) CircularProgressIndicator( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 8c9068efe..e4dd86234 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -155,7 +155,7 @@ fun LabelBottomSheet( ) { ModalBottomSheet( onDismissRequest = { onDismiss() }, - containerColor = Color.Transparent, + containerColor = MaterialTheme.colorScheme.background, sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ), @@ -215,7 +215,14 @@ fun AddLinkBottomSheet( saveViewModel: SaveViewModel, onDismiss: () -> Unit = {} ) { - ModalBottomSheet(onDismissRequest = { onDismiss() }) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + containerColor = MaterialTheme.colorScheme.background, + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ), + ) { + AddLinkSheetContent( viewModel = saveViewModel, onCancel = { @@ -237,7 +244,13 @@ fun EditBottomSheet( libraryViewModel: LibraryViewModel, onDismiss: () -> Unit = {} ) { - ModalBottomSheet(onDismissRequest = { onDismiss() }) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + containerColor = MaterialTheme.colorScheme.background, + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ), + ) { val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() EditInfoSheetContent( savedItemId = currentSavedItemData?.savedItem?.savedItemId, From da8eca478795655e7673b49325fba6f4fd18c801 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 14:50:44 +0800 Subject: [PATCH 059/278] Fix the steps on reader sliders --- .../omnivore/omnivore/ui/reader/ReaderPreferencesView.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt index afd68399b..84f42c7db 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt @@ -112,8 +112,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { fontSizeSliderValue = it webReaderViewModel.setFontSize(it.toInt()) }, - steps = 10, - valueRange = 10f..48f, + steps = 40, + valueRange = 10f..50f, ) Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle( @@ -127,7 +127,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { marginSliderValue = it webReaderViewModel.setMaxWidthPercentage(it.toInt()) }, - steps = 4, + steps = 40, valueRange = 60f..100f, ) @@ -142,7 +142,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { lineSpacingSliderValue = it webReaderViewModel.setLineHeight(it.toInt()) }, - steps = 8, + steps = 50, valueRange = 100f..300f, ) From 9f71f1f58d0cca966ce468c98907338375f1962b Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 14:54:04 +0800 Subject: [PATCH 060/278] Bump android version --- android/Omnivore/app/build.gradle | 4 ++-- .../app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index f975ca5b0..e1a4d438e 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 170 - versionName "0.0.170" + versionCode 174 + versionName "0.0.174" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt index 84f42c7db..40e967f22 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt @@ -37,7 +37,6 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { val selectedWebFontName = remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) } - var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) } var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) } var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) } From 42d21ebbaec1f4109a6af4eca06c955950ec3b52 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 15:14:29 +0800 Subject: [PATCH 061/278] Display version number in settings --- .../omnivore/ui/settings/SettingsContent.kt | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt index e303b2ca6..56de10295 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt @@ -14,7 +14,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController +import app.omnivore.omnivore.BuildConfig import app.omnivore.omnivore.R import app.omnivore.omnivore.Routes import app.omnivore.omnivore.ui.auth.LoginViewModel @@ -64,9 +66,12 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti Box( modifier = modifier.fillMaxSize() ) { + + val version = "Omnivore Version: " + BuildConfig.VERSION_NAME + Column( verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally, + horizontalAlignment = Alignment.Start, modifier = Modifier .background(MaterialTheme.colorScheme.background) .fillMaxSize() @@ -74,23 +79,6 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti .verticalScroll(rememberScrollState()) ) { - // profile pic and name - -// SettingRow(text = "Labels") { Log.d("settings", "labels button tapped") } -// RowDivider() -// SettingRow(text = "Emails") { Log.d("settings", "emails button tapped") } -// RowDivider() -// SettingRow(text = "Subscriptions") { Log.d("settings", "subscriptions button tapped") } -// RowDivider() -// SettingRow(text = "Clubs") { Log.d("settings", "clubs button tapped") } - -// SectionSpacer() - -// SettingRow(text = "Push Notifications") { Log.d("settings", "pn button tapped") } -// RowDivider() -// SettingRow(text = "Text to Speech") { Log.d("settings", "tts button tapped") } -// -// SectionSpacer() SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) { navController.navigate(Routes.Documentation.route) @@ -118,6 +106,13 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti showLogoutDialog.value = true } RowDivider() + + Text( + text = version, + fontSize = 12.sp, + modifier = Modifier + .padding(15.dp) + ) } if (showLogoutDialog.value) { From 8f9f8bb43b6cded259d72449a1ce75575debddba Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 15:21:36 +0800 Subject: [PATCH 062/278] Bump version --- android/Omnivore/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index e1a4d438e..7fbe9d35f 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -19,8 +19,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 174 - versionName "0.0.174" + versionCode 176 + versionName "0.0.176" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From cb48c755f1b79f8a16ff5598d6016af0bf966200 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Jan 2024 22:27:48 +0800 Subject: [PATCH 063/278] Start creating a separate sync queue for android highlight changes --- .../Omnivore/app/src/main/assets/bundle.js | 2 +- .../dataService/HighlightActionHandlers.kt | 90 +++++++++++++------ .../dataService/SyncOfflineChanges.kt | 8 +- .../omnivore/networking/HighlightMutations.kt | 6 +- .../omnivore/persistence/AppDatabase.kt | 4 +- .../persistence/entities/HighlightChange.kt | 70 +++++++++++++++ .../omnivore/ui/reader/WebReaderViewModel.kt | 2 +- .../Sources/Views/Resources/bundle.js | 2 +- packages/appreader/src/index.jsx | 4 +- .../web/components/patterns/ArticleNotes.tsx | 1 + .../components/patterns/HighlightNotes.tsx | 1 + .../templates/article/HighlightNoteModal.tsx | 2 + .../templates/article/HighlightsLayer.tsx | 8 +- .../components/templates/article/Notebook.tsx | 4 +- .../templates/article/PdfArticleContainer.tsx | 7 +- packages/web/lib/articleActions.ts | 5 +- .../mutations/deleteHighlightMutation.ts | 1 + .../mutations/updateHighlightMutation.ts | 1 + 18 files changed, 175 insertions(+), 43 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/HighlightChange.kt diff --git a/android/Omnivore/app/src/main/assets/bundle.js b/android/Omnivore/app/src/main/assets/bundle.js index a6561475d..0b189a361 100644 --- a/android/Omnivore/app/src/main/assets/bundle.js +++ b/android/Omnivore/app/src/main/assets/bundle.js @@ -1,2 +1,2 @@ /*! For license information please see bundle.js.LICENSE.txt */ -(()=>{var e,t,n={7162:(e,t,n)=>{e.exports=n(5047)},1365:e=>{e.exports={trueFunc:function(){return!0},falseFunc:function(){return!1}}},6279:function(e,t){var n="undefined"!=typeof self?self:this,r=function(){function e(){this.fetch=!1,this.DOMException=n.DOMException}return e.prototype=n,new e}();!function(e){!function(t){var n="URLSearchParams"in e,r="Symbol"in e&&"iterator"in Symbol,o="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),i="FormData"in e,a="ArrayBuffer"in e;if(a)var l=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],s=ArrayBuffer.isView||function(e){return e&&l.indexOf(Object.prototype.toString.call(e))>-1};function u(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function c(e){return"string"!=typeof e&&(e=String(e)),e}function d(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return r&&(t[Symbol.iterator]=function(){return t}),t}function f(e){this.map={},e instanceof f?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function p(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function h(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function g(e){var t=new FileReader,n=h(t);return t.readAsArrayBuffer(e),n}function m(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function v(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:o&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:i&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():a&&o&&(t=e)&&DataView.prototype.isPrototypeOf(t)?(this._bodyArrayBuffer=m(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a&&(ArrayBuffer.prototype.isPrototypeOf(e)||s(e))?this._bodyArrayBuffer=m(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},o&&(this.blob=function(){var e=p(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?p(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(g)}),this.text=function(){var e,t,n,r=p(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,n=h(t=new FileReader),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function A(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function w(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new f(t.headers),this.url=t.url||"",this._initBody(e)}y.prototype.clone=function(){return new y(this,{body:this._bodyInit})},v.call(y.prototype),v.call(w.prototype),w.prototype.clone=function(){return new w(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new f(this.headers),url:this.url})},w.error=function(){var e=new w(null,{status:0,statusText:""});return e.type="error",e};var x=[301,302,303,307,308];w.redirect=function(e,t){if(-1===x.indexOf(t))throw new RangeError("Invalid status code");return new w(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function E(e,n){return new Promise((function(r,i){var a=new y(e,n);if(a.signal&&a.signal.aborted)return i(new t.DOMException("Aborted","AbortError"));var l=new XMLHttpRequest;function s(){l.abort()}l.onload=function(){var e,t,n={status:l.status,statusText:l.statusText,headers:(e=l.getAllResponseHeaders()||"",t=new f,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in l?l.responseURL:n.headers.get("X-Request-URL");var o="response"in l?l.response:l.responseText;r(new w(o,n))},l.onerror=function(){i(new TypeError("Network request failed"))},l.ontimeout=function(){i(new TypeError("Network request failed"))},l.onabort=function(){i(new t.DOMException("Aborted","AbortError"))},l.open(a.method,a.url,!0),"include"===a.credentials?l.withCredentials=!0:"omit"===a.credentials&&(l.withCredentials=!1),"responseType"in l&&o&&(l.responseType="blob"),a.headers.forEach((function(e,t){l.setRequestHeader(t,e)})),a.signal&&(a.signal.addEventListener("abort",s),l.onreadystatechange=function(){4===l.readyState&&a.signal.removeEventListener("abort",s)}),l.send(void 0===a._bodyInit?null:a._bodyInit)}))}E.polyfill=!0,e.fetch||(e.fetch=E,e.Headers=f,e.Request=y,e.Response=w),t.Headers=f,t.Request=y,t.Response=w,t.fetch=E,Object.defineProperty(t,"__esModule",{value:!0})}({})}(r),r.fetch.ponyfill=!0,delete r.fetch.polyfill;var o=r;(t=o.fetch).default=o.fetch,t.fetch=o.fetch,t.Headers=o.Headers,t.Request=o.Request,t.Response=o.Response,e.exports=t},8105:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.attributeRules=void 0;var o=r(n(1365)),i=/[-[\]{}()*+?.,\\^$|#\s]/g;function a(e){return e.replace(i,"\\$&")}var l=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function s(e,t){return"boolean"==typeof e.ignoreCase?e.ignoreCase:"quirks"===e.ignoreCase?!!t.quirksMode:!t.xmlMode&&l.has(e.name)}t.attributeRules={equals:function(e,t,n){var r=n.adapter,o=t.name,i=t.value;return s(t,n)?(i=i.toLowerCase(),function(t){var n=r.getAttributeValue(t,o);return null!=n&&n.length===i.length&&n.toLowerCase()===i&&e(t)}):function(t){return r.getAttributeValue(t,o)===i&&e(t)}},hyphen:function(e,t,n){var r=n.adapter,o=t.name,i=t.value,a=i.length;return s(t,n)?(i=i.toLowerCase(),function(t){var n=r.getAttributeValue(t,o);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a).toLowerCase()===i&&e(t)}):function(t){var n=r.getAttributeValue(t,o);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a)===i&&e(t)}},element:function(e,t,n){var r=n.adapter,i=t.name,l=t.value;if(/\s/.test(l))return o.default.falseFunc;var u=new RegExp("(?:^|\\s)".concat(a(l),"(?:$|\\s)"),s(t,n)?"i":"");return function(t){var n=r.getAttributeValue(t,i);return null!=n&&n.length>=l.length&&u.test(n)&&e(t)}},exists:function(e,t,n){var r=t.name,o=n.adapter;return function(t){return o.hasAttrib(t,r)&&e(t)}},start:function(e,t,n){var r=n.adapter,i=t.name,a=t.value,l=a.length;return 0===l?o.default.falseFunc:s(t,n)?(a=a.toLowerCase(),function(t){var n=r.getAttributeValue(t,i);return null!=n&&n.length>=l&&n.substr(0,l).toLowerCase()===a&&e(t)}):function(t){var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.startsWith(a))&&e(t)}},end:function(e,t,n){var r=n.adapter,i=t.name,a=t.value,l=-a.length;return 0===l?o.default.falseFunc:s(t,n)?(a=a.toLowerCase(),function(t){var n;return(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.substr(l).toLowerCase())===a&&e(t)}):function(t){var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.endsWith(a))&&e(t)}},any:function(e,t,n){var r=n.adapter,i=t.name,l=t.value;if(""===l)return o.default.falseFunc;if(s(t,n)){var u=new RegExp(a(l),"i");return function(t){var n=r.getAttributeValue(t,i);return null!=n&&n.length>=l.length&&u.test(n)&&e(t)}}return function(t){var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.includes(l))&&e(t)}},not:function(e,t,n){var r=n.adapter,o=t.name,i=t.value;return""===i?function(t){return!!r.getAttributeValue(t,o)&&e(t)}:s(t,n)?(i=i.toLowerCase(),function(t){var n=r.getAttributeValue(t,o);return(null==n||n.length!==i.length||n.toLowerCase()!==i)&&e(t)}):function(t){return r.getAttributeValue(t,o)!==i&&e(t)}}}},5716:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var o=Object.getOwnPropertyDescriptor(t,n);o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,o)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return o(t,e),t},a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.compileToken=t.compileUnsafe=t.compile=void 0;var l=n(3658),s=a(n(1365)),u=i(n(3055)),c=n(9919),d=n(3973);function f(e,t,n){return v("string"==typeof e?(0,l.parse)(e):e,t,n)}function p(e){return e.type===l.SelectorType.Pseudo&&("scope"===e.name||Array.isArray(e.data)&&e.data.some((function(e){return e.some(p)})))}t.compile=function(e,t,n){var r=f(e,t,n);return(0,d.ensureIsTag)(r,t.adapter)},t.compileUnsafe=f;var h={type:l.SelectorType.Descendant},g={type:"_flexibleDescendant"},m={type:l.SelectorType.Pseudo,name:"scope",data:null};function v(e,t,n){var r;e.forEach(u.default),n=null!==(r=t.context)&&void 0!==r?r:n;var o=Array.isArray(n),i=n&&(Array.isArray(n)?n:[n]);if(!1!==t.relativeSelector)!function(e,t,n){for(var r=t.adapter,o=!!(null==n?void 0:n.every((function(e){var t=r.isTag(e)&&r.getParent(e);return e===d.PLACEHOLDER_ELEMENT||t&&r.isTag(t)}))),i=0,a=e;i0&&(0,u.isTraversal)(s[0])&&s[0].type!==l.SelectorType.Descendant);else{if(!o||s.some(p))continue;s.unshift(h)}s.unshift(m)}}(e,t,i);else if(e.some((function(e){return e.length>0&&(0,u.isTraversal)(e[0])})))throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");var a=!1,f=e.map((function(e){if(e.length>=2){var n=e[0],r=e[1];n.type!==l.SelectorType.Pseudo||"scope"!==n.name||(o&&r.type===l.SelectorType.Descendant?e[1]=g:r.type!==l.SelectorType.Adjacent&&r.type!==l.SelectorType.Sibling||(a=!0))}return function(e,t,n){var r;return e.reduce((function(e,r){return e===s.default.falseFunc?s.default.falseFunc:(0,c.compileGeneralSelector)(e,r,t,n,v)}),null!==(r=t.rootFunc)&&void 0!==r?r:s.default.trueFunc)}(e,t,i)})).reduce(b,s.default.falseFunc);return f.shouldTestNextSiblings=a,f}function b(e,t){return t===s.default.falseFunc||e===s.default.trueFunc?e:e===s.default.falseFunc||t===s.default.trueFunc?t:function(n){return e(n)||t(n)}}t.compileToken=v},9919:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.compileGeneralSelector=void 0;var r=n(8105),o=n(7466),i=n(3658);function a(e,t){var n=t.getParent(e);return n&&t.isTag(n)?n:null}t.compileGeneralSelector=function(e,t,n,l,s){var u=n.adapter,c=n.equals;switch(t.type){case i.SelectorType.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case i.SelectorType.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case i.SelectorType.Attribute:if(null!=t.namespace)throw new Error("Namespaced attributes are not yet supported by css-select");return n.xmlMode&&!n.lowerCaseAttributeNames||(t.name=t.name.toLowerCase()),r.attributeRules[t.action](e,t,n);case i.SelectorType.Pseudo:return(0,o.compilePseudoSelector)(e,t,n,l,s);case i.SelectorType.Tag:if(null!=t.namespace)throw new Error("Namespaced tag names are not yet supported by css-select");var d=t.name;return n.xmlMode&&!n.lowerCaseTags||(d=d.toLowerCase()),function(t){return u.getName(t)===d&&e(t)};case i.SelectorType.Descendant:if(!1===n.cacheResults||"undefined"==typeof WeakSet)return function(t){for(var n=t;n=a(n,u);)if(e(n))return!0;return!1};var f=new WeakSet;return function(t){for(var n=t;n=a(n,u);)if(!f.has(n)){if(u.isTag(n)&&e(n))return!0;f.add(n)}return!1};case"_flexibleDescendant":return function(t){var n=t;do{if(e(n))return!0}while(n=a(n,u));return!1};case i.SelectorType.Parent:return function(t){return u.getChildren(t).some((function(t){return u.isTag(t)&&e(t)}))};case i.SelectorType.Child:return function(t){var n=u.getParent(t);return null!=n&&u.isTag(n)&&e(n)};case i.SelectorType.Sibling:return function(t){for(var n=u.getSiblings(t),r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.aliases=void 0,t.aliases={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:":is(\n :is(button, input, select, textarea, optgroup, option)[disabled],\n optgroup[disabled] > option,\n fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)\n )",enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"}},4449:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.filters=void 0;var o=r(n(7346)),i=r(n(1365));function a(e,t){return function(n){var r=t.getParent(n);return null!=r&&t.isTag(r)&&e(n)}}function l(e){return function(t,n,r){var o=r.adapter[e];return"function"!=typeof o?i.default.falseFunc:function(e){return o(e)&&t(e)}}}t.filters={contains:function(e,t,n){var r=n.adapter;return function(n){return e(n)&&r.getText(n).includes(t)}},icontains:function(e,t,n){var r=n.adapter,o=t.toLowerCase();return function(t){return e(t)&&r.getText(t).toLowerCase().includes(o)}},"nth-child":function(e,t,n){var r=n.adapter,l=n.equals,s=(0,o.default)(t);return s===i.default.falseFunc?i.default.falseFunc:s===i.default.trueFunc?a(e,r):function(t){for(var n=r.getSiblings(t),o=0,i=0;i=0&&!l(t,n[i]);i--)r.isTag(n[i])&&o++;return s(o)&&e(t)}},"nth-of-type":function(e,t,n){var r=n.adapter,l=n.equals,s=(0,o.default)(t);return s===i.default.falseFunc?i.default.falseFunc:s===i.default.trueFunc?a(e,r):function(t){for(var n=r.getSiblings(t),o=0,i=0;i=0;i--){var a=n[i];if(l(t,a))break;r.isTag(a)&&r.getName(a)===r.getName(t)&&o++}return s(o)&&e(t)}},root:function(e,t,n){var r=n.adapter;return function(t){var n=r.getParent(t);return(null==n||!r.isTag(n))&&e(t)}},scope:function(e,n,r,o){var i=r.equals;return o&&0!==o.length?1===o.length?function(t){return i(o[0],t)&&e(t)}:function(t){return o.includes(t)&&e(t)}:t.filters.root(e,n,r)},hover:l("isHovered"),visited:l("isVisited"),active:l("isActive")}},7466:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.compilePseudoSelector=t.aliases=t.pseudos=t.filters=void 0;var r=n(3658),o=n(4449);Object.defineProperty(t,"filters",{enumerable:!0,get:function(){return o.filters}});var i=n(8702);Object.defineProperty(t,"pseudos",{enumerable:!0,get:function(){return i.pseudos}});var a=n(4169);Object.defineProperty(t,"aliases",{enumerable:!0,get:function(){return a.aliases}});var l=n(3973);t.compilePseudoSelector=function(e,t,n,s,u){var c,d=t.name,f=t.data;if(Array.isArray(f)){if(!(d in l.subselects))throw new Error("Unknown pseudo-class :".concat(d,"(").concat(f,")"));return l.subselects[d](e,f,n,s,u)}var p=null===(c=n.pseudos)||void 0===c?void 0:c[d],h="string"==typeof p?p:a.aliases[d];if("string"==typeof h){if(null!=f)throw new Error("Pseudo ".concat(d," doesn't have any arguments"));var g=(0,r.parse)(h);return l.subselects.is(e,g,n,s,u)}if("function"==typeof p)return(0,i.verifyPseudoArgs)(p,d,f,1),function(t){return p(t,f)&&e(t)};if(d in o.filters)return o.filters[d](e,f,n,s);if(d in i.pseudos){var m=i.pseudos[d];return(0,i.verifyPseudoArgs)(m,d,f,2),function(t){return m(t,n,f)&&e(t)}}throw new Error("Unknown pseudo-class :".concat(d))}},8702:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.verifyPseudoArgs=t.pseudos=void 0,t.pseudos={empty:function(e,t){var n=t.adapter;return!n.getChildren(e).some((function(e){return n.isTag(e)||""!==n.getText(e)}))},"first-child":function(e,t){var n=t.adapter,r=t.equals;if(n.prevElementSibling)return null==n.prevElementSibling(e);var o=n.getSiblings(e).find((function(e){return n.isTag(e)}));return null!=o&&r(e,o)},"last-child":function(e,t){for(var n=t.adapter,r=t.equals,o=n.getSiblings(e),i=o.length-1;i>=0;i--){if(r(e,o[i]))return!0;if(n.isTag(o[i]))break}return!1},"first-of-type":function(e,t){for(var n=t.adapter,r=t.equals,o=n.getSiblings(e),i=n.getName(e),a=0;a=0;a--){var l=o[a];if(r(e,l))return!0;if(n.isTag(l)&&n.getName(l)===i)break}return!1},"only-of-type":function(e,t){var n=t.adapter,r=t.equals,o=n.getName(e);return n.getSiblings(e).every((function(t){return r(e,t)||!n.isTag(t)||n.getName(t)!==o}))},"only-child":function(e,t){var n=t.adapter,r=t.equals;return n.getSiblings(e).every((function(t){return r(e,t)||!n.isTag(t)}))}},t.verifyPseudoArgs=function(e,t,n,r){if(null===n){if(e.length>r)throw new Error("Pseudo-class :".concat(t," requires an argument"))}else if(e.length===r)throw new Error("Pseudo-class :".concat(t," doesn't have any arguments"))}},3973:function(e,t,n){"use strict";var r=this&&this.__spreadArray||function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isTraversal=void 0;var r=n(3658),o=new Map([[r.SelectorType.Universal,50],[r.SelectorType.Tag,30],[r.SelectorType.Attribute,1],[r.SelectorType.Pseudo,0]]);t.isTraversal=function(e){return!o.has(e.type)};var i=new Map([[r.AttributeAction.Exists,10],[r.AttributeAction.Equals,8],[r.AttributeAction.Not,7],[r.AttributeAction.Start,6],[r.AttributeAction.End,6],[r.AttributeAction.Any,5]]);function a(e){var t,n,l=null!==(t=o.get(e.type))&&void 0!==t?t:-1;return e.type===r.SelectorType.Attribute?(l=null!==(n=i.get(e.action))&&void 0!==n?n:4,e.action===r.AttributeAction.Equals&&"id"===e.name&&(l=9),e.ignoreCase&&(l>>=1)):e.type===r.SelectorType.Pseudo&&(e.data?"has"===e.name||"contains"===e.name?l=0:Array.isArray(e.data)?(l=Math.min.apply(Math,e.data.map((function(e){return Math.min.apply(Math,e.map(a))}))))<0&&(l=0):l=2:l=3),l}t.default=function(e){for(var t=e.map(a),n=1;n=0&&r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.attributeNames=t.elementNames=void 0,t.elementNames=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((function(e){return[e.toLowerCase(),e]}))),t.attributeNames=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((function(e){return[e.toLowerCase(),e]})))},7974:function(e,t,n){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n");case l.Comment:return"\x3c!--".concat(e.data,"--\x3e");case l.CDATA:return function(e){return"")}(e);case l.Script:case l.Style:case l.Tag:return function(e,t){var n;"foreign"===t.xmlMode&&(e.name=null!==(n=u.elementNames.get(e.name))&&void 0!==n?n:e.name,e.parent&&g.has(e.parent.name)&&(t=r(r({},t),{xmlMode:!1}))),!t.xmlMode&&m.has(e.name)&&(t=r(r({},t),{xmlMode:"foreign"}));var o="<".concat(e.name),i=function(e,t){var n;if(e){var r=!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)?d:t.xmlMode||"utf8"!==t.encodeEntities?s.encodeXML:s.escapeAttribute;return Object.keys(e).map((function(n){var o,i,a=null!==(o=e[n])&&void 0!==o?o:"";return"foreign"===t.xmlMode&&(n=null!==(i=u.attributeNames.get(n))&&void 0!==i?i:n),t.emptyAttrs||t.xmlMode||""!==a?"".concat(n,'="').concat(r(a),'"'):n})).join(" ")}}(e.attribs,t);return i&&(o+=" ".concat(i)),0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&f.has(e.name))?(t.xmlMode||(o+=" "),o+="/>"):(o+=">",e.children.length>0&&(o+=p(e.children,t)),!t.xmlMode&&f.has(e.name)||(o+=""))),o}(e,t);case l.Text:return function(e,t){var n,r=e.data||"";return!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)||!t.xmlMode&&e.parent&&c.has(e.parent.name)||(r=t.xmlMode||"utf8"!==t.encodeEntities?(0,s.encodeXML)(r):(0,s.escapeText)(r)),r}(e,t)}}t.render=p,t.default=p;var g=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),m=new Set(["svg","math"])},4932:(e,t)=>{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.Doctype=t.CDATA=t.Tag=t.Style=t.Script=t.Comment=t.Directive=t.Text=t.Root=t.isTag=t.ElementType=void 0,function(e){e.Root="root",e.Text="text",e.Directive="directive",e.Comment="comment",e.Script="script",e.Style="style",e.Tag="tag",e.CDATA="cdata",e.Doctype="doctype"}(n=t.ElementType||(t.ElementType={})),t.isTag=function(e){return e.type===n.Tag||e.type===n.Script||e.type===n.Style},t.Root=n.Root,t.Text=n.Text,t.Directive=n.Directive,t.Comment=n.Comment,t.Script=n.Script,t.Style=n.Style,t.Tag=n.Tag,t.CDATA=n.CDATA,t.Doctype=n.Doctype},2465:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getFeed=void 0;var r=n(2849),o=n(4420);t.getFeed=function(e){var t=s(d,e);return t?"feed"===t.name?function(e){var t,n=e.children,r={type:"atom",items:(0,o.getElementsByTagName)("entry",n).map((function(e){var t,n=e.children,r={media:l(n)};c(r,"id","id",n),c(r,"title","title",n);var o=null===(t=s("link",n))||void 0===t?void 0:t.attribs.href;o&&(r.link=o);var i=u("summary",n)||u("content",n);i&&(r.description=i);var a=u("updated",n);return a&&(r.pubDate=new Date(a)),r}))};c(r,"id","id",n),c(r,"title","title",n);var i=null===(t=s("link",n))||void 0===t?void 0:t.attribs.href;i&&(r.link=i),c(r,"description","subtitle",n);var a=u("updated",n);return a&&(r.updated=new Date(a)),c(r,"author","email",n,!0),r}(t):function(e){var t,n,r=null!==(n=null===(t=s("channel",e.children))||void 0===t?void 0:t.children)&&void 0!==n?n:[],i={type:e.name.substr(0,3),id:"",items:(0,o.getElementsByTagName)("item",e.children).map((function(e){var t=e.children,n={media:l(t)};c(n,"id","guid",t),c(n,"title","title",t),c(n,"link","link",t),c(n,"description","description",t);var r=u("pubDate",t);return r&&(n.pubDate=new Date(r)),n}))};c(i,"title","title",r),c(i,"link","link",r),c(i,"description","description",r);var a=u("lastBuildDate",r);return a&&(i.updated=new Date(a)),c(i,"author","managingEditor",r,!0),i}(t):null};var i=["url","type","lang"],a=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function l(e){return(0,o.getElementsByTagName)("media:content",e).map((function(e){for(var t=e.attribs,n={medium:t.medium,isDefault:!!t.isDefault},r=0,o=i;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.uniqueSort=t.compareDocumentPosition=t.DocumentPosition=t.removeSubsets=void 0;var r,o=n(9959);function i(e,t){var n=[],i=[];if(e===t)return 0;for(var a=(0,o.hasChildren)(e)?e:e.parent;a;)n.unshift(a),a=a.parent;for(a=(0,o.hasChildren)(t)?t:t.parent;a;)i.unshift(a),a=a.parent;for(var l=Math.min(n.length,i.length),s=0;sc.indexOf(f)?u===t?r.FOLLOWING|r.CONTAINED_BY:r.FOLLOWING:u===e?r.PRECEDING|r.CONTAINS:r.PRECEDING}t.removeSubsets=function(e){for(var t=e.length;--t>=0;){var n=e[t];if(t>0&&e.lastIndexOf(n,t-1)>=0)e.splice(t,1);else for(var r=n.parent;r;r=r.parent)if(e.includes(r)){e.splice(t,1);break}}return e},function(e){e[e.DISCONNECTED=1]="DISCONNECTED",e[e.PRECEDING=2]="PRECEDING",e[e.FOLLOWING=4]="FOLLOWING",e[e.CONTAINS=8]="CONTAINS",e[e.CONTAINED_BY=16]="CONTAINED_BY"}(r=t.DocumentPosition||(t.DocumentPosition={})),t.compareDocumentPosition=i,t.uniqueSort=function(e){return(e=e.filter((function(e,t,n){return!n.includes(e,t+1)}))).sort((function(e,t){var n=i(e,t);return n&r.PRECEDING?-1:n&r.FOLLOWING?1:0})),e}},3926:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var o=Object.getOwnPropertyDescriptor(t,n);o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,o)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),t.hasChildren=t.isDocument=t.isComment=t.isText=t.isCDATA=t.isTag=void 0,o(n(2849),t),o(n(8861),t),o(n(6860),t),o(n(5358),t),o(n(4420),t),o(n(765),t),o(n(2465),t);var i=n(9959);Object.defineProperty(t,"isTag",{enumerable:!0,get:function(){return i.isTag}}),Object.defineProperty(t,"isCDATA",{enumerable:!0,get:function(){return i.isCDATA}}),Object.defineProperty(t,"isText",{enumerable:!0,get:function(){return i.isText}}),Object.defineProperty(t,"isComment",{enumerable:!0,get:function(){return i.isComment}}),Object.defineProperty(t,"isDocument",{enumerable:!0,get:function(){return i.isDocument}}),Object.defineProperty(t,"hasChildren",{enumerable:!0,get:function(){return i.hasChildren}})},4420:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getElementsByTagType=t.getElementsByTagName=t.getElementById=t.getElements=t.testElement=void 0;var r=n(9959),o=n(5358),i={tag_name:function(e){return"function"==typeof e?function(t){return(0,r.isTag)(t)&&e(t.name)}:"*"===e?r.isTag:function(t){return(0,r.isTag)(t)&&t.name===e}},tag_type:function(e){return"function"==typeof e?function(t){return e(t.type)}:function(t){return t.type===e}},tag_contains:function(e){return"function"==typeof e?function(t){return(0,r.isText)(t)&&e(t.data)}:function(t){return(0,r.isText)(t)&&t.data===e}}};function a(e,t){return"function"==typeof t?function(n){return(0,r.isTag)(n)&&t(n.attribs[e])}:function(n){return(0,r.isTag)(n)&&n.attribs[e]===t}}function l(e,t){return function(n){return e(n)||t(n)}}function s(e){var t=Object.keys(e).map((function(t){var n=e[t];return Object.prototype.hasOwnProperty.call(i,t)?i[t](n):a(t,n)}));return 0===t.length?null:t.reduce(l)}t.testElement=function(e,t){var n=s(e);return!n||n(t)},t.getElements=function(e,t,n,r){void 0===r&&(r=1/0);var i=s(e);return i?(0,o.filter)(i,t,n,r):[]},t.getElementById=function(e,t,n){return void 0===n&&(n=!0),Array.isArray(t)||(t=[t]),(0,o.findOne)(a("id",e),t,n)},t.getElementsByTagName=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),(0,o.filter)(i.tag_name(e),t,n,r)},t.getElementsByTagType=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),(0,o.filter)(i.tag_type(e),t,n,r)}},6860:(e,t)=>{"use strict";function n(e){if(e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent){var t=e.parent.children;t.splice(t.lastIndexOf(e),1)}}Object.defineProperty(t,"__esModule",{value:!0}),t.prepend=t.prependChild=t.append=t.appendChild=t.replaceElement=t.removeElement=void 0,t.removeElement=n,t.replaceElement=function(e,t){var n=t.prev=e.prev;n&&(n.next=t);var r=t.next=e.next;r&&(r.prev=t);var o=t.parent=e.parent;if(o){var i=o.children;i[i.lastIndexOf(e)]=t,e.parent=null}},t.appendChild=function(e,t){if(n(t),t.next=null,t.parent=e,e.children.push(t)>1){var r=e.children[e.children.length-2];r.next=t,t.prev=r}else t.prev=null},t.append=function(e,t){n(t);var r=e.parent,o=e.next;if(t.next=o,t.prev=e,e.next=t,t.parent=r,o){if(o.prev=t,r){var i=r.children;i.splice(i.lastIndexOf(o),0,t)}}else r&&r.children.push(t)},t.prependChild=function(e,t){if(n(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)){var r=e.children[1];r.prev=t,t.next=r}else t.next=null},t.prepend=function(e,t){n(t);var r=e.parent;if(r){var o=r.children;o.splice(o.indexOf(e),0,t)}e.prev&&(e.prev.next=t),t.parent=r,t.prev=e.prev,t.next=e,e.prev=t}},5358:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.findAll=t.existsOne=t.findOne=t.findOneChild=t.find=t.filter=void 0;var r=n(9959);function o(e,t,n,i){for(var a=[],l=0,s=t;l0){var c=o(e,u.children,n,i);if(a.push.apply(a,c),(i-=c.length)<=0)break}}return a}t.filter=function(e,t,n,r){return void 0===n&&(n=!0),void 0===r&&(r=1/0),Array.isArray(t)||(t=[t]),o(e,t,n,r)},t.find=o,t.findOneChild=function(e,t){return t.find(e)},t.findOne=function e(t,n,o){void 0===o&&(o=!0);for(var i=null,a=0;a0&&(i=e(t,l.children,!0)))}return i},t.existsOne=function e(t,n){return n.some((function(n){return(0,r.isTag)(n)&&(t(n)||n.children.length>0&&e(t,n.children))}))},t.findAll=function(e,t){for(var n,o,i=[],a=t.filter(r.isTag);o=a.shift();){var l=null===(n=o.children)||void 0===n?void 0:n.filter(r.isTag);l&&l.length>0&&a.unshift.apply(a,l),e(o)&&i.push(o)}return i}},2849:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.innerText=t.textContent=t.getText=t.getInnerHTML=t.getOuterHTML=void 0;var o=n(9959),i=r(n(7974)),a=n(4932);function l(e,t){return(0,i.default)(e,t)}t.getOuterHTML=l,t.getInnerHTML=function(e,t){return(0,o.hasChildren)(e)?e.children.map((function(e){return l(e,t)})).join(""):""},t.getText=function e(t){return Array.isArray(t)?t.map(e).join(""):(0,o.isTag)(t)?"br"===t.name?"\n":e(t.children):(0,o.isCDATA)(t)?e(t.children):(0,o.isText)(t)?t.data:""},t.textContent=function e(t){return Array.isArray(t)?t.map(e).join(""):(0,o.hasChildren)(t)&&!(0,o.isComment)(t)?e(t.children):(0,o.isText)(t)?t.data:""},t.innerText=function e(t){return Array.isArray(t)?t.map(e).join(""):(0,o.hasChildren)(t)&&(t.type===a.ElementType.Tag||(0,o.isCDATA)(t))?e(t.children):(0,o.isText)(t)?t.data:""}},8861:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.prevElementSibling=t.nextElementSibling=t.getName=t.hasAttrib=t.getAttributeValue=t.getSiblings=t.getParent=t.getChildren=void 0;var r=n(9959);function o(e){return(0,r.hasChildren)(e)?e.children:[]}function i(e){return e.parent||null}t.getChildren=o,t.getParent=i,t.getSiblings=function(e){var t=i(e);if(null!=t)return o(t);for(var n=[e],r=e.prev,a=e.next;null!=r;)n.unshift(r),r=r.prev;for(;null!=a;)n.push(a),a=a.next;return n},t.getAttributeValue=function(e,t){var n;return null===(n=e.attribs)||void 0===n?void 0:n[t]},t.hasAttrib=function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},t.getName=function(e){return e.name},t.nextElementSibling=function(e){for(var t=e.next;null!==t&&!(0,r.isTag)(t);)t=t.next;return t},t.prevElementSibling=function(e){for(var t=e.prev;null!==t&&!(0,r.isTag)(t);)t=t.prev;return t}},4753:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXML=t.decodeHTMLStrict=t.decodeHTML=t.determineBranch=t.BinTrieFlags=t.fromCodePoint=t.replaceCodePoint=t.decodeCodePoint=t.xmlDecodeTree=t.htmlDecodeTree=void 0;var o=r(n(3561));t.htmlDecodeTree=o.default;var i=r(n(2491));t.xmlDecodeTree=i.default;var a=r(n(5041));t.decodeCodePoint=a.default;var l,s,u=n(5041);function c(e){return function(t,n){for(var r="",o=0,i=0;(i=t.indexOf("&",i))>=0;)if(r+=t.slice(o,i),o=i,i+=1,t.charCodeAt(i)!==l.NUM){for(var u=0,c=1,f=0,p=e[f];i>14)-1))break;f+=g}}0!==u&&(r+=1==(g=(e[u]&s.VALUE_LENGTH)>>14)?String.fromCharCode(e[u]&~s.VALUE_LENGTH):2===g?String.fromCharCode(e[u+1]):String.fromCharCode(e[u+1],e[u+2]),o=i-c+1)}else{var m=i+1,v=10,b=t.charCodeAt(m);(b|l.To_LOWER_BIT)===l.LOWER_X&&(v=16,i+=1,m+=1);do{b=t.charCodeAt(++i)}while(b>=l.ZERO&&b<=l.NINE||16===v&&(b|l.To_LOWER_BIT)>=l.LOWER_A&&(b|l.To_LOWER_BIT)<=l.LOWER_F);if(m!==i){var y=t.substring(m,i),A=parseInt(y,v);if(t.charCodeAt(i)===l.SEMI)i+=1;else if(n)continue;r+=(0,a.default)(A),o=i}}return r+t.slice(o)}}function d(e,t,n,r){var o=(t&s.BRANCH_LENGTH)>>7,i=t&s.JUMP_TABLE;if(0===o)return 0!==i&&r===i?n:-1;if(i){var a=r-i;return a<0||a>o?-1:e[n+a]-1}for(var l=n,u=l+o-1;l<=u;){var c=l+u>>>1,d=e[c];if(dr))return e[c+o];u=c-1}}return-1}Object.defineProperty(t,"replaceCodePoint",{enumerable:!0,get:function(){return u.replaceCodePoint}}),Object.defineProperty(t,"fromCodePoint",{enumerable:!0,get:function(){return u.fromCodePoint}}),function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.To_LOWER_BIT=32]="To_LOWER_BIT"}(l||(l={})),function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(s=t.BinTrieFlags||(t.BinTrieFlags={})),t.determineBranch=d;var f=c(o.default),p=c(i.default);t.decodeHTML=function(e){return f(e,!1)},t.decodeHTMLStrict=function(e){return f(e,!0)},t.decodeXML=function(e){return p(e,!0)}},5041:(e,t)=>{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.replaceCodePoint=t.fromCodePoint=void 0;var r=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);function o(e){var t;return e>=55296&&e<=57343||e>1114111?65533:null!==(t=r.get(e))&&void 0!==t?t:e}t.fromCodePoint=null!==(n=String.fromCodePoint)&&void 0!==n?n:function(e){var t="";return e>65535&&(e-=65536,t+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t+String.fromCharCode(e)},t.replaceCodePoint=o,t.default=function(e){return(0,t.fromCodePoint)(o(e))}},6913:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.encodeNonAsciiHTML=t.encodeHTML=void 0;var o=r(n(2208)),i=n(8168),a=/[\t\n!-,./:-@[-`\f{-}$\x80-\uFFFF]/g;function l(e,t){for(var n,r="",a=0;null!==(n=e.exec(t));){var l=n.index;r+=t.substring(a,l);var s=t.charCodeAt(l),u=o.default.get(s);if("object"==typeof u){if(l+1{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.escapeText=t.escapeAttribute=t.escapeUTF8=t.escape=t.encodeXML=t.getCodePoint=t.xmlReplacer=void 0,t.xmlReplacer=/["&'<>$\x80-\uFFFF]/g;var n=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]);function r(e){for(var r,o="",i=0;null!==(r=t.xmlReplacer.exec(e));){var a=r.index,l=e.charCodeAt(a),s=n.get(l);void 0!==s?(o+=e.substring(i,a)+s,i=a+1):(o+="".concat(e.substring(i,a),"&#x").concat((0,t.getCodePoint)(e,a).toString(16),";"),i=t.xmlReplacer.lastIndex+=Number(55296==(64512&l)))}return o+e.substr(i)}function o(e,t){return function(n){for(var r,o=0,i="";r=e.exec(n);)o!==r.index&&(i+=n.substring(o,r.index)),i+=t.get(r[0].charCodeAt(0)),o=r.index+1;return i+n.substring(o)}}t.getCodePoint=null!=String.prototype.codePointAt?function(e,t){return e.codePointAt(t)}:function(e,t){return 55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t)},t.encodeXML=r,t.escape=r,t.escapeUTF8=o(/[&<>'"]/g,n),t.escapeAttribute=o(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),t.escapeText=o(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]))},3561:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array([7489,60,213,305,650,1181,1403,1488,1653,1758,1954,2006,2063,2634,2705,3489,3693,3849,3878,4298,4648,4833,5141,5277,5315,5343,5413,0,0,0,0,0,0,5483,5837,6541,7186,7645,8062,8288,8624,8845,9152,9211,9282,10276,10514,11528,11848,12238,12310,12986,13881,14252,14590,14888,14961,15072,15150,2048,69,77,97,98,99,102,103,108,109,110,111,112,114,115,116,117,92,98,102,109,115,127,132,139,144,149,152,166,179,185,200,207,108,105,103,32827,198,16582,80,32827,38,16422,99,117,116,101,32827,193,16577,114,101,118,101,59,16642,256,105,121,120,125,114,99,32827,194,16578,59,17424,114,59,49152,55349,56580,114,97,118,101,32827,192,16576,112,104,97,59,17297,97,99,114,59,16640,100,59,27219,256,103,112,157,161,111,110,59,16644,102,59,49152,55349,56632,112,108,121,70,117,110,99,116,105,111,110,59,24673,105,110,103,32827,197,16581,256,99,115,190,195,114,59,49152,55349,56476,105,103,110,59,25172,105,108,100,101,32827,195,16579,109,108,32827,196,16580,1024,97,99,101,102,111,114,115,117,229,251,254,279,284,290,295,298,256,99,114,234,242,107,115,108,97,115,104,59,25110,374,246,248,59,27367,101,100,59,25350,121,59,17425,384,99,114,116,261,267,276,97,117,115,101,59,25141,110,111,117,108,108,105,115,59,24876,97,59,17298,114,59,49152,55349,56581,112,102,59,49152,55349,56633,101,118,101,59,17112,99,242,275,109,112,101,113,59,25166,1792,72,79,97,99,100,101,102,104,105,108,111,114,115,117,333,337,342,384,414,418,437,439,442,476,533,627,632,638,99,121,59,17447,80,89,32827,169,16553,384,99,112,121,349,354,378,117,116,101,59,16646,256,59,105,359,360,25298,116,97,108,68,105,102,102,101,114,101,110,116,105,97,108,68,59,24901,108,101,121,115,59,24877,512,97,101,105,111,393,398,404,408,114,111,110,59,16652,100,105,108,32827,199,16583,114,99,59,16648,110,105,110,116,59,25136,111,116,59,16650,256,100,110,423,429,105,108,108,97,59,16568,116,101,114,68,111,116,59,16567,242,383,105,59,17319,114,99,108,101,512,68,77,80,84,455,459,465,470,111,116,59,25241,105,110,117,115,59,25238,108,117,115,59,25237,105,109,101,115,59,25239,111,256,99,115,482,504,107,119,105,115,101,67,111,110,116,111,117,114,73,110,116,101,103,114,97,108,59,25138,101,67,117,114,108,121,256,68,81,515,527,111,117,98,108,101,81,117,111,116,101,59,24605,117,111,116,101,59,24601,512,108,110,112,117,542,552,583,597,111,110,256,59,101,549,550,25143,59,27252,384,103,105,116,559,566,570,114,117,101,110,116,59,25185,110,116,59,25135,111,117,114,73,110,116,101,103,114,97,108,59,25134,256,102,114,588,590,59,24834,111,100,117,99,116,59,25104,110,116,101,114,67,108,111,99,107,119,105,115,101,67,111,110,116,111,117,114,73,110,116,101,103,114,97,108,59,25139,111,115,115,59,27183,99,114,59,49152,55349,56478,112,256,59,67,644,645,25299,97,112,59,25165,1408,68,74,83,90,97,99,101,102,105,111,115,672,684,688,692,696,715,727,737,742,819,1165,256,59,111,377,677,116,114,97,104,100,59,26897,99,121,59,17410,99,121,59,17413,99,121,59,17423,384,103,114,115,703,708,711,103,101,114,59,24609,114,59,24993,104,118,59,27364,256,97,121,720,725,114,111,110,59,16654,59,17428,108,256,59,116,733,734,25095,97,59,17300,114,59,49152,55349,56583,256,97,102,747,807,256,99,109,752,802,114,105,116,105,99,97,108,512,65,68,71,84,768,774,790,796,99,117,116,101,59,16564,111,372,779,781,59,17113,98,108,101,65,99,117,116,101,59,17117,114,97,118,101,59,16480,105,108,100,101,59,17116,111,110,100,59,25284,102,101,114,101,110,116,105,97,108,68,59,24902,1136,829,0,0,0,834,852,0,1029,102,59,49152,55349,56635,384,59,68,69,840,841,845,16552,111,116,59,24796,113,117,97,108,59,25168,98,108,101,768,67,68,76,82,85,86,867,882,898,975,994,1016,111,110,116,111,117,114,73,110,116,101,103,114,97,236,569,111,628,889,0,0,891,187,841,110,65,114,114,111,119,59,25043,256,101,111,903,932,102,116,384,65,82,84,912,918,929,114,114,111,119,59,25040,105,103,104,116,65,114,114,111,119,59,25044,101,229,714,110,103,256,76,82,939,964,101,102,116,256,65,82,947,953,114,114,111,119,59,26616,105,103,104,116,65,114,114,111,119,59,26618,105,103,104,116,65,114,114,111,119,59,26617,105,103,104,116,256,65,84,984,990,114,114,111,119,59,25042,101,101,59,25256,112,577,1001,0,0,1007,114,114,111,119,59,25041,111,119,110,65,114,114,111,119,59,25045,101,114,116,105,99,97,108,66,97,114,59,25125,110,768,65,66,76,82,84,97,1042,1066,1072,1118,1151,892,114,114,111,119,384,59,66,85,1053,1054,1058,24979,97,114,59,26899,112,65,114,114,111,119,59,25077,114,101,118,101,59,17169,101,102,116,722,1082,0,1094,0,1104,105,103,104,116,86,101,99,116,111,114,59,26960,101,101,86,101,99,116,111,114,59,26974,101,99,116,111,114,256,59,66,1113,1114,25021,97,114,59,26966,105,103,104,116,468,1127,0,1137,101,101,86,101,99,116,111,114,59,26975,101,99,116,111,114,256,59,66,1146,1147,25025,97,114,59,26967,101,101,256,59,65,1158,1159,25252,114,114,111,119,59,24999,256,99,116,1170,1175,114,59,49152,55349,56479,114,111,107,59,16656,2048,78,84,97,99,100,102,103,108,109,111,112,113,115,116,117,120,1213,1216,1220,1227,1246,1250,1255,1262,1269,1313,1327,1334,1362,1373,1376,1381,71,59,16714,72,32827,208,16592,99,117,116,101,32827,201,16585,384,97,105,121,1234,1239,1244,114,111,110,59,16666,114,99,32827,202,16586,59,17453,111,116,59,16662,114,59,49152,55349,56584,114,97,118,101,32827,200,16584,101,109,101,110,116,59,25096,256,97,112,1274,1278,99,114,59,16658,116,121,595,1286,0,0,1298,109,97,108,108,83,113,117,97,114,101,59,26107,101,114,121,83,109,97,108,108,83,113,117,97,114,101,59,26027,256,103,112,1318,1322,111,110,59,16664,102,59,49152,55349,56636,115,105,108,111,110,59,17301,117,256,97,105,1340,1353,108,256,59,84,1346,1347,27253,105,108,100,101,59,25154,108,105,98,114,105,117,109,59,25036,256,99,105,1367,1370,114,59,24880,109,59,27251,97,59,17303,109,108,32827,203,16587,256,105,112,1386,1391,115,116,115,59,25091,111,110,101,110,116,105,97,108,69,59,24903,640,99,102,105,111,115,1413,1416,1421,1458,1484,121,59,17444,114,59,49152,55349,56585,108,108,101,100,595,1431,0,0,1443,109,97,108,108,83,113,117,97,114,101,59,26108,101,114,121,83,109,97,108,108,83,113,117,97,114,101,59,26026,880,1466,0,1471,0,0,1476,102,59,49152,55349,56637,65,108,108,59,25088,114,105,101,114,116,114,102,59,24881,99,242,1483,1536,74,84,97,98,99,100,102,103,111,114,115,116,1512,1516,1519,1530,1536,1554,1558,1563,1565,1571,1644,1650,99,121,59,17411,32827,62,16446,109,109,97,256,59,100,1527,1528,17299,59,17372,114,101,118,101,59,16670,384,101,105,121,1543,1548,1552,100,105,108,59,16674,114,99,59,16668,59,17427,111,116,59,16672,114,59,49152,55349,56586,59,25305,112,102,59,49152,55349,56638,101,97,116,101,114,768,69,70,71,76,83,84,1589,1604,1614,1622,1627,1638,113,117,97,108,256,59,76,1598,1599,25189,101,115,115,59,25307,117,108,108,69,113,117,97,108,59,25191,114,101,97,116,101,114,59,27298,101,115,115,59,25207,108,97,110,116,69,113,117,97,108,59,27262,105,108,100,101,59,25203,99,114,59,49152,55349,56482,59,25195,1024,65,97,99,102,105,111,115,117,1669,1675,1686,1691,1694,1706,1726,1738,82,68,99,121,59,17450,256,99,116,1680,1684,101,107,59,17095,59,16478,105,114,99,59,16676,114,59,24844,108,98,101,114,116,83,112,97,99,101,59,24843,496,1711,0,1714,102,59,24845,105,122,111,110,116,97,108,76,105,110,101,59,25856,256,99,116,1731,1733,242,1705,114,111,107,59,16678,109,112,324,1744,1752,111,119,110,72,117,109,240,303,113,117,97,108,59,25167,1792,69,74,79,97,99,100,102,103,109,110,111,115,116,117,1786,1790,1795,1799,1806,1818,1822,1825,1832,1860,1912,1931,1935,1941,99,121,59,17429,108,105,103,59,16690,99,121,59,17409,99,117,116,101,32827,205,16589,256,105,121,1811,1816,114,99,32827,206,16590,59,17432,111,116,59,16688,114,59,24849,114,97,118,101,32827,204,16588,384,59,97,112,1824,1839,1855,256,99,103,1844,1847,114,59,16682,105,110,97,114,121,73,59,24904,108,105,101,243,989,500,1865,0,1890,256,59,101,1869,1870,25132,256,103,114,1875,1880,114,97,108,59,25131,115,101,99,116,105,111,110,59,25282,105,115,105,98,108,101,256,67,84,1900,1906,111,109,109,97,59,24675,105,109,101,115,59,24674,384,103,112,116,1919,1923,1928,111,110,59,16686,102,59,49152,55349,56640,97,59,17305,99,114,59,24848,105,108,100,101,59,16680,491,1946,0,1950,99,121,59,17414,108,32827,207,16591,640,99,102,111,115,117,1964,1975,1980,1986,2e3,256,105,121,1969,1973,114,99,59,16692,59,17433,114,59,49152,55349,56589,112,102,59,49152,55349,56641,483,1991,0,1996,114,59,49152,55349,56485,114,99,121,59,17416,107,99,121,59,17412,896,72,74,97,99,102,111,115,2020,2024,2028,2033,2045,2050,2056,99,121,59,17445,99,121,59,17420,112,112,97,59,17306,256,101,121,2038,2043,100,105,108,59,16694,59,17434,114,59,49152,55349,56590,112,102,59,49152,55349,56642,99,114,59,49152,55349,56486,1408,74,84,97,99,101,102,108,109,111,115,116,2085,2089,2092,2128,2147,2483,2488,2503,2509,2615,2631,99,121,59,17417,32827,60,16444,640,99,109,110,112,114,2103,2108,2113,2116,2125,117,116,101,59,16697,98,100,97,59,17307,103,59,26602,108,97,99,101,116,114,102,59,24850,114,59,24990,384,97,101,121,2135,2140,2145,114,111,110,59,16701,100,105,108,59,16699,59,17435,256,102,115,2152,2416,116,1280,65,67,68,70,82,84,85,86,97,114,2174,2217,2225,2272,2278,2300,2351,2395,912,2410,256,110,114,2179,2191,103,108,101,66,114,97,99,107,101,116,59,26600,114,111,119,384,59,66,82,2201,2202,2206,24976,97,114,59,25060,105,103,104,116,65,114,114,111,119,59,25030,101,105,108,105,110,103,59,25352,111,501,2231,0,2243,98,108,101,66,114,97,99,107,101,116,59,26598,110,468,2248,0,2258,101,101,86,101,99,116,111,114,59,26977,101,99,116,111,114,256,59,66,2267,2268,25027,97,114,59,26969,108,111,111,114,59,25354,105,103,104,116,256,65,86,2287,2293,114,114,111,119,59,24980,101,99,116,111,114,59,26958,256,101,114,2305,2327,101,384,59,65,86,2313,2314,2320,25251,114,114,111,119,59,24996,101,99,116,111,114,59,26970,105,97,110,103,108,101,384,59,66,69,2340,2341,2345,25266,97,114,59,27087,113,117,97,108,59,25268,112,384,68,84,86,2359,2370,2380,111,119,110,86,101,99,116,111,114,59,26961,101,101,86,101,99,116,111,114,59,26976,101,99,116,111,114,256,59,66,2390,2391,25023,97,114,59,26968,101,99,116,111,114,256,59,66,2405,2406,25020,97,114,59,26962,105,103,104,116,225,924,115,768,69,70,71,76,83,84,2430,2443,2453,2461,2466,2477,113,117,97,108,71,114,101,97,116,101,114,59,25306,117,108,108,69,113,117,97,108,59,25190,114,101,97,116,101,114,59,25206,101,115,115,59,27297,108,97,110,116,69,113,117,97,108,59,27261,105,108,100,101,59,25202,114,59,49152,55349,56591,256,59,101,2493,2494,25304,102,116,97,114,114,111,119,59,25050,105,100,111,116,59,16703,384,110,112,119,2516,2582,2587,103,512,76,82,108,114,2526,2551,2562,2576,101,102,116,256,65,82,2534,2540,114,114,111,119,59,26613,105,103,104,116,65,114,114,111,119,59,26615,105,103,104,116,65,114,114,111,119,59,26614,101,102,116,256,97,114,947,2570,105,103,104,116,225,959,105,103,104,116,225,970,102,59,49152,55349,56643,101,114,256,76,82,2594,2604,101,102,116,65,114,114,111,119,59,24985,105,103,104,116,65,114,114,111,119,59,24984,384,99,104,116,2622,2624,2626,242,2124,59,25008,114,111,107,59,16705,59,25194,1024,97,99,101,102,105,111,115,117,2650,2653,2656,2679,2684,2693,2699,2702,112,59,26885,121,59,17436,256,100,108,2661,2671,105,117,109,83,112,97,99,101,59,24671,108,105,110,116,114,102,59,24883,114,59,49152,55349,56592,110,117,115,80,108,117,115,59,25107,112,102,59,49152,55349,56644,99,242,2678,59,17308,1152,74,97,99,101,102,111,115,116,117,2723,2727,2733,2752,2836,2841,3473,3479,3486,99,121,59,17418,99,117,116,101,59,16707,384,97,101,121,2740,2745,2750,114,111,110,59,16711,100,105,108,59,16709,59,17437,384,103,115,119,2759,2800,2830,97,116,105,118,101,384,77,84,86,2771,2783,2792,101,100,105,117,109,83,112,97,99,101,59,24587,104,105,256,99,110,2790,2776,235,2777,101,114,121,84,104,105,238,2777,116,101,100,256,71,76,2808,2822,114,101,97,116,101,114,71,114,101,97,116,101,242,1651,101,115,115,76,101,115,243,2632,76,105,110,101,59,16394,114,59,49152,55349,56593,512,66,110,112,116,2850,2856,2871,2874,114,101,97,107,59,24672,66,114,101,97,107,105,110,103,83,112,97,99,101,59,16544,102,59,24853,1664,59,67,68,69,71,72,76,78,80,82,83,84,86,2901,2902,2922,2940,2977,3051,3076,3166,3204,3238,3288,3425,3461,27372,256,111,117,2907,2916,110,103,114,117,101,110,116,59,25186,112,67,97,112,59,25197,111,117,98,108,101,86,101,114,116,105,99,97,108,66,97,114,59,25126,384,108,113,120,2947,2954,2971,101,109,101,110,116,59,25097,117,97,108,256,59,84,2962,2963,25184,105,108,100,101,59,49152,8770,824,105,115,116,115,59,25092,114,101,97,116,101,114,896,59,69,70,71,76,83,84,2998,2999,3005,3017,3027,3032,3045,25199,113,117,97,108,59,25201,117,108,108,69,113,117,97,108,59,49152,8807,824,114,101,97,116,101,114,59,49152,8811,824,101,115,115,59,25209,108,97,110,116,69,113,117,97,108,59,49152,10878,824,105,108,100,101,59,25205,117,109,112,324,3058,3069,111,119,110,72,117,109,112,59,49152,8782,824,113,117,97,108,59,49152,8783,824,101,256,102,115,3082,3111,116,84,114,105,97,110,103,108,101,384,59,66,69,3098,3099,3105,25322,97,114,59,49152,10703,824,113,117,97,108,59,25324,115,768,59,69,71,76,83,84,3125,3126,3132,3140,3147,3160,25198,113,117,97,108,59,25200,114,101,97,116,101,114,59,25208,101,115,115,59,49152,8810,824,108,97,110,116,69,113,117,97,108,59,49152,10877,824,105,108,100,101,59,25204,101,115,116,101,100,256,71,76,3176,3193,114,101,97,116,101,114,71,114,101,97,116,101,114,59,49152,10914,824,101,115,115,76,101,115,115,59,49152,10913,824,114,101,99,101,100,101,115,384,59,69,83,3218,3219,3227,25216,113,117,97,108,59,49152,10927,824,108,97,110,116,69,113,117,97,108,59,25312,256,101,105,3243,3257,118,101,114,115,101,69,108,101,109,101,110,116,59,25100,103,104,116,84,114,105,97,110,103,108,101,384,59,66,69,3275,3276,3282,25323,97,114,59,49152,10704,824,113,117,97,108,59,25325,256,113,117,3293,3340,117,97,114,101,83,117,256,98,112,3304,3321,115,101,116,256,59,69,3312,3315,49152,8847,824,113,117,97,108,59,25314,101,114,115,101,116,256,59,69,3331,3334,49152,8848,824,113,117,97,108,59,25315,384,98,99,112,3347,3364,3406,115,101,116,256,59,69,3355,3358,49152,8834,8402,113,117,97,108,59,25224,99,101,101,100,115,512,59,69,83,84,3378,3379,3387,3398,25217,113,117,97,108,59,49152,10928,824,108,97,110,116,69,113,117,97,108,59,25313,105,108,100,101,59,49152,8831,824,101,114,115,101,116,256,59,69,3416,3419,49152,8835,8402,113,117,97,108,59,25225,105,108,100,101,512,59,69,70,84,3438,3439,3445,3455,25153,113,117,97,108,59,25156,117,108,108,69,113,117,97,108,59,25159,105,108,100,101,59,25161,101,114,116,105,99,97,108,66,97,114,59,25124,99,114,59,49152,55349,56489,105,108,100,101,32827,209,16593,59,17309,1792,69,97,99,100,102,103,109,111,112,114,115,116,117,118,3517,3522,3529,3541,3547,3552,3559,3580,3586,3616,3618,3634,3647,3652,108,105,103,59,16722,99,117,116,101,32827,211,16595,256,105,121,3534,3539,114,99,32827,212,16596,59,17438,98,108,97,99,59,16720,114,59,49152,55349,56594,114,97,118,101,32827,210,16594,384,97,101,105,3566,3570,3574,99,114,59,16716,103,97,59,17321,99,114,111,110,59,17311,112,102,59,49152,55349,56646,101,110,67,117,114,108,121,256,68,81,3598,3610,111,117,98,108,101,81,117,111,116,101,59,24604,117,111,116,101,59,24600,59,27220,256,99,108,3623,3628,114,59,49152,55349,56490,97,115,104,32827,216,16600,105,364,3639,3644,100,101,32827,213,16597,101,115,59,27191,109,108,32827,214,16598,101,114,256,66,80,3659,3680,256,97,114,3664,3667,114,59,24638,97,99,256,101,107,3674,3676,59,25566,101,116,59,25524,97,114,101,110,116,104,101,115,105,115,59,25564,1152,97,99,102,104,105,108,111,114,115,3711,3719,3722,3727,3730,3732,3741,3760,3836,114,116,105,97,108,68,59,25090,121,59,17439,114,59,49152,55349,56595,105,59,17318,59,17312,117,115,77,105,110,117,115,59,16561,256,105,112,3746,3757,110,99,97,114,101,112,108,97,110,229,1693,102,59,24857,512,59,101,105,111,3769,3770,3808,3812,27323,99,101,100,101,115,512,59,69,83,84,3784,3785,3791,3802,25210,113,117,97,108,59,27311,108,97,110,116,69,113,117,97,108,59,25212,105,108,100,101,59,25214,109,101,59,24627,256,100,112,3817,3822,117,99,116,59,25103,111,114,116,105,111,110,256,59,97,549,3833,108,59,25117,256,99,105,3841,3846,114,59,49152,55349,56491,59,17320,512,85,102,111,115,3857,3862,3867,3871,79,84,32827,34,16418,114,59,49152,55349,56596,112,102,59,24858,99,114,59,49152,55349,56492,1536,66,69,97,99,101,102,104,105,111,114,115,117,3902,3907,3911,3936,3955,4007,4010,4013,4246,4265,4276,4286,97,114,114,59,26896,71,32827,174,16558,384,99,110,114,3918,3923,3926,117,116,101,59,16724,103,59,26603,114,256,59,116,3932,3933,24992,108,59,26902,384,97,101,121,3943,3948,3953,114,111,110,59,16728,100,105,108,59,16726,59,17440,256,59,118,3960,3961,24860,101,114,115,101,256,69,85,3970,3993,256,108,113,3975,3982,101,109,101,110,116,59,25099,117,105,108,105,98,114,105,117,109,59,25035,112,69,113,117,105,108,105,98,114,105,117,109,59,26991,114,187,3961,111,59,17313,103,104,116,1024,65,67,68,70,84,85,86,97,4033,4075,4083,4130,4136,4187,4231,984,256,110,114,4038,4050,103,108,101,66,114,97,99,107,101,116,59,26601,114,111,119,384,59,66,76,4060,4061,4065,24978,97,114,59,25061,101,102,116,65,114,114,111,119,59,25028,101,105,108,105,110,103,59,25353,111,501,4089,0,4101,98,108,101,66,114,97,99,107,101,116,59,26599,110,468,4106,0,4116,101,101,86,101,99,116,111,114,59,26973,101,99,116,111,114,256,59,66,4125,4126,25026,97,114,59,26965,108,111,111,114,59,25355,256,101,114,4141,4163,101,384,59,65,86,4149,4150,4156,25250,114,114,111,119,59,24998,101,99,116,111,114,59,26971,105,97,110,103,108,101,384,59,66,69,4176,4177,4181,25267,97,114,59,27088,113,117,97,108,59,25269,112,384,68,84,86,4195,4206,4216,111,119,110,86,101,99,116,111,114,59,26959,101,101,86,101,99,116,111,114,59,26972,101,99,116,111,114,256,59,66,4226,4227,25022,97,114,59,26964,101,99,116,111,114,256,59,66,4241,4242,25024,97,114,59,26963,256,112,117,4251,4254,102,59,24861,110,100,73,109,112,108,105,101,115,59,26992,105,103,104,116,97,114,114,111,119,59,25051,256,99,104,4281,4284,114,59,24859,59,25009,108,101,68,101,108,97,121,101,100,59,27124,1664,72,79,97,99,102,104,105,109,111,113,115,116,117,4324,4337,4343,4349,4377,4382,4433,4438,4449,4455,4533,4539,4543,256,67,99,4329,4334,72,99,121,59,17449,121,59,17448,70,84,99,121,59,17452,99,117,116,101,59,16730,640,59,97,101,105,121,4360,4361,4366,4371,4375,27324,114,111,110,59,16736,100,105,108,59,16734,114,99,59,16732,59,17441,114,59,49152,55349,56598,111,114,116,512,68,76,82,85,4394,4404,4414,4425,111,119,110,65,114,114,111,119,187,1054,101,102,116,65,114,114,111,119,187,2202,105,103,104,116,65,114,114,111,119,187,4061,112,65,114,114,111,119,59,24977,103,109,97,59,17315,97,108,108,67,105,114,99,108,101,59,25112,112,102,59,49152,55349,56650,626,4461,0,0,4464,116,59,25114,97,114,101,512,59,73,83,85,4475,4476,4489,4527,26017,110,116,101,114,115,101,99,116,105,111,110,59,25235,117,256,98,112,4495,4510,115,101,116,256,59,69,4503,4504,25231,113,117,97,108,59,25233,101,114,115,101,116,256,59,69,4520,4521,25232,113,117,97,108,59,25234,110,105,111,110,59,25236,99,114,59,49152,55349,56494,97,114,59,25286,512,98,99,109,112,4552,4571,4617,4619,256,59,115,4557,4558,25296,101,116,256,59,69,4557,4565,113,117,97,108,59,25222,256,99,104,4576,4613,101,101,100,115,512,59,69,83,84,4589,4590,4596,4607,25211,113,117,97,108,59,27312,108,97,110,116,69,113,117,97,108,59,25213,105,108,100,101,59,25215,84,104,225,3980,59,25105,384,59,101,115,4626,4627,4643,25297,114,115,101,116,256,59,69,4636,4637,25219,113,117,97,108,59,25223,101,116,187,4627,1408,72,82,83,97,99,102,104,105,111,114,115,4670,4676,4681,4693,4702,4721,4726,4767,4802,4808,4817,79,82,78,32827,222,16606,65,68,69,59,24866,256,72,99,4686,4690,99,121,59,17419,121,59,17446,256,98,117,4698,4700,59,16393,59,17316,384,97,101,121,4709,4714,4719,114,111,110,59,16740,100,105,108,59,16738,59,17442,114,59,49152,55349,56599,256,101,105,4731,4745,498,4736,0,4743,101,102,111,114,101,59,25140,97,59,17304,256,99,110,4750,4760,107,83,112,97,99,101,59,49152,8287,8202,83,112,97,99,101,59,24585,108,100,101,512,59,69,70,84,4779,4780,4786,4796,25148,113,117,97,108,59,25155,117,108,108,69,113,117,97,108,59,25157,105,108,100,101,59,25160,112,102,59,49152,55349,56651,105,112,108,101,68,111,116,59,24795,256,99,116,4822,4827,114,59,49152,55349,56495,114,111,107,59,16742,2785,4855,4878,4890,4902,0,4908,4913,0,0,0,0,0,4920,4925,4983,4997,0,5119,5124,5130,5136,256,99,114,4859,4865,117,116,101,32827,218,16602,114,256,59,111,4871,4872,24991,99,105,114,59,26953,114,483,4883,0,4886,121,59,17422,118,101,59,16748,256,105,121,4894,4899,114,99,32827,219,16603,59,17443,98,108,97,99,59,16752,114,59,49152,55349,56600,114,97,118,101,32827,217,16601,97,99,114,59,16746,256,100,105,4929,4969,101,114,256,66,80,4936,4957,256,97,114,4941,4944,114,59,16479,97,99,256,101,107,4951,4953,59,25567,101,116,59,25525,97,114,101,110,116,104,101,115,105,115,59,25565,111,110,256,59,80,4976,4977,25283,108,117,115,59,25230,256,103,112,4987,4991,111,110,59,16754,102,59,49152,55349,56652,1024,65,68,69,84,97,100,112,115,5013,5038,5048,5060,1e3,5074,5079,5107,114,114,111,119,384,59,66,68,4432,5024,5028,97,114,59,26898,111,119,110,65,114,114,111,119,59,25029,111,119,110,65,114,114,111,119,59,24981,113,117,105,108,105,98,114,105,117,109,59,26990,101,101,256,59,65,5067,5068,25253,114,114,111,119,59,24997,111,119,110,225,1011,101,114,256,76,82,5086,5096,101,102,116,65,114,114,111,119,59,24982,105,103,104,116,65,114,114,111,119,59,24983,105,256,59,108,5113,5114,17362,111,110,59,17317,105,110,103,59,16750,99,114,59,49152,55349,56496,105,108,100,101,59,16744,109,108,32827,220,16604,1152,68,98,99,100,101,102,111,115,118,5159,5164,5168,5171,5182,5253,5258,5264,5270,97,115,104,59,25259,97,114,59,27371,121,59,17426,97,115,104,256,59,108,5179,5180,25257,59,27366,256,101,114,5187,5189,59,25281,384,98,116,121,5196,5200,5242,97,114,59,24598,256,59,105,5199,5205,99,97,108,512,66,76,83,84,5217,5221,5226,5236,97,114,59,25123,105,110,101,59,16508,101,112,97,114,97,116,111,114,59,26456,105,108,100,101,59,25152,84,104,105,110,83,112,97,99,101,59,24586,114,59,49152,55349,56601,112,102,59,49152,55349,56653,99,114,59,49152,55349,56497,100,97,115,104,59,25258,640,99,101,102,111,115,5287,5292,5297,5302,5308,105,114,99,59,16756,100,103,101,59,25280,114,59,49152,55349,56602,112,102,59,49152,55349,56654,99,114,59,49152,55349,56498,512,102,105,111,115,5323,5328,5330,5336,114,59,49152,55349,56603,59,17310,112,102,59,49152,55349,56655,99,114,59,49152,55349,56499,1152,65,73,85,97,99,102,111,115,117,5361,5365,5369,5373,5380,5391,5396,5402,5408,99,121,59,17455,99,121,59,17415,99,121,59,17454,99,117,116,101,32827,221,16605,256,105,121,5385,5389,114,99,59,16758,59,17451,114,59,49152,55349,56604,112,102,59,49152,55349,56656,99,114,59,49152,55349,56500,109,108,59,16760,1024,72,97,99,100,101,102,111,115,5429,5433,5439,5451,5455,5469,5472,5476,99,121,59,17430,99,117,116,101,59,16761,256,97,121,5444,5449,114,111,110,59,16765,59,17431,111,116,59,16763,498,5460,0,5467,111,87,105,100,116,232,2777,97,59,17302,114,59,24872,112,102,59,24868,99,114,59,49152,55349,56501,3041,5507,5514,5520,0,5552,5558,5567,0,0,0,0,5574,5595,5611,5727,5741,0,5781,5787,5810,5817,0,5822,99,117,116,101,32827,225,16609,114,101,118,101,59,16643,768,59,69,100,105,117,121,5532,5533,5537,5539,5544,5549,25150,59,49152,8766,819,59,25151,114,99,32827,226,16610,116,101,32955,180,774,59,17456,108,105,103,32827,230,16614,256,59,114,178,5562,59,49152,55349,56606,114,97,118,101,32827,224,16608,256,101,112,5578,5590,256,102,112,5583,5588,115,121,109,59,24885,232,5587,104,97,59,17329,256,97,112,5599,99,256,99,108,5604,5607,114,59,16641,103,59,27199,612,5616,0,0,5642,640,59,97,100,115,118,5626,5627,5631,5633,5639,25127,110,100,59,27221,59,27228,108,111,112,101,59,27224,59,27226,896,59,101,108,109,114,115,122,5656,5657,5659,5662,5695,5711,5721,25120,59,27044,101,187,5657,115,100,256,59,97,5669,5670,25121,1121,5680,5682,5684,5686,5688,5690,5692,5694,59,27048,59,27049,59,27050,59,27051,59,27052,59,27053,59,27054,59,27055,116,256,59,118,5701,5702,25119,98,256,59,100,5708,5709,25278,59,27037,256,112,116,5716,5719,104,59,25122,187,185,97,114,114,59,25468,256,103,112,5731,5735,111,110,59,16645,102,59,49152,55349,56658,896,59,69,97,101,105,111,112,4801,5755,5757,5762,5764,5767,5770,59,27248,99,105,114,59,27247,59,25162,100,59,25163,115,59,16423,114,111,120,256,59,101,4801,5778,241,5763,105,110,103,32827,229,16613,384,99,116,121,5793,5798,5800,114,59,49152,55349,56502,59,16426,109,112,256,59,101,4801,5807,241,648,105,108,100,101,32827,227,16611,109,108,32827,228,16612,256,99,105,5826,5832,111,110,105,110,244,626,110,116,59,27153,2048,78,97,98,99,100,101,102,105,107,108,110,111,112,114,115,117,5869,5873,5936,5948,5955,5960,6008,6013,6112,6118,6201,6224,5901,6461,6472,6512,111,116,59,27373,256,99,114,5878,5918,107,512,99,101,112,115,5888,5893,5901,5907,111,110,103,59,25164,112,115,105,108,111,110,59,17398,114,105,109,101,59,24629,105,109,256,59,101,5914,5915,25149,113,59,25293,374,5922,5926,101,101,59,25277,101,100,256,59,103,5932,5933,25349,101,187,5933,114,107,256,59,116,4956,5943,98,114,107,59,25526,256,111,121,5889,5953,59,17457,113,117,111,59,24606,640,99,109,112,114,116,5971,5979,5985,5988,5992,97,117,115,256,59,101,266,265,112,116,121,118,59,27056,115,233,5900,110,111,245,275,384,97,104,119,5999,6001,6003,59,17330,59,24886,101,101,110,59,25196,114,59,49152,55349,56607,103,896,99,111,115,116,117,118,119,6029,6045,6067,6081,6101,6107,6110,384,97,105,117,6036,6038,6042,240,1888,114,99,59,26095,112,187,4977,384,100,112,116,6052,6056,6061,111,116,59,27136,108,117,115,59,27137,105,109,101,115,59,27138,625,6073,0,0,6078,99,117,112,59,27142,97,114,59,26117,114,105,97,110,103,108,101,256,100,117,6093,6098,111,119,110,59,26045,112,59,26035,112,108,117,115,59,27140,101,229,5188,229,5293,97,114,111,119,59,26893,384,97,107,111,6125,6182,6197,256,99,110,6130,6179,107,384,108,115,116,6138,1451,6146,111,122,101,110,103,101,59,27115,114,105,97,110,103,108,101,512,59,100,108,114,6162,6163,6168,6173,26036,111,119,110,59,26046,101,102,116,59,26050,105,103,104,116,59,26040,107,59,25635,433,6187,0,6195,434,6191,0,6193,59,26002,59,26001,52,59,26003,99,107,59,25992,256,101,111,6206,6221,256,59,113,6211,6214,49152,61,8421,117,105,118,59,49152,8801,8421,116,59,25360,512,112,116,119,120,6233,6238,6247,6252,102,59,49152,55349,56659,256,59,116,5067,6243,111,109,187,5068,116,105,101,59,25288,1536,68,72,85,86,98,100,104,109,112,116,117,118,6277,6294,6314,6331,6359,6363,6380,6399,6405,6410,6416,6433,512,76,82,108,114,6286,6288,6290,6292,59,25943,59,25940,59,25942,59,25939,640,59,68,85,100,117,6305,6306,6308,6310,6312,25936,59,25958,59,25961,59,25956,59,25959,512,76,82,108,114,6323,6325,6327,6329,59,25949,59,25946,59,25948,59,25945,896,59,72,76,82,104,108,114,6346,6347,6349,6351,6353,6355,6357,25937,59,25964,59,25955,59,25952,59,25963,59,25954,59,25951,111,120,59,27081,512,76,82,108,114,6372,6374,6376,6378,59,25941,59,25938,59,25872,59,25868,640,59,68,85,100,117,1725,6391,6393,6395,6397,59,25957,59,25960,59,25900,59,25908,105,110,117,115,59,25247,108,117,115,59,25246,105,109,101,115,59,25248,512,76,82,108,114,6425,6427,6429,6431,59,25947,59,25944,59,25880,59,25876,896,59,72,76,82,104,108,114,6448,6449,6451,6453,6455,6457,6459,25858,59,25962,59,25953,59,25950,59,25916,59,25892,59,25884,256,101,118,291,6466,98,97,114,32827,166,16550,512,99,101,105,111,6481,6486,6490,6496,114,59,49152,55349,56503,109,105,59,24655,109,256,59,101,5914,5916,108,384,59,98,104,6504,6505,6507,16476,59,27077,115,117,98,59,26568,364,6516,6526,108,256,59,101,6521,6522,24610,116,187,6522,112,384,59,69,101,303,6533,6535,59,27310,256,59,113,1756,1755,3297,6567,0,6632,6673,6677,6706,0,6711,6736,0,0,6836,0,0,6849,0,0,6945,6958,6989,6994,0,7165,0,7180,384,99,112,114,6573,6578,6621,117,116,101,59,16647,768,59,97,98,99,100,115,6591,6592,6596,6602,6613,6617,25129,110,100,59,27204,114,99,117,112,59,27209,256,97,117,6607,6610,112,59,27211,112,59,27207,111,116,59,27200,59,49152,8745,65024,256,101,111,6626,6629,116,59,24641,238,1683,512,97,101,105,117,6640,6651,6657,6661,496,6645,0,6648,115,59,27213,111,110,59,16653,100,105,108,32827,231,16615,114,99,59,16649,112,115,256,59,115,6668,6669,27212,109,59,27216,111,116,59,16651,384,100,109,110,6683,6688,6694,105,108,32955,184,429,112,116,121,118,59,27058,116,33024,162,59,101,6701,6702,16546,114,228,434,114,59,49152,55349,56608,384,99,101,105,6717,6720,6733,121,59,17479,99,107,256,59,109,6727,6728,26387,97,114,107,187,6728,59,17351,114,896,59,69,99,101,102,109,115,6751,6752,6754,6763,6820,6826,6830,26059,59,27075,384,59,101,108,6761,6762,6765,17094,113,59,25175,101,609,6772,0,0,6792,114,114,111,119,256,108,114,6780,6785,101,102,116,59,25018,105,103,104,116,59,25019,640,82,83,97,99,100,6802,6804,6806,6810,6815,187,3911,59,25800,115,116,59,25243,105,114,99,59,25242,97,115,104,59,25245,110,105,110,116,59,27152,105,100,59,27375,99,105,114,59,27074,117,98,115,256,59,117,6843,6844,26211,105,116,187,6844,748,6855,6868,6906,0,6922,111,110,256,59,101,6861,6862,16442,256,59,113,199,198,621,6873,0,0,6882,97,256,59,116,6878,6879,16428,59,16448,384,59,102,108,6888,6889,6891,25089,238,4448,101,256,109,120,6897,6902,101,110,116,187,6889,101,243,589,487,6910,0,6919,256,59,100,4795,6914,111,116,59,27245,110,244,582,384,102,114,121,6928,6932,6935,59,49152,55349,56660,111,228,596,33024,169,59,115,341,6941,114,59,24855,256,97,111,6949,6953,114,114,59,25013,115,115,59,26391,256,99,117,6962,6967,114,59,49152,55349,56504,256,98,112,6972,6980,256,59,101,6977,6978,27343,59,27345,256,59,101,6985,6986,27344,59,27346,100,111,116,59,25327,896,100,101,108,112,114,118,119,7008,7020,7031,7042,7084,7124,7161,97,114,114,256,108,114,7016,7018,59,26936,59,26933,624,7026,0,0,7029,114,59,25310,99,59,25311,97,114,114,256,59,112,7039,7040,25014,59,26941,768,59,98,99,100,111,115,7055,7056,7062,7073,7077,7080,25130,114,99,97,112,59,27208,256,97,117,7067,7070,112,59,27206,112,59,27210,111,116,59,25229,114,59,27205,59,49152,8746,65024,512,97,108,114,118,7093,7103,7134,7139,114,114,256,59,109,7100,7101,25015,59,26940,121,384,101,118,119,7111,7124,7128,113,624,7118,0,0,7122,114,101,227,7027,117,227,7029,101,101,59,25294,101,100,103,101,59,25295,101,110,32827,164,16548,101,97,114,114,111,119,256,108,114,7150,7155,101,102,116,187,7040,105,103,104,116,187,7101,101,228,7133,256,99,105,7169,7175,111,110,105,110,244,503,110,116,59,25137,108,99,116,121,59,25389,2432,65,72,97,98,99,100,101,102,104,105,106,108,111,114,115,116,117,119,122,7224,7227,7231,7261,7273,7285,7306,7326,7340,7351,7419,7423,7437,7547,7569,7595,7611,7622,7629,114,242,897,97,114,59,26981,512,103,108,114,115,7240,7245,7250,7252,103,101,114,59,24608,101,116,104,59,24888,242,4403,104,256,59,118,7258,7259,24592,187,2314,363,7265,7271,97,114,111,119,59,26895,97,227,789,256,97,121,7278,7283,114,111,110,59,16655,59,17460,384,59,97,111,818,7292,7300,256,103,114,703,7297,114,59,25034,116,115,101,113,59,27255,384,103,108,109,7313,7316,7320,32827,176,16560,116,97,59,17332,112,116,121,118,59,27057,256,105,114,7331,7336,115,104,116,59,27007,59,49152,55349,56609,97,114,256,108,114,7347,7349,187,2268,187,4126,640,97,101,103,115,118,7362,888,7382,7388,7392,109,384,59,111,115,806,7370,7380,110,100,256,59,115,806,7377,117,105,116,59,26214,97,109,109,97,59,17373,105,110,59,25330,384,59,105,111,7399,7400,7416,16631,100,101,33024,247,59,111,7399,7408,110,116,105,109,101,115,59,25287,110,248,7415,99,121,59,17490,99,623,7430,0,0,7434,114,110,59,25374,111,112,59,25357,640,108,112,116,117,119,7448,7453,7458,7497,7509,108,97,114,59,16420,102,59,49152,55349,56661,640,59,101,109,112,115,779,7469,7479,7485,7490,113,256,59,100,850,7475,111,116,59,25169,105,110,117,115,59,25144,108,117,115,59,25108,113,117,97,114,101,59,25249,98,108,101,98,97,114,119,101,100,103,229,250,110,384,97,100,104,4398,7517,7527,111,119,110,97,114,114,111,119,243,7299,97,114,112,111,111,110,256,108,114,7538,7542,101,102,244,7348,105,103,104,244,7350,354,7551,7557,107,97,114,111,247,3906,623,7562,0,0,7566,114,110,59,25375,111,112,59,25356,384,99,111,116,7576,7587,7590,256,114,121,7581,7585,59,49152,55349,56505,59,17493,108,59,27126,114,111,107,59,16657,256,100,114,7600,7604,111,116,59,25329,105,256,59,102,7610,6166,26047,256,97,104,7616,7619,114,242,1065,97,242,4006,97,110,103,108,101,59,27046,256,99,105,7634,7637,121,59,17503,103,114,97,114,114,59,26623,2304,68,97,99,100,101,102,103,108,109,110,111,112,113,114,115,116,117,120,7681,7689,7705,7736,1400,7740,7753,7777,7806,7845,7855,7869,7905,7978,7991,8004,8014,8026,256,68,111,7686,7476,111,244,7305,256,99,115,7694,7700,117,116,101,32827,233,16617,116,101,114,59,27246,512,97,105,111,121,7714,7719,7729,7734,114,111,110,59,16667,114,256,59,99,7725,7726,25174,32827,234,16618,108,111,110,59,25173,59,17485,111,116,59,16663,256,68,114,7745,7749,111,116,59,25170,59,49152,55349,56610,384,59,114,115,7760,7761,7767,27290,97,118,101,32827,232,16616,256,59,100,7772,7773,27286,111,116,59,27288,512,59,105,108,115,7786,7787,7794,7796,27289,110,116,101,114,115,59,25575,59,24851,256,59,100,7801,7802,27285,111,116,59,27287,384,97,112,115,7813,7817,7831,99,114,59,16659,116,121,384,59,115,118,7826,7827,7829,25093,101,116,187,7827,112,256,49,59,7837,7844,307,7841,7843,59,24580,59,24581,24579,256,103,115,7850,7852,59,16715,112,59,24578,256,103,112,7860,7864,111,110,59,16665,102,59,49152,55349,56662,384,97,108,115,7876,7886,7890,114,256,59,115,7882,7883,25301,108,59,27107,117,115,59,27249,105,384,59,108,118,7898,7899,7903,17333,111,110,187,7899,59,17397,512,99,115,117,118,7914,7923,7947,7971,256,105,111,7919,7729,114,99,187,7726,617,7929,0,0,7931,237,1352,97,110,116,256,103,108,7938,7942,116,114,187,7773,101,115,115,187,7802,384,97,101,105,7954,7958,7962,108,115,59,16445,115,116,59,25183,118,256,59,68,565,7968,68,59,27256,112,97,114,115,108,59,27109,256,68,97,7983,7987,111,116,59,25171,114,114,59,26993,384,99,100,105,7998,8001,7928,114,59,24879,111,244,850,256,97,104,8009,8011,59,17335,32827,240,16624,256,109,114,8019,8023,108,32827,235,16619,111,59,24748,384,99,105,112,8033,8036,8039,108,59,16417,115,244,1390,256,101,111,8044,8052,99,116,97,116,105,111,238,1369,110,101,110,116,105,97,108,229,1401,2529,8082,0,8094,0,8097,8103,0,0,8134,8140,0,8147,0,8166,8170,8192,0,8200,8282,108,108,105,110,103,100,111,116,115,101,241,7748,121,59,17476,109,97,108,101,59,26176,384,105,108,114,8109,8115,8129,108,105,103,59,32768,64259,617,8121,0,0,8125,103,59,32768,64256,105,103,59,32768,64260,59,49152,55349,56611,108,105,103,59,32768,64257,108,105,103,59,49152,102,106,384,97,108,116,8153,8156,8161,116,59,26221,105,103,59,32768,64258,110,115,59,26033,111,102,59,16786,496,8174,0,8179,102,59,49152,55349,56663,256,97,107,1471,8183,256,59,118,8188,8189,25300,59,27353,97,114,116,105,110,116,59,27149,256,97,111,8204,8277,256,99,115,8209,8274,945,8218,8240,8248,8261,8264,0,8272,946,8226,8229,8231,8234,8236,0,8238,32827,189,16573,59,24915,32827,188,16572,59,24917,59,24921,59,24923,435,8244,0,8246,59,24916,59,24918,692,8254,8257,0,0,8259,32827,190,16574,59,24919,59,24924,53,59,24920,438,8268,0,8270,59,24922,59,24925,56,59,24926,108,59,24644,119,110,59,25378,99,114,59,49152,55349,56507,2176,69,97,98,99,100,101,102,103,105,106,108,110,111,114,115,116,118,8322,8329,8351,8357,8368,8372,8432,8437,8442,8447,8451,8466,8504,791,8510,8530,8606,256,59,108,1613,8327,59,27276,384,99,109,112,8336,8341,8349,117,116,101,59,16885,109,97,256,59,100,8348,7386,17331,59,27270,114,101,118,101,59,16671,256,105,121,8362,8366,114,99,59,16669,59,17459,111,116,59,16673,512,59,108,113,115,1598,1602,8381,8393,384,59,113,115,1598,1612,8388,108,97,110,244,1637,512,59,99,100,108,1637,8402,8405,8421,99,59,27305,111,116,256,59,111,8412,8413,27264,256,59,108,8418,8419,27266,59,27268,256,59,101,8426,8429,49152,8923,65024,115,59,27284,114,59,49152,55349,56612,256,59,103,1651,1563,109,101,108,59,24887,99,121,59,17491,512,59,69,97,106,1626,8460,8462,8464,59,27282,59,27301,59,27300,512,69,97,101,115,8475,8477,8489,8500,59,25193,112,256,59,112,8483,8484,27274,114,111,120,187,8484,256,59,113,8494,8495,27272,256,59,113,8494,8475,105,109,59,25319,112,102,59,49152,55349,56664,256,99,105,8515,8518,114,59,24842,109,384,59,101,108,1643,8526,8528,59,27278,59,27280,33536,62,59,99,100,108,113,114,1518,8544,8554,8558,8563,8569,256,99,105,8549,8551,59,27303,114,59,27258,111,116,59,25303,80,97,114,59,27029,117,101,115,116,59,27260,640,97,100,101,108,115,8580,8554,8592,1622,8603,496,8585,0,8590,112,114,111,248,8350,114,59,27e3,113,256,108,113,1599,8598,108,101,115,243,8328,105,237,1643,256,101,110,8611,8621,114,116,110,101,113,113,59,49152,8809,65024,197,8618,1280,65,97,98,99,101,102,107,111,115,121,8644,8647,8689,8693,8698,8728,8733,8751,8808,8829,114,242,928,512,105,108,109,114,8656,8660,8663,8667,114,115,240,5252,102,187,8228,105,108,244,1705,256,100,114,8672,8676,99,121,59,17482,384,59,99,119,2292,8683,8687,105,114,59,26952,59,25005,97,114,59,24847,105,114,99,59,16677,384,97,108,114,8705,8718,8723,114,116,115,256,59,117,8713,8714,26213,105,116,187,8714,108,105,112,59,24614,99,111,110,59,25273,114,59,49152,55349,56613,115,256,101,119,8739,8745,97,114,111,119,59,26917,97,114,111,119,59,26918,640,97,109,111,112,114,8762,8766,8771,8798,8803,114,114,59,25087,116,104,116,59,25147,107,256,108,114,8777,8787,101,102,116,97,114,114,111,119,59,25001,105,103,104,116,97,114,114,111,119,59,25002,102,59,49152,55349,56665,98,97,114,59,24597,384,99,108,116,8815,8820,8824,114,59,49152,55349,56509,97,115,232,8692,114,111,107,59,16679,256,98,112,8834,8839,117,108,108,59,24643,104,101,110,187,7259,2785,8867,0,8874,0,8888,8901,8910,0,8917,8947,0,0,8952,8994,9063,9058,9087,0,9094,9130,9140,99,117,116,101,32827,237,16621,384,59,105,121,1905,8880,8885,114,99,32827,238,16622,59,17464,256,99,120,8892,8895,121,59,17461,99,108,32827,161,16545,256,102,114,927,8905,59,49152,55349,56614,114,97,118,101,32827,236,16620,512,59,105,110,111,1854,8925,8937,8942,256,105,110,8930,8934,110,116,59,27148,116,59,25133,102,105,110,59,27100,116,97,59,24873,108,105,103,59,16691,384,97,111,112,8958,8986,8989,384,99,103,116,8965,8968,8983,114,59,16683,384,101,108,112,1823,8975,8979,105,110,229,1934,97,114,244,1824,104,59,16689,102,59,25271,101,100,59,16821,640,59,99,102,111,116,1268,9004,9009,9021,9025,97,114,101,59,24837,105,110,256,59,116,9016,9017,25118,105,101,59,27101,100,111,244,8985,640,59,99,101,108,112,1879,9036,9040,9051,9057,97,108,59,25274,256,103,114,9045,9049,101,114,243,5475,227,9037,97,114,104,107,59,27159,114,111,100,59,27196,512,99,103,112,116,9071,9074,9078,9083,121,59,17489,111,110,59,16687,102,59,49152,55349,56666,97,59,17337,117,101,115,116,32827,191,16575,256,99,105,9098,9103,114,59,49152,55349,56510,110,640,59,69,100,115,118,1268,9115,9117,9121,1267,59,25337,111,116,59,25333,256,59,118,9126,9127,25332,59,25331,256,59,105,1911,9134,108,100,101,59,16681,491,9144,0,9148,99,121,59,17494,108,32827,239,16623,768,99,102,109,111,115,117,9164,9175,9180,9185,9191,9205,256,105,121,9169,9173,114,99,59,16693,59,17465,114,59,49152,55349,56615,97,116,104,59,16951,112,102,59,49152,55349,56667,483,9196,0,9201,114,59,49152,55349,56511,114,99,121,59,17496,107,99,121,59,17492,1024,97,99,102,103,104,106,111,115,9227,9238,9250,9255,9261,9265,9269,9275,112,112,97,256,59,118,9235,9236,17338,59,17392,256,101,121,9243,9248,100,105,108,59,16695,59,17466,114,59,49152,55349,56616,114,101,101,110,59,16696,99,121,59,17477,99,121,59,17500,112,102,59,49152,55349,56668,99,114,59,49152,55349,56512,2944,65,66,69,72,97,98,99,100,101,102,103,104,106,108,109,110,111,112,114,115,116,117,118,9328,9345,9350,9357,9361,9486,9533,9562,9600,9806,9822,9829,9849,9853,9882,9906,9944,10077,10088,10123,10176,10241,10258,384,97,114,116,9335,9338,9340,114,242,2502,242,917,97,105,108,59,26907,97,114,114,59,26894,256,59,103,2452,9355,59,27275,97,114,59,26978,2403,9381,0,9386,0,9393,0,0,0,0,0,9397,9402,0,9414,9416,9421,0,9465,117,116,101,59,16698,109,112,116,121,118,59,27060,114,97,238,2124,98,100,97,59,17339,103,384,59,100,108,2190,9409,9411,59,27025,229,2190,59,27269,117,111,32827,171,16555,114,1024,59,98,102,104,108,112,115,116,2201,9438,9446,9449,9451,9454,9457,9461,256,59,102,2205,9443,115,59,26911,115,59,26909,235,8786,112,59,25003,108,59,26937,105,109,59,26995,108,59,24994,384,59,97,101,9471,9472,9476,27307,105,108,59,26905,256,59,115,9481,9482,27309,59,49152,10925,65024,384,97,98,114,9493,9497,9501,114,114,59,26892,114,107,59,26482,256,97,107,9506,9516,99,256,101,107,9512,9514,59,16507,59,16475,256,101,115,9521,9523,59,27019,108,256,100,117,9529,9531,59,27023,59,27021,512,97,101,117,121,9542,9547,9558,9560,114,111,110,59,16702,256,100,105,9552,9556,105,108,59,16700,236,2224,226,9513,59,17467,512,99,113,114,115,9571,9574,9581,9597,97,59,26934,117,111,256,59,114,3609,5958,256,100,117,9586,9591,104,97,114,59,26983,115,104,97,114,59,26955,104,59,25010,640,59,102,103,113,115,9611,9612,2441,9715,9727,25188,116,640,97,104,108,114,116,9624,9636,9655,9666,9704,114,114,111,119,256,59,116,2201,9633,97,233,9462,97,114,112,111,111,110,256,100,117,9647,9652,111,119,110,187,1114,112,187,2406,101,102,116,97,114,114,111,119,115,59,25031,105,103,104,116,384,97,104,115,9677,9686,9694,114,114,111,119,256,59,115,2292,2215,97,114,112,111,111,110,243,3992,113,117,105,103,97,114,114,111,247,8688,104,114,101,101,116,105,109,101,115,59,25291,384,59,113,115,9611,2451,9722,108,97,110,244,2476,640,59,99,100,103,115,2476,9738,9741,9757,9768,99,59,27304,111,116,256,59,111,9748,9749,27263,256,59,114,9754,9755,27265,59,27267,256,59,101,9762,9765,49152,8922,65024,115,59,27283,640,97,100,101,103,115,9779,9785,9789,9801,9803,112,112,114,111,248,9414,111,116,59,25302,113,256,103,113,9795,9797,244,2441,103,116,242,9356,244,2459,105,237,2482,384,105,108,114,9813,2273,9818,115,104,116,59,27004,59,49152,55349,56617,256,59,69,2460,9827,59,27281,353,9833,9846,114,256,100,117,9650,9838,256,59,108,2405,9843,59,26986,108,107,59,25988,99,121,59,17497,640,59,97,99,104,116,2632,9864,9867,9873,9878,114,242,9665,111,114,110,101,242,7432,97,114,100,59,26987,114,105,59,26106,256,105,111,9887,9892,100,111,116,59,16704,117,115,116,256,59,97,9900,9901,25520,99,104,101,187,9901,512,69,97,101,115,9915,9917,9929,9940,59,25192,112,256,59,112,9923,9924,27273,114,111,120,187,9924,256,59,113,9934,9935,27271,256,59,113,9934,9915,105,109,59,25318,1024,97,98,110,111,112,116,119,122,9961,9972,9975,10010,10031,10049,10055,10064,256,110,114,9966,9969,103,59,26604,114,59,25085,114,235,2241,103,384,108,109,114,9983,9997,10004,101,102,116,256,97,114,2534,9991,105,103,104,116,225,2546,97,112,115,116,111,59,26620,105,103,104,116,225,2557,112,97,114,114,111,119,256,108,114,10021,10025,101,102,244,9453,105,103,104,116,59,25004,384,97,102,108,10038,10041,10045,114,59,27013,59,49152,55349,56669,117,115,59,27181,105,109,101,115,59,27188,353,10059,10063,115,116,59,25111,225,4942,384,59,101,102,10071,10072,6144,26058,110,103,101,187,10072,97,114,256,59,108,10084,10085,16424,116,59,27027,640,97,99,104,109,116,10099,10102,10108,10117,10119,114,242,2216,111,114,110,101,242,7564,97,114,256,59,100,3992,10115,59,26989,59,24590,114,105,59,25279,768,97,99,104,105,113,116,10136,10141,2624,10146,10158,10171,113,117,111,59,24633,114,59,49152,55349,56513,109,384,59,101,103,2482,10154,10156,59,27277,59,27279,256,98,117,9514,10163,111,256,59,114,3615,10169,59,24602,114,111,107,59,16706,33792,60,59,99,100,104,105,108,113,114,2091,10194,9785,10204,10208,10213,10218,10224,256,99,105,10199,10201,59,27302,114,59,27257,114,101,229,9714,109,101,115,59,25289,97,114,114,59,26998,117,101,115,116,59,27259,256,80,105,10229,10233,97,114,59,27030,384,59,101,102,10240,2349,6171,26051,114,256,100,117,10247,10253,115,104,97,114,59,26954,104,97,114,59,26982,256,101,110,10263,10273,114,116,110,101,113,113,59,49152,8808,65024,197,10270,1792,68,97,99,100,101,102,104,105,108,110,111,112,115,117,10304,10309,10370,10382,10387,10400,10405,10408,10458,10466,10468,2691,10483,10498,68,111,116,59,25146,512,99,108,112,114,10318,10322,10339,10365,114,32827,175,16559,256,101,116,10327,10329,59,26178,256,59,101,10334,10335,26400,115,101,187,10335,256,59,115,4155,10344,116,111,512,59,100,108,117,4155,10355,10359,10363,111,119,238,1164,101,102,244,2319,240,5073,107,101,114,59,26030,256,111,121,10375,10380,109,109,97,59,27177,59,17468,97,115,104,59,24596,97,115,117,114,101,100,97,110,103,108,101,187,5670,114,59,49152,55349,56618,111,59,24871,384,99,100,110,10415,10420,10441,114,111,32827,181,16565,512,59,97,99,100,5220,10429,10432,10436,115,244,5799,105,114,59,27376,111,116,32955,183,437,117,115,384,59,98,100,10450,6403,10451,25106,256,59,117,7484,10456,59,27178,355,10462,10465,112,59,27355,242,8722,240,2689,256,100,112,10473,10478,101,108,115,59,25255,102,59,49152,55349,56670,256,99,116,10488,10493,114,59,49152,55349,56514,112,111,115,187,5533,384,59,108,109,10505,10506,10509,17340,116,105,109,97,112,59,25272,3072,71,76,82,86,97,98,99,100,101,102,103,104,105,106,108,109,111,112,114,115,116,117,118,119,10562,10579,10622,10633,10648,10714,10729,10773,10778,10840,10845,10883,10901,10916,10920,11012,11015,11076,11135,11182,11316,11367,11388,11497,256,103,116,10567,10571,59,49152,8921,824,256,59,118,10576,3023,49152,8811,8402,384,101,108,116,10586,10610,10614,102,116,256,97,114,10593,10599,114,114,111,119,59,25037,105,103,104,116,97,114,114,111,119,59,25038,59,49152,8920,824,256,59,118,10619,3143,49152,8810,8402,105,103,104,116,97,114,114,111,119,59,25039,256,68,100,10638,10643,97,115,104,59,25263,97,115,104,59,25262,640,98,99,110,112,116,10659,10663,10668,10673,10700,108,97,187,734,117,116,101,59,16708,103,59,49152,8736,8402,640,59,69,105,111,112,3460,10684,10688,10693,10696,59,49152,10864,824,100,59,49152,8779,824,115,59,16713,114,111,248,3460,117,114,256,59,97,10707,10708,26222,108,256,59,115,10707,2872,499,10719,0,10723,112,32955,160,2871,109,112,256,59,101,3065,3072,640,97,101,111,117,121,10740,10750,10755,10768,10771,496,10745,0,10747,59,27203,111,110,59,16712,100,105,108,59,16710,110,103,256,59,100,3454,10762,111,116,59,49152,10861,824,112,59,27202,59,17469,97,115,104,59,24595,896,59,65,97,100,113,115,120,2962,10793,10797,10811,10817,10821,10832,114,114,59,25047,114,256,104,114,10803,10806,107,59,26916,256,59,111,5106,5104,111,116,59,49152,8784,824,117,105,246,2915,256,101,105,10826,10830,97,114,59,26920,237,2968,105,115,116,256,59,115,2976,2975,114,59,49152,55349,56619,512,69,101,115,116,3013,10854,10873,10876,384,59,113,115,3004,10861,3041,384,59,113,115,3004,3013,10868,108,97,110,244,3042,105,237,3050,256,59,114,2998,10881,187,2999,384,65,97,112,10890,10893,10897,114,242,10609,114,114,59,25006,97,114,59,27378,384,59,115,118,3981,10908,3980,256,59,100,10913,10914,25340,59,25338,99,121,59,17498,896,65,69,97,100,101,115,116,10935,10938,10942,10946,10949,10998,11001,114,242,10598,59,49152,8806,824,114,114,59,24986,114,59,24613,512,59,102,113,115,3131,10958,10979,10991,116,256,97,114,10964,10969,114,114,111,247,10945,105,103,104,116,97,114,114,111,247,10896,384,59,113,115,3131,10938,10986,108,97,110,244,3157,256,59,115,3157,10996,187,3126,105,237,3165,256,59,114,3125,11006,105,256,59,101,3098,3109,105,228,3472,256,112,116,11020,11025,102,59,49152,55349,56671,33152,172,59,105,110,11033,11034,11062,16556,110,512,59,69,100,118,2953,11044,11048,11054,59,49152,8953,824,111,116,59,49152,8949,824,481,2953,11059,11061,59,25335,59,25334,105,256,59,118,3256,11068,481,3256,11073,11075,59,25342,59,25341,384,97,111,114,11083,11107,11113,114,512,59,97,115,116,2939,11093,11098,11103,108,108,101,236,2939,108,59,49152,11005,8421,59,49152,8706,824,108,105,110,116,59,27156,384,59,99,101,3218,11120,11123,117,229,3237,256,59,99,3224,11128,256,59,101,3218,11133,241,3224,512,65,97,105,116,11144,11147,11165,11175,114,242,10632,114,114,384,59,99,119,11156,11157,11161,24987,59,49152,10547,824,59,49152,8605,824,103,104,116,97,114,114,111,119,187,11157,114,105,256,59,101,3275,3286,896,99,104,105,109,112,113,117,11197,11213,11225,11012,2936,11236,11247,512,59,99,101,114,3378,11206,3383,11209,117,229,3397,59,49152,55349,56515,111,114,116,621,11013,0,0,11222,97,114,225,11094,109,256,59,101,3438,11231,256,59,113,3444,3443,115,117,256,98,112,11243,11245,229,3320,229,3339,384,98,99,112,11254,11281,11289,512,59,69,101,115,11263,11264,3362,11268,25220,59,49152,10949,824,101,116,256,59,101,3355,11275,113,256,59,113,3363,11264,99,256,59,101,3378,11287,241,3384,512,59,69,101,115,11298,11299,3423,11303,25221,59,49152,10950,824,101,116,256,59,101,3416,11310,113,256,59,113,3424,11299,512,103,105,108,114,11325,11327,11333,11335,236,3031,108,100,101,32827,241,16625,231,3139,105,97,110,103,108,101,256,108,114,11346,11356,101,102,116,256,59,101,3098,11354,241,3110,105,103,104,116,256,59,101,3275,11365,241,3287,256,59,109,11372,11373,17341,384,59,101,115,11380,11381,11385,16419,114,111,59,24854,112,59,24583,1152,68,72,97,100,103,105,108,114,115,11407,11412,11417,11422,11427,11440,11446,11475,11491,97,115,104,59,25261,97,114,114,59,26884,112,59,49152,8781,8402,97,115,104,59,25260,256,101,116,11432,11436,59,49152,8805,8402,59,49152,62,8402,110,102,105,110,59,27102,384,65,101,116,11453,11457,11461,114,114,59,26882,59,49152,8804,8402,256,59,114,11466,11469,49152,60,8402,105,101,59,49152,8884,8402,256,65,116,11480,11484,114,114,59,26883,114,105,101,59,49152,8885,8402,105,109,59,49152,8764,8402,384,65,97,110,11504,11508,11522,114,114,59,25046,114,256,104,114,11514,11517,107,59,26915,256,59,111,5095,5093,101,97,114,59,26919,4691,6805,0,0,0,0,0,0,0,0,0,0,0,0,0,11565,0,11576,11592,11616,11621,11634,11652,6919,0,0,11661,11691,0,11720,11726,0,11740,11801,11819,11838,11843,256,99,115,11569,6807,117,116,101,32827,243,16627,256,105,121,11580,11589,114,256,59,99,6814,11586,32827,244,16628,59,17470,640,97,98,105,111,115,6816,11602,11607,456,11610,108,97,99,59,16721,118,59,27192,111,108,100,59,27068,108,105,103,59,16723,256,99,114,11625,11629,105,114,59,27071,59,49152,55349,56620,879,11641,0,0,11644,0,11650,110,59,17115,97,118,101,32827,242,16626,59,27073,256,98,109,11656,3572,97,114,59,27061,512,97,99,105,116,11669,11672,11685,11688,114,242,6784,256,105,114,11677,11680,114,59,27070,111,115,115,59,27067,110,229,3666,59,27072,384,97,101,105,11697,11701,11705,99,114,59,16717,103,97,59,17353,384,99,100,110,11712,11717,461,114,111,110,59,17343,59,27062,112,102,59,49152,55349,56672,384,97,101,108,11732,11735,466,114,59,27063,114,112,59,27065,896,59,97,100,105,111,115,118,11754,11755,11758,11784,11789,11792,11798,25128,114,242,6790,512,59,101,102,109,11767,11768,11778,11781,27229,114,256,59,111,11774,11775,24884,102,187,11775,32827,170,16554,32827,186,16570,103,111,102,59,25270,114,59,27222,108,111,112,101,59,27223,59,27227,384,99,108,111,11807,11809,11815,242,11777,97,115,104,32827,248,16632,108,59,25240,105,364,11823,11828,100,101,32827,245,16629,101,115,256,59,97,475,11834,115,59,27190,109,108,32827,246,16630,98,97,114,59,25405,2785,11870,0,11901,0,11904,11933,0,11938,11961,0,0,11979,3740,0,12051,0,0,12075,12220,0,12232,114,512,59,97,115,116,1027,11879,11890,3717,33024,182,59,108,11885,11886,16566,108,101,236,1027,617,11896,0,0,11899,109,59,27379,59,27389,121,59,17471,114,640,99,105,109,112,116,11915,11919,11923,6245,11927,110,116,59,16421,111,100,59,16430,105,108,59,24624,101,110,107,59,24625,114,59,49152,55349,56621,384,105,109,111,11944,11952,11956,256,59,118,11949,11950,17350,59,17365,109,97,244,2678,110,101,59,26126,384,59,116,118,11967,11968,11976,17344,99,104,102,111,114,107,187,8189,59,17366,256,97,117,11983,11999,110,256,99,107,11989,11997,107,256,59,104,8692,11995,59,24846,246,8692,115,1152,59,97,98,99,100,101,109,115,116,12019,12020,6408,12025,12029,12036,12038,12042,12046,16427,99,105,114,59,27171,105,114,59,27170,256,111,117,7488,12034,59,27173,59,27250,110,32955,177,3741,105,109,59,27174,119,111,59,27175,384,105,112,117,12057,12064,12069,110,116,105,110,116,59,27157,102,59,49152,55349,56673,110,100,32827,163,16547,1280,59,69,97,99,101,105,110,111,115,117,3784,12095,12097,12100,12103,12161,12169,12178,12158,12214,59,27315,112,59,27319,117,229,3801,256,59,99,3790,12108,768,59,97,99,101,110,115,3784,12121,12127,12134,12136,12158,112,112,114,111,248,12099,117,114,108,121,101,241,3801,241,3790,384,97,101,115,12143,12150,12154,112,112,114,111,120,59,27321,113,113,59,27317,105,109,59,25320,105,237,3807,109,101,256,59,115,12168,3758,24626,384,69,97,115,12152,12176,12154,240,12149,384,100,102,112,3820,12185,12207,384,97,108,115,12192,12197,12202,108,97,114,59,25390,105,110,101,59,25362,117,114,102,59,25363,256,59,116,3835,12212,239,3835,114,101,108,59,25264,256,99,105,12224,12229,114,59,49152,55349,56517,59,17352,110,99,115,112,59,24584,768,102,105,111,112,115,117,12250,8930,12255,12261,12267,12273,114,59,49152,55349,56622,112,102,59,49152,55349,56674,114,105,109,101,59,24663,99,114,59,49152,55349,56518,384,97,101,111,12280,12297,12307,116,256,101,105,12286,12293,114,110,105,111,110,243,1712,110,116,59,27158,115,116,256,59,101,12304,12305,16447,241,7961,244,3860,2688,65,66,72,97,98,99,100,101,102,104,105,108,109,110,111,112,114,115,116,117,120,12352,12369,12373,12377,12512,12558,12587,12615,12642,12658,12686,12806,12821,12836,12841,12888,12910,12914,12944,12976,12983,384,97,114,116,12359,12362,12364,114,242,4275,242,989,97,105,108,59,26908,97,114,242,7269,97,114,59,26980,896,99,100,101,110,113,114,116,12392,12405,12408,12415,12431,12436,12492,256,101,117,12397,12401,59,49152,8765,817,116,101,59,16725,105,227,4462,109,112,116,121,118,59,27059,103,512,59,100,101,108,4049,12425,12427,12429,59,27026,59,27045,229,4049,117,111,32827,187,16571,114,1408,59,97,98,99,102,104,108,112,115,116,119,4060,12460,12463,12471,12473,12476,12478,12480,12483,12487,12490,112,59,26997,256,59,102,4064,12468,115,59,26912,59,26931,115,59,26910,235,8797,240,10030,108,59,26949,105,109,59,26996,108,59,24995,59,24989,256,97,105,12497,12501,105,108,59,26906,111,256,59,110,12507,12508,25142,97,108,243,3870,384,97,98,114,12519,12522,12526,114,242,6117,114,107,59,26483,256,97,107,12531,12541,99,256,101,107,12537,12539,59,16509,59,16477,256,101,115,12546,12548,59,27020,108,256,100,117,12554,12556,59,27022,59,27024,512,97,101,117,121,12567,12572,12583,12585,114,111,110,59,16729,256,100,105,12577,12581,105,108,59,16727,236,4082,226,12538,59,17472,512,99,108,113,115,12596,12599,12605,12612,97,59,26935,100,104,97,114,59,26985,117,111,256,59,114,526,525,104,59,25011,384,97,99,103,12622,12639,3908,108,512,59,105,112,115,3960,12632,12635,4252,110,229,4283,97,114,244,4009,116,59,26029,384,105,108,114,12649,4131,12654,115,104,116,59,27005,59,49152,55349,56623,256,97,111,12663,12678,114,256,100,117,12669,12671,187,1147,256,59,108,4241,12676,59,26988,256,59,118,12683,12684,17345,59,17393,384,103,110,115,12693,12793,12796,104,116,768,97,104,108,114,115,116,12708,12720,12738,12760,12772,12782,114,114,111,119,256,59,116,4060,12717,97,233,12488,97,114,112,111,111,110,256,100,117,12731,12735,111,119,238,12670,112,187,4242,101,102,116,256,97,104,12746,12752,114,114,111,119,243,4074,97,114,112,111,111,110,243,1361,105,103,104,116,97,114,114,111,119,115,59,25033,113,117,105,103,97,114,114,111,247,12491,104,114,101,101,116,105,109,101,115,59,25292,103,59,17114,105,110,103,100,111,116,115,101,241,7986,384,97,104,109,12813,12816,12819,114,242,4074,97,242,1361,59,24591,111,117,115,116,256,59,97,12830,12831,25521,99,104,101,187,12831,109,105,100,59,27374,512,97,98,112,116,12850,12861,12864,12882,256,110,114,12855,12858,103,59,26605,114,59,25086,114,235,4099,384,97,102,108,12871,12874,12878,114,59,27014,59,49152,55349,56675,117,115,59,27182,105,109,101,115,59,27189,256,97,112,12893,12903,114,256,59,103,12899,12900,16425,116,59,27028,111,108,105,110,116,59,27154,97,114,242,12771,512,97,99,104,113,12923,12928,4284,12933,113,117,111,59,24634,114,59,49152,55349,56519,256,98,117,12539,12938,111,256,59,114,532,531,384,104,105,114,12951,12955,12960,114,101,229,12792,109,101,115,59,25290,105,512,59,101,102,108,12970,4185,6177,12971,26041,116,114,105,59,27086,108,117,104,97,114,59,26984,59,24862,3425,13013,13019,13023,13100,13112,13169,0,13178,13220,0,0,13292,13296,0,13352,13384,13402,13485,13489,13514,13553,0,13846,0,0,13875,99,117,116,101,59,16731,113,117,239,10170,1280,59,69,97,99,101,105,110,112,115,121,4589,13043,13045,13055,13058,13067,13071,13087,13094,13097,59,27316,496,13050,0,13052,59,27320,111,110,59,16737,117,229,4606,256,59,100,4595,13063,105,108,59,16735,114,99,59,16733,384,69,97,115,13078,13080,13083,59,27318,112,59,27322,105,109,59,25321,111,108,105,110,116,59,27155,105,237,4612,59,17473,111,116,384,59,98,101,13108,7495,13109,25285,59,27238,896,65,97,99,109,115,116,120,13126,13130,13143,13147,13150,13155,13165,114,114,59,25048,114,256,104,114,13136,13138,235,8744,256,59,111,2614,2612,116,32827,167,16551,105,59,16443,119,97,114,59,26921,109,256,105,110,13161,240,110,117,243,241,116,59,26422,114,256,59,111,13174,8277,49152,55349,56624,512,97,99,111,121,13186,13190,13201,13216,114,112,59,26223,256,104,121,13195,13199,99,121,59,17481,59,17480,114,116,621,13209,0,0,13212,105,228,5220,97,114,97,236,11887,32827,173,16557,256,103,109,13224,13236,109,97,384,59,102,118,13233,13234,13234,17347,59,17346,1024,59,100,101,103,108,110,112,114,4779,13253,13257,13262,13270,13278,13281,13286,111,116,59,27242,256,59,113,4785,4784,256,59,69,13267,13268,27294,59,27296,256,59,69,13275,13276,27293,59,27295,101,59,25158,108,117,115,59,27172,97,114,114,59,26994,97,114,242,4413,512,97,101,105,116,13304,13320,13327,13335,256,108,115,13309,13316,108,115,101,116,109,233,13162,104,112,59,27187,112,97,114,115,108,59,27108,256,100,108,5219,13332,101,59,25379,256,59,101,13340,13341,27306,256,59,115,13346,13347,27308,59,49152,10924,65024,384,102,108,112,13358,13363,13378,116,99,121,59,17484,256,59,98,13368,13369,16431,256,59,97,13374,13375,27076,114,59,25407,102,59,49152,55349,56676,97,256,100,114,13389,1026,101,115,256,59,117,13396,13397,26208,105,116,187,13397,384,99,115,117,13408,13433,13471,256,97,117,13413,13423,112,256,59,115,4488,13419,59,49152,8851,65024,112,256,59,115,4532,13429,59,49152,8852,65024,117,256,98,112,13439,13455,384,59,101,115,4503,4508,13446,101,116,256,59,101,4503,13453,241,4509,384,59,101,115,4520,4525,13462,101,116,256,59,101,4520,13469,241,4526,384,59,97,102,4475,13478,1456,114,357,13483,1457,187,4476,97,114,242,4424,512,99,101,109,116,13497,13502,13506,13509,114,59,49152,55349,56520,116,109,238,241,105,236,13333,97,114,230,4542,256,97,114,13518,13525,114,256,59,102,13524,6079,26118,256,97,110,13530,13549,105,103,104,116,256,101,112,13539,13546,112,115,105,108,111,238,7904,104,233,11951,115,187,10322,640,98,99,109,110,112,13563,13662,4617,13707,13710,1152,59,69,100,101,109,110,112,114,115,13582,13583,13585,13589,13598,13603,13612,13617,13622,25218,59,27333,111,116,59,27325,256,59,100,4570,13594,111,116,59,27331,117,108,116,59,27329,256,69,101,13608,13610,59,27339,59,25226,108,117,115,59,27327,97,114,114,59,27001,384,101,105,117,13629,13650,13653,116,384,59,101,110,13582,13637,13643,113,256,59,113,4570,13583,101,113,256,59,113,13611,13608,109,59,27335,256,98,112,13658,13660,59,27349,59,27347,99,768,59,97,99,101,110,115,4589,13676,13682,13689,13691,13094,112,112,114,111,248,13050,117,114,108,121,101,241,4606,241,4595,384,97,101,115,13698,13704,13083,112,112,114,111,248,13082,113,241,13079,103,59,26218,1664,49,50,51,59,69,100,101,104,108,109,110,112,115,13737,13740,13743,4636,13746,13748,13760,13769,13781,13786,13791,13800,13805,32827,185,16569,32827,178,16562,32827,179,16563,59,27334,256,111,115,13753,13756,116,59,27326,117,98,59,27352,256,59,100,4642,13765,111,116,59,27332,115,256,111,117,13775,13778,108,59,26569,98,59,27351,97,114,114,59,27003,117,108,116,59,27330,256,69,101,13796,13798,59,27340,59,25227,108,117,115,59,27328,384,101,105,117,13812,13833,13836,116,384,59,101,110,4636,13820,13826,113,256,59,113,4642,13746,101,113,256,59,113,13799,13796,109,59,27336,256,98,112,13841,13843,59,27348,59,27350,384,65,97,110,13852,13856,13869,114,114,59,25049,114,256,104,114,13862,13864,235,8750,256,59,111,2603,2601,119,97,114,59,26922,108,105,103,32827,223,16607,3041,13905,13917,13920,4814,13939,13945,0,13950,14018,0,0,0,0,0,14043,14083,0,14089,14188,0,0,0,14215,626,13910,0,0,13915,103,101,116,59,25366,59,17348,114,235,3679,384,97,101,121,13926,13931,13936,114,111,110,59,16741,100,105,108,59,16739,59,17474,108,114,101,99,59,25365,114,59,49152,55349,56625,512,101,105,107,111,13958,13981,14005,14012,498,13963,0,13969,101,256,52,102,4740,4737,97,384,59,115,118,13976,13977,13979,17336,121,109,59,17361,256,99,110,13986,14002,107,256,97,115,13992,13998,112,112,114,111,248,4801,105,109,187,4780,115,240,4766,256,97,115,14010,13998,240,4801,114,110,32827,254,16638,492,799,14022,8935,101,115,33152,215,59,98,100,14031,14032,14040,16599,256,59,97,6415,14037,114,59,27185,59,27184,384,101,112,115,14049,14051,14080,225,10829,512,59,98,99,102,1158,14060,14064,14068,111,116,59,25398,105,114,59,27377,256,59,111,14073,14076,49152,55349,56677,114,107,59,27354,225,13154,114,105,109,101,59,24628,384,97,105,112,14095,14098,14180,100,229,4680,896,97,100,101,109,112,115,116,14113,14157,14144,14161,14167,14172,14175,110,103,108,101,640,59,100,108,113,114,14128,14129,14134,14144,14146,26037,111,119,110,187,7611,101,102,116,256,59,101,10240,14142,241,2350,59,25180,105,103,104,116,256,59,101,12970,14155,241,4186,111,116,59,26092,105,110,117,115,59,27194,108,117,115,59,27193,98,59,27085,105,109,101,59,27195,101,122,105,117,109,59,25570,384,99,104,116,14194,14205,14209,256,114,121,14199,14203,59,49152,55349,56521,59,17478,99,121,59,17499,114,111,107,59,16743,256,105,111,14219,14222,120,244,6007,104,101,97,100,256,108,114,14231,14240,101,102,116,97,114,114,111,247,2127,105,103,104,116,97,114,114,111,119,187,3933,2304,65,72,97,98,99,100,102,103,104,108,109,111,112,114,115,116,117,119,14288,14291,14295,14308,14320,14332,14350,14364,14371,14388,14417,14429,14443,14505,14540,14546,14570,14582,114,242,1005,97,114,59,26979,256,99,114,14300,14306,117,116,101,32827,250,16634,242,4432,114,483,14314,0,14317,121,59,17502,118,101,59,16749,256,105,121,14325,14330,114,99,32827,251,16635,59,17475,384,97,98,104,14339,14342,14347,114,242,5037,108,97,99,59,16753,97,242,5059,256,105,114,14355,14360,115,104,116,59,27006,59,49152,55349,56626,114,97,118,101,32827,249,16633,353,14375,14385,114,256,108,114,14380,14382,187,2391,187,4227,108,107,59,25984,256,99,116,14393,14413,623,14399,0,0,14410,114,110,256,59,101,14405,14406,25372,114,187,14406,111,112,59,25359,114,105,59,26104,256,97,108,14422,14426,99,114,59,16747,32955,168,841,256,103,112,14434,14438,111,110,59,16755,102,59,49152,55349,56678,768,97,100,104,108,115,117,4427,14456,14461,4978,14481,14496,111,119,110,225,5043,97,114,112,111,111,110,256,108,114,14472,14476,101,102,244,14381,105,103,104,244,14383,105,384,59,104,108,14489,14490,14492,17349,187,5114,111,110,187,14490,112,97,114,114,111,119,115,59,25032,384,99,105,116,14512,14532,14536,623,14518,0,0,14529,114,110,256,59,101,14524,14525,25373,114,187,14525,111,112,59,25358,110,103,59,16751,114,105,59,26105,99,114,59,49152,55349,56522,384,100,105,114,14553,14557,14562,111,116,59,25328,108,100,101,59,16745,105,256,59,102,14128,14568,187,6163,256,97,109,14575,14578,114,242,14504,108,32827,252,16636,97,110,103,108,101,59,27047,1920,65,66,68,97,99,100,101,102,108,110,111,112,114,115,122,14620,14623,14633,14637,14773,14776,14781,14815,14820,14824,14835,14841,14845,14849,14880,114,242,1015,97,114,256,59,118,14630,14631,27368,59,27369,97,115,232,993,256,110,114,14642,14647,103,114,116,59,27036,896,101,107,110,112,114,115,116,13539,14662,14667,14674,14685,14692,14742,97,112,112,225,9237,111,116,104,105,110,231,7830,384,104,105,114,13547,11976,14681,111,112,244,12213,256,59,104,5047,14690,239,12685,256,105,117,14697,14701,103,109,225,13235,256,98,112,14706,14724,115,101,116,110,101,113,256,59,113,14717,14720,49152,8842,65024,59,49152,10955,65024,115,101,116,110,101,113,256,59,113,14735,14738,49152,8843,65024,59,49152,10956,65024,256,104,114,14747,14751,101,116,225,13980,105,97,110,103,108,101,256,108,114,14762,14767,101,102,116,187,2341,105,103,104,116,187,4177,121,59,17458,97,115,104,187,4150,384,101,108,114,14788,14802,14807,384,59,98,101,11754,14795,14799,97,114,59,25275,113,59,25178,108,105,112,59,25326,256,98,116,14812,5224,97,242,5225,114,59,49152,55349,56627,116,114,233,14766,115,117,256,98,112,14831,14833,187,3356,187,3417,112,102,59,49152,55349,56679,114,111,240,3835,116,114,233,14772,256,99,117,14854,14859,114,59,49152,55349,56523,256,98,112,14864,14872,110,256,69,101,14720,14870,187,14718,110,256,69,101,14738,14878,187,14736,105,103,122,97,103,59,27034,896,99,101,102,111,112,114,115,14902,14907,14934,14939,14932,14945,14954,105,114,99,59,16757,256,100,105,14912,14929,256,98,103,14917,14921,97,114,59,27231,101,256,59,113,5626,14927,59,25177,101,114,112,59,24856,114,59,49152,55349,56628,112,102,59,49152,55349,56680,256,59,101,5241,14950,97,116,232,5241,99,114,59,49152,55349,56524,2787,6030,14983,0,14987,0,14992,15003,0,0,15005,15016,15019,15023,0,0,15043,15054,0,15064,6108,6111,116,114,233,6097,114,59,49152,55349,56629,256,65,97,14996,14999,114,242,963,114,242,2550,59,17342,256,65,97,15009,15012,114,242,952,114,242,2539,97,240,10003,105,115,59,25339,384,100,112,116,6052,15029,15038,256,102,108,15034,6057,59,49152,55349,56681,105,109,229,6066,256,65,97,15047,15050,114,242,974,114,242,2561,256,99,113,15058,6072,114,59,49152,55349,56525,256,112,116,6102,15068,114,233,6100,1024,97,99,101,102,105,111,115,117,15088,15101,15112,15116,15121,15125,15131,15137,99,256,117,121,15094,15099,116,101,32827,253,16637,59,17487,256,105,121,15106,15110,114,99,59,16759,59,17483,110,32827,165,16549,114,59,49152,55349,56630,99,121,59,17495,112,102,59,49152,55349,56682,99,114,59,49152,55349,56526,256,99,109,15142,15145,121,59,17486,108,32827,255,16639,1280,97,99,100,101,102,104,105,111,115,119,15170,15176,15188,15192,15204,15209,15213,15220,15226,15232,99,117,116,101,59,16762,256,97,121,15181,15186,114,111,110,59,16766,59,17463,111,116,59,16764,256,101,116,15197,15201,116,114,230,5471,97,59,17334,114,59,49152,55349,56631,99,121,59,17462,103,114,97,114,114,59,25053,112,102,59,49152,55349,56683,99,114,59,49152,55349,56527,256,106,110,15237,15239,59,24589,106,59,24588])},2491:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array([512,97,103,108,113,9,21,24,27,621,15,0,0,18,112,59,16422,111,115,59,16423,116,59,16446,116,59,16444,117,111,116,59,16418])},2208:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Map([[9," "],[10," "],[33,"!"],[34,"""],[35,"#"],[36,"$"],[37,"%"],[38,"&"],[39,"'"],[40,"("],[41,")"],[42,"*"],[43,"+"],[44,","],[46,"."],[47,"/"],[58,":"],[59,";"],[60,{v:"<",n:8402,o:"<⃒"}],[61,{v:"=",n:8421,o:"=⃥"}],[62,{v:">",n:8402,o:">⃒"}],[63,"?"],[64,"@"],[91,"["],[92,"\"],[93,"]"],[94,"^"],[95,"_"],[96,"`"],[102,{n:106,o:"fj"}],[123,"{"],[124,"|"],[125,"}"],[160," "],[161,"¡"],[162,"¢"],[163,"£"],[164,"¤"],[165,"¥"],[166,"¦"],[167,"§"],[168,"¨"],[169,"©"],[170,"ª"],[171,"«"],[172,"¬"],[173,"­"],[174,"®"],[175,"¯"],[176,"°"],[177,"±"],[178,"²"],[179,"³"],[180,"´"],[181,"µ"],[182,"¶"],[183,"·"],[184,"¸"],[185,"¹"],[186,"º"],[187,"»"],[188,"¼"],[189,"½"],[190,"¾"],[191,"¿"],[192,"À"],[193,"Á"],[194,"Â"],[195,"Ã"],[196,"Ä"],[197,"Å"],[198,"Æ"],[199,"Ç"],[200,"È"],[201,"É"],[202,"Ê"],[203,"Ë"],[204,"Ì"],[205,"Í"],[206,"Î"],[207,"Ï"],[208,"Ð"],[209,"Ñ"],[210,"Ò"],[211,"Ó"],[212,"Ô"],[213,"Õ"],[214,"Ö"],[215,"×"],[216,"Ø"],[217,"Ù"],[218,"Ú"],[219,"Û"],[220,"Ü"],[221,"Ý"],[222,"Þ"],[223,"ß"],[224,"à"],[225,"á"],[226,"â"],[227,"ã"],[228,"ä"],[229,"å"],[230,"æ"],[231,"ç"],[232,"è"],[233,"é"],[234,"ê"],[235,"ë"],[236,"ì"],[237,"í"],[238,"î"],[239,"ï"],[240,"ð"],[241,"ñ"],[242,"ò"],[243,"ó"],[244,"ô"],[245,"õ"],[246,"ö"],[247,"÷"],[248,"ø"],[249,"ù"],[250,"ú"],[251,"û"],[252,"ü"],[253,"ý"],[254,"þ"],[255,"ÿ"],[256,"Ā"],[257,"ā"],[258,"Ă"],[259,"ă"],[260,"Ą"],[261,"ą"],[262,"Ć"],[263,"ć"],[264,"Ĉ"],[265,"ĉ"],[266,"Ċ"],[267,"ċ"],[268,"Č"],[269,"č"],[270,"Ď"],[271,"ď"],[272,"Đ"],[273,"đ"],[274,"Ē"],[275,"ē"],[278,"Ė"],[279,"ė"],[280,"Ę"],[281,"ę"],[282,"Ě"],[283,"ě"],[284,"Ĝ"],[285,"ĝ"],[286,"Ğ"],[287,"ğ"],[288,"Ġ"],[289,"ġ"],[290,"Ģ"],[292,"Ĥ"],[293,"ĥ"],[294,"Ħ"],[295,"ħ"],[296,"Ĩ"],[297,"ĩ"],[298,"Ī"],[299,"ī"],[302,"Į"],[303,"į"],[304,"İ"],[305,"ı"],[306,"IJ"],[307,"ij"],[308,"Ĵ"],[309,"ĵ"],[310,"Ķ"],[311,"ķ"],[312,"ĸ"],[313,"Ĺ"],[314,"ĺ"],[315,"Ļ"],[316,"ļ"],[317,"Ľ"],[318,"ľ"],[319,"Ŀ"],[320,"ŀ"],[321,"Ł"],[322,"ł"],[323,"Ń"],[324,"ń"],[325,"Ņ"],[326,"ņ"],[327,"Ň"],[328,"ň"],[329,"ʼn"],[330,"Ŋ"],[331,"ŋ"],[332,"Ō"],[333,"ō"],[336,"Ő"],[337,"ő"],[338,"Œ"],[339,"œ"],[340,"Ŕ"],[341,"ŕ"],[342,"Ŗ"],[343,"ŗ"],[344,"Ř"],[345,"ř"],[346,"Ś"],[347,"ś"],[348,"Ŝ"],[349,"ŝ"],[350,"Ş"],[351,"ş"],[352,"Š"],[353,"š"],[354,"Ţ"],[355,"ţ"],[356,"Ť"],[357,"ť"],[358,"Ŧ"],[359,"ŧ"],[360,"Ũ"],[361,"ũ"],[362,"Ū"],[363,"ū"],[364,"Ŭ"],[365,"ŭ"],[366,"Ů"],[367,"ů"],[368,"Ű"],[369,"ű"],[370,"Ų"],[371,"ų"],[372,"Ŵ"],[373,"ŵ"],[374,"Ŷ"],[375,"ŷ"],[376,"Ÿ"],[377,"Ź"],[378,"ź"],[379,"Ż"],[380,"ż"],[381,"Ž"],[382,"ž"],[402,"ƒ"],[437,"Ƶ"],[501,"ǵ"],[567,"ȷ"],[710,"ˆ"],[711,"ˇ"],[728,"˘"],[729,"˙"],[730,"˚"],[731,"˛"],[732,"˜"],[733,"˝"],[785,"̑"],[913,"Α"],[914,"Β"],[915,"Γ"],[916,"Δ"],[917,"Ε"],[918,"Ζ"],[919,"Η"],[920,"Θ"],[921,"Ι"],[922,"Κ"],[923,"Λ"],[924,"Μ"],[925,"Ν"],[926,"Ξ"],[927,"Ο"],[928,"Π"],[929,"Ρ"],[931,"Σ"],[932,"Τ"],[933,"Υ"],[934,"Φ"],[935,"Χ"],[936,"Ψ"],[937,"Ω"],[945,"α"],[946,"β"],[947,"γ"],[948,"δ"],[949,"ε"],[950,"ζ"],[951,"η"],[952,"θ"],[953,"ι"],[954,"κ"],[955,"λ"],[956,"μ"],[957,"ν"],[958,"ξ"],[959,"ο"],[960,"π"],[961,"ρ"],[962,"ς"],[963,"σ"],[964,"τ"],[965,"υ"],[966,"φ"],[967,"χ"],[968,"ψ"],[969,"ω"],[977,"ϑ"],[978,"ϒ"],[981,"ϕ"],[982,"ϖ"],[988,"Ϝ"],[989,"ϝ"],[1008,"ϰ"],[1009,"ϱ"],[1013,"ϵ"],[1014,"϶"],[1025,"Ё"],[1026,"Ђ"],[1027,"Ѓ"],[1028,"Є"],[1029,"Ѕ"],[1030,"І"],[1031,"Ї"],[1032,"Ј"],[1033,"Љ"],[1034,"Њ"],[1035,"Ћ"],[1036,"Ќ"],[1038,"Ў"],[1039,"Џ"],[1040,"А"],[1041,"Б"],[1042,"В"],[1043,"Г"],[1044,"Д"],[1045,"Е"],[1046,"Ж"],[1047,"З"],[1048,"И"],[1049,"Й"],[1050,"К"],[1051,"Л"],[1052,"М"],[1053,"Н"],[1054,"О"],[1055,"П"],[1056,"Р"],[1057,"С"],[1058,"Т"],[1059,"У"],[1060,"Ф"],[1061,"Х"],[1062,"Ц"],[1063,"Ч"],[1064,"Ш"],[1065,"Щ"],[1066,"Ъ"],[1067,"Ы"],[1068,"Ь"],[1069,"Э"],[1070,"Ю"],[1071,"Я"],[1072,"а"],[1073,"б"],[1074,"в"],[1075,"г"],[1076,"д"],[1077,"е"],[1078,"ж"],[1079,"з"],[1080,"и"],[1081,"й"],[1082,"к"],[1083,"л"],[1084,"м"],[1085,"н"],[1086,"о"],[1087,"п"],[1088,"р"],[1089,"с"],[1090,"т"],[1091,"у"],[1092,"ф"],[1093,"х"],[1094,"ц"],[1095,"ч"],[1096,"ш"],[1097,"щ"],[1098,"ъ"],[1099,"ы"],[1100,"ь"],[1101,"э"],[1102,"ю"],[1103,"я"],[1105,"ё"],[1106,"ђ"],[1107,"ѓ"],[1108,"є"],[1109,"ѕ"],[1110,"і"],[1111,"ї"],[1112,"ј"],[1113,"љ"],[1114,"њ"],[1115,"ћ"],[1116,"ќ"],[1118,"ў"],[1119,"џ"],[8194," "],[8195," "],[8196," "],[8197," "],[8199," "],[8200," "],[8201," "],[8202," "],[8203,"​"],[8204,"‌"],[8205,"‍"],[8206,"‎"],[8207,"‏"],[8208,"‐"],[8211,"–"],[8212,"—"],[8213,"―"],[8214,"‖"],[8216,"‘"],[8217,"’"],[8218,"‚"],[8220,"“"],[8221,"”"],[8222,"„"],[8224,"†"],[8225,"‡"],[8226,"•"],[8229,"‥"],[8230,"…"],[8240,"‰"],[8241,"‱"],[8242,"′"],[8243,"″"],[8244,"‴"],[8245,"‵"],[8249,"‹"],[8250,"›"],[8254,"‾"],[8257,"⁁"],[8259,"⁃"],[8260,"⁄"],[8271,"⁏"],[8279,"⁗"],[8287,{v:" ",n:8202,o:"  "}],[8288,"⁠"],[8289,"⁡"],[8290,"⁢"],[8291,"⁣"],[8364,"€"],[8411,"⃛"],[8412,"⃜"],[8450,"ℂ"],[8453,"℅"],[8458,"ℊ"],[8459,"ℋ"],[8460,"ℌ"],[8461,"ℍ"],[8462,"ℎ"],[8463,"ℏ"],[8464,"ℐ"],[8465,"ℑ"],[8466,"ℒ"],[8467,"ℓ"],[8469,"ℕ"],[8470,"№"],[8471,"℗"],[8472,"℘"],[8473,"ℙ"],[8474,"ℚ"],[8475,"ℛ"],[8476,"ℜ"],[8477,"ℝ"],[8478,"℞"],[8482,"™"],[8484,"ℤ"],[8487,"℧"],[8488,"ℨ"],[8489,"℩"],[8492,"ℬ"],[8493,"ℭ"],[8495,"ℯ"],[8496,"ℰ"],[8497,"ℱ"],[8499,"ℳ"],[8500,"ℴ"],[8501,"ℵ"],[8502,"ℶ"],[8503,"ℷ"],[8504,"ℸ"],[8517,"ⅅ"],[8518,"ⅆ"],[8519,"ⅇ"],[8520,"ⅈ"],[8531,"⅓"],[8532,"⅔"],[8533,"⅕"],[8534,"⅖"],[8535,"⅗"],[8536,"⅘"],[8537,"⅙"],[8538,"⅚"],[8539,"⅛"],[8540,"⅜"],[8541,"⅝"],[8542,"⅞"],[8592,"←"],[8593,"↑"],[8594,"→"],[8595,"↓"],[8596,"↔"],[8597,"↕"],[8598,"↖"],[8599,"↗"],[8600,"↘"],[8601,"↙"],[8602,"↚"],[8603,"↛"],[8605,{v:"↝",n:824,o:"↝̸"}],[8606,"↞"],[8607,"↟"],[8608,"↠"],[8609,"↡"],[8610,"↢"],[8611,"↣"],[8612,"↤"],[8613,"↥"],[8614,"↦"],[8615,"↧"],[8617,"↩"],[8618,"↪"],[8619,"↫"],[8620,"↬"],[8621,"↭"],[8622,"↮"],[8624,"↰"],[8625,"↱"],[8626,"↲"],[8627,"↳"],[8629,"↵"],[8630,"↶"],[8631,"↷"],[8634,"↺"],[8635,"↻"],[8636,"↼"],[8637,"↽"],[8638,"↾"],[8639,"↿"],[8640,"⇀"],[8641,"⇁"],[8642,"⇂"],[8643,"⇃"],[8644,"⇄"],[8645,"⇅"],[8646,"⇆"],[8647,"⇇"],[8648,"⇈"],[8649,"⇉"],[8650,"⇊"],[8651,"⇋"],[8652,"⇌"],[8653,"⇍"],[8654,"⇎"],[8655,"⇏"],[8656,"⇐"],[8657,"⇑"],[8658,"⇒"],[8659,"⇓"],[8660,"⇔"],[8661,"⇕"],[8662,"⇖"],[8663,"⇗"],[8664,"⇘"],[8665,"⇙"],[8666,"⇚"],[8667,"⇛"],[8669,"⇝"],[8676,"⇤"],[8677,"⇥"],[8693,"⇵"],[8701,"⇽"],[8702,"⇾"],[8703,"⇿"],[8704,"∀"],[8705,"∁"],[8706,{v:"∂",n:824,o:"∂̸"}],[8707,"∃"],[8708,"∄"],[8709,"∅"],[8711,"∇"],[8712,"∈"],[8713,"∉"],[8715,"∋"],[8716,"∌"],[8719,"∏"],[8720,"∐"],[8721,"∑"],[8722,"−"],[8723,"∓"],[8724,"∔"],[8726,"∖"],[8727,"∗"],[8728,"∘"],[8730,"√"],[8733,"∝"],[8734,"∞"],[8735,"∟"],[8736,{v:"∠",n:8402,o:"∠⃒"}],[8737,"∡"],[8738,"∢"],[8739,"∣"],[8740,"∤"],[8741,"∥"],[8742,"∦"],[8743,"∧"],[8744,"∨"],[8745,{v:"∩",n:65024,o:"∩︀"}],[8746,{v:"∪",n:65024,o:"∪︀"}],[8747,"∫"],[8748,"∬"],[8749,"∭"],[8750,"∮"],[8751,"∯"],[8752,"∰"],[8753,"∱"],[8754,"∲"],[8755,"∳"],[8756,"∴"],[8757,"∵"],[8758,"∶"],[8759,"∷"],[8760,"∸"],[8762,"∺"],[8763,"∻"],[8764,{v:"∼",n:8402,o:"∼⃒"}],[8765,{v:"∽",n:817,o:"∽̱"}],[8766,{v:"∾",n:819,o:"∾̳"}],[8767,"∿"],[8768,"≀"],[8769,"≁"],[8770,{v:"≂",n:824,o:"≂̸"}],[8771,"≃"],[8772,"≄"],[8773,"≅"],[8774,"≆"],[8775,"≇"],[8776,"≈"],[8777,"≉"],[8778,"≊"],[8779,{v:"≋",n:824,o:"≋̸"}],[8780,"≌"],[8781,{v:"≍",n:8402,o:"≍⃒"}],[8782,{v:"≎",n:824,o:"≎̸"}],[8783,{v:"≏",n:824,o:"≏̸"}],[8784,{v:"≐",n:824,o:"≐̸"}],[8785,"≑"],[8786,"≒"],[8787,"≓"],[8788,"≔"],[8789,"≕"],[8790,"≖"],[8791,"≗"],[8793,"≙"],[8794,"≚"],[8796,"≜"],[8799,"≟"],[8800,"≠"],[8801,{v:"≡",n:8421,o:"≡⃥"}],[8802,"≢"],[8804,{v:"≤",n:8402,o:"≤⃒"}],[8805,{v:"≥",n:8402,o:"≥⃒"}],[8806,{v:"≦",n:824,o:"≦̸"}],[8807,{v:"≧",n:824,o:"≧̸"}],[8808,{v:"≨",n:65024,o:"≨︀"}],[8809,{v:"≩",n:65024,o:"≩︀"}],[8810,{v:"≪",n:new Map([[824,"≪̸"],[8402,"≪⃒"]])}],[8811,{v:"≫",n:new Map([[824,"≫̸"],[8402,"≫⃒"]])}],[8812,"≬"],[8813,"≭"],[8814,"≮"],[8815,"≯"],[8816,"≰"],[8817,"≱"],[8818,"≲"],[8819,"≳"],[8820,"≴"],[8821,"≵"],[8822,"≶"],[8823,"≷"],[8824,"≸"],[8825,"≹"],[8826,"≺"],[8827,"≻"],[8828,"≼"],[8829,"≽"],[8830,"≾"],[8831,{v:"≿",n:824,o:"≿̸"}],[8832,"⊀"],[8833,"⊁"],[8834,{v:"⊂",n:8402,o:"⊂⃒"}],[8835,{v:"⊃",n:8402,o:"⊃⃒"}],[8836,"⊄"],[8837,"⊅"],[8838,"⊆"],[8839,"⊇"],[8840,"⊈"],[8841,"⊉"],[8842,{v:"⊊",n:65024,o:"⊊︀"}],[8843,{v:"⊋",n:65024,o:"⊋︀"}],[8845,"⊍"],[8846,"⊎"],[8847,{v:"⊏",n:824,o:"⊏̸"}],[8848,{v:"⊐",n:824,o:"⊐̸"}],[8849,"⊑"],[8850,"⊒"],[8851,{v:"⊓",n:65024,o:"⊓︀"}],[8852,{v:"⊔",n:65024,o:"⊔︀"}],[8853,"⊕"],[8854,"⊖"],[8855,"⊗"],[8856,"⊘"],[8857,"⊙"],[8858,"⊚"],[8859,"⊛"],[8861,"⊝"],[8862,"⊞"],[8863,"⊟"],[8864,"⊠"],[8865,"⊡"],[8866,"⊢"],[8867,"⊣"],[8868,"⊤"],[8869,"⊥"],[8871,"⊧"],[8872,"⊨"],[8873,"⊩"],[8874,"⊪"],[8875,"⊫"],[8876,"⊬"],[8877,"⊭"],[8878,"⊮"],[8879,"⊯"],[8880,"⊰"],[8882,"⊲"],[8883,"⊳"],[8884,{v:"⊴",n:8402,o:"⊴⃒"}],[8885,{v:"⊵",n:8402,o:"⊵⃒"}],[8886,"⊶"],[8887,"⊷"],[8888,"⊸"],[8889,"⊹"],[8890,"⊺"],[8891,"⊻"],[8893,"⊽"],[8894,"⊾"],[8895,"⊿"],[8896,"⋀"],[8897,"⋁"],[8898,"⋂"],[8899,"⋃"],[8900,"⋄"],[8901,"⋅"],[8902,"⋆"],[8903,"⋇"],[8904,"⋈"],[8905,"⋉"],[8906,"⋊"],[8907,"⋋"],[8908,"⋌"],[8909,"⋍"],[8910,"⋎"],[8911,"⋏"],[8912,"⋐"],[8913,"⋑"],[8914,"⋒"],[8915,"⋓"],[8916,"⋔"],[8917,"⋕"],[8918,"⋖"],[8919,"⋗"],[8920,{v:"⋘",n:824,o:"⋘̸"}],[8921,{v:"⋙",n:824,o:"⋙̸"}],[8922,{v:"⋚",n:65024,o:"⋚︀"}],[8923,{v:"⋛",n:65024,o:"⋛︀"}],[8926,"⋞"],[8927,"⋟"],[8928,"⋠"],[8929,"⋡"],[8930,"⋢"],[8931,"⋣"],[8934,"⋦"],[8935,"⋧"],[8936,"⋨"],[8937,"⋩"],[8938,"⋪"],[8939,"⋫"],[8940,"⋬"],[8941,"⋭"],[8942,"⋮"],[8943,"⋯"],[8944,"⋰"],[8945,"⋱"],[8946,"⋲"],[8947,"⋳"],[8948,"⋴"],[8949,{v:"⋵",n:824,o:"⋵̸"}],[8950,"⋶"],[8951,"⋷"],[8953,{v:"⋹",n:824,o:"⋹̸"}],[8954,"⋺"],[8955,"⋻"],[8956,"⋼"],[8957,"⋽"],[8958,"⋾"],[8965,"⌅"],[8966,"⌆"],[8968,"⌈"],[8969,"⌉"],[8970,"⌊"],[8971,"⌋"],[8972,"⌌"],[8973,"⌍"],[8974,"⌎"],[8975,"⌏"],[8976,"⌐"],[8978,"⌒"],[8979,"⌓"],[8981,"⌕"],[8982,"⌖"],[8988,"⌜"],[8989,"⌝"],[8990,"⌞"],[8991,"⌟"],[8994,"⌢"],[8995,"⌣"],[9005,"⌭"],[9006,"⌮"],[9014,"⌶"],[9021,"⌽"],[9023,"⌿"],[9084,"⍼"],[9136,"⎰"],[9137,"⎱"],[9140,"⎴"],[9141,"⎵"],[9142,"⎶"],[9180,"⏜"],[9181,"⏝"],[9182,"⏞"],[9183,"⏟"],[9186,"⏢"],[9191,"⏧"],[9251,"␣"],[9416,"Ⓢ"],[9472,"─"],[9474,"│"],[9484,"┌"],[9488,"┐"],[9492,"└"],[9496,"┘"],[9500,"├"],[9508,"┤"],[9516,"┬"],[9524,"┴"],[9532,"┼"],[9552,"═"],[9553,"║"],[9554,"╒"],[9555,"╓"],[9556,"╔"],[9557,"╕"],[9558,"╖"],[9559,"╗"],[9560,"╘"],[9561,"╙"],[9562,"╚"],[9563,"╛"],[9564,"╜"],[9565,"╝"],[9566,"╞"],[9567,"╟"],[9568,"╠"],[9569,"╡"],[9570,"╢"],[9571,"╣"],[9572,"╤"],[9573,"╥"],[9574,"╦"],[9575,"╧"],[9576,"╨"],[9577,"╩"],[9578,"╪"],[9579,"╫"],[9580,"╬"],[9600,"▀"],[9604,"▄"],[9608,"█"],[9617,"░"],[9618,"▒"],[9619,"▓"],[9633,"□"],[9642,"▪"],[9643,"▫"],[9645,"▭"],[9646,"▮"],[9649,"▱"],[9651,"△"],[9652,"▴"],[9653,"▵"],[9656,"▸"],[9657,"▹"],[9661,"▽"],[9662,"▾"],[9663,"▿"],[9666,"◂"],[9667,"◃"],[9674,"◊"],[9675,"○"],[9708,"◬"],[9711,"◯"],[9720,"◸"],[9721,"◹"],[9722,"◺"],[9723,"◻"],[9724,"◼"],[9733,"★"],[9734,"☆"],[9742,"☎"],[9792,"♀"],[9794,"♂"],[9824,"♠"],[9827,"♣"],[9829,"♥"],[9830,"♦"],[9834,"♪"],[9837,"♭"],[9838,"♮"],[9839,"♯"],[10003,"✓"],[10007,"✗"],[10016,"✠"],[10038,"✶"],[10072,"❘"],[10098,"❲"],[10099,"❳"],[10184,"⟈"],[10185,"⟉"],[10214,"⟦"],[10215,"⟧"],[10216,"⟨"],[10217,"⟩"],[10218,"⟪"],[10219,"⟫"],[10220,"⟬"],[10221,"⟭"],[10229,"⟵"],[10230,"⟶"],[10231,"⟷"],[10232,"⟸"],[10233,"⟹"],[10234,"⟺"],[10236,"⟼"],[10239,"⟿"],[10498,"⤂"],[10499,"⤃"],[10500,"⤄"],[10501,"⤅"],[10508,"⤌"],[10509,"⤍"],[10510,"⤎"],[10511,"⤏"],[10512,"⤐"],[10513,"⤑"],[10514,"⤒"],[10515,"⤓"],[10518,"⤖"],[10521,"⤙"],[10522,"⤚"],[10523,"⤛"],[10524,"⤜"],[10525,"⤝"],[10526,"⤞"],[10527,"⤟"],[10528,"⤠"],[10531,"⤣"],[10532,"⤤"],[10533,"⤥"],[10534,"⤦"],[10535,"⤧"],[10536,"⤨"],[10537,"⤩"],[10538,"⤪"],[10547,{v:"⤳",n:824,o:"⤳̸"}],[10549,"⤵"],[10550,"⤶"],[10551,"⤷"],[10552,"⤸"],[10553,"⤹"],[10556,"⤼"],[10557,"⤽"],[10565,"⥅"],[10568,"⥈"],[10569,"⥉"],[10570,"⥊"],[10571,"⥋"],[10574,"⥎"],[10575,"⥏"],[10576,"⥐"],[10577,"⥑"],[10578,"⥒"],[10579,"⥓"],[10580,"⥔"],[10581,"⥕"],[10582,"⥖"],[10583,"⥗"],[10584,"⥘"],[10585,"⥙"],[10586,"⥚"],[10587,"⥛"],[10588,"⥜"],[10589,"⥝"],[10590,"⥞"],[10591,"⥟"],[10592,"⥠"],[10593,"⥡"],[10594,"⥢"],[10595,"⥣"],[10596,"⥤"],[10597,"⥥"],[10598,"⥦"],[10599,"⥧"],[10600,"⥨"],[10601,"⥩"],[10602,"⥪"],[10603,"⥫"],[10604,"⥬"],[10605,"⥭"],[10606,"⥮"],[10607,"⥯"],[10608,"⥰"],[10609,"⥱"],[10610,"⥲"],[10611,"⥳"],[10612,"⥴"],[10613,"⥵"],[10614,"⥶"],[10616,"⥸"],[10617,"⥹"],[10619,"⥻"],[10620,"⥼"],[10621,"⥽"],[10622,"⥾"],[10623,"⥿"],[10629,"⦅"],[10630,"⦆"],[10635,"⦋"],[10636,"⦌"],[10637,"⦍"],[10638,"⦎"],[10639,"⦏"],[10640,"⦐"],[10641,"⦑"],[10642,"⦒"],[10643,"⦓"],[10644,"⦔"],[10645,"⦕"],[10646,"⦖"],[10650,"⦚"],[10652,"⦜"],[10653,"⦝"],[10660,"⦤"],[10661,"⦥"],[10662,"⦦"],[10663,"⦧"],[10664,"⦨"],[10665,"⦩"],[10666,"⦪"],[10667,"⦫"],[10668,"⦬"],[10669,"⦭"],[10670,"⦮"],[10671,"⦯"],[10672,"⦰"],[10673,"⦱"],[10674,"⦲"],[10675,"⦳"],[10676,"⦴"],[10677,"⦵"],[10678,"⦶"],[10679,"⦷"],[10681,"⦹"],[10683,"⦻"],[10684,"⦼"],[10686,"⦾"],[10687,"⦿"],[10688,"⧀"],[10689,"⧁"],[10690,"⧂"],[10691,"⧃"],[10692,"⧄"],[10693,"⧅"],[10697,"⧉"],[10701,"⧍"],[10702,"⧎"],[10703,{v:"⧏",n:824,o:"⧏̸"}],[10704,{v:"⧐",n:824,o:"⧐̸"}],[10716,"⧜"],[10717,"⧝"],[10718,"⧞"],[10723,"⧣"],[10724,"⧤"],[10725,"⧥"],[10731,"⧫"],[10740,"⧴"],[10742,"⧶"],[10752,"⨀"],[10753,"⨁"],[10754,"⨂"],[10756,"⨄"],[10758,"⨆"],[10764,"⨌"],[10765,"⨍"],[10768,"⨐"],[10769,"⨑"],[10770,"⨒"],[10771,"⨓"],[10772,"⨔"],[10773,"⨕"],[10774,"⨖"],[10775,"⨗"],[10786,"⨢"],[10787,"⨣"],[10788,"⨤"],[10789,"⨥"],[10790,"⨦"],[10791,"⨧"],[10793,"⨩"],[10794,"⨪"],[10797,"⨭"],[10798,"⨮"],[10799,"⨯"],[10800,"⨰"],[10801,"⨱"],[10803,"⨳"],[10804,"⨴"],[10805,"⨵"],[10806,"⨶"],[10807,"⨷"],[10808,"⨸"],[10809,"⨹"],[10810,"⨺"],[10811,"⨻"],[10812,"⨼"],[10815,"⨿"],[10816,"⩀"],[10818,"⩂"],[10819,"⩃"],[10820,"⩄"],[10821,"⩅"],[10822,"⩆"],[10823,"⩇"],[10824,"⩈"],[10825,"⩉"],[10826,"⩊"],[10827,"⩋"],[10828,"⩌"],[10829,"⩍"],[10832,"⩐"],[10835,"⩓"],[10836,"⩔"],[10837,"⩕"],[10838,"⩖"],[10839,"⩗"],[10840,"⩘"],[10842,"⩚"],[10843,"⩛"],[10844,"⩜"],[10845,"⩝"],[10847,"⩟"],[10854,"⩦"],[10858,"⩪"],[10861,{v:"⩭",n:824,o:"⩭̸"}],[10862,"⩮"],[10863,"⩯"],[10864,{v:"⩰",n:824,o:"⩰̸"}],[10865,"⩱"],[10866,"⩲"],[10867,"⩳"],[10868,"⩴"],[10869,"⩵"],[10871,"⩷"],[10872,"⩸"],[10873,"⩹"],[10874,"⩺"],[10875,"⩻"],[10876,"⩼"],[10877,{v:"⩽",n:824,o:"⩽̸"}],[10878,{v:"⩾",n:824,o:"⩾̸"}],[10879,"⩿"],[10880,"⪀"],[10881,"⪁"],[10882,"⪂"],[10883,"⪃"],[10884,"⪄"],[10885,"⪅"],[10886,"⪆"],[10887,"⪇"],[10888,"⪈"],[10889,"⪉"],[10890,"⪊"],[10891,"⪋"],[10892,"⪌"],[10893,"⪍"],[10894,"⪎"],[10895,"⪏"],[10896,"⪐"],[10897,"⪑"],[10898,"⪒"],[10899,"⪓"],[10900,"⪔"],[10901,"⪕"],[10902,"⪖"],[10903,"⪗"],[10904,"⪘"],[10905,"⪙"],[10906,"⪚"],[10909,"⪝"],[10910,"⪞"],[10911,"⪟"],[10912,"⪠"],[10913,{v:"⪡",n:824,o:"⪡̸"}],[10914,{v:"⪢",n:824,o:"⪢̸"}],[10916,"⪤"],[10917,"⪥"],[10918,"⪦"],[10919,"⪧"],[10920,"⪨"],[10921,"⪩"],[10922,"⪪"],[10923,"⪫"],[10924,{v:"⪬",n:65024,o:"⪬︀"}],[10925,{v:"⪭",n:65024,o:"⪭︀"}],[10926,"⪮"],[10927,{v:"⪯",n:824,o:"⪯̸"}],[10928,{v:"⪰",n:824,o:"⪰̸"}],[10931,"⪳"],[10932,"⪴"],[10933,"⪵"],[10934,"⪶"],[10935,"⪷"],[10936,"⪸"],[10937,"⪹"],[10938,"⪺"],[10939,"⪻"],[10940,"⪼"],[10941,"⪽"],[10942,"⪾"],[10943,"⪿"],[10944,"⫀"],[10945,"⫁"],[10946,"⫂"],[10947,"⫃"],[10948,"⫄"],[10949,{v:"⫅",n:824,o:"⫅̸"}],[10950,{v:"⫆",n:824,o:"⫆̸"}],[10951,"⫇"],[10952,"⫈"],[10955,{v:"⫋",n:65024,o:"⫋︀"}],[10956,{v:"⫌",n:65024,o:"⫌︀"}],[10959,"⫏"],[10960,"⫐"],[10961,"⫑"],[10962,"⫒"],[10963,"⫓"],[10964,"⫔"],[10965,"⫕"],[10966,"⫖"],[10967,"⫗"],[10968,"⫘"],[10969,"⫙"],[10970,"⫚"],[10971,"⫛"],[10980,"⫤"],[10982,"⫦"],[10983,"⫧"],[10984,"⫨"],[10985,"⫩"],[10987,"⫫"],[10988,"⫬"],[10989,"⫭"],[10990,"⫮"],[10991,"⫯"],[10992,"⫰"],[10993,"⫱"],[10994,"⫲"],[10995,"⫳"],[11005,{v:"⫽",n:8421,o:"⫽⃥"}],[55349,{n:new Map([[56476,"𝒜"],[56478,"𝒞"],[56479,"𝒟"],[56482,"𝒢"],[56485,"𝒥"],[56486,"𝒦"],[56489,"𝒩"],[56490,"𝒪"],[56491,"𝒫"],[56492,"𝒬"],[56494,"𝒮"],[56495,"𝒯"],[56496,"𝒰"],[56497,"𝒱"],[56498,"𝒲"],[56499,"𝒳"],[56500,"𝒴"],[56501,"𝒵"],[56502,"𝒶"],[56503,"𝒷"],[56504,"𝒸"],[56505,"𝒹"],[56507,"𝒻"],[56509,"𝒽"],[56510,"𝒾"],[56511,"𝒿"],[56512,"𝓀"],[56513,"𝓁"],[56514,"𝓂"],[56515,"𝓃"],[56517,"𝓅"],[56518,"𝓆"],[56519,"𝓇"],[56520,"𝓈"],[56521,"𝓉"],[56522,"𝓊"],[56523,"𝓋"],[56524,"𝓌"],[56525,"𝓍"],[56526,"𝓎"],[56527,"𝓏"],[56580,"𝔄"],[56581,"𝔅"],[56583,"𝔇"],[56584,"𝔈"],[56585,"𝔉"],[56586,"𝔊"],[56589,"𝔍"],[56590,"𝔎"],[56591,"𝔏"],[56592,"𝔐"],[56593,"𝔑"],[56594,"𝔒"],[56595,"𝔓"],[56596,"𝔔"],[56598,"𝔖"],[56599,"𝔗"],[56600,"𝔘"],[56601,"𝔙"],[56602,"𝔚"],[56603,"𝔛"],[56604,"𝔜"],[56606,"𝔞"],[56607,"𝔟"],[56608,"𝔠"],[56609,"𝔡"],[56610,"𝔢"],[56611,"𝔣"],[56612,"𝔤"],[56613,"𝔥"],[56614,"𝔦"],[56615,"𝔧"],[56616,"𝔨"],[56617,"𝔩"],[56618,"𝔪"],[56619,"𝔫"],[56620,"𝔬"],[56621,"𝔭"],[56622,"𝔮"],[56623,"𝔯"],[56624,"𝔰"],[56625,"𝔱"],[56626,"𝔲"],[56627,"𝔳"],[56628,"𝔴"],[56629,"𝔵"],[56630,"𝔶"],[56631,"𝔷"],[56632,"𝔸"],[56633,"𝔹"],[56635,"𝔻"],[56636,"𝔼"],[56637,"𝔽"],[56638,"𝔾"],[56640,"𝕀"],[56641,"𝕁"],[56642,"𝕂"],[56643,"𝕃"],[56644,"𝕄"],[56646,"𝕆"],[56650,"𝕊"],[56651,"𝕋"],[56652,"𝕌"],[56653,"𝕍"],[56654,"𝕎"],[56655,"𝕏"],[56656,"𝕐"],[56658,"𝕒"],[56659,"𝕓"],[56660,"𝕔"],[56661,"𝕕"],[56662,"𝕖"],[56663,"𝕗"],[56664,"𝕘"],[56665,"𝕙"],[56666,"𝕚"],[56667,"𝕛"],[56668,"𝕜"],[56669,"𝕝"],[56670,"𝕞"],[56671,"𝕟"],[56672,"𝕠"],[56673,"𝕡"],[56674,"𝕢"],[56675,"𝕣"],[56676,"𝕤"],[56677,"𝕥"],[56678,"𝕦"],[56679,"𝕧"],[56680,"𝕨"],[56681,"𝕩"],[56682,"𝕪"],[56683,"𝕫"]])}],[64256,"ff"],[64257,"fi"],[64258,"fl"],[64259,"ffi"],[64260,"ffl"]])},4946:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXMLStrict=t.decodeHTML5Strict=t.decodeHTML4Strict=t.decodeHTML5=t.decodeHTML4=t.decodeHTMLStrict=t.decodeHTML=t.decodeXML=t.encodeHTML5=t.encodeHTML4=t.encodeNonAsciiHTML=t.encodeHTML=t.escapeText=t.escapeAttribute=t.escapeUTF8=t.escape=t.encodeXML=t.encode=t.decodeStrict=t.decode=t.EncodingMode=t.DecodingMode=t.EntityLevel=void 0;var r,o,i,a=n(4753),l=n(6913),s=n(8168);!function(e){e[e.XML=0]="XML",e[e.HTML=1]="HTML"}(r=t.EntityLevel||(t.EntityLevel={})),function(e){e[e.Legacy=0]="Legacy",e[e.Strict=1]="Strict"}(o=t.DecodingMode||(t.DecodingMode={})),function(e){e[e.UTF8=0]="UTF8",e[e.ASCII=1]="ASCII",e[e.Extensive=2]="Extensive",e[e.Attribute=3]="Attribute",e[e.Text=4]="Text"}(i=t.EncodingMode||(t.EncodingMode={})),t.decode=function(e,t){void 0===t&&(t=r.XML);var n="number"==typeof t?{level:t}:t;return n.level===r.HTML?n.mode===o.Strict?(0,a.decodeHTMLStrict)(e):(0,a.decodeHTML)(e):(0,a.decodeXML)(e)},t.decodeStrict=function(e,t){void 0===t&&(t=r.XML);var n="number"==typeof t?{level:t}:t;return n.level===r.HTML?n.mode===o.Legacy?(0,a.decodeHTML)(e):(0,a.decodeHTMLStrict)(e):(0,a.decodeXML)(e)},t.encode=function(e,t){void 0===t&&(t=r.XML);var n="number"==typeof t?{level:t}:t;return n.mode===i.UTF8?(0,s.escapeUTF8)(e):n.mode===i.Attribute?(0,s.escapeAttribute)(e):n.mode===i.Text?(0,s.escapeText)(e):n.level===r.HTML?n.mode===i.ASCII?(0,l.encodeNonAsciiHTML)(e):(0,l.encodeHTML)(e):(0,s.encodeXML)(e)};var u=n(8168);Object.defineProperty(t,"encodeXML",{enumerable:!0,get:function(){return u.encodeXML}}),Object.defineProperty(t,"escape",{enumerable:!0,get:function(){return u.escape}}),Object.defineProperty(t,"escapeUTF8",{enumerable:!0,get:function(){return u.escapeUTF8}}),Object.defineProperty(t,"escapeAttribute",{enumerable:!0,get:function(){return u.escapeAttribute}}),Object.defineProperty(t,"escapeText",{enumerable:!0,get:function(){return u.escapeText}});var c=n(6913);Object.defineProperty(t,"encodeHTML",{enumerable:!0,get:function(){return c.encodeHTML}}),Object.defineProperty(t,"encodeNonAsciiHTML",{enumerable:!0,get:function(){return c.encodeNonAsciiHTML}}),Object.defineProperty(t,"encodeHTML4",{enumerable:!0,get:function(){return c.encodeHTML}}),Object.defineProperty(t,"encodeHTML5",{enumerable:!0,get:function(){return c.encodeHTML}});var d=n(4753);Object.defineProperty(t,"decodeXML",{enumerable:!0,get:function(){return d.decodeXML}}),Object.defineProperty(t,"decodeHTML",{enumerable:!0,get:function(){return d.decodeHTML}}),Object.defineProperty(t,"decodeHTMLStrict",{enumerable:!0,get:function(){return d.decodeHTMLStrict}}),Object.defineProperty(t,"decodeHTML4",{enumerable:!0,get:function(){return d.decodeHTML}}),Object.defineProperty(t,"decodeHTML5",{enumerable:!0,get:function(){return d.decodeHTML}}),Object.defineProperty(t,"decodeHTML4Strict",{enumerable:!0,get:function(){return d.decodeHTMLStrict}}),Object.defineProperty(t,"decodeHTML5Strict",{enumerable:!0,get:function(){return d.decodeHTMLStrict}}),Object.defineProperty(t,"decodeXMLStrict",{enumerable:!0,get:function(){return d.decodeXML}})},3658:(e,t,n)=>{"use strict";var r;n.r(t),n.d(t,{AttributeAction:()=>i,IgnoreCaseMode:()=>o,SelectorType:()=>r,isTraversal:()=>c,parse:()=>m,stringify:()=>E}),function(e){e.Attribute="attribute",e.Pseudo="pseudo",e.PseudoElement="pseudo-element",e.Tag="tag",e.Universal="universal",e.Adjacent="adjacent",e.Child="child",e.Descendant="descendant",e.Parent="parent",e.Sibling="sibling",e.ColumnCombinator="column-combinator"}(r||(r={}));const o={Unknown:null,QuirksMode:"quirks",IgnoreCase:!0,CaseSensitive:!1};var i;!function(e){e.Any="any",e.Element="element",e.End="end",e.Equals="equals",e.Exists="exists",e.Hyphen="hyphen",e.Not="not",e.Start="start"}(i||(i={}));const a=/^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/,l=/\\([\da-f]{1,6}\s?|(\s)|.)/gi,s=new Map([[126,i.Element],[94,i.Start],[36,i.End],[42,i.Any],[33,i.Not],[124,i.Hyphen]]),u=new Set(["has","not","matches","is","where","host","host-context"]);function c(e){switch(e.type){case r.Adjacent:case r.Child:case r.Descendant:case r.Parent:case r.Sibling:case r.ColumnCombinator:return!0;default:return!1}}const d=new Set(["contains","icontains"]);function f(e,t,n){const r=parseInt(t,16)-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)}function p(e){return e.replace(l,f)}function h(e){return 39===e||34===e}function g(e){return 32===e||9===e||10===e||12===e||13===e}function m(e){const t=[],n=v(t,`${e}`,0);if(n0&&n0&&c(o[o.length-1]))throw new Error("Did not expect successive traversals.")}function A(e){o.length>0&&o[o.length-1].type===r.Descendant?o[o.length-1].type=e:(y(),o.push({type:e}))}function w(e,t){o.push({type:r.Attribute,name:e,action:t,value:l(1),namespace:null,ignoreCase:"quirks"})}function x(){if(o.length&&o[o.length-1].type===r.Descendant&&o.pop(),0===o.length)throw new Error("Empty sub-selector");e.push(o)}if(f(0),t.length===n)return n;e:for(;ne.charCodeAt(0)))),w=new Set(y.map((e=>e.charCodeAt(0)))),x=new Set([...y,"~","^","$","*","+","!","|",":","[","]"," ","."].map((e=>e.charCodeAt(0))));function E(e){return e.map((e=>e.map(k).join(""))).join(", ")}function k(e,t,n){switch(e.type){case r.Child:return 0===t?"> ":" > ";case r.Parent:return 0===t?"< ":" < ";case r.Sibling:return 0===t?"~ ":" ~ ";case r.Adjacent:return 0===t?"+ ":" + ";case r.Descendant:return" ";case r.ColumnCombinator:return 0===t?"|| ":" || ";case r.Universal:return"*"===e.namespace&&t+10?r+e.slice(n):e}},1427:e=>{var t=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},n=-1;t.Diff=function(e,t){return[e,t]},t.prototype.diff_main=function(e,n,r,o){void 0===o&&(o=this.Diff_Timeout<=0?Number.MAX_VALUE:(new Date).getTime()+1e3*this.Diff_Timeout);var i=o;if(null==e||null==n)throw new Error("Null input. (diff_main)");if(e==n)return e?[new t.Diff(0,e)]:[];void 0===r&&(r=!0);var a=r,l=this.diff_commonPrefix(e,n),s=e.substring(0,l);e=e.substring(l),n=n.substring(l),l=this.diff_commonSuffix(e,n);var u=e.substring(e.length-l);e=e.substring(0,e.length-l),n=n.substring(0,n.length-l);var c=this.diff_compute_(e,n,a,i);return s&&c.unshift(new t.Diff(0,s)),u&&c.push(new t.Diff(0,u)),this.diff_cleanupMerge(c),c},t.prototype.diff_compute_=function(e,r,o,i){var a;if(!e)return[new t.Diff(1,r)];if(!r)return[new t.Diff(n,e)];var l=e.length>r.length?e:r,s=e.length>r.length?r:e,u=l.indexOf(s);if(-1!=u)return a=[new t.Diff(1,l.substring(0,u)),new t.Diff(0,s),new t.Diff(1,l.substring(u+s.length))],e.length>r.length&&(a[0][0]=a[2][0]=n),a;if(1==s.length)return[new t.Diff(n,e),new t.Diff(1,r)];var c=this.diff_halfMatch_(e,r);if(c){var d=c[0],f=c[1],p=c[2],h=c[3],g=c[4],m=this.diff_main(d,p,o,i),v=this.diff_main(f,h,o,i);return m.concat([new t.Diff(0,g)],v)}return o&&e.length>100&&r.length>100?this.diff_lineMode_(e,r,i):this.diff_bisect_(e,r,i)},t.prototype.diff_lineMode_=function(e,r,o){var i=this.diff_linesToChars_(e,r);e=i.chars1,r=i.chars2;var a=i.lineArray,l=this.diff_main(e,r,!1,o);this.diff_charsToLines_(l,a),this.diff_cleanupSemantic(l),l.push(new t.Diff(0,""));for(var s=0,u=0,c=0,d="",f="";s=1&&c>=1){l.splice(s-u-c,u+c),s=s-u-c;for(var p=this.diff_main(d,f,!1,o),h=p.length-1;h>=0;h--)l.splice(s,0,p[h]);s+=p.length}c=0,u=0,d="",f=""}s++}return l.pop(),l},t.prototype.diff_bisect_=function(e,r,o){for(var i=e.length,a=r.length,l=Math.ceil((i+a)/2),s=l,u=2*l,c=new Array(u),d=new Array(u),f=0;fo);y++){for(var A=-y+g;A<=y-m;A+=2){for(var w=s+A,x=(C=A==-y||A!=y&&c[w-1]i)m+=2;else if(x>a)g+=2;else if(h&&(S=s+p-A)>=0&&S=(k=i-d[S]))return this.diff_bisectSplit_(e,r,C,x,o)}for(var E=-y+v;E<=y-b;E+=2){for(var k,S=s+E,_=(k=E==-y||E!=y&&d[S-1]i)b+=2;else if(_>a)v+=2;else if(!h){var C;if((w=s+p-E)>=0&&w=(k=i-k))return this.diff_bisectSplit_(e,r,C,x,o)}}}return[new t.Diff(n,e),new t.Diff(1,r)]},t.prototype.diff_bisectSplit_=function(e,t,n,r,o){var i=e.substring(0,n),a=t.substring(0,r),l=e.substring(n),s=t.substring(r),u=this.diff_main(i,a,!1,o),c=this.diff_main(l,s,!1,o);return u.concat(c)},t.prototype.diff_linesToChars_=function(e,t){var n=[],r={};function o(e){for(var t="",o=0,a=-1,l=n.length;ar?e=e.substring(n-r):nt.length?e:t,r=e.length>t.length?t:e;if(n.length<4||2*r.length=e.length?[r,i,a,l,c]:null}var a,l,s,u,c,d=i(n,r,Math.ceil(n.length/4)),f=i(n,r,Math.ceil(n.length/2));return d||f?(a=f?d&&d[4].length>f[4].length?d:f:d,e.length>t.length?(l=a[0],s=a[1],u=a[2],c=a[3]):(u=a[0],c=a[1],l=a[2],s=a[3]),[l,s,u,c,a[4]]):null},t.prototype.diff_cleanupSemantic=function(e){for(var r=!1,o=[],i=0,a=null,l=0,s=0,u=0,c=0,d=0;l0?o[i-1]:-1,s=0,u=0,c=0,d=0,a=null,r=!0)),l++;for(r&&this.diff_cleanupMerge(e),this.diff_cleanupSemanticLossless(e),l=1;l=g?(h>=f.length/2||h>=p.length/2)&&(e.splice(l,0,new t.Diff(0,p.substring(0,h))),e[l-1][1]=f.substring(0,f.length-h),e[l+1][1]=p.substring(h),l++):(g>=f.length/2||g>=p.length/2)&&(e.splice(l,0,new t.Diff(0,f.substring(0,g))),e[l-1][0]=1,e[l-1][1]=p.substring(0,p.length-g),e[l+1][0]=n,e[l+1][1]=f.substring(g),l++),l++}l++}},t.prototype.diff_cleanupSemanticLossless=function(e){function n(e,n){if(!e||!n)return 6;var r=e.charAt(e.length-1),o=n.charAt(0),i=r.match(t.nonAlphaNumericRegex_),a=o.match(t.nonAlphaNumericRegex_),l=i&&r.match(t.whitespaceRegex_),s=a&&o.match(t.whitespaceRegex_),u=l&&r.match(t.linebreakRegex_),c=s&&o.match(t.linebreakRegex_),d=u&&e.match(t.blanklineEndRegex_),f=c&&n.match(t.blanklineStartRegex_);return d||f?5:u||c?4:i&&!l&&s?3:l||s?2:i||a?1:0}for(var r=1;r=f&&(f=p,u=o,c=i,d=a)}e[r-1][1]!=u&&(u?e[r-1][1]=u:(e.splice(r-1,1),r--),e[r][1]=c,d?e[r+1][1]=d:(e.splice(r+1,1),r--))}r++}},t.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,t.whitespaceRegex_=/\s/,t.linebreakRegex_=/[\r\n]/,t.blanklineEndRegex_=/\n\r?\n$/,t.blanklineStartRegex_=/^\r?\n\r?\n/,t.prototype.diff_cleanupEfficiency=function(e){for(var r=!1,o=[],i=0,a=null,l=0,s=!1,u=!1,c=!1,d=!1;l0?o[i-1]:-1,c=d=!1),r=!0)),l++;r&&this.diff_cleanupMerge(e)},t.prototype.diff_cleanupMerge=function(e){e.push(new t.Diff(0,""));for(var r,o=0,i=0,a=0,l="",s="";o1?(0!==i&&0!==a&&(0!==(r=this.diff_commonPrefix(s,l))&&(o-i-a>0&&0==e[o-i-a-1][0]?e[o-i-a-1][1]+=s.substring(0,r):(e.splice(0,0,new t.Diff(0,s.substring(0,r))),o++),s=s.substring(r),l=l.substring(r)),0!==(r=this.diff_commonSuffix(s,l))&&(e[o][1]=s.substring(s.length-r)+e[o][1],s=s.substring(0,s.length-r),l=l.substring(0,l.length-r))),o-=i+a,e.splice(o,i+a),l.length&&(e.splice(o,0,new t.Diff(n,l)),o++),s.length&&(e.splice(o,0,new t.Diff(1,s)),o++),o++):0!==o&&0==e[o-1][0]?(e[o-1][1]+=e[o][1],e.splice(o,1)):o++,a=0,i=0,l="",s=""}""===e[e.length-1][1]&&e.pop();var u=!1;for(o=1;ot));r++)a=o,l=i;return e.length!=r&&e[r][0]===n?l:l+(t-a)},t.prototype.diff_prettyHtml=function(e){for(var t=[],r=/&/g,o=//g,a=/\n/g,l=0;l");switch(s){case 1:t[l]=''+u+"";break;case n:t[l]=''+u+"";break;case 0:t[l]=""+u+""}}return t.join("")},t.prototype.diff_text1=function(e){for(var t=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var r=this.match_alphabet_(t),o=this;function i(e,r){var i=e/t.length,a=Math.abs(n-r);return o.Match_Distance?i+a/o.Match_Distance:a?1:i}var a=this.Match_Threshold,l=e.indexOf(t,n);-1!=l&&(a=Math.min(i(0,l),a),-1!=(l=e.lastIndexOf(t,n+t.length))&&(a=Math.min(i(0,l),a)));var s,u,c=1<=h;v--){var b=r[e.charAt(v-1)];if(m[v]=0===p?(m[v+1]<<1|1)&b:(m[v+1]<<1|1)&b|(d[v+1]|d[v])<<1|1|d[v+1],m[v]&c){var y=i(p,v-1);if(y<=a){if(a=y,!((l=v-1)>n))break;h=Math.max(1,2*n-l)}}}if(i(p+1,n)>a)break;d=m}return l},t.prototype.match_alphabet_=function(e){for(var t={},n=0;n2&&(this.diff_cleanupSemantic(a),this.diff_cleanupEfficiency(a));else if(e&&"object"==typeof e&&void 0===r&&void 0===o)a=e,i=this.diff_text1(a);else if("string"==typeof e&&r&&"object"==typeof r&&void 0===o)i=e,a=r;else{if("string"!=typeof e||"string"!=typeof r||!o||"object"!=typeof o)throw new Error("Unknown call format to patch_make.");i=e,a=o}if(0===a.length)return[];for(var l=[],s=new t.patch_obj,u=0,c=0,d=0,f=i,p=i,h=0;h=2*this.Patch_Margin&&u&&(this.patch_addContext_(s,f),l.push(s),s=new t.patch_obj,u=0,f=p,c=d)}1!==g&&(c+=m.length),g!==n&&(d+=m.length)}return u&&(this.patch_addContext_(s,f),l.push(s)),l},t.prototype.patch_deepCopy=function(e){for(var n=[],r=0;rthis.Match_MaxBits?-1!=(l=this.match_main(t,c.substring(0,this.Match_MaxBits),u))&&(-1==(d=this.match_main(t,c.substring(c.length-this.Match_MaxBits),u+c.length-this.Match_MaxBits))||l>=d)&&(l=-1):l=this.match_main(t,c,u),-1==l)i[a]=!1,o-=e[a].length2-e[a].length1;else if(i[a]=!0,o=l-u,c==(s=-1==d?t.substring(l,l+c.length):t.substring(l,d+this.Match_MaxBits)))t=t.substring(0,l)+this.diff_text2(e[a].diffs)+t.substring(l+c.length);else{var f=this.diff_main(c,s,!1);if(c.length>this.Match_MaxBits&&this.diff_levenshtein(f)/c.length>this.Patch_DeleteThreshold)i[a]=!1;else{this.diff_cleanupSemanticLossless(f);for(var p,h=0,g=0;ga[0][1].length){var l=n-a[0][1].length;a[0][1]=r.substring(a[0][1].length)+a[0][1],i.start1-=l,i.start2-=l,i.length1+=l,i.length2+=l}return 0==(a=(i=e[e.length-1]).diffs).length||0!=a[a.length-1][0]?(a.push(new t.Diff(0,r)),i.length1+=n,i.length2+=n):n>a[a.length-1][1].length&&(l=n-a[a.length-1][1].length,a[a.length-1][1]+=r.substring(0,l),i.length1+=l,i.length2+=l),r},t.prototype.patch_splitMax=function(e){for(var r=this.Match_MaxBits,o=0;o2*r?(u.length1+=f.length,a+=f.length,c=!1,u.diffs.push(new t.Diff(d,f)),i.diffs.shift()):(f=f.substring(0,r-u.length1-this.Patch_Margin),u.length1+=f.length,a+=f.length,0===d?(u.length2+=f.length,l+=f.length):c=!1,u.diffs.push(new t.Diff(d,f)),f==i.diffs[0][1]?i.diffs.shift():i.diffs[0][1]=i.diffs[0][1].substring(f.length))}s=(s=this.diff_text2(u.diffs)).substring(s.length-this.Patch_Margin);var p=this.diff_text1(i.diffs).substring(0,this.Patch_Margin);""!==p&&(u.length1+=p.length,u.length2+=p.length,0!==u.diffs.length&&0===u.diffs[u.diffs.length-1][0]?u.diffs[u.diffs.length-1][1]+=p:u.diffs.push(new t.Diff(0,p))),c||e.splice(++o,0,u)}}},t.prototype.patch_toText=function(e){for(var t=[],n=0;n0?this.children[this.children.length-1]:null},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"childNodes",{get:function(){return this.children},set:function(e){this.children=e},enumerable:!1,configurable:!0}),t}(l);t.NodeWithChildren=f;var p=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.type=a.ElementType.CDATA,t}return o(t,e),Object.defineProperty(t.prototype,"nodeType",{get:function(){return 4},enumerable:!1,configurable:!0}),t}(f);t.CDATA=p;var h=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.type=a.ElementType.Root,t}return o(t,e),Object.defineProperty(t.prototype,"nodeType",{get:function(){return 9},enumerable:!1,configurable:!0}),t}(f);t.Document=h;var g=function(e){function t(t,n,r,o){void 0===r&&(r=[]),void 0===o&&(o="script"===t?a.ElementType.Script:"style"===t?a.ElementType.Style:a.ElementType.Tag);var i=e.call(this,r)||this;return i.name=t,i.attribs=n,i.type=o,i}return o(t,e),Object.defineProperty(t.prototype,"nodeType",{get:function(){return 1},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"tagName",{get:function(){return this.name},set:function(e){this.name=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"attributes",{get:function(){var e=this;return Object.keys(this.attribs).map((function(t){var n,r;return{name:t,value:e.attribs[t],namespace:null===(n=e["x-attribsNamespace"])||void 0===n?void 0:n[t],prefix:null===(r=e["x-attribsPrefix"])||void 0===r?void 0:r[t]}}))},enumerable:!1,configurable:!0}),t}(f);function m(e){return(0,a.isTag)(e)}function v(e){return e.type===a.ElementType.CDATA}function b(e){return e.type===a.ElementType.Text}function y(e){return e.type===a.ElementType.Comment}function A(e){return e.type===a.ElementType.Directive}function w(e){return e.type===a.ElementType.Root}function x(e,t){var n;if(void 0===t&&(t=!1),b(e))n=new u(e.data);else if(y(e))n=new c(e.data);else if(m(e)){var r=t?E(e.children):[],o=new g(e.name,i({},e.attribs),r);r.forEach((function(e){return e.parent=o})),null!=e.namespace&&(o.namespace=e.namespace),e["x-attribsNamespace"]&&(o["x-attribsNamespace"]=i({},e["x-attribsNamespace"])),e["x-attribsPrefix"]&&(o["x-attribsPrefix"]=i({},e["x-attribsPrefix"])),n=o}else if(v(e)){r=t?E(e.children):[];var a=new p(r);r.forEach((function(e){return e.parent=a})),n=a}else if(w(e)){r=t?E(e.children):[];var l=new h(r);r.forEach((function(e){return e.parent=l})),e["x-mode"]&&(l["x-mode"]=e["x-mode"]),n=l}else{if(!A(e))throw new Error("Not implemented yet: ".concat(e.type));var s=new d(e.name,e.data);null!=e["x-name"]&&(s["x-name"]=e["x-name"],s["x-publicId"]=e["x-publicId"],s["x-systemId"]=e["x-systemId"]),n=s}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,null!=e.sourceCodeLocation&&(n.sourceCodeLocation=e.sourceCodeLocation),n}function E(e){for(var t=e.map((function(e){return x(e,!0)})),n=1;n{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.Doctype=t.CDATA=t.Tag=t.Style=t.Script=t.Comment=t.Directive=t.Text=t.Root=t.isTag=t.ElementType=void 0,function(e){e.Root="root",e.Text="text",e.Directive="directive",e.Comment="comment",e.Script="script",e.Style="style",e.Tag="tag",e.CDATA="cdata",e.Doctype="doctype"}(n=t.ElementType||(t.ElementType={})),t.isTag=function(e){return e.type===n.Tag||e.type===n.Script||e.type===n.Style},t.Root=n.Root,t.Text=n.Text,t.Directive=n.Directive,t.Comment=n.Comment,t.Script=n.Script,t.Style=n.Style,t.Tag=n.Tag,t.CDATA=n.CDATA,t.Doctype=n.Doctype},161:e=>{"use strict";var t=Object.prototype.hasOwnProperty,n="~";function r(){}function o(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function i(e,t,r,i,a){if("function"!=typeof r)throw new TypeError("The listener must be a function");var l=new o(r,i||e,a),s=n?n+t:t;return e._events[s]?e._events[s].fn?e._events[s]=[e._events[s],l]:e._events[s].push(l):(e._events[s]=l,e._eventsCount++),e}function a(e,t){0==--e._eventsCount?e._events=new r:delete e._events[t]}function l(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=Object.create(null),(new r).__proto__||(n=!1)),l.prototype.eventNames=function(){var e,r,o=[];if(0===this._eventsCount)return o;for(r in e=this._events)t.call(e,r)&&o.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(e)):o},l.prototype.listeners=function(e){var t=n?n+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var o=0,i=r.length,a=new Array(i);o{var r;!function(){"use strict";var o=!("undefined"==typeof window||!window.document||!window.document.createElement),i={canUseDOM:o,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:o&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:o&&!!window.screen};void 0===(r=function(){return i}.call(t,n,t,e))||(e.exports=r)}()},9385:e=>{"use strict";var t=Object.prototype.hasOwnProperty,n=Object.prototype.toString,r=Object.defineProperty,o=Object.getOwnPropertyDescriptor,i=function(e){return"function"==typeof Array.isArray?Array.isArray(e):"[object Array]"===n.call(e)},a=function(e){if(!e||"[object Object]"!==n.call(e))return!1;var r,o=t.call(e,"constructor"),i=e.constructor&&e.constructor.prototype&&t.call(e.constructor.prototype,"isPrototypeOf");if(e.constructor&&!o&&!i)return!1;for(r in e);return void 0===r||t.call(e,r)},l=function(e,t){r&&"__proto__"===t.name?r(e,t.name,{enumerable:!0,configurable:!0,value:t.newValue,writable:!0}):e[t.name]=t.newValue},s=function(e,n){if("__proto__"===n){if(!t.call(e,n))return;if(o)return o(e,n).value}return e[n]};e.exports=function e(){var t,n,r,o,u,c,d=arguments[0],f=1,p=arguments.length,h=!1;for("boolean"==typeof d&&(h=d,d=arguments[1]||{},f=2),(null==d||"object"!=typeof d&&"function"!=typeof d)&&(d={});f{"use strict";e.exports=function(e){var t=e.uri,n=e.name,r=e.type;this.uri=t,this.name=n,this.type=r}},2929:(e,t,n)=>{"use strict";var r=n(1278);e.exports=function e(t,n,o){var i;void 0===n&&(n=""),void 0===o&&(o=r);var a=new Map;function l(e,t){var n=a.get(t);n?n.push.apply(n,e):a.set(t,e)}if(o(t))i=null,l([n],t);else{var s=n?n+".":"";if("undefined"!=typeof FileList&&t instanceof FileList)i=Array.prototype.map.call(t,(function(e,t){return l([""+s+t],e),null}));else if(Array.isArray(t))i=t.map((function(t,n){var r=e(t,""+s+n,o);return r.files.forEach(l),r.clone}));else if(t&&t.constructor===Object)for(var u in i={},t){var c=e(t[u],""+s+u,o);c.files.forEach(l),i[u]=c.clone}else i=t}return{clone:i,files:a}}},9384:(e,t,n)=>{"use strict";t.ReactNativeFile=n(7570),t.extractFiles=n(2929),t.isExtractableFile=n(1278)},1278:(e,t,n)=>{"use strict";var r=n(7570);e.exports=function(e){return"undefined"!=typeof File&&e instanceof File||"undefined"!=typeof Blob&&e instanceof Blob||e instanceof r}},8749:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var o=n(9384),i=r(n(5145)),a=function(e){return o.isExtractableFile(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe};t.default=function(e,t,n){var r=o.extractFiles({query:e,variables:t,operationName:n},"",a),l=r.clone,s=r.files;if(0===s.size){if(!Array.isArray(e))return JSON.stringify(l);if(void 0!==t&&!Array.isArray(t))throw new Error("Cannot create request body with given variable type, array expected");var u=e.reduce((function(e,n,r){return e.push({query:n,variables:t?t[r]:void 0}),e}),[]);return JSON.stringify(u)}var c=new("undefined"==typeof FormData?i.default:FormData);c.append("operations",JSON.stringify(l));var d={},f=0;return s.forEach((function(e){d[++f]=e})),c.append("map",JSON.stringify(d)),f=0,s.forEach((function(e,t){c.append(""+ ++f,t)})),c}},6647:function(e,t,n){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]{e.exports="object"==typeof self?self.FormData:window.FormData},2678:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=e.prototype.toJSON;"function"==typeof t||(0,r.default)(0),e.prototype.inspect=t,o.default&&(e.prototype[o.default]=t)};var r=i(n(5006)),o=i(n(8019));function i(e){return e&&e.__esModule?e:{default:e}}},8048:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return a(e,[])};var r,o=(r=n(8019))&&r.__esModule?r:{default:r};function i(e){return i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i(e)}function a(e,t){switch(i(e)){case"string":return JSON.stringify(e);case"function":return e.name?"[function ".concat(e.name,"]"):"[function]";case"object":return null===e?"null":function(e,t){if(-1!==t.indexOf(e))return"[Circular]";var n=[].concat(t,[e]),r=function(e){var t=e[String(o.default)];return"function"==typeof t?t:"function"==typeof e.inspect?e.inspect:void 0}(e);if(void 0!==r){var i=r.call(e);if(i!==e)return"string"==typeof i?i:a(i,n)}else if(Array.isArray(e))return function(e,t){if(0===e.length)return"[]";if(t.length>2)return"[Array]";for(var n=Math.min(10,e.length),r=e.length-n,o=[],i=0;i1&&o.push("... ".concat(r," more items")),"["+o.join(", ")+"]"}(e,n);return function(e,t){var n=Object.keys(e);return 0===n.length?"{}":t.length>2?"["+function(e){var t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){var n=e.constructor.name;if("string"==typeof n&&""!==n)return n}return t}(e)+"]":"{ "+n.map((function(n){return n+": "+a(e[n],t)})).join(", ")+" }"}(e,n)}(e,t);default:return String(e)}}},5006:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){if(!Boolean(e))throw new Error(null!=t?t:"Unexpected invariant triggered.")}},8019:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):void 0;t.default=n},4560:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNode=function(e){return null!=e&&"string"==typeof e.kind},t.Token=t.Location=void 0;var r,o=(r=n(2678))&&r.__esModule?r:{default:r},i=function(){function e(e,t,n){this.start=e.start,this.end=t.end,this.startToken=e,this.endToken=t,this.source=n}return e.prototype.toJSON=function(){return{start:this.start,end:this.end}},e}();t.Location=i,(0,o.default)(i);var a=function(){function e(e,t,n,r,o,i,a){this.kind=e,this.start=t,this.end=n,this.line=r,this.column=o,this.value=a,this.prev=i,this.next=null}return e.prototype.toJSON=function(){return{kind:this.kind,value:this.value,line:this.line,column:this.column}},e}();t.Token=a,(0,o.default)(a)},9501:(e,t)=>{"use strict";function n(e){for(var t=0;ta&&n(t[l-1]);)--l;return t.slice(a,l).join("\n")},t.getBlockStringIndentation=r,t.printBlockString=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=-1===e.indexOf("\n"),o=" "===e[0]||"\t"===e[0],i='"'===e[e.length-1],a="\\"===e[e.length-1],l=!r||i||a||n,s="";return!l||r&&o||(s+="\n"+t),s+=t?e.replace(/\n/g,"\n"+t):e,l&&(s+="\n"),'"""'+s.replace(/"""/g,'\\"""')+'"""'}},3083:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.print=function(e){return(0,r.visit)(e,{leave:i})};var r=n(2624),o=n(9501),i={Name:function(e){return e.value},Variable:function(e){return"$"+e.name},Document:function(e){return l(e.definitions,"\n\n")+"\n"},OperationDefinition:function(e){var t=e.operation,n=e.name,r=u("(",l(e.variableDefinitions,", "),")"),o=l(e.directives," "),i=e.selectionSet;return n||o||r||"query"!==t?l([t,l([n,r]),o,i]," "):i},VariableDefinition:function(e){var t=e.variable,n=e.type,r=e.defaultValue,o=e.directives;return t+": "+n+u(" = ",r)+u(" ",l(o," "))},SelectionSet:function(e){return s(e.selections)},Field:function(e){var t=e.alias,n=e.name,r=e.arguments,o=e.directives,i=e.selectionSet,a=u("",t,": ")+n,s=a+u("(",l(r,", "),")");return s.length>80&&(s=a+u("(\n",c(l(r,"\n")),"\n)")),l([s,l(o," "),i]," ")},Argument:function(e){return e.name+": "+e.value},FragmentSpread:function(e){return"..."+e.name+u(" ",l(e.directives," "))},InlineFragment:function(e){var t=e.typeCondition,n=e.directives,r=e.selectionSet;return l(["...",u("on ",t),l(n," "),r]," ")},FragmentDefinition:function(e){var t=e.name,n=e.typeCondition,r=e.variableDefinitions,o=e.directives,i=e.selectionSet;return"fragment ".concat(t).concat(u("(",l(r,", "),")")," ")+"on ".concat(n," ").concat(u("",l(o," ")," "))+i},IntValue:function(e){return e.value},FloatValue:function(e){return e.value},StringValue:function(e,t){var n=e.value;return e.block?(0,o.printBlockString)(n,"description"===t?"":" "):JSON.stringify(n)},BooleanValue:function(e){return e.value?"true":"false"},NullValue:function(){return"null"},EnumValue:function(e){return e.value},ListValue:function(e){return"["+l(e.values,", ")+"]"},ObjectValue:function(e){return"{"+l(e.fields,", ")+"}"},ObjectField:function(e){return e.name+": "+e.value},Directive:function(e){return"@"+e.name+u("(",l(e.arguments,", "),")")},NamedType:function(e){return e.name},ListType:function(e){return"["+e.type+"]"},NonNullType:function(e){return e.type+"!"},SchemaDefinition:a((function(e){var t=e.directives,n=e.operationTypes;return l(["schema",l(t," "),s(n)]," ")})),OperationTypeDefinition:function(e){return e.operation+": "+e.type},ScalarTypeDefinition:a((function(e){return l(["scalar",e.name,l(e.directives," ")]," ")})),ObjectTypeDefinition:a((function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["type",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")})),FieldDefinition:a((function(e){var t=e.name,n=e.arguments,r=e.type,o=e.directives;return t+(f(n)?u("(\n",c(l(n,"\n")),"\n)"):u("(",l(n,", "),")"))+": "+r+u(" ",l(o," "))})),InputValueDefinition:a((function(e){var t=e.name,n=e.type,r=e.defaultValue,o=e.directives;return l([t+": "+n,u("= ",r),l(o," ")]," ")})),InterfaceTypeDefinition:a((function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["interface",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")})),UnionTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.types;return l(["union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")})),EnumTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.values;return l(["enum",t,l(n," "),s(r)]," ")})),EnumValueDefinition:a((function(e){return l([e.name,l(e.directives," ")]," ")})),InputObjectTypeDefinition:a((function(e){var t=e.name,n=e.directives,r=e.fields;return l(["input",t,l(n," "),s(r)]," ")})),DirectiveDefinition:a((function(e){var t=e.name,n=e.arguments,r=e.repeatable,o=e.locations;return"directive @"+t+(f(n)?u("(\n",c(l(n,"\n")),"\n)"):u("(",l(n,", "),")"))+(r?" repeatable":"")+" on "+l(o," | ")})),SchemaExtension:function(e){var t=e.directives,n=e.operationTypes;return l(["extend schema",l(t," "),s(n)]," ")},ScalarTypeExtension:function(e){return l(["extend scalar",e.name,l(e.directives," ")]," ")},ObjectTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["extend type",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")},InterfaceTypeExtension:function(e){var t=e.name,n=e.interfaces,r=e.directives,o=e.fields;return l(["extend interface",t,u("implements ",l(n," & ")),l(r," "),s(o)]," ")},UnionTypeExtension:function(e){var t=e.name,n=e.directives,r=e.types;return l(["extend union",t,l(n," "),r&&0!==r.length?"= "+l(r," | "):""]," ")},EnumTypeExtension:function(e){var t=e.name,n=e.directives,r=e.values;return l(["extend enum",t,l(n," "),s(r)]," ")},InputObjectTypeExtension:function(e){var t=e.name,n=e.directives,r=e.fields;return l(["extend input",t,l(n," "),s(r)]," ")}};function a(e){return function(t){return l([t.description,e(t)],"\n")}}function l(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return null!==(t=null==e?void 0:e.filter((function(e){return e})).join(n))&&void 0!==t?t:""}function s(e){return u("{\n",c(l(e,"\n")),"\n}")}function u(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return null!=t&&""!==t?e+t+n:""}function c(e){return u(" ",e.replace(/\n/g,"\n "))}function d(e){return-1!==e.indexOf("\n")}function f(e){return null!=e&&e.some(d)}},2624:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.visit=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:a,r=void 0,u=Array.isArray(e),c=[e],d=-1,f=[],p=void 0,h=void 0,g=void 0,m=[],v=[],b=e;do{var y=++d===c.length,A=y&&0!==f.length;if(y){if(h=0===v.length?void 0:m[m.length-1],p=g,g=v.pop(),A){if(u)p=p.slice();else{for(var w={},x=0,E=Object.keys(p);x\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g,c={"­":"shy","‌":"zwnj","‍":"zwj","‎":"lrm","⁣":"ic","⁢":"it","⁡":"af","‏":"rlm","​":"ZeroWidthSpace","⁠":"NoBreak","̑":"DownBreve","⃛":"tdot","⃜":"DotDot","\t":"Tab","\n":"NewLine"," ":"puncsp"," ":"MediumSpace"," ":"thinsp"," ":"hairsp"," ":"emsp13"," ":"ensp"," ":"emsp14"," ":"emsp"," ":"numsp"," ":"nbsp","  ":"ThickSpace","‾":"oline",_:"lowbar","‐":"dash","–":"ndash","—":"mdash","―":"horbar",",":"comma",";":"semi","⁏":"bsemi",":":"colon","⩴":"Colone","!":"excl","¡":"iexcl","?":"quest","¿":"iquest",".":"period","‥":"nldr","…":"mldr","·":"middot","'":"apos","‘":"lsquo","’":"rsquo","‚":"sbquo","‹":"lsaquo","›":"rsaquo",'"':"quot","“":"ldquo","”":"rdquo","„":"bdquo","«":"laquo","»":"raquo","(":"lpar",")":"rpar","[":"lsqb","]":"rsqb","{":"lcub","}":"rcub","⌈":"lceil","⌉":"rceil","⌊":"lfloor","⌋":"rfloor","⦅":"lopar","⦆":"ropar","⦋":"lbrke","⦌":"rbrke","⦍":"lbrkslu","⦎":"rbrksld","⦏":"lbrksld","⦐":"rbrkslu","⦑":"langd","⦒":"rangd","⦓":"lparlt","⦔":"rpargt","⦕":"gtlPar","⦖":"ltrPar","⟦":"lobrk","⟧":"robrk","⟨":"lang","⟩":"rang","⟪":"Lang","⟫":"Rang","⟬":"loang","⟭":"roang","❲":"lbbrk","❳":"rbbrk","‖":"Vert","§":"sect","¶":"para","@":"commat","*":"ast","/":"sol",undefined:null,"&":"amp","#":"num","%":"percnt","‰":"permil","‱":"pertenk","†":"dagger","‡":"Dagger","•":"bull","⁃":"hybull","′":"prime","″":"Prime","‴":"tprime","⁗":"qprime","‵":"bprime","⁁":"caret","`":"grave","´":"acute","˜":"tilde","^":"Hat","¯":"macr","˘":"breve","˙":"dot","¨":"die","˚":"ring","˝":"dblac","¸":"cedil","˛":"ogon",ˆ:"circ",ˇ:"caron","°":"deg","©":"copy","®":"reg","℗":"copysr",℘:"wp","℞":"rx","℧":"mho","℩":"iiota","←":"larr","↚":"nlarr","→":"rarr","↛":"nrarr","↑":"uarr","↓":"darr","↔":"harr","↮":"nharr","↕":"varr","↖":"nwarr","↗":"nearr","↘":"searr","↙":"swarr","↝":"rarrw","↝̸":"nrarrw","↞":"Larr","↟":"Uarr","↠":"Rarr","↡":"Darr","↢":"larrtl","↣":"rarrtl","↤":"mapstoleft","↥":"mapstoup","↦":"map","↧":"mapstodown","↩":"larrhk","↪":"rarrhk","↫":"larrlp","↬":"rarrlp","↭":"harrw","↰":"lsh","↱":"rsh","↲":"ldsh","↳":"rdsh","↵":"crarr","↶":"cularr","↷":"curarr","↺":"olarr","↻":"orarr","↼":"lharu","↽":"lhard","↾":"uharr","↿":"uharl","⇀":"rharu","⇁":"rhard","⇂":"dharr","⇃":"dharl","⇄":"rlarr","⇅":"udarr","⇆":"lrarr","⇇":"llarr","⇈":"uuarr","⇉":"rrarr","⇊":"ddarr","⇋":"lrhar","⇌":"rlhar","⇐":"lArr","⇍":"nlArr","⇑":"uArr","⇒":"rArr","⇏":"nrArr","⇓":"dArr","⇔":"iff","⇎":"nhArr","⇕":"vArr","⇖":"nwArr","⇗":"neArr","⇘":"seArr","⇙":"swArr","⇚":"lAarr","⇛":"rAarr","⇝":"zigrarr","⇤":"larrb","⇥":"rarrb","⇵":"duarr","⇽":"loarr","⇾":"roarr","⇿":"hoarr","∀":"forall","∁":"comp","∂":"part","∂̸":"npart","∃":"exist","∄":"nexist","∅":"empty","∇":"Del","∈":"in","∉":"notin","∋":"ni","∌":"notni","϶":"bepsi","∏":"prod","∐":"coprod","∑":"sum","+":"plus","±":"pm","÷":"div","×":"times","<":"lt","≮":"nlt","<⃒":"nvlt","=":"equals","≠":"ne","=⃥":"bne","⩵":"Equal",">":"gt","≯":"ngt",">⃒":"nvgt","¬":"not","|":"vert","¦":"brvbar","−":"minus","∓":"mp","∔":"plusdo","⁄":"frasl","∖":"setmn","∗":"lowast","∘":"compfn","√":"Sqrt","∝":"prop","∞":"infin","∟":"angrt","∠":"ang","∠⃒":"nang","∡":"angmsd","∢":"angsph","∣":"mid","∤":"nmid","∥":"par","∦":"npar","∧":"and","∨":"or","∩":"cap","∩︀":"caps","∪":"cup","∪︀":"cups","∫":"int","∬":"Int","∭":"tint","⨌":"qint","∮":"oint","∯":"Conint","∰":"Cconint","∱":"cwint","∲":"cwconint","∳":"awconint","∴":"there4","∵":"becaus","∶":"ratio","∷":"Colon","∸":"minusd","∺":"mDDot","∻":"homtht","∼":"sim","≁":"nsim","∼⃒":"nvsim","∽":"bsim","∽̱":"race","∾":"ac","∾̳":"acE","∿":"acd","≀":"wr","≂":"esim","≂̸":"nesim","≃":"sime","≄":"nsime","≅":"cong","≇":"ncong","≆":"simne","≈":"ap","≉":"nap","≊":"ape","≋":"apid","≋̸":"napid","≌":"bcong","≍":"CupCap","≭":"NotCupCap","≍⃒":"nvap","≎":"bump","≎̸":"nbump","≏":"bumpe","≏̸":"nbumpe","≐":"doteq","≐̸":"nedot","≑":"eDot","≒":"efDot","≓":"erDot","≔":"colone","≕":"ecolon","≖":"ecir","≗":"cire","≙":"wedgeq","≚":"veeeq","≜":"trie","≟":"equest","≡":"equiv","≢":"nequiv","≡⃥":"bnequiv","≤":"le","≰":"nle","≤⃒":"nvle","≥":"ge","≱":"nge","≥⃒":"nvge","≦":"lE","≦̸":"nlE","≧":"gE","≧̸":"ngE","≨︀":"lvnE","≨":"lnE","≩":"gnE","≩︀":"gvnE","≪":"ll","≪̸":"nLtv","≪⃒":"nLt","≫":"gg","≫̸":"nGtv","≫⃒":"nGt","≬":"twixt","≲":"lsim","≴":"nlsim","≳":"gsim","≵":"ngsim","≶":"lg","≸":"ntlg","≷":"gl","≹":"ntgl","≺":"pr","⊀":"npr","≻":"sc","⊁":"nsc","≼":"prcue","⋠":"nprcue","≽":"sccue","⋡":"nsccue","≾":"prsim","≿":"scsim","≿̸":"NotSucceedsTilde","⊂":"sub","⊄":"nsub","⊂⃒":"vnsub","⊃":"sup","⊅":"nsup","⊃⃒":"vnsup","⊆":"sube","⊈":"nsube","⊇":"supe","⊉":"nsupe","⊊︀":"vsubne","⊊":"subne","⊋︀":"vsupne","⊋":"supne","⊍":"cupdot","⊎":"uplus","⊏":"sqsub","⊏̸":"NotSquareSubset","⊐":"sqsup","⊐̸":"NotSquareSuperset","⊑":"sqsube","⋢":"nsqsube","⊒":"sqsupe","⋣":"nsqsupe","⊓":"sqcap","⊓︀":"sqcaps","⊔":"sqcup","⊔︀":"sqcups","⊕":"oplus","⊖":"ominus","⊗":"otimes","⊘":"osol","⊙":"odot","⊚":"ocir","⊛":"oast","⊝":"odash","⊞":"plusb","⊟":"minusb","⊠":"timesb","⊡":"sdotb","⊢":"vdash","⊬":"nvdash","⊣":"dashv","⊤":"top","⊥":"bot","⊧":"models","⊨":"vDash","⊭":"nvDash","⊩":"Vdash","⊮":"nVdash","⊪":"Vvdash","⊫":"VDash","⊯":"nVDash","⊰":"prurel","⊲":"vltri","⋪":"nltri","⊳":"vrtri","⋫":"nrtri","⊴":"ltrie","⋬":"nltrie","⊴⃒":"nvltrie","⊵":"rtrie","⋭":"nrtrie","⊵⃒":"nvrtrie","⊶":"origof","⊷":"imof","⊸":"mumap","⊹":"hercon","⊺":"intcal","⊻":"veebar","⊽":"barvee","⊾":"angrtvb","⊿":"lrtri","⋀":"Wedge","⋁":"Vee","⋂":"xcap","⋃":"xcup","⋄":"diam","⋅":"sdot","⋆":"Star","⋇":"divonx","⋈":"bowtie","⋉":"ltimes","⋊":"rtimes","⋋":"lthree","⋌":"rthree","⋍":"bsime","⋎":"cuvee","⋏":"cuwed","⋐":"Sub","⋑":"Sup","⋒":"Cap","⋓":"Cup","⋔":"fork","⋕":"epar","⋖":"ltdot","⋗":"gtdot","⋘":"Ll","⋘̸":"nLl","⋙":"Gg","⋙̸":"nGg","⋚︀":"lesg","⋚":"leg","⋛":"gel","⋛︀":"gesl","⋞":"cuepr","⋟":"cuesc","⋦":"lnsim","⋧":"gnsim","⋨":"prnsim","⋩":"scnsim","⋮":"vellip","⋯":"ctdot","⋰":"utdot","⋱":"dtdot","⋲":"disin","⋳":"isinsv","⋴":"isins","⋵":"isindot","⋵̸":"notindot","⋶":"notinvc","⋷":"notinvb","⋹":"isinE","⋹̸":"notinE","⋺":"nisd","⋻":"xnis","⋼":"nis","⋽":"notnivc","⋾":"notnivb","⌅":"barwed","⌆":"Barwed","⌌":"drcrop","⌍":"dlcrop","⌎":"urcrop","⌏":"ulcrop","⌐":"bnot","⌒":"profline","⌓":"profsurf","⌕":"telrec","⌖":"target","⌜":"ulcorn","⌝":"urcorn","⌞":"dlcorn","⌟":"drcorn","⌢":"frown","⌣":"smile","⌭":"cylcty","⌮":"profalar","⌶":"topbot","⌽":"ovbar","⌿":"solbar","⍼":"angzarr","⎰":"lmoust","⎱":"rmoust","⎴":"tbrk","⎵":"bbrk","⎶":"bbrktbrk","⏜":"OverParenthesis","⏝":"UnderParenthesis","⏞":"OverBrace","⏟":"UnderBrace","⏢":"trpezium","⏧":"elinters","␣":"blank","─":"boxh","│":"boxv","┌":"boxdr","┐":"boxdl","└":"boxur","┘":"boxul","├":"boxvr","┤":"boxvl","┬":"boxhd","┴":"boxhu","┼":"boxvh","═":"boxH","║":"boxV","╒":"boxdR","╓":"boxDr","╔":"boxDR","╕":"boxdL","╖":"boxDl","╗":"boxDL","╘":"boxuR","╙":"boxUr","╚":"boxUR","╛":"boxuL","╜":"boxUl","╝":"boxUL","╞":"boxvR","╟":"boxVr","╠":"boxVR","╡":"boxvL","╢":"boxVl","╣":"boxVL","╤":"boxHd","╥":"boxhD","╦":"boxHD","╧":"boxHu","╨":"boxhU","╩":"boxHU","╪":"boxvH","╫":"boxVh","╬":"boxVH","▀":"uhblk","▄":"lhblk","█":"block","░":"blk14","▒":"blk12","▓":"blk34","□":"squ","▪":"squf","▫":"EmptyVerySmallSquare","▭":"rect","▮":"marker","▱":"fltns","△":"xutri","▴":"utrif","▵":"utri","▸":"rtrif","▹":"rtri","▽":"xdtri","▾":"dtrif","▿":"dtri","◂":"ltrif","◃":"ltri","◊":"loz","○":"cir","◬":"tridot","◯":"xcirc","◸":"ultri","◹":"urtri","◺":"lltri","◻":"EmptySmallSquare","◼":"FilledSmallSquare","★":"starf","☆":"star","☎":"phone","♀":"female","♂":"male","♠":"spades","♣":"clubs","♥":"hearts","♦":"diams","♪":"sung","✓":"check","✗":"cross","✠":"malt","✶":"sext","❘":"VerticalSeparator","⟈":"bsolhsub","⟉":"suphsol","⟵":"xlarr","⟶":"xrarr","⟷":"xharr","⟸":"xlArr","⟹":"xrArr","⟺":"xhArr","⟼":"xmap","⟿":"dzigrarr","⤂":"nvlArr","⤃":"nvrArr","⤄":"nvHarr","⤅":"Map","⤌":"lbarr","⤍":"rbarr","⤎":"lBarr","⤏":"rBarr","⤐":"RBarr","⤑":"DDotrahd","⤒":"UpArrowBar","⤓":"DownArrowBar","⤖":"Rarrtl","⤙":"latail","⤚":"ratail","⤛":"lAtail","⤜":"rAtail","⤝":"larrfs","⤞":"rarrfs","⤟":"larrbfs","⤠":"rarrbfs","⤣":"nwarhk","⤤":"nearhk","⤥":"searhk","⤦":"swarhk","⤧":"nwnear","⤨":"toea","⤩":"tosa","⤪":"swnwar","⤳":"rarrc","⤳̸":"nrarrc","⤵":"cudarrr","⤶":"ldca","⤷":"rdca","⤸":"cudarrl","⤹":"larrpl","⤼":"curarrm","⤽":"cularrp","⥅":"rarrpl","⥈":"harrcir","⥉":"Uarrocir","⥊":"lurdshar","⥋":"ldrushar","⥎":"LeftRightVector","⥏":"RightUpDownVector","⥐":"DownLeftRightVector","⥑":"LeftUpDownVector","⥒":"LeftVectorBar","⥓":"RightVectorBar","⥔":"RightUpVectorBar","⥕":"RightDownVectorBar","⥖":"DownLeftVectorBar","⥗":"DownRightVectorBar","⥘":"LeftUpVectorBar","⥙":"LeftDownVectorBar","⥚":"LeftTeeVector","⥛":"RightTeeVector","⥜":"RightUpTeeVector","⥝":"RightDownTeeVector","⥞":"DownLeftTeeVector","⥟":"DownRightTeeVector","⥠":"LeftUpTeeVector","⥡":"LeftDownTeeVector","⥢":"lHar","⥣":"uHar","⥤":"rHar","⥥":"dHar","⥦":"luruhar","⥧":"ldrdhar","⥨":"ruluhar","⥩":"rdldhar","⥪":"lharul","⥫":"llhard","⥬":"rharul","⥭":"lrhard","⥮":"udhar","⥯":"duhar","⥰":"RoundImplies","⥱":"erarr","⥲":"simrarr","⥳":"larrsim","⥴":"rarrsim","⥵":"rarrap","⥶":"ltlarr","⥸":"gtrarr","⥹":"subrarr","⥻":"suplarr","⥼":"lfisht","⥽":"rfisht","⥾":"ufisht","⥿":"dfisht","⦚":"vzigzag","⦜":"vangrt","⦝":"angrtvbd","⦤":"ange","⦥":"range","⦦":"dwangle","⦧":"uwangle","⦨":"angmsdaa","⦩":"angmsdab","⦪":"angmsdac","⦫":"angmsdad","⦬":"angmsdae","⦭":"angmsdaf","⦮":"angmsdag","⦯":"angmsdah","⦰":"bemptyv","⦱":"demptyv","⦲":"cemptyv","⦳":"raemptyv","⦴":"laemptyv","⦵":"ohbar","⦶":"omid","⦷":"opar","⦹":"operp","⦻":"olcross","⦼":"odsold","⦾":"olcir","⦿":"ofcir","⧀":"olt","⧁":"ogt","⧂":"cirscir","⧃":"cirE","⧄":"solb","⧅":"bsolb","⧉":"boxbox","⧍":"trisb","⧎":"rtriltri","⧏":"LeftTriangleBar","⧏̸":"NotLeftTriangleBar","⧐":"RightTriangleBar","⧐̸":"NotRightTriangleBar","⧜":"iinfin","⧝":"infintie","⧞":"nvinfin","⧣":"eparsl","⧤":"smeparsl","⧥":"eqvparsl","⧫":"lozf","⧴":"RuleDelayed","⧶":"dsol","⨀":"xodot","⨁":"xoplus","⨂":"xotime","⨄":"xuplus","⨆":"xsqcup","⨍":"fpartint","⨐":"cirfnint","⨑":"awint","⨒":"rppolint","⨓":"scpolint","⨔":"npolint","⨕":"pointint","⨖":"quatint","⨗":"intlarhk","⨢":"pluscir","⨣":"plusacir","⨤":"simplus","⨥":"plusdu","⨦":"plussim","⨧":"plustwo","⨩":"mcomma","⨪":"minusdu","⨭":"loplus","⨮":"roplus","⨯":"Cross","⨰":"timesd","⨱":"timesbar","⨳":"smashp","⨴":"lotimes","⨵":"rotimes","⨶":"otimesas","⨷":"Otimes","⨸":"odiv","⨹":"triplus","⨺":"triminus","⨻":"tritime","⨼":"iprod","⨿":"amalg","⩀":"capdot","⩂":"ncup","⩃":"ncap","⩄":"capand","⩅":"cupor","⩆":"cupcap","⩇":"capcup","⩈":"cupbrcap","⩉":"capbrcup","⩊":"cupcup","⩋":"capcap","⩌":"ccups","⩍":"ccaps","⩐":"ccupssm","⩓":"And","⩔":"Or","⩕":"andand","⩖":"oror","⩗":"orslope","⩘":"andslope","⩚":"andv","⩛":"orv","⩜":"andd","⩝":"ord","⩟":"wedbar","⩦":"sdote","⩪":"simdot","⩭":"congdot","⩭̸":"ncongdot","⩮":"easter","⩯":"apacir","⩰":"apE","⩰̸":"napE","⩱":"eplus","⩲":"pluse","⩳":"Esim","⩷":"eDDot","⩸":"equivDD","⩹":"ltcir","⩺":"gtcir","⩻":"ltquest","⩼":"gtquest","⩽":"les","⩽̸":"nles","⩾":"ges","⩾̸":"nges","⩿":"lesdot","⪀":"gesdot","⪁":"lesdoto","⪂":"gesdoto","⪃":"lesdotor","⪄":"gesdotol","⪅":"lap","⪆":"gap","⪇":"lne","⪈":"gne","⪉":"lnap","⪊":"gnap","⪋":"lEg","⪌":"gEl","⪍":"lsime","⪎":"gsime","⪏":"lsimg","⪐":"gsiml","⪑":"lgE","⪒":"glE","⪓":"lesges","⪔":"gesles","⪕":"els","⪖":"egs","⪗":"elsdot","⪘":"egsdot","⪙":"el","⪚":"eg","⪝":"siml","⪞":"simg","⪟":"simlE","⪠":"simgE","⪡":"LessLess","⪡̸":"NotNestedLessLess","⪢":"GreaterGreater","⪢̸":"NotNestedGreaterGreater","⪤":"glj","⪥":"gla","⪦":"ltcc","⪧":"gtcc","⪨":"lescc","⪩":"gescc","⪪":"smt","⪫":"lat","⪬":"smte","⪬︀":"smtes","⪭":"late","⪭︀":"lates","⪮":"bumpE","⪯":"pre","⪯̸":"npre","⪰":"sce","⪰̸":"nsce","⪳":"prE","⪴":"scE","⪵":"prnE","⪶":"scnE","⪷":"prap","⪸":"scap","⪹":"prnap","⪺":"scnap","⪻":"Pr","⪼":"Sc","⪽":"subdot","⪾":"supdot","⪿":"subplus","⫀":"supplus","⫁":"submult","⫂":"supmult","⫃":"subedot","⫄":"supedot","⫅":"subE","⫅̸":"nsubE","⫆":"supE","⫆̸":"nsupE","⫇":"subsim","⫈":"supsim","⫋︀":"vsubnE","⫋":"subnE","⫌︀":"vsupnE","⫌":"supnE","⫏":"csub","⫐":"csup","⫑":"csube","⫒":"csupe","⫓":"subsup","⫔":"supsub","⫕":"subsub","⫖":"supsup","⫗":"suphsub","⫘":"supdsub","⫙":"forkv","⫚":"topfork","⫛":"mlcp","⫤":"Dashv","⫦":"Vdashl","⫧":"Barv","⫨":"vBar","⫩":"vBarv","⫫":"Vbar","⫬":"Not","⫭":"bNot","⫮":"rnmid","⫯":"cirmid","⫰":"midcir","⫱":"topcir","⫲":"nhpar","⫳":"parsim","⫽":"parsl","⫽⃥":"nparsl","♭":"flat","♮":"natur","♯":"sharp","¤":"curren","¢":"cent",$:"dollar","£":"pound","¥":"yen","€":"euro","¹":"sup1","½":"half","⅓":"frac13","¼":"frac14","⅕":"frac15","⅙":"frac16","⅛":"frac18","²":"sup2","⅔":"frac23","⅖":"frac25","³":"sup3","¾":"frac34","⅗":"frac35","⅜":"frac38","⅘":"frac45","⅚":"frac56","⅝":"frac58","⅞":"frac78",𝒶:"ascr",𝕒:"aopf",𝔞:"afr",𝔸:"Aopf",𝔄:"Afr",𝒜:"Ascr",ª:"ordf",á:"aacute",Á:"Aacute",à:"agrave",À:"Agrave",ă:"abreve",Ă:"Abreve",â:"acirc",Â:"Acirc",å:"aring",Å:"angst",ä:"auml",Ä:"Auml",ã:"atilde",Ã:"Atilde",ą:"aogon",Ą:"Aogon",ā:"amacr",Ā:"Amacr",æ:"aelig",Æ:"AElig",𝒷:"bscr",𝕓:"bopf",𝔟:"bfr",𝔹:"Bopf",ℬ:"Bscr",𝔅:"Bfr",𝔠:"cfr",𝒸:"cscr",𝕔:"copf",ℭ:"Cfr",𝒞:"Cscr",ℂ:"Copf",ć:"cacute",Ć:"Cacute",ĉ:"ccirc",Ĉ:"Ccirc",č:"ccaron",Č:"Ccaron",ċ:"cdot",Ċ:"Cdot",ç:"ccedil",Ç:"Ccedil","℅":"incare",𝔡:"dfr",ⅆ:"dd",𝕕:"dopf",𝒹:"dscr",𝒟:"Dscr",𝔇:"Dfr",ⅅ:"DD",𝔻:"Dopf",ď:"dcaron",Ď:"Dcaron",đ:"dstrok",Đ:"Dstrok",ð:"eth",Ð:"ETH",ⅇ:"ee",ℯ:"escr",𝔢:"efr",𝕖:"eopf",ℰ:"Escr",𝔈:"Efr",𝔼:"Eopf",é:"eacute",É:"Eacute",è:"egrave",È:"Egrave",ê:"ecirc",Ê:"Ecirc",ě:"ecaron",Ě:"Ecaron",ë:"euml",Ë:"Euml",ė:"edot",Ė:"Edot",ę:"eogon",Ę:"Eogon",ē:"emacr",Ē:"Emacr",𝔣:"ffr",𝕗:"fopf",𝒻:"fscr",𝔉:"Ffr",𝔽:"Fopf",ℱ:"Fscr",ff:"fflig",ffi:"ffilig",ffl:"ffllig",fi:"filig",fj:"fjlig",fl:"fllig",ƒ:"fnof",ℊ:"gscr",𝕘:"gopf",𝔤:"gfr",𝒢:"Gscr",𝔾:"Gopf",𝔊:"Gfr",ǵ:"gacute",ğ:"gbreve",Ğ:"Gbreve",ĝ:"gcirc",Ĝ:"Gcirc",ġ:"gdot",Ġ:"Gdot",Ģ:"Gcedil",𝔥:"hfr",ℎ:"planckh",𝒽:"hscr",𝕙:"hopf",ℋ:"Hscr",ℌ:"Hfr",ℍ:"Hopf",ĥ:"hcirc",Ĥ:"Hcirc",ℏ:"hbar",ħ:"hstrok",Ħ:"Hstrok",𝕚:"iopf",𝔦:"ifr",𝒾:"iscr",ⅈ:"ii",𝕀:"Iopf",ℐ:"Iscr",ℑ:"Im",í:"iacute",Í:"Iacute",ì:"igrave",Ì:"Igrave",î:"icirc",Î:"Icirc",ï:"iuml",Ï:"Iuml",ĩ:"itilde",Ĩ:"Itilde",İ:"Idot",į:"iogon",Į:"Iogon",ī:"imacr",Ī:"Imacr",ij:"ijlig",IJ:"IJlig",ı:"imath",𝒿:"jscr",𝕛:"jopf",𝔧:"jfr",𝒥:"Jscr",𝔍:"Jfr",𝕁:"Jopf",ĵ:"jcirc",Ĵ:"Jcirc",ȷ:"jmath",𝕜:"kopf",𝓀:"kscr",𝔨:"kfr",𝒦:"Kscr",𝕂:"Kopf",𝔎:"Kfr",ķ:"kcedil",Ķ:"Kcedil",𝔩:"lfr",𝓁:"lscr",ℓ:"ell",𝕝:"lopf",ℒ:"Lscr",𝔏:"Lfr",𝕃:"Lopf",ĺ:"lacute",Ĺ:"Lacute",ľ:"lcaron",Ľ:"Lcaron",ļ:"lcedil",Ļ:"Lcedil",ł:"lstrok",Ł:"Lstrok",ŀ:"lmidot",Ŀ:"Lmidot",𝔪:"mfr",𝕞:"mopf",𝓂:"mscr",𝔐:"Mfr",𝕄:"Mopf",ℳ:"Mscr",𝔫:"nfr",𝕟:"nopf",𝓃:"nscr",ℕ:"Nopf",𝒩:"Nscr",𝔑:"Nfr",ń:"nacute",Ń:"Nacute",ň:"ncaron",Ň:"Ncaron",ñ:"ntilde",Ñ:"Ntilde",ņ:"ncedil",Ņ:"Ncedil","№":"numero",ŋ:"eng",Ŋ:"ENG",𝕠:"oopf",𝔬:"ofr",ℴ:"oscr",𝒪:"Oscr",𝔒:"Ofr",𝕆:"Oopf",º:"ordm",ó:"oacute",Ó:"Oacute",ò:"ograve",Ò:"Ograve",ô:"ocirc",Ô:"Ocirc",ö:"ouml",Ö:"Ouml",ő:"odblac",Ő:"Odblac",õ:"otilde",Õ:"Otilde",ø:"oslash",Ø:"Oslash",ō:"omacr",Ō:"Omacr",œ:"oelig",Œ:"OElig",𝔭:"pfr",𝓅:"pscr",𝕡:"popf",ℙ:"Popf",𝔓:"Pfr",𝒫:"Pscr",𝕢:"qopf",𝔮:"qfr",𝓆:"qscr",𝒬:"Qscr",𝔔:"Qfr",ℚ:"Qopf",ĸ:"kgreen",𝔯:"rfr",𝕣:"ropf",𝓇:"rscr",ℛ:"Rscr",ℜ:"Re",ℝ:"Ropf",ŕ:"racute",Ŕ:"Racute",ř:"rcaron",Ř:"Rcaron",ŗ:"rcedil",Ŗ:"Rcedil",𝕤:"sopf",𝓈:"sscr",𝔰:"sfr",𝕊:"Sopf",𝔖:"Sfr",𝒮:"Sscr","Ⓢ":"oS",ś:"sacute",Ś:"Sacute",ŝ:"scirc",Ŝ:"Scirc",š:"scaron",Š:"Scaron",ş:"scedil",Ş:"Scedil",ß:"szlig",𝔱:"tfr",𝓉:"tscr",𝕥:"topf",𝒯:"Tscr",𝔗:"Tfr",𝕋:"Topf",ť:"tcaron",Ť:"Tcaron",ţ:"tcedil",Ţ:"Tcedil","™":"trade",ŧ:"tstrok",Ŧ:"Tstrok",𝓊:"uscr",𝕦:"uopf",𝔲:"ufr",𝕌:"Uopf",𝔘:"Ufr",𝒰:"Uscr",ú:"uacute",Ú:"Uacute",ù:"ugrave",Ù:"Ugrave",ŭ:"ubreve",Ŭ:"Ubreve",û:"ucirc",Û:"Ucirc",ů:"uring",Ů:"Uring",ü:"uuml",Ü:"Uuml",ű:"udblac",Ű:"Udblac",ũ:"utilde",Ũ:"Utilde",ų:"uogon",Ų:"Uogon",ū:"umacr",Ū:"Umacr",𝔳:"vfr",𝕧:"vopf",𝓋:"vscr",𝔙:"Vfr",𝕍:"Vopf",𝒱:"Vscr",𝕨:"wopf",𝓌:"wscr",𝔴:"wfr",𝒲:"Wscr",𝕎:"Wopf",𝔚:"Wfr",ŵ:"wcirc",Ŵ:"Wcirc",𝔵:"xfr",𝓍:"xscr",𝕩:"xopf",𝕏:"Xopf",𝔛:"Xfr",𝒳:"Xscr",𝔶:"yfr",𝓎:"yscr",𝕪:"yopf",𝒴:"Yscr",𝔜:"Yfr",𝕐:"Yopf",ý:"yacute",Ý:"Yacute",ŷ:"ycirc",Ŷ:"Ycirc",ÿ:"yuml",Ÿ:"Yuml",𝓏:"zscr",𝔷:"zfr",𝕫:"zopf",ℨ:"Zfr",ℤ:"Zopf",𝒵:"Zscr",ź:"zacute",Ź:"Zacute",ž:"zcaron",Ž:"Zcaron",ż:"zdot",Ż:"Zdot",Ƶ:"imped",þ:"thorn",Þ:"THORN",ʼn:"napos",α:"alpha",Α:"Alpha",β:"beta",Β:"Beta",γ:"gamma",Γ:"Gamma",δ:"delta",Δ:"Delta",ε:"epsi",ϵ:"epsiv",Ε:"Epsilon",ϝ:"gammad",Ϝ:"Gammad",ζ:"zeta",Ζ:"Zeta",η:"eta",Η:"Eta",θ:"theta",ϑ:"thetav",Θ:"Theta",ι:"iota",Ι:"Iota",κ:"kappa",ϰ:"kappav",Κ:"Kappa",λ:"lambda",Λ:"Lambda",μ:"mu",µ:"micro",Μ:"Mu",ν:"nu",Ν:"Nu",ξ:"xi",Ξ:"Xi",ο:"omicron",Ο:"Omicron",π:"pi",ϖ:"piv",Π:"Pi",ρ:"rho",ϱ:"rhov",Ρ:"Rho",σ:"sigma",Σ:"Sigma",ς:"sigmaf",τ:"tau",Τ:"Tau",υ:"upsi",Υ:"Upsilon",ϒ:"Upsi",φ:"phi",ϕ:"phiv",Φ:"Phi",χ:"chi",Χ:"Chi",ψ:"psi",Ψ:"Psi",ω:"omega",Ω:"ohm",а:"acy",А:"Acy",б:"bcy",Б:"Bcy",в:"vcy",В:"Vcy",г:"gcy",Г:"Gcy",ѓ:"gjcy",Ѓ:"GJcy",д:"dcy",Д:"Dcy",ђ:"djcy",Ђ:"DJcy",е:"iecy",Е:"IEcy",ё:"iocy",Ё:"IOcy",є:"jukcy",Є:"Jukcy",ж:"zhcy",Ж:"ZHcy",з:"zcy",З:"Zcy",ѕ:"dscy",Ѕ:"DScy",и:"icy",И:"Icy",і:"iukcy",І:"Iukcy",ї:"yicy",Ї:"YIcy",й:"jcy",Й:"Jcy",ј:"jsercy",Ј:"Jsercy",к:"kcy",К:"Kcy",ќ:"kjcy",Ќ:"KJcy",л:"lcy",Л:"Lcy",љ:"ljcy",Љ:"LJcy",м:"mcy",М:"Mcy",н:"ncy",Н:"Ncy",њ:"njcy",Њ:"NJcy",о:"ocy",О:"Ocy",п:"pcy",П:"Pcy",р:"rcy",Р:"Rcy",с:"scy",С:"Scy",т:"tcy",Т:"Tcy",ћ:"tshcy",Ћ:"TSHcy",у:"ucy",У:"Ucy",ў:"ubrcy",Ў:"Ubrcy",ф:"fcy",Ф:"Fcy",х:"khcy",Х:"KHcy",ц:"tscy",Ц:"TScy",ч:"chcy",Ч:"CHcy",џ:"dzcy",Џ:"DZcy",ш:"shcy",Ш:"SHcy",щ:"shchcy",Щ:"SHCHcy",ъ:"hardcy",Ъ:"HARDcy",ы:"ycy",Ы:"Ycy",ь:"softcy",Ь:"SOFTcy",э:"ecy",Э:"Ecy",ю:"yucy",Ю:"YUcy",я:"yacy",Я:"YAcy",ℵ:"aleph",ℶ:"beth",ℷ:"gimel",ℸ:"daleth"},d=/["&'<>`]/g,f={'"':""","&":"&","'":"'","<":"<",">":">","`":"`"},p=/&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/,h=/[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,g=/&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g,m={aacute:"á",Aacute:"Á",abreve:"ă",Abreve:"Ă",ac:"∾",acd:"∿",acE:"∾̳",acirc:"â",Acirc:"Â",acute:"´",acy:"а",Acy:"А",aelig:"æ",AElig:"Æ",af:"⁡",afr:"𝔞",Afr:"𝔄",agrave:"à",Agrave:"À",alefsym:"ℵ",aleph:"ℵ",alpha:"α",Alpha:"Α",amacr:"ā",Amacr:"Ā",amalg:"⨿",amp:"&",AMP:"&",and:"∧",And:"⩓",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",aogon:"ą",Aogon:"Ą",aopf:"𝕒",Aopf:"𝔸",ap:"≈",apacir:"⩯",ape:"≊",apE:"⩰",apid:"≋",apos:"'",ApplyFunction:"⁡",approx:"≈",approxeq:"≊",aring:"å",Aring:"Å",ascr:"𝒶",Ascr:"𝒜",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",bcy:"б",Bcy:"Б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",beta:"β",Beta:"Β",beth:"ℶ",between:"≬",bfr:"𝔟",Bfr:"𝔅",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bnot:"⌐",bNot:"⫭",bopf:"𝕓",Bopf:"𝔹",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"┬",boxhD:"╥",boxHd:"╤",boxHD:"╦",boxhu:"┴",boxhU:"╨",boxHu:"╧",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"┼",boxvH:"╪",boxVh:"╫",boxVH:"╬",boxvl:"┤",boxvL:"╡",boxVl:"╢",boxVL:"╣",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"𝒷",Bscr:"ℬ",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpe:"≏",bumpE:"⪮",bumpeq:"≏",Bumpeq:"≎",cacute:"ć",Cacute:"Ć",cap:"∩",Cap:"⋒",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",ccaron:"č",Ccaron:"Č",ccedil:"ç",Ccedil:"Ç",ccirc:"ĉ",Ccirc:"Ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",cdot:"ċ",Cdot:"Ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"𝔠",Cfr:"ℭ",chcy:"ч",CHcy:"Ч",check:"✓",checkmark:"✓",chi:"χ",Chi:"Χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cire:"≗",cirE:"⧃",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",colon:":",Colon:"∷",colone:"≔",Colone:"⩴",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",conint:"∮",Conint:"∯",ContourIntegral:"∮",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"©",COPY:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",cross:"✗",Cross:"⨯",cscr:"𝒸",Cscr:"𝒞",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",cup:"∪",Cup:"⋓",cupbrcap:"⩈",cupcap:"⩆",CupCap:"≍",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",dArr:"⇓",Darr:"↡",dash:"‐",dashv:"⊣",Dashv:"⫤",dbkarow:"⤏",dblac:"˝",dcaron:"ď",Dcaron:"Ď",dcy:"д",Dcy:"Д",dd:"ⅆ",DD:"ⅅ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",delta:"δ",Delta:"Δ",demptyv:"⦱",dfisht:"⥿",dfr:"𝔡",Dfr:"𝔇",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",djcy:"ђ",DJcy:"Ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",Dopf:"𝔻",dot:"˙",Dot:"¨",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",downarrow:"↓",Downarrow:"⇓",DownArrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVector:"↽",DownLeftVectorBar:"⥖",DownRightTeeVector:"⥟",DownRightVector:"⇁",DownRightVectorBar:"⥗",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",Dscr:"𝒟",dscy:"ѕ",DScy:"Ѕ",dsol:"⧶",dstrok:"đ",Dstrok:"Đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",dzcy:"џ",DZcy:"Џ",dzigrarr:"⟿",eacute:"é",Eacute:"É",easter:"⩮",ecaron:"ě",Ecaron:"Ě",ecir:"≖",ecirc:"ê",Ecirc:"Ê",ecolon:"≕",ecy:"э",Ecy:"Э",eDDot:"⩷",edot:"ė",eDot:"≑",Edot:"Ė",ee:"ⅇ",efDot:"≒",efr:"𝔢",Efr:"𝔈",eg:"⪚",egrave:"è",Egrave:"È",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",emacr:"ē",Emacr:"Ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp:" ",emsp13:" ",emsp14:" ",eng:"ŋ",ENG:"Ŋ",ensp:" ",eogon:"ę",Eogon:"Ę",eopf:"𝕖",Eopf:"𝔼",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",epsilon:"ε",Epsilon:"Ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",esim:"≂",Esim:"⩳",eta:"η",Eta:"Η",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",fcy:"ф",Fcy:"Ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",ffr:"𝔣",Ffr:"𝔉",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",fopf:"𝕗",Fopf:"𝔽",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",fscr:"𝒻",Fscr:"ℱ",gacute:"ǵ",gamma:"γ",Gamma:"Γ",gammad:"ϝ",Gammad:"Ϝ",gap:"⪆",gbreve:"ğ",Gbreve:"Ğ",Gcedil:"Ģ",gcirc:"ĝ",Gcirc:"Ĝ",gcy:"г",Gcy:"Г",gdot:"ġ",Gdot:"Ġ",ge:"≥",gE:"≧",gel:"⋛",gEl:"⪌",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",gfr:"𝔤",Gfr:"𝔊",gg:"≫",Gg:"⋙",ggg:"⋙",gimel:"ℷ",gjcy:"ѓ",GJcy:"Ѓ",gl:"≷",gla:"⪥",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gnE:"≩",gneq:"⪈",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",Gopf:"𝔾",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",gscr:"ℊ",Gscr:"𝒢",gsim:"≳",gsime:"⪎",gsiml:"⪐",gt:">",Gt:"≫",GT:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",hardcy:"ъ",HARDcy:"Ъ",harr:"↔",hArr:"⇔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",hcirc:"ĥ",Hcirc:"Ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"𝔥",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"𝒽",Hscr:"ℋ",hslash:"ℏ",hstrok:"ħ",Hstrok:"Ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",iacute:"í",Iacute:"Í",ic:"⁣",icirc:"î",Icirc:"Î",icy:"и",Icy:"И",Idot:"İ",iecy:"е",IEcy:"Е",iexcl:"¡",iff:"⇔",ifr:"𝔦",Ifr:"ℑ",igrave:"ì",Igrave:"Ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"ij",IJlig:"IJ",Im:"ℑ",imacr:"ī",Imacr:"Ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",int:"∫",Int:"∬",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",iocy:"ё",IOcy:"Ё",iogon:"į",Iogon:"Į",iopf:"𝕚",Iopf:"𝕀",iota:"ι",Iota:"Ι",iprod:"⨼",iquest:"¿",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",itilde:"ĩ",Itilde:"Ĩ",iukcy:"і",Iukcy:"І",iuml:"ï",Iuml:"Ï",jcirc:"ĵ",Jcirc:"Ĵ",jcy:"й",Jcy:"Й",jfr:"𝔧",Jfr:"𝔍",jmath:"ȷ",jopf:"𝕛",Jopf:"𝕁",jscr:"𝒿",Jscr:"𝒥",jsercy:"ј",Jsercy:"Ј",jukcy:"є",Jukcy:"Є",kappa:"κ",Kappa:"Κ",kappav:"ϰ",kcedil:"ķ",Kcedil:"Ķ",kcy:"к",Kcy:"К",kfr:"𝔨",Kfr:"𝔎",kgreen:"ĸ",khcy:"х",KHcy:"Х",kjcy:"ќ",KJcy:"Ќ",kopf:"𝕜",Kopf:"𝕂",kscr:"𝓀",Kscr:"𝒦",lAarr:"⇚",lacute:"ĺ",Lacute:"Ĺ",laemptyv:"⦴",lagran:"ℒ",lambda:"λ",Lambda:"Λ",lang:"⟨",Lang:"⟪",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",larr:"←",lArr:"⇐",Larr:"↞",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",latail:"⤙",lAtail:"⤛",late:"⪭",lates:"⪭︀",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",lcaron:"ľ",Lcaron:"Ľ",lcedil:"ļ",Lcedil:"Ļ",lceil:"⌈",lcub:"{",lcy:"л",Lcy:"Л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",lE:"≦",LeftAngleBracket:"⟨",leftarrow:"←",Leftarrow:"⇐",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",Leftrightarrow:"⇔",LeftRightArrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",leg:"⋚",lEg:"⪋",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",lfr:"𝔩",Lfr:"𝔏",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",ljcy:"љ",LJcy:"Љ",ll:"≪",Ll:"⋘",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",lmidot:"ŀ",Lmidot:"Ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lnE:"≨",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",Longleftarrow:"⟸",LongLeftArrow:"⟵",longleftrightarrow:"⟷",Longleftrightarrow:"⟺",LongLeftRightArrow:"⟷",longmapsto:"⟼",longrightarrow:"⟶",Longrightarrow:"⟹",LongRightArrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",lopf:"𝕝",Lopf:"𝕃",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",Lstrok:"Ł",lt:"<",Lt:"≪",LT:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"↦",Map:"⤅",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",mcy:"м",Mcy:"М",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",mfr:"𝔪",Mfr:"𝔐",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",mopf:"𝕞",Mopf:"𝕄",mp:"∓",mscr:"𝓂",Mscr:"ℳ",mstpos:"∾",mu:"μ",Mu:"Μ",multimap:"⊸",mumap:"⊸",nabla:"∇",nacute:"ń",Nacute:"Ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",ncaron:"ň",Ncaron:"Ň",ncedil:"ņ",Ncedil:"Ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",ncy:"н",Ncy:"Н",ndash:"–",ne:"≠",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",nfr:"𝔫",Nfr:"𝔑",nge:"≱",ngE:"≧̸",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",ngt:"≯",nGt:"≫⃒",ngtr:"≯",nGtv:"≫̸",nharr:"↮",nhArr:"⇎",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",njcy:"њ",NJcy:"Њ",nlarr:"↚",nlArr:"⇍",nldr:"‥",nle:"≰",nlE:"≦̸",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nlt:"≮",nLt:"≪⃒",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",not:"¬",Not:"⫬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrarr:"↛",nrArr:"⇏",nrarrc:"⤳̸",nrarrw:"↝̸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",nscr:"𝓃",Nscr:"𝒩",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsube:"⊈",nsubE:"⫅̸",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupe:"⊉",nsupE:"⫆̸",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",ntilde:"ñ",Ntilde:"Ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",nu:"ν",Nu:"Ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"⊬",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",oacute:"ó",Oacute:"Ó",oast:"⊛",ocir:"⊚",ocirc:"ô",Ocirc:"Ô",ocy:"о",Ocy:"О",odash:"⊝",odblac:"ő",Odblac:"Ő",odiv:"⨸",odot:"⊙",odsold:"⦼",oelig:"œ",OElig:"Œ",ofcir:"⦿",ofr:"𝔬",Ofr:"𝔒",ogon:"˛",ograve:"ò",Ograve:"Ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",omacr:"ō",Omacr:"Ō",omega:"ω",Omega:"Ω",omicron:"ο",Omicron:"Ο",omid:"⦶",ominus:"⊖",oopf:"𝕠",Oopf:"𝕆",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",or:"∨",Or:"⩔",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",oscr:"ℴ",Oscr:"𝒪",oslash:"ø",Oslash:"Ø",osol:"⊘",otilde:"õ",Otilde:"Õ",otimes:"⊗",Otimes:"⨷",otimesas:"⨶",ouml:"ö",Ouml:"Ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",pcy:"п",Pcy:"П",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",pfr:"𝔭",Pfr:"𝔓",phi:"φ",Phi:"Φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",pi:"π",Pi:"Π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"£",pr:"≺",Pr:"⪻",prap:"⪷",prcue:"≼",pre:"⪯",prE:"⪳",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",prime:"′",Prime:"″",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",Pscr:"𝒫",psi:"ψ",Psi:"Ψ",puncsp:" ",qfr:"𝔮",Qfr:"𝔔",qint:"⨌",qopf:"𝕢",Qopf:"ℚ",qprime:"⁗",qscr:"𝓆",Qscr:"𝒬",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"∽̱",racute:"ŕ",Racute:"Ŕ",radic:"√",raemptyv:"⦳",rang:"⟩",Rang:"⟫",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarr:"→",rArr:"⇒",Rarr:"↠",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",rarrtl:"↣",Rarrtl:"⤖",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"∶",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",rcaron:"ř",Rcaron:"Ř",rcedil:"ŗ",Rcedil:"Ŗ",rceil:"⌉",rcub:"}",rcy:"р",Rcy:"Р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"®",REG:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",rfr:"𝔯",Rfr:"ℜ",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",rho:"ρ",Rho:"Ρ",rhov:"ϱ",RightAngleBracket:"⟩",rightarrow:"→",Rightarrow:"⇒",RightArrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"𝕣",Ropf:"ℝ",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",sacute:"ś",Sacute:"Ś",sbquo:"‚",sc:"≻",Sc:"⪼",scap:"⪸",scaron:"š",Scaron:"Š",sccue:"≽",sce:"⪰",scE:"⪴",scedil:"ş",Scedil:"Ş",scirc:"ŝ",Scirc:"Ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",scy:"с",Scy:"С",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",sfr:"𝔰",Sfr:"𝔖",sfrown:"⌢",sharp:"♯",shchcy:"щ",SHCHcy:"Щ",shcy:"ш",SHcy:"Ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",sigma:"σ",Sigma:"Σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",softcy:"ь",SOFTcy:"Ь",sol:"/",solb:"⧄",solbar:"⌿",sopf:"𝕤",Sopf:"𝕊",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",sscr:"𝓈",Sscr:"𝒮",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",star:"☆",Star:"⋆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"⪽",sube:"⊆",subE:"⫅",subedot:"⫃",submult:"⫁",subne:"⊊",subnE:"⫋",subplus:"⪿",subrarr:"⥹",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"♪",sup:"⊃",Sup:"⋑",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supe:"⊇",supE:"⫆",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supne:"⊋",supnE:"⫌",supplus:"⫀",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",tau:"τ",Tau:"Τ",tbrk:"⎴",tcaron:"ť",Tcaron:"Ť",tcedil:"ţ",Tcedil:"Ţ",tcy:"т",Tcy:"Т",tdot:"⃛",telrec:"⌕",tfr:"𝔱",Tfr:"𝔗",there4:"∴",therefore:"∴",Therefore:"∴",theta:"θ",Theta:"Θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",thorn:"þ",THORN:"Þ",tilde:"˜",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",topf:"𝕥",Topf:"𝕋",topfork:"⫚",tosa:"⤩",tprime:"‴",trade:"™",TRADE:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",tscr:"𝓉",Tscr:"𝒯",tscy:"ц",TScy:"Ц",tshcy:"ћ",TSHcy:"Ћ",tstrok:"ŧ",Tstrok:"Ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uacute:"ú",Uacute:"Ú",uarr:"↑",uArr:"⇑",Uarr:"↟",Uarrocir:"⥉",ubrcy:"ў",Ubrcy:"Ў",ubreve:"ŭ",Ubreve:"Ŭ",ucirc:"û",Ucirc:"Û",ucy:"у",Ucy:"У",udarr:"⇅",udblac:"ű",Udblac:"Ű",udhar:"⥮",ufisht:"⥾",ufr:"𝔲",Ufr:"𝔘",ugrave:"ù",Ugrave:"Ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ū",Umacr:"Ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",uogon:"ų",Uogon:"Ų",uopf:"𝕦",Uopf:"𝕌",uparrow:"↑",Uparrow:"⇑",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",updownarrow:"↕",Updownarrow:"⇕",UpDownArrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"υ",Upsi:"ϒ",upsih:"ϒ",upsilon:"υ",Upsilon:"Υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",Uring:"Ů",urtri:"◹",uscr:"𝓊",Uscr:"𝒰",utdot:"⋰",utilde:"ũ",Utilde:"Ũ",utri:"▵",utrif:"▴",uuarr:"⇈",uuml:"ü",Uuml:"Ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",vBar:"⫨",Vbar:"⫫",vBarv:"⫩",vcy:"в",Vcy:"В",vdash:"⊢",vDash:"⊨",Vdash:"⊩",VDash:"⊫",Vdashl:"⫦",vee:"∨",Vee:"⋁",veebar:"⊻",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",vfr:"𝔳",Vfr:"𝔙",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",Vopf:"𝕍",vprop:"∝",vrtri:"⊳",vscr:"𝓋",Vscr:"𝒱",vsubne:"⊊︀",vsubnE:"⫋︀",vsupne:"⊋︀",vsupnE:"⫌︀",Vvdash:"⊪",vzigzag:"⦚",wcirc:"ŵ",Wcirc:"Ŵ",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",wfr:"𝔴",Wfr:"𝔚",wopf:"𝕨",Wopf:"𝕎",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",Wscr:"𝒲",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",xfr:"𝔵",Xfr:"𝔛",xharr:"⟷",xhArr:"⟺",xi:"ξ",Xi:"Ξ",xlarr:"⟵",xlArr:"⟸",xmap:"⟼",xnis:"⋻",xodot:"⨀",xopf:"𝕩",Xopf:"𝕏",xoplus:"⨁",xotime:"⨂",xrarr:"⟶",xrArr:"⟹",xscr:"𝓍",Xscr:"𝒳",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",yacute:"ý",Yacute:"Ý",yacy:"я",YAcy:"Я",ycirc:"ŷ",Ycirc:"Ŷ",ycy:"ы",Ycy:"Ы",yen:"¥",yfr:"𝔶",Yfr:"𝔜",yicy:"ї",YIcy:"Ї",yopf:"𝕪",Yopf:"𝕐",yscr:"𝓎",Yscr:"𝒴",yucy:"ю",YUcy:"Ю",yuml:"ÿ",Yuml:"Ÿ",zacute:"ź",Zacute:"Ź",zcaron:"ž",Zcaron:"Ž",zcy:"з",Zcy:"З",zdot:"ż",Zdot:"Ż",zeetrf:"ℨ",ZeroWidthSpace:"​",zeta:"ζ",Zeta:"Ζ",zfr:"𝔷",Zfr:"ℨ",zhcy:"ж",ZHcy:"Ж",zigrarr:"⇝",zopf:"𝕫",Zopf:"ℤ",zscr:"𝓏",Zscr:"𝒵",zwj:"‍",zwnj:"‌"},v={aacute:"á",Aacute:"Á",acirc:"â",Acirc:"Â",acute:"´",aelig:"æ",AElig:"Æ",agrave:"à",Agrave:"À",amp:"&",AMP:"&",aring:"å",Aring:"Å",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",brvbar:"¦",ccedil:"ç",Ccedil:"Ç",cedil:"¸",cent:"¢",copy:"©",COPY:"©",curren:"¤",deg:"°",divide:"÷",eacute:"é",Eacute:"É",ecirc:"ê",Ecirc:"Ê",egrave:"è",Egrave:"È",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",frac12:"½",frac14:"¼",frac34:"¾",gt:">",GT:">",iacute:"í",Iacute:"Í",icirc:"î",Icirc:"Î",iexcl:"¡",igrave:"ì",Igrave:"Ì",iquest:"¿",iuml:"ï",Iuml:"Ï",laquo:"«",lt:"<",LT:"<",macr:"¯",micro:"µ",middot:"·",nbsp:" ",not:"¬",ntilde:"ñ",Ntilde:"Ñ",oacute:"ó",Oacute:"Ó",ocirc:"ô",Ocirc:"Ô",ograve:"ò",Ograve:"Ò",ordf:"ª",ordm:"º",oslash:"ø",Oslash:"Ø",otilde:"õ",Otilde:"Õ",ouml:"ö",Ouml:"Ö",para:"¶",plusmn:"±",pound:"£",quot:'"',QUOT:'"',raquo:"»",reg:"®",REG:"®",sect:"§",shy:"­",sup1:"¹",sup2:"²",sup3:"³",szlig:"ß",thorn:"þ",THORN:"Þ",times:"×",uacute:"ú",Uacute:"Ú",ucirc:"û",Ucirc:"Û",ugrave:"ù",Ugrave:"Ù",uml:"¨",uuml:"ü",Uuml:"Ü",yacute:"ý",Yacute:"Ý",yen:"¥",yuml:"ÿ"},b={0:"�",128:"€",130:"‚",131:"ƒ",132:"„",133:"…",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Š",139:"‹",140:"Œ",142:"Ž",145:"‘",146:"’",147:"“",148:"”",149:"•",150:"–",151:"—",152:"˜",153:"™",154:"š",155:"›",156:"œ",158:"ž",159:"Ÿ"},y=[1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65e3,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111],A=String.fromCharCode,w={}.hasOwnProperty,x=function(e,t){return w.call(e,t)},E=function(e,t){if(!e)return t;var n,r={};for(n in t)r[n]=x(e,n)?e[n]:t[n];return r},k=function(e,t){var n="";return e>=55296&&e<=57343||e>1114111?(t&&C("character reference outside the permissible Unicode range"),"�"):x(b,e)?(t&&C("disallowed character reference"),b[e]):(t&&function(e,t){for(var n=-1,r=e.length;++n65535&&(n+=A((e-=65536)>>>10&1023|55296),e=56320|1023&e),n+=A(e))},S=function(e){return"&#x"+e.toString(16).toUpperCase()+";"},_=function(e){return"&#"+e+";"},C=function(e){throw Error("Parse error: "+e)},T=function(e,t){(t=E(t,T.options)).strict&&h.test(e)&&C("forbidden code point");var n=t.encodeEverything,r=t.useNamedReferences,o=t.allowUnsafeSymbols,i=t.decimal?_:S,f=function(e){return i(e.charCodeAt(0))};return n?(e=e.replace(l,(function(e){return r&&x(c,e)?"&"+c[e]+";":f(e)})),r&&(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒").replace(/fj/g,"fj")),r&&(e=e.replace(u,(function(e){return"&"+c[e]+";"})))):r?(o||(e=e.replace(d,(function(e){return"&"+c[e]+";"}))),e=(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒")).replace(u,(function(e){return"&"+c[e]+";"}))):o||(e=e.replace(d,f)),e.replace(a,(function(e){var t=e.charCodeAt(0),n=e.charCodeAt(1);return i(1024*(t-55296)+n-56320+65536)})).replace(s,f)};T.options={allowUnsafeSymbols:!1,encodeEverything:!1,strict:!1,useNamedReferences:!1,decimal:!1};var D=function(e,t){var n=(t=E(t,D.options)).strict;return n&&p.test(e)&&C("malformed character reference"),e.replace(g,(function(e,r,o,i,a,l,s,u,c){var d,f,p,h,g,b;return r?m[g=r]:o?(g=o,(b=i)&&t.isAttributeValue?(n&&"="==b&&C("`&` did not start a character reference"),e):(n&&C("named character reference was not terminated by a semicolon"),v[g]+(b||""))):a?(p=a,f=l,n&&!f&&C("character reference was not terminated by a semicolon"),d=parseInt(p,10),k(d,n)):s?(h=s,f=u,n&&!f&&C("character reference was not terminated by a semicolon"),d=parseInt(h,16),k(d,n)):(n&&C("named character reference was not terminated by a semicolon"),e)}))};D.options={isAttributeValue:!1,strict:!1};var L={version:"1.2.0",encode:T,decode:D,escape:function(e){return e.replace(d,(function(e){return f[e]}))},unescape:D};void 0===(r=function(){return L}.call(t,n,t,e))||(e.exports=r)}()},6299:e=>{var t=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,n=/\n/g,r=/^\s*/,o=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,i=/^:\s*/,a=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,l=/^[;\s]*/,s=/^\s+|\s+$/g,u="";function c(e){return e?e.replace(s,u):u}e.exports=function(e,s){if("string"!=typeof e)throw new TypeError("First argument must be a string");if(!e)return[];s=s||{};var d=1,f=1;function p(e){var t=e.match(n);t&&(d+=t.length);var r=e.lastIndexOf("\n");f=~r?e.length-r:f+e.length}function h(){var e={line:d,column:f};return function(t){return t.position=new g(e),y(),t}}function g(e){this.start=e,this.end={line:d,column:f},this.source=s.source}g.prototype.content=e;var m=[];function v(t){var n=new Error(s.source+":"+d+":"+f+": "+t);if(n.reason=t,n.filename=s.source,n.line=d,n.column=f,n.source=e,!s.silent)throw n;m.push(n)}function b(t){var n=t.exec(e);if(n){var r=n[0];return p(r),e=e.slice(r.length),n}}function y(){b(r)}function A(e){var t;for(e=e||[];t=w();)!1!==t&&e.push(t);return e}function w(){var t=h();if("/"==e.charAt(0)&&"*"==e.charAt(1)){for(var n=2;u!=e.charAt(n)&&("*"!=e.charAt(n)||"/"!=e.charAt(n+1));)++n;if(n+=2,u===e.charAt(n-1))return v("End of comment missing");var r=e.slice(2,n-2);return f+=2,p(r),e=e.slice(n),f+=2,t({type:"comment",comment:r})}}function x(){var e=h(),n=b(o);if(n){if(w(),!b(i))return v("property missing ':'");var r=b(a),s=e({type:"declaration",property:c(n[0].replace(t,u)),value:r?c(r[0].replace(t,u)):u});return b(l),s}}return y(),function(){var e,t=[];for(A(t);e=x();)!1!==e&&(t.push(e),A(t));return t}()}},1170:function(e,t){var n,r;void 0===(r="function"==typeof(n=function(){var e=function(){},t={},n={},r={};function o(e,t){if(e){var o=r[e];if(n[e]=t,o)for(;o.length;)o[0](e,t),o.splice(0,1)}}function i(t,n){t.call&&(t={success:t}),n.length?(t.error||e)(n):(t.success||e)(t)}function a(t,n,r,o){var i,l,s,u=document,c=r.async,d=(r.numRetries||0)+1,f=r.before||e,p=t.replace(/[\?|#].*$/,""),h=t.replace(/^(css|img|module|nomodule)!/,"");if(o=o||0,/(^css!|\.css$)/.test(p))(s=u.createElement("link")).rel="stylesheet",s.href=h,(i="hideFocus"in s)&&s.relList&&(i=0,s.rel="preload",s.as="style");else if(/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(p))(s=u.createElement("img")).src=h;else if((s=u.createElement("script")).src=h,s.async=void 0===c||c,l="noModule"in s,/^module!/.test(p)){if(!l)return n(t,"l");s.type="module"}else if(/^nomodule!/.test(p)&&l)return n(t,"l");s.onload=s.onerror=s.onbeforeload=function(e){var l=e.type[0];if(i)try{s.sheet.cssText.length||(l="e")}catch(e){18!=e.code&&(l="e")}if("e"==l){if((o+=1){var r=n(7772).Symbol;e.exports=r},3366:(e,t,n)=>{var r=n(857),o=n(2107),i=n(7157),a=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":a&&a in Object(e)?o(e):i(e)}},1704:(e,t,n)=>{var r=n(2153),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},1242:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},2107:(e,t,n)=>{var r=n(857),o=Object.prototype,i=o.hasOwnProperty,a=o.toString,l=r?r.toStringTag:void 0;e.exports=function(e){var t=i.call(e,l),n=e[l];try{e[l]=void 0;var r=!0}catch(e){}var o=a.call(e);return r&&(t?e[l]=n:delete e[l]),o}},7157:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},7772:(e,t,n)=>{var r=n(1242),o="object"==typeof self&&self&&self.Object===Object&&self,i=r||o||Function("return this")();e.exports=i},2153:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},4073:(e,t,n)=>{var r=n(9259),o=n(1100),i=n(7642),a=Math.max,l=Math.min;e.exports=function(e,t,n){var s,u,c,d,f,p,h=0,g=!1,m=!1,v=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function b(t){var n=s,r=u;return s=u=void 0,h=t,d=e.apply(r,n)}function y(e){return h=e,f=setTimeout(w,t),g?b(e):d}function A(e){var n=e-p;return void 0===p||n>=t||n<0||m&&e-h>=c}function w(){var e=o();if(A(e))return x(e);f=setTimeout(w,function(e){var n=t-(e-p);return m?l(n,c-(e-h)):n}(e))}function x(e){return f=void 0,v&&s?b(e):(s=u=void 0,d)}function E(){var e=o(),n=A(e);if(s=arguments,u=this,p=e,n){if(void 0===f)return y(p);if(m)return clearTimeout(f),f=setTimeout(w,t),b(p)}return void 0===f&&(f=setTimeout(w,t)),d}return t=i(t)||0,r(n)&&(g=!!n.leading,c=(m="maxWait"in n)?a(i(n.maxWait)||0,t):c,v="trailing"in n?!!n.trailing:v),E.cancel=function(){void 0!==f&&clearTimeout(f),h=0,s=p=u=f=void 0},E.flush=function(){return void 0===f?d:x(o())},E}},9259:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},5125:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},4795:(e,t,n)=>{var r=n(3366),o=n(5125);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},1100:(e,t,n)=>{var r=n(7772);e.exports=function(){return r.Date.now()}},2436:(e,t,n)=>{var r=n(4073),o=n(9259);e.exports=function(e,t,n){var i=!0,a=!0;if("function"!=typeof e)throw new TypeError("Expected a function");return o(n)&&(i="leading"in n?!!n.leading:i,a="trailing"in n?!!n.trailing:a),r(e,t,{leading:i,maxWait:t,trailing:a})}},7642:(e,t,n)=>{var r=n(1704),o=n(9259),i=n(4795),a=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,s=/^0o[0-7]+$/i,u=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(i(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=l.test(e);return n||s.test(e)?u(e.slice(2),n?2:8):a.test(e)?NaN:+e}},3771:e=>{"use strict";var t={};function n(e,r){var o;return"string"!=typeof r&&(r=n.defaultChars),o=function(e){var n,r,o=t[e];if(o)return o;for(o=t[e]=[],n=0;n<128;n++)r=String.fromCharCode(n),o.push(r);for(n=0;n=55296&&s<=57343?"���":String.fromCharCode(s),t+=6):240==(248&r)&&t+91114111?u+="����":(s-=65536,u+=String.fromCharCode(55296+(s>>10),56320+(1023&s))),t+=9):u+="�";return u}))}n.defaultChars=";/?:@&=+$,#",n.componentChars="",e.exports=n},1737:e=>{"use strict";var t={};function n(e,r,o){var i,a,l,s,u,c="";for("string"!=typeof r&&(o=r,r=n.defaultChars),void 0===o&&(o=!0),u=function(e){var n,r,o=t[e];if(o)return o;for(o=t[e]=[],n=0;n<128;n++)r=String.fromCharCode(n),/^[0-9a-z]$/i.test(r)?o.push(r):o.push("%"+("0"+n.toString(16).toUpperCase()).slice(-2));for(n=0;n=55296&&l<=57343){if(l>=55296&&l<=56319&&i+1=56320&&s<=57343){c+=encodeURIComponent(e[i]+e[i+1]),i++;continue}c+="%EF%BF%BD"}else c+=encodeURIComponent(e[i]);return c}n.defaultChars=";/?:@&=+$,-_.!~*'()#",n.componentChars="-_.!~*'()",e.exports=n},5024:e=>{"use strict";e.exports=function(e){var t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",e.hostname&&-1!==e.hostname.indexOf(":")?t+="["+e.hostname+"]":t+=e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",(t+=e.search||"")+(e.hash||"")}},4976:(e,t,n)=>{"use strict";e.exports.encode=n(1737),e.exports.decode=n(3771),e.exports.format=n(5024),e.exports.parse=n(2084)},2084:e=>{"use strict";function t(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}var n=/^([a-z0-9.+-]+:)/i,r=/:[0-9]*$/,o=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,i=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),a=["'"].concat(i),l=["%","/","?",";","#"].concat(a),s=["/","?","#"],u=/^[+a-z0-9A-Z_-]{0,63}$/,c=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,d={javascript:!0,"javascript:":!0},f={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};t.prototype.parse=function(e,t){var r,i,a,p,h,g=e;if(g=g.trim(),!t&&1===e.split("#").length){var m=o.exec(g);if(m)return this.pathname=m[1],m[2]&&(this.search=m[2]),this}var v=n.exec(g);if(v&&(a=(v=v[0]).toLowerCase(),this.protocol=v,g=g.substr(v.length)),(t||v||g.match(/^\/\/[^@\/]+@[^@\/]+/))&&(!(h="//"===g.substr(0,2))||v&&d[v]||(g=g.substr(2),this.slashes=!0)),!d[v]&&(h||v&&!f[v])){var b,y,A=-1;for(r=0;r127?S+="x":S+=k[_];if(!S.match(u)){var T=E.slice(0,r),D=E.slice(r+1),L=k.match(c);L&&(T.push(L[1]),D.unshift(L[2])),D.length&&(g=D.join(".")+g),this.hostname=T.join(".");break}}}}this.hostname.length>255&&(this.hostname=""),x&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}var O=g.indexOf("#");-1!==O&&(this.hash=g.substr(O),g=g.slice(0,O));var R=g.indexOf("?");return-1!==R&&(this.search=g.substr(R),g=g.slice(0,R)),g&&(this.pathname=g),f[a]&&this.hostname&&!this.pathname&&(this.pathname=""),this},t.prototype.parseHost=function(e){var t=r.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)},e.exports=function(e,n){if(e&&e instanceof t)return e;var r=new t;return r.parse(e,n),r}},9515:(e,t)=>{"use strict";function n(){return window.omnivoreEnv.NEXT_DEPLOYMENT_ID?`?dpl=${window.omnivoreEnv.NEXT_DEPLOYMENT_ID}`:""}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDeploymentIdQueryOrEmptyString",{enumerable:!0,get:function(){return n}})},1054:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addBasePath",{enumerable:!0,get:function(){return a}});const r=n(284),o=n(1119),i=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"";function a(e,t){return(0,o.normalizePathTrailingSlash)(window.omnivoreEnv.__NEXT_MANUAL_CLIENT_BASE_PATH&&!t?e:(0,r.addPathPrefix)(e,i))}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},891:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return o}});const r=n(1119),o=function(e){for(var t=arguments.length,o=new Array(t>1?t-1:0),i=1;i{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{RSC:function(){return n},ACTION:function(){return r},NEXT_ROUTER_STATE_TREE:function(){return o},NEXT_ROUTER_PREFETCH:function(){return i},NEXT_URL:function(){return a},RSC_CONTENT_TYPE_HEADER:function(){return l},RSC_VARY_HEADER:function(){return s},FLIGHT_PARAMETERS:function(){return u},NEXT_RSC_UNION_QUERY:function(){return c}});const n="RSC",r="Next-Action",o="Next-Router-State-Tree",i="Next-Router-Prefetch",a="Next-Url",l="text/x-component",s=n+", "+o+", "+i+", "+a,u=[[n],[o],[i]],c="_rsc";("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4644:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{PrefetchKind:function(){return u},ACTION_REFRESH:function(){return n},ACTION_NAVIGATE:function(){return r},ACTION_RESTORE:function(){return o},ACTION_SERVER_PATCH:function(){return i},ACTION_PREFETCH:function(){return a},ACTION_FAST_REFRESH:function(){return l},ACTION_SERVER_ACTION:function(){return s}});const n="refresh",r="navigate",o="restore",i="server-patch",a="prefetch",l="fast-refresh",s="server-action";var u;!function(e){e.AUTO="auto",e.FULL="full",e.TEMPORARY="temporary"}(u||(u={})),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},936:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"detectDomainLocale",{enumerable:!0,get:function(){return r}});const r=function(){for(var e=arguments.length,t=new Array(e),r=0;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getDomainLocale",{enumerable:!0,get:function(){return i}});const r=n(1119),o=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"";function i(e,t,i,a){if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){const l=n(9789).normalizeLocalePath,s=n(936).detectDomainLocale,u=t||l(e,i).detectedLocale,c=s(a,void 0,u);if(c){const t="http"+(c.http?"":"s")+"://",n=u===c.defaultLocale?"":"/"+u;return""+t+c.domain+(0,r.normalizePathTrailingSlash)(""+o+n+e)}return!1}return!1}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},6200:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"hasBasePath",{enumerable:!0,get:function(){return i}});const r=n(9181),o=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"";function i(e){return(0,r.pathHasPrefix)(e,o)}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9410:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{DOMAttributeNames:function(){return n},isEqualNode:function(){return o},default:function(){return a}});const n={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv",noModule:"noModule"};function r(e){let{type:t,props:r}=e;const o=document.createElement(t);for(const e in r){if(!r.hasOwnProperty(e))continue;if("children"===e||"dangerouslySetInnerHTML"===e)continue;if(void 0===r[e])continue;const i=n[e]||e.toLowerCase();"script"!==t||"async"!==i&&"defer"!==i&&"noModule"!==i?o.setAttribute(i,r[e]):o[i]=!!r[e]}const{children:i,dangerouslySetInnerHTML:a}=r;return a?o.innerHTML=a.__html||"":i&&(o.textContent="string"==typeof i?i:Array.isArray(i)?i.join(""):""),o}function o(e,t){if(e instanceof HTMLElement&&t instanceof HTMLElement){const n=t.getAttribute("nonce");if(n&&!e.getAttribute("nonce")){const r=t.cloneNode(!0);return r.setAttribute("nonce",""),r.nonce=n,n===e.nonce&&e.isEqualNode(r)}}return e.isEqualNode(t)}let i;function a(){return{mountedInstances:new Set,updateHead:e=>{const t={};e.forEach((e=>{if("link"===e.type&&e.props["data-optimized-fonts"]){if(document.querySelector('style[data-href="'+e.props["data-href"]+'"]'))return;e.props.href=e.props["data-href"],e.props["data-href"]=void 0}const n=t[e.type]||[];n.push(e),t[e.type]=n}));const n=t.title?t.title[0]:null;let r="";if(n){const{children:e}=n.props;r="string"==typeof e?e:Array.isArray(e)?e.join(""):""}r!==document.title&&(document.title=r),["meta","base","link","style","script"].forEach((e=>{i(e,t[e]||[])}))}}}i=window.omnivoreEnv.__NEXT_STRICT_NEXT_HEAD?(e,t)=>{const n=document.querySelector("head");if(!n)return;const i=n.querySelectorAll('meta[name="next-head"]')||[],a=[];if("meta"===e){const e=n.querySelector("meta[charset]");e&&a.push(e)}for(let t=0;t{for(let t=0,n=a.length;t{var t;const n=e.previousSibling;var r;n&&"next-head"===n.getAttribute("name")&&(null==(r=e.parentNode)||r.removeChild(n)),null==(t=e.parentNode)||t.removeChild(e)})),s.forEach((e=>{var t;const r=document.createElement("meta");r.name="next-head",r.content="1","meta"===(null==(t=e.tagName)?void 0:t.toLowerCase())&&e.getAttribute("charset")||n.appendChild(r),n.appendChild(e)}))}:(e,t)=>{const n=document.getElementsByTagName("head")[0],i=n.querySelector("meta[name=next-head-count]"),a=Number(i.content),l=[];for(let t=0,n=i.previousElementSibling;t{for(let t=0,n=l.length;t{var t;return null==(t=e.parentNode)?void 0:t.removeChild(e)})),u.forEach((e=>n.insertBefore(e,i))),i.content=(a-l.length+u.length).toString()},("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4529:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return b}});const r=n(7083)._(n(2784)),o=n(1589),i=n(8657),a=n(6555),l=n(1624),s=n(891),u=n(516),c=n(3077),d=n(3321),f=n(6963),p=n(1054),h=n(4644),g=new Set;function m(e,t,n,r,o,a){if("undefined"==typeof window)return;if(!a&&!(0,i.isLocalURL)(t))return;if(!r.bypassPrefetchedCheck){const o=t+"%"+n+"%"+(void 0!==r.locale?r.locale:"locale"in e?e.locale:void 0);if(g.has(o))return;g.add(o)}const l=a?e.prefetch(t,o):e.prefetch(t,n,r);Promise.resolve(l).catch((e=>{}))}function v(e){return"string"==typeof e?e:(0,a.formatUrl)(e)}const b=r.default.forwardRef((function(e,t){let n;const{href:a,as:g,children:b,prefetch:y=null,passHref:A,replace:w,shallow:x,scroll:E,locale:k,onClick:S,onMouseEnter:_,onTouchStart:C,legacyBehavior:T=!1,...D}=e;n=b,!T||"string"!=typeof n&&"number"!=typeof n||(n=r.default.createElement("a",null,n));const L=r.default.useContext(u.RouterContext),O=r.default.useContext(c.AppRouterContext),R=null!=L?L:O,P=!L,M=!1!==y,N=null===y?h.PrefetchKind.AUTO:h.PrefetchKind.FULL,{href:B,as:I}=r.default.useMemo((()=>{if(!L){const e=v(a);return{href:e,as:g?v(g):e}}const[e,t]=(0,o.resolveHref)(L,a,!0);return{href:e,as:g?(0,o.resolveHref)(L,g):t||e}}),[L,a,g]),j=r.default.useRef(B),F=r.default.useRef(I);let H;T&&(H=r.default.Children.only(n));const z=T?H&&"object"==typeof H&&H.ref:t,[q,U,V]=(0,d.useIntersection)({rootMargin:"200px"}),W=r.default.useCallback((e=>{F.current===I&&j.current===B||(V(),F.current=I,j.current=B),q(e),z&&("function"==typeof z?z(e):"object"==typeof z&&(z.current=e))}),[I,z,B,V,q]);r.default.useEffect((()=>{R&&U&&M&&m(R,B,I,{locale:k},{kind:N},P)}),[I,B,U,k,M,null==L?void 0:L.locale,R,P,N]);const Q={ref:W,onClick(e){T||"function"!=typeof S||S(e),T&&H.props&&"function"==typeof H.props.onClick&&H.props.onClick(e),R&&(e.defaultPrevented||function(e,t,n,o,a,l,s,u,c,d){const{nodeName:f}=e.currentTarget;if("A"===f.toUpperCase()&&(function(e){const t=e.currentTarget.getAttribute("target");return t&&"_self"!==t||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.nativeEvent&&2===e.nativeEvent.which}(e)||!c&&!(0,i.isLocalURL)(n)))return;e.preventDefault();const p=()=>{const e=null==s||s;"beforePopState"in t?t[a?"replace":"push"](n,o,{shallow:l,locale:u,scroll:e}):t[a?"replace":"push"](o||n,{forceOptimisticNavigation:!d,scroll:e})};c?r.default.startTransition(p):p()}(e,R,B,I,w,x,E,k,P,M))},onMouseEnter(e){T||"function"!=typeof _||_(e),T&&H.props&&"function"==typeof H.props.onMouseEnter&&H.props.onMouseEnter(e),R&&(!M&&P||m(R,B,I,{locale:k,priority:!0,bypassPrefetchedCheck:!0},{kind:N},P))},onTouchStart(e){T||"function"!=typeof C||C(e),T&&H.props&&"function"==typeof H.props.onTouchStart&&H.props.onTouchStart(e),R&&(!M&&P||m(R,B,I,{locale:k,priority:!0,bypassPrefetchedCheck:!0},{kind:N},P))}};if((0,l.isAbsoluteUrl)(I))Q.href=I;else if(!T||A||"a"===H.type&&!("href"in H.props)){const e=void 0!==k?k:null==L?void 0:L.locale,t=(null==L?void 0:L.isLocaleDomain)&&(0,f.getDomainLocale)(I,e,null==L?void 0:L.locales,null==L?void 0:L.domainLocales);Q.href=t||(0,p.addBasePath)((0,s.addLocale)(I,e,null==L?void 0:L.defaultLocale))}return T?r.default.cloneElement(H,Q):r.default.createElement("a",{...D,...Q},n)}));("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9789:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizeLocalePath",{enumerable:!0,get:function(){return r}});const r=(e,t)=>window.omnivoreEnv.__NEXT_I18N_SUPPORT?n(816).normalizeLocalePath(e,t):{pathname:e,detectedLocale:void 0};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1119:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathTrailingSlash",{enumerable:!0,get:function(){return i}});const r=n(2794),o=n(2593),i=e=>{if(!e.startsWith("/")||window.omnivoreEnv.__NEXT_MANUAL_TRAILING_SLASH)return e;const{pathname:t,query:n,hash:i}=(0,o.parsePath)(e);return window.omnivoreEnv.__NEXT_TRAILING_SLASH?/\.[^/]+\/?$/.test(t)?""+(0,r.removeTrailingSlash)(t)+n+i:t.endsWith("/")?""+t+n+i:t+"/"+n+i:""+(0,r.removeTrailingSlash)(t)+n+i};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},2087:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeBasePath",{enumerable:!0,get:function(){return i}});const r=n(6200),o=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"";function i(e){return window.omnivoreEnv.__NEXT_MANUAL_CLIENT_BASE_PATH&&!(0,r.hasBasePath)(e)||0===o.length||(e=e.slice(o.length)).startsWith("/")||(e="/"+e),e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},4884:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeLocale",{enumerable:!0,get:function(){return o}});const r=n(2593);function o(e,t){if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){const{pathname:n}=(0,r.parsePath)(e),o=n.toLowerCase(),i=null==t?void 0:t.toLowerCase();return t&&(o.startsWith("/"+i+"/")||o==="/"+i)?(n.length===t.length+1?"/":"")+e.slice(t.length+1):e}return e}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1976:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{requestIdleCallback:function(){return n},cancelIdleCallback:function(){return r}});const n="undefined"!=typeof self&&self.requestIdleCallback&&self.requestIdleCallback.bind(window)||function(e){let t=Date.now();return self.setTimeout((function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-t))}})}),1)},r="undefined"!=typeof self&&self.cancelIdleCallback&&self.cancelIdleCallback.bind(window)||function(e){return clearTimeout(e)};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},1589:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"resolveHref",{enumerable:!0,get:function(){return d}});const r=n(646),o=n(6555),i=n(2093),a=n(1624),l=n(1119),s=n(8657),u=n(9150),c=n(2685);function d(e,t,n){let d,f="string"==typeof t?t:(0,o.formatWithValidation)(t);const p=f.match(/^[a-zA-Z]{1,}:\/\//),h=p?f.slice(p[0].length):f;if((h.split("?")[0]||"").match(/(\/\/|\\)/)){console.error("Invalid href '"+f+"' passed to next/router in page: '"+e.pathname+"'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.");const t=(0,a.normalizeRepeatedSlashes)(h);f=(p?p[0]:"")+t}if(!(0,s.isLocalURL)(f))return n?[f]:f;try{d=new URL(f.startsWith("#")?e.asPath:e.pathname,"http://n")}catch(e){d=new URL("/","http://n")}try{const e=new URL(f,d);e.pathname=(0,l.normalizePathTrailingSlash)(e.pathname);let t="";if((0,u.isDynamicRoute)(e.pathname)&&e.searchParams&&n){const n=(0,r.searchParamsToUrlQuery)(e.searchParams),{result:a,params:l}=(0,c.interpolateAs)(e.pathname,e.pathname,n);a&&(t=(0,o.formatWithValidation)({pathname:a,hash:e.hash,query:(0,i.omit)(n,l)}))}const a=e.origin===d.origin?e.href.slice(e.origin.length):e.href;return n?[a,t||a]:a}catch(e){return n?[f]:f}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},7928:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{markAssetError:function(){return s},isAssetError:function(){return u},getClientBuildManifest:function(){return p},createRouteLoader:function(){return g}}),n(9983);const r=n(3251),o=n(1976),i=n(9515);function a(e,t,n){let r,o=t.get(e);if(o)return"future"in o?o.future:Promise.resolve(o);const i=new Promise((e=>{r=e}));return t.set(e,o={resolve:r,future:i}),n?n().then((e=>(r(e),e))).catch((n=>{throw t.delete(e),n})):i}const l=Symbol("ASSET_LOAD_ERROR");function s(e){return Object.defineProperty(e,l,{})}function u(e){return e&&l in e}const c=function(e){try{return e=document.createElement("link"),!!window.MSInputMethodContext&&!!document.documentMode||e.relList.supports("prefetch")}catch(e){return!1}}(),d=()=>(0,i.getDeploymentIdQueryOrEmptyString)();function f(e,t,n){return new Promise(((r,i)=>{let a=!1;e.then((e=>{a=!0,r(e)})).catch(i),(0,o.requestIdleCallback)((()=>setTimeout((()=>{a||i(n)}),t)))}))}function p(){return self.__BUILD_MANIFEST?Promise.resolve(self.__BUILD_MANIFEST):f(new Promise((e=>{const t=self.__BUILD_MANIFEST_CB;self.__BUILD_MANIFEST_CB=()=>{e(self.__BUILD_MANIFEST),t&&t()}})),3800,s(new Error("Failed to load client build manifest")))}function h(e,t){return p().then((n=>{if(!(t in n))throw s(new Error("Failed to lookup route: "+t));const o=n[t].map((t=>e+"/_next/"+encodeURI(t)));return{scripts:o.filter((e=>e.endsWith(".js"))).map((e=>(0,r.__unsafeCreateTrustedScriptURL)(e)+d())),css:o.filter((e=>e.endsWith(".css"))).map((e=>e+d()))}}))}function g(e){const t=new Map,n=new Map,r=new Map,i=new Map;function l(e){{let t=n.get(e.toString());return t||(document.querySelector('script[src^="'+e+'"]')?Promise.resolve():(n.set(e.toString(),t=function(e,t){return new Promise(((n,r)=>{(t=document.createElement("script")).onload=n,t.onerror=()=>r(s(new Error("Failed to load script: "+e))),t.crossOrigin=window.omnivoreEnv.__NEXT_CROSS_ORIGIN,t.src=e,document.body.appendChild(t)}))}(e)),t))}}function u(e){let t=r.get(e);return t||(r.set(e,t=fetch(e).then((t=>{if(!t.ok)throw new Error("Failed to load stylesheet: "+e);return t.text().then((t=>({href:e,content:t})))})).catch((e=>{throw s(e)}))),t)}return{whenEntrypoint:e=>a(e,t),onEntrypoint(e,n){(n?Promise.resolve().then((()=>n())).then((e=>({component:e&&e.default||e,exports:e})),(e=>({error:e}))):Promise.resolve(void 0)).then((n=>{const r=t.get(e);r&&"resolve"in r?n&&(t.set(e,n),r.resolve(n)):(n?t.set(e,n):t.delete(e),i.delete(e))}))},loadRoute(n,r){return a(n,i,(()=>f(h(e,n).then((e=>{let{scripts:r,css:o}=e;return Promise.all([t.has(n)?[]:Promise.all(r.map(l)),Promise.all(o.map(u))])})).then((e=>this.whenEntrypoint(n).then((t=>({entrypoint:t,styles:e[1]}))))),3800,s(new Error("Route did not complete loading: "+n))).then((e=>{let{entrypoint:t,styles:n}=e;const r=Object.assign({styles:n},t);return"error"in t?t:r})).catch((e=>{if(r)throw e;return{error:e}})).finally((()=>{}))))},prefetch(t){let n;return(n=navigator.connection)&&(n.saveData||/2g/.test(n.effectiveType))?Promise.resolve():h(e,t).then((e=>Promise.all(c?e.scripts.map((e=>{return t=e.toString(),n="script",new Promise(((e,o)=>{const i='\n link[rel="prefetch"][href^="'+t+'"],\n link[rel="preload"][href^="'+t+'"],\n script[src^="'+t+'"]';if(document.querySelector(i))return e();(r=document.createElement("link")).as=n,r.rel="prefetch",r.crossOrigin=window.omnivoreEnv.__NEXT_CROSS_ORIGIN,r.onload=e,r.onerror=()=>o(s(new Error("Failed to prefetch: "+t))),r.href=t,document.head.appendChild(r)}));var t,n,r})):[]))).then((()=>{(0,o.requestIdleCallback)((()=>this.loadRoute(t,!0).catch((()=>{}))))})).catch((()=>{}))}}}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9518:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{Router:function(){return i.default},default:function(){return p},withRouter:function(){return s.default},useRouter:function(){return h},createRouter:function(){return g},makePublicRouterInstance:function(){return m}});const r=n(7083),o=r._(n(2784)),i=r._(n(6640)),a=n(516),l=r._(n(274)),s=r._(n(9564)),u={router:null,readyCallbacks:[],ready(e){if(this.router)return e();"undefined"!=typeof window&&this.readyCallbacks.push(e)}},c=["pathname","route","query","asPath","components","isFallback","basePath","locale","locales","defaultLocale","isReady","isPreview","isLocaleDomain","domainLocales"],d=["push","replace","reload","back","prefetch","beforePopState"];function f(){if(!u.router)throw new Error('No router instance found.\nYou should only use "next/router" on the client side of your app.\n');return u.router}Object.defineProperty(u,"events",{get:()=>i.default.events}),c.forEach((e=>{Object.defineProperty(u,e,{get:()=>f()[e]})})),d.forEach((e=>{u[e]=function(){for(var t=arguments.length,n=new Array(t),r=0;r{u.ready((()=>{i.default.events.on(e,(function(){for(var t=arguments.length,n=new Array(t),r=0;re())),u.readyCallbacks=[],u.router}function m(e){const t=e,n={};for(const e of c)"object"!=typeof t[e]?n[e]=t[e]:n[e]=Object.assign(Array.isArray(t[e])?[]:{},t[e]);return n.events=i.default.events,d.forEach((e=>{n[e]=function(){for(var n=arguments.length,r=new Array(n),o=0;o{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{handleClientScriptLoad:function(){return h},initScriptLoader:function(){return g},default:function(){return v}});const r=n(7083),o=n(5570),i=r._(n(8316)),a=o._(n(2784)),l=n(2560),s=n(9410),u=n(1976),c=new Map,d=new Set,f=["onLoad","onReady","dangerouslySetInnerHTML","children","onError","strategy","stylesheets"],p=e=>{const{src:t,id:n,onLoad:r=(()=>{}),onReady:o=null,dangerouslySetInnerHTML:a,children:l="",strategy:u="afterInteractive",onError:p,stylesheets:h}=e,g=n||t;if(g&&d.has(g))return;if(c.has(t))return d.add(g),void c.get(t).then(r,p);const m=()=>{o&&o(),d.add(g)},v=document.createElement("script"),b=new Promise(((e,t)=>{v.addEventListener("load",(function(t){e(),r&&r.call(this,t),m()})),v.addEventListener("error",(function(e){t(e)}))})).catch((function(e){p&&p(e)}));a?(v.innerHTML=a.__html||"",m()):l?(v.textContent="string"==typeof l?l:Array.isArray(l)?l.join(""):"",m()):t&&(v.src=t,c.set(t,b));for(const[t,n]of Object.entries(e)){if(void 0===n||f.includes(t))continue;const e=s.DOMAttributeNames[t]||t.toLowerCase();v.setAttribute(e,n)}"worker"===u&&v.setAttribute("type","text/partytown"),v.setAttribute("data-nscript",u),h&&(e=>{if(i.default.preinit)e.forEach((e=>{i.default.preinit(e,{as:"style"})}));else if("undefined"!=typeof window){let t=document.head;e.forEach((e=>{let n=document.createElement("link");n.type="text/css",n.rel="stylesheet",n.href=e,t.appendChild(n)}))}})(h),document.body.appendChild(v)};function h(e){const{strategy:t="afterInteractive"}=e;"lazyOnload"===t?window.addEventListener("load",(()=>{(0,u.requestIdleCallback)((()=>p(e)))})):p(e)}function g(e){e.forEach(h),[...document.querySelectorAll('[data-nscript="beforeInteractive"]'),...document.querySelectorAll('[data-nscript="beforePageRender"]')].forEach((e=>{const t=e.id||e.getAttribute("src");d.add(t)}))}function m(e){const{id:t,src:n="",onLoad:r=(()=>{}),onReady:o=null,strategy:s="afterInteractive",onError:c,stylesheets:f,...h}=e,{updateScripts:g,scripts:m,getIsSsr:v,appDir:b,nonce:y}=(0,a.useContext)(l.HeadManagerContext),A=(0,a.useRef)(!1);(0,a.useEffect)((()=>{const e=t||n;A.current||(o&&e&&d.has(e)&&o(),A.current=!0)}),[o,t,n]);const w=(0,a.useRef)(!1);if((0,a.useEffect)((()=>{w.current||("afterInteractive"===s?p(e):"lazyOnload"===s&&function(e){"complete"===document.readyState?(0,u.requestIdleCallback)((()=>p(e))):window.addEventListener("load",(()=>{(0,u.requestIdleCallback)((()=>p(e)))}))}(e),w.current=!0)}),[e,s]),"beforeInteractive"!==s&&"worker"!==s||(g?(m[s]=(m[s]||[]).concat([{id:t,src:n,onLoad:r,onReady:o,onError:c,...h}]),g(m)):v&&v()?d.add(t||n):v&&!v()&&p(e)),b){if(f&&f.forEach((e=>{i.default.preinit(e,{as:"style"})})),"beforeInteractive"===s)return n?(i.default.preload(n,h.integrity?{as:"script",integrity:h.integrity}:{as:"script"}),a.default.createElement("script",{nonce:y,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([n])+")"}})):(h.dangerouslySetInnerHTML&&(h.children=h.dangerouslySetInnerHTML.__html,delete h.dangerouslySetInnerHTML),a.default.createElement("script",{nonce:y,dangerouslySetInnerHTML:{__html:"(self.__next_s=self.__next_s||[]).push("+JSON.stringify([0,{...h}])+")"}}));"afterInteractive"===s&&n&&i.default.preload(n,h.integrity?{as:"script",integrity:h.integrity}:{as:"script"})}return null}Object.defineProperty(m,"__nextScript",{value:!0});const v=m;("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3251:(e,t)=>{"use strict";let n;function r(e){var t,r;return(null==(void 0===n&&"undefined"!=typeof window&&(n=(null==(r=window.trustedTypes)?void 0:r.createPolicy("nextjs",{createHTML:e=>e,createScript:e=>e,createScriptURL:e=>e}))||null),t=n)?void 0:t.createScriptURL(e))||e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"__unsafeCreateTrustedScriptURL",{enumerable:!0,get:function(){return r}}),("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3321:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"useIntersection",{enumerable:!0,get:function(){return s}});const r=n(2784),o=n(1976),i="function"==typeof IntersectionObserver,a=new Map,l=[];function s(e){let{rootRef:t,rootMargin:n,disabled:s}=e;const u=s||!i,[c,d]=(0,r.useState)(!1),f=(0,r.useRef)(null),p=(0,r.useCallback)((e=>{f.current=e}),[]);(0,r.useEffect)((()=>{if(i){if(u||c)return;const e=f.current;if(e&&e.tagName)return function(e,t,n){const{id:r,observer:o,elements:i}=function(e){const t={root:e.root||null,margin:e.rootMargin||""},n=l.find((e=>e.root===t.root&&e.margin===t.margin));let r;if(n&&(r=a.get(n),r))return r;const o=new Map,i=new IntersectionObserver((e=>{e.forEach((e=>{const t=o.get(e.target),n=e.isIntersecting||e.intersectionRatio>0;t&&n&&t(n)}))}),e);return r={id:t,observer:i,elements:o},l.push(t),a.set(t,r),r}(n);return i.set(e,t),o.observe(e),function(){if(i.delete(e),o.unobserve(e),0===i.size){o.disconnect(),a.delete(r);const e=l.findIndex((e=>e.root===r.root&&e.margin===r.margin));e>-1&&l.splice(e,1)}}}(e,(e=>e&&d(e)),{root:null==t?void 0:t.current,rootMargin:n})}else if(!c){const e=(0,o.requestIdleCallback)((()=>d(!0)));return()=>(0,o.cancelIdleCallback)(e)}}),[u,n,t,c,f.current]);const h=(0,r.useCallback)((()=>{d(!1)}),[]);return[p,c,h]}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9564:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});const r=n(7083)._(n(2784)),o=n(9518);function i(e){function t(t){return r.default.createElement(e,{router:(0,o.useRouter)(),...t})}return t.getInitialProps=e.getInitialProps,t.origGetInitialProps=e.origGetInitialProps,t}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3980:e=>{(()=>{"use strict";"undefined"!=typeof __nccwpck_require__&&(__nccwpck_require__.ab="//");var t={};(()=>{var e=t;e.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");for(var r={},i=t||{},l=e.split(o),s=i.decode||n,u=0;u{(()=>{var t={154:(e,t,n)=>{var r=n(781),o=["write","end","destroy"],i=["resume","pause"],a=["data","close"],l=Array.prototype.slice;function s(e,t){if(e.forEach)return e.forEach(t);for(var n=0;n{"use strict";const r=n(147),o=n(781),i=n(796),a=n(154),l=n(530),s=e=>Object.assign({level:9},e);e.exports=(e,t)=>e?l(i.gzip)(e,s(t)).then((e=>e.length)).catch((e=>0)):Promise.resolve(0),e.exports.sync=(e,t)=>i.gzipSync(e,s(t)).length,e.exports.stream=e=>{const t=new o.PassThrough,n=new o.PassThrough,r=a(t,n);let l=0;const u=i.createGzip(s(e)).on("data",(e=>{l+=e.length})).on("error",(()=>{r.gzipSize=0})).on("end",(()=>{r.gzipSize=l,r.emit("gzip-size",l),n.end()}));return t.pipe(u),t.pipe(n,{end:!1}),r},e.exports.file=(t,n)=>new Promise(((o,i)=>{const a=r.createReadStream(t);a.on("error",i);const l=a.pipe(e.exports.stream(n));l.on("error",i),l.on("gzip-size",o)})),e.exports.fileSync=(t,n)=>e.exports.sync(r.readFileSync(t),n)},530:e=>{"use strict";const t=(e,t)=>function(...n){return new(0,t.promiseModule)(((r,o)=>{t.multiArgs?n.push(((...e)=>{t.errorFirst?e[0]?o(e):(e.shift(),r(e)):r(e)})):t.errorFirst?n.push(((e,t)=>{e?o(e):r(t)})):n.push(r),e.apply(this,n)}))};e.exports=(e,n)=>{n=Object.assign({exclude:[/.+(Sync|Stream)$/],errorFirst:!0,promiseModule:Promise},n);const r=typeof e;if(null===e||"object"!==r&&"function"!==r)throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${null===e?"null":r}\``);const o=e=>{const t=t=>"string"==typeof t?e===t:t.test(e);return n.include?n.include.some(t):!n.exclude.some(t)};let i;i="function"===r?function(...r){return n.excludeMain?e(...r):t(e,n).apply(this,r)}:Object.create(Object.getPrototypeOf(e));for(const r in e){const a=e[r];i[r]="function"==typeof a&&o(r)?t(a,n):a}return i}},147:e=>{"use strict";e.exports=n(6032)},781:e=>{"use strict";e.exports=n(5775)},796:e=>{"use strict";e.exports=n(1264)}},r={};function o(e){var n=r[e];if(void 0!==n)return n.exports;var i=r[e]={exports:{}},a=!0;try{t[e](i,i.exports,o),a=!1}finally{a&&delete r[e]}return i.exports}o.ab="//";var i=o(349);e.exports=i})()},9264:(e,t)=>{"use strict";function n(e,t){void 0===t&&(t={});for(var n=function(e){for(var t=[],n=0;n=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122||95===s))break;a+=e[l++]}if(!a)throw new TypeError("Missing parameter name at "+n);t.push({type:"NAME",index:n,value:a}),n=l}else t.push({type:"CLOSE",index:n,value:e[n++]});else t.push({type:"OPEN",index:n,value:e[n++]});else t.push({type:"ESCAPED_CHAR",index:n++,value:e[n++]});else t.push({type:"MODIFIER",index:n,value:e[n++]})}return t.push({type:"END",index:n,value:""}),t}(e),r=t.prefixes,o=void 0===r?"./":r,a="[^"+i(t.delimiter||"/#?")+"]+?",l=[],s=0,u=0,c="",d=function(e){if(u-1:void 0===x;o||(g+="(?:"+h+"(?="+p+"))?"),E||(g+="(?="+h+"|"+p+")")}return new RegExp(g,a(n))}function s(e,t,r){return e instanceof RegExp?function(e,t){if(!t)return e;var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r{"use strict";function n(e){return"/api"===e||Boolean(null==e?void 0:e.startsWith("/api/"))}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isAPIRoute",{enumerable:!0,get:function(){return n}})},274:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{default:function(){return o},getProperError:function(){return i}});const r=n(9910);function o(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}function i(e){return o(e)?e:new Error((0,r.isPlainObject)(e)?JSON.stringify(e):e+"")}},1702:(e,t,n)=>{"use strict";function r(e){return function(){const{cookie:t}=e;if(!t)return{};const{parse:r}=n(3980);return r(Array.isArray(t)?t.join("; "):t)}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getCookieParser",{enumerable:!0,get:function(){return r}})},6520:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{INTERCEPTION_ROUTE_MARKERS:function(){return o},isInterceptionRouteAppPath:function(){return i},extractInterceptionRouteInformation:function(){return a}});const r=n(9361),o=["(..)(..)","(.)","(..)","(...)"];function i(e){return void 0!==e.split("/").find((e=>o.find((t=>e.startsWith(t)))))}function a(e){let t,n,i;for(const r of e.split("/"))if(n=o.find((e=>r.startsWith(e))),n){[t,i]=e.split(n,2);break}if(!t||!n||!i)throw new Error(`Invalid interception route: ${e}. Must be in the format //(..|...|..)(..)/`);switch(t=(0,r.normalizeAppPath)(t),n){case"(.)":i="/"===t?`/${i}`:t+"/"+i;break;case"(..)":if("/"===t)throw new Error(`Invalid interception route: ${e}. Cannot use (..) marker at the root level, use (.) instead.`);i=t.split("/").slice(0,-1).concat(i).join("/");break;case"(...)":i="/"+i;break;case"(..)(..)":const n=t.split("/");if(n.length<=2)throw new Error(`Invalid interception route: ${e}. Cannot use (..)(..) marker at the root level or one level up.`);i=n.slice(0,-2).concat(i).join("/");break;default:throw new Error("Invariant: unexpected marker")}return{interceptingRoute:t,interceptedRoute:i}}},3077:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{CacheStates:function(){return o},AppRouterContext:function(){return i},LayoutRouterContext:function(){return a},GlobalLayoutRouterContext:function(){return l},TemplateContext:function(){return s}});const r=n(7083)._(n(2784));var o;!function(e){e.LAZY_INITIALIZED="LAZYINITIALIZED",e.DATA_FETCH="DATAFETCH",e.READY="READY"}(o||(o={}));const i=r.default.createContext(null),a=r.default.createContext(null),l=r.default.createContext(null),s=r.default.createContext(null)},8261:(e,t,n)=>{"use strict";function r(e){let t=0;for(let n=0;n>>13,t=Math.imul(t,1540483477)}return t>>>0}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"BloomFilter",{enumerable:!0,get:function(){return o}});class o{static from(e,t){void 0===t&&(t=.01);const n=new o(e.length,t);for(const t of e)n.add(t);return n}export(){const e={numItems:this.numItems,errorRate:this.errorRate,numBits:this.numBits,numHashes:this.numHashes,bitArray:this.bitArray};if("nodejs"===window.omnivoreEnv.NEXT_RUNTIME&&this.errorRate<.01){const t=JSON.stringify(e),r=n(4595).sync(t);r>1024&&console.warn("Creating filter with error rate less than 1% (0.01) can increase the size dramatically proceed with caution. Received error rate "+this.errorRate+" resulted in size "+t.length+" bytes, "+r+" bytes (gzip)")}return e}import(e){this.numItems=e.numItems,this.errorRate=e.errorRate,this.numBits=e.numBits,this.numHashes=e.numHashes,this.bitArray=e.bitArray}add(e){this.getHashValues(e).forEach((e=>{this.bitArray[e]=1}))}contains(e){return this.getHashValues(e).every((e=>this.bitArray[e]))}getHashValues(e){const t=[];for(let n=1;n<=this.numHashes;n++){const o=r(""+e+n)%this.numBits;t.push(o)}return t}constructor(e,t){this.numItems=e,this.errorRate=t,this.numBits=Math.ceil(-e*Math.log(t)/(Math.log(2)*Math.log(2))),this.numHashes=Math.ceil(this.numBits/e*Math.log(2)),this.bitArray=new Array(this.numBits).fill(0)}}},8058:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"escapeStringRegexp",{enumerable:!0,get:function(){return o}});const n=/[|\\{}()[\]^$+*?.-]/,r=/[|\\{}()[\]^$+*?.-]/g;function o(e){return n.test(e)?e.replace(r,"\\$&"):e}},2560:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HeadManagerContext",{enumerable:!0,get:function(){return r}});const r=n(7083)._(n(2784)).default.createContext({})},927:(e,t)=>{"use strict";function n(e,t,n){if(e){n&&(n=n.toLowerCase());for(const i of e){var r,o;if(t===(null==(r=i.domain)?void 0:r.split(":")[0].toLowerCase())||n===i.defaultLocale.toLowerCase()||(null==(o=i.locales)?void 0:o.some((e=>e.toLowerCase()===n))))return i}}}Object.defineProperty(t,"D",{enumerable:!0,get:function(){return n}})},816:(e,t)=>{"use strict";function n(e,t){let n;const r=e.split("/");return(t||[]).some((t=>!(!r[1]||r[1].toLowerCase()!==t.toLowerCase()||(n=t,r.splice(1,1),e=r.join("/")||"/",0)))),{pathname:e,detectedLocale:n}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizeLocalePath",{enumerable:!0,get:function(){return n}})},9910:(e,t)=>{"use strict";function n(e){return Object.prototype.toString.call(e)}function r(e){if("[object Object]"!==n(e))return!1;const t=Object.getPrototypeOf(e);return null===t||t.hasOwnProperty("isPrototypeOf")}Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{getObjectClassLabel:function(){return n},isPlainObject:function(){return r}})},7471:(e,t)=>{"use strict";function n(){const e=Object.create(null);return{on(t,n){(e[t]||(e[t]=[])).push(n)},off(t,n){e[t]&&e[t].splice(e[t].indexOf(n)>>>0,1)},emit(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{e(...r)}))}}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return n}})},997:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"denormalizePagePath",{enumerable:!0,get:function(){return i}});const r=n(9150),o=n(9356);function i(e){let t=(0,o.normalizePathSep)(e);return t.startsWith("/index/")&&!(0,r.isDynamicRoute)(t)?t.slice(6):"/index"!==t?t:"/"}},9739:(e,t)=>{"use strict";function n(e){return e.startsWith("/")?e:"/"+e}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"ensureLeadingSlash",{enumerable:!0,get:function(){return n}})},9356:(e,t)=>{"use strict";function n(e){return e.replace(/\\/g,"/")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"normalizePathSep",{enumerable:!0,get:function(){return n}})},516:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"RouterContext",{enumerable:!0,get:function(){return r}});const r=n(7083)._(n(2784)).default.createContext(null)},6640:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{default:function(){return Z},matchesMiddleware:function(){return I},createKey:function(){return G}});const r=n(7083),o=n(5570),i=n(2794),a=n(7928),l=n(4725),s=o._(n(274)),u=n(997),c=n(816),d=r._(n(7471)),f=n(1624),p=n(7482),h=n(1577),g=r._(n(5317)),m=n(3107),v=n(4794),b=n(6555),y=n(936),A=n(2593),w=n(891),x=n(4884),E=n(2087),k=n(1054),S=n(6200),_=n(1589),C=n(4219),T=n(5930),D=n(3778),L=n(2121),O=n(8657),R=n(343),P=n(2093),M=n(2685),N=n(6576);function B(){return Object.assign(new Error("Route Cancelled"),{cancelled:!0})}async function I(e){const t=await Promise.resolve(e.router.pageLoader.getMiddleware());if(!t)return!1;const{pathname:n}=(0,A.parsePath)(e.asPath),r=(0,S.hasBasePath)(n)?(0,E.removeBasePath)(n):n,o=(0,k.addBasePath)((0,w.addLocale)(r,e.locale));return t.some((e=>new RegExp(e.regexp).test(o)))}function j(e){const t=(0,f.getLocationOrigin)();return e.startsWith(t)?e.substring(t.length):e}function F(e,t,n){let[r,o]=(0,_.resolveHref)(e,t,!0);const i=(0,f.getLocationOrigin)(),a=r.startsWith(i),l=o&&o.startsWith(i);r=j(r),o=o?j(o):o;const s=a?r:(0,k.addBasePath)(r),u=n?j((0,_.resolveHref)(e,n)):o||r;return{url:s,as:l?u:(0,k.addBasePath)(u)}}function H(e,t){const n=(0,i.removeTrailingSlash)((0,u.denormalizePagePath)(e));return"/404"===n||"/_error"===n?e:(t.includes(n)||t.some((t=>{if((0,p.isDynamicRoute)(t)&&(0,v.getRouteRegex)(t).re.test(n))return e=t,!0})),(0,i.removeTrailingSlash)(e))}async function z(e){if(!await I(e)||!e.fetchData)return null;try{const t=await e.fetchData(),n=await function(e,t,n){const r={basePath:n.router.basePath,i18n:{locales:n.router.locales},trailingSlash:Boolean(window.omnivoreEnv.__NEXT_TRAILING_SLASH)},o=t.headers.get("x-nextjs-rewrite");let l=o||t.headers.get("x-nextjs-matched-path");const s=t.headers.get("x-matched-path");if(!s||l||s.includes("__next_data_catchall")||s.includes("/_error")||s.includes("/404")||(l=s),l){if(l.startsWith("/")||window.omnivoreEnv.__NEXT_EXTERNAL_MIDDLEWARE_REWRITE_RESOLVE){const t=(0,h.parseRelativeUrl)(l),s=(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:r,parseData:!0});let u=(0,i.removeTrailingSlash)(s.pathname);return Promise.all([n.router.pageLoader.getPageList(),(0,a.getClientBuildManifest)()]).then((i=>{let[a,{__rewrites:l}]=i,d=(0,w.addLocale)(s.pathname,s.locale);if((0,p.isDynamicRoute)(d)||!o&&a.includes((0,c.normalizeLocalePath)((0,E.removeBasePath)(d),n.router.locales).pathname)){const n=(0,T.getNextPathnameInfo)((0,h.parseRelativeUrl)(e).pathname,{nextConfig:window.omnivoreEnv.__NEXT_HAS_REWRITES?void 0:r,parseData:!0});d=(0,k.addBasePath)(n.pathname),t.pathname=d}if(window.omnivoreEnv.__NEXT_HAS_REWRITES){const e=(0,g.default)(d,a,l,t.query,(e=>H(e,a)),n.router.locales);e.matchedPage&&(t.pathname=e.parsedAs.pathname,d=t.pathname,Object.assign(t.query,e.parsedAs.query))}else if(!a.includes(u)){const e=H(u,a);e!==u&&(u=e)}const f=a.includes(u)?u:H((0,c.normalizeLocalePath)((0,E.removeBasePath)(t.pathname),n.router.locales).pathname,a);if((0,p.isDynamicRoute)(f)){const e=(0,m.getRouteMatcher)((0,v.getRouteRegex)(f))(d);Object.assign(t.query,e||{})}return{type:"rewrite",parsedAs:t,resolvedHref:f}}))}const t=(0,A.parsePath)(e),s=(0,D.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(t.pathname,{nextConfig:r,parseData:!0}),defaultLocale:n.router.defaultLocale,buildId:""});return Promise.resolve({type:"redirect-external",destination:""+s+t.query+t.hash})}const u=t.headers.get("x-nextjs-redirect");if(u){if(u.startsWith("/")){const e=(0,A.parsePath)(u),t=(0,D.formatNextPathnameInfo)({...(0,T.getNextPathnameInfo)(e.pathname,{nextConfig:r,parseData:!0}),defaultLocale:n.router.defaultLocale,buildId:""});return Promise.resolve({type:"redirect-internal",newAs:""+t+e.query+e.hash,newUrl:""+t+e.query+e.hash})}return Promise.resolve({type:"redirect-external",destination:u})}return Promise.resolve({type:"next"})}(t.dataHref,t.response,e);return{dataHref:t.dataHref,json:t.json,response:t.response,text:t.text,cacheKey:t.cacheKey,effect:n}}catch(e){return null}}const q=window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&"undefined"!=typeof window&&"scrollRestoration"in window.history&&!!function(){try{let e="__next";return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(e){}}(),U=Symbol("SSG_DATA_NOT_FOUND");function V(e,t,n){return fetch(e,{credentials:"same-origin",method:n.method||"GET",headers:Object.assign({},n.headers,{"x-nextjs-data":"1"})}).then((r=>!r.ok&&t>1&&r.status>=500?V(e,t-1,n):r))}function W(e){try{return JSON.parse(e)}catch(e){return null}}function Q(e){let{dataHref:t,inflightCache:n,isPrefetch:r,hasMiddleware:o,isServerRender:i,parseJSON:l,persistCache:s,isBackground:u,unstable_skipClientCache:c}=e;const{href:d}=new URL(t,window.location.href);var f;const p=e=>V(t,i?3:1,{headers:Object.assign({},r?{purpose:"prefetch"}:{},r&&o?{"x-middleware-prefetch":"1"}:{}),method:null!=(f=null==e?void 0:e.method)?f:"GET"}).then((n=>n.ok&&"HEAD"===(null==e?void 0:e.method)?{dataHref:t,response:n,text:"",json:{},cacheKey:d}:n.text().then((e=>{if(!n.ok){if(o&&[301,302,307,308].includes(n.status))return{dataHref:t,response:n,text:e,json:{},cacheKey:d};var r;if(404===n.status&&(null==(r=W(e))?void 0:r.notFound))return{dataHref:t,json:{notFound:U},response:n,text:e,cacheKey:d};const l=new Error("Failed to load static props");throw i||(0,a.markAssetError)(l),l}return{dataHref:t,json:l?W(e):null,response:n,text:e,cacheKey:d}})))).then((e=>(s&&"no-cache"!==e.response.headers.get("x-middleware-cache")||delete n[d],e))).catch((e=>{throw c||delete n[d],"Failed to fetch"!==e.message&&"NetworkError when attempting to fetch resource."!==e.message&&"Load failed"!==e.message||(0,a.markAssetError)(e),e}));return c&&s?p({}).then((e=>(n[d]=Promise.resolve(e),e))):void 0!==n[d]?n[d]:n[d]=p(u?{method:"HEAD"}:{})}function G(){return Math.random().toString(36).slice(2,10)}function $(e){let{url:t,router:n}=e;if(t===(0,k.addBasePath)((0,w.addLocale)(n.asPath,n.locale)))throw new Error("Invariant: attempted to hard navigate to the same URL "+t+" "+location.href);window.location.href=t}const Y=e=>{let{route:t,router:n}=e,r=!1;const o=n.clc=()=>{r=!0};return()=>{if(r){const e=new Error('Abort fetching component for route: "'+t+'"');throw e.cancelled=!0,e}o===n.clc&&(n.clc=null)}};class Z{reload(){window.location.reload()}back(){window.history.back()}forward(){window.history.forward()}push(e,t,n){if(void 0===n&&(n={}),window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&q)try{sessionStorage.setItem("__next_scroll_"+this._key,JSON.stringify({x:self.pageXOffset,y:self.pageYOffset}))}catch(e){}return({url:e,as:t}=F(this,e,t)),this.change("pushState",e,t,n)}replace(e,t,n){return void 0===n&&(n={}),({url:e,as:t}=F(this,e,t)),this.change("replaceState",e,t,n)}async _bfl(e,t,n,r){if(window.omnivoreEnv.__NEXT_CLIENT_ROUTER_FILTER_ENABLED){let s=!1,u=!1;for(const c of[e,t])if(c){const t=(0,i.removeTrailingSlash)(new URL(c,"http://n").pathname),d=(0,k.addBasePath)((0,w.addLocale)(t,n||this.locale));if(t!==(0,i.removeTrailingSlash)(new URL(this.asPath,"http://n").pathname)){var o,a;s=s||!!(null==(o=this._bfl_s)?void 0:o.contains(t))||!!(null==(a=this._bfl_s)?void 0:a.contains(d));for(const e of[t,d]){const t=e.split("/");for(let e=0;!u&&e{})))}}}return!1}async change(e,t,n,r,o){var u;if(!(0,O.isLocalURL)(t))return $({url:t,router:this}),!1;const d=1===r._h;d||r.shallow||await this._bfl(n,void 0,r.locale);let _=d||r._shouldResolveHref||(0,A.parsePath)(t).pathname===(0,A.parsePath)(n).pathname;const C={...this.state},T=!0!==this.isReady;this.isReady=!0;const D=this.isSsr;if(d||(this.isSsr=!1),d&&this.clc)return!1;const R=C.locale;if(window.omnivoreEnv.__NEXT_I18N_SUPPORT){C.locale=!1===r.locale?this.defaultLocale:r.locale||C.locale,void 0===r.locale&&(r.locale=C.locale);const e=(0,h.parseRelativeUrl)((0,S.hasBasePath)(n)?(0,E.removeBasePath)(n):n),o=(0,c.normalizeLocalePath)(e.pathname,this.locales);o.detectedLocale&&(C.locale=o.detectedLocale,e.pathname=(0,k.addBasePath)(e.pathname),n=(0,b.formatWithValidation)(e),t=(0,k.addBasePath)((0,c.normalizeLocalePath)((0,S.hasBasePath)(t)?(0,E.removeBasePath)(t):t,this.locales).pathname));let i=!1;var N;window.omnivoreEnv.__NEXT_I18N_SUPPORT&&((null==(N=this.locales)?void 0:N.includes(C.locale))||(e.pathname=(0,w.addLocale)(e.pathname,C.locale),$({url:(0,b.formatWithValidation)(e),router:this}),i=!0));const a=(0,y.detectDomainLocale)(this.domainLocales,void 0,C.locale);if(window.omnivoreEnv.__NEXT_I18N_SUPPORT&&!i&&a&&this.isLocaleDomain&&self.location.hostname!==a.domain){const e=(0,E.removeBasePath)(n);$({url:"http"+(a.http?"":"s")+"://"+a.domain+(0,k.addBasePath)((C.locale===a.defaultLocale?"":"/"+C.locale)+("/"===e?"":e)||"/"),router:this}),i=!0}if(i)return new Promise((()=>{}))}f.ST&&performance.mark("routeChange");const{shallow:j=!1,scroll:z=!0}=r,q={shallow:j};this._inFlightRoute&&this.clc&&(D||Z.events.emit("routeChangeError",B(),this._inFlightRoute,q),this.clc(),this.clc=null),n=(0,k.addBasePath)((0,w.addLocale)((0,S.hasBasePath)(n)?(0,E.removeBasePath)(n):n,r.locale,this.defaultLocale));const V=(0,x.removeLocale)((0,S.hasBasePath)(n)?(0,E.removeBasePath)(n):n,C.locale);this._inFlightRoute=n;const W=R!==C.locale;if(!d&&this.onlyAHashChange(V)&&!W){C.asPath=V,Z.events.emit("hashChangeStart",n,q),this.changeState(e,t,n,{...r,scroll:!1}),z&&this.scrollToHash(V);try{await this.set(C,this.components[C.route],null)}catch(e){throw(0,s.default)(e)&&e.cancelled&&Z.events.emit("routeChangeError",e,V,q),e}return Z.events.emit("hashChangeComplete",n,q),!0}let Q,G,Y=(0,h.parseRelativeUrl)(t),{pathname:K,query:J}=Y;if(null==(u=this.components[K])?void 0:u.__appRouter)return $({url:n,router:this}),new Promise((()=>{}));try{[Q,{__rewrites:G}]=await Promise.all([this.pageLoader.getPageList(),(0,a.getClientBuildManifest)(),this.pageLoader.getMiddleware()])}catch(e){return $({url:n,router:this}),!1}this.urlIsNew(V)||W||(e="replaceState");let X=n;K=K?(0,i.removeTrailingSlash)((0,E.removeBasePath)(K)):K;let ee=(0,i.removeTrailingSlash)(K);const te=n.startsWith("/")&&(0,h.parseRelativeUrl)(n).pathname,ne=!(!te||ee===te||(0,p.isDynamicRoute)(ee)&&(0,m.getRouteMatcher)((0,v.getRouteRegex)(ee))(te)),re=!r.shallow&&await I({asPath:n,locale:C.locale,router:this});if(d&&re&&(_=!1),_&&"/_error"!==K)if(r._shouldResolveHref=!0,window.omnivoreEnv.__NEXT_HAS_REWRITES&&n.startsWith("/")){const e=(0,g.default)((0,k.addBasePath)((0,w.addLocale)(V,C.locale),!0),Q,G,J,(e=>H(e,Q)),this.locales);if(e.externalDest)return $({url:n,router:this}),!0;re||(X=e.asPath),e.matchedPage&&e.resolvedHref&&(K=e.resolvedHref,Y.pathname=(0,k.addBasePath)(K),re||(t=(0,b.formatWithValidation)(Y)))}else Y.pathname=H(K,Q),Y.pathname!==K&&(K=Y.pathname,Y.pathname=(0,k.addBasePath)(K),re||(t=(0,b.formatWithValidation)(Y)));if(!(0,O.isLocalURL)(n))return $({url:n,router:this}),!1;X=(0,x.removeLocale)((0,E.removeBasePath)(X),C.locale),ee=(0,i.removeTrailingSlash)(K);let oe=!1;if((0,p.isDynamicRoute)(ee)){const e=(0,h.parseRelativeUrl)(X),r=e.pathname,o=(0,v.getRouteRegex)(ee);oe=(0,m.getRouteMatcher)(o)(r);const i=ee===r,a=i?(0,M.interpolateAs)(ee,r,J):{};if(!oe||i&&!a.result){const e=Object.keys(o.groups).filter((e=>!J[e]&&!o.groups[e].optional));if(e.length>0&&!re)throw new Error((i?"The provided `href` ("+t+") value is missing query values ("+e.join(", ")+") to be interpolated properly. ":"The provided `as` value ("+r+") is incompatible with the `href` value ("+ee+"). ")+"Read more: https://nextjs.org/docs/messages/"+(i?"href-interpolation-failed":"incompatible-href-as"))}else i?n=(0,b.formatWithValidation)(Object.assign({},e,{pathname:a.result,query:(0,P.omit)(J,a.params)})):Object.assign(J,oe)}d||Z.events.emit("routeChangeStart",n,q);const ie="/404"===this.pathname||"/_error"===this.pathname;try{var ae,le,se;let i=await this.getRouteInfo({route:ee,pathname:K,query:J,as:n,resolvedAs:X,routeProps:q,locale:C.locale,isPreview:C.isPreview,hasMiddleware:re,unstable_skipClientCache:r.unstable_skipClientCache,isQueryUpdating:d&&!this.isFallback,isMiddlewareRewrite:ne});if(d||r.shallow||await this._bfl(n,"resolvedAs"in i?i.resolvedAs:void 0,C.locale),"route"in i&&re){K=i.route||ee,ee=K,q.shallow||(J=Object.assign({},i.query||{},J));const e=(0,S.hasBasePath)(Y.pathname)?(0,E.removeBasePath)(Y.pathname):Y.pathname;if(oe&&K!==e&&Object.keys(oe).forEach((e=>{oe&&J[e]===oe[e]&&delete J[e]})),(0,p.isDynamicRoute)(K)){let e=!q.shallow&&i.resolvedAs?i.resolvedAs:(0,k.addBasePath)((0,w.addLocale)(new URL(n,location.href).pathname,C.locale),!0);if((0,S.hasBasePath)(e)&&(e=(0,E.removeBasePath)(e)),window.omnivoreEnv.__NEXT_I18N_SUPPORT){const t=(0,c.normalizeLocalePath)(e,this.locales);C.locale=t.detectedLocale||C.locale,e=t.pathname}const t=(0,v.getRouteRegex)(K),r=(0,m.getRouteMatcher)(t)(new URL(e,location.href).pathname);r&&Object.assign(J,r)}}if("type"in i)return"redirect-internal"===i.type?this.change(e,i.newUrl,i.newAs,r):($({url:i.destination,router:this}),new Promise((()=>{})));const a=i.Component;if(a&&a.unstable_scriptLoader&&[].concat(a.unstable_scriptLoader()).forEach((e=>{(0,l.handleClientScriptLoad)(e.props)})),(i.__N_SSG||i.__N_SSP)&&i.props){if(i.props.pageProps&&i.props.pageProps.__N_REDIRECT){r.locale=!1;const t=i.props.pageProps.__N_REDIRECT;if(t.startsWith("/")&&!1!==i.props.pageProps.__N_REDIRECT_BASE_PATH){const n=(0,h.parseRelativeUrl)(t);n.pathname=H(n.pathname,Q);const{url:o,as:i}=F(this,t,t);return this.change(e,o,i,r)}return $({url:t,router:this}),new Promise((()=>{}))}if(C.isPreview=!!i.props.__N_PREVIEW,i.props.notFound===U){let e;try{await this.fetchComponent("/404"),e="/404"}catch(t){e="/_error"}if(i=await this.getRouteInfo({route:e,pathname:e,query:J,as:n,resolvedAs:X,routeProps:{shallow:!1},locale:C.locale,isPreview:C.isPreview,isNotFound:!0}),"type"in i)throw new Error("Unexpected middleware effect on /404")}}var ue;d&&"/_error"===this.pathname&&500===(null==(le=self.__NEXT_DATA__.props)||null==(ae=le.pageProps)?void 0:ae.statusCode)&&(null==(se=i.props)?void 0:se.pageProps)&&(i.props.pageProps.statusCode=500);const u=r.shallow&&C.route===(null!=(ue=i.route)?ue:ee);var ce;const f=null!=(ce=r.scroll)?ce:!d&&!u,g=f?{x:0,y:0}:null,b=null!=o?o:g,y={...C,route:ee,pathname:K,query:J,asPath:V,isFallback:!1};if(d&&ie){var de,fe,pe;if(i=await this.getRouteInfo({route:this.pathname,pathname:this.pathname,query:J,as:n,resolvedAs:X,routeProps:{shallow:!1},locale:C.locale,isPreview:C.isPreview,isQueryUpdating:d&&!this.isFallback}),"type"in i)throw new Error("Unexpected middleware effect on "+this.pathname);"/_error"===this.pathname&&500===(null==(fe=self.__NEXT_DATA__.props)||null==(de=fe.pageProps)?void 0:de.statusCode)&&(null==(pe=i.props)?void 0:pe.pageProps)&&(i.props.pageProps.statusCode=500);try{await this.set(y,i,b)}catch(e){throw(0,s.default)(e)&&e.cancelled&&Z.events.emit("routeChangeError",e,V,q),e}return!0}if(Z.events.emit("beforeHistoryChange",n,q),this.changeState(e,t,n,r),!d||b||T||W||!(0,L.compareRouterStates)(y,this.state)){try{await this.set(y,i,b)}catch(e){if(!e.cancelled)throw e;i.error=i.error||e}if(i.error)throw d||Z.events.emit("routeChangeError",i.error,V,q),i.error;window.omnivoreEnv.__NEXT_I18N_SUPPORT&&C.locale&&(document.documentElement.lang=C.locale),d||Z.events.emit("routeChangeComplete",n,q);const e=/#.+$/;f&&e.test(n)&&this.scrollToHash(n)}return!0}catch(e){if((0,s.default)(e)&&e.cancelled)return!1;throw e}}changeState(e,t,n,r){void 0===r&&(r={}),"pushState"===e&&(0,f.getURL)()===n||(this._shallow=r.shallow,window.history[e]({url:t,as:n,options:r,__N:!0,key:this._key="pushState"!==e?this._key:G()},"",n))}async handleRouteInfoError(e,t,n,r,o,i){if(console.error(e),e.cancelled)throw e;if((0,a.isAssetError)(e)||i)throw Z.events.emit("routeChangeError",e,r,o),$({url:r,router:this}),B();try{let r;const{page:o,styleSheets:i}=await this.fetchComponent("/_error"),a={props:r,Component:o,styleSheets:i,err:e,error:e};if(!a.props)try{a.props=await this.getInitialProps(o,{err:e,pathname:t,query:n})}catch(e){console.error("Error in error page `getInitialProps`: ",e),a.props={}}return a}catch(e){return this.handleRouteInfoError((0,s.default)(e)?e:new Error(e+""),t,n,r,o,!0)}}async getRouteInfo(e){let{route:t,pathname:n,query:r,as:o,resolvedAs:a,routeProps:l,locale:u,hasMiddleware:d,isPreview:f,unstable_skipClientCache:p,isQueryUpdating:h,isMiddlewareRewrite:g,isNotFound:m}=e,v=t;try{var y,A,w,x;const e=Y({route:v,router:this});let t=this.components[v];if(l.shallow&&t&&this.route===v)return t;d&&(t=void 0);let s=t&&!("initial"in t)?t:void 0;const k=h,S={dataHref:this.pageLoader.getDataHref({href:(0,b.formatWithValidation)({pathname:n,query:r}),skipInterpolation:!0,asPath:m?"/404":a,locale:u}),hasMiddleware:!0,isServerRender:this.isSsr,parseJSON:!0,inflightCache:k?this.sbc:this.sdc,persistCache:!f,isPrefetch:!1,unstable_skipClientCache:p,isBackground:k};let _=h&&!g?null:await z({fetchData:()=>Q(S),asPath:m?"/404":a,locale:u,router:this}).catch((e=>{if(h)return null;throw e}));if(!_||"/_error"!==n&&"/404"!==n||(_.effect=void 0),h&&(_?_.json=self.__NEXT_DATA__.props:_={json:self.__NEXT_DATA__.props}),e(),"redirect-internal"===(null==_||null==(y=_.effect)?void 0:y.type)||"redirect-external"===(null==_||null==(A=_.effect)?void 0:A.type))return _.effect;if("rewrite"===(null==_||null==(w=_.effect)?void 0:w.type)){const e=(0,i.removeTrailingSlash)(_.effect.resolvedHref),o=await this.pageLoader.getPageList();if((!h||o.includes(e))&&(v=e,n=_.effect.resolvedHref,r={...r,..._.effect.parsedAs.query},a=(0,E.removeBasePath)((0,c.normalizeLocalePath)(_.effect.parsedAs.pathname,this.locales).pathname),t=this.components[v],l.shallow&&t&&this.route===v&&!d))return{...t,route:v}}if((0,C.isAPIRoute)(v))return $({url:o,router:this}),new Promise((()=>{}));const T=s||await this.fetchComponent(v).then((e=>({Component:e.page,styleSheets:e.styleSheets,__N_SSG:e.mod.__N_SSG,__N_SSP:e.mod.__N_SSP}))),D=null==_||null==(x=_.response)?void 0:x.headers.get("x-middleware-skip"),L=T.__N_SSG||T.__N_SSP;D&&(null==_?void 0:_.dataHref)&&delete this.sdc[_.dataHref];const{props:O,cacheKey:R}=await this._getData((async()=>{if(L){if((null==_?void 0:_.json)&&!D)return{cacheKey:_.cacheKey,props:_.json};const e=(null==_?void 0:_.dataHref)?_.dataHref:this.pageLoader.getDataHref({href:(0,b.formatWithValidation)({pathname:n,query:r}),asPath:a,locale:u}),t=await Q({dataHref:e,isServerRender:this.isSsr,parseJSON:!0,inflightCache:D?{}:this.sdc,persistCache:!f,isPrefetch:!1,unstable_skipClientCache:p});return{cacheKey:t.cacheKey,props:t.json||{}}}return{headers:{},props:await this.getInitialProps(T.Component,{pathname:n,query:r,asPath:o,locale:u,locales:this.locales,defaultLocale:this.defaultLocale})}}));return T.__N_SSP&&S.dataHref&&R&&delete this.sdc[R],this.isPreview||!T.__N_SSG||h||Q(Object.assign({},S,{isBackground:!0,persistCache:!1,inflightCache:this.sbc})).catch((()=>{})),O.pageProps=Object.assign({},O.pageProps),T.props=O,T.route=v,T.query=r,T.resolvedAs=a,this.components[v]=T,T}catch(e){return this.handleRouteInfoError((0,s.getProperError)(e),n,r,o,l)}}set(e,t,n){return this.state=e,this.sub(t,this.components["/_app"].Component,n)}beforePopState(e){this._bps=e}onlyAHashChange(e){if(!this.asPath)return!1;const[t,n]=this.asPath.split("#"),[r,o]=e.split("#");return!(!o||t!==r||n!==o)||t===r&&n!==o}scrollToHash(e){const[,t=""]=e.split("#");(0,N.handleSmoothScroll)((()=>{if(""===t||"top"===t)return void window.scrollTo(0,0);const e=decodeURIComponent(t),n=document.getElementById(e);if(n)return void n.scrollIntoView();const r=document.getElementsByName(e)[0];r&&r.scrollIntoView()}),{onlyHashChange:this.onlyAHashChange(e)})}urlIsNew(e){return this.asPath!==e}async prefetch(e,t,n){if(void 0===t&&(t=e),void 0===n&&(n={}),"undefined"!=typeof window&&(0,R.isBot)(window.navigator.userAgent))return;let r=(0,h.parseRelativeUrl)(e);const o=r.pathname;let{pathname:l,query:s}=r;const u=l;if(window.omnivoreEnv.__NEXT_I18N_SUPPORT&&!1===n.locale){l=(0,c.normalizeLocalePath)(l,this.locales).pathname,r.pathname=l,e=(0,b.formatWithValidation)(r);let o=(0,h.parseRelativeUrl)(t);const i=(0,c.normalizeLocalePath)(o.pathname,this.locales);o.pathname=i.pathname,n.locale=i.detectedLocale||this.defaultLocale,t=(0,b.formatWithValidation)(o)}const d=await this.pageLoader.getPageList();let f=t;const y=void 0!==n.locale?n.locale||void 0:this.locale,S=await I({asPath:t,locale:y,router:this});if(window.omnivoreEnv.__NEXT_HAS_REWRITES&&t.startsWith("/")){let n;({__rewrites:n}=await(0,a.getClientBuildManifest)());const o=(0,g.default)((0,k.addBasePath)((0,w.addLocale)(t,this.locale),!0),d,n,r.query,(e=>H(e,d)),this.locales);if(o.externalDest)return;S||(f=(0,x.removeLocale)((0,E.removeBasePath)(o.asPath),this.locale)),o.matchedPage&&o.resolvedHref&&(l=o.resolvedHref,r.pathname=l,S||(e=(0,b.formatWithValidation)(r)))}r.pathname=H(r.pathname,d),(0,p.isDynamicRoute)(r.pathname)&&(l=r.pathname,r.pathname=l,Object.assign(s,(0,m.getRouteMatcher)((0,v.getRouteRegex)(r.pathname))((0,A.parsePath)(t).pathname)||{}),S||(e=(0,b.formatWithValidation)(r)));const _="strict"===window.omnivoreEnv.__NEXT_MIDDLEWARE_PREFETCH?null:await z({fetchData:()=>Q({dataHref:this.pageLoader.getDataHref({href:(0,b.formatWithValidation)({pathname:u,query:s}),skipInterpolation:!0,asPath:f,locale:y}),hasMiddleware:!0,isServerRender:this.isSsr,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0}),asPath:t,locale:y,router:this});if("rewrite"===(null==_?void 0:_.effect.type)&&(r.pathname=_.effect.resolvedHref,l=_.effect.resolvedHref,s={...s,..._.effect.parsedAs.query},f=_.effect.parsedAs.pathname,e=(0,b.formatWithValidation)(r)),"redirect-external"===(null==_?void 0:_.effect.type))return;const C=(0,i.removeTrailingSlash)(l);await this._bfl(t,f,n.locale,!0)&&(this.components[o]={__appRouter:!0}),await Promise.all([this.pageLoader._isSsg(C).then((t=>!!t&&Q({dataHref:(null==_?void 0:_.json)?null==_?void 0:_.dataHref:this.pageLoader.getDataHref({href:e,asPath:f,locale:y}),isServerRender:!1,parseJSON:!0,inflightCache:this.sdc,persistCache:!this.isPreview,isPrefetch:!0,unstable_skipClientCache:n.unstable_skipClientCache||n.priority&&!!window.omnivoreEnv.__NEXT_OPTIMISTIC_CLIENT_CACHE}).then((()=>!1)).catch((()=>!1)))),this.pageLoader[n.priority?"loadPage":"prefetch"](C)])}async fetchComponent(e){const t=Y({route:e,router:this});try{const n=await this.pageLoader.loadPage(e);return t(),n}catch(e){throw t(),e}}_getData(e){let t=!1;const n=()=>{t=!0};return this.clc=n,e().then((e=>{if(n===this.clc&&(this.clc=null),t){const e=new Error("Loading initial props cancelled");throw e.cancelled=!0,e}return e}))}_getFlightData(e){return Q({dataHref:e,isServerRender:!0,parseJSON:!1,inflightCache:this.sdc,persistCache:!1,isPrefetch:!1}).then((e=>{let{text:t}=e;return{data:t}}))}getInitialProps(e,t){const{Component:n}=this.components["/_app"],r=this._wrapApp(n);return t.AppTree=r,(0,f.loadGetInitialProps)(n,{AppTree:r,Component:e,router:this,ctx:t})}get route(){return this.state.route}get pathname(){return this.state.pathname}get query(){return this.state.query}get asPath(){return this.state.asPath}get locale(){return this.state.locale}get isFallback(){return this.state.isFallback}get isPreview(){return this.state.isPreview}constructor(e,t,r,{initialProps:o,pageLoader:a,App:l,wrapApp:s,Component:u,err:c,subscription:d,isFallback:g,locale:m,locales:v,defaultLocale:A,domainLocales:w,isPreview:x}){this.sdc={},this.sbc={},this.isFirstPopStateEvent=!0,this._key=G(),this.onPopState=e=>{const{isFirstPopStateEvent:t}=this;this.isFirstPopStateEvent=!1;const n=e.state;if(!n){const{pathname:e,query:t}=this;return void this.changeState("replaceState",(0,b.formatWithValidation)({pathname:(0,k.addBasePath)(e),query:t}),(0,f.getURL)())}if(n.__NA)return void window.location.reload();if(!n.__N)return;if(t&&this.locale===n.options.locale&&n.as===this.asPath)return;let r;const{url:o,as:i,options:a,key:l}=n;if(window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&q&&this._key!==l){try{sessionStorage.setItem("__next_scroll_"+this._key,JSON.stringify({x:self.pageXOffset,y:self.pageYOffset}))}catch(e){}try{const e=sessionStorage.getItem("__next_scroll_"+l);r=JSON.parse(e)}catch(e){r={x:0,y:0}}}this._key=l;const{pathname:s}=(0,h.parseRelativeUrl)(o);this.isSsr&&i===(0,k.addBasePath)(this.asPath)&&s===(0,k.addBasePath)(this.pathname)||this._bps&&!this._bps(n)||this.change("replaceState",o,i,Object.assign({},a,{shallow:a.shallow&&this._shallow,locale:a.locale||this.defaultLocale,_h:0}),r)};const E=(0,i.removeTrailingSlash)(e);if(this.components={},"/_error"!==e&&(this.components[E]={Component:u,initial:!0,props:o,err:c,__N_SSG:o&&o.__N_SSG,__N_SSP:o&&o.__N_SSP}),this.components["/_app"]={Component:l,styleSheets:[]},window.omnivoreEnv.__NEXT_CLIENT_ROUTER_FILTER_ENABLED){const{BloomFilter:e}=n(8261),t=window.omnivoreEnv.__NEXT_CLIENT_ROUTER_S_FILTER,r=window.omnivoreEnv.__NEXT_CLIENT_ROUTER_D_FILTER;(null==t?void 0:t.numHashes)&&(this._bfl_s=new e(t.numItems,t.errorRate),this._bfl_s.import(t)),(null==r?void 0:r.numHashes)&&(this._bfl_d=new e(r.numItems,r.errorRate),this._bfl_d.import(r))}this.events=Z.events,this.pageLoader=a;const S=(0,p.isDynamicRoute)(e)&&self.__NEXT_DATA__.autoExport;if(this.basePath=window.omnivoreEnv.__NEXT_ROUTER_BASEPATH||"",this.sub=d,this.clc=null,this._wrapApp=s,this.isSsr=!0,this.isLocaleDomain=!1,this.isReady=!(!(self.__NEXT_DATA__.gssp||self.__NEXT_DATA__.gip||self.__NEXT_DATA__.isExperimentalCompile||self.__NEXT_DATA__.appGip&&!self.__NEXT_DATA__.gsp)&&(S||self.location.search||window.omnivoreEnv.__NEXT_HAS_REWRITES)),window.omnivoreEnv.__NEXT_I18N_SUPPORT&&(this.locales=v,this.defaultLocale=A,this.domainLocales=w,this.isLocaleDomain=!!(0,y.detectDomainLocale)(w,self.location.hostname)),this.state={route:E,pathname:e,query:t,asPath:S?e:r,isPreview:!!x,locale:window.omnivoreEnv.__NEXT_I18N_SUPPORT?m:void 0,isFallback:g},this._initialMatchesMiddlewarePromise=Promise.resolve(!1),"undefined"!=typeof window){if(!r.startsWith("//")){const n={locale:m},o=(0,f.getURL)();this._initialMatchesMiddlewarePromise=I({router:this,locale:m,asPath:o}).then((i=>(n._shouldResolveHref=r!==e,this.changeState("replaceState",i?o:(0,b.formatWithValidation)({pathname:(0,k.addBasePath)(e),query:t}),o,n),i)))}window.addEventListener("popstate",this.onPopState),window.omnivoreEnv.__NEXT_SCROLL_RESTORATION&&q&&(window.history.scrollRestoration="manual")}}}Z.events=(0,d.default)()},6707:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addLocale",{enumerable:!0,get:function(){return i}});const r=n(284),o=n(9181);function i(e,t,n,i){if(!t||t===n)return e;const a=e.toLowerCase();if(!i){if((0,o.pathHasPrefix)(a,"/api"))return e;if((0,o.pathHasPrefix)(a,"/"+t.toLowerCase()))return e}return(0,r.addPathPrefix)(e,"/"+t)}},284:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathPrefix",{enumerable:!0,get:function(){return o}});const r=n(2593);function o(e,t){if(!e.startsWith("/")||!t)return e;const{pathname:n,query:o,hash:i}=(0,r.parsePath)(e);return""+t+n+o+i}},8030:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"addPathSuffix",{enumerable:!0,get:function(){return o}});const r=n(2593);function o(e,t){if(!e.startsWith("/")||!t)return e;const{pathname:n,query:o,hash:i}=(0,r.parsePath)(e);return""+n+t+o+i}},9361:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{normalizeAppPath:function(){return i},normalizeRscPath:function(){return a}});const r=n(9739),o=n(4334);function i(e){return(0,r.ensureLeadingSlash)(e.split("/").reduce(((e,t,n,r)=>t?(0,o.isGroupSegment)(t)||"@"===t[0]?e:"page"!==t&&"route"!==t||n!==r.length-1?e+"/"+t:e:e),""))}function a(e,t){return t?e.replace(/\.rsc($|\?)/,"$1"):e}},2121:(e,t)=>{"use strict";function n(e,t){const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(let r=n.length;r--;){const o=n[r];if("query"===o){const n=Object.keys(e.query);if(n.length!==Object.keys(t.query).length)return!1;for(let r=n.length;r--;){const o=n[r];if(!t.query.hasOwnProperty(o)||e.query[o]!==t.query[o])return!1}}else if(!t.hasOwnProperty(o)||e[o]!==t[o])return!1}return!0}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"compareRouterStates",{enumerable:!0,get:function(){return n}})},3778:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"formatNextPathnameInfo",{enumerable:!0,get:function(){return l}});const r=n(2794),o=n(284),i=n(8030),a=n(6707);function l(e){let t=(0,a.addLocale)(e.pathname,e.locale,e.buildId?void 0:e.defaultLocale,e.ignorePrefix);return!e.buildId&&e.trailingSlash||(t=(0,r.removeTrailingSlash)(t)),e.buildId&&(t=(0,i.addPathSuffix)((0,o.addPathPrefix)(t,"/_next/data/"+e.buildId),"/"===e.pathname?"index.json":".json")),t=(0,o.addPathPrefix)(t,e.basePath),!e.buildId&&e.trailingSlash?t.endsWith("/")?t:(0,i.addPathSuffix)(t,"/"):(0,r.removeTrailingSlash)(t)}},6555:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{formatUrl:function(){return i},urlObjectKeys:function(){return a},formatWithValidation:function(){return l}});const r=n(5570)._(n(646)),o=/https?|ftp|gopher|file/;function i(e){let{auth:t,hostname:n}=e,i=e.protocol||"",a=e.pathname||"",l=e.hash||"",s=e.query||"",u=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?u=t+e.host:n&&(u=t+(~n.indexOf(":")?"["+n+"]":n),e.port&&(u+=":"+e.port)),s&&"object"==typeof s&&(s=String(r.urlQueryToSearchParams(s)));let c=e.search||s&&"?"+s||"";return i&&!i.endsWith(":")&&(i+=":"),e.slashes||(!i||o.test(i))&&!1!==u?(u="//"+(u||""),a&&"/"!==a[0]&&(a="/"+a)):u||(u=""),l&&"#"!==l[0]&&(l="#"+l),c&&"?"!==c[0]&&(c="?"+c),a=a.replace(/[?#]/g,encodeURIComponent),c=c.replace("#","%23"),""+i+u+a+c+l}const a=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function l(e){return i(e)}},9983:(e,t)=>{"use strict";function n(e,t){return void 0===t&&(t=""),("/"===e?"/index":/^\/index(\/|$)/.test(e)?"/index"+e:""+e)+t}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return n}})},5930:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getNextPathnameInfo",{enumerable:!0,get:function(){return a}});const r=n(816),o=n(5803),i=n(9181);function a(e,t){var n;const{basePath:a,i18n:l,trailingSlash:s}=null!=(n=t.nextConfig)?n:{},u={pathname:e,trailingSlash:"/"!==e?e.endsWith("/"):s};a&&(0,i.pathHasPrefix)(u.pathname,a)&&(u.pathname=(0,o.removePathPrefix)(u.pathname,a),u.basePath=a);let c=u.pathname;if(u.pathname.startsWith("/_next/data/")&&u.pathname.endsWith(".json")){const e=u.pathname.replace(/^\/_next\/data\//,"").replace(/\.json$/,"").split("/"),n=e[0];u.buildId=n,c="index"!==e[1]?"/"+e.slice(1).join("/"):"/",!0===t.parseData&&(u.pathname=c)}if(l){let e=t.i18nProvider?t.i18nProvider.analyze(u.pathname):(0,r.normalizeLocalePath)(u.pathname,l.locales);var d;u.locale=e.detectedLocale,u.pathname=null!=(d=e.pathname)?d:u.pathname,!e.detectedLocale&&u.buildId&&(e=t.i18nProvider?t.i18nProvider.analyze(c):(0,r.normalizeLocalePath)(c,l.locales),e.detectedLocale&&(u.locale=e.detectedLocale))}return u}},6576:(e,t)=>{"use strict";function n(e,t){if(void 0===t&&(t={}),t.onlyHashChange)return void e();const n=document.documentElement,r=n.style.scrollBehavior;n.style.scrollBehavior="auto",t.dontForceLayout||n.getClientRects(),e(),n.style.scrollBehavior=r}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"handleSmoothScroll",{enumerable:!0,get:function(){return n}})},9150:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{getSortedRoutes:function(){return r.getSortedRoutes},isDynamicRoute:function(){return o.isDynamicRoute}});const r=n(9036),o=n(7482)},2685:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"interpolateAs",{enumerable:!0,get:function(){return i}});const r=n(3107),o=n(4794);function i(e,t,n){let i="";const a=(0,o.getRouteRegex)(e),l=a.groups,s=(t!==e?(0,r.getRouteMatcher)(a)(t):"")||n;i=e;const u=Object.keys(l);return u.every((e=>{let t=s[e]||"";const{repeat:n,optional:r}=l[e];let o="["+(n?"...":"")+e+"]";return r&&(o=(t?"":"/")+"["+o+"]"),n&&!Array.isArray(t)&&(t=[t]),(r||e in s)&&(i=i.replace(o,n?t.map((e=>encodeURIComponent(e))).join("/"):encodeURIComponent(t))||"/")}))||(i=""),{params:u,result:i}}},343:(e,t)=>{"use strict";function n(e){return/Googlebot|Mediapartners-Google|AdsBot-Google|googleweblight|Storebot-Google|Google-PageRenderer|Bingbot|BingPreview|Slurp|DuckDuckBot|baiduspider|yandex|sogou|LinkedInBot|bitlybot|tumblr|vkShare|quora link preview|facebookexternalhit|facebookcatalog|Twitterbot|applebot|redditbot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|ia_archiver/i.test(e)}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isBot",{enumerable:!0,get:function(){return n}})},7482:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isDynamicRoute",{enumerable:!0,get:function(){return r}});const n=/\/\[[^/]+?\](?=\/|$)/;function r(e){return n.test(e)}},8657:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isLocalURL",{enumerable:!0,get:function(){return i}});const r=n(1624),o=n(6200);function i(e){if(!(0,r.isAbsoluteUrl)(e))return!0;try{const t=(0,r.getLocationOrigin)(),n=new URL(e,t);return n.origin===t&&(0,o.hasBasePath)(n.pathname)}catch(e){return!1}}},2093:(e,t)=>{"use strict";function n(e,t){const n={};return Object.keys(e).forEach((r=>{t.includes(r)||(n[r]=e[r])})),n}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"omit",{enumerable:!0,get:function(){return n}})},2593:(e,t)=>{"use strict";function n(e){const t=e.indexOf("#"),n=e.indexOf("?"),r=n>-1&&(t<0||n-1?{pathname:e.substring(0,r?n:t),query:r?e.substring(n,t>-1?t:void 0):"",hash:t>-1?e.slice(t):""}:{pathname:e,query:"",hash:""}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parsePath",{enumerable:!0,get:function(){return n}})},1577:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parseRelativeUrl",{enumerable:!0,get:function(){return i}});const r=n(1624),o=n(646);function i(e,t){const n=new URL("undefined"==typeof window?"http://n":(0,r.getLocationOrigin)()),i=t?new URL(t,n):e.startsWith(".")?new URL("undefined"==typeof window?"http://n":window.location.href):n,{pathname:a,searchParams:l,search:s,hash:u,href:c,origin:d}=new URL(e,i);if(d!==n.origin)throw new Error("invariant: invalid relative URL, router received "+e);return{pathname:a,query:(0,o.searchParamsToUrlQuery)(l),search:s,hash:u,href:c.slice(n.origin.length)}}},2011:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"parseUrl",{enumerable:!0,get:function(){return i}});const r=n(646),o=n(1577);function i(e){if(e.startsWith("/"))return(0,o.parseRelativeUrl)(e);const t=new URL(e);return{hash:t.hash,hostname:t.hostname,href:t.href,pathname:t.pathname,port:t.port,protocol:t.protocol,query:(0,r.searchParamsToUrlQuery)(t.searchParams),search:t.search}}},9181:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"pathHasPrefix",{enumerable:!0,get:function(){return o}});const r=n(2593);function o(e,t){if("string"!=typeof e)return!1;const{pathname:n}=(0,r.parsePath)(e);return n===t||n.startsWith(t+"/")}},1095:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getPathMatch",{enumerable:!0,get:function(){return o}});const r=n(9264);function o(e,t){const n=[],o=(0,r.pathToRegexp)(e,n,{delimiter:"/",sensitive:"boolean"==typeof(null==t?void 0:t.sensitive)&&t.sensitive,strict:null==t?void 0:t.strict}),i=(0,r.regexpToFunction)((null==t?void 0:t.regexModifier)?new RegExp(t.regexModifier(o.source),o.flags):o,n);return(e,r)=>{if("string"!=typeof e)return!1;const o=i(e);if(!o)return!1;if(null==t?void 0:t.removeUnnamedParams)for(const e of n)"number"==typeof e.name&&delete o.params[e.name];return{...r,...o.params}}}},9716:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{matchHas:function(){return c},compileNonPath:function(){return d},prepareDestination:function(){return f}});const r=n(9264),o=n(8058),i=n(2011),a=n(6520),l=n(3042),s=n(1702);function u(e){return e.replace(/__ESC_COLON_/gi,":")}function c(e,t,n,r){void 0===n&&(n=[]),void 0===r&&(r=[]);const o={},i=n=>{let r,i=n.key;switch(n.type){case"header":i=i.toLowerCase(),r=e.headers[i];break;case"cookie":r="cookies"in e?e.cookies[n.key]:(0,s.getCookieParser)(e.headers)()[n.key];break;case"query":r=t[i];break;case"host":{const{host:t}=(null==e?void 0:e.headers)||{};r=null==t?void 0:t.split(":")[0].toLowerCase();break}}if(!n.value&&r)return o[function(e){let t="";for(let n=0;n64&&r<91||r>96&&r<123)&&(t+=e[n])}return t}(i)]=r,!0;if(r){const e=new RegExp("^"+n.value+"$"),t=Array.isArray(r)?r.slice(-1)[0].match(e):r.match(e);if(t)return Array.isArray(t)&&(t.groups?Object.keys(t.groups).forEach((e=>{o[e]=t.groups[e]})):"host"===n.type&&t[0]&&(o.host=t[0])),!0}return!1};return!(!n.every((e=>i(e)))||r.some((e=>i(e))))&&o}function d(e,t){if(!e.includes(":"))return e;for(const n of Object.keys(t))e.includes(":"+n)&&(e=e.replace(new RegExp(":"+n+"\\*","g"),":"+n+"--ESCAPED_PARAM_ASTERISKS").replace(new RegExp(":"+n+"\\?","g"),":"+n+"--ESCAPED_PARAM_QUESTION").replace(new RegExp(":"+n+"\\+","g"),":"+n+"--ESCAPED_PARAM_PLUS").replace(new RegExp(":"+n+"(?!\\w)","g"),"--ESCAPED_PARAM_COLON"+n));return e=e.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g,"\\$1").replace(/--ESCAPED_PARAM_PLUS/g,"+").replace(/--ESCAPED_PARAM_COLON/g,":").replace(/--ESCAPED_PARAM_QUESTION/g,"?").replace(/--ESCAPED_PARAM_ASTERISKS/g,"*"),(0,r.compile)("/"+e,{validate:!1})(t).slice(1)}function f(e){const t=Object.assign({},e.query);delete t.__nextLocale,delete t.__nextDefaultLocale,delete t.__nextDataReq,delete t.__nextInferredLocaleFromDefault,delete t[l.NEXT_RSC_UNION_QUERY];let n=e.destination;for(const r of Object.keys({...e.params,...t}))s=r,n=n.replace(new RegExp(":"+(0,o.escapeStringRegexp)(s),"g"),"__ESC_COLON_"+s);var s;const c=(0,i.parseUrl)(n),f=c.query,p=u(""+c.pathname+(c.hash||"")),h=u(c.hostname||""),g=[],m=[];(0,r.pathToRegexp)(p,g),(0,r.pathToRegexp)(h,m);const v=[];g.forEach((e=>v.push(e.name))),m.forEach((e=>v.push(e.name)));const b=(0,r.compile)(p,{validate:!1}),y=(0,r.compile)(h,{validate:!1});for(const[t,n]of Object.entries(f))Array.isArray(n)?f[t]=n.map((t=>d(u(t),e.params))):"string"==typeof n&&(f[t]=d(u(n),e.params));let A,w=Object.keys(e.params).filter((e=>"nextInternalLocale"!==e));if(e.appendParamsToQuery&&!w.some((e=>v.includes(e))))for(const t of w)t in f||(f[t]=e.params[t]);if((0,a.isInterceptionRouteAppPath)(p))for(const t of p.split("/")){const n=a.INTERCEPTION_ROUTE_MARKERS.find((e=>t.startsWith(e)));if(n){e.params[0]=n;break}}try{A=b(e.params);const[t,n]=A.split("#");c.hostname=y(e.params),c.pathname=t,c.hash=(n?"#":"")+(n||""),delete c.search}catch(e){if(e.message.match(/Expected .*? to not repeat, but got an array/))throw new Error("To use a multi-match in the destination you must add `*` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match");throw e}return c.query={...t,...c.query},{newUrl:A,destQuery:f,parsedDestination:c}}},646:(e,t)=>{"use strict";function n(e){const t={};return e.forEach(((e,n)=>{void 0===t[n]?t[n]=e:Array.isArray(t[n])?t[n].push(e):t[n]=[t[n],e]})),t}function r(e){return"string"==typeof e||"number"==typeof e&&!isNaN(e)||"boolean"==typeof e?String(e):""}function o(e){const t=new URLSearchParams;return Object.entries(e).forEach((e=>{let[n,o]=e;Array.isArray(o)?o.forEach((e=>t.append(n,r(e)))):t.set(n,r(o))})),t}function i(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{Array.from(t.keys()).forEach((t=>e.delete(t))),t.forEach(((t,n)=>e.append(n,t)))})),e}Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{searchParamsToUrlQuery:function(){return n},urlQueryToSearchParams:function(){return o},assign:function(){return i}})},5803:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removePathPrefix",{enumerable:!0,get:function(){return o}});const r=n(9181);function o(e,t){if(!(0,r.pathHasPrefix)(e,t))return e;const n=e.slice(t.length);return n.startsWith("/")?n:"/"+n}},2794:(e,t)=>{"use strict";function n(e){return e.replace(/\/$/,"")||"/"}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return n}})},5317:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return u}});const r=n(1095),o=n(9716),i=n(2794),a=n(816),l=n(2087),s=n(1577);function u(e,t,n,u,c,d){let f,p=!1,h=!1,g=(0,s.parseRelativeUrl)(e),m=(0,i.removeTrailingSlash)((0,a.normalizeLocalePath)((0,l.removeBasePath)(g.pathname),d).pathname);const v=n=>{let s=(0,r.getPathMatch)(n.source+(window.omnivoreEnv.__NEXT_TRAILING_SLASH?"(/)?":""),{removeUnnamedParams:!0,strict:!0})(g.pathname);if((n.has||n.missing)&&s){const e=(0,o.matchHas)({headers:{host:document.location.hostname,"user-agent":navigator.userAgent},cookies:document.cookie.split("; ").reduce(((e,t)=>{const[n,...r]=t.split("=");return e[n]=r.join("="),e}),{})},g.query,n.has,n.missing);e?Object.assign(s,e):s=!1}if(s){if(!n.destination)return h=!0,!0;const r=(0,o.prepareDestination)({appendParamsToQuery:!0,destination:n.destination,params:s,query:u});if(g=r.parsedDestination,e=r.newUrl,Object.assign(u,r.parsedDestination.query),m=(0,i.removeTrailingSlash)((0,a.normalizeLocalePath)((0,l.removeBasePath)(e),d).pathname),t.includes(m))return p=!0,f=m,!0;if(f=c(m),f!==e&&t.includes(f))return p=!0,!0}};let b=!1;for(let e=0;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getRouteMatcher",{enumerable:!0,get:function(){return o}});const r=n(1624);function o(e){let{re:t,groups:n}=e;return e=>{const o=t.exec(e);if(!o)return!1;const i=e=>{try{return decodeURIComponent(e)}catch(e){throw new r.DecodeError("failed to decode param")}},a={};return Object.keys(n).forEach((e=>{const t=n[e],r=o[t.pos];void 0!==r&&(a[e]=~r.indexOf("/")?r.split("/").map((e=>i(e))):t.repeat?[i(r)]:i(r))})),a}}},4794:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{getRouteRegex:function(){return s},getNamedRouteRegex:function(){return d},getNamedMiddlewareRegex:function(){return f}});const r=n(6520),o=n(8058),i=n(2794);function a(e){const t=e.startsWith("[")&&e.endsWith("]");t&&(e=e.slice(1,-1));const n=e.startsWith("...");return n&&(e=e.slice(3)),{key:e,repeat:n,optional:t}}function l(e){const t=(0,i.removeTrailingSlash)(e).slice(1).split("/"),n={};let l=1;return{parameterizedRoute:t.map((e=>{const t=r.INTERCEPTION_ROUTE_MARKERS.find((t=>e.startsWith(t))),i=e.match(/\[((?:\[.*\])|.+)\]/);if(t&&i){const{key:e,optional:r,repeat:s}=a(i[1]);return n[e]={pos:l++,repeat:s,optional:r},"/"+(0,o.escapeStringRegexp)(t)+"([^/]+?)"}if(i){const{key:e,repeat:t,optional:r}=a(i[1]);return n[e]={pos:l++,repeat:t,optional:r},t?r?"(?:/(.+?))?":"/(.+?)":"/([^/]+?)"}return"/"+(0,o.escapeStringRegexp)(e)})).join(""),groups:n}}function s(e){const{parameterizedRoute:t,groups:n}=l(e);return{re:new RegExp("^"+t+"(?:/)?$"),groups:n}}function u(e){let{getSafeRouteKey:t,segment:n,routeKeys:r,keyPrefix:o}=e;const{key:i,optional:l,repeat:s}=a(n);let u=i.replace(/\W/g,"");o&&(u=""+o+u);let c=!1;return(0===u.length||u.length>30)&&(c=!0),isNaN(parseInt(u.slice(0,1)))||(c=!0),c&&(u=t()),r[u]=o?""+o+i:""+i,s?l?"(?:/(?<"+u+">.+?))?":"/(?<"+u+">.+?)":"/(?<"+u+">[^/]+?)"}function c(e,t){const n=(0,i.removeTrailingSlash)(e).slice(1).split("/"),a=function(){let e=0;return()=>{let t="",n=++e;for(;n>0;)t+=String.fromCharCode(97+(n-1)%26),n=Math.floor((n-1)/26);return t}}(),l={};return{namedParameterizedRoute:n.map((e=>{const n=r.INTERCEPTION_ROUTE_MARKERS.some((t=>e.startsWith(t))),i=e.match(/\[((?:\[.*\])|.+)\]/);return n&&i?u({getSafeRouteKey:a,segment:i[1],routeKeys:l,keyPrefix:t?"nxtI":void 0}):i?u({getSafeRouteKey:a,segment:i[1],routeKeys:l,keyPrefix:t?"nxtP":void 0}):"/"+(0,o.escapeStringRegexp)(e)})).join(""),routeKeys:l}}function d(e,t){const n=c(e,t);return{...s(e),namedRegex:"^"+n.namedParameterizedRoute+"(?:/)?$",routeKeys:n.routeKeys}}function f(e,t){const{parameterizedRoute:n}=l(e),{catchAll:r=!0}=t;if("/"===n)return{namedRegex:"^/"+(r?".*":"")+"$"};const{namedParameterizedRoute:o}=c(e,!1);return{namedRegex:"^"+o+(r?"(?:(/.*)?)":"")+"$"}}},9036:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"getSortedRoutes",{enumerable:!0,get:function(){return r}});class n{insert(e){this._insert(e.split("/").filter(Boolean),[],!1)}smoosh(){return this._smoosh()}_smoosh(e){void 0===e&&(e="/");const t=[...this.children.keys()].sort();null!==this.slugName&&t.splice(t.indexOf("[]"),1),null!==this.restSlugName&&t.splice(t.indexOf("[...]"),1),null!==this.optionalRestSlugName&&t.splice(t.indexOf("[[...]]"),1);const n=t.map((t=>this.children.get(t)._smoosh(""+e+t+"/"))).reduce(((e,t)=>[...e,...t]),[]);if(null!==this.slugName&&n.push(...this.children.get("[]")._smoosh(e+"["+this.slugName+"]/")),!this.placeholder){const t="/"===e?"/":e.slice(0,-1);if(null!=this.optionalRestSlugName)throw new Error('You cannot define a route with the same specificity as a optional catch-all route ("'+t+'" and "'+t+"[[..."+this.optionalRestSlugName+']]").');n.unshift(t)}return null!==this.restSlugName&&n.push(...this.children.get("[...]")._smoosh(e+"[..."+this.restSlugName+"]/")),null!==this.optionalRestSlugName&&n.push(...this.children.get("[[...]]")._smoosh(e+"[[..."+this.optionalRestSlugName+"]]/")),n}_insert(e,t,r){if(0===e.length)return void(this.placeholder=!1);if(r)throw new Error("Catch-all must be the last part of the URL.");let o=e[0];if(o.startsWith("[")&&o.endsWith("]")){let i=o.slice(1,-1),a=!1;if(i.startsWith("[")&&i.endsWith("]")&&(i=i.slice(1,-1),a=!0),i.startsWith("...")&&(i=i.substring(3),r=!0),i.startsWith("[")||i.endsWith("]"))throw new Error("Segment names may not start or end with extra brackets ('"+i+"').");if(i.startsWith("."))throw new Error("Segment names may not start with erroneous periods ('"+i+"').");function l(e,n){if(null!==e&&e!==n)throw new Error("You cannot use different slug names for the same dynamic path ('"+e+"' !== '"+n+"').");t.forEach((e=>{if(e===n)throw new Error('You cannot have the same slug name "'+n+'" repeat within a single dynamic path');if(e.replace(/\W/g,"")===o.replace(/\W/g,""))throw new Error('You cannot have the slug names "'+e+'" and "'+n+'" differ only by non-word symbols within a single dynamic path')})),t.push(n)}if(r)if(a){if(null!=this.restSlugName)throw new Error('You cannot use both an required and optional catch-all route at the same level ("[...'+this.restSlugName+']" and "'+e[0]+'" ).');l(this.optionalRestSlugName,i),this.optionalRestSlugName=i,o="[[...]]"}else{if(null!=this.optionalRestSlugName)throw new Error('You cannot use both an optional and required catch-all route at the same level ("[[...'+this.optionalRestSlugName+']]" and "'+e[0]+'").');l(this.restSlugName,i),this.restSlugName=i,o="[...]"}else{if(a)throw new Error('Optional route parameters are not yet supported ("'+e[0]+'").');l(this.slugName,i),this.slugName=i,o="[]"}}this.children.has(o)||this.children.set(o,new n),this.children.get(o)._insert(e.slice(1),t,r)}constructor(){this.placeholder=!0,this.children=new Map,this.slugName=null,this.restSlugName=null,this.optionalRestSlugName=null}}function r(e){const t=new n;return e.forEach((e=>t.insert(e))),t.smoosh()}},4334:(e,t)=>{"use strict";function n(e){return"("===e[0]&&e.endsWith(")")}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"isGroupSegment",{enumerable:!0,get:function(){return n}})},1624:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e,t){for(var n in t)Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}(t,{WEB_VITALS:function(){return n},execOnce:function(){return r},isAbsoluteUrl:function(){return i},getLocationOrigin:function(){return a},getURL:function(){return l},getDisplayName:function(){return s},isResSent:function(){return u},normalizeRepeatedSlashes:function(){return c},loadGetInitialProps:function(){return d},SP:function(){return f},ST:function(){return p},DecodeError:function(){return h},NormalizeError:function(){return g},PageNotFoundError:function(){return m},MissingStaticPage:function(){return v},MiddlewareNotFoundError:function(){return b},stringifyError:function(){return y}});const n=["CLS","FCP","FID","INP","LCP","TTFB"];function r(e){let t,n=!1;return function(){for(var r=arguments.length,o=new Array(r),i=0;io.test(e);function a(){const{protocol:e,hostname:t,port:n}=window.location;return e+"//"+t+(n?":"+n:"")}function l(){const{href:e}=window.location,t=a();return e.substring(t.length)}function s(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function u(e){return e.finished||e.headersSent}function c(e){const t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?"?"+t.slice(1).join("?"):"")}async function d(e,t){const n=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await d(t.Component,t.ctx)}:{};const r=await e.getInitialProps(t);if(n&&u(n))return r;if(!r){const t='"'+s(e)+'.getInitialProps()" should resolve to an object. But found "'+r+'" instead.';throw new Error(t)}return r}const f="undefined"!=typeof performance,p=f&&["mark","measure","getEntriesByName"].every((e=>"function"==typeof performance[e]));class h extends Error{}class g extends Error{}class m extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message="Cannot find module for page: "+e}}class v extends Error{constructor(e,t){super(),this.message="Failed to load static file for page: "+e+" "+t}}class b extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function y(e){return JSON.stringify({message:e.message,stack:e.stack})}},9097:(e,t,n)=>{n(4529)},5632:(e,t,n)=>{e.exports=n(9518)},2852:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.nodeHtmlParserConfig=t.aTagTranslatorConfig=t.defaultCodeBlockTranslators=t.tableCellTranslatorConfig=t.tableRowTranslatorConfig=t.tableTranslatorConfig=t.defaultTranslators=t.defaultOptions=t.contentlessElements=t.defaultIgnoreElements=t.defaultBlockElements=void 0;const r=n(3525),o=n(6183);t.defaultBlockElements=["ADDRESS","ARTICLE","ASIDE","AUDIO","BLOCKQUOTE","BODY","CANVAS","CENTER","DD","DIR","DIV","DL","DT","FIELDSET","FIGCAPTION","FIGURE","FOOTER","FORM","FRAMESET","H1","H2","H3","H4","H5","H6","HEADER","HGROUP","HR","HTML","ISINDEX","LI","MAIN","MENU","NAV","NOFRAMES","NOSCRIPT","OL","OUTPUT","P","PRE","SECTION","TABLE","TBODY","TD","TFOOT","TH","THEAD","TR","UL"],t.defaultIgnoreElements=["AREA","BASE","COL","COMMAND","EMBED","HEAD","INPUT","KEYGEN","LINK","META","PARAM","SCRIPT","SOURCE","STYLE","TRACK","WBR"],t.contentlessElements=["BR","HR","IMG"],t.defaultOptions=Object.freeze({preferNativeParser:!1,codeFence:"```",bulletMarker:"*",indent:" ",codeBlockStyle:"fenced",emDelimiter:"_",strongDelimiter:"**",strikeDelimiter:"~~",maxConsecutiveNewlines:3,globalEscape:[/[\\`*_~\[\]]/gm,"\\$&"],lineStartEscape:[/^(\s*?)((?:\+\s)|(?:[=>-])|(?:#{1,6}\s))|(?:(\d+)(\.\s))/gm,"$1$3\\$2$4"],useInlineLinks:!0}),t.defaultTranslators={pre:{noEscape:!0,preserveWhitespace:!0},br:{content:" \n",recurse:!1},hr:{content:"---",recurse:!1},"h1,h2,h3,h4,h5,h6":({node:e})=>({prefix:"#".repeat(+e.tagName.charAt(1))+" "}),"strong,b":{spaceIfRepeatingChar:!0,postprocess:({content:e,options:{strongDelimiter:t}})=>(0,r.isWhiteSpaceOnly)(e)?o.PostProcessResult.RemoveNode:(0,r.tagSurround)(e,t)},"del,s,strike":{spaceIfRepeatingChar:!0,postprocess:({content:e,options:{strikeDelimiter:t}})=>(0,r.isWhiteSpaceOnly)(e)?o.PostProcessResult.RemoveNode:(0,r.tagSurround)(e,t)},"em,i":{spaceIfRepeatingChar:!0,postprocess:({content:e,options:{emDelimiter:t}})=>(0,r.isWhiteSpaceOnly)(e)?o.PostProcessResult.RemoveNode:(0,r.tagSurround)(e,t)},"ol,ul":({listKind:e})=>({surroundingNewlines:e?1:2}),li:({options:{bulletMarker:e},indentLevel:t,listKind:n,listItemNumber:i})=>{const a=+(t||0);return{prefix:" ".repeat(+(t||0))+("OL"===n&&void 0!==i?`${i}. `:`${e} `),surroundingNewlines:1,postprocess:({content:e})=>(0,r.isWhiteSpaceOnly)(e)?o.PostProcessResult.RemoveNode:e.trim().replace(/([^\r\n])(?:\r?\n)+/g,`$1 \n${" ".repeat(a)}`).replace(/(\S+?)[^\S\r\n]+$/gm,"$1 ")}},blockquote:{postprocess:({content:e})=>(0,r.trimNewLines)(e).replace(/^(>*)[^\S\r\n]?/gm,">$1 ")},code:({node:e,parent:t,options:{codeFence:n,codeBlockStyle:o},visitor:i})=>{var a,l;return["PRE","WRAPPED-PRE"].includes(null==t?void 0:t.tagName)&&t.childNodes.length<2?"fenced"===o?{noEscape:!0,prefix:n+((null===(l=null===(a=e.getAttribute("class"))||void 0===a?void 0:a.match(/language-(\S+)/))||void 0===l?void 0:l[1])||"")+"\n",postfix:"\n"+n,childTranslators:i.instance.codeBlockTranslators}:{noEscape:!0,postprocess:({content:e})=>e.replace(/^/gm," "),childTranslators:i.instance.codeBlockTranslators}:{spaceIfRepeatingChar:!0,noEscape:!0,postprocess:({content:e})=>{var t,n;const o="`"+((null===(n=null===(t=e.match(/`+/g))||void 0===t?void 0:t.sort(((e,t)=>t.length-e.length)))||void 0===n?void 0:n[0])||""),i=o.length>1?" ":"";return(0,r.surround)((0,r.surround)(e,i),o)}}},table:({visitor:e})=>({surroundingNewlines:2,childTranslators:e.instance.tableTranslators,postprocess:({content:e,nodeMetadata:t,node:n})=>{const i=(0,r.splitSpecial)(e).map((({text:e})=>e.replace(/^(?:\|\s+)?(.+)\s*\|\s*$/,"$1"))),a=[];let l=[];for(const e of i){if(!e)continue;const t=e.split(" |").map(((e,t)=>(e=e.trim(),(l.length{var n;u+="| ";for(let t=0;t" "+"-".repeat(e)+" |")).join("")+"\n")})),u}}),a:({node:e,options:t,visitor:n})=>{const r=e.getAttribute("href");if(!r)return{};let o="";for(const e of r)switch(e){case"(":o+="%28";break;case")":o+="%29";break;case"_":o+="%5F";break;case"*":o+="%2A";break;default:o+=e}const i=e.getAttribute("title");return e.textContent===r&&t.useInlineLinks?{content:`<${o}>`}:{postprocess:({content:e})=>e.replace(/(?:\r?\n)+/g," "),childTranslators:n.instance.aTagTranslators,prefix:"[",postfix:"]"+(t.useLinkReferenceDefinitions?`[${n.addOrGetUrlDefinition(o)}]`:`(${o}${i?` "${i}"`:""})`)}},img:({node:e,options:t})=>{const n=e.getAttribute("src")||"";if(!n||!t.keepDataImages&&/^data:/i.test(n))return{ignore:!0};const r=e.getAttribute("alt")||"",o=e.getAttribute("title")||"";return{content:`![${r}](${n}${o&&` "${o}"`})`,recurse:!1}}},t.tableTranslatorConfig={caption:({visitor:e})=>({surroundingNewlines:!1,childTranslators:e.instance.tableCellTranslators,postprocess:({content:e,nodeMetadata:t,node:n})=>{const r=e.replace(/(?:\r?\n)+/g," ").trim();return r&&(t.get(n).tableMeta.caption="__"+r+"__"),o.PostProcessResult.RemoveNode}}),tr:({visitor:e})=>({surroundingNewlines:!1,childTranslators:e.instance.tableRowTranslators,postfix:"\n",prefix:"| ",postprocess:({content:e})=>/ \|\s*$/.test(e)?e:o.PostProcessResult.RemoveNode}),"th,td":({visitor:e})=>({surroundingNewlines:!1,childTranslators:e.instance.tableCellTranslators,prefix:" ",postfix:" |",postprocess:({content:e})=>(0,r.trimNewLines)(e).replace("|","\\|").replace(/(?:\r?\n)+/g," ").trim()})},t.tableRowTranslatorConfig={"th,td":t.tableTranslatorConfig["th,td"]},t.tableCellTranslatorConfig={a:t.defaultTranslators.a,"strong,b":t.defaultTranslators["strong,b"],"del,s,strike":t.defaultTranslators["del,s,strike"],"em,i":t.defaultTranslators["em,i"],img:t.defaultTranslators.img},t.defaultCodeBlockTranslators={br:{content:"\n",recurse:!1},hr:{content:"---",recurse:!1},"h1,h2,h3,h4,h5,h6":{prefix:"[",postfix:"]"},"ol,ul":t.defaultTranslators["ol,ul"],li:t.defaultTranslators.li,tr:{surroundingNewlines:!0},img:{recurse:!1}},t.aTagTranslatorConfig={br:{content:"\n",recurse:!1},hr:{content:"\n",recurse:!1},pre:t.defaultTranslators.pre,"strong,b":t.defaultTranslators["strong,b"],"del,s,strike":t.defaultTranslators["del,s,strike"],"em,i":t.defaultTranslators["em,i"],img:t.defaultTranslators.img},t.nodeHtmlParserConfig={lowerCaseTagName:!1,comment:!1,fixNestedATags:!0,blockTextElements:{script:!1,noscript:!1,style:!1}}},3468:(e,t,n)=>{"use strict";t.dx=void 0;var r=n(2189);Object.defineProperty(t,"dx",{enumerable:!0,get:function(){return r.NodeHtmlMarkdown}});n(6183)},2189:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NodeHtmlMarkdown=void 0;const r=n(6183),o=n(2852),i=n(3525),a=n(819);class l{constructor(e,t,n){var i,a,l,s;this.translators=new r.TranslatorCollection,this.aTagTranslators=new r.TranslatorCollection,this.codeBlockTranslators=new r.TranslatorCollection,this.tableTranslators=new r.TranslatorCollection,this.tableRowTranslators=new r.TranslatorCollection,this.tableCellTranslators=new r.TranslatorCollection,this.options=Object.assign(Object.assign({},o.defaultOptions),e);const u=null!==(a=null===(i=this.options.ignore)||void 0===i?void 0:i.concat(o.defaultIgnoreElements))&&void 0!==a?a:o.defaultIgnoreElements,c=null!==(s=null===(l=this.options.blockElements)||void 0===l?void 0:l.concat(o.defaultBlockElements))&&void 0!==s?s:o.defaultBlockElements;null==u||u.forEach((e=>{this.translators.set(e,{ignore:!0,recurse:!1}),this.codeBlockTranslators.set(e,{ignore:!0,recurse:!1})})),null==c||c.forEach((e=>{this.translators.set(e,{surroundingNewlines:2}),this.codeBlockTranslators.set(e,{surroundingNewlines:2})}));for(const[e,n]of Object.entries(Object.assign(Object.assign({},o.defaultTranslators),t)))this.translators.set(e,n,!0);for(const[e,t]of Object.entries(Object.assign(Object.assign({},o.defaultCodeBlockTranslators),n)))this.codeBlockTranslators.set(e,t,!0);for(const[e,t]of Object.entries(o.aTagTranslatorConfig))this.aTagTranslators.set(e,t,!0);for(const[e,t]of Object.entries(o.tableTranslatorConfig))this.tableTranslators.set(e,t,!0);for(const[e,t]of Object.entries(o.tableRowTranslatorConfig))this.tableRowTranslators.set(e,t,!0);for(const[e,t]of Object.entries(o.tableCellTranslatorConfig))this.tableCellTranslators.set(e,t,!0);this.options.textReplace||(this.options.textReplace=[]),this.options.textReplace.push([/^/gim,""])}static translate(e,t,n,r){return l.prototype.translateWorker.call(new l(t,n,r),e)}translate(e){return this.translateWorker(e)}translateWorker(e){const t="string"!=typeof e,n=t?e:{default:e},r={};for(const[e,t]of Object.entries(n)){const n=(0,i.parseHTML)(t,this.options);r[e]=(0,a.getMarkdownForHtmlNodes)(this,n,"default"!==e?e:void 0)}return t?r:r.default}}t.NodeHtmlMarkdown=l},5101:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isElementNode=t.isCommentNode=t.isTextNode=t.CommentNode=t.NodeType=void 0;const r=n(42);Object.defineProperty(t,"CommentNode",{enumerable:!0,get:function(){return r.CommentNode}}),Object.defineProperty(t,"NodeType",{enumerable:!0,get:function(){return r.NodeType}}),t.isTextNode=e=>e.nodeType===r.NodeType.TEXT_NODE,t.isCommentNode=e=>e.nodeType===r.NodeType.COMMENT_NODE,t.isElementNode=e=>e.nodeType===r.NodeType.ELEMENT_NODE},6183:(e,t)=>{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.createTranslatorContext=t.isTranslatorConfig=t.TranslatorCollection=t.PostProcessResult=void 0,(n=t.PostProcessResult||(t.PostProcessResult={}))[n.NoChange=0]="NoChange",n[n.RemoveNode=1]="RemoveNode",t.TranslatorCollection=class{get size(){return Object.keys(this).length}set(e,n,r){e.split(",").forEach((e=>{e=e.toUpperCase();let o=n;if(r){const r=this[e];(0,t.isTranslatorConfig)(r)&&(o=(0,t.isTranslatorConfig)(n)?Object.assign(Object.assign({},r),n):Object.assign(((...e)=>n.apply(void 0,e)),{base:r}))}this[e]=o}))}get(e){return this[e.toUpperCase()]}entries(){return Object.entries(this)}remove(e){e.split(",").forEach((e=>delete this[e.toUpperCase()]))}},t.isTranslatorConfig=e=>"object"==typeof e,t.createTranslatorContext=function(e,t,n,r){const{instance:o,nodeMetadata:i}=e;return Object.assign({node:t,options:o.options,parent:t.parentNode,nodeMetadata:i,visitor:e,base:r},n)}},3525:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.perfStop=t.perfStart=t.getChildNodes=t.parseHTML=t.truthyStr=t.getTrailingWhitespaceInfo=t.tagSurround=t.splitSpecial=t.isWhiteSpaceOnly=t.surround=t.trimNewLines=void 0;const r=n(2852);function o(e){const t=[],n=e.length;for(let r=0,o=0;r1&&++r)}return t}t.trimNewLines=e=>e.replace(/^\n+|\n+$/g,""),t.surround=(e,t)=>`${t}${e}${t}`,t.isWhiteSpaceOnly=e=>!/\S/.test(e),t.splitSpecial=o,t.tagSurround=function(e,t){e.indexOf(t)>=0&&(e=e.replace(new RegExp(`([^\\\\])\\${t.split("").join("\\")}`,"gm"),"$1"));const n=o(e);let r="";for(const{text:e,newLineChar:o}of n){let n,i,a=0;for(;a>=0&&a0?e[n-1]:"",s=i{const t={whitespace:0,newLines:0},n=Math.max(e.length-10,0);for(let r=e.length-1;r>=n;--r){const n=e.slice(r,r+1);if(!/\s/.test(n))break;++t.whitespace,["\r","\n"].includes(n)&&++t.newLines}return t},t.truthyStr=(e,t)=>e?void 0!==t?t:String(e):"";const i=()=>{try{return n(42).parse}catch(e){return}};function a(e){window.omnivoreEnv.LOG_PERF&&console.time(e)}function l(e){window.omnivoreEnv.LOG_PERF&&console.timeEnd(e)}t.parseHTML=function(e,t){let n,o;if(a("parse"),t.preferNativeParser)try{o=function(e){try{if(!(null===window||void 0===window?void 0:window.DOMParser)||!(new window.DOMParser).parseFromString("","text/html"))return}catch(e){return}let t;try{t=document.implementation.createHTMLDocument("").open()}catch(e){const{ActiveXObject:t}=window;if(t){const e=t("htmlfile");return e.designMode="on",e.open()}throw e}return t.write(""+e+""),t.close(),t.documentElement}(e)}catch(e){if(n=i(),!n)throw e;console.warn("Native DOM parser encountered an error during parse",e)}else n=i();return o||(o=n(e,r.nodeHtmlParserConfig)),l("parse"),o},t.getChildNodes=function(e){if(null==(t=e.childNodes)&&"function"!=typeof t[Symbol.iterator])return e.childNodes;var t;const n=[];return e.childNodes.forEach((e=>n.push(e))),n},t.perfStart=a,t.perfStop=l},819:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getMarkdownForHtmlNodes=t.Visitor=void 0;const r=n(5101),o=n(3525),i=n(6183),a=n(2852);class l{constructor(e,t,n){this.instance=e,this.rootNode=t,this.fileName=n,this.nodeMetadata=new Map,this.urlDefinitions=[],this.result={text:"",trailingNewlineStats:{whitespace:0,newLines:0}},this.options=e.options,this.optimizeTree(t),this.visitNode(t)}addOrGetUrlDefinition(e){let t=this.urlDefinitions.findIndex((t=>t===e));return t<0&&(t=this.urlDefinitions.push(e)-1),t+1}appendResult(e,t,n){if(!e&&void 0===t)return;const{result:r}=this;void 0!==t&&(r.text=r.text.substr(0,t)),r.text+=(n&&r.text.slice(-1)===e[0]?" ":"")+e,r.trailingNewlineStats=(0,o.getTrailingWhitespaceInfo)(r.text)}appendNewlines(e){const{newLines:t}=this.result.trailingNewlineStats;this.appendResult("\n".repeat(Math.max(0,+e-t)))}optimizeTree(e){(0,o.perfStart)("Optimize tree");const{translators:t}=this.instance;!function e(n){let i=!1;if((0,r.isTextNode)(n)||(0,r.isElementNode)(n)&&a.contentlessElements.includes(n.tagName))i=!0;else{const r=(0,o.getChildNodes)(n);if(r.length)for(const t of r)i?e(t):i=e(t);else{const e=t[n.tagName];((null==e?void 0:e.preserveIfEmpty)||"function"==typeof e)&&(i=!0)}}return n.preserve=i}(e),(0,o.perfStop)("Optimize tree")}processText(e,t){let n=e;if((null==t?void 0:t.preserveWhitespace)||(n=n.replace(/\s+/g," ")),null==t?void 0:t.noEscape)return n;const{lineStartEscape:r,globalEscape:o,textReplace:i}=this.options;if(n=n.replace(o[0],o[1]).replace(r[0],r[1]),i)for(const[e,t]of i)n=n.replace(e,t);return n}visitNode(e,t,n){var a,l,s,u,c,d;const{result:f}=this;if(!e.preserve)return;if((0,r.isTextNode)(e))return e.wholeText&&(null!==(a=(c=e).text)&&void 0!==a||(c.text=e.wholeText),null!==(l=(d=e).trimmedText)&&void 0!==l||(d.trimmedText=(0,o.trimNewLines)(e.wholeText))),e.isWhitespace&&!(null==n?void 0:n.preserveWhitespace)?!f.text.length||f.trailingNewlineStats.whitespace>0?void 0:this.appendResult(" "):this.appendResult(this.processText((null==n?void 0:n.preserveWhitespace)?e.text:e.trimmedText,n));if(t||!(0,r.isElementNode)(e))return;const p=(null==n?void 0:n.translators)?n.translators[e.tagName]:this.instance.translators[e.tagName];switch(e.tagName){case"UL":case"OL":n=Object.assign(Object.assign({},n),{listItemNumber:0,listKind:e.tagName,indentLevel:(null!==(s=null==n?void 0:n.indentLevel)&&void 0!==s?s:-1)+1});break;case"LI":"OL"===(null==n?void 0:n.listKind)&&(n.listItemNumber=(null!==(u=n.listItemNumber)&&void 0!==u?u:0)+1);break;case"PRE":n=Object.assign(Object.assign({},n),{preserveWhitespace:!0});break;case"TABLE":n=Object.assign(Object.assign({},n),{tableMeta:{node:e}})}if(n&&this.nodeMetadata.set(e,n),!p){for(const r of(0,o.getChildNodes)(e))this.visitNode(r,t,n);return}let h,g;if((0,i.isTranslatorConfig)(p)?h=p:(g=(0,i.createTranslatorContext)(this,e,n,p.base),h=Object.assign(Object.assign({},p.base),p(g))),h.ignore)return;h.noEscape&&!(null==n?void 0:n.noEscape)&&(n=Object.assign(Object.assign({},n),{noEscape:h.noEscape}),this.nodeMetadata.set(e,n)),h.childTranslators&&h.childTranslators!==(null==n?void 0:n.translators)&&(n=Object.assign(Object.assign({},n),{translators:h.childTranslators}),this.nodeMetadata.set(e,n));const m=f.text.length;if(h.surroundingNewlines&&this.appendNewlines(+h.surroundingNewlines),h.prefix&&this.appendResult(h.prefix),"string"==typeof h.content)this.appendResult(h.content,void 0,h.spaceIfRepeatingChar);else{const t=f.text.length;for(const t of(0,o.getChildNodes)(e))this.visitNode(t,!1===h.recurse,n);if(h.postprocess){const r=h.postprocess(Object.assign(Object.assign({},g||(0,i.createTranslatorContext)(this,e,n)),{content:f.text.substr(t)}));if(r===i.PostProcessResult.RemoveNode)return"LI"===e.tagName&&(null==n?void 0:n.listItemNumber)&&--n.listItemNumber,this.appendResult("",m);"string"==typeof r&&this.appendResult(r,t,h.spaceIfRepeatingChar)}}h.postfix&&this.appendResult(h.postfix),h.surroundingNewlines&&this.appendNewlines(+h.surroundingNewlines)}}t.Visitor=l,t.getMarkdownForHtmlNodes=function(e,t,n){(0,o.perfStart)("walk");const r=new l(e,t,n);let i=r.result.text;(0,o.perfStop)("walk"),e.options.useLinkReferenceDefinitions&&(/[^\r\n]/.test(i.slice(-1))&&(i+="\n"),r.urlDefinitions.forEach(((e,t)=>{i+=`\n[${t+1}]: ${e}`})));const{maxConsecutiveNewlines:a}=e.options;return a&&(i=i.replace(new RegExp(String.raw`(?:\r?\n\s*)+((?:\r?\n\s*){${a}})`,"g"),"$1")),(0,o.trimNewLines)(i)}},1937:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return e[e.length-1]}},42:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.NodeType=t.TextNode=t.Node=t.valid=t.CommentNode=t.HTMLElement=t.parse=void 0;var o=r(n(4113));t.CommentNode=o.default;var i=r(n(9234));t.HTMLElement=i.default;var a=r(n(8869));t.Node=a.default;var l=r(n(6218));t.TextNode=l.default;var s=r(n(2751));t.NodeType=s.default;var u=r(n(5349)),c=r(n(6692));function d(e,t){return void 0===t&&(t={}),(0,u.default)(e,t)}t.valid=c.default,t.default=d,t.parse=d,d.parse=u.default,d.HTMLElement=i.default,d.CommentNode=o.default,d.valid=c.default,d.Node=a.default,d.TextNode=l.default,d.NodeType=s.default},5798:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var o=r(n(2751));function i(e){return e&&e.nodeType===o.default.ELEMENT_NODE}function a(e,t){return i(e)?e.getAttribute(t):void 0}function l(e){return e&&e.childNodes}function s(e){return e?e.parentNode:null}t.default={isTag:i,getAttributeValue:a,getName:function(e){return(e&&e.rawTagName||"").toLowerCase()},getChildren:l,getParent:s,getText:function(e){return e.text},removeSubsets:function(e){for(var t,n,r,o=e.length;--o>-1;){for(t=n=e[o],e[o]=null,r=!0;n;){if(e.indexOf(n)>-1){r=!1,e.splice(o,1);break}n=s(n)}r&&(e[o]=t)}return e},existsOne:function e(t,n){return n.some((function(n){return!!i(n)&&(t(n)||e(t,l(n)))}))},getSiblings:function(e){var t=s(e);return t?l(t):[]},hasAttrib:function(e,t){return void 0!==a(e,t)},findOne:function e(t,n){for(var r=null,o=0,i=null==n?void 0:n.length;o0&&(r=e(t,s))}}return r},findAll:function e(t,n){for(var r=[],o=0,a=n.length;o0&&t.push(e=[]),r.childNodes.forEach(n),e.length>0&&t.push(e=[])):r.childNodes.forEach(n);else if(r.nodeType===m.default.TEXT_NODE)if(r.isWhitespace)e.prependWhitespace=!0;else{var o=r.trimmedText;e.prependWhitespace&&(o=" ".concat(o),e.prependWhitespace=!1),e.push(o)}}(this),t.map((function(e){return e.join("").replace(/\s{2,}/g," ")})).join("\n").replace(/\s+$/,"")},enumerable:!1,configurable:!0}),t.prototype.toString=function(){var e=this.rawTagName;if(e){var t=this.rawAttrs?" ".concat(this.rawAttrs):"";return this.voidTag.formatNode(e,t,this.innerHTML)}return this.innerHTML},Object.defineProperty(t.prototype,"innerHTML",{get:function(){return this.childNodes.map((function(e){return e.toString()})).join("")},set:function(e){var t=T(e,this._parseOptions),n=t.childNodes.length?t.childNodes:[new g.default(e,this)];D(n,this),D(this.childNodes,null),this.childNodes=n},enumerable:!1,configurable:!0}),t.prototype.set_content=function(e,t){if(void 0===t&&(t={}),e instanceof h.default)e=[e];else if("string"==typeof e){var n=T(e,t=i(i({},this._parseOptions),t));e=n.childNodes.length?n.childNodes:[new g.default(n.innerHTML,this)]}return D(this.childNodes,null),D(e,this),this.childNodes=e,this},t.prototype.replaceWith=function(){for(var e=this,t=[],n=0;n-1&&(n.rawText=n.rawText.substr(0,r),this.childNodes.length=t+1)}}return this},Object.defineProperty(t.prototype,"structure",{get:function(){var e=[],t=0;function n(n){e.push(" ".repeat(t)+n)}return function e(r){var o=r.id?"#".concat(r.id):"",i=r.classList.length?".".concat(r.classList.value.join(".")):"";n("".concat(r.rawTagName).concat(o).concat(i)),t++,r.childNodes.forEach((function(t){t.nodeType===m.default.ELEMENT_NODE?e(t):t.nodeType===m.default.TEXT_NODE&&(t.isWhitespace||n("#text"))})),t--}(this),e.join("\n")},enumerable:!1,configurable:!0}),t.prototype.removeWhitespace=function(){var e=this,t=0;return this.childNodes.forEach((function(n){if(n.nodeType===m.default.TEXT_NODE){if(n.isWhitespace)return;n.rawText=n.trimmedRawText}else n.nodeType===m.default.ELEMENT_NODE&&n.removeWhitespace();e.childNodes[t++]=n})),this.childNodes.length=t,this},t.prototype.querySelectorAll=function(e){return(0,s.selectAll)(e,this,{xmlMode:!0,adapter:d.default})},t.prototype.querySelector=function(e){return(0,s.selectOne)(e,this,{xmlMode:!0,adapter:d.default})},t.prototype.getElementsByTagName=function(e){for(var t=e.toUpperCase(),n=[],r=[],o=this,i=0;void 0!==i;){var a=void 0;do{a=o.childNodes[i++]}while(i0&&(r.push(i),o=a,i=0)):(o=o.parentNode,i=r.pop())}return n},t.prototype.getElementById=function(e){for(var t=[],n=this,r=0;void 0!==r;){var o=void 0;do{o=n.childNodes[r++]}while(r0&&(t.push(r),n=o,r=0)}}else n=n.parentNode,r=t.pop()}return null},t.prototype.closest=function(e){var t=new Map,n=this,r=null;function o(e,n){for(var r=null,i=0,a=n.length;i0;)if(this===e[--t])return e[t-1]||null;return null}},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"previousElementSibling",{get:function(){if(this.parentNode){for(var e=this.parentNode.childNodes,n=e.length,r=!1;n>0;){var o=e[--n];if(r){if(o instanceof t)return o||null}else this===o&&(r=!0)}return null}},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"classNames",{get:function(){return this.classList.toString()},enumerable:!1,configurable:!0}),t.prototype.clone=function(){return T(this.toString(),this._parseOptions).firstChild},t}(h.default);t.default=A;var w=/|<(\/?)([a-zA-Z][-.:0-9_a-zA-Z]*)((?:\s+[^>]*?(?:(?:'[^']*')|(?:"[^"]*"))?)*)\s*(\/?)>/g,x=/(?:^|\s)(id|class)\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+)/gi,E={area:!0,AREA:!0,base:!0,BASE:!0,br:!0,BR:!0,col:!0,COL:!0,hr:!0,HR:!0,img:!0,IMG:!0,input:!0,INPUT:!0,link:!0,LINK:!0,meta:!0,META:!0,source:!0,SOURCE:!0,embed:!0,EMBED:!0,param:!0,PARAM:!0,track:!0,TRACK:!0,wbr:!0,WBR:!0},k={li:{li:!0,LI:!0},LI:{li:!0,LI:!0},p:{p:!0,div:!0,P:!0,DIV:!0},P:{p:!0,div:!0,P:!0,DIV:!0},b:{div:!0,DIV:!0},B:{div:!0,DIV:!0},td:{td:!0,th:!0,TD:!0,TH:!0},TD:{td:!0,th:!0,TD:!0,TH:!0},th:{td:!0,th:!0,TD:!0,TH:!0},TH:{td:!0,th:!0,TD:!0,TH:!0},h1:{h1:!0,H1:!0},H1:{h1:!0,H1:!0},h2:{h2:!0,H2:!0},H2:{h2:!0,H2:!0},h3:{h3:!0,H3:!0},H3:{h3:!0,H3:!0},h4:{h4:!0,H4:!0},H4:{h4:!0,H4:!0},h5:{h5:!0,H5:!0},H5:{h5:!0,H5:!0},h6:{h6:!0,H6:!0},H6:{h6:!0,H6:!0}},S={li:{ul:!0,ol:!0,UL:!0,OL:!0},LI:{ul:!0,ol:!0,UL:!0,OL:!0},a:{div:!0,DIV:!0},A:{div:!0,DIV:!0},b:{div:!0,DIV:!0},B:{div:!0,DIV:!0},i:{div:!0,DIV:!0},I:{div:!0,DIV:!0},p:{div:!0,DIV:!0},P:{div:!0,DIV:!0},td:{tr:!0,table:!0,TR:!0,TABLE:!0},TD:{tr:!0,table:!0,TR:!0,TABLE:!0},th:{tr:!0,table:!0,TR:!0,TABLE:!0},TH:{tr:!0,table:!0,TR:!0,TABLE:!0}},_="documentfragmentcontainer";function C(e,t){var n,r;void 0===t&&(t={});var o=new f.default(null===(n=null==t?void 0:t.voidTag)||void 0===n?void 0:n.closingSlash,null===(r=null==t?void 0:t.voidTag)||void 0===r?void 0:r.tags),i=t.blockTextElements||{script:!0,noscript:!0,style:!0,pre:!0},a=Object.keys(i),l=a.map((function(e){return new RegExp("^".concat(e,"$"),"i")})),s=a.filter((function(e){return i[e]})).map((function(e){return new RegExp("^".concat(e,"$"),"i")}));function u(e){return s.some((function(t){return t.test(e)}))}function d(e){return l.some((function(t){return t.test(e)}))}var h,m=function(e,t){return[e-R,t-R]},v=new A(null,{},"",null,[0,e.length],o,t),b=v,y=[v],C=-1,T=void 0;e="<".concat(_,">").concat(e,"");for(var D=t.lowerCaseTagName,L=t.fixNestedATags,O=e.length-(_.length+2),R=_.length+2;h=w.exec(e);){var P=h[0],M=h[1],N=h[2],B=h[3],I=h[4],j=P.length,F=w.lastIndex-j,H=w.lastIndex;if(C>-1&&C+j"),K=D?e.toLocaleLowerCase().indexOf(Z,w.lastIndex):e.indexOf(Z,w.lastIndex),J=-1===K?O:K;u(N)&&(z=e.substring($,J)).length>0&&/\S/.test(z)&&b.appendChild(new g.default(z,b,m($,J))),-1===K?C=w.lastIndex=e.length+1:(C=w.lastIndex=K+Z.length,M="/")}}if(M||I||E[N])for(;;){if(null==T||"a"!==N&&"A"!==N||(T=void 0),b.rawTagName===N){b.range[1]=m(-1,Math.max(C,H))[1],y.pop(),b=(0,c.default)(y);break}if(G=b.tagName,!S[G]||!S[G][N])break;y.pop(),b=(0,c.default)(y)}}else if(t.comment){z=e.substring(F+4,H-3);b.appendChild(new p.default(z,b,m(F,H)))}}return y}function T(e,t){void 0===t&&(t={});for(var n=C(e,t),r=n[0],o=function(){var e=n.pop(),r=(0,c.default)(n);e.parentNode&&e.parentNode.parentNode&&(e.parentNode===r&&e.tagName===r.tagName?!0!==t.parseNoneClosedTags&&(r.removeChild(e),e.childNodes.forEach((function(e){r.parentNode.appendChild(e)})),n.pop()):!0!==t.parseNoneClosedTags&&(r.removeChild(e),e.childNodes.forEach((function(e){r.appendChild(e)}))))};n.length>1;)o();return r}function D(e,t){return e.map((function(e){return e.parentNode=t,e}))}t.base_parse=C,t.parse=T},8869:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(361),o=function(){function e(e,t){void 0===e&&(e=null),this.parentNode=e,this.childNodes=[],Object.defineProperty(this,"range",{enumerable:!1,writable:!0,configurable:!0,value:null!=t?t:[-1,-1]})}return e.prototype.remove=function(){var e=this;if(this.parentNode){var t=this.parentNode.childNodes;this.parentNode.childNodes=t.filter((function(t){return e!==t})),this.parentNode=null}return this},Object.defineProperty(e.prototype,"innerText",{get:function(){return this.rawText},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"textContent",{get:function(){return(0,r.decode)(this.rawText)},set:function(e){this.rawText=(0,r.encode)(e)},enumerable:!1,configurable:!0}),e}();t.default=o},6218:function(e,t,n){"use strict";var r,o=this&&this.__extends||(r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var a=n(361),l=i(n(8869)),s=i(n(2751)),u=function(e){function t(t,n,r){var o=e.call(this,n,r)||this;return o.nodeType=s.default.TEXT_NODE,o._rawText=t,o}return o(t,e),t.prototype.clone=function(){return new t(this._rawText,null)},Object.defineProperty(t.prototype,"rawText",{get:function(){return this._rawText},set:function(e){this._rawText=e,this._trimmedRawText=void 0,this._trimmedText=void 0},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"trimmedRawText",{get:function(){return void 0!==this._trimmedRawText||(this._trimmedRawText=c(this.rawText)),this._trimmedRawText},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"trimmedText",{get:function(){return void 0!==this._trimmedText||(this._trimmedText=c(this.text)),this._trimmedText},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"text",{get:function(){return(0,a.decode)(this.rawText)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"isWhitespace",{get:function(){return/^(\s| )*$/.test(this.rawText)},enumerable:!1,configurable:!0}),t.prototype.toString=function(){return this.rawText},t}(l.default);function c(e){for(var t,n,r=0;r>=0&&r0&&/[^\S\r\n]/.test(e[t-1]),i=n{"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.ELEMENT_NODE=1]="ELEMENT_NODE",e[e.TEXT_NODE=3]="TEXT_NODE",e[e.COMMENT_NODE=8]="COMMENT_NODE"}(n||(n={})),t.default=n},5349:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n(9234);Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r.parse}})},6692:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(9234);t.default=function(e,t){void 0===t&&(t={});var n=(0,r.base_parse)(e,t);return Boolean(1===n.length)}},6312:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){void 0===e&&(e=!1),this.addClosingSlash=e,Array.isArray(t)?this.voidTags=t.reduce((function(e,t){return e.add(t.toLowerCase())}),new Set):this.voidTags=["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"].reduce((function(e,t){return e.add(t)}),new Set)}return e.prototype.formatNode=function(e,t,n){var r=this.addClosingSlash,o=r&&t&&!t.endsWith(" ")?" ":"",i=r?"".concat(o,"/"):"";return this.isVoidElement(e.toLowerCase())?"<".concat(e).concat(t).concat(i,">"):"<".concat(e).concat(t,">").concat(n,"")},e.prototype.isVoidElement=function(e){return this.voidTags.has(e)},e}();t.default=n},4062:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.compile=void 0;var r=n(1365);t.compile=function(e){var t=e[0],n=e[1]-1;if(n<0&&t<=0)return r.falseFunc;if(-1===t)return function(e){return e<=n};if(0===t)return function(e){return e===n};if(1===t)return n<0?r.trueFunc:function(e){return e>=n};var o=Math.abs(t),i=(n%o+o)%o;return t>1?function(e){return e>=n&&e%o===i}:function(e){return e<=n&&e%o===i}}},7346:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.compile=t.parse=void 0;var r=n(3950);Object.defineProperty(t,"parse",{enumerable:!0,get:function(){return r.parse}});var o=n(4062);Object.defineProperty(t,"compile",{enumerable:!0,get:function(){return o.compile}}),t.default=function(e){return(0,o.compile)((0,r.parse)(e))}},3950:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parse=void 0;var n=new Set([9,10,12,13,32]),r="0".charCodeAt(0),o="9".charCodeAt(0);t.parse=function(e){if("even"===(e=e.trim().toLowerCase()))return[2,0];if("odd"===e)return[2,1];var t=0,i=0,a=s(),l=u();if(t=r&&e.charCodeAt(t)<=o;)i=10*i+(e.charCodeAt(t)-r),t++;return t===n?null:i}function c(){for(;t{"use strict";var r=n(3586);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,i,a){if(a!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:o};return n.PropTypes=n,n}},5440:(e,t,n)=>{e.exports=n(8262)()},3586:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},300:(e,t,n)=>{"use strict";n.r(t),n.d(t,{decode:()=>v,default:()=>w,encode:()=>b,toASCII:()=>A,toUnicode:()=>y,ucs2decode:()=>p,ucs2encode:()=>h});const r=2147483647,o=36,i=/^xn--/,a=/[^\0-\x7E]/,l=/[\x2E\u3002\uFF0E\uFF61]/g,s={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},u=Math.floor,c=String.fromCharCode;function d(e){throw new RangeError(s[e])}function f(e,t){const n=e.split("@");let r="";n.length>1&&(r=n[0]+"@",e=n[1]);const o=function(e,t){const n=[];let r=e.length;for(;r--;)n[r]=t(e[r]);return n}((e=e.replace(l,".")).split("."),t).join(".");return r+o}function p(e){const t=[];let n=0;const r=e.length;for(;n=55296&&o<=56319&&nString.fromCodePoint(...e),g=function(e,t){return e+22+75*(e<26)-((0!=t)<<5)},m=function(e,t,n){let r=0;for(e=n?u(e/700):e>>1,e+=u(e/t);e>455;r+=o)e=u(e/35);return u(r+36*e/(e+38))},v=function(e){const t=[],n=e.length;let i=0,a=128,l=72,s=e.lastIndexOf("-");s<0&&(s=0);for(let n=0;n=128&&d("not-basic"),t.push(e.charCodeAt(n));for(let f=s>0?s+1:0;f=n&&d("invalid-input");const s=(c=e.charCodeAt(f++))-48<10?c-22:c-65<26?c-65:c-97<26?c-97:o;(s>=o||s>u((r-i)/t))&&d("overflow"),i+=s*t;const p=a<=l?1:a>=l+26?26:a-l;if(su(r/h)&&d("overflow"),t*=h}const p=t.length+1;l=m(i-s,p,0==s),u(i/p)>r-a&&d("overflow"),a+=u(i/p),i%=p,t.splice(i++,0,a)}var c;return String.fromCodePoint(...t)},b=function(e){const t=[];let n=(e=p(e)).length,i=128,a=0,l=72;for(const n of e)n<128&&t.push(c(n));let s=t.length,f=s;for(s&&t.push("-");f=i&&tu((r-a)/p)&&d("overflow"),a+=(n-i)*p,i=n;for(const n of e)if(nr&&d("overflow"),n==i){let e=a;for(let n=o;;n+=o){const r=n<=l?1:n>=l+26?26:n-l;if(e{"use strict";var r=n(2784),o=n(4616);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n