diff options
-rw-r--r-- | config.json.example | 2 | ||||
-rw-r--r-- | public/_locales/en/messages.json | 25 | ||||
-rw-r--r-- | src/components/CategoryChooserComponent.tsx | 3 | ||||
-rw-r--r-- | src/components/CategorySkipOptionsComponent.tsx | 12 | ||||
-rw-r--r-- | src/components/SkipNoticeComponent.tsx | 12 | ||||
-rw-r--r-- | src/components/SponsorTimeEditComponent.tsx | 91 | ||||
-rw-r--r-- | src/config.ts | 128 | ||||
-rw-r--r-- | src/content.ts | 19 | ||||
-rw-r--r-- | src/js-components/previewBar.ts | 2 | ||||
-rw-r--r-- | src/types.ts | 18 | ||||
-rw-r--r-- | src/utils.ts | 11 |
11 files changed, 148 insertions, 175 deletions
diff --git a/config.json.example b/config.json.example index 0d5db7f5..fae36834 100644 --- a/config.json.example +++ b/config.json.example @@ -2,5 +2,5 @@ "serverAddress": "https://sponsor.ajay.app", "testingServerAddress": "https://sponsor.ajay.app/test", "serverAddressComment": "This specifies the default SponsorBlock server to connect to", - "categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic"] + "categoryList": ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "highlight"] } diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index bf491142..0396ec2d 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -335,9 +335,6 @@ "createdBy": { "message": "Created By" }, - "autoSkip": { - "message": "Auto Skip" - }, "showSkipNotice": { "message": "Show Notice After A Segment Is Skipped" }, @@ -534,14 +531,20 @@ "category_music_offtopic_short": { "message": "Non-Music" }, + "category_highlight": { + "message": "Highlight" + }, + "category_highlight_description": { + "message": "The part of the video that most people are looking for. Similar to \"Video starts at x\" comments." + }, "category_livestream_messages": { "message": "Livestream: Donation/Message Readings" }, "category_livestream_messages_short": { "message": "Message Reading" }, - "disable": { - "message": "Disable" + "autoSkip": { + "message": "Auto Skip" }, "manualSkip": { "message": "Manual Skip" @@ -549,6 +552,18 @@ "showOverlay": { "message": "Show In Seek Bar" }, + "disable": { + "message": "Disable" + }, + "autoSkip_POI": { + "message": "Auto skip to the start" + }, + "manualSkip_POI": { + "message": "Ask when video loads" + }, + "showOverlay_POI": { + "message": "Show In Seek Bar" + }, "colorFormatIncorrect": { "message": "Your color is formatted incorrectly. It should be a 3 or 6 digit hex code with a number sign at the beginning." }, diff --git a/src/components/CategoryChooserComponent.tsx b/src/components/CategoryChooserComponent.tsx index bb86e10f..e5b08923 100644 --- a/src/components/CategoryChooserComponent.tsx +++ b/src/components/CategoryChooserComponent.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; +import { Category } from "../types"; import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent"; export interface CategoryChooserProps { @@ -58,7 +59,7 @@ class CategoryChooserComponent extends React.Component<CategoryChooserProps, Cat for (const category of CompileConfig.categoryList) { elements.push( - <CategorySkipOptionsComponent category={category} + <CategorySkipOptionsComponent category={category as Category} key={category}> </CategorySkipOptionsComponent> ); diff --git a/src/components/CategorySkipOptionsComponent.tsx b/src/components/CategorySkipOptionsComponent.tsx index 3cc03527..a51d7a26 100644 --- a/src/components/CategorySkipOptionsComponent.tsx +++ b/src/components/CategorySkipOptionsComponent.tsx @@ -1,10 +1,13 @@ import * as React from "react"; import Config from "../config" -import { CategorySkipOption } from "../types"; +import { Category, CategorySkipOption } from "../types"; + +import Utils from "../utils"; +const utils = new Utils(); export interface CategorySkipOptionsProps { - category: string; + category: Category; defaultColor?: string; defaultPreviewColor?: string; } @@ -146,10 +149,13 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr const optionNames = ["disable", "showOverlay", "manualSkip", "autoSkip"]; + console.log(utils.getCategoryActionType(this.props.category)) + for (const optionName of optionNames) { elements.push( <option key={optionName} value={optionName}> - {chrome.i18n.getMessage(optionName)} + {chrome.i18n.getMessage(optionName !== "disable" ? optionName + utils.getCategoryActionType(this.props.category) + : optionName)} </option> ); } diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 2990eee3..f042bcd5 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,10 +1,13 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config" -import { ContentContainer, SponsorHideType, SponsorTime } from "../types"; +import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime } from "../types"; import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; +import Utils from "../utils"; +const utils = new Utils(); + export enum SkipNoticeAction { None, Upvote, @@ -342,7 +345,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta this.contentContainer().vote(0, this.segments[index].UUID, undefined, this); break; case SkipNoticeAction.CategoryVote: - this.contentContainer().vote(undefined, this.segments[index].UUID, this.categoryOptionRef.current.value, this) + this.contentContainer().vote(undefined, this.segments[index].UUID, this.categoryOptionRef.current.value as Category, this) break; case SkipNoticeAction.Unskip: this.state.unskipCallback(index); @@ -389,7 +392,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta getCategoryOptions(): React.ReactElement[] { const elements = []; - for (const category of Config.config.categorySelections) { + const categories = Config.config.categorySelections.filter((cat => utils.getCategoryActionType(cat.name as Category) === CategoryActionType.Skippable)); + for (const category of categories) { elements.push( <option value={category.name} key={category.name}> @@ -476,7 +480,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta }); } - afterVote(segment: SponsorTime, type: number, category: string): void { + afterVote(segment: SponsorTime, type: number, category: Category): void { this.addVoteButtonInfo(chrome.i18n.getMessage("voted")); if (type === 0) { diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index e00813ca..5d111617 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -4,7 +4,7 @@ import Config from "../config"; import * as CompileConfig from "../../config.json"; import Utils from "../utils"; -import { ContentContainer, SponsorTime } from "../types"; +import { Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; const utils = new Utils(); @@ -16,6 +16,7 @@ export interface SponsorTimeEditProps { contentContainer: ContentContainer, submissionNotice: SubmissionNoticeComponent; + categoryList?: Category[]; } export interface SponsorTimeEditState { @@ -106,43 +107,47 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo onChange={(e) => { const sponsorTimeEdits = this.state.sponsorTimeEdits; sponsorTimeEdits[0] = e.target.value; + if (utils.getCategoryActionType(sponsorTime.category) === CategoryActionType.POI) sponsorTimeEdits[1] = e.target.value; this.setState({sponsorTimeEdits}); - - this.saveEditTimes(); - }}> - </input> - - <span> - {" " + chrome.i18n.getMessage("to") + " "} - </span> - - <input id={"submittingTime1" + this.idSuffix} - className="sponsorTimeEdit sponsorTimeEditInput" - ref={oldYouTubeDarkStyles} - type="text" - value={this.state.sponsorTimeEdits[1]} - onChange={(e) => { - const sponsorTimeEdits = this.state.sponsorTimeEdits; - sponsorTimeEdits[1] = e.target.value; - - this.setState({sponsorTimeEdits}); - this.saveEditTimes(); }}> </input> - <span id={"nowButton1" + this.idSuffix} - className="sponsorNowButton" - onClick={() => this.setTimeToNow(1)}> - {chrome.i18n.getMessage("bracketNow")} - </span> - - <span id={"endButton" + this.idSuffix} - className="sponsorNowButton" - onClick={() => this.setTimeToEnd()}> - {chrome.i18n.getMessage("bracketEnd")} - </span> + {utils.getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable ? ( + <span> + <span> + {" " + chrome.i18n.getMessage("to") + " "} + </span> + + <input id={"submittingTime1" + this.idSuffix} + className="sponsorTimeEdit sponsorTimeEditInput" + ref={oldYouTubeDarkStyles} + type="text" + value={this.state.sponsorTimeEdits[1]} + onChange={(e) => { + const sponsorTimeEdits = this.state.sponsorTimeEdits; + sponsorTimeEdits[1] = e.target.value; + + this.setState({sponsorTimeEdits}); + + this.saveEditTimes(); + }}> + </input> + + <span id={"nowButton1" + this.idSuffix} + className="sponsorNowButton" + onClick={() => this.setTimeToNow(1)}> + {chrome.i18n.getMessage("bracketNow")} + </span> + + <span id={"endButton" + this.idSuffix} + className="sponsorNowButton" + onClick={() => this.setTimeToEnd()}> + {chrome.i18n.getMessage("bracketEnd")} + </span> + </span> + ): ""} </div> ); } else { @@ -151,7 +156,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo className="sponsorTimeDisplay" onClick={this.toggleEditTime.bind(this)}> {utils.getFormattedTime(segment[0], true) + - ((!isNaN(segment[1])) ? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")} + ((!isNaN(segment[1]) && utils.getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable) + ? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")} </div> ); } @@ -190,7 +196,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo {chrome.i18n.getMessage("delete")} </span> - {(!isNaN(segment[1])) ? ( + {(!isNaN(segment[1]) && utils.getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable) ? ( <span id={"sponsorTimePreviewButton" + this.idSuffix} className="sponsorTimeEditButton" onClick={this.previewTime.bind(this)}> @@ -225,7 +231,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo </option> )]; - for (const category of CompileConfig.categoryList) { + for (const category of (this.props.categoryList ?? CompileConfig.categoryList)) { elements.push( <option value={category} key={category}> @@ -252,6 +258,10 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo return; } + + if (utils.getCategoryActionType(event.target.value as Category) === CategoryActionType.POI) { + this.setTimeTo(1, null); + } this.saveEditTimes(); } @@ -264,11 +274,16 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo this.setTimeTo(1, this.props.contentContainer().v.duration); } + /** + * @param index + * @param time If null, will set time to the first index's time + */ setTimeTo(index: number, time: number): void { const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index]; + if (time === null) time = sponsorTime.segment[0]; - sponsorTime.segment[index] = - time; + sponsorTime.segment[index] = time; + if (utils.getCategoryActionType(sponsorTime.category) === CategoryActionType.POI) sponsorTime.segment[1] = time; this.setState({ sponsorTimeEdits: this.getFormattedSponsorTimesEdits(sponsorTime) @@ -312,7 +327,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo } } - sponsorTimesSubmitting[this.props.index].category = this.categoryOptionRef.current.value; + sponsorTimesSubmitting[this.props.index].category = this.categoryOptionRef.current.value as Category; Config.config.segmentTimes.set(this.props.contentContainer().sponsorVideoID, sponsorTimesSubmitting); diff --git a/src/config.ts b/src/config.ts index 962564fd..c7e2ab70 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,13 +1,10 @@ import * as CompileConfig from "../config.json"; -import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes } from "./types"; - -import Utils from "./utils"; -const utils = new Utils(); +import { Category, CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes } from "./types"; interface SBConfig { userID: string, segmentTimes: SBMap<string, SponsorTime[]>, - defaultCategory: string, + defaultCategory: Category, whitelistedChannels: string[], forceChannelCheck: boolean, skipKeybind: string, @@ -57,6 +54,8 @@ interface SBConfig { "preview-preview": PreviewBarOption, "music_offtopic": PreviewBarOption, "preview-music_offtopic": PreviewBarOption, + "highlight": PreviewBarOption, + "preview-highlight": PreviewBarOption, } } @@ -142,7 +141,7 @@ const Config: SBObject = { defaults: { userID: null, segmentTimes: new SBMap("segmentTimes"), - defaultCategory: "chooseACategory", + defaultCategory: "chooseACategory" as Category, whitelistedChannels: [], forceChannelCheck: false, skipKeybind: "Enter", @@ -173,7 +172,7 @@ const Config: SBObject = { refetchWhenNotFound: true, categorySelections: [{ - name: "sponsor", + name: "sponsor" as Category, option: CategorySkipOption.AutoSkip }], @@ -238,6 +237,14 @@ const Config: SBObject = { "preview-music_offtopic": { color: "#a6634a", opacity: "0.7" + }, + "highlight": { + color: "#ff1684", + opacity: "0.7" + }, + "preview-highlight": { + color: "#9b044c", + opacity: "0.7" } } }, @@ -334,108 +341,15 @@ function fetchConfig(): Promise<void> { } function migrateOldFormats(config: SBConfig) { - if (config["disableAutoSkip"]) { - for (const selection of config.categorySelections) { - if (selection.name === "sponsor") { - selection.option = CategorySkipOption.ManualSkip; - - chrome.storage.sync.remove("disableAutoSkip"); - } - } - } - - // Auto vote removal - if (config["autoUpvote"]) { - chrome.storage.sync.remove("autoUpvote"); - } - // mobileUpdateShowCount removal - if (config["mobileUpdateShowCount"] !== undefined) { - chrome.storage.sync.remove("mobileUpdateShowCount"); - } - // categoryUpdateShowCount removal - if (config["categoryUpdateShowCount"] !== undefined) { - chrome.storage.sync.remove("categoryUpdateShowCount"); - } - - // Channel URLS - if (config.whitelistedChannels.length > 0 && - (config.whitelistedChannels[0] == null || config.whitelistedChannels[0].includes("/"))) { - const channelURLFixer = async() => { - const newChannelList: string[] = []; - for (const item of config.whitelistedChannels) { - if (item != null) { - if (item.includes("/channel/")) { - newChannelList.push(item.split("/")[2]); - } else if (item.includes("/user/") && utils.isContentScript()) { - - - // Replace channel URL with channelID - const response = await utils.asyncRequestToCustomServer("GET", "https://sponsor.ajay.app/invidious/api/v1/channels/" + item.split("/")[2] + "?fields=authorId"); - - if (response.ok) { - newChannelList.push((JSON.parse(response.responseText)).authorId); - } else { - // Add it at the beginning so it gets converted later - newChannelList.unshift(item); - } - } else if (item.includes("/user/")) { - // Add it at the beginning so it gets converted later (The API can only be called in the content script due to CORS issues) - newChannelList.unshift(item); - } else { - newChannelList.push(item); - } - } - } + if (!config["highlightCategoryAdded"] && !config.categorySelections.some((s) => s.name === "highlight")) { + config["highlightCategoryAdded"] = true; - config.whitelistedChannels = newChannelList; - } - - channelURLFixer(); - } - - // Check if off-topic category needs to be removed - for (let i = 0; i < config.categorySelections.length; i++) { - if (config.categorySelections[i].name === "offtopic") { - config.categorySelections.splice(i, 1); - // Call set listener - config.categorySelections = config.categorySelections; - break; - } - } - - // Migrate old "sponsorTimes" - if (config["sponsorTimes"]) { - let jsonData: unknown = config["sponsorTimes"]; - - // Check if data is stored in the old format for SBMap (a JSON string) - if (typeof jsonData === "string") { - try { - jsonData = JSON.parse(jsonData); - } catch(e) { - // Continue normally (out of this if statement) - } - } - - // Otherwise junk data - if (Array.isArray(jsonData)) { - const oldMap = new Map(jsonData); - oldMap.forEach((sponsorTimes: number[][], key) => { - const segmentTimes: SponsorTime[] = []; - for (const segment of sponsorTimes) { - segmentTimes.push({ - segment: segment, - category: "sponsor", - UUID: null - }); - } - - config.segmentTimes.rawSet(key, segmentTimes); - }); - - config.segmentTimes.update(); - } + config.categorySelections.push({ + name: "highlight" as Category, + option: CategorySkipOption.AutoSkip + }); - chrome.storage.sync.remove("sponsorTimes"); + config.categorySelections = config.categorySelections; } } diff --git a/src/content.ts b/src/content.ts index 9d92d259..2434c617 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1,6 +1,6 @@ import Config from "./config"; -import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types"; +import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject, CategoryActionType, SegmentUUID, Category } from "./types"; import { ContentContainer } from "./types"; import Utils from "./utils"; @@ -691,14 +691,16 @@ function startSkipScheduleCheckingForStartSponsors() { // See if there are any starting sponsors let startingSponsor = -1; for (const time of sponsorTimes) { - if (time.segment[0] <= video.currentTime && time.segment[0] > startingSponsor && time.segment[1] > video.currentTime) { + if (time.segment[0] <= video.currentTime && time.segment[0] > startingSponsor && time.segment[1] > video.currentTime + && utils.getCategoryActionType(time.category) === CategoryActionType.Skippable) { startingSponsor = time.segment[0]; break; } } if (startingSponsor === -1) { for (const time of sponsorTimesSubmitting) { - if (time.segment[0] <= video.currentTime && time.segment[0] > startingSponsor && time.segment[1] > video.currentTime) { + if (time.segment[0] <= video.currentTime && time.segment[0] > startingSponsor && time.segment[1] > video.currentTime + && utils.getCategoryActionType(time.category) === CategoryActionType.Skippable) { startingSponsor = time.segment[0]; break; } @@ -959,7 +961,8 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: || ((includeNonIntersectingSegments && sponsorTimes[i].segment[0] >= minimum) || (includeIntersectingSegments && sponsorTimes[i].segment[0] < minimum && sponsorTimes[i].segment[1] > minimum))) && (!onlySkippableSponsors || utils.getCategorySelection(sponsorTimes[i].category).option !== CategorySkipOption.ShowOverlay) - && (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible)) { + && (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible) + && utils.getCategoryActionType(sponsorTimes[i].category) === CategoryActionType.Skippable) { startTimes.push(sponsorTimes[i].segment[0]); } @@ -1031,10 +1034,8 @@ function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: S } function unskipSponsorTime(segment: SponsorTime) { - if (sponsorTimes != null) { - //add a tiny bit of time to make sure it is not skipped again - video.currentTime = segment.segment[0] + 0.001; - } + //add a tiny bit of time to make sure it is not skipped again + video.currentTime = segment.segment[0] + 0.001; } function reskipSponsorTime(segment: SponsorTime) { @@ -1353,7 +1354,7 @@ function clearSponsorTimes() { } //if skipNotice is null, it will not affect the UI -function vote(type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) { +function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) { if (skipNotice !== null && skipNotice !== undefined) { //add loading info skipNotice.addVoteButtonInfo.bind(skipNotice)(chrome.i18n.getMessage("Loading")) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 5cb8b95f..49059f76 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -198,7 +198,7 @@ class PreviewBar { if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[barSegmentType].opacity; bar.style.position = "absolute"; - bar.style.width = this.timeToPercentage(segment[1] - segment[0]); + if (segment[1] - segment[0] > 0) bar.style.width = this.timeToPercentage(segment[1] - segment[0]); bar.style.left = this.timeToPercentage(segment[0]); return bar; diff --git a/src/types.ts b/src/types.ts index dd870f37..024e1125 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,7 @@ import SkipNotice from "./render/SkipNotice"; export interface ContentContainer { (): { - vote: (type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) => void, + vote: (type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) => void, dontShowNoticeAgain: () => void, unskipSponsorTime: (segment: SponsorTime) => void, sponsorTimes: SponsorTime[], @@ -41,7 +41,7 @@ export enum CategorySkipOption { } export interface CategorySelection { - name: string; + name: Category; option: CategorySkipOption } @@ -51,11 +51,19 @@ export enum SponsorHideType { MinimumDuration } +export enum CategoryActionType { + Skippable = "", // Strings are used to find proper language configs + POI = "_POI" +} + +export type SegmentUUID = string & { __segmentUUIDBrand: unknown }; +export type Category = string & { __categoryBrand: unknown }; + export interface SponsorTime { segment: number[]; - UUID: string; + UUID: SegmentUUID; - category: string; + category: Category; hidden?: SponsorHideType; } @@ -145,7 +153,7 @@ export interface VideoInfo { isUnlisted: boolean, hasYpcMetadata: boolean, viewCount: string, - category: string, + category: Category, publishDate: string, ownerChannelName: string, uploadDate: string, diff --git a/src/utils.ts b/src/utils.ts index bb42afc5..a7335189 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import Config from "./config"; -import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types"; +import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, Category, CategoryActionType } from "./types"; import * as CompileConfig from "../config.json"; @@ -43,6 +43,15 @@ export default class Utils { }); } + getCategoryActionType(category: Category): CategoryActionType { + switch (category) { + case "highlight": + return CategoryActionType.POI; + default: + return CategoryActionType.Skippable; + } + } + containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> { return new Promise((resolve) => { chrome.permissions.contains(permissions, resolve) |