From 8ddb43324aa430e45c046336b627c4cc1e2fe834 Mon Sep 17 00:00:00 2001 From: Definitely Not Vlad Date: Wed, 23 Dec 2020 13:26:26 +0100 Subject: [PATCH] Release/0.13.10 (#104) * Bump decompress from 4.0.0 to 4.2.1 (#94) Bumps [decompress](https://github.com/kevva/decompress) from 4.0.0 to 4.2.1. - [Release notes](https://github.com/kevva/decompress/releases) - [Commits](https://github.com/kevva/decompress/compare/v4.0.0...v4.2.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * SEEXT-8959 Update cli settings page template * Update settings page templates * Switch to preinstall * Remove excess translation, adjust developer name source * Cleanup and add developer to context when creating page * Resolve translation and style issues * Fix dependencies and use PureComponent * Clean up imports * Fix package.json and use prepare instead of preinstall * Rename extension-page to shortcut-page in shortcut template * Version bump, switch to preinstall for testing * Replace Component with PureComponent * Feature/remove en json and add messaging and example (#103) * Add examples and remove en.json * Adjust messaging for page add * Add missing 'and' * Switch to prepare Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 +- src/cli/page/add.js | 13 +- src/commands/init.js | 13 +- src/services/extension-template.js | 5 +- src/services/page.js | 8 +- src/services/template.js | 7 +- src/templates/extension-js/template-init.js | 2 +- .../pages/{{pageDirectoryName}}/index.js | 1 - .../settings-page-html/template-init.js | 6 +- .../settings-page-react-bin/server/.babelrc | 8 -- .../bin/localization/LocalizationProvider.js | 108 ++++++++++++++ .../server/bin/localization/index.js | 1 + .../server/bin/main.js | 24 ++-- .../server/bin/page/Page.js | 1 - .../server/bin/page/PageProvider.js | 10 +- .../server/bin/page/connectPage.js | 9 +- .../server/bin/page/index.js | 11 +- .../server/bin/reducers.js | 4 +- .../SyncStateEngineProvider.jsx | 15 +- .../server/bin/webpack/devServer.js | 1 + .../server/bin/webpack/moduleRules.js | 133 ++++++++++-------- .../server/bin/webpack/optimizations.js | 35 +++++ .../server/bin/webpack/plugins.js | 81 ++--------- .../server/bin/webpack/webpack.config.js | 10 +- .../settings-page-react-bin/template-init.js | 90 +++++++----- .../{{pageDirectoryName}}/localization.js | 11 ++ .../pages/{{pageDirectoryName}}/style.scss | 18 +-- .../{{pageClassName}}.jsx | 26 ++-- .../server/translations/example.json | 12 ++ .../{{pageDirectoryName}}/localization.js | 11 ++ .../pages/{{pageDirectoryName}}/style.scss | 18 +-- .../{{pageClassName}}.jsx | 33 +++-- .../server/translations/example.json | 12 ++ .../settings-page-react/template-init.js | 6 +- src/templates/settings-page/template-init.js | 6 +- src/templates/shortcut/template-init.js | 2 +- 36 files changed, 462 insertions(+), 293 deletions(-) delete mode 100644 src/templates/settings-page-react-bin/server/.babelrc create mode 100644 src/templates/settings-page-react-bin/server/bin/localization/LocalizationProvider.js create mode 100644 src/templates/settings-page-react-bin/server/bin/localization/index.js create mode 100644 src/templates/settings-page-react-bin/server/bin/webpack/optimizations.js create mode 100644 src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/localization.js create mode 100644 src/templates/settings-page-react-extension/server/translations/example.json create mode 100644 src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/localization.js create mode 100644 src/templates/settings-page-react-shortcut/server/translations/example.json diff --git a/package.json b/package.json index c9a2d55..01c5551 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shoutem/cli", - "version": "0.13.9", + "version": "0.13.10", "description": "Command-line tools for Shoutem applications", "repository": { "type": "git", @@ -32,7 +32,7 @@ "colors": "1.1.2", "command-exists": "~1.2.4", "decamelize": "^1.2.0", - "decompress": "4.0.0", + "decompress": "4.2.1", "diff": "^3.3.1", "download-cached": "1.0.8", "download-file": "latest", diff --git a/src/cli/page/add.js b/src/cli/page/add.js index 2dc81b4..09a00be 100644 --- a/src/cli/page/add.js +++ b/src/cli/page/add.js @@ -1,8 +1,9 @@ +import { ensureUserIsLoggedIn } from '../../commands/login'; import { executeAndHandleError } from '../../services/error-handler'; -import {ensureInExtensionDir, loadExtensionJson} from '../../services/extension'; -import {askPageCreationQuestions} from "../../services/page"; -import {instantiateExtensionTemplate} from "../../services/extension-template"; -import {offerChanges} from "../../services/diff"; +import { ensureInExtensionDir, loadExtensionJson } from '../../services/extension'; +import { askPageCreationQuestions } from '../../services/page'; +import { instantiateExtensionTemplate } from '../../services/extension-template'; +import { offerChanges } from '../../services/diff'; export const description = 'Adds a settings page to the current extension.'; export const command = 'add [name]'; @@ -14,7 +15,9 @@ export const handler = args => executeAndHandleError(async () => { }); export async function createPage(opts, extensionPath) { - const changes = await instantiateExtensionTemplate('settings-page', { ...opts, extensionPath }); + const developer = await ensureUserIsLoggedIn(); + const changes = await instantiateExtensionTemplate('settings-page', { ...opts, extensionPath, developer }); await offerChanges(changes); console.log('Success'.green.bold); + console.log('Remember to create \'server/translations/en.json\' and add your translation strings to it.\nYou can use \'server/translations/example.json\' to check the format.'); } diff --git a/src/commands/init.js b/src/commands/init.js index a82df1b..4dabf97 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -1,17 +1,16 @@ import _ from 'lodash'; import inquirer from 'inquirer'; import decamelize from 'decamelize'; -import { pathExists } from 'fs-extra'; +import fs from 'fs-extra'; import path from 'path'; import semver from 'semver'; -import { ensureUserIsLoggedIn } from '../commands/login'; import msg from '../user_messages'; import { getPlatforms } from '../clients/extension-manager'; import * as utils from '../services/extension'; -import {instantiateExtensionTemplate} from "../services/extension-template"; -import {offerChanges} from "../services/diff"; -import {stringify} from "../services/data"; - +import { instantiateExtensionTemplate } from '../services/extension-template'; +import { offerChanges } from '../services/diff'; +import { stringify } from '../services/data'; +import { ensureUserIsLoggedIn } from './login'; function generateNoPatchSemver(version) { const [a, b] = version.split('.'); @@ -54,7 +53,7 @@ export async function initExtension(extName, extensionPath = process.cwd()) { utils.getExtensionCanonicalName(developer.name, extJson.name, extJson.version); const dirname = `${developer.name}.${extJson.name}`; - if (await pathExists(path.join(process.cwd(), dirname))) { + if (fs.existsSync(path.join(process.cwd(), dirname))) { throw new Error(`Folder ${dirname} already exists.`); } diff --git a/src/services/extension-template.js b/src/services/extension-template.js index 357ec00..b094eb5 100644 --- a/src/services/extension-template.js +++ b/src/services/extension-template.js @@ -1,5 +1,5 @@ -import {loadExtensionJson} from "./extension"; -import * as template from "./template"; +import { loadExtensionJson } from './extension'; +import * as template from './template'; export async function instantiateExtensionTemplate(localTemplatePath, context, opts) { if (!context.extJson && context.extensionPath) { @@ -11,5 +11,6 @@ export async function instantiateExtensionTemplate(localTemplatePath, context, o } await template.instantiateTemplatePath(localTemplatePath, context.extensionPath, context, opts); + return await template.instantiateTemplatePath('extension-js', context.extensionPath, context, opts); } diff --git a/src/services/page.js b/src/services/page.js index cfb2878..8326882 100644 --- a/src/services/page.js +++ b/src/services/page.js @@ -1,8 +1,8 @@ import _ from 'lodash'; -import decamelize from "decamelize"; -import { prompt } from "inquirer"; -import {isVariableName} from "./cli-parsing"; -import { askScreenCreationQuestions } from "./screen"; +import decamelize from 'decamelize'; +import { prompt } from 'inquirer'; +import { isVariableName } from './cli-parsing'; +import { askScreenCreationQuestions } from './screen'; function validatePageName(name, existingPages) { if (!isVariableName(name)) { diff --git a/src/services/template.js b/src/services/template.js index 2c46bbb..0619858 100644 --- a/src/services/template.js +++ b/src/services/template.js @@ -1,15 +1,15 @@ -import getOrSet from 'lodash-get-or-set'; import Promise from 'bluebird'; import fs from 'fs-extra'; -import path from 'path'; +import getOrSet from 'lodash-get-or-set'; import Mustache from 'mustache'; -import { pathExists } from 'fs-extra'; +import path from 'path'; const templatesDirectory = path.join(__dirname, '../..', 'src/templates'); export function load(pathWithSlashes, templateContext) { const p = path.join(templatesDirectory, ...pathWithSlashes.split('/')); const template = fs.readFileSync(p, 'utf8'); + return Mustache.render(template, templateContext); } @@ -28,6 +28,7 @@ async function instantiateTemplatePathRec(localTemplatePath, destinationPath, co await Promise.map(files, file => { const src = path.join(localTemplatePath, file); const dest = path.join(destinationPath, file); + return instantiateTemplatePathRec(src, dest, context, opts); }); } else if (templatePathState.isFile()) { diff --git a/src/templates/extension-js/template-init.js b/src/templates/extension-js/template-init.js index ec8c043..f86ef38 100644 --- a/src/templates/extension-js/template-init.js +++ b/src/templates/extension-js/template-init.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import decamelize from 'decamelize'; -import {isReactPage} from "../settings-page-react/template-init"; +import {isReactPage} from '../settings-page-react/template-init'; function importStatements(names, path, directoriesNames = names) { return names.map((name, i) => `import ${name} from '${path}/${directoriesNames[i]}';`).join('\n'); diff --git a/src/templates/settings-page-html-extension/server/pages/{{pageDirectoryName}}/index.js b/src/templates/settings-page-html-extension/server/pages/{{pageDirectoryName}}/index.js index 1e4c75f..27ad3aa 100644 --- a/src/templates/settings-page-html-extension/server/pages/{{pageDirectoryName}}/index.js +++ b/src/templates/settings-page-html-extension/server/pages/{{pageDirectoryName}}/index.js @@ -11,7 +11,6 @@ function onShoutemReady(event) { $(document).ready(function() { shoutem.api.init(config.context); onPageReady(config); - }); }; diff --git a/src/templates/settings-page-html/template-init.js b/src/templates/settings-page-html/template-init.js index 71aa371..4f12794 100644 --- a/src/templates/settings-page-html/template-init.js +++ b/src/templates/settings-page-html/template-init.js @@ -1,7 +1,7 @@ +import decamelize from 'decamelize'; import _ from 'lodash'; import getOrSet from 'lodash-get-or-set'; -import {instantiateExtensionTemplate} from "../../services/extension-template"; -import decamelize from 'decamelize'; +import { instantiateExtensionTemplate } from '../../services/extension-template'; function isHtmlPage({ type, path }) { return type === 'html' && !_.includes(path, 'server/build'); @@ -12,7 +12,7 @@ export async function before(context) { const pages = getOrSet(extJson, 'pages', []); if (!_.every(pages, isHtmlPage)) { - throw new Error("Html pages can't be mixed with non-html settings pages in the same extension"); + throw new Error('Html pages can\'t be mixed with non-html settings pages in the same extension'); } if (_.find(pages, { name })) { diff --git a/src/templates/settings-page-react-bin/server/.babelrc b/src/templates/settings-page-react-bin/server/.babelrc deleted file mode 100644 index b4e0857..0000000 --- a/src/templates/settings-page-react-bin/server/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": [ - ["es2015", { "modules": false }], - "react", - "stage-0" - ], - "plugins": ["transform-runtime"] -} diff --git a/src/templates/settings-page-react-bin/server/bin/localization/LocalizationProvider.js b/src/templates/settings-page-react-bin/server/bin/localization/LocalizationProvider.js new file mode 100644 index 0000000..c96dae2 --- /dev/null +++ b/src/templates/settings-page-react-bin/server/bin/localization/LocalizationProvider.js @@ -0,0 +1,108 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import i18next from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import _ from 'lodash'; +import { connect } from 'react-redux'; +import { LoaderContainer } from '@shoutem/react-web-ui'; +import translation from '../../translations/en.json'; + +export class LocalizationProvider extends PureComponent { + constructor(props) { + super(props); + + this.handleInjection = this.handleInjection.bind(this); + + this.state = { + inProgress: true, + }; + } + + componentDidMount() { + const { ownExtensionName, locale, translationUrl } = this.props; + const dictionary = _.get(translation, ownExtensionName); + + i18next.use(initReactI18next).init({ + lng: 'en', + fallbackLng: 'en', + ns: [ownExtensionName], + defaultNS: ownExtensionName, + nsSeparator: false, + keySeparator: false, + resources: { + en: { + [ownExtensionName]: dictionary, + }, + }, + }); + + this.handleInjection(locale, translationUrl); + } + + componentWillReceiveProps(nextProps) { + const { + locale: nextLocale, + translationUrl: nextTranslationUrl, + } = nextProps; + const { locale, translationUrl } = this.props; + + if (translationUrl !== nextTranslationUrl || nextLocale !== locale) { + this.handleInjection(nextLocale, nextTranslationUrl); + } + } + + async handleInjection(locale, translationUrl) { + const { ownExtensionName } = this.props; + + if (translationUrl) { + try { + const response = await fetch(translationUrl); + const translation = await response.json(); + const dictionary = _.get(translation, ownExtensionName); + + if (dictionary) { + await i18next.addResourceBundle(locale, ownExtensionName, dictionary); + await i18next.changeLanguage(locale); + } + } catch (error) { + // do nothing + } + } + + this.setState({ inProgress: false }); + } + + render() { + const { children } = this.props; + const { inProgress } = this.state; + + if (inProgress) { + return ; + } + + return children; + } +} + +LocalizationProvider.propTypes = { + children: PropTypes.node, + ownExtensionName: PropTypes.string, + locale: PropTypes.string, + translationUrl: PropTypes.string, +}; + +function mapStateToProps(state, ownProps) { + const { context } = ownProps; + + const ownExtensionName = _.get(context, 'ownExtensionName'); + const locale = _.get(context, 'i18n.locale'); + const translationUrl = _.get(context, 'i18n.translationUrl'); + + return { + ownExtensionName, + locale, + translationUrl, + }; +} + +export default connect(mapStateToProps)(LocalizationProvider); diff --git a/src/templates/settings-page-react-bin/server/bin/localization/index.js b/src/templates/settings-page-react-bin/server/bin/localization/index.js new file mode 100644 index 0000000..bfb3ecd --- /dev/null +++ b/src/templates/settings-page-react-bin/server/bin/localization/index.js @@ -0,0 +1 @@ +export { default as LocalizationProvider } from './LocalizationProvider'; diff --git a/src/templates/settings-page-react-bin/server/bin/main.js b/src/templates/settings-page-react-bin/server/bin/main.js index f811017..7010265 100644 --- a/src/templates/settings-page-react-bin/server/bin/main.js +++ b/src/templates/settings-page-react-bin/server/bin/main.js @@ -1,8 +1,7 @@ -require('es6-promise').polyfill(); import 'fetch-everywhere'; - import '@shoutem/react-web-ui/lib/styles/index.scss'; import '@shoutem/extension-sandbox'; + import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; @@ -14,8 +13,10 @@ import { RioStateSerializer } from '@shoutem/redux-io'; import { SyncStateEngine } from '@shoutem/redux-sync-state-engine'; import * as extension from '../src/index'; import { PageProvider, connectPage, Page } from './page'; +import { LocalizationProvider } from './localization'; import { SyncStateEngineProvider } from './syncStateEngine'; import configureStore from './configureStore'; +require('es6-promise').polyfill(); const uri = new URI(window.location.href); const pageName = _.get(uri.search(true), 'page', ''); @@ -24,13 +25,12 @@ const rioStateSerializer = new RioStateSerializer(); function renderPage() { if (!PageComponent) { - return ( -
Page not found: {pageName}
- ); + return
Page not found: {pageName}
; } const ConnectedPageComponent = connectPage()(PageComponent); - return (); + + return ; } // handler for Shoutem initialization finished @@ -60,12 +60,12 @@ function onShoutemReady(event) { ReactDOM.render( - - {renderPage()} - + + {renderPage()} + , - document.getElementById('root') + document.getElementById('root'), ); } @@ -75,7 +75,5 @@ document.addEventListener('shoutemready', onShoutemReady, false); // Render it to DOM ReactDOM.render( , - document.getElementById('root') + document.getElementById('root'), ); - - diff --git a/src/templates/settings-page-react-bin/server/bin/page/Page.js b/src/templates/settings-page-react-bin/server/bin/page/Page.js index 3522922..8ce7fbe 100644 --- a/src/templates/settings-page-react-bin/server/bin/page/Page.js +++ b/src/templates/settings-page-react-bin/server/bin/page/Page.js @@ -2,7 +2,6 @@ import { ext } from '../../src/const'; export default class Page { constructor(context, parameters) { - this.pageContext = { ownExtensionName: ext(), ...context, diff --git a/src/templates/settings-page-react-bin/server/bin/page/PageProvider.js b/src/templates/settings-page-react-bin/server/bin/page/PageProvider.js index cb2df05..ddfc36e 100644 --- a/src/templates/settings-page-react-bin/server/bin/page/PageProvider.js +++ b/src/templates/settings-page-react-bin/server/bin/page/PageProvider.js @@ -1,8 +1,10 @@ -import React, { PropTypes, Component, Children } from 'react'; +import { PureComponent, Children } from 'react'; +import PropTypes from 'prop-types'; -export default class PageProvider extends Component { +export default class PageProvider extends PureComponent { getChildContext() { const { page } = this.props; + return { page }; } @@ -14,10 +16,10 @@ export default class PageProvider extends Component { } PageProvider.propTypes = { - page: React.PropTypes.object, + page: PropTypes.object, children: PropTypes.node, }; PageProvider.childContextTypes = { - page: React.PropTypes.object, + page: PropTypes.object, }; diff --git a/src/templates/settings-page-react-bin/server/bin/page/connectPage.js b/src/templates/settings-page-react-bin/server/bin/page/connectPage.js index fd8da86..68dd716 100644 --- a/src/templates/settings-page-react-bin/server/bin/page/connectPage.js +++ b/src/templates/settings-page-react-bin/server/bin/page/connectPage.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import _ from 'lodash'; import { connect } from 'react-redux'; import { getShortcut, getExtension } from '@shoutem/redux-api-sdk'; @@ -8,6 +9,7 @@ export function connectPageContext(WrappedComponent) { const { page } = context; const pageProps = _.pick(page.getPageContext(), [ 'appId', + 'appOwnerId', 'extensionName', 'ownExtensionName', 'shortcutId', @@ -16,11 +18,11 @@ export function connectPageContext(WrappedComponent) { const parameters = page.getParameters(); - return (); + return ; } PageProvider.contextTypes = { - page: React.PropTypes.object, + page: PropTypes.object, }; return PageProvider; @@ -37,5 +39,6 @@ function mapStateToProps(state, ownProps) { } export default function connectPage() { - return wrappedComponent => connectPageContext(connect(mapStateToProps)(wrappedComponent)); + return wrappedComponent => + connectPageContext(connect(mapStateToProps)(wrappedComponent)); } diff --git a/src/templates/settings-page-react-bin/server/bin/page/index.js b/src/templates/settings-page-react-bin/server/bin/page/index.js index d498e83..f87f955 100644 --- a/src/templates/settings-page-react-bin/server/bin/page/index.js +++ b/src/templates/settings-page-react-bin/server/bin/page/index.js @@ -1,8 +1,3 @@ -import connectPage from './connectPage'; -export { connectPage }; - -import Page from './Page'; -export { Page }; - -import PageProvider from './PageProvider'; -export { PageProvider }; +export { default as connectPage } from './connectPage'; +export { default as Page } from './Page'; +export { default as PageProvider } from './PageProvider'; diff --git a/src/templates/settings-page-react-bin/server/bin/reducers.js b/src/templates/settings-page-react-bin/server/bin/reducers.js index 6cbf294..10649ae 100644 --- a/src/templates/settings-page-react-bin/server/bin/reducers.js +++ b/src/templates/settings-page-react-bin/server/bin/reducers.js @@ -1,7 +1,5 @@ import { combineReducers } from 'redux'; -import { - reducer as coreReducer, -} from '@shoutem/redux-api-sdk'; +import { reducer as coreReducer } from '@shoutem/redux-api-sdk'; export function createRootReducer(extensionName, reducer) { return combineReducers({ diff --git a/src/templates/settings-page-react-bin/server/bin/syncStateEngine/SyncStateEngineProvider.jsx b/src/templates/settings-page-react-bin/server/bin/syncStateEngine/SyncStateEngineProvider.jsx index 0092d08..c1fc640 100644 --- a/src/templates/settings-page-react-bin/server/bin/syncStateEngine/SyncStateEngineProvider.jsx +++ b/src/templates/settings-page-react-bin/server/bin/syncStateEngine/SyncStateEngineProvider.jsx @@ -1,10 +1,11 @@ -import React, { PropTypes, Component, Children } from 'react'; +import { Children, PureComponent } from 'react'; +import PropTypes from 'prop-types'; import _ from 'lodash'; import { connect } from 'react-redux'; import sandbox from '@shoutem/extension-sandbox'; import { ext } from '../../src/const'; -export class SyncStateEngineProvider extends Component { +export class SyncStateEngineProvider extends PureComponent { constructor(props) { super(props); @@ -72,16 +73,14 @@ export class SyncStateEngineProvider extends Component { } SyncStateEngineProvider.propTypes = { - state: React.PropTypes.object, - syncStateEngine: React.PropTypes.object, - syncAction: React.PropTypes.func, + state: PropTypes.object, + syncStateEngine: PropTypes.object, + syncAction: PropTypes.func, children: PropTypes.node, }; function mapStateToProps(state) { - return { - state, - }; + return { state }; } function mapDispatchToProps(dispatch) { diff --git a/src/templates/settings-page-react-bin/server/bin/webpack/devServer.js b/src/templates/settings-page-react-bin/server/bin/webpack/devServer.js index a7361c0..4438031 100644 --- a/src/templates/settings-page-react-bin/server/bin/webpack/devServer.js +++ b/src/templates/settings-page-react-bin/server/bin/webpack/devServer.js @@ -12,6 +12,7 @@ function resolveDevServer() { port: 4790, compress: isProduction, inline: !isProduction, + disableHostCheck: true, hot: !isProduction, host: '0.0.0.0', https: true, diff --git a/src/templates/settings-page-react-bin/server/bin/webpack/moduleRules.js b/src/templates/settings-page-react-bin/server/bin/webpack/moduleRules.js index e9c4780..74bfbbb 100644 --- a/src/templates/settings-page-react-bin/server/bin/webpack/moduleRules.js +++ b/src/templates/settings-page-react-bin/server/bin/webpack/moduleRules.js @@ -1,48 +1,60 @@ -const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const isProduction = require('./env'); function resolveModuleRules() { const jsRule = { test: /\.(js|jsx)$/, - exclude: /node_modules/, - use: [ - 'babel-loader', - ], + use: { loader: 'babel-loader' }, }; - const styleProductionRule = { - test: /\.scss$/, - use: ExtractTextPlugin.extract({ - use: [{ - loader: 'css-loader', - }, { - loader: 'postcss-loader', - }, { - loader: 'sass-loader', - }], - fallback: 'style-loader', - }), - }; - - const styleDevelopmentRule = { - test: /\.scss$/, - use: [ - 'style-loader', - // Using source maps breaks urls in the CSS loader - // https://github.com/webpack/css-loader/issues/232 - // This comment solves it, but breaks testing from a local network - // https://github.com/webpack/css-loader/issues/232#issuecomment-240449998 - // 'css-loader?sourceMap', - 'css-loader', - 'postcss-loader', - 'sass-loader?sourceMap', - ], - }; + const styleRules = [ + { + test: /\.css$/, + use: [ + !isProduction ? 'style-loader' : MiniCssExtractPlugin.loader, + { loader: 'css-loader' }, + ], + }, + { + test: /\.scss$/, + use: [ + !isProduction ? 'style-loader' : MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + sourceMap: !isProduction, + }, + }, + { + loader: 'postcss-loader', + options: { + plugins: [require('cssnano')()], + sourceMap: !isProduction, + }, + }, + { + loader: 'sass-loader', + options: { + sourceMap: !isProduction, + }, + }, + ], + }, + ]; - const imgRule = { - test: /\.(png|gif|jpg|svg)$/, - use: 'url-loader?limit=8192', - }; + const imgRules = [ + { + test: /\.(png|gif|jpg|svg)$/, + use: [ + { + loader: 'url-loader', + options: { + limit: 10000, + }, + }, + ], + }, + ]; const fontRules = [ { @@ -50,7 +62,11 @@ function resolveModuleRules() { use: [ { loader: 'url-loader', - query: 'prefix=fonts/&name=fonts/[name].[ext]&limit=10000&mimetype=application/font-woff', + options: { + name: '[path][name].[ext]', + mimetype: 'application/font-woff', + limit: 10000, + }, }, ], }, @@ -59,8 +75,11 @@ function resolveModuleRules() { use: [ { loader: 'url-loader', - query: - 'prefix=fonts/&name=fonts/[name].[ext]&limit=10000&mimetype=application/font-woff2', + options: { + name: '[path][name].[ext]', + mimetype: 'application/font-woff2', + limit: 10000, + }, }, ], }, @@ -69,7 +88,11 @@ function resolveModuleRules() { use: [ { loader: 'file-loader', - query: 'prefix=fonts/&name=fonts/[name].[ext]&limit=10000&mimetype=font/opentype', + options: { + name: '[path][name].[ext]', + mimetype: 'font/opentype', + limit: 10000, + }, }, ], }, @@ -78,7 +101,11 @@ function resolveModuleRules() { use: [ { loader: 'url-loader', - query: 'prefix=fonts/&name=fonts/[name].[ext]&limit=10000&mimetype=application/octet-stream', + options: { + name: '[path][name].[ext]', + mimetype: 'application/octet-stream', + limit: 10000, + }, }, ], }, @@ -87,27 +114,15 @@ function resolveModuleRules() { use: [ { loader: 'file-loader', - query: 'prefix=fonts/&name=fonts/[name].[ext]', + options: { + name: '[path][name].[ext]', + }, }, ], }, ]; - if (isProduction) { - return [ - jsRule, - styleProductionRule, - ...fontRules, - imgRule, - ]; - }; - - return [ - jsRule, - styleDevelopmentRule, - ...fontRules, - imgRule, - ]; + return [jsRule, ...styleRules, ...fontRules, ...imgRules]; } -exports = module.exports = resolveModuleRules; +module.exports = resolveModuleRules; diff --git a/src/templates/settings-page-react-bin/server/bin/webpack/optimizations.js b/src/templates/settings-page-react-bin/server/bin/webpack/optimizations.js new file mode 100644 index 0000000..e913eec --- /dev/null +++ b/src/templates/settings-page-react-bin/server/bin/webpack/optimizations.js @@ -0,0 +1,35 @@ +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); + +function resolveOptimizations() { + return { + splitChunks: { + cacheGroups: { + styles: { + name: 'app', + test: /\.css$/, + chunks: 'all', + enforce: true, + }, + vendors: { + name: 'vendor', + test: /[\\/]node_modules[\\/]/, + chunks: 'all', + enforce: true, + }, + }, + }, + minimizer: [ + new OptimizeCSSAssetsPlugin({}), + new TerserPlugin({ + terserOptions: { + output: { + comments: false, + }, + }, + }), + ], + }; +} + +module.exports = resolveOptimizations; diff --git a/src/templates/settings-page-react-bin/server/bin/webpack/plugins.js b/src/templates/settings-page-react-bin/server/bin/webpack/plugins.js index d073bbd..62cf800 100644 --- a/src/templates/settings-page-react-bin/server/bin/webpack/plugins.js +++ b/src/templates/settings-page-react-bin/server/bin/webpack/plugins.js @@ -1,18 +1,12 @@ const webpack = require('webpack'); -const cssnano = require('cssnano'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -const DashboardPlugin = require('webpack-dashboard/plugin'); const isProduction = require('./env'); function resolvePlugins() { - const commonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - minChunks: (module) => ( - // this assumes your vendor imports exist in the node_modules directory - module.context && module.context.indexOf('node_modules') !== -1 - ), - filename: 'vendor.[hash].js', + const miniCssExtractPlugin = new MiniCssExtractPlugin({ + filename: '[name].css?q=[contenthash]', + allChunks: true, }); const htmlWebpackPlugin = new HtmlWebpackPlugin({ @@ -21,55 +15,7 @@ function resolvePlugins() { filename: 'index.html', }); - const loaderOptionsPlugin = new webpack.LoaderOptionsPlugin({ - options: { - postcss: [ - cssnano({ - autoprefixer: { - add: true, - remove: true, - browsers: ['last 2 versions'], - }, - discardComments: { - removeAll: true, - }, - safe: true, - sourcemap: true, - }), - ], - context: '/', - }, - }); - - const minimizeLoaderOptionsPlugin = new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false, - }); - - const extractTextPlugin = new ExtractTextPlugin({ - filename: '[name].[hash].css', - allChunks: true, - }); - - const uglifyJsPlugin = new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - screw_ie8: true, - conditionals: true, - unused: true, - comparisons: true, - sequences: true, - dead_code: true, - evaluate: true, - if_return: true, - join_vars: true, - }, - output: { - comments: false, - }, - }); - - const dashboardPlugin = new DashboardPlugin(); + const occurrenceOrderPlugin = new webpack.optimize.OccurrenceOrderPlugin(); const hotModuleReplacementPlugin = new webpack.HotModuleReplacementPlugin(); @@ -82,22 +28,13 @@ function resolvePlugins() { if (isProduction) { return [ nodeEnv, - commonsChunkPlugin, htmlWebpackPlugin, - loaderOptionsPlugin, - minimizeLoaderOptionsPlugin, - uglifyJsPlugin, - extractTextPlugin, + occurrenceOrderPlugin, + miniCssExtractPlugin, ]; } - return [ - commonsChunkPlugin, - htmlWebpackPlugin, - loaderOptionsPlugin, - hotModuleReplacementPlugin, - dashboardPlugin, - ]; + return [htmlWebpackPlugin, hotModuleReplacementPlugin, occurrenceOrderPlugin]; } -exports = module.exports = resolvePlugins; +module.exports = resolvePlugins; diff --git a/src/templates/settings-page-react-bin/server/bin/webpack/webpack.config.js b/src/templates/settings-page-react-bin/server/bin/webpack/webpack.config.js index 9fa5986..4528cee 100644 --- a/src/templates/settings-page-react-bin/server/bin/webpack/webpack.config.js +++ b/src/templates/settings-page-react-bin/server/bin/webpack/webpack.config.js @@ -2,9 +2,11 @@ const path = require('path'); const resolvePlugins = require('./plugins'); const resolveModuleRules = require('./moduleRules'); const resolveDevServer = require('./devServer'); +const resolveOptimizations = require('./optimizations'); const isProduction = require('./env'); module.exports = { + mode: isProduction ? 'production' : 'development', devtool: isProduction ? 'false' : '#source-maps', context: path.join(__dirname, '../../'), entry: { @@ -19,12 +21,10 @@ module.exports = { rules: resolveModuleRules(), }, plugins: resolvePlugins(), + optimization: resolveOptimizations(), resolve: { - modules: [ - path.join(__dirname, '../..'), - 'node_modules', - ], - extensions: ['.js', '.jsx', '.json', '.css', '.sass', '.scss', '.html'] + modules: [path.join(__dirname, '../..'), 'node_modules'], + extensions: ['.js', '.jsx', '.json', '.css', '.sass', '.scss', '.html'], }, devServer: resolveDevServer(), }; diff --git a/src/templates/settings-page-react-bin/template-init.js b/src/templates/settings-page-react-bin/template-init.js index bd6c817..c07326e 100644 --- a/src/templates/settings-page-react-bin/template-init.js +++ b/src/templates/settings-page-react-bin/template-init.js @@ -10,59 +10,85 @@ import { const pkgJsonTemplate = { "scripts": { + "lint": "eslint --no-eslintrc -c .eslintrc src/**/*.{js,jsx}", "clean": "rimraf ./build/*", "build": "npm run clean && cross-env NODE_ENV=production webpack --config ./bin/webpack/webpack.config.js", - "dev-dashboard": "webpack-dashboard -c -- webpack-dev-server --config ./bin/webpack/webpack.config.js", "dev": "webpack-dev-server --config ./bin/webpack/webpack.config.js" }, "devDependencies": { - "babel-cli": "^6.24.0", - "babel-core": "^6.24.0", - "babel-loader": "^6.4.1", - "babel-plugin-transform-react-jsx": "^6.23.0", - "babel-preset-es2015": "^6.24.0", - "babel-preset-react": "^6.23.0", - "babel-preset-stage-0": "^6.22.0", + "@babel/core": "^7.8.4", + "@babel/plugin-proposal-class-properties": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.8.3", + "@babel/preset-env": "^7.8.4", + "@babel/preset-react": "^7.8.3", + "@shoutem/eslint-config-react": "^1.0.1", + "babel-loader": "^8.0.6", + "babel-eslint": "^10.1.0", "cross-env": "^4.0.0", - "css-loader": "^0.27.3", - "cssnano": "^3.10.0", - "extract-text-webpack-plugin": "^2.1.0", - "file-loader": "^0.10.1", - "html-webpack-plugin": "^2.28.0", - "node-sass": "^4.5.0", - "postcss-loader": "^1.3.3", - "rimraf": "^2.6.1", + "css-loader": "^3.4.2", + "cssnano": "^4.1.10", + "eslint": "^6.8.0", + "eslint-loader": "^3.0.3", + "eslint-plugin-babel": "^5.3.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^1.3.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^5.1.1", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.0.3", + "mini-css-extract-plugin": "0.9.0", + "optimize-css-assets-webpack-plugin": "5.0.3", + "terser-webpack-plugin": "2.3.5", + "node-sass": "^4.13.1", + "path": "^0.12.7", + "postcss-loader": "^3.0.0", + "prettier": "^1.19.1", + "rimraf": "^3.0.2", "sass-loader": "^6.0.3", - "style-loader": "^0.14.1", - "url-loader": "^0.5.8", - "webpack": "^2.2.1", - "webpack-dashboard": "^0.3.0", - "webpack-dev-server": "^2.4.2" + "style-loader": "^1.1.3", + "url-loader": "^3.0.0", + "webpack": "^4.41.6", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3" }, "dependencies": { "@shoutem/extension-sandbox": "^0.1.4", - "@shoutem/react-web-ui": "^0.5.1", - "@shoutem/redux-api-sdk": "^1.1.0", - "@shoutem/redux-composers": "^0.1.5", - "@shoutem/redux-io": "^2.3.0", + "@shoutem/react-web-ui": "0.12.4", + "@shoutem/redux-api-sdk": "^2.0.0", + "@shoutem/redux-composers": "^0.1.6", + "@shoutem/redux-io": "^3.2.1", "@shoutem/redux-sync-state-engine": "^0.0.2", + "auto-bind": "^4.0.0", "es6-promise": "^4.1.1", "fetch-everywhere": "^1.0.5", "lodash": "^4.17.4", - "react": "^15.4.2", - "react-dom": "^15.4.2", + "moment": "^2.16.0", + "normalize-url": "^1.6.0", + "prop-types": "^15.7.2", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "i18next": "^19.7.0", + "react-i18next": "^11.7.3", "react-redux": "^5.0.3", + "react-select": "^1.0.0-rc.5", "redux": "^3.6.0", + "redux-form": "^5.2.5", "redux-thunk": "^2.2.0", - "urijs": "^1.18.9" + "reselect": "^2.5.4", + "urijs": "^1.18.9", + "validator": "^6.2.1" }, "babel": { "presets": [ - ["es2015", { "modules": false }], - "react", - "stage-0" + "@babel/preset-env", + "@babel/preset-react" ], - "plugins": ["transform-runtime"] + "plugins": [ + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-modules-commonjs" + ] } }; diff --git a/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/localization.js b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/localization.js new file mode 100644 index 0000000..2d0a3ab --- /dev/null +++ b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/localization.js @@ -0,0 +1,11 @@ +const key = 'extension-page'; + +const SAVE_BUTTON = `${key}.save-button`; +const ENTER_COMPANY_NAME = `${key}.enter-company-name`; +const COMPANY = `${key}.company`; + +export default { + SAVE_BUTTON, + ENTER_COMPANY_NAME, + COMPANY, +}; diff --git a/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/style.scss b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/style.scss index 9e1c0ed..c640fa6 100644 --- a/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/style.scss +++ b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/style.scss @@ -1,12 +1,14 @@ -:global { - body.extension-sandbox-container { - .settings-page { - max-width: 552px; - margin: auto; +body.extension-sandbox-container { + .settings-page { + max-width: 552px; + margin: auto; - &.is-wide { - max-width: 808px; - } + &.is-wide { + max-width: 808px; } } } + +.save-button { + padding-top: 20px; +} diff --git a/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx index 7e22828..c6e548d 100644 --- a/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx +++ b/src/templates/settings-page-react-extension/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx @@ -1,5 +1,8 @@ -import React, { Component, PropTypes } from 'react'; +import React, { PureComponent } from 'react'; +import autoBindReact from 'auto-bind/react'; +import i18next from 'i18next'; import _ from 'lodash'; +import PropTypes from 'prop-types'; import { Button, ButtonToolbar, @@ -8,17 +11,18 @@ import { FormGroup, HelpBlock, } from 'react-bootstrap'; +import { connect } from 'react-redux'; import { LoaderContainer } from '@shoutem/react-web-ui'; import { fetchExtension, - updateExtensionSettings, getExtension, + updateExtensionSettings, } from '@shoutem/redux-api-sdk'; import { shouldRefresh } from '@shoutem/redux-io'; -import { connect } from 'react-redux'; +import LOCALIZATION from './localization'; import './style.scss'; -class {{pageClassName}} extends Component { +class {{pageClassName}} extends PureComponent { static propTypes = { extension: PropTypes.object, fetchExtension: PropTypes.func, @@ -28,9 +32,7 @@ class {{pageClassName}} extends Component { constructor(props) { super(props); - this.handleTextChange = this.handleTextChange.bind(this); - this.handleSave = this.handleSave.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + autoBindReact(this); props.fetchExtension(); @@ -87,11 +89,11 @@ class {{pageClassName}} extends Component { const { error, hasChanges, inProgress, company } = this.state; return ( -
+
-

Enter company name

- Company: +

{i18next.t(LOCALIZATION.ENTER_COMPANY_NAME)}

+ {i18next.t(LOCALIZATION.COMPANY)} {error} } - + diff --git a/src/templates/settings-page-react-extension/server/translations/example.json b/src/templates/settings-page-react-extension/server/translations/example.json new file mode 100644 index 0000000..6bbf437 --- /dev/null +++ b/src/templates/settings-page-react-extension/server/translations/example.json @@ -0,0 +1,12 @@ +{ + "{{developer.name}}": { + "{{extJson.name}}": { + "extension-page.save-button": "Save", + "extension-page.enter-company-name": "Enter company name", + "extension-page.company": "Company:", + "shortcut-page.save-button": "Save", + "shortcut-page.choose-your-greeting": "Choose your greeting", + "shortcut-page.name": "Name:" + } + } +} diff --git a/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/localization.js b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/localization.js new file mode 100644 index 0000000..9845e40 --- /dev/null +++ b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/localization.js @@ -0,0 +1,11 @@ +const key = 'shortcut-page'; + +const SAVE_BUTTON = `${key}.save-button`; +const CHOOSE_YOUR_GREETING = `${key}.choose-your-greeting`; +const NAME = `${key}.name`; + +export default { + SAVE_BUTTON, + CHOOSE_YOUR_GREETING, + NAME, +}; diff --git a/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/style.scss b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/style.scss index 9e1c0ed..c640fa6 100644 --- a/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/style.scss +++ b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/style.scss @@ -1,12 +1,14 @@ -:global { - body.extension-sandbox-container { - .settings-page { - max-width: 552px; - margin: auto; +body.extension-sandbox-container { + .settings-page { + max-width: 552px; + margin: auto; - &.is-wide { - max-width: 808px; - } + &.is-wide { + max-width: 808px; } } } + +.save-button { + padding-top: 20px; +} diff --git a/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx index 5732da8..a1d27f0 100644 --- a/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx +++ b/src/templates/settings-page-react-shortcut/server/src/pages/{{pageDirectoryName}}/{{pageClassName}}.jsx @@ -1,5 +1,8 @@ -import React, { Component, PropTypes } from 'react'; +import React, { PureComponent } from 'react'; +import autoBindReact from 'auto-bind/react'; +import i18next from 'i18next'; import _ from 'lodash'; +import PropTypes from 'prop-types'; import { Button, ButtonToolbar, @@ -8,12 +11,13 @@ import { FormGroup, HelpBlock, } from 'react-bootstrap'; +import { connect } from 'react-redux'; import { LoaderContainer } from '@shoutem/react-web-ui'; import { updateShortcutSettings } from '@shoutem/redux-api-sdk'; -import { connect } from 'react-redux'; +import LOCALIZATION from './localization'; import './style.scss'; -class {{pageClassName}} extends Component { +class {{pageClassName}} extends PureComponent { static propTypes = { shortcut: PropTypes.object, updateShortcutSettings: PropTypes.func, @@ -22,9 +26,7 @@ class {{pageClassName}} extends Component { constructor(props) { super(props); - this.handleTextChange = this.handleTextChange.bind(this); - this.handleSave = this.handleSave.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + autoBindReact(this); this.state = { error: null, @@ -65,20 +67,21 @@ class {{pageClassName}} extends Component { this.props.updateShortcutSettings(shortcut, { greeting }) .then(() => ( this.setState({ hasChanges: false, inProgress: false }) - )).catch((err) => { - this.setState({ error: err, inProgress: false }); - }); + )) + .catch((err) => { + this.setState({ error: err, inProgress: false }); + }); } render() { const { error, hasChanges, inProgress, greeting } = this.state; return ( -
+
-

Choose your greeting

- Name: +

{i18next.t(LOCALIZATION.CHOOSE_YOUR_GREETING)}

+ {i18next.t(LOCALIZATION.NAME)}
{error && - {error} + {error} }
- + diff --git a/src/templates/settings-page-react-shortcut/server/translations/example.json b/src/templates/settings-page-react-shortcut/server/translations/example.json new file mode 100644 index 0000000..6bbf437 --- /dev/null +++ b/src/templates/settings-page-react-shortcut/server/translations/example.json @@ -0,0 +1,12 @@ +{ + "{{developer.name}}": { + "{{extJson.name}}": { + "extension-page.save-button": "Save", + "extension-page.enter-company-name": "Enter company name", + "extension-page.company": "Company:", + "shortcut-page.save-button": "Save", + "shortcut-page.choose-your-greeting": "Choose your greeting", + "shortcut-page.name": "Name:" + } + } +} diff --git a/src/templates/settings-page-react/template-init.js b/src/templates/settings-page-react/template-init.js index 3c788d2..707e108 100644 --- a/src/templates/settings-page-react/template-init.js +++ b/src/templates/settings-page-react/template-init.js @@ -1,8 +1,8 @@ +import decamelize from 'decamelize'; import _ from 'lodash'; import getOrSet from 'lodash-get-or-set'; -import decamelize from "decamelize"; import pascalize from 'uppercamelcase'; -import {instantiateExtensionTemplate} from "../../services/extension-template"; +import { instantiateExtensionTemplate } from '../../services/extension-template'; export function isReactPage({ type, path }) { return type === 'react-page' || _.includes(path, 'server/build'); @@ -18,7 +18,7 @@ export async function before(context) { } if (!_.every(pages, isReactPage)) { - throw new Error("React pages can't be mixed with non-react settings pages in the same extension"); + throw new Error('React pages can\'t be mixed with non-react settings pages in the same extension'); } pages.push({ name, type: 'react-page' }); diff --git a/src/templates/settings-page/template-init.js b/src/templates/settings-page/template-init.js index 5786288..267f6bd 100644 --- a/src/templates/settings-page/template-init.js +++ b/src/templates/settings-page/template-init.js @@ -1,9 +1,10 @@ import getOrSet from 'lodash-get-or-set'; -import {instantiateExtensionTemplate} from "../../services/extension-template"; -import {linkSettingsPageWithExistingScreen} from "../../services/shortcut"; +import { instantiateExtensionTemplate } from '../../services/extension-template'; +import { linkSettingsPageWithExistingScreen } from '../../services/shortcut'; export async function after(context) { const { type, extensionScope, extJson, existingScreenName, newScreen, name, title } = context; + if (type === 'react') { await instantiateExtensionTemplate('settings-page-react', context) } else if (type === 'html') { @@ -27,6 +28,7 @@ export async function after(context) { if (newScreen) { await instantiateExtensionTemplate('screen', { ...context, ...newScreen }); + if (newScreen.newShortcut) { linkSettingsPageWithExistingScreen(extJson, context, newScreen.name); } diff --git a/src/templates/shortcut/template-init.js b/src/templates/shortcut/template-init.js index ce3825a..b39d1f1 100644 --- a/src/templates/shortcut/template-init.js +++ b/src/templates/shortcut/template-init.js @@ -1,4 +1,4 @@ -import {addShortcut} from "../../services/shortcut"; +import {addShortcut} from '../../services/shortcut'; export async function before(context) { addShortcut(context.extJson, context);