diff options
22 files changed, 851 insertions, 454 deletions
@@ -36,5 +36,13 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] - } + }, + "overrides": [ + { + "files": ["**/*.tsx"], + "rules": { + "react/prop-types": "off" + } + } + ] } diff --git a/e2e/completion.test.ts b/e2e/completion.test.ts index bc065d3..c0e7052 100644 --- a/e2e/completion.test.ts +++ b/e2e/completion.test.ts @@ -28,33 +28,24 @@ describe("general completion test", () => { page = await Page.navigateTo(webdriver, "about:blank"); }); - it("should all commands on empty line", async () => { + it("should shows all commands on empty line", async () => { const console = await page.showConsole(); - const items = await console.getCompletions(); - assert.strictEqual(items.length, 12); - assert.deepStrictEqual(items[0], { - type: "title", - text: "Console Command", - }); - assert.ok(items[1].text.startsWith("set")); - assert.ok(items[2].text.startsWith("open")); - assert.ok(items[3].text.startsWith("tabopen")); + const groups = await console.getCompletions(); + assert.strictEqual(groups.length, 1); + assert.strictEqual(groups[0].title, "Console Command"); + assert.strictEqual(groups[0].items.length, 11); }); - it("should only commands filtered by prefix", async () => { + it("should shows commands filtered by prefix", async () => { const console = await page.showConsole(); await console.inputKeys("b"); - const items = await console.getCompletions(); - assert.strictEqual(items.length, 4); - assert.deepStrictEqual(items[0], { - type: "title", - text: "Console Command", - }); - assert.ok(items[1].text.startsWith("buffer")); - assert.ok(items[2].text.startsWith("bdelete")); - assert.ok(items[3].text.startsWith("bdeletes")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[0].text.startsWith("buffer")); + assert.ok(items[1].text.startsWith("bdelete")); + assert.ok(items[2].text.startsWith("bdeletes")); }); // > byffer @@ -65,21 +56,24 @@ describe("general completion test", () => { const console = await page.showConsole(); await console.inputKeys("b"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 4); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 3); }); await console.sendKeys(Key.TAB); await eventually(async () => { - const items = await console.getCompletions(); - assert.ok(items[1].highlight); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[0].highlight); assert.strictEqual(await console.currentValue(), "buffer"); }); await console.sendKeys(Key.TAB, Key.TAB); await eventually(async () => { - const items = await console.getCompletions(); - assert.ok(items[3].highlight); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[2].highlight); assert.strictEqual(await console.currentValue(), "bdeletes"); }); @@ -90,8 +84,9 @@ describe("general completion test", () => { await console.sendKeys(Key.SHIFT, Key.TAB); await eventually(async () => { - const items = await console.getCompletions(); - assert.ok(items[3].highlight); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[2].highlight); assert.strictEqual(await console.currentValue(), "bdeletes"); }); }); diff --git a/e2e/completion_buffers.test.ts b/e2e/completion_buffers.test.ts index 57603f6..13d07ea 100644 --- a/e2e/completion_buffers.test.ts +++ b/e2e/completion_buffers.test.ts @@ -72,17 +72,19 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("buffer "); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 6); - assert.deepStrictEqual(items[0], { type: "title", text: "Buffers" }); - assert.ok(items[1].text.startsWith("1:")); - assert.ok(items[2].text.startsWith("2:")); - assert.ok(items[3].text.startsWith("3:")); - assert.ok(items[4].text.startsWith("4:")); - assert.ok(items[5].text.startsWith("5:")); - - assert.ok(items[3].text.includes("%")); - assert.ok(items[5].text.includes("#")); + const groups = await console.getCompletions(); + assert.strictEqual(groups.length, 1); + assert.strictEqual(groups[0].title, "Buffers"); + + const items = groups[0].items; + assert.ok(items[0].text.startsWith("1:")); + assert.ok(items[1].text.startsWith("2:")); + assert.ok(items[2].text.startsWith("3:")); + assert.ok(items[3].text.startsWith("4:")); + assert.ok(items[4].text.startsWith("5:")); + + assert.ok(items[2].text.includes("%")); + assert.ok(items[4].text.includes("#")); }); }); @@ -91,11 +93,10 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("buffer title_site2"); await eventually(async () => { - const items = await console.getCompletions(); - assert.deepStrictEqual(items[0], { type: "title", text: "Buffers" }); - assert.ok(items[1].text.startsWith("2:")); - assert.ok(items[1].text.includes("title_site2")); - assert.ok(items[1].text.includes(server.url("/site2"))); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[0].text.startsWith("2:")); + assert.ok(items[0].text.includes("title_site2")); }); }); @@ -104,9 +105,9 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("buffer /site2"); await eventually(async () => { - const items = await console.getCompletions(); - assert.deepStrictEqual(items[0], { type: "title", text: "Buffers" }); - assert.ok(items[1].text.startsWith("2:")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.ok(items[0].text.startsWith("2:")); }); }); @@ -115,10 +116,11 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("buffer 2"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 2); - assert.deepStrictEqual(items[0], { type: "title", text: "Buffers" }); - assert.ok(items[1].text.startsWith("2:")); + const groups = await console.getCompletions(); + const items = groups[0].items; + + assert.strictEqual(items.length, 1); + assert.ok(items[0].text.startsWith("2:")); }); }); @@ -127,11 +129,12 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("bdelete site"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 4); - assert.ok(items[1].text.includes("site3")); - assert.ok(items[2].text.includes("site4")); - assert.ok(items[3].text.includes("site5")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 3); + assert.ok(items[0].text.includes("site3")); + assert.ok(items[1].text.includes("site4")); + assert.ok(items[2].text.includes("site5")); }); }); @@ -140,11 +143,12 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("bdeletes site"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 4); - assert.ok(items[1].text.includes("site3")); - assert.ok(items[2].text.includes("site4")); - assert.ok(items[3].text.includes("site5")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 3); + assert.ok(items[0].text.includes("site3")); + assert.ok(items[1].text.includes("site4")); + assert.ok(items[2].text.includes("site5")); }); }); @@ -153,13 +157,14 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("bdelete! site"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 6); - assert.ok(items[1].text.includes("site1")); - assert.ok(items[2].text.includes("site2")); - assert.ok(items[3].text.includes("site3")); - assert.ok(items[4].text.includes("site4")); - assert.ok(items[5].text.includes("site5")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 5); + assert.ok(items[0].text.includes("site1")); + assert.ok(items[1].text.includes("site2")); + assert.ok(items[2].text.includes("site3")); + assert.ok(items[3].text.includes("site4")); + assert.ok(items[4].text.includes("site5")); }); }); @@ -168,13 +173,14 @@ describe("completion on buffer/bdelete/bdeletes", () => { await console.inputKeys("bdeletes! site"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 6); - assert.ok(items[1].text.includes("site1")); - assert.ok(items[2].text.includes("site2")); - assert.ok(items[3].text.includes("site3")); - assert.ok(items[4].text.includes("site4")); - assert.ok(items[5].text.includes("site5")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 5); + assert.ok(items[0].text.includes("site1")); + assert.ok(items[1].text.includes("site2")); + assert.ok(items[2].text.includes("site3")); + assert.ok(items[3].text.includes("site4")); + assert.ok(items[4].text.includes("site5")); }); }); }); diff --git a/e2e/completion_open.test.ts b/e2e/completion_open.test.ts index cefb44f..7eef4c2 100644 --- a/e2e/completion_open.test.ts +++ b/e2e/completion_open.test.ts @@ -45,31 +45,13 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open "); await eventually(async () => { - const completions = await console.getCompletions(); - assert.ok( - completions.find( - (x) => x.type === "title" && x.text === "Search Engines" - ) - ); - assert.ok( - completions.find((x) => x.type === "title" && x.text === "Bookmarks") - ); - assert.ok( - completions.find((x) => x.type === "title" && x.text === "History") - ); - }); - }); - - it('should filter items with URLs by keywords on "open" command', async () => { - const console = await page.showConsole(); - await console.inputKeys("open https://"); - - await eventually(async () => { - const completions = await console.getCompletions(); - const items = completions - .filter((x) => x.type === "item") - .map((x) => x.text); - assert.ok(items.every((x) => x.includes("https://"))); + const groups = await console.getCompletions(); + const titles = groups.map((group) => group.title); + assert.deepStrictEqual(titles, [ + "Search Engines", + "Bookmarks", + "History", + ]); }); }); @@ -78,11 +60,9 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open getting"); await eventually(async () => { - const completions = await console.getCompletions(); - const items = completions - .filter((x) => x.type === "item") - .map((x) => x.text); - assert.ok(items.every((x) => x.toLowerCase().includes("getting"))); + const groups = await console.getCompletions(); + const items = groups.map((group) => group.items).flat(); + assert.ok(items.every((x) => x.text.toLowerCase().includes("getting"))); }); }); @@ -91,24 +71,20 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("tabopen getting"); await eventually(async () => { - const completions = await console.getCompletions(); - const items = completions - .filter((x) => x.type === "item") - .map((x) => x.text); - assert.ok(items.every((x) => x.includes("https://"))); + const groups = await console.getCompletions(); + const items = groups.map((group) => group.items).flat(); + assert.ok(items.every((x) => x.text.toLowerCase().includes("getting"))); }); }); it('should filter items with titles by keywords on "winopen" command', async () => { const console = await page.showConsole(); - await console.inputKeys("winopen https://"); + await console.inputKeys("winopen getting"); await eventually(async () => { - const completions = await console.getCompletions(); - const items = completions - .filter((x) => x.type === "item") - .map((x) => x.text); - assert.ok(items.every((x) => x.includes("https://"))); + const groups = await console.getCompletions(); + const items = groups.map((group) => group.items).flat(); + assert.ok(items.every((x) => x.text.toLowerCase().includes("getting"))); }); }); @@ -121,10 +97,8 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open "); await eventually(async () => { - const completions = await console.getCompletions(); - const titles = completions - .filter((x) => x.type === "title") - .map((x) => x.text); + const groups = await console.getCompletions(); + const titles = groups.map((group) => group.title); assert.deepStrictEqual(titles, [ "Search Engines", "Bookmarks", @@ -141,10 +115,8 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open "); await eventually(async () => { - const completions = await console.getCompletions(); - const titles = completions - .filter((x) => x.type === "title") - .map((x) => x.text); + const groups = await console.getCompletions(); + const titles = groups.map((group) => group.title); assert.deepStrictEqual(titles, [ "Bookmarks", "Search Engines", @@ -164,10 +136,8 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open "); await eventually(async () => { - const completions = await console.getCompletions(); - const titles = completions - .filter((x) => x.type === "title") - .map((x) => x.text); + const groups = await console.getCompletions(); + const titles = groups.map((group) => group.title); assert.deepStrictEqual(titles, [ "Search Engines", "Bookmarks", @@ -188,10 +158,8 @@ describe("completion on open/tabopen/winopen commands", () => { await console.inputKeys("open "); await eventually(async () => { - const completions = await console.getCompletions(); - const titles = completions - .filter((x) => x.type === "title") - .map((x) => x.text); + const groups = await console.getCompletions(); + const titles = groups.map((group) => group.title); assert.deepStrictEqual(titles, [ "Bookmarks", "Search Engines", diff --git a/e2e/completion_set.test.ts b/e2e/completion_set.test.ts index 0a45ed3..929f649 100644 --- a/e2e/completion_set.test.ts +++ b/e2e/completion_set.test.ts @@ -33,14 +33,14 @@ describe("completion on set commands", () => { await console.inputKeys("set "); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 6); - assert.deepStrictEqual(items[0], { type: "title", text: "Properties" }); - assert.ok(items[1].text.startsWith("hintchars")); - assert.ok(items[2].text.startsWith("smoothscroll")); - assert.ok(items[3].text.startsWith("nosmoothscroll")); - assert.ok(items[4].text.startsWith("complete")); - assert.ok(items[5].text.startsWith("colorscheme")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 5); + assert.ok(items[0].text.startsWith("hintchars")); + assert.ok(items[1].text.startsWith("smoothscroll")); + assert.ok(items[2].text.startsWith("nosmoothscroll")); + assert.ok(items[3].text.startsWith("complete")); + assert.ok(items[4].text.startsWith("colorscheme")); }); }); @@ -49,9 +49,10 @@ describe("completion on set commands", () => { await console.inputKeys("set no"); await eventually(async () => { - const items = await console.getCompletions(); - assert.strictEqual(items.length, 2); - assert.ok(items[1].text.includes("nosmoothscroll")); + const groups = await console.getCompletions(); + const items = groups[0].items; + assert.strictEqual(items.length, 1); + assert.ok(items[0].text.includes("nosmoothscroll")); }); }); }); diff --git a/e2e/lib/Console.ts b/e2e/lib/Console.ts index 2c128d1..4d017cc 100644 --- a/e2e/lib/Console.ts +++ b/e2e/lib/Console.ts @@ -1,7 +1,11 @@ import { WebDriver, By, Key } from "selenium-webdriver"; +export type CompletionGroups = { + title: string; + items: Array<CompletionItem>; +}; + export type CompletionItem = { - type: string; text: string; highlight: boolean; }; @@ -25,23 +29,17 @@ export class Console { } async execCommand(command: string): Promise<void> { - const input = await this.webdriver.findElement( - By.css("input.vimvixen-console-command-input") - ); + const input = await this.webdriver.findElement(By.css("input")); await input.sendKeys(command, Key.ENTER); } async getErrorMessage(): Promise<string> { - const p = await this.webdriver.findElement( - By.css(".vimvixen-console-error") - ); + const p = await this.webdriver.findElement(By.css("[role=alert]")); return p.getText(); } async getInformationMessage(): Promise<string> { - const p = await this.webdriver.findElement( - By.css(".vimvixen-console-info") - ); + const p = await this.webdriver.findElement(By.css("[role=status]")); return p.getText(); } @@ -50,36 +48,42 @@ export class Console { await input.sendKeys(...keys); } - getCompletions(): Promise<CompletionItem[]> { + getCompletions(): Promise<CompletionGroups[]> { return this.webdriver.executeScript(() => { - const items = document.querySelectorAll( - ".vimvixen-console-completion > li" - ); - if (items.length === 0) { + const groups = document.querySelectorAll("[role=group]"); + if (groups.length === 0) { throw new Error("completion items not found"); } - const objs = []; - for (const li of Array.from(items)) { - if (li.classList.contains("vimvixen-console-completion-title")) { - objs.push({ type: "title", text: li.textContent!.trim() }); - } else if (li.classList.contains("vimvixen-console-completion-item")) { - const highlight = li.classList.contains( - "vimvixen-completion-selected" - ); - objs.push({ type: "item", text: li.textContent!.trim(), highlight }); - } else { - throw new Error(`unexpected class: ${li.className}`); - } - } - return objs; + return Array.from(groups).map((group) => { + const describedby = group.getAttribute("aria-describedby") as string; + const title = document.getElementById(describedby)!; + const items = group.querySelectorAll("[role=menuitem]"); + + return { + title: title.textContent!.trim(), + items: Array.from(items).map((item) => ({ + text: document.getElementById( + item.getAttribute("aria-labelledby")! + )!.textContent, + highlight: item.getAttribute("aria-selected") === "true", + })), + }; + }); }); } async getTheme(): Promise<string> { - const wrapper = await this.webdriver.findElement(By.css("div[data-theme]")); - const theme = await wrapper.getAttribute("data-theme"); - return theme; + const color = (await this.webdriver.executeScript(() => { + const input = document.querySelector("input")!; + return window.getComputedStyle(input).backgroundColor; + })) as string; + if (color === "rgb(5, 32, 39)") { + return "dark"; + } else if (color === "rgb(255, 255, 255)") { + return "light"; + } + throw new Error(`unknown input color: ${color}`); } async close(): Promise<void> { diff --git a/e2e/lib/Page.ts b/e2e/lib/Page.ts index 85bda8d..6531f19 100644 --- a/e2e/lib/Page.ts +++ b/e2e/lib/Page.ts @@ -46,9 +46,7 @@ export default class Page { await this.sendKeys(":"); await this.webdriver.wait(until.elementIsVisible(iframe)); await this.webdriver.switchTo().frame(0); - await this.webdriver.wait( - until.elementLocated(By.css("input.vimvixen-console-command-input")) - ); + await this.webdriver.wait(until.elementLocated(By.css("input"))); return new Console(this.webdriver); } diff --git a/package.json b/package.json index 3c5c94c..a5d9389 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@types/redux-promise": "^0.5.28", "@types/selenium-webdriver": "^4.0.6", "@types/sinon": "^9.0.0", + "@types/styled-components": "^5.1.2", "@typescript-eslint/eslint-plugin": "3.9.0", "@typescript-eslint/parser": "3.10.1", "ajv": "^6.11.0", @@ -76,6 +77,7 @@ "sinon": "^9.0.3", "sinon-chrome": "^3.0.1", "style-loader": "^1.1.3", + "styled-components": "^5.1.1", "ts-loader": "^8.0.2", "ts-node": "^9.0.0", "tsyringe": "4.3.0", diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx index a0e22e4..a23c459 100644 --- a/src/console/components/Console.tsx +++ b/src/console/components/Console.tsx @@ -11,6 +11,13 @@ import CommandLineParser, { } from "../commandline/CommandLineParser"; import { Command } from "../../shared/Command"; import ColorScheme from "../../shared/ColorScheme"; +import { LightTheme, DarkTheme } from "./Theme"; +import styled from "./Theme"; +import { ThemeProvider } from "styled-components"; + +const ConsoleWrapper = styled.div` + border-top: 1px solid gray; +`; const COMPLETION_MAX_ITEMS = 33; @@ -143,28 +150,34 @@ class Console extends React.Component<Props> { case "command": case "find": return ( - <div data-theme={theme} className="vimvixen-console-command-wrapper"> - <Completion - size={COMPLETION_MAX_ITEMS} - completions={this.props.completions} - select={this.props.select} - /> - <Input - ref={this.input} - mode={this.props.mode} - onBlur={this.onBlur.bind(this)} - onKeyDown={this.onKeyDown.bind(this)} - onChange={this.onChange.bind(this)} - value={this.props.consoleText} - /> - </div> + <ThemeProvider + theme={theme === ColorScheme.Dark ? DarkTheme : LightTheme} + > + <ConsoleWrapper> + <Completion + size={COMPLETION_MAX_ITEMS} + completions={this.props.completions} + select={this.props.select} + /> + <Input + ref={this.input} + mode={this.props.mode} + onBlur={this.onBlur.bind(this)} + onKeyDown={this.onKeyDown.bind(this)} + onChange={this.onChange.bind(this)} + value={this.props.consoleText} + /> + </ConsoleWrapper> + </ThemeProvider> ); case "info": case "error": return ( - <div data-theme={theme}> + <ThemeProvider + theme={theme === ColorScheme.Dark ? DarkTheme : LightTheme} + > <Message mode={this.props.mode}>{this.props.messageText}</Message> - </div> + </ThemeProvider> ); default: return null; diff --git a/src/console/components/Theme.ts b/src/console/components/Theme.ts new file mode 100644 index 0000000..dd7baa5 --- /dev/null +++ b/src/console/components/Theme.ts @@ -0,0 +1,53 @@ +import baseStyled, { ThemedStyledInterface } from "styled-components"; + +type Theme = { + completionTitleBackground: string; + completionTitleForeground: string; + completionItemBackground: string; + completionItemForeground: string; + completionItemDescriptionForeground: string; + completionSelectedBackground: string; + completionSelectedForeground: string; + commandBackground: string; + commandForeground: string; + consoleErrorBackground: string; + consoleErrorForeground: string; + consoleInfoBackground: string; + consoleInfoForeground: string; +}; + +export const LightTheme: Theme = { + completionTitleBackground: "lightgray", + completionTitleForeground: "#000000", + completionItemBackground: "#ffffff", + completionItemForeground: "#000000", + completionItemDescriptionForeground: "#008000", + completionSelectedBackground: "#ffff00", + completionSelectedForeground: "#000000", + commandBackground: "#ffffff", + commandForeground: "#000000", + consoleErrorBackground: "#ff0000", + consoleErrorForeground: "#ffffff", + consoleInfoBackground: "#ffffff", + consoleInfoForeground: "#018786", +}; + +export const DarkTheme: Theme = { + completionTitleBackground: "#052027", + completionTitleForeground: "white", + completionItemBackground: "#2f474f", + completionItemForeground: "white", + completionItemDescriptionForeground: "#86fab0", + completionSelectedBackground: "#eeff41", + completionSelectedForeground: "#000000", + commandBackground: "#052027", + commandForeground: "white", + consoleErrorBackground: "red", + consoleErrorForeground: "white", + consoleInfoBackground: "#052027", + consoleInfoForeground: "#ffffff", +}; + +const styled = baseStyled as ThemedStyledInterface<Theme>; + +export default styled; diff --git a/src/console/components/console.scss b/src/console/components/console.scss index ccb769b..2d548df 100644 --- a/src/console/components/console.scss +++ b/src/console/components/console.scss @@ -1,38 +1,18 @@ -[data-theme="light"] { - --completion-title-background: lightgray; - --completion-title-foreground: #000000; - --completion-item-background: #ffffff; - --completion-item-foreground: #000000; - --completion-item-description-foreground: #008000; - --completion-selected-background: #ffff00; - --completion-selected-foreground: #000000; - --command-background: #ffffff; - --command-foreground: #000000; - --console-error-background: #ff0000; - --console-error-foreground: #ffffff; - --console-info-background: #ffffff; - --console-info-foreground: #018786; -} - -[data-theme="dark"] { - --completion-title-background: #052027; - --completion-title-foreground: white; - --completion-item-background: #2f474f; - --completion-item-foreground: white; - --completion-item-description-foreground: #86fab0; - --completion-selected-background: #eeff41; - --completion-selected-foreground: #000000; - --command-background: #052027; - --command-foreground: white; - --console-error-background: red; - --console-error-foreground: white; - --console-info-background: #052027; - --console-info-foreground: #ffffff; -} - html, body, * { margin: 0; padding: 0; + + font-style: normal; + font-family: monospace; + font-size: 12px; + line-height: 16px; +} + +input { + font-style: normal; + font-family: monospace; + font-size: 12px; + line-height: 16px; } body { @@ -47,95 +27,4 @@ body { bottom: 0; margin: 0; padding: 0; - - @mixin console-font { - font-style: normal; - font-family: monospace; - font-size: 12px; - line-height: 16px; - } - - &-command-wrapper { - border-top: 1px solid gray; - } - - &-completion { - @include console-font; - - &-title { - background-color: var(--completion-title-background); - color: var(--completion-title-foreground); - font-weight: bold; - margin: 0; - padding: 0; - } - - &-item { - background-color: var(--completion-item-background); - color: var(--completion-item-foreground); - - padding-left: 1.5rem; - background-position: 0 center; - background-size: contain; - background-repeat: no-repeat; - white-space: pre; - - &.vimvixen-completion-selected { - background-color: var(--completion-selected-background); - color: var(--completion-selected-foreground); - } - - &-caption { - display: inline-block; - width: 40%; - text-overflow: ellipsis; - overflow: hidden; - } - - &-url { - display: inline-block; - color: var(--completion-item-description-foreground); - width: 60%; - text-overflow: ellipsis; - overflow: hidden; - } - } - } - - &-message { - @include console-font; - - border-top: 1px solid gray; - } - - &-error { - background-color: var(--console-error-background); - color: var(--console-error-foreground); - font-weight: bold; - } - - &-info { - background-color: var(--console-info-background); - color: var(--console-info-foreground); - font-weight: normal; - } - - &-command { - background-color: var(--command-background); - color: var(--command-foreground); - display: flex; - - &-prompt { - @include console-font; - } - - &-input { - border: none; - flex-grow: 1; - background-color: var(--command-background); - color: var(--command-foreground); - - @include console-font; - } - } } diff --git a/src/console/components/console/Completion.tsx b/src/console/components/console/Completion.tsx index 9b4cf15..09ae278 100644 --- a/src/console/components/console/Completion.tsx +++ b/src/console/components/console/Completion.tsx @@ -63,29 +63,52 @@ class Completion extends React.Component<Props, State> { } render() { - let eles = []; - let index = 0; + let itemIndex = 0; + let viewIndex = 0; + const groups: Array<JSX.Element> = []; + const viewOffset = this.state.viewOffset; + const viewSize = this.props.size; - for (const group of this.props.completions) { - eles.push(<CompletionTitle key={`group-${index}`} title={group.name} />); + this.props.completions.forEach((group, groupIndex) => { + const items = []; + const title = ( + <CompletionTitle + id={`title-${groupIndex}`} + key={`group-${groupIndex}`} + shown={viewOffset <= viewIndex && viewIndex < viewOffset + viewSize} + title={group.name} + /> + ); + ++viewIndex; for (const item of group.items) { - eles.push( + items.push( <CompletionItem - key={`item-${index}`} + shown={viewOffset <= viewIndex && viewIndex < viewOffset + viewSize} + key={`item-${itemIndex}`} icon={item.icon} caption={item.caption} url={item.url} - highlight={index === this.props.select} + highlight={itemIndex === this.props.select} + aria-selected={itemIndex === this.props.select} + role="menuitem" /> ); - ++index; + ++viewIndex; + ++itemIndex; } - } - - const viewOffset = this.state.viewOffset; - eles = eles.slice(viewOffset, viewOffset + this.props.size); + groups.push( + <div + key={`group-${groupIndex}`} + role="group" + aria-describedby={`title-${groupIndex}`} + > + {title} + <ul>{items}</ul> + </div> + ); + }); - return <ul className="vimvixen-console-completion">{eles}</ul>; + return <div role="menu">{groups}</div>; } } diff --git a/src/console/components/console/CompletionItem.tsx b/src/console/components/console/CompletionItem.tsx index 657f360..5f2f9f6 100644 --- a/src/console/components/console/CompletionItem.tsx +++ b/src/console/components/console/CompletionItem.tsx @@ -1,35 +1,62 @@ import React from "react"; -import PropTypes from "prop-types"; +import styled from "../Theme"; + +const Container = styled.li<{ + shown: boolean; + icon: string; + highlight: boolean; +}>` + background-image: ${({ icon }) => "url(" + icon + ")"}; + background-color: ${({ highlight, theme }) => + highlight + ? theme.completionSelectedBackground + : theme.completionItemBackground}; + color: ${({ highlight, theme }) => + highlight + ? theme.completionSelectedForeground + : theme.completionItemForeground}; + display: ${({ shown }) => (shown ? "display" : "none")}; + padding-left: 1.8rem; + background-position: 0 center; + background-size: contain; + background-repeat: no-repeat; + white-space: pre; +`; + +const Caption = styled.span` + display: inline-block; + width: 40%; + text-overflow: ellipsis; + overflow: hidden; +`; + +const Description = styled.span` + display: inline-block; + color: ${({ theme }) => theme.completionItemDescriptionForeground}; + width: 60%; + text-overflow: ellipsis; + overflow: hidden; +`; interface Props { + shown: boolean; highlight: boolean; caption?: string; url?: string; icon?: string; } -const CompletionItem = (props: Props) => { - let className = "vimvixen-console-completion-item"; - if (props.highlight) { - className += " vimvixen-completion-selected"; - } - return ( - <li - className={className} - style={{ backgroundImage: "url(" + props.icon + ")" }} - > - <span className="vimvixen-console-completion-item-caption"> - {props.caption} - </span> - <span className="vimvixen-console-completion-item-url">{props.url}</span> - </li> - ); -}; - -CompletionItem.propTypes = { - highlight: PropTypes.bool, - caption: PropTypes.string, - url: PropTypes.string, -}; +const CompletionItem: React.FC<React.HTMLAttributes<HTMLElement> & Props> = ( + props +) => ( + <Container + icon={props.icon || ""} + aria-labelledby={`completion-item-${props.caption}`} + {...props} + > + <Caption id={`completion-item-${props.caption}`}>{props.caption}</Caption> + <Description>{props.url}</Description> + </Container> +); export default CompletionItem; diff --git a/src/console/components/console/CompletionTitle.tsx b/src/console/components/console/CompletionTitle.tsx index 7257006..ec2fc8b 100644 --- a/src/console/components/console/CompletionTitle.tsx +++ b/src/console/components/console/CompletionTitle.tsx @@ -1,11 +1,22 @@ import React from "react"; +import styled from "../Theme"; + +const Li = styled.li<{ shown: boolean }>` + display: ${({ shown }) => (shown ? "display" : "none")}; + background-color: ${({ theme }) => theme.completionTitleBackground}; + color: ${({ theme }) => theme.completionTitleForeground}; + font-weight: bold; + margin: 0; + padding: 0; +`; interface Props { + shown: boolean; title: string; } -const CompletionTitle = (props: Props) => { - return <li className="vimvixen-console-completion-title">{props.title}</li>; -}; +const CompletionTitle: React.FC<React.HTMLAttributes<HTMLElement> & Props> = ( + props +) => <Li {...props}>{props.title}</Li>; export default CompletionTitle; diff --git a/src/console/components/console/Input.tsx b/src/console/components/console/Input.tsx index e412a0c..448b096 100644 --- a/src/console/components/console/Input.tsx +++ b/src/console/components/console/Input.tsx @@ -1,4 +1,22 @@ import React from "react"; +import styled from "../Theme"; + +const Container = styled.div` + background-color: ${({ theme }) => theme.commandBackground}; + color: ${({ theme }) => theme.commandForeground}; + display: flex; +`; + +const Prompt = styled.i` + font-style: normal; +`; + +const InputInner = styled.input` + border: none; + flex-grow: 1; + background-color: ${({ theme }) => theme.commandBackground}; + color: ${({ theme }) => theme.commandForeground}; +`; interface Props { mode: string; @@ -32,17 +50,16 @@ class Input extends React.Component<Props> { } return ( - <div className="vimvixen-console-command"> - <i className="vimvixen-console-command-prompt">{prompt}</i> - <input - className="vimvixen-console-command-input" + <Container> + <Prompt>{prompt}</Prompt> + <InputInner ref={this.input} onBlur={this.props.onBlur} onKeyDown={this.props.onKeyDown} onChange={this.props.onChange} value={this.props.value} /> - </div> + </Container> ); } } diff --git a/src/console/components/console/Message.tsx b/src/console/components/console/Message.tsx index fd1c9d7..73498fd 100644 --- a/src/console/components/console/Message.tsx +++ b/src/console/components/console/Message.tsx @@ -1,24 +1,31 @@ import React from "react"; +import styled from "../Theme"; + +const Error = styled.p` + border-top: 1px solid gray; + background-color: ${({ theme }) => theme.consoleErrorBackground}; + color: ${({ theme }) => theme.consoleErrorForeground}; + font-weight: bold; +`; + +const Info = styled.p` + border-top: 1px solid gray; + background-color: ${({ theme }) => theme.consoleInfoBackground}; + color: ${({ theme }) => theme.consoleInfoForeground}; + font-weight: normal; +`; interface Props { mode: string; children: string; } -const Message = (props: Props) => { - switch (props.mode) { +const Message: React.FC<Props> = ({ mode, children }) => { + switch (mode) { case "error": - return ( - <p className="vimvixen-console-message vimvixen-console-error"> - {props.children} - </p> - ); + return <Error role="alert">{children}</Error>; case "info": - return ( - <p className="vimvixen-console-message vimvixen-console-info"> - {props.children} - </p> - ); + return <Info role="status">{children}</Info>; } return null; }; diff --git a/test/console/components/console/Completion.test.tsx b/test/console/components/console/Completion.test.tsx index 921720b..0e4e21f 100644 --- a/test/console/components/console/Completion.test.tsx +++ b/test/console/components/console/Completion.test.tsx @@ -1,11 +1,11 @@ import React from "react"; import Completion from "../../../../src/console/components/console/Completion"; -import ReactTestRenderer, { ReactTestInstance } from "react-test-renderer"; +import ReactTestRenderer from "react-test-renderer"; import { expect } from "chai"; import CompletionTitle from "../../../../src/console/components/console/CompletionTitle"; import CompletionItem from "../../../../src/console/components/console/CompletionItem"; -describe("console/components/console/completion", () => { +describe("console/components/console/completion/Completion", () => { const completions = [ { name: "Fruit", @@ -30,27 +30,19 @@ describe("console/components/console/completion", () => { <Completion completions={completions} size={30} select={-1} /> ).root; - // const children = root.findByType('ul').children as Array<ReactTestInstance>; - const children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(8); - expect(children.map((e) => e.type)).to.deep.equal([ - CompletionTitle, - CompletionItem, - CompletionItem, - CompletionItem, - CompletionTitle, - CompletionItem, - CompletionItem, - CompletionItem, - ]); - expect(children[0].props.title).to.equal("Fruit"); - expect(children[1].props.caption).to.equal("apple"); - expect(children[2].props.caption).to.equal("banana"); - expect(children[3].props.caption).to.equal("cherry"); - expect(children[4].props.title).to.equal("Element"); - expect(children[5].props.caption).to.equal("argon"); - expect(children[6].props.caption).to.equal("boron"); - expect(children[7].props.caption).to.equal("carbon"); + const groups = root.findAllByProps({ role: "group" }); + expect(groups).to.have.lengthOf(2); + + groups.forEach((group, i) => { + const title = group.findByType(CompletionTitle); + expect(title.props.title).to.equal(completions[i].name); + + const items = group.findAllByType(CompletionItem); + expect(items).to.have.lengthOf(completions[i].items.length); + items.forEach((item, j) => { + expect(item.props.caption).to.equal(completions[i].items[j].caption); + }); + }); }); it("highlight current item", () => { @@ -58,8 +50,8 @@ describe("console/components/console/completion", () => { <Completion completions={completions} size={30} select={3} /> ).root; - const children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children[5].props.highlight).to.be.true; + const items = root.findAllByType(CompletionItem); + expect(items[3].props.highlight).to.be.true; }); it("does not highlight any items", () => { @@ -67,8 +59,8 @@ describe("console/components/console/completion", () => { <Completion completions={completions} size={30} select={-1} /> ).root; - const children = root.findByType("ul").findAllByType(CompletionItem); - expect(children.every((e) => e.props.highlight === false)).to.be.true; + const items = root.findAllByType(CompletionItem); + expect(items.every((item) => item.props.highlight === false)).to.be.true; }); it("limits completion items", () => { @@ -76,19 +68,35 @@ describe("console/components/console/completion", () => { <Completion completions={completions} size={3} select={-1} /> ).root; - let children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(3); + const showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); - expect(children[0].props.title).to.equal("Fruit"); - expect(children[1].props.caption).to.equal("apple"); - expect(children[2].props.caption).to.equal("banana"); + expect(showns).to.deep.equal([ + true, + true, + true, + false, + false, + false, + false, + false, + ]); root = ReactTestRenderer.create( <Completion completions={completions} size={3} select={0} /> ).root; - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children[1].props.highlight).to.be.true; + const items = root + .findAllByType(CompletionItem) + .map((item) => item.props.shown); + expect(items[1]).to.be.true; }); it("scrolls up to down with select", () => { @@ -97,33 +105,76 @@ describe("console/components/console/completion", () => { ); const root = component.root; - let children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(3); - expect(children[0].props.title).to.equal("Fruit"); - expect(children[1].props.caption).to.equal("apple"); - expect(children[2].props.caption).to.equal("banana"); + let items = root.findAllByType(CompletionItem); + let showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + true, + true, + true, + false, + false, + false, + false, + false, + ]); component.update( <Completion completions={completions} size={3} select={2} /> ); - - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(3); - expect(children[0].props.caption).to.equal("apple"); - expect(children[1].props.caption).to.equal("banana"); - expect(children[2].props.caption).to.equal("cherry"); - expect(children[2].props.highlight).to.be.true; + items = root.findAllByType(CompletionItem); + showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + false, + true, + true, + true, + false, + false, + false, + false, + ]); + expect(items[2].props.highlight).to.be.true; component.update( <Completion completions={completions} size={3} select={3} /> ); - - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(3); - expect(children[0].props.caption).to.equal("cherry"); - expect(children[1].props.title).to.equal("Element"); - expect(children[2].props.caption).to.equal("argon"); - expect(children[2].props.highlight).to.be.true; + items = root.findAllByType(CompletionItem); + showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + false, + false, + false, + true, + true, + true, + false, + false, + ]); + expect(items[3].props.highlight).to.be.true; }); it("scrolls down to up with select", () => { @@ -132,34 +183,102 @@ describe("console/components/console/completion", () => { ); const root = component.root; - let children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children).to.have.lengthOf(3); - expect(children[0].props.caption).to.equal("argon"); - expect(children[1].props.caption).to.equal("boron"); - expect(children[2].props.caption).to.equal("carbon"); + let items = root.findAllByType(CompletionItem); + let showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + + expect(showns).to.deep.equal([ + false, + false, + false, + false, + false, + true, + true, + true, + ]); + expect(items[5].props.highlight).to.be.true; component.update( <Completion completions={completions} size={3} select={4} /> ); - - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children[1].props.highlight).to.be.true; + items = root.findAllByType(CompletionItem); + showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + false, + false, + false, + false, + false, + true, + true, + true, + ]); + expect(items[4].props.highlight).to.be.true; component.update( <Completion completions={completions} size={3} select={3} /> ); - - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children[0].props.highlight).to.be.true; + items = root.findAllByType(CompletionItem); + showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + false, + false, + false, + false, + false, + true, + true, + true, + ]); + expect(items[3].props.highlight).to.be.true; component.update( <Completion completions={completions} size={3} select={2} /> ); - - children = root.findByType("ul").children as Array<ReactTestInstance>; - expect(children[0].props.caption).to.equal("cherry"); - expect(children[1].props.title).to.equal("Element"); - expect(children[2].props.caption).to.equal("argon"); - expect(children[0].props.highlight).to.be.true; + items = root.findAllByType(CompletionItem); + showns = root + .findAllByProps({ role: "group" }) + .map((group) => + [ + group.findByType(CompletionTitle).props.shown, + group.findAllByType(CompletionItem).map((item) => item.props.shown), + ].flat() + ) + .flat(); + expect(showns).to.deep.equal([ + false, + false, + false, + true, + true, + true, + false, + false, + ]); + expect(items[2].props.highlight).to.be.true; }); }); diff --git a/test/console/components/console/CompletionItem.test.tsx b/test/console/components/console/CompletionItem.test.tsx new file mode 100644 index 0000000..3a4b1f2 --- /dev/null +++ b/test/console/components/console/CompletionItem.test.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import ReactTestRenderer from "react-test-renderer"; +import { expect } from "chai"; +import CompletionItem from "../../../../src/console/components/console/CompletionItem"; + +describe("console/components/console/completion/CompletionItem", () => { + it("renders a CompletionItem", () => { + const root = ReactTestRenderer.create( + <CompletionItem + shown={true} + highlight={false} + caption="twitter" + url="https://twitter.com/" + /> + ).root; + const spans = root.findAllByType("span"); + expect(spans).to.have.lengthOf(2); + expect(spans[0].children).to.deep.equal(["twitter"]); + expect(spans[1].children).to.deep.equal(["https://twitter.com/"]); + }); +}); diff --git a/test/console/components/console/CompletionTitle.test.tsx b/test/console/components/console/CompletionTitle.test.tsx new file mode 100644 index 0000000..d8cc411 --- /dev/null +++ b/test/console/components/console/CompletionTitle.test.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import ReactTestRenderer from "react-test-renderer"; +import { expect } from "chai"; +import CompletionTitle from "../../../../src/console/components/console/CompletionTitle"; + +describe("console/components/console/completion/CompletionTitle", () => { + it("renders a CompletionTitle", () => { + const root = ReactTestRenderer.create( + <CompletionTitle title="Fruits" shown={true} /> + ).root; + + const li = root.findByType("li"); + expect(li.children).to.deep.equal(["Fruits"]); + }); +}); diff --git a/test/console/components/console/Message.test.tsx b/test/console/components/console/Message.test.tsx new file mode 100644 index 0000000..f8f950a --- /dev/null +++ b/test/console/components/console/Message.test.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import ReactTestRenderer from "react-test-renderer"; +import { expect } from "chai"; +import Message from "../../../../src/console/components/console/Message"; + +describe("console/components/console/completion/Message", () => { + it("renders an information message", () => { + const root = ReactTestRenderer.create(<Message mode="info">Hello!</Message>) + .root; + + const p = root.findByType("p"); + + expect(p.props["role"]).to.equal("status"); + expect(p.children).to.deep.equal(["Hello!"]); + }); + + it("renders an error message", () => { + const root = ReactTestRenderer.create( + <Message mode="error">Hello!</Message> + ).root; + + const p = root.findByType("p"); + + expect(p.props["role"]).to.equal("alert"); + expect(p.children).to.deep.equal(["Hello!"]); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index f3a6a33..9d56c01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2017", "module": "commonjs", - "lib": ["es6", "dom", "es2017"], + "lib": ["es6", "dom", "esnext"], "allowJs": true, "checkJs": false, "jsx": "react", @@ -2,13 +2,59 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: "@babel/highlight" "^7.10.4" +"@babel/generator@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" + integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== + dependencies: + "@babel/types" "^7.11.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-imports@^7.0.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" @@ -23,6 +69,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.10.4", "@babel/parser@^7.11.0": + version "7.11.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.3.tgz#9e1eae46738bcd08e23e867bab43e7b95299a8f9" + integrity sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA== + "@babel/runtime@^7.5.5": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" @@ -30,6 +81,61 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.4.5": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" + integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.0" + "@babel/types" "^7.11.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.10.4", "@babel/types@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" + integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@emotion/is-prop-valid@^0.8.8": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -142,7 +248,7 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/hoist-non-react-statics@^3.3.0": +"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -202,6 +308,13 @@ dependencies: "@types/react" "*" +"@types/react-native@*": + version "0.63.8" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.63.8.tgz#73ec087122c64c309eeaf150b565b8d755f0fb1f" + integrity sha512-QRwGFRTyGafRVTUS+0GYyJrlpmS3boyBaFI0ULSc+mh/lQNxrzbdQvoL2k5X7+t9hxyqA4dTQAlP6l0ir/fNJQ== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.7": version "7.1.9" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3" @@ -264,6 +377,16 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/styled-components@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.2.tgz#652af475b4af917b355ea1c3068acae63d46455f" + integrity sha512-HNocYLfrsnNNm8NTS/W53OERSjRA8dx5Bn6wBd2rXXwt4Z3s+oqvY6/PbVt3e6sgtzI63GX//WiWiRhWur08qQ== + dependencies: + "@types/hoist-non-react-statics" "*" + "@types/react" "*" + "@types/react-native" "*" + csstype "^3.0.2" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -884,6 +1007,21 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== +"babel-plugin-styled-components@>= 1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.11.1.tgz#5296a9e557d736c3186be079fff27c6665d63d76" + integrity sha512-YwrInHyKUk1PU3avIRdiLyCpM++18Rs1NgyMXEAQC33rIXs/vro0A+stf4sT0Gf22Got+xRWB8Cm0tw+qkRzBA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-module-imports" "^7.0.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -1219,6 +1357,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== +camelize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1666,6 +1809,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= + css-loader@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.1.tgz#9f48fd7eae1219d629a3f085ba9a9102ca1141a7" @@ -1695,6 +1843,15 @@ css-select@^1.1.0: domutils "1.5.1" nth-check "~1.0.1" +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -1758,7 +1915,7 @@ debug@3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.1, debug@~4.1.0: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -2920,6 +3077,11 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globals@^12.1.0: version "12.4.0" resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" @@ -3067,7 +3229,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -3659,6 +3821,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -4027,7 +4194,7 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.0.0, lodash@^4.16.3, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.2.1, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.16.3, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.2.1, lodash@~4.17.10: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -5033,7 +5200,7 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -5857,6 +6024,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -6064,7 +6236,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.6: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -6342,6 +6514,22 @@ style-loader@^1.1.3: loader-utils "^2.0.0" schema-utils "^2.6.6" +styled-components@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.1.1.tgz#96dfb02a8025794960863b9e8e365e3b6be5518d" + integrity sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^0.8.8" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + supports-color@7.1.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -6477,6 +6665,11 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" |