diff --git a/ext/src/_locales/en/messages.json b/ext/src/_locales/en/messages.json index 820c892..c758d73 100755 --- a/ext/src/_locales/en/messages.json +++ b/ext/src/_locales/en/messages.json @@ -1,97 +1,97 @@ { - "extension_name": { + "extensionName": { "message": "EXTENSION_NAME" } - , "extension_description": { + , "extensionDescription": { "message": "" } - , "popup_cast_button_label": { + , "popupCastButtonLabel": { "message": "Cast" } - , "popup_casting_button_label": { + , "popupCastingButtonLabel": { "message": "Casting" } - , "context_media_cast": { + , "contextCast": { "message": "Cast..." } - , "options_category_media": { + , "optionsMediaCategoryName": { "message": "Media casting" } - , "options_category_media_description": { + , "optionsMediaCategoryDescription": { "message": "HTML5 video/audio media casting." } - , "options_option_mediaEnabled": { + , "optionsMediaEnabled": { "message": "Enabled" } - , "options_category_localMedia": { + , "optionsLocalMediaCategoryName": { "message": "Local media casting" } - , "options_category_localMedia_description": { + , "optionsLocalMediaCategoryDescription": { "message": "HTTP server started by the bridge app to stream local media files to the cast receiver." } - , "options_option_localMediaEnabled": { + , "optionsLocalMediaEnabled": { "message": "Enabled" } - , "options_option_localMediaServerPort": { + , "optionsLocalMediaServerPort": { "message": "HTTP server port" } - , "options_category_uaWhitelist": { + , "optionsUserAgentWhitelistCategoryName": { "message": "User agent whitelist" } - , "options_category_uaWhitelist_description": { + , "optionsUserAgentWhitelistCategoryDescription": { "message": "Sites for which to replace the user agent with a Chrome version for compatibility. Must be valid match patterns." } - , "options_option_uaWhitelistEnabled": { + , "optionsUserAgentWhitelistEnabled": { "message": "Enabled" } - , "options_option_uaWhitelist": { + , "optionsUserAgentWhitelistContent": { "message": "Match patterns (newline-separated)" } - , "options_option_uaWhitelistBasicView": { + , "optionsUserAgentWhitelistBasicView": { "message": "Basic View" } - , "options_option_uaWhitelistRawView": { + , "optionsUserAgentWhitelistRawView": { "message": "Raw View" } - , "options_option_uaWhitelistSaveRaw": { + , "optionsUserAgentWhitelistSaveRaw": { "message": "Save Raw" } - , "options_option_uaWhitelistAddItem": { + , "optionsUserAgentWhitelistAddItem": { "message": "Add Item" } - , "options_option_uaWhitelistItemEdit": { + , "optionsUserAgentWhitelistEditItem": { "message": "Edit" } - , "options_option_uaWhitelistItemRemove": { + , "optionsUserAgentWhitelistRemoveItem": { "message": "Remove" } - , "options_option_uaWhitelistInvalidMatchPattern": { + , "optionsUserAgentWhitelistInvalidMatchPattern": { "message": "Invalid match pattern $1" } - , "options_category_mirroring": { + , "optionsMirroringCategoryName": { "message": "Screen mirroring" } - , "options_category_mirroring_description": { + , "optionsMirroringCategoryDescription": { "message": "Screen/Tab mirroring to a Chromecast receiver app." } - , "options_option_mirroringEnabled": { + , "optionsMirroringEnabled": { "message": "Enabled" } - , "options_option_mirroringAppId": { + , "optionsMirroringAppId": { "message": "Receiver app ID" } - , "options_reset": { + , "optionsReset": { "message": "Reset to defaults" } - , "options_submit": { + , "optionsSubmit": { "message": "Submit" } } diff --git a/ext/src/main.js b/ext/src/main.js index 1eb1d65..88bd884 100755 --- a/ext/src/main.js +++ b/ext/src/main.js @@ -10,20 +10,23 @@ browser.runtime.onInstalled.addListener(async details => { switch (details.reason) { // Set default options - case "install": + case "install": { await browser.storage.sync.set({ options: defaultOptions }); break; + }; // Set newly added options - case "update": - const { options } = await browser.storage.sync.get("options"); + case "update": { + const { options: existingOptions } + = await browser.storage.sync.get("options"); + const newOptions = {}; // Find options not already in storage for (const [ key, val ] of Object.entries(defaultOptions)) { - if (!options.hasOwnProperty(key)) { + if (!existingOptions.hasOwnProperty(key)) { newOptions[key] = val; } } @@ -31,12 +34,13 @@ browser.runtime.onInstalled.addListener(async details => { // Update storage with default values of new options await browser.storage.sync.set({ options: { - ...options + ...existingOptions , ...newOptions } }); break; + }; } // Call after default options have been set @@ -64,7 +68,7 @@ async function createMenus () { */ if (!options || mirrorCastMenuId || mediaCastMenuId) return; - if (options.option_localMediaEnabled) { + if (options.localMediaEnabled) { mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN); } @@ -73,16 +77,16 @@ async function createMenus () { contexts: [ "audio", "video" ] , id: "contextCastMedia" , targetUrlPatterns: Array.from(mediaCastTargetUrlPatterns) - , title: _("context_media_cast") - , visible: options.option_mediaEnabled + , title: _("contextCast") + , visible: options.mediaEnabled }); // Screen/Tab mirroring "Cast..." context menu item mirrorCastMenuId = await browser.menus.create({ contexts: [ "browser_action", "page" ] , id: "contextCast" - , title: _("context_media_cast") - , visible: options.option_mirroringEnabled + , title: _("contextCast") + , visible: options.mirroringEnabled }); } @@ -190,8 +194,8 @@ async function onOptionsUpdated (alteredOptions) { onBeforeSendHeaders () { browser.webRequest.onBeforeSendHeaders.addListener( onBeforeSendHeaders - , { urls: options.option_uaWhitelistEnabled - ? options.option_uaWhitelist + , { urls: options.userAgentWhitelistEnabled + ? options.userAgentWhitelist : [] } , [ "blocking", "requestHeaders" ]); } @@ -203,26 +207,26 @@ async function onOptionsUpdated (alteredOptions) { func(); } } else { - if (alteredOptions.includes("option_uaWhitelist") - || alteredOptions.includes("option_uaWhitelistEnabled")) { + if (alteredOptions.includes("userAgentWhitelist") + || alteredOptions.includes("userAgentWhitelistEnabled")) { browser.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); registerFunctions.onBeforeSendHeaders(); } - if (alteredOptions.includes("option_mirroringEnabled")) { + if (alteredOptions.includes("mirroringEnabled")) { browser.menus.update(mirrorCastMenuId, { - visible: options.option_mirroringEnabled + visible: options.mirroringEnabled }); } - if (alteredOptions.includes("option_mediaEnabled")) { + if (alteredOptions.includes("mediaEnabled")) { browser.menus.update(mediaCastMenuId, { - visible: options.option_mediaEnabled + visible: options.mediaEnabled }) } - if (alteredOptions.includes("option_localMediaEnabled")) { - if (options.option_localMediaEnabled) { + if (alteredOptions.includes("localMediaEnabled")) { + if (options.localMediaEnabled) { mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN); } else { mediaCastTargetUrlPatterns.delete(LOCAL_MEDIA_URL_PATTERN); @@ -276,7 +280,7 @@ browser.menus.onClicked.addListener(async (info, tab) => { await browser.tabs.executeScript(tab.id, { code: `let selectedMedia = "${info.pageUrl ? "tab" : "screen"}"; - let FX_CAST_RECEIVER_APP_ID = "${options.option_mirroringAppId}";` + let FX_CAST_RECEIVER_APP_ID = "${options.mirroringEnabled}";` , frameId }); diff --git a/ext/src/manifest.json b/ext/src/manifest.json index cca9e6b..66e5dc6 100755 --- a/ext/src/manifest.json +++ b/ext/src/manifest.json @@ -1,6 +1,6 @@ { - "name": "__MSG_extension_name__" - , "description": "__MSG_extension_description__" + "name": "__MSG_extensionName__" + , "description": "__MSG_extensionDescription__" , "version": "EXTENSION_VERSION" , "applications": { diff --git a/ext/src/mediaCast.js b/ext/src/mediaCast.js index 301d2ff..1993c96 100644 --- a/ext/src/mediaCast.js +++ b/ext/src/mediaCast.js @@ -122,7 +122,7 @@ async function onRequestSessionSuccess (session_) { session = session_; let mediaUrl = new URL(srcUrl); - const port = options.option_localMediaServerPort; + const port = options.localMediaServerPort; if (isLocalFile) { await new Promise((resolve, reject) => { @@ -295,7 +295,7 @@ window.__onGCastApiAvailable = async function (loaded, errorInfo) { options = (await browser.storage.sync.get("options")).options; - if (isLocalFile && !options.option_localMediaEnabled) { + if (isLocalFile && !options.localMediaEnabled) { logMessage("Local media casting not enabled"); return; } diff --git a/ext/src/options/EditableList.jsx b/ext/src/options/EditableList.jsx new file mode 100644 index 0000000..56ebe28 --- /dev/null +++ b/ext/src/options/EditableList.jsx @@ -0,0 +1,175 @@ +import React, { Component } from "react"; +import EditableListItem from "./EditableListItem"; + +const _ = browser.i18n.getMessage; + + +export default class EditableList extends Component { + constructor (props) { + super(props); + this.state = { + items: new Set(this.props.data) + , addingNewItem: false + , rawView: false + , rawViewValue: "" + }; + + this.handleItemRemove = this.handleItemRemove.bind(this); + this.handleItemEdit = this.handleItemEdit.bind(this); + this.handleSwitchView = this.handleSwitchView.bind(this); + this.handleSaveRaw = this.handleSaveRaw.bind(this); + this.handleRawViewTextAreaChange = this.handleRawViewTextAreaChange.bind(this); + this.handleAddItem = this.handleAddItem.bind(this); + this.handleNewItemRemove = this.handleNewItemRemove.bind(this); + this.handleNewItemEdit = this.handleNewItemEdit.bind(this); + } + + handleItemRemove (item) { + this.setState(currentState => { + const newItems = new Set(currentState.items); + newItems.delete(item); + return { + items: newItems + }; + }, () => { + this.props.onChange(Array.from(this.state.items)); + }); + } + + handleItemEdit (item, newValue) { + this.setState(currentState => ({ + items: new Set([...currentState.items] + .map(item_ => item_ === item ? newValue : item_)) + }), () => { + this.props.onChange(Array.from(this.state.items)); + }); + } + + handleSwitchView () { + this.setState(currentState => { + if (currentState.rawView) { + return { + rawView: false + , rawViewValue: "" + }; + } + + return { + rawView: true + , rawViewValue: [...currentState.items.values()].join("\n") + }; + }); + } + + handleSaveRaw () { + this.setState(currentState => { + console.log(currentState.rawViewValue); + const newItems = currentState.rawViewValue.split("\n") + .filter(item => item !== ""); + + if ("itemPattern" in this.props) { + for (const item of newItems) { + if (!this.props.itemPattern.test(item)) { + this.rawViewTextArea.setCustomValidity( + this.props.itemPatternError(item)); + return; + } + } + + this.rawViewTextArea.setCustomValidity(""); + } + + return { + items: new Set(newItems) + }; + }, () => { + this.props.onChange(Array.from(this.state.items)); + }); + } + + handleRawViewTextAreaChange (ev) { + if (this.rawViewTextArea.scrollHeight > this.rawViewTextArea.clientHeight) { + this.rawViewTextArea.style.height = `${this.rawViewTextArea.scrollHeight}px`; + } + + this.setState({ + rawViewValue: ev.target.value + }); + } + + handleAddItem () { + this.setState({ + addingNewItem: true + }); + } + + handleNewItemRemove () { + this.setState({ + addingNewItem: false + }); + } + + handleNewItemEdit (item, newItem) { + this.setState(currentState => ({ + items: [ ...currentState.items, newItem ] + , addingNewItem: false + }), () => { + this.props.onChange(Array.from(this.state.items)); + }); + } + + render () { + const items = Array.from(this.state.items.values()); + + return ( +
+ + { this.state.rawView && + } +
+ { do { + if (this.state.rawView) { + + } else { + + } + }} +
+ ); + } +} \ No newline at end of file diff --git a/ext/src/options/EditableListItem.jsx b/ext/src/options/EditableListItem.jsx new file mode 100644 index 0000000..5921a53 --- /dev/null +++ b/ext/src/options/EditableListItem.jsx @@ -0,0 +1,91 @@ +import React, { Component } from "react"; + +const _ = browser.i18n.getMessage; + + +export default class EditableListItem extends Component { + constructor (props) { + super(props); + this.state = { + editing: this.props.editing || false + , editValue: "" + }; + + this.handleRemove = this.handleRemove.bind(this); + this.handleEditBegin = this.handleEditBegin.bind(this); + this.handleEditEnd = this.handleEditEnd.bind(this); + this.handleInputChange = this.handleInputChange.bind(this); + this.handleInputKeyPress = this.handleInputKeyPress.bind(this); + } + + handleRemove () { + this.props.onRemove(this.props.text); + } + + handleEditBegin () { + this.setState({ + editing: true + , editValue: this.props.text + }); + } + + handleEditEnd (ev) { + if (this.props.editing + && !this.props.itemPattern.test(this.state.editValue)) { + ev.target.setCustomValidity(this.props.itemPatternError()); + } + + if (!ev.target.validity.valid) { + return; + } + + this.props.onEdit(this.props.text, this.state.editValue); + this.setState({ + editing: false + , editValue: "" + }); + } + + handleInputChange (ev) { + this.setState({ + editValue: ev.target.value + }); + + if (!this.props.itemPattern.test(ev.target.value)) { + ev.target.setCustomValidity(this.props.itemPatternError()); + } else { + ev.target.setCustomValidity(""); + } + } + + handleInputKeyPress (ev) { + if (ev.key === "Enter") { + this.handleEditEnd({ target: ev.target }); + } + } + + render () { + return ( +
  • +
    + { this.state.editing + ? + : this.props.text } +
    + + +
  • + ); + } +} diff --git a/ext/src/options/defaultOptions.js b/ext/src/options/defaultOptions.js index ec49e19..8d1214e 100644 --- a/ext/src/options/defaultOptions.js +++ b/ext/src/options/defaultOptions.js @@ -1,11 +1,11 @@ export default { - option_mediaEnabled: true - , option_localMediaEnabled: true - , option_localMediaServerPort: 9555 - , option_uaWhitelistEnabled: true - , option_uaWhitelist: [ + mediaEnabled: true + , localMediaEnabled: true + , localMediaServerPort: 9555 + , mirroringEnabled: false + , mirroringAppId: MIRRORING_APP_ID + , userAgentWhitelistEnabled: true + , userAgentWhitelist: [ "https://www.netflix.com/*" ] - , option_mirroringEnabled: false - , option_mirroringAppId: MIRRORING_APP_ID } diff --git a/ext/src/options/index.css b/ext/src/options/index.css index 51e569a..ce0e475 100644 --- a/ext/src/options/index.css +++ b/ext/src/options/index.css @@ -4,8 +4,8 @@ grid-column-gap: 20px; grid-row-gap: 5px; } -.category-name {} -.category-description, +.category__name {} +.category__description, .category .category { color: graytext; grid-column: span 2; @@ -15,7 +15,7 @@ display: contents; } -.option-label { +.option__label { text-align: right; display: inline-block; } diff --git a/ext/src/options/index.jsx b/ext/src/options/index.jsx index 161299e..63a0147 100644 --- a/ext/src/options/index.jsx +++ b/ext/src/options/index.jsx @@ -1,9 +1,10 @@ "use strict"; import React, { Component } from "react"; -import ReactDOM from "react-dom"; +import ReactDOM from "react-dom"; -import defaultOptions from "./defaultOptions"; +import defaultOptions from "./defaultOptions"; +import EditableList from "./EditableList"; const _ = browser.i18n.getMessage; @@ -11,266 +12,24 @@ const _ = browser.i18n.getMessage; const MATCH_PATTERN_REGEX = /^(?:(?:(\*|https?|ftp):\/\/((?:\*\.|[^\/\*])+)|(file):\/\/\/?(?:\*\.|[^\/\*])+)(\/.*)|)$/; -class EditableListItem extends React.Component { - constructor (props) { - super(props); - this.state = { - editing: this.props.editing || false - , editValue: "" - }; +function getInputValue (input) { + switch (input.type) { + case "checkbox": + return input.checked; + case "number": + return parseFloat(input.value); - this.handleRemove = this.handleRemove.bind(this); - this.handleEditBegin = this.handleEditBegin.bind(this); - this.handleEditEnd = this.handleEditEnd.bind(this); - this.handleInputChange = this.handleInputChange.bind(this); - this.handleInputKeyPress = this.handleInputKeyPress.bind(this); - } - - handleRemove () { - this.props.onRemove(this.props.text); - } - - handleEditBegin () { - this.setState({ - editing: true - , editValue: this.props.text - }); - } - - handleEditEnd (ev) { - if (this.props.editing - && !this.props.itemPattern.test(this.state.editValue)) { - ev.target.setCustomValidity(this.props.itemPatternError()); - } - - if (!ev.target.validity.valid) { - return; - } - - this.props.onEdit(this.props.text, this.state.editValue); - this.setState({ - editing: false - , editValue: "" - }); - } - - handleInputChange (ev) { - this.setState({ - editValue: ev.target.value - }); - - if (!this.props.itemPattern.test(ev.target.value)) { - ev.target.setCustomValidity(this.props.itemPatternError()); - } else { - ev.target.setCustomValidity(""); - } - } - - handleInputKeyPress (ev) { - if (ev.key === "Enter") { - this.handleEditEnd({ target: ev.target }); - } - } - - render () { - return ( -
  • -
    - { this.state.editing - ? - : this.props.text } -
    - - -
  • - ); + default: + return input.value; } } -class EditableList extends React.Component { - constructor (props) { - super(props); - this.state = { - items: new Set(this.props.data) - , addingNewItem: false - , rawView: false - , rawViewValue: "" - }; - - this.handleItemRemove = this.handleItemRemove.bind(this); - this.handleItemEdit = this.handleItemEdit.bind(this); - this.handleSwitchView = this.handleSwitchView.bind(this); - this.handleSaveRaw = this.handleSaveRaw.bind(this); - this.handleRawViewTextAreaChange = this.handleRawViewTextAreaChange.bind(this); - this.handleAddItem = this.handleAddItem.bind(this); - this.handleNewItemRemove = this.handleNewItemRemove.bind(this); - this.handleNewItemEdit = this.handleNewItemEdit.bind(this); - } - - handleItemRemove (item) { - this.setState(currentState => { - const newItems = new Set(currentState.items); - newItems.delete(item); - return { - items: newItems - }; - }, () => { - this.props.onListChange(Array.from(this.state.items)); - }); - } - - handleItemEdit (item, newValue) { - this.setState(currentState => ({ - items: new Set([...currentState.items] - .map(item_ => item_ === item ? newValue : item_)) - }), () => { - this.props.onListChange(Array.from(this.state.items)); - }); - } - - handleSwitchView () { - this.setState(currentState => { - if (currentState.rawView) { - return { - rawView: false - , rawViewValue: "" - }; - } - - return { - rawView: true - , rawViewValue: [...currentState.items.values()].join("\n") - }; - }); - } - - handleSaveRaw () { - this.setState(currentState => { - console.log(currentState.rawViewValue); - const newItems = currentState.rawViewValue.split("\n") - .filter(item => item !== ""); - - if ("itemPattern" in this.props) { - for (const item of newItems) { - if (!this.props.itemPattern.test(item)) { - this.rawViewTextArea.setCustomValidity( - this.props.itemPatternError(item)); - return; - } - } - - this.rawViewTextArea.setCustomValidity(""); - } - - return { - items: new Set(newItems) - }; - }, () => { - this.props.onListChange(Array.from(this.state.items)); - }); - } - - handleRawViewTextAreaChange (ev) { - if (this.rawViewTextArea.scrollHeight > this.rawViewTextArea.clientHeight) { - this.rawViewTextArea.style.height = `${this.rawViewTextArea.scrollHeight}px`; - } - - this.setState({ - rawViewValue: ev.target.value - }); - } - - handleAddItem () { - this.setState({ - addingNewItem: true - }); - } - - handleNewItemRemove () { - this.setState({ - addingNewItem: false - }); - } - - handleNewItemEdit (item, newItem) { - this.setState(currentState => ({ - items: [ ...currentState.items, newItem ] - , addingNewItem: false - }), () => { - this.props.onListChange(Array.from(this.state.items)); - }); - } - - render () { - const items = Array.from(this.state.items.values()); - - return ( -
    - - { this.state.rawView && - } -
    - { - this.state.rawView - ? ( ) - : ( ) - } -
    - ); - } -} - -class OptionsApp extends Component { +class App extends Component { constructor (props) { super(props); this.state = { - ...props.options + options: props.options , isFormValid: true }; @@ -278,8 +37,10 @@ class OptionsApp extends Component { this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleFormChange = this.handleFormChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this); - this.handleListChange = this.handleListChange.bind(this); - this.getItemPatternError = this.getItemPatternError.bind(this); + this.handleWhitelistChange = this.handleWhitelistChange.bind(this); + + this.getWhitelistItemPatternError + = this.getWhitelistItemPatternError.bind(this); } /** @@ -287,21 +48,13 @@ class OptionsApp extends Component { */ setStorage () { return browser.storage.sync.set({ - options: { - option_mediaEnabled: this.state.option_mediaEnabled - , option_localMediaEnabled: this.state.option_localMediaEnabled - , option_localMediaServerPort: this.state.option_localMediaServerPort - , option_uaWhitelistEnabled: this.state.option_uaWhitelistEnabled - , option_uaWhitelist: this.state.option_uaWhitelist - , option_mirroringEnabled: this.state.option_mirroringEnabled - , option_mirroringAppId: this.state.option_mirroringAppId - } + options: this.state.options }); } handleReset () { this.setState({ - ...defaultOptions + options: defaultOptions }); // TODO: Propagate state properly @@ -314,7 +67,8 @@ class OptionsApp extends Component { this.form.reportValidity(); try { - const { options: oldOptions } = await browser.storage.sync.get("options"); + const { options: oldOptions } + = await browser.storage.sync.get("options"); await this.setStorage(); const { options } = await browser.storage.sync.get("options"); @@ -341,31 +95,24 @@ class OptionsApp extends Component { }); } - handleInputChange (ev) { - const val = do { - if (ev.target.type === "checkbox") { - ev.target.checked; - } else if (ev.target.type === "number") { - parseInt(ev.target.value); - } else { - ev.target.value; - } - }; + const { target } = ev; - this.setState({ - [ ev.target.name ]: val + this.setState(({ options }) => { + options[target.name] = getInputValue(target); + return { options }; }); } - handleListChange (data) { - this.setState({ - option_uaWhitelist: data + handleWhitelistChange (whitelist) { + this.setState(({ options }) => { + options.userAgentWhitelist = whitelist; + return { options }; }); } - getItemPatternError (info) { - return _("options_option_uaWhitelistInvalidMatchPattern", info); + getWhitelistItemPatternError (info) { + return _("optionsUserAgentWhitelistInvalidMatchPattern", info); } render () { @@ -374,122 +121,122 @@ class OptionsApp extends Component { onSubmit={ this.handleFormSubmit } onChange={ this.handleFormChange }>
    - - { _("options_category_media") } + + { _("optionsMediaCategoryName") } -

    - { _("options_category_media_description") } +

    + { _("optionsMediaCategoryDescription") }

    - - { _("options_category_mirroring") } + + { _("optionsMirroringCategoryName") } -

    - { _("options_category_mirroring_description") } +

    + { _("optionsMirroringCategoryDescription") }

    - - { _("options_category_uaWhitelist") } + + { _("optionsUserAgentWhitelistCategoryName") } -

    - { _("options_category_uaWhitelist_description") } +

    + { _("optionsUserAgentWhitelistCategoryDescription") }

    @@ -500,6 +247,6 @@ class OptionsApp extends Component { browser.storage.sync.get("options").then(({options}) => { ReactDOM.render( - + , document.querySelector("#root")); }); diff --git a/ext/src/popup/index.js b/ext/src/popup/index.js index 5794e64..530d616 100755 --- a/ext/src/popup/index.js +++ b/ext/src/popup/index.js @@ -87,7 +87,9 @@ class App extends Component {
    Cast - @@ -152,12 +154,12 @@ class Receiver extends Component { disabled={this.props.isLoading}> { do { if (this.state.isLoading) { - _("popup_casting_button_label") + + _("popupCastingButtonLabel") + (this.state.isLoading ? this.state.ellipsis : "" ) } else { - _("popup_cast_button_label") + _("popupCastButtonLabel") } }} diff --git a/ext/webpack.config.js b/ext/webpack.config.js index d4312df..b9cc25b 100755 --- a/ext/webpack.config.js +++ b/ext/webpack.config.js @@ -56,6 +56,9 @@ module.exports = (env) => ({ rules: [ { test: /\.jsx?$/ + , resolve: { + extensions: [ ".js", ".jsx" ] + } , include: `${includePath}` , use: { loader: "babel-loader"