Commit 40e3db9b by Jason Zhou

v1.1.0

parent 86203129
node_modules/ node_modules/
packages/backups/*
yarn-error.log yarn-error.log
.DS_Store .DS_Store
\ No newline at end of file
!.gitkeep
\ No newline at end of file
...@@ -16,9 +16,7 @@ $ npm i -g git+https://cd.i.strikingly.com/jason.zhou/wildcat.git ...@@ -16,9 +16,7 @@ $ npm i -g git+https://cd.i.strikingly.com/jason.zhou/wildcat.git
``` ```
wildcat run wildcat run
``` ```
This command will backup your current config and code files and do the upgrade. The command that does the trick.
> NOTE: Make sure your current config is the old version. It will not do the judement.
![wildcat run](assets/run-example.png) ![wildcat run](assets/run-example.png)
1. restore 1. restore
``` ```
......
...@@ -6,11 +6,11 @@ const { spawn } = require('child_process') ...@@ -6,11 +6,11 @@ const { spawn } = require('child_process')
const _ = require('lodash') const _ = require('lodash')
const { const {
updateConfigFiles, updateConfigs,
updateLegacyCodes, updateCodes,
upgradePackages, updatePackages,
preserveOldFiles, restoreConfigs,
restoreOldFiles restoreLegacyCodes
} = require('../packages/actions') } = require('../packages/actions')
const BANNER = ` const BANNER = `
...@@ -31,7 +31,7 @@ const BANNER = ` ...@@ -31,7 +31,7 @@ const BANNER = `
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
` `
const VERSION = '0.1.0' const VERSION = '1.1.0'
const run = async () => { const run = async () => {
console.info(BANNER) console.info(BANNER)
...@@ -56,16 +56,24 @@ const run = async () => { ...@@ -56,16 +56,24 @@ const run = async () => {
// add extra \n for good looking // add extra \n for good looking
console.log('\n') console.log('\n')
log.title('📥 Preserve current configs')
preserveOldFiles()
log.title('📝 Update config files') log.title('📝 Update config files')
updateConfigFiles(mode) updateConfigs(mode)
log.title('💯 Update legacy codes') log.title('💯 Update legacy codes')
updateLegacyCodes() updateCodes()
log.title('🐣 Upgrade pacakages') const { willUpdatePackages } = await inquirer.prompt([
upgradePackages() {
name: 'will-update-packages',
type: 'confirm',
default: true,
message: 'Would you like update packages? It is OK to skip if already updated.'
}
])
if (willUpdatePackages) {
log.title('🐣 Update pacakages')
updatePackages()
}
log.title('🐥 Everything set! Run yarn dev') log.title('🐥 Everything set! Run yarn dev now')
// since shelljs.exec doesn't work with inqurier.js // since shelljs.exec doesn't work with inqurier.js
// use spawn instead // use spawn instead
spawn('yarn', ['dev'], { spawn('yarn', ['dev'], {
...@@ -82,10 +90,11 @@ program ...@@ -82,10 +90,11 @@ program
program program
.command('restore') .command('restore')
.description( .description('Restore the previous configs and codes.')
'Restore the previous configs, make sure you have execed wildcat run command or it may go wrong.' .action(() => {
) restoreConfigs()
.action(restoreOldFiles) restoreLegacyCodes()
})
program.parse(process.argv) program.parse(process.argv)
......
{ {
"name": "wildcat", "name": "wildcat",
"version": "1.0.0", "version": "1.1.0",
"description": "a cli tool for accelerating webpack dev server of Bobcat.", "description": "A cli tool for accelerating webpack dev server of Bobcat.",
"main": "index.js", "main": "index.js",
"bin": { "bin": {
"wildcat": "./bin/index.js" "wildcat": "./bin/index.js"
......
...@@ -7,62 +7,57 @@ const handlebars = require('handlebars') ...@@ -7,62 +7,57 @@ const handlebars = require('handlebars')
const { log } = require('./utils/log') const { log } = require('./utils/log')
const { TEMPLATES, PACKAGES, LEGACY_CODES, BACKUP_FILES } = require('./configs') const { TEMPLATES, PACKAGES, LEGACY_CODES } = require('./configs')
handlebars.registerHelper('if_eq', function(a, b, opts) { handlebars.registerHelper('if_eq', function(a, b, opts) {
return a === b ? opts.fn(this) : opts.inverse(this) return a === b ? opts.fn(this) : opts.inverse(this)
}) })
const preserveOldFiles = () => { const restoreLegacyCodes = () => {
BACKUP_FILES.forEach(file => { log.title('📤 Restore previous codes')
log.item(`Preserving ${file} ...`) LEGACY_CODES.forEach(file => {
const basename = path.basename(file)
fs.copyFileSync(file, `${__dirname}/backups/${basename}`)
})
}
const restoreOldFiles = () => {
log.title('📤 Restore previous configs')
log.title(
`⚠️ Make sure you have execed ${chalk.bgGreen.black(
'wildcat run'
)}, or it may go wrong`
)
BACKUP_FILES.forEach(file => {
log.item(`Restoring ${file} ...`) log.item(`Restoring ${file} ...`)
const basename = path.basename(file) const basename = path.basename(file)
fs.copyFileSync(`${__dirname}/backups/${basename}`, file) fs.copyFileSync(`${__dirname}/codes/old/${basename}`, file)
}) })
log.title( log.title(
`⚠️ Run ${chalk.bgGreen.black( `⚠️ Run ${chalk.bgGreen.black(
'npm update' 'npm update'
)} yourself to reset node_mouduls` )} yourself to reset node_modules`
) )
} }
const updateConfigFiles = (mode = 'chill') => { const updateConfigs = (mode = 'chill') => {
TEMPLATES.forEach(filename => { TEMPLATES.forEach(file => {
log.item(`Updating ${filename} ...`) log.item(`Updating ${file} ...`)
const content = fs.readFileSync( const content = fs.readFileSync(
`${__dirname}/templates/${filename}.template`, `${__dirname}/templates/${file}.template`,
'utf8' 'utf8'
) )
const template = handlebars.compile(content) const template = handlebars.compile(content)
const output = template({ mode }) const output = template({ mode })
fs.writeFileSync(filename, output, 'utf8') fs.writeFileSync(file, output, 'utf8')
}) })
} }
const updateLegacyCodes = () => { const restoreConfigs = () => {
LEGACY_CODES.forEach(({ name, update }) => { log.title('📤 Restore previous codes')
log.item(`Updating ${name} ...`) TEMPLATES.forEach(file => {
const content = fs.readFileSync(name, 'utf8') log.item(`Restoring ${file} ...`)
const output = update(content) const basename = path.basename(file)
fs.writeFileSync(name, output, 'utf8') fs.copyFileSync(`${__dirname}/templates/${basename}`, file)
})
}
const updateCodes = () => {
LEGACY_CODES.forEach(file => {
const basename = path.basename(file)
log.item(`Updating ${basename} ...`)
fs.copyFileSync(`${__dirname}/codes/new/${basename}`, file)
}) })
} }
const upgradePackages = () => { const updatePackages = () => {
const command = const command =
'yarn add -D ' + 'yarn add -D ' +
PACKAGES.map(({ name, version }) => { PACKAGES.map(({ name, version }) => {
...@@ -74,9 +69,9 @@ const upgradePackages = () => { ...@@ -74,9 +69,9 @@ const upgradePackages = () => {
} }
module.exports = { module.exports = {
preserveOldFiles, restoreLegacyCodes,
restoreOldFiles, updateConfigs,
updateConfigFiles, updateCodes,
updateLegacyCodes, updatePackages,
upgradePackages restoreConfigs
} }
[
"package.json",
"yarn.lock",
"config/fonts.json",
"Procfile",
"fe/js/App.es6",
"fe/js/blog.client.es6",
"fe/js/BlogEditor.es6",
"fe/js/components/HtmlComponent.es6",
"fe/js/components/page_settings_dialog/NewDomainsTab.es6",
"fe/js/EcommerceManager.es6",
"fe/js/editor.es6",
"fe/js/Landing.es6",
"fe/js/MainDashboard.es6",
"fe/js/page.client.es6",
"fe/js/PortfolioManager.es6",
"fe/js/stores/font_store.es6",
"fe/js/utils/helpers/EcommerceHelper.es6",
"fe/js/v3_bridge/page_analytics_engine.es6",
"fe/js/v4_bridge/Bridge.es6",
"fe/lib/webpack/entries-generation-webpack-plugin.js",
"fe/manifests/verticals/app.js",
"fe/manifests/verticals/bright.js",
"fe/manifests/verticals/fresh.js",
"fe/manifests/verticals/glow.js",
"fe/manifests/verticals/ion.js",
"fe/manifests/verticals/minimal.js",
"fe/manifests/verticals/onyx_new.js",
"fe/manifests/verticals/persona.js",
"fe/manifests/verticals/personal.js",
"fe/manifests/verticals/perspective.js",
"fe/manifests/verticals/pitch_new.js",
"fe/manifests/verticals/profile.js",
"fe/manifests/verticals/s5-theme.js",
"fe/manifests/verticals/sleek.js",
"fe/manifests/verticals/spectre.js",
"fe/manifests/verticals/zine.js",
"fe/nextgen/app.es6",
"fe/scripts/fonts/generateFontsJson.js"
]
import init from './init'
/* eslint-disable import/first */
init()
import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import 'js/reactInit.es6'
import Immutable from 'immutable'
import { createStore, applyMiddleware, compose } from 'redux'
import { combineReducers } from 'redux-immutable'
import { Provider } from 'react-redux'
import {
Router,
Route,
IndexRedirect,
IndexRoute,
browserHistory,
} from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { Iterable } from 'immutable'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import 'js/vendor/jquery/browser'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import reducers from 'nextgen/app/reducers'
import BlogManager from 'nextgen/blog/manager/Manager'
import BlogToWechatPreview from 'nextgen/blog/BlogToWechatPreview'
import SelectTemplate from 'nextgen/app/scenes/SelectTemplate'
import LiveChat from 'nextgen/app/scenes/LiveChat'
import Analytics from 'nextgen/app/scenes/Analytics'
import Preview from 'nextgen/app/scenes/Preview'
import ResellerDashboard from 'nextgen/app/scenes/ResellerDashboard'
import Domains from 'nextgen/app/scenes/Domains'
import Domain from 'nextgen/app/scenes/Domain'
import DomainPurchase from 'nextgen/app/scenes/DomainPurchase'
import DonationManager from 'nextgen/app/scenes/donation/DonationManager'
import ClientTab from 'nextgen/app/scenes/resellerDashboard/ClientTab'
import BillingTab from 'nextgen/app/scenes/resellerDashboard/BillingTab'
import AddCreditTab from 'nextgen/app/scenes/resellerDashboard/AddCreditTab'
import PartnerTab from 'nextgen/app/scenes/resellerDashboard/PartnerTab'
import SalesDashboard from 'nextgen/app/scenes/SalesDashboard'
import ResellerTab from 'nextgen/app/scenes/salesDashboard/ResellerTab'
import StatsTab from 'nextgen/app/scenes/salesDashboard/StatsTab'
import Audience from 'nextgen/app/scenes/audience'
import Miniprogram from 'nextgen/dashboard/miniprogram'
import ComboDashboard from 'nextgen/dashboard/Combo'
import MiniprogramDashboard from 'nextgen/dashboard/miniprogram/components/dashboard'
import MiniprogramSelectTemplate from 'nextgen/dashboard/miniprogram/components/selectTemplate'
/* eslint-enable import/first */
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (getState, action) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const middleware = [thunkMiddleware, loggerMiddleware]
// Add the reducer to your store on the `routing` key
const composeEnhancers =
(localStorage &&
localStorage.getItem('__strk_developer__') &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(...middleware)),
)
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: state => state.get('router').toJS(),
})
class RouterComponent extends React.Component {
static childContextTypes() {
PropTypes.object
}
static getChildContext() {
return { location: this.props.location }
}
render() {
return (
<Provider store={store}>
{/* Tell the Router to use our enhanced history */}
<Router history={history}>
<Route path="/s/sites/:siteId/blog/manage" component={BlogManager} />
<Route
path="/s/blog_posts/:blogPostId/wechat_preview"
component={BlogToWechatPreview}
/>
<Route path="/s/select_template" component={SelectTemplate} />
<Route path="/s/analytics/:siteId" component={Analytics} />
<Route path="/s/sites/:siteId/preview" component={Preview} />
<Route
path="/s/sites/:siteId/donation/manage"
component={DonationManager}
/>
<Route path="/s/reseller" component={ResellerDashboard}>
<IndexRedirect to="clients" />
<Route path="clients" component={ClientTab} />
<Route path="billing" component={BillingTab} />
<Route path="addcredit" component={AddCreditTab} />
<Route path="partners" component={PartnerTab} />
<Route path="select_template" component={SelectTemplate} />
<Route
path="select_mini_program_template"
component={props => (
<MiniprogramSelectTemplate
{...props}
skipCategorySelector={true}
/>
)}
/>
</Route>
<Route path="/s/sales" component={SalesDashboard}>
<IndexRedirect to="resellers" />
<Route path="resellers" component={ResellerTab} />
<Route path="stats" component={StatsTab} />
</Route>
<Route path="/s/v2_domains" component={Domains} />
<Route path="/s/v2_domains/purchase" component={DomainPurchase} />
<Route path="/s/v2_domains/:domainId" component={Domain} />
<Route path="/s/live_chat" component={LiveChat} />
<Route path="/s/audience/:id" component={Audience} />
<Route path="/s/miniprogram" component={Miniprogram}>
<IndexRoute component={MiniprogramDashboard} />
<Route
path="select_template"
component={MiniprogramSelectTemplate}
/>
</Route>
<Route path="/s/reseller/zhubajie/instances" component={ComboDashboard} />
</Router>
</Provider>
)
}
}
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
const cloudinary = require('cloudinary')
const ConfStore = require('js/stores/conf_store')
I18n.init(poFile)
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
const RouterComponentWithContext = ComponentKitContext(RouterComponent)
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<RouterComponentWithContext />
</AppContainer>
</ErrorBoundary>,
document.getElementById('container'),
)
})
.catch(e => {
console.error(e, e.stack)
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
require('./init')
import createClass from 'create-react-class'
import Morearty from 'morearty'
import React from 'react'
import ReactDOM from 'react-dom'
import $ from 'jquery'
import logger from 'js/utils/logger'
import 'js/vendor/jquery/browser'
import 'js/vendor/jquery/purl'
import 'js/vendor/jquery/tooltip'
import 'js/vendor/jquery/easing'
import 'js/reactInit.es6'
import 'js/v3_bridge/template_helper/modules/core'
import 'js/v3_bridge/template_helper/modules/decorator'
import 'js/v3_bridge/template_helper/modules/event'
import 'js/v3_bridge/template_helper/modules/fixer'
import 'js/v3_bridge/template_helper/modules/util'
import 'js/v3_bridge/template_helper/main'
import TimerMixin from 'react-timer-mixin'
import EditorActions from 'js/actions/editor_actions'
import BlogPostEditorStore from 'js/stores/BlogPostEditorStore'
import CollaboratorsStore from 'js/stores/collaborators_store'
import 'js/stores/BlogPostMetaStore'
import 'js/stores/BlogPostManagerStore'
import 'js/componentsForBlog'
import $B from 'js/v3_bridge/b'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import { wrapComponentWithReduxStore } from 'js/utils/reduxUtil'
import * as editorStoreCreator from 'js/reducers/editorStoreCreator'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import connectReduxStoreToBinding from 'js/utils/helpers/connectReduxStoreToBinding'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
if (typeof window.timerStart === 'undefined') {
window.timerStart = new Date().getTime()
}
window.timerCheck = function(label) {
const time = new Date().getTime() - window.timerStart
const msg = `${label} in ${time}ms`
logger.log(msg)
return msg
}
window.edit_page = require('js/v3_bridge/edit_page_bridge')
window.edit_page.isBlog = true
// These two are direct reads so promises can be fired quicker
// const locale = $S.globalConf.locale
const themeName = $S.blogPostData.pageMeta.theme.name_with_v4_fallback
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
const p2 = import(`manifests/themes/${themeName}.js`)
Promise.all([p1, p2])
.then(([poFile, manifest]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const BootstrapWrapper = createClass({
mixins: [Morearty.Mixin, TimerMixin],
componentWillMount() {
const cloudinary = require('cloudinary')
const ConfStore = require('js/stores/conf_store')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
},
componentDidMount() {
if (CollaboratorsStore.getLocked()) {
return EditorActions.openDialog('collaborationWarning')
}
if ($.url().param('open') === 'welcome') {
EditorActions.openDialog('welcomeDialog')
}
// setup tooltips
$('.strikingly-menu-container [rel="tooltip-right"]').tooltip({
placement: 'right',
container: '#strikingly-tooltip-container',
})
// expend dialog
try {
if (
window !== window.parent &&
window.parent.edit_page &&
window.parent.edit_page.blogDialog
) {
window.parent.edit_page.blogDialog.expand()
}
} catch (e) {}
this.setTimeout(() => {
$B.TH.Fixer.overrideContentLang()
$B.TH.Fixer.fixNbspForEditor()
// iframes will use parent's Event first
// App store should be loaded after main editor to properly subscribe events from parent
// If load app store first, then Event is wrong, subscription will fail.
$B.loadIframe($('#app-store-iframe'))
}, 0)
},
render() {
const BlogPostEditor = require('js/components/blog/BlogPostEditor')
return <BlogPostEditor />
},
})
const Ctx = BlogPostEditorStore.init(window.$S)
const reduxStore = editorStoreCreator.getStore()
const BlogEditorBootstrap = wrapComponentWithReduxStore(
Ctx.bootstrap(BootstrapWrapper),
reduxStore,
)
connectReduxStoreToBinding(reduxStore, Ctx.getBinding())
const BlogEditorWithContext = ComponentKitContext(BlogEditorBootstrap)
$(() => {
ReactDOM.render(
<ErrorBoundary>
<BlogEditorWithContext />
</ErrorBoundary>,
document.getElementById('s-blog-editor-container'),
)
window.timerCheck('React has finished rendering')
})
})
.catch(e => console.error(e))
// Provide webpack modules to v3
// Note: es6 imports will break - Andy Feb 2016
require('./PageSaver')
require('../init')
const I18n = require('js/utils/i18n')
const i18nHelper = require('js/utils/helpers/i18nHelper')
import { AppContainer } from 'react-hot-loader'
// Note: Intially, the following chunk was loaded asynchronously with
// require.ensure, but it breaks on uat of sxl and thus is removed
// Andy Feb 2016
// used in dashboard to send data to angular
import EditPage from 'js/v3_bridge/edit_page_bridge'
import ComponentKitContext from 'js/utils/ComponentKitContext'
let Event = null
window.edit_page = EditPage
try {
if (parent.window.edit_page && parent.window.edit_page.Event) {
Event = parent.window.edit_page.Event
EditPage.Event = Event
} else {
Event = EditPage.Event
if (parent.window.edit_page) {
parent.window.edit_page.Event = Event
} else {
parent.window.edit_page = {
Event,
}
}
}
} catch (e) {
if (window.edit_page && window.edit_page.Event) {
Event = window.edit_page.Event
EditPage.Event = Event
} else {
Event = EditPage.Event
if (window.edit_page) {
window.edit_page.Event = Event
} else {
window.edit_page = {
Event,
}
}
}
}
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1])
.then(([poFile]) => {
I18n.init(poFile)
const $ = require('jquery')
const React = require('react')
const ReactDOM = require('react-dom')
const SupportWidget = require('js/components/support_widget/SupportWidget')
const PurchaseBridge = require('../../nextgen/domain/PurchaseBridge')
const SupportWidgetWithComponentKit = ComponentKitContext(SupportWidget)
const PublishManager = require('nextgen/subApps/publishManager')
PublishManager.bindToGlobal()
$(() => {
const supportWidgetContainer = document.getElementById(
's-support-widget-container',
)
if (supportWidgetContainer) {
ReactDOM.render(
<AppContainer>
<SupportWidgetWithComponentKit />
</AppContainer>,
supportWidgetContainer,
)
}
PurchaseBridge({
Event,
})
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
import _ from 'lodash'
import PageDataStore from 'js/stores/page_data_store'
import DeviceHelper from 'js/utils/helpers/device_helper'
import { getFormattedPrice } from 'js/utils/EcommerceUtils'
import { traverseObj } from 'common/utils/lodashb'
import * as Immutable from 'immutable'
import EcommerceConstants from 'js/constants/ecommerce_constants'
import { flatten, path, pathEq } from 'ramda'
const { ZERO_DECIMAL_CURRENCY_LIST } = EcommerceConstants
function _getSettings() {
const EcommerceStore = require('js/stores/ecommerce_store')
return EcommerceStore.getSettings()
}
function _getCart() {
const EcommerceBuyStore = require('js/stores/EcommerceBuyStore')
return EcommerceBuyStore.getCart()
}
function _getCurrentDevice() {
if (!DeviceHelper.isWechat() && DeviceHelper.isMobile()) {
return 'mobile'
}
if (DeviceHelper.isWechat()) {
return 'wechat'
}
return 'desktop'
}
function _getSuportedChannelsWithCurrentDevice(channels) {
const __hasProp = {}.hasOwnProperty
const deviceedChannels = {
wechat: [],
mobile: [],
desktop: [],
}
for (const key in channels) {
if (!__hasProp.call(channels, key)) {
continue
}
const value = channels[key]
if (value) {
switch (key) {
case 'stripe':
case 'alipay':
deviceedChannels.mobile.push(key)
deviceedChannels.desktop.push(key)
break
case 'paypal':
case 'offline':
deviceedChannels.mobile.push(key)
deviceedChannels.wechat.push(key)
deviceedChannels.desktop.push(key)
break
case 'wechatpay':
deviceedChannels.wechat.push(key)
deviceedChannels.desktop.push(key)
break
case 'pingppAlipayWap':
deviceedChannels.mobile.push(key)
break
case 'pingppAlipayQr':
case 'pingppWxPubQr':
deviceedChannels.desktop.push(key)
break
case 'pingppWxPub':
deviceedChannels.wechat.push(key)
break
default:
break
}
}
}
return deviceedChannels
}
// function _getProducts() {
// return EcommerceStore.getProducts()
// }
function _addCurrencySymbol(price) {
const settings = _getSettings()
return getFormattedPrice(price, settings.currencyData)
}
function _getCountryByCode(countryCode) {
return ($S.country_list || {})[countryCode]
}
function _removeHttpProtocol(url) {
if (!url) {
return ''
}
return url.replace('http:', '')
}
export default {
availableDevicesToPayment() {
const settings = _getSettings()
const deviceedChannels = _getSuportedChannelsWithCurrentDevice(
settings.paymentGateways,
)
const supportedDevices = []
if (deviceedChannels.wechat.length > 0) {
supportedDevices.push('wechat')
}
if (deviceedChannels.mobile.length > 0) {
supportedDevices.push('mobile')
}
if (deviceedChannels.desktop.length > 0) {
supportedDevices.push('desktop')
}
return supportedDevices
},
hasAvailablePaymentWithCurrentDevice() {
const channels = this.getAvailableChannelsWithCurrentDevice()
return channels.length > 0
},
getAvailableChannelsWithCurrentDevice() {
const settings = _getSettings()
let channels = []
if (PageDataStore.hasSection('ecommerce')) {
const device = _getCurrentDevice()
const deviceedChannels = _getSuportedChannelsWithCurrentDevice(
settings.paymentGateways,
)
channels = deviceedChannels[device]
}
return channels
},
getAvailableChannels(
channels = [
'paypal',
'stripe',
'alipay',
'pingppAlipayQr',
'pingppAlipayWap',
'pingppWxPub',
'pingppWxPubQr',
'wechatpay',
'offline',
'midtrans',
],
) {
const settings = _getSettings()
const availableChannels = []
const { currencyCode, paymentGateways } = settings
const self = this
channels.map(channel => {
const supportCurrencies = self.getSupportCurrencies(channel)
if (
supportCurrencies.includes(currencyCode.toString()) &&
paymentGateways[channel]
) {
availableChannels.push(channel)
}
})
return availableChannels
},
getAvailableChannelsCount(
channels = [
'paypal',
'stripe',
'alipay',
'pingppAlipayQr',
'pingppAlipayWap',
'pingppWxPub',
'pingppWxPubQr',
'wechatpay',
'offline',
'midtrans',
],
) {
return this.getAvailableChannels(channels).length
},
isPaymentAccountSet() {
if (PageDataStore.hasSection('ecommerce')) {
const paymentGatewaysCount = this.getAvailableChannelsCount()
return paymentGatewaysCount > 0
}
return true
},
pageHasCoupon() {
return _getSettings().hasCoupon
},
userHasCoupon(coupon = _getCart().coupon) {
if (coupon && coupon.category) {
return true
}
},
userHasCouponWithType(type, coupon = _getCart().coupon) {
if (this.userHasCoupon(coupon) && coupon.category === type) {
return true
}
},
isInCondition(condition, coupon = _getCart().coupon) {
if (this.userHasCoupon() && coupon.option.condition[condition]) {
return true
}
},
needToShowDiscountInfo(coupon = _getCart().coupon) {
return this.userHasCoupon()
},
addCurrencySymbol: _addCurrencySymbol,
getPriceScope(product) {
const priceList =
product && product.variations
? product.variations.map(variation => variation.price)
: [0]
const minPrice = Math.min(...priceList)
const maxPrice = Math.max(...priceList)
let rs
if (minPrice === maxPrice) {
rs = _addCurrencySymbol(minPrice)
} else {
rs = `${_addCurrencySymbol(minPrice)} - ${_addCurrencySymbol(maxPrice)}`
}
return rs
},
getDecimalNum(currency = _getSettings().currencyCode) {
let rs = 2
if (ZERO_DECIMAL_CURRENCY_LIST.indexOf(currency) !== -1) {
rs = 0
}
return rs
},
getDiscountItem() {
if (!this.userHasCoupon()) {
return null
}
const { items: itemsInCart, coupon } = _getCart()
const amount = coupon.option.amount
const discountProductId = coupon.option.condition.productId
const itemsInProduct = itemsInCart.filter(
item => String(item.productId) === String(discountProductId),
)
if (itemsInProduct.length) {
const maxPriceItem = itemsInProduct.reduce((acc, item) => {
if (
acc &&
Number(acc.orderItem.price) >= Number(item.orderItem.price)
) {
return acc
} else {
return item
}
}, null)
return maxPriceItem
} else {
return null
}
},
getDiscountForProductCondition() {
if (!this.userHasCoupon()) {
return 0
}
const discountItem = this.getDiscountItem()
if (discountItem) {
const { coupon } = _getCart()
const amount = coupon.option.amount
return (discountItem.orderItem.price * amount) / 100
} else {
return 0
}
},
getDiscountNum() {
const { coupon } = _getCart()
let number = 0
if (!this.needToShowDiscountInfo()) {
return number
}
if (this.userHasCoupon()) {
switch (coupon.category) {
case 'free_shipping':
number = this.getShippingFeeNum()
return number
case 'flat':
number = coupon.option.amount / 100
break
case 'percentage':
number = this.isInCondition('productId')
? this.getDiscountForProductCondition()
: (this.getTotalItemPriceNum() * coupon.option.amount) / 100
if (this.getDecimalNum() === 0) {
number = Math.round(number)
}
break
// no default
}
}
if (number >= this.getTotalItemPriceNum()) {
number = this.getTotalItemPriceNum()
}
return number
},
getShippingFeeNum() {
const settings = _getSettings()
const { orderData, shipping } = _getCart()
let shippingFee = 0
if (!orderData || !shipping || !settings.shippingRegions) {
return 0
}
if (!orderData.shipping && !Array.isArray(settings.shippingRegions)) {
const countryCode = shipping.country ? shipping.country.value : false
if (!countryCode) {
return 0
}
const feeData =
settings.shippingRegions[countryCode] ||
settings.shippingRegions[
_getCountryByCode(countryCode)
? _getCountryByCode(countryCode).continent
: undefined
] ||
settings.shippingRegions.default
if (!feeData) {
return 0
}
const decimalNum = this.getDecimalNum()
const feePerAdditionalItem = (
feeData.feePerAdditionalItem / 100.0
).toFixed(decimalNum)
const feePerOrder = (feeData.feePerOrder / 100.0).toFixed(decimalNum)
const { items } = _getCart()
if (_.all(items, i => !i.product.shippingInfo)) {
return 0
}
const additionalFee = items.reduce((total, next) => {
const fee = next.product.shippingInfo
? feePerAdditionalItem * next.orderItem.quantity
: 0
return total + fee
}, 0)
shippingFee = feePerOrder - feePerAdditionalItem + additionalFee
} else {
shippingFee = orderData.shipping / 100.0
}
return shippingFee
},
getTotalItemPriceNum() {
const { items, coupon } = _getCart()
let totalPrice = _.reduce(
items,
(total, next) => total + next.orderItem.price * next.orderItem.quantity,
0,
)
if (this.getDecimalNum() === 0) {
totalPrice = Math.round(totalPrice)
}
return totalPrice
},
getTotalNum() {
let amount = 0
if (!this.userHasCoupon()) {
return (
this.getShippingFeeNum() +
this.getTotalItemPriceNum() +
this.getTaxesNum()
)
}
if (this.userHasCouponWithType('free_shipping')) {
return this.getTotalItemPriceNum() + this.getTaxesNum()
}
amount = this.getTotalItemPriceNum() - this.getDiscountNum()
if (amount < 0) {
amount = 0
}
return amount + this.getShippingFeeNum() + this.getTaxesNum()
},
getTaxesNum() {
const settings = _getSettings()
const taxesRate = (settings.taxes || 0) / 100
const itemPrice = this.getTotalItemPriceNum()
const discountForTax = this.userHasCouponWithType('free_shipping')
? 0
: this.getDiscountNum()
let taxesNum = 0
if (!taxesRate) {
return taxesNum
}
taxesNum = ((itemPrice - discountForTax) * taxesRate) / (1 + taxesRate)
if (this.getDecimalNum() === 0) {
taxesNum = Math.round(taxesNum)
}
return taxesNum
},
getDiscountDescription(coupon = _getCart().coupon, options) {
let description = ''
if (this.userHasCouponWithType('free_shipping', coupon)) {
description = __('EcommerceCoupon|Free Shipping')
} else if (this.userHasCouponWithType('percentage', coupon)) {
if (this.isInCondition('productId')) {
const item = this.getDiscountItem()
description = __(
'EcommerceCoupon|%{amount} off for %{productCount} %{productName} ',
{
productCount: options ? options.productCount : 1,
productName: options ? options.productName : item.product.name,
amount: `${coupon.option.amount}%`,
},
)
} else {
description = __('EcommerceCoupon|%{amount} Off', {
amount: `${coupon.option.amount}%`,
})
}
} else if (this.userHasCouponWithType('flat', coupon)) {
const decimalNum = this.getDecimalNum()
const fee = (coupon.option.amount / 100).toFixed(decimalNum)
description = __('EcommerceCoupon|%{amount} Off', {
amount: this.addCurrencySymbol(fee),
})
}
return description
},
getDiscount() {
return `- ${this.addCurrencySymbol(this.getDiscountNum())}`
},
getShippingFee() {
return this.addCurrencySymbol(this.getShippingFeeNum())
},
getSubtotal() {
return this.addCurrencySymbol(this.getTotalItemPriceNum())
},
getTaxes() {
return this.addCurrencySymbol(this.getTaxesNum())
},
getSubtotalWithDiscount() {
if (this.userHasCouponWithType('free_shipping')) {
return this.addCurrencySymbol(this.getTotalItemPriceNum())
}
return this.addCurrencySymbol(
this.getTotalItemPriceNum() - this.getDiscountNum(),
)
},
getTotalPrice() {
return this.addCurrencySymbol(this.getTotalNum())
},
getTrackData() {
if (__PRODUCT_NAME__ === 'sxl') {
return {}
}
const cartData = _getCart()
const trackData = {
currency: _getSettings().currencyData.code,
content_type: 'product',
content_ids: [],
content_name: [],
value: this.getTotalNum(),
num_items: 0,
}
cartData.items.forEach(item => {
const { orderItem } = item
trackData.content_name.push((item.product && item.product.name) || '')
trackData.content_ids.push(item.product.id)
trackData.num_items += Number(orderItem.quantity)
})
return trackData
},
loadDistrictsAsync(selectedCode = '000000', callback = () => {}) {
if (__PRODUCT_NAME__ === 'sxl') {
require.ensure([], require => {
const asyncResult = import(`../../data/china_districts/${selectedCode}.json`)
asyncResult().then(json => callback(json))
})
}
},
/**
* @param {conf} {string} example as following
* {
* "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
* "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
* "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
* "package" : "prepay_id=u802345jgfjsdfgsdg888"
* "signType" : "MD5", //微信签名方式
* "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
}
*/
useWechatpay(conf, cb) {
if (DeviceHelper.isWechat()) {
if (typeof WeixinJSBridge === 'undefined') {
alert('请确认您的网站已正确加载微信sdk')
return false
}
WeixinJSBridge.invoke('getBrandWCPayRequest', conf, res => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg, 将在用户支付成功后返回ok,但并不保证它绝对可靠
cb && cb()
}
})
} else {
// pay on desktop with wechat qr code scanning
const EcommerceActions = require('js/actions/EcommerceActions')
EcommerceActions.gotoEcommerceBuyDialog('paymentqr', true)
}
},
getInitialPaymentAccount(provider) {
switch (provider) {
case 'alipay':
return { pid: null, md5Key: null }
case 'wechatpay':
return { mchId: null, appId: null, apiKey: null, appSecret: null }
case 'paypal':
return { email: null }
case 'offline':
return { instructions: null, method: null }
case 'midtrans':
return { merchantId: null, clientKey: null, serverKey: null }
default:
return {}
}
},
getSupportCurrencies(provider) {
switch (provider) {
case 'alipay':
case 'wechatpay':
case 'pingppAlipayQr':
case 'pingppAlipayWap':
case 'pingppWxPub':
case 'pingppWxPubQr':
return ['CNY']
case 'paypal':
return [
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PHP',
'PLN',
'RUB',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
]
case 'stripe':
return [
'AUD',
'BRL',
'CAD',
'CHF',
'CLP',
'DKK',
'EUR',
'GBP',
'HKD',
'IDR',
'INR',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PHP',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
'COP',
'VND',
'ZAR',
]
case 'offline':
return [
'AUD',
'BDT',
'BRL',
'CAD',
'CHF',
'CLP',
'CNY',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'IDR',
'ILS',
'INR',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PEN',
'PHP',
'PLN',
'RUB',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
'COP',
'VND',
'ZAR',
]
case 'midtrans':
return ['IDR']
default:
return []
}
},
getProviderNameMap(provider) {
switch (provider) {
// TODO: Server has two name for stripe. On disconnect and accounts.
case 'stripe':
return 'stripe_connect'
case 'stripe_connect':
return 'stripe'
default:
return provider
}
},
removeHttpProtocolForImageUrl(products) {
traverseObj(products, obj => {
if (obj.thumbnailUrl) {
obj.thumbnailUrl = _removeHttpProtocol(obj.thumbnailUrl)
}
if (obj.url) {
obj.url = _removeHttpProtocol(obj.url)
}
})
return products
},
needNarrowCurrencySymbol() {
const settings = _getSettings()
return settings.currencyCode == 'KRW'
},
isEmptyDimension(dimension) {
const dimen = Immutable.isImmutable(dimension)
? dimension.toJS()
: dimension
if (
dimen === undefined ||
dimen.name === '' ||
dimen.options === undefined ||
dimension.options === null ||
dimen.options.length === 0
) {
return true
} else {
return false
}
},
hasMutipleDimensions(dimensions) {
if (!dimensions) {
return false
}
const { dimension1, dimension2 } = Immutable.isImmutable(dimensions)
? dimensions.toJS()
: dimensions
return (
!this.isEmptyDimension(dimension1) && !this.isEmptyDimension(dimension2)
)
},
normalizeItemName(name) {
if (name && name.indexOf('_') !== -1) {
return name.split('_').join(' ')
} else {
return name
}
},
convertProductPrice(products) {
for (const product of Array.from(products)) {
if (!product.picture) {
product.picture = []
}
product.description = product.description.replace(/\n/g, '<br>')
const decimalNum = Array.from(ZERO_DECIMAL_CURRENCY_LIST).includes(
__PRODUCT_NAME__ === 'sxl' ? 'CNY' : 'USD', // _settings.currencyCode in EcommerceStore
)
? 0
: 2
product.variations = _.sortBy(product.variations, variant =>
Number(variant.id),
)
for (const variant of Array.from(product.variations)) {
/*
* hack price adapting if iframe wrapped whole site,
* productchange event will trigger twice,
* and secound time will re-use prevState,
* eg: input 99, will wrong final result 0.99
*/
if (!/\.\d{2}$/.test(variant.price)) {
variant.price = (variant.price / 100).toFixed(decimalNum)
}
}
}
return products
},
_formatCategoryOption(category) {
let label = ''
if (category.level === 1) {
label = category.name
} else if (category.level === 2) {
label = `\u00A0\u00A0\u00A0\u00A0\u00A0${category.name}`
}
return { value: category.name, label, id: category.id }
},
formattedCategoryOptions(categories, formatter = this._formatCategoryOption) {
const categoryOptions = []
const rawCategories = categories.toJS ? categories.toJS() : categories
rawCategories.forEach(category => {
categoryOptions.push(formatter(category))
})
return categoryOptions
},
addLabelForCategory(categories) {
const newCategories = categories.map(category => {
let label
if (!category.get('level') || category.get('level') === 1) {
label =
category.get('name') +
(category.get('products_count') ? category.get('products_count') : '')
} else if (category.get('level') === 2) {
label = `\u00A0\u00A0\u00A0\u00A0\u00A0${category.get(
'name',
)} (${category.get('products_count')})`
}
return category.set('label', label)
})
return newCategories
},
sortedCategories(categoryObjs, orderList) {
orderList = orderList || {}
let allSortedCategory = []
const sortedCategory = categoryObjs
.filter(category => category.level === 1)
.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex >= bIndex ? 1 : -1
})
sortedCategory.forEach(category => {
const subCategories =
(category.children &&
category.children.map(subCategoryId =>
categoryObjs.find(category => category.id === subCategoryId),
)) ||
[]
const sortedChildren = subCategories.sort((a, b) => {
const aIndex =
(category.children_category_order &&
category.children_category_order[a.id]) ||
-a.id
const bIndex =
(category.children_category_order &&
category.children_category_order[b.id]) ||
-b.id
return aIndex >= bIndex ? 1 : -1
})
allSortedCategory.push([category, ...sortedChildren])
})
allSortedCategory = flatten(allSortedCategory)
return allSortedCategory
},
isSubCategory(item) {
const category = item.toJS ? item.toJS() : item
if (!item) {
return false
}
return (
path(['payload', 'item', 'parent_id'])(category) &&
pathEq(['payload', 'item', 'level'], 2)(category)
)
},
sortedCategory(categories, orderList) {
orderList = orderList || {}
// param default value not accurate enough
// sortedCategory(categories, orderList = {}) => var orderList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // not check `null` param case here
const categoriesObj =
categories && categories.toJS ? categories.toJS() : categories
if (categoriesObj) {
return categoriesObj.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex >= bIndex ? 1 : -1
})
}
},
checkHasSubCategory(categories) {
return (
categories &&
categories.some(item => {
if (categories.toJS) {
return item.get('children') && item.get('children').size > 0
} else {
return item.children && item.children.length > 0
}
})
)
},
}
require('./init')
import $ from 'jquery'
import 'js/vendor/jquery/easing'
import React from 'react'
import ReactDOM from 'react-dom'
import { Iterable } from 'immutable'
import { tct } from 'r-i18n'
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import ManagerReducer from 'nextgen/ecommerce/manager/reducers/ManagerReducer'
import connectReduxStoreToBinding from 'js/utils/helpers/connectReduxStoreToBinding'
import EcommerceManagerStore from 'js/stores/EcommerceManagerStore'
import FeatureStore from 'js/stores/FeatureStore'
import ConfStore from 'js/stores/conf_store'
import 'js/reactInit.es6'
import * as UrlConstants from 'js/constants/url_constants'
import ProductPanel from 'nextgen/ecommerce/manager/components/productPanel'
import CategoryManagerWrapper from 'nextgen/ecommerce/manager/components/settings/categoryManagerWrapper'
import MembershipManager from 'nextgen/ecommerce/manager/components/settings/membershipManager'
import PaymentGatewaySettingsPanel from 'nextgen/ecommerce/manager/components/settings/paymentChannel/PaymentGatewaySettingsPanel'
// import Orders from 'nextgen/manager/Products'
// import Coupons from 'nextgen/manager/Products'
// import Settings from 'nextgen/manager/Products'
import gonReader from 'js/utils/gonReader'
import { getSupportCurrencies } from 'js/utils/helpers/EcommerceHelper'
import * as RHL from 'react-hot-loader'
import wrapErrorBoundary from 'js/components/ErrorBoundary'
import { UPDATE_SETTINGS_SUCCESS } from 'nextgen/ecommerce/manager/actions/entities/settings'
import { initializeAccountFromV3 } from 'nextgen/ecommerce/manager/actions/entities/accounts'
import EcommerceManagerBridge from 'js/v4_bridge/EcommerceManagerBridge'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import ComponentKitContext from 'js/utils/ComponentKitContext'
const AppContainer = wrapErrorBoundary(RHL.AppContainer)
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (getState, action) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const store = createStore(
ManagerReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware, // neat middleware that logs actions
),
)
const baseProps = {
siteId: gonReader('id'),
siteState: gonReader('state'),
publicUrl: gonReader('public_url'),
meta: gonReader('pageOwner.ecommerce'),
isStandAlone: window.parent === window,
isPro: ['pro', 'namecheap', 'sxlbiz'].includes(
gonReader('pageOwner.membership'),
),
isVip: gonReader('pageOwner.membership') === 'vip',
isSxl: Boolean(gonReader('globalConf.isSxl')),
isAdmin: Boolean(gonReader('pageOwner.isAdmin')),
isSupport: Boolean(gonReader('pageOwner.isSupport')),
supportedCurrency: gonReader('conf.SUPPORTED_CURRENCY'),
}
const productProps = {
...baseProps,
productLimit:
gonReader('pageOwner.ecommerce').productLimit ||
(gonReader('globalConf.isSxl') ? 5 : 1),
upgradeUrl: UrlConstants.PRICING.GOTO_AND_RETURN(
gonReader('id'),
'ecommerce',
),
productPageRollout: gonReader('globalConf.rollout.product_page'),
productDetailRollout: gonReader('globalConf.rollout.product_detail'),
siteMemberShip: gonReader('globalConf.rollout.siteMemberShip'),
noCategoryLimit: gonReader('globalConf.rollout.no_category_limit'),
}
const supportedChannels = () => {
const isSxl = Boolean(gonReader('globalConf.isSxl'))
const paypal = {
channel: 'paypal',
supportedCurrencies: getSupportCurrencies('paypal'),
features: [
__('Ecommerce|~2.9% + 30¢ per transaction'),
__('Ecommerce|Get funds in just a few minutes'),
tct(__('Ecommerce|Accepts [link]'), {
link: (
<span>
<i className="fa fa-cc-visa" aria-hidden="true" />
<i className="fa fa-cc-mastercard" aria-hidden="true" />
<i className="fa fa-cc-amex" aria-hidden="true" />
<i className="fa fa-cc-discover" aria-hidden="true" />
</span>
),
}),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/paypal.png',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more about PayPal.]'), {
link: <a target="_blank" href="https://www.paypal.com/" />,
}),
}
const stripe = {
channel: 'stripe',
supportedCurrencies: getSupportCurrencies('stripe'),
features: [
__('Ecommerce|~2.9% + 30¢ per transaction'),
__('Ecommerce|Get funds in about 7 days'),
tct(__('Ecommerce|Accepts [link]'), {
link: (
<span>
<i className="fa fa-cc-visa" aria-hidden="true" />
<i className="fa fa-cc-mastercard" aria-hidden="true" />
<i className="fa fa-cc-amex" aria-hidden="true" />
<i className="fa fa-cc-discover" aria-hidden="true" />
<i className="fa fa-cc-jcb" aria-hidden="true" />
</span>
),
}),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/stripe-logo.png',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more about Stripe.]'), {
link: <a target="_blank" href="https://stripe.com/" />,
}),
}
const alipay = {
channel: 'alipay',
supportedCurrencies: getSupportCurrencies('alipay'),
features: [
__('Ecommerce|Needs business bank account and ICP license'),
__('Ecommerce|Get funds immediately after payment'),
__('Ecommerce|0.6% per successful charge from Alipay'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/ic_alipay.png',
methods: __('Ecommerce|Instant Payment'),
additionalInfo: tct(
__(
'Ecommerce|Enter your PID and MD5 Key to enable your Alipay account.[link: Learn more about how to get PID and MD5 Key from Alipay manage platform.]',
),
{
link: (
<a
target="_blank"
href="http://help.sxl.cn/hc/zh-cn/articles/115000046301"
/>
),
},
),
}
const wechatpay = {
channel: 'wechatpay',
supportedCurrencies: getSupportCurrencies('wechatpay'),
features: [
__('Ecommerce|Needs business bank account and ICP license'),
__('Ecommerce|Get funds immediately after payment'),
__('Ecommerce|0.6% per successful charge from WeChat Pay'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/ic_wechat_pay.png',
methods: __('Ecommerce|In-App Web-based Payment'),
additionalInfo: tct(
__(
'Ecommerce|Enter your WeChat Pay account info below to enable your Alipay account.[link: Learn more about how to get WeChat Pay account info.]',
),
{
link: (
<a
target="_blank"
href="http://help.sxl.cn/hc/zh-cn/articles/115000046661"
/>
),
},
),
}
const offline = {
channel: 'offline',
supportedCurrencies: getSupportCurrencies('offline'),
features: [],
logo: '',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more]'), {
link: (
<a
target="_blank"
href={
isSxl
? 'http://help.sxl.cn/hc/zh-cn/articles/115000100782'
: 'https://support.strikingly.com/hc/en-us/articles/115000100802'
}
/>
),
}),
description: __(
'Ecommerce|Customers will skip payment while placing the order. You must set up offline payment yourself!',
),
}
const midtrans = {
channel: 'midtrans',
supportedCurrencies: getSupportCurrencies('midtrans'),
features: [
__(
'Ecommerce|Accept card payment, bank transfer, direct debit, e-wallet, over the counter',
),
__('Ecommerce|Transaction fee varies with different payment methods'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/midtrans.png',
methods: '',
}
let supportedChannelsConf = []
if (isSxl) {
supportedChannelsConf = [paypal, alipay, wechatpay]
} else {
supportedChannelsConf = [paypal, stripe]
}
// Rollout for Midtrans payments
if (!isSxl && ConfStore.getMidtransPayments()) {
supportedChannelsConf.push(midtrans)
}
// Rollout for offline payments
if (ConfStore.getOfflinePayments()) {
supportedChannelsConf.push(offline)
}
return supportedChannelsConf
}
const KitWrappedProductPanel = ComponentKitContext(ProductPanel)
class ProductsContainer extends React.Component {
componentWillMount() {
EcommerceManagerBridge.subPublishUrlChange((topic, data) => {
baseProps.publicUrl = data.url
this.forceUpdate()
})
EcommerceManagerBridge.subSiteStateChange((topic, data) => {
baseProps.siteState = data.state
this.forceUpdate()
})
}
render() {
return (
<Provider store={store}>
<KitWrappedProductPanel
{...productProps}
currentProductDetail={this.props.currentProductDetail}
canUseCategory={FeatureStore.canUse('ecommerce_category')}
canSeeCategory={FeatureStore.canSee('ecommerce_category')}
/>
</Provider>
)
}
}
class CategoryContainer extends React.Component {
render() {
return (
<Provider store={store}>
<CategoryManagerWrapper
{...productProps}
canUseCategory={FeatureStore.canUse('ecommerce_category')}
canSeeCategory={FeatureStore.canSee('ecommerce_category')}
/>
</Provider>
)
}
}
const CategoryContainerWithComponentKit = ComponentKitContext(CategoryContainer)
class MembershipContainer extends React.Component {
render() {
return (
<Provider store={store}>
<MembershipManager {...productProps} />
</Provider>
)
}
}
const PaymentSettingsContainer = () => (
<Provider store={store}>
<PaymentGatewaySettingsPanel
{...baseProps}
supportedChannels={supportedChannels()}
/>
</Provider>
)
EcommerceManagerBridge.subSettingsChange((topic, data) => {
const siteId = $S.id.toString()
let settings =
store
.getState()
.getIn(['entities', 'settings', 'data', siteId, 'settings']) || {}
if (settings.toJS) {
settings = settings.toJS()
}
store.dispatch({
type: UPDATE_SETTINGS_SUCCESS,
payload: {
settings: Object.assign(settings, data.settings),
},
meta: {
siteId,
},
})
store.dispatch(initializeAccountFromV3(settings.accounts))
})
const siteIdString = $S.id.toString()
const prevStates = {
product: store
.getState()
.getIn(['entities', 'product', 'data', siteIdString]),
settings: store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']),
category: store.getState().getIn(['entities', 'category', 'data']),
}
store.subscribe(a => {
const newProductsState = store
.getState()
.getIn(['entities', 'product', 'data', $S.id.toString()])
const newSettingsState = store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings'])
const newCategoryState = store
.getState()
.getIn(['entities', 'category', 'data'])
if (newProductsState !== prevStates.product) {
prevStates.product = newProductsState
EcommerceManagerBridge.pubProductsChange(_.flatten(newProductsState.toJS()))
}
if (newSettingsState !== prevStates.settings) {
prevStates.settings = newSettingsState
EcommerceManagerBridge.pubSettingsChange(newSettingsState.toJS())
}
if (newCategoryState !== prevStates.category) {
prevStates.category = newCategoryState
EcommerceManagerBridge.pubCategoriesChange(
_.flatten(newCategoryState.toJS()),
)
}
})
Promise.all([p1])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
const cloudinary = require('cloudinary')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
if (window.parent === window) {
let ImageAssetDialog = require('js/v4_bridge/react_app_bridge/ImageAssetDialogApp')
const EditorStore = require('js/stores/editor_store')
const ctx = EditorStore.init()
connectReduxStoreToBinding(store, ctx.getBinding())
ImageAssetDialog = ctx.bootstrap(ImageAssetDialog())
ReactDOM.render(
<Provider store={store}>
<AppContainer>
<ImageAssetDialog />
</AppContainer>
</Provider>,
document.getElementById('container'),
)
}
EcommerceManagerStore.init()
FeatureStore.hydrate($S.features)
connectReduxStoreToBinding(store, EcommerceManagerStore.getBinding())
// render counpon part for ecommerce manager
const couponsCtx = EcommerceManagerStore.getCouponsContext()
const CouponManager = require('js/components/ecommerce/manager/coupon/CouponManager')
const WrappedCoupon = ComponentKitContext(
couponsCtx.bootstrap(CouponManager),
)
ReactDOM.render(
<Provider store={store}>
<AppContainer>
<WrappedCoupon />
</AppContainer>
</Provider>,
document.getElementById('coupon-container'),
)
/*
Add morearty binding to product container
currentProductDetail is a data container and binding path never get changed, only data can be set
*/
const productsContext = EcommerceManagerStore.getProductsContext()
const currentProductDetail = EcommerceManagerStore.getProductsBinding().sub(
'currentProductDetail',
)
const WrapperedProductsContainer = productsContext.bootstrap(
ProductsContainer,
)
ReactDOM.render(
<AppContainer>
<WrapperedProductsContainer
currentProductDetail={currentProductDetail}
/>
</AppContainer>,
document.getElementById('products-container'),
)
ReactDOM.render(
<AppContainer>
<PaymentSettingsContainer />
</AppContainer>,
document.getElementById('payment-manager'),
)
ReactDOM.render(
<CategoryContainerWithComponentKit />,
document.getElementById('category-manager'),
)
ReactDOM.render(
<AppContainer>
<MembershipContainer />
</AppContainer>,
document.getElementById('membership-manager'),
)
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
// SPEC
// Owner: Jerry
// Feature name: HtmlComponent
// * Hover over the component, expect the overlay to show.
// * Click on the component, expect the App Store popup to open.
// * After interaction with App Store, clicking 'Save' should cause popup to close and iframe to refresh/resize.
// * This should also fire an API call to /s/components to save the component data.
// * Remove the section containing the component, expect an API call to /s/components to destroy the component data.
// * On editor init, expect the iframe to resize itself to a proper height.
import React from 'react'
import PropTypes from 'prop-types'
import ConfStore from 'js/stores/conf_store'
function eachSeries(items, timeoutInMilliseconds, fn) {
if (items.length > 0) {
let nextCalled = false
const next = () => {
if (nextCalled) {
console.error('done function called after timeout')
return
}
clearTimeout(timeout)
nextCalled = true
eachSeries(items.slice(1), timeoutInMilliseconds, fn)
}
const timeout = setTimeout(next, timeoutInMilliseconds)
fn(items[0], next)
}
}
let HtmlComponent = null
if (__NATIVE_WEB__) {
const MobileDisabledNotice = require('js/components/MobileDisabledNotice')
HtmlComponent = () => (
<MobileDisabledNotice
disabledNotice={__(
'Mobile|App store is not yet editable on the mobile app.',
)}
/>
)
} else {
const ReactDOM = require('react-dom')
const _ = require('lodash')
const $ = require('jquery')
const ComponentFactory = require('js/utils/comp_factory')
const PageMetaStore = require('../stores/page_meta_store')
const EditorActions = require('../actions/editor_actions')
const ComponentDataUtils = require('../utils/apis/components_api_utils')
const MetaMixin = require('js/utils/mixins/meta_mixin')
const CustomPropTypes = require('../utils/custom_prop_types')
const AppStoreDialog = require('js/v3_bridge/app_store_dialog')
const IframeHelper = require('../utils/helpers/IframeHelper')
const logger = require('js/utils/logger')
const loadFancyBox = import('js/vendor/jquery/jquery.fancybox3.js')
const rt = require('./templates/html_component')
const _htmlFieldsToSave = [
'id',
'value',
'htmlValue',
'selected_app_name',
'page_id',
'render_as_iframe',
'app_list',
]
const _bobcatPropTypes = {
data: {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: CustomPropTypes.html,
render_as_iframe: PropTypes.bool,
selected_app_name: PropTypes.string,
app_list: PropTypes.string,
binding: PropTypes.object,
},
}
const _bobcatDefaultProps = () => ({
data: {
render_as_iframe: false,
app_list: '{}',
},
})
HtmlComponent = ComponentFactory.createPageComponent({
displayName: 'HtmlComponent',
mixins: [MetaMixin('editor')],
bobcatPropTypes: _bobcatPropTypes,
getBobcatDefaultProps: _bobcatDefaultProps,
componentWillMount() {
this.initMeta({
iframeSrcQ: 0,
canceled: false,
})
if (__IN_EDITOR__ && !this._hasId(this.props.id)) {
return this._getId()
}
// if (!__IN_EDITOR__ && this._isTypeForm() && !__SERVER_RENDERING__) {
// this._applyTypeformScrollTopHack()
// }
},
componentDidMount() {
IframeHelper.startTimer()
this._injectHtml()
this._resizeIFrame()
if (!__IN_EDITOR__ && this._isTypeForm()) {
loadFancyBox.then(() => {
this._initTypeForm()
})
}
},
componentDidUpdate(prevProps) {
if (__IN_EDITOR__) {
if (prevProps.value !== this.dtProps.value) {
const dataToSave = _.pick(this.dtProps, _htmlFieldsToSave)
this._saveComponent(dataToSave)
this._injectHtml()
return this._resizeIFrame()
}
}
},
componentWillUnmount() {
if (this.props.isBlog && window.Ecwid) {
window.Ecwid.destroy()
window.Ecwid = null
}
},
_initTypeForm() {
const $this = $(ReactDOM.findDOMNode(this)).find('.type-form-popup')
const basicClass = 'button-component'
const styleClass = 'type-form-button-style'
const $typeFormButton = $this.find('.type-form-button').eq(0)
if ($.fn.fancybox) {
const content = $typeFormButton.attr('data-iframe-content')
const $virtualEl = $(content)
const iframeSrc = $virtualEl.eq(0).attr('src')
$typeFormButton.attr('data-src', iframeSrc)
$typeFormButton.attr('data-href', 'javascript:;')
$typeFormButton.fancybox({
fullScreen: false,
slideClass: 's-fancybox-typeform',
iframe: {
scrolling: 'auto',
},
})
}
const originalBg = $this.find('.type-form-button').css('background')
$this.find('.type-form-button').removeClass(basicClass)
const newBg = $this.find('.type-form-button').css('background')
if (originalBg === newBg) {
$this.addClass(styleClass)
}
$this.find('.type-form-button').addClass(basicClass)
},
_applyTypeformScrollTopHack() {
// HACK: embed typeform iframe will scroll parent window to iframe position
// but we can not prevent the code execution or the form will not show
// so we manually check big jump
let lastTop = $(window).scrollTop()
let scrollCheck = null
if (!$B.TH.isMobile()) {
scrollCheck = function() {
const currentTop = $(window).scrollTop()
if (Math.abs(currentTop - lastTop) > 200) {
$(window).scrollTop(lastTop)
} else {
lastTop = currentTop
}
}
$(window).on('scroll', scrollCheck)
setTimeout(() => {
$(window).off('scroll', scrollCheck)
}, 15000)
}
},
_isTypeForm() {
return this.props.selected_app_name === 'TypeFormApp'
},
_hasId(id) {
return typeof id === 'number'
},
_getId() {
this._setCanceled(false)
return EditorActions[
`createComponent${this.props.isBlog ? 'InBlog' : ''}`
]({
data: {
component: {},
},
success: data => {
this.updateData({
id: data.data.component.id,
})
return this.savePage()
},
error: data => {
if (
window.confirm(
__(
"Uh oh! There's been an error creating this HTML component. Try again?",
),
)
) {
return this._getId()
}
return this._setCanceled(true)
},
})
},
_resizeIFrame() {
const $iframes = $(ReactDOM.findDOMNode(this)).find('iframe')
if ($iframes.length) {
return IframeHelper.resizeIFrames($iframes)
}
},
_injectHtml() {
if (!this.dtProps.render_as_iframe && !__IN_EDITOR__) {
// HACK: use try catch to avoid breaking the entire rendering
try {
const htmlInjectDiv = ReactDOM.findDOMNode(this.refs.htmlInject)
htmlInjectDiv.innerHTML = this._rawHtml()
// We could use jQuery html to execute the script tags on insertion,
// but we want to maintain their execution order and wait for each
// script to load one at a time
eachSeries(
$(htmlInjectDiv).find('script'),
4000, // need timeout because don't get a failure result from runScript
(script, done) => {
const rscriptType = /^$|\/(?:java|ecma)script/i // taken from jQuery .html source
if (script.src && rscriptType.test(script.type || '')) {
$.getScript(script.src).done(done)
} else {
const rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g // from jQuery
$.globalEval(script.textContent.replace(rcleanScript, ''))
return done()
}
},
)
} catch (error) {
// use console.log because user might want to see this error
logger.log(`Html section script error: ${error}`)
return $(ReactDOM.findDOMNode(this.refs.htmlInject)).append(
`Script error: ${error}`,
)
}
}
},
_hasContent() {
return this.dtProps.value.length > 0
},
_renderAsIframe() {
return this.dtProps.render_as_iframe
},
_rawHtml() {
return _.unescape(this.dtProps.value || '')
},
_onClickEditor() {
const cb = data => {
if (data.id === this.dtProps.id) {
const dataToSave = _.pick(data, _htmlFieldsToSave)
this.updateData(dataToSave)
return this.savePage()
}
return window.error(
__(
"Uh oh! There's been an error saving this HTML component. Try again.",
),
)
}
if (this.props.isBlog) {
var dialog = new AppStoreDialog(
_.extend({}, this.dtProps, {
htmlValue: this._rawHtml(),
page_id: PageMetaStore.getId(),
}),
data => {
cb(data)
return dialog.close()
},
() => dialog.close(),
)
// let dialog = new AppStoreDialog(
// _.extend({}, this.dtProps, {
// htmlValue: this._rawHtml(),
// page_id: PageMetaStore.getId(),
// }),
// (data) => {
// cb(data)
// dialog.close()
// }, () => {
// dialog.close()
// }
// )
} else {
return EditorActions.openAppStoreDialog(
_.extend({}, this.dtProps, {
htmlValue: this._rawHtml(),
page_id: PageMetaStore.getId(),
}),
cb,
)
}
},
_saveComponent(data) {
if (this.props.isBlog) {
ComponentDataUtils.update(this.dtProps.id, data, this._reloadIframe)
} else {
// TODO: Using promise instead of callback
return EditorActions.saveHTMLComponent(
this.dtProps.id,
data,
this._reloadIframe,
)
}
},
// ComponentDataUtils.update(@dtProps.id, data, @_reloadIframe)
_iframeSrcQ() {
return this.getMeta('iframeSrcQ')
},
_reloadIframe() {
return this.updateMeta({ iframeSrcQ: this.getMeta('iframeSrcQ') + 1 })
},
// for admin use only to fix component that have repeated id
_recreateComponent() {
if (
window.confirm(
'Recreating will delete any existing component! Make sure you understand what this does',
)
) {
this.updateData(_bobcatDefaultProps().data)
return this._getId()
}
},
render() {
if (this._getCanceled()) {
return (
<div
className="s-common-status"
style={{ cursor: 'pointer' }}
onClick={this._getId}>
{__('Click here to create HTML component again.')}
</div>
)
} else if (!this._hasId(this.props.id)) {
return __IN_EDITOR__ ? (
<div className="s-loading-wrapper">
<div className="s-loading" />
</div>
) : null
}
return rt.apply(this)
},
})
}
export default HtmlComponent
require('./init')
import React from 'react'
import $ from 'jquery'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import PhotoPage from './components/PhotoPage'
require('./utils/extensions/native')
const p1 = import(`locales/${i18nHelper.getTranslationFile(
'zh_CN',
)}`)
Promise.all([p1]).then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<PhotoPage setType="background" />
</AppContainer>
</ErrorBoundary>,
document.getElementById('photo-page'),
)
})
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
require('./init')
import React from 'react'
import $ from 'jquery'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import { Provider } from 'react-redux'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import {
Router,
Route,
IndexRedirect,
IndexRoute,
hashHistory,
} from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import reducers from 'nextgen/app/reducers'
import MiniprogramDashboard from 'nextgen/dashboard/miniprogram/components/dashboard'
import Domains from 'nextgen/app/scenes/Domains'
import Domain from 'nextgen/app/scenes/Domain'
import DomainPurchase from 'nextgen/app/scenes/DomainPurchase'
const middleware = [thunkMiddleware]
const composeEnhancers =
(localStorage &&
localStorage.getItem('__strk_developer__') &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(...middleware)),
)
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(hashHistory, store, {
selectLocationState: state => state.get('router').toJS(),
})
function EmptyComponent() {
return <div />
}
function recordLatestViewedHash(nextState, replace) {
const highlightClass = {
miniprogram: '.my-miniprogram',
v2_domains: '.my-domains',
}[location.hash && location.hash.split('/')[1]]
$('.subnav-container .s-link')
.removeClass('current')
.filter(highlightClass)
.addClass('current')
if (localStorage && ['#/miniprogram'].indexOf(location.hash) !== -1) {
localStorage.setItem('dashboard_latest_viewed_hash', location.hash)
}
}
class DashboardRouter extends React.Component {
static childContextTypes() {
PropTypes.object
}
static getChildContext() {
return { location: this.props.location }
}
componentWillMount() {
$('.nav-menu .s-link')
.removeClass('current')
.filter('.my-sites')
.addClass('current')
}
render() {
return (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={EmptyComponent} />
<Route
path="/miniprogram"
component={MiniprogramDashboard}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains"
component={Domains}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains/purchase"
component={DomainPurchase}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains/:domainId"
component={Domain}
onEnter={recordLatestViewedHash}
/>
</Router>
</Provider>
)
}
}
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
const DashboardRouterWithContext = ComponentKitContext(DashboardRouter)
$(() => {
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<Provider store={store}>
<DashboardRouterWithContext />
</Provider>
</AppContainer>
</ErrorBoundary>,
document.getElementById('mainDashboard'),
)
})
})
.catch(e => {
console.error(e, e.stack)
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
import React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import DOM from 'react-dom-factories'
import Immutable from 'immutable'
import Morearty from 'morearty'
import reactMixin from 'react-mixin'
import TimerMixin from 'react-timer-mixin'
import ReactTransitionGroup from 'react-transition-group/TransitionGroup'
import classNames from 'classnames'
import { tct } from 'r-i18n'
import { Icon } from 'component-kit'
import PremiumFeature from './PremiumFeature'
import EditorActions from 'js/actions/editor_actions'
import SiteConnectionActions from 'js/actions/SiteConnectionActions'
import EditorConstants from 'js/constants/editor_constants'
import EditorDispatcher from 'js/dispatcher/editor_dispatcher'
import ConfStore from 'js/stores/conf_store'
import CurrentUserStore from 'js/stores/current_user_store'
import CollaboratorsStore from 'js/stores/collaborators_store'
import PageMetaStore from 'js/stores/page_meta_store'
import UserMetaStore from 'js/stores/user_meta_store'
import TooltipMixin from 'js/utils/mixins/tooltip_mixin'
import PurchaseContainer from 'js/components/domain_purchase/PurchaseContainer'
import JQSlide from 'js/components/helpers/jqslide'
import StrikinglyOrSxl from 'js/components/StrikinglyOrSxl'
import _ from 'lodash'
import { openDialog } from 'nextgen/shared/actions/dialogsActions'
const parseDomainPromise = import('parse-domain')
// 1 has a recored
// 2 has forwarind
// 3 both or others
const _checkConfig = {
'1&1': 2,
'101 Domain': 1,
'123-reg': 2,
Bluehost: 2,
cPanel: 2,
CrazyDomains: 1,
Gandi: 2,
GoDaddy: 2,
'Microsoft 365': 1,
Namecheap: 2,
'Network Solutions': 1,
OVH: 1,
'Register.com': 2,
Wix: 1,
'Wordpress.com': 1,
'Yahoo!': 1,
Others: 3,
新网: 1,
阿里云万网: 1,
'Nic.cl': 1,
Amen: 1,
'Sakura Internet(さくらインターネット)': 1,
'Muu Muu (ムームードメイン)': 1,
'Value-Domain': 2,
XSERVER: 2,
}
@reactMixin.decorate(Morearty.Mixin)
@reactMixin.decorate(TooltipMixin.enableAfterUpdate())
@reactMixin.decorate(TimerMixin)
class DomainsTab extends React.Component {
static propTypes = {
permalink: PropTypes.string.isRequired,
customDomain: PropTypes.string,
isDirLink: PropTypes.bool,
binding: PropTypes.object.isRequired,
onSaveSettings: PropTypes.func.isRequired,
selected: PropTypes.bool,
currentTabName: PropTypes.string,
changeTabName: PropTypes.func,
showQrCode: PropTypes.func,
}
constructor(props) {
super(props)
this._onConnectDomainAfterRegister = this._onConnectDomainAfterRegister.bind(
this,
)
this._onRegisterNewDomain = this._onRegisterNewDomain.bind(this)
this._onRegisterNewDomainWithName = this._onRegisterNewDomainWithName.bind(
this,
)
this._onChangeStep = this._onChangeStep.bind(this)
this._onBackToTab = this._onBackToTab.bind(this)
this._removePurchaseSuccessMsg = this._removePurchaseSuccessMsg.bind(this)
this._onChangeCustomDomain = this._onChangeCustomDomain.bind(this)
}
componentWillMount() {
this.setStateWithForceUpdate(this.props)
}
componentDidMount() {
if (['editor', 'blogger'].includes(CollaboratorsStore.getRole())) {
return
}
// Hack: category and name can be updated from publish dialog, update states when open setting dialog
this._openPageSettingsToken = EditorDispatcher.register(payload => {
switch (payload.actionType) {
case EditorConstants.ActionTypes.OPEN_PAGE_SETTINGS:
this.setStateWithForceUpdate(this.props)
// refresh status when domain tab is selected after opened settings dialog
if (this.props.selected) {
this._refreshDomainStatus()
}
}
})
}
componentWillUnmount() {
EditorDispatcher.unregister(this._openPageSettingsToken)
}
componentDidUpdate(prevProps, prevState) {
// refresh status when swtich to domain tab
if (!prevProps.selected && this.props.selected) {
this._refreshDomainStatus()
}
}
getDefaultState() {
return Immutable.Map({
// global common tab state
isUpdating: false,
isSaved: false,
status: '',
// domains state
showRegisterBtn: true,
showPurchaseSuccessMsg: false,
customDomainSaved: false,
customDomainStatus: '',
customDomainMessage: '',
customDomainCheckRs: '',
domainProvider:
window.localStorage.getItem('_strk_domain_provider') || '',
currentStep: 'tab',
currentDomain: this.props.customDomain || '',
domainPurchaseStatus: CurrentUserStore.domainPurchaseProcessStatus(),
v2DomainSetting: (() => {
const ds = PageMetaStore.getUserV2DomainSetting()
return ds ? ds.toJS() : null
})(),
})
}
// because morearty's shouldComponentUpdate doesn't account for changes in state
setStateWithForceUpdate(h) {
this.setState(h)
this.forceUpdate()
}
_refreshDomainStatus() {
const customDomainStatus = this.getDefaultBinding().get(
'customDomainStatus',
)
let checkRs = this.getDefaultBinding().get('customDomainCheckRs')
if (!customDomainStatus && checkRs) {
checkRs = checkRs.toJS()
if (checkRs.correct === false) {
this._onCheckDomainStatus()
}
}
}
_onPermalinkChange(e) {
this.setStateWithForceUpdate({
permalink: e.target.value,
})
}
_onBackToTab() {
this._onChangeStep('tab')
this.props.changeTabName(__('EditorSettings|Domain'))
}
_onConnectDomainAfterRegister(newDomain) {
EditorActions.addDomainToPool(newDomain)
newDomain = `www.${newDomain}`
EditorActions.updateCustomDomain(newDomain)
this.getDefaultBinding().set('currentDomain', newDomain)
// TODO change that to connect / disconnect later
SiteConnectionActions.updateCustomDomain({
siteId: this.props.siteId,
domain: newDomain,
success: data => {
this.props.updateV2CustomDomain(data)
this.getDefaultBinding().set('customDomainStatus', 'success')
},
})
this.getDefaultBinding().set('showPurchaseSuccessMsg', true)
this.getDefaultBinding().set('domainPurchaseStatus', 'buy')
this.getDefaultBinding().set('justConnectedAfterRegister', true)
this.getDefaultBinding().set('showRegisterBtn', false)
this.setTimeout(() => {
this._onBackToTab()
}, 1500)
}
_removePurchaseSuccessMsg() {
this.getDefaultBinding().set('showPurchaseSuccessMsg', false)
}
_onSavePermalink = () => {
if (this.refs.permalink.value.length >= 63) {
this.getDefaultBinding().set(
'status',
__('EditorSettings|Domain name cannot be longer than 63 characters.'),
)
} else {
this.getDefaultBinding().set('status', '')
this.props.onSaveSettings({
permalink: this.refs.permalink.value,
})
}
}
_saveDomainProvider(provider) {
this.getDefaultBinding().set('domainProvider', provider)
window.localStorage.setItem('_strk_domain_provider', provider)
}
_clearDomainProvider() {
this.getDefaultBinding().set('domainProvider', 'default')
window.localStorage.removeItem('_strk_domain_provider')
}
_onRegisterNewDomain() {
this._onChangeStep('purchase')
this._initialDomainName = ''
// EditorActions.registerDomain()
}
_onRegisterNewDomainWithName() {
this._onChangeStep('purchase')
this._initialDomainName = this.getDefaultBinding().get('currentDomain')
}
_onChangeStep(step) {
this.getDefaultBinding().set('currentStep', step)
}
_onChangeCustomDomain(e) {
this.getDefaultBinding().set('currentDomain', e.target.value)
}
_onSaveCustomDomain = e => {
let currentDomain = this.getDefaultBinding()
.get('currentDomain')
.toLowerCase()
if (
ConfStore.getIsSxl() &&
!currentDomain &&
!window.confirm(
'若停止使用自定义域名,网站将临时下线。你需要发布审核,通过后即可访问。确定停止使用自定义域名?',
)
) {
return
}
this.getDefaultBinding().set('saveBtnClicked', true)
const customDomainStatus = this.getDefaultBinding().get(
'customDomainStatus',
)
const { v2Domains } = this.props
if (['updating', 'checking'].includes(customDomainStatus)) {
return
}
this._clearDomainProvider()
this.setState({ currentDomain })
// If current domain is empty, we don't even bother, it's a disconnect
// If current domain start with www., we don't need to test (unless it's connecting to www.com)
if (!currentDomain || currentDomain.startsWith('www.')) {
this._setUpdating(currentDomain)
this._updateV2CustomDomain(currentDomain)
return
}
const existingDomain = _.find(v2Domains, d => d.name == currentDomain)
if (!existingDomain) {
this.getDefaultBinding().merge(
Immutable.fromJS({
customDomainStatus: 'updating',
}),
)
Promise.all([parseDomainPromise]).then(([parseDomain]) => {
const domainChunks = parseDomain(currentDomain)
if (domainChunks && !domainChunks.subdomain) {
currentDomain = `www.${currentDomain}`
}
this.getDefaultBinding().merge(
Immutable.fromJS({
currentDomain,
}),
)
this._updateV2CustomDomain(currentDomain)
})
return
}
if (existingDomain.is_wwwizered) {
currentDomain = `www.${currentDomain}`
}
this._setUpdating(currentDomain)
this._updateV2CustomDomain(currentDomain)
}
_setUpdating(currentDomain) {
this.getDefaultBinding().merge(
Immutable.fromJS({
customDomainStatus: 'updating',
currentDomain,
}),
)
}
_updateV2CustomDomain(fqdn) {
// TODO change that to connect / disconnect later
SiteConnectionActions.updateCustomDomain({
siteId: this.props.siteId,
domain: fqdn,
success: data => {
this.props.updateV2CustomDomain(data)
this.getDefaultBinding().set('customDomainStatus', 'success')
},
afterError: res => {
this.getDefaultBinding().set('customDomainStatus', '')
this.getDefaultBinding().set('currentDomain', '')
},
})
}
_onCheckDomainStatus() {
this.getDefaultBinding().set('customDomainStatus', 'checking')
EditorActions.checkDomainStatus()
}
_onClickshowRegisterBtn() {
this.getDefaultBinding().set('showRegisterBtn', true)
}
_onChangeProvider(e) {
this._saveDomainProvider(e.target.value)
}
_renderDomainSettingsBox(v2Domain = false) {
const status = this.getDefaultBinding().get('customDomainStatus')
let checkRs = this.getDefaultBinding().get('customDomainCheckRs')
let linkUrl = ''
if (!v2Domain) {
if (checkRs && checkRs.toJS) {
checkRs = checkRs.toJS()
}
if (
typeof checkRs !== 'object' ||
checkRs.correct ||
!checkRs.domainRegistered ||
status === 'updating'
) {
return {
renderRs: null,
showDomainSettingsStatus: false,
}
}
}
const domainProviders = ConfStore.getDomainSupportedProvider()
const currentProvider = this.getDefaultBinding().get('domainProvider')
const saveBtnClicked = this.getDefaultBinding().get('saveBtnClicked')
const currentProviderObj = domainProviders.find(
provider => provider.name === currentProvider,
)
const subdomainUrl = ConfStore.getIsSxl()
? 'http://help.sxl.cn/hc/zh-cn/articles/214721838'
: 'http://support.strikingly.com/hc/en-us/articles/214364738-Generic-Subdomain-Setup-Tutorial'
let checkType = 'arecord'
if (currentProviderObj) {
switch (_checkConfig[currentProviderObj.name]) {
case 2:
checkType = 'forwarding'
break
case 3:
checkType = 'forwarding_or_arecord'
break
// no default
}
}
function getCheckDetail(type, index) {
let text = ''
let success = true
if (checkRs.domainType === 'non_www_subdomain') {
if (
!checkRs.nonWwwSubdomainHasCname ||
!checkRs.nonWwwSubdomainHasCnameToRoot
) {
success = false
}
text = __(
'EditorSettings|Add a CNAME record pointing from <strong>%{subDomain}</strong> to <strong>dns.strikingly.com</strong>',
{ subDomain: checkRs.nonWwwSubdomain },
)
} else {
switch (type) {
case 'cname':
if (
!checkRs.wwwSubdomainHasCname ||
!checkRs.wwwSubdomainHasCnameToRoot
) {
success = false
}
text = __(
'EditorSettings|Add a CNAME record pointing from <strong>www</strong> to <strong>%{rootDomain}</strong>',
{ rootDomain: 'dns.strikingly.com' },
)
break
case 'arecord':
if (
!checkRs.rootDomainRedirected ||
checkRs.rootDomainHasHerokuIp ||
checkRs.wwwSubdomainHasHerokuIp
) {
success = false
}
text = __(
'EditorSettings|Add an A record pointing from <strong>@</strong> to <strong>54.183.102.22</strong>',
)
break
case 'forwarding':
if (!checkRs.rootDomainRedirected) {
success = false
}
text = __(
'EditorSettings|Forward %{rootDomain} to <strong>http://%{wwwSubdomain}</strong>',
{
rootDomain: checkRs.rootDomain,
wwwSubdomain: checkRs.wwwSubdomain,
},
)
break
case 'forwarding_or_arecord':
if (
!checkRs.rootDomainRedirected ||
checkRs.rootDomainHasHerokuIp ||
checkRs.wwwSubdomainHasHerokuIp
) {
success = false
}
text = __(
'EditorSettings|Forward %{rootDomain} to <strong>http://%{wwwSubdomain}</strong> <br> <strong>OR</strong> Add an A record pointing from <strong>@</strong> to <strong>54.183.102.22</strong>',
{
rootDomain: checkRs.rootDomain,
wwwSubdomain: checkRs.wwwSubdomain,
},
)
break
// no default
}
}
return (
<tr key={type}>
<td className="domain-check-result-item">
&#8226;&nbsp;
<span dangerouslySetInnerHTML={{ __html: text }} />
</td>
<td className="domain-check-result-tag">
{status === 'checking' ? (
<i className="fa fa-spinner fa-pulse" />
) : (
<span
className={classNames('s-box-tag', {
red: !success,
green: success,
})}>
{success
? __('EditorSettings|Complete')
: __('EditorSettings|Pending')}
</span>
)}
</td>
</tr>
)
}
if (currentProviderObj) {
linkUrl =
checkRs.domainType === 'non_www_subdomain'
? currentProviderObj.subdomain || subdomainUrl
: currentProviderObj.url
}
__('The page has an invalid domain.')
// if (v2Domain) {
// currentProviderObj = domainProviders.find((provider) => provider.name === 'Others')
// linkUrl = currentProviderObj.url
// }
return {
showDomainSettingsStatus: true,
renderRs: (
<ReactTransitionGroup>
<JQSlide component={DOM.div} className="domain-check-box s-box last">
<div>
<div className="field-title">
{saveBtnClicked
? __(
'EditorSettings|Almost done! You still need to configure your domain settings.',
)
: __(
'EditorSettings|Note: Make sure your domain is configured properly.',
)}
</div>
<select
onChange={this._onChangeProvider.bind(this)}
value={currentProvider}>
<option value="default" disabled={true}>
{__('EditorSettings|Where did you register this domain?')}
</option>
{domainProviders.map((item, index) => (
<option key={item.name} value={item.name}>
{item.name === 'Others' ? __('Domain|Others') : item.name}
</option>
))}
</select>
{currentProviderObj && (
<div>
<ReactTransitionGroup>
<JQSlide
component={DOM.div}
className="domain-check-result">
<div style={{ marginBottom: '12px' }} />
</JQSlide>
</ReactTransitionGroup>
<div>
<a
className="s-btn basic-blue"
target="_blank"
href={linkUrl}>
<i className="fa fa-book left-icon" />
{__('EditorSettings|Read Tutorial')}
</a>
<i
className="fa fa-question-circle"
rel="tooltip-right"
data-original-title={__(
'EditorSettings|Changes in domain settings usually take effect in a few hours, but can take up to 2 days.',
)}
/>
</div>
</div>
)}
</div>
</JQSlide>
</ReactTransitionGroup>
),
}
}
_renderDomainTab() {
const {
isUpdating,
status,
isSaved,
customDomainSaved,
customDomainStatus,
customDomainMessage,
customDomainCheckRs,
showRegisterBtn,
showPurchaseSuccessMsg,
domainPurchaseStatus,
justConnectedAfterRegister,
currentDomain,
} = this.getDefaultBinding()
.get()
.toJS()
const { permalink, isDirLink } = this.state
const { v2DomainSetting } = this.props
const domainSupported = /(.com|.net|.org|.me|.co)$/.test(currentDomain)
const isSxl = ConfStore.getIsSxl()
const v2DomainConnection = PageMetaStore.getV2DomainConnection()
const showConnectedMessage =
v2DomainConnection && v2DomainConnection.get('domain_id')
const forceShowKB =
v2DomainConnection && !v2DomainConnection.get('domain_id')
const isSiteOfResellerClient = PageMetaStore.isSiteOfResellerClient()
let registerDomainContainer
if (!isSxl) {
if (domainPurchaseStatus === 'free') {
registerDomainContainer = (
<div className="register-domain">
{tct(
__(
"Only premium users can register a new domain through our system. If you [link: purchase a yearly subscription plan] to Strikingly, we'll give you a domain for free!",
),
{
root: <div className="field-hint" />,
link: <a href={
UserMetaStore.isZbjUser()
? 'javascript: alert("八戒云站暂不支持升级,请添加新的套餐。")'
: '/s/pricing?ref=free_domain'
} />,
},
)}
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Check Availability')}
</div>
</div>
)
} else if (
!justConnectedAfterRegister &&
v2DomainSetting.entitledToFreeDomain
) {
registerDomainContainer = (
<div className="register-domain">
<div className="field-hint">
{__(
`You're on a yearly plan and can register a new domain for free!`,
)}
</div>
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Claim My Free Domain!')}
</div>
</div>
)
} else {
registerDomainContainer = (
<div className="register-domain">
<div className="field-hint">
{__(`Don't own a domain yet? Grab one here for $24.95/year.`)}
</div>
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Register New Domain')}
</div>
</div>
)
}
}
let permalinkInner
let completedPermalink
if (isDirLink) {
completedPermalink = `${
isSxl ? 'http://www.sxl.cn/' : 'http://www.strikingly.com/'
}${permalink}`
permalinkInner = (
<div className="permalink-inner">
{isSxl ? 'http://www.sxl.cn/' : 'http://www.strikingly.com/'}
<input
type="text"
ref="permalink"
value={permalink}
onChange={e => {
// permalink could be updated from prepublish dialog, shouldn't use defaultValue here.
this._onPermalinkChange(e)
}}
/>
</div>
)
} else {
completedPermalink = `http://${permalink}${
isSxl ? '.sxl.cn/' : '.strikingly.com/'
}`
permalinkInner = (
<div className="permalink-inner">
http://
<input
type="text"
ref="permalink"
value={permalink}
onChange={e => {
// permalink could be updated from prepublish dialog, shouldn't use defaultValue here.
this._onPermalinkChange(e)
}}
/>
{isSxl ? '.sxl.cn/' : '.strikingly.com/'}
</div>
)
}
const domainSettingsRs = this._renderDomainSettingsBox(forceShowKB)
const MoreartyInput = Morearty.DOM.input
return (
<div
key="first-step"
className="page-settings-content s-dialog-content domains-tab">
{
<PremiumFeature
featureName="custom_domain"
overlayStyle="fieldOverlay"
title={__(
'Domain|Register for or connect to a custom domain for this site.',
)}
hint={__(
'Upgrade your account to either Limited or Pro to access this feature!',
)}
source="cd">
{!isSxl && (
<div>
{showPurchaseSuccessMsg && (
<ReactTransitionGroup>
<JQSlide
component={DOM.div}
className="s-box green small fist">
<i className="fa fa-check" />
{tct(
__(
"Domain|[placeholderStrong: You just got a new domain!] And we've already connected it to your site. Remember, you must check your email to validate your domain.",
),
{
root: <span />,
placeholderStrong: <strong />,
},
)}
&nbsp;
<a
href={
v2DomainSetting ? '/s/v2_domains/' : '/s#/domains'
}
target="_blank">
{__('Domain|View your new domain in domain dashboard.')}
</a>
<div
className="close-btn"
onClick={this._removePurchaseSuccessMsg}>
×
</div>
</JQSlide>
</ReactTransitionGroup>
)}
{!isSiteOfResellerClient &&
(showRegisterBtn ? (
<div className="form-field">
<div className="field-title">
{__('Register A New Domain')}
</div>
{registerDomainContainer}
</div>
) : (
<a
onClick={this._onClickshowRegisterBtn.bind(this)}
href="javascript: void(0);">
{__('Register New Domain')}
</a>
))}
<div className="hr" />
</div>
)}
<div className="form-field custom-domain-field">
<div className="field-title">
{__('Custom Domain/Subdomain')}
<StrikinglyOrSxl>
<span className="s-pro-icon">
{__('EditorSettings|Pro / Limited')}
</span>
<span className="s-pro-icon">
{__('EditorSettings|Biz / Pro / Limited')}
</span>
</StrikinglyOrSxl>
</div>
{tct(
__(
'EditorSettings|If you already own a domain, enter it here. Empty it to disconnect.',
),
{
root: <div className="field-hint" />,
},
)}
<div className="custom-domain-update">
<MoreartyInput
type="text"
placeholder={__('e.g. www.mydomain.com')}
value={currentDomain}
onChange={this._onChangeCustomDomain}
/>
<div className="s-btn" onClick={this._onSaveCustomDomain}>
{__('Update')}
{['updating', 'checking'].includes(customDomainStatus) ? (
<i className="fa fa-spinner fa-pulse right-icon" />
) : null}
{customDomainSaved ? <i className="fa fa-check" /> : null}
</div>
{isSxl &&
currentDomain && (
<i
className="fa fa-qrcode qrcode-btn"
style={{ marginLeft: '10px' }}
onClick={() =>
this.props.showQrCode(`http://${currentDomain}`)
}
/>
)}
{showConnectedMessage && (
<div className="s-box-tag green">
{__('EditorSettings|Domain is connected!')}
</div>
)}
{isSxl &&
!this.props.customDomain && (
<div style={{ display: 'block' }}>
<ol className="connect-custom-domain-tips">
<li>
<Icon type="fa-check-circle-o" /> SEO 排名优化
</li>
<li>
<Icon type="fa-check-circle-o" /> 品牌更专业
</li>
<li>
<Icon type="fa-check-circle-o" /> 访问更快速
</li>
</ol>
<a href="https://wanwang.aliyun.com/" target="_blank">
还没有域名?前往万网购买
</a>
</div>
)}
</div>
{customDomainStatus !== 'updating' && domainSettingsRs.renderRs}
{customDomainStatus === 'error' && (
<div className="field-notice error">
<div>
<i className="entypo-info-circled" />
{customDomainMessage === 'Already taken'
? tct(
__(
'Domain|Sorry,[placeholderStrong: %{domain}] is already taken.',
{
domain: this.state.currentDomain,
},
),
{
root: <span />,
placeholderStrong: <strong />,
},
)
: customDomainMessage}
</div>
</div>
)}
{!['updating', 'checking'].includes(customDomainStatus) &&
customDomainCheckRs &&
customDomainCheckRs.domainRegistered === false && (
<div className="field-notice error">
<div>
<i className="entypo-info-circled" />
{domainSupported ? (
<span>
{__(
'EditorSettings|This domain is not registered yet! You can register it now.',
)}
&nbsp;
<StrikinglyOrSxl>
<a
href="javascript: void(0);"
onClick={this._onRegisterNewDomainWithName}>
{__('EditorSettings|Claim your Domain')}
</a>
{null}
</StrikinglyOrSxl>
</span>
) : (
<span>
{__(
'EditorSettings|This domain is not registered yet!',
)}
</span>
)}
</div>
</div>
)}
</div>
</PremiumFeature>
}
{<div className="hr" />}
<div className="form-field permalink">
<div className="field-title">{__('Strikingly.com URL')}</div>
<div className="field-hint">
{__(
'Enter a unique Strikingly.com URL. You may change this at any time.',
)}
</div>
{
<div>
<div className="permalink-container">
{permalinkInner}
<div
className="s-btn permalink-update-btn"
onClick={this._onSavePermalink}>
{__('Update')}
{isUpdating ? <i className="fa fa-spinner fa-pulse" /> : null}
{isSaved ? <i className="fa fa-check" /> : null}
</div>
{isSxl &&
permalink && (
<i
className="fa fa-qrcode qrcode-btn"
onClick={() => this.props.showQrCode(completedPermalink)}
/>
)}
</div>
<div className="tab-status">{status}</div>
</div>
}
</div>
</div>
)
}
render() {
const step = this.getDefaultBinding().get('currentStep')
const domainPurchaseStatus = this.getDefaultBinding().get(
'domainPurchaseStatus',
)
const v2DomainSetting = this.getDefaultBinding().get('v2DomainSetting')
const { currentTabName, changeTabName } = this.props
return (
<div>
{step === 'tab' ? (
this._renderDomainTab()
) : (
<PurchaseContainer
key="second-step"
inSettingsPanel={true}
domainPurchaseStatus={domainPurchaseStatus}
v2DomainSetting={v2DomainSetting}
currentTabName={currentTabName}
changeTabName={changeTabName}
backToTab={this._onBackToTab}
connectDomain={this._onConnectDomainAfterRegister}
initialDomainName={this._initialDomainName}
/>
)}
</div>
)
}
}
/*
Connected wrapper will use shadow equal for props checking by default.
So when binding content changed, connected wrapper will avoid to render, binding ref is not changed (binding is a mutable object).
Set pure option to false to let connected wrapper can re-render when it's parent calls re-render.
The extra cost for this change is the re-render for connected wrapper (HOC).
Won't introduce addtional cost (won't cause addtional render) for the original component (DomainsTab).
*/
export default connect(
null,
{
showQrCode: url => openDialog('urlQrCodeDialog', { url }),
},
null,
{
pure: false,
},
)(DomainsTab)
import './init'
import _ from 'lodash'
import $ from 'jquery'
import 'js/vendor/jquery/easing'
import React from 'react'
import ReactDOM from 'react-dom'
import { Iterable } from 'immutable'
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import ManagerReducer from 'nextgen/ecommerce/manager/reducers/ManagerReducer'
import connectReduxStoreToBinding from 'js/utils/helpers/connectReduxStoreToBinding'
import PortfolioManagerStore from 'js/stores/PortfolioManagerStore'
import FeatureStore from 'js/stores/FeatureStore'
import ConfStore from 'js/stores/conf_store'
import * as UrlConstants from 'js/constants/url_constants'
import ProductPanel from 'nextgen/portfolio/manager/components/productPanel'
import CategoryManagerWrapper from 'nextgen/ecommerce/manager/components/settings/categoryManagerWrapper'
import gonReader from 'js/utils/gonReader'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import { UPDATE_SETTINGS_SUCCESS } from 'nextgen/ecommerce/manager/actions/entities/settings'
import PortfolioManagerBridge from 'js/v4_bridge/PortfolioManagerBridge'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import ComponentKitContext from 'js/utils/ComponentKitContext'
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (/* getState, action */) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const store = createStore(
ManagerReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware, // neat middleware that logs actions
),
)
const baseProps = {
siteId: gonReader('id'),
siteState: gonReader('state'),
publicUrl: gonReader('public_url'),
meta: gonReader('pageOwner.portfolio'),
isStandAlone: window.parent === window,
isPro: ['pro', 'namecheap', 'sxlbiz'].includes(
gonReader('pageOwner.membership'),
),
isVip: gonReader('pageOwner.membership') === 'vip',
isSxl: Boolean(gonReader('globalConf.isSxl')),
isAdmin: Boolean(gonReader('pageOwner.isAdmin')),
isSupport: Boolean(gonReader('pageOwner.isSupport')),
}
const productProps = {
...baseProps,
productLimit:
gonReader('pageOwner.portfolio').productLimit ||
(gonReader('globalConf.isSxl') ? 6 : 1),
upgradeUrl: UrlConstants.PRICING.GOTO_AND_RETURN(
gonReader('id'),
'portfolio',
),
productPageRollout: gonReader('globalConf.rollout.product_page'),
productDetailRollout: gonReader('globalConf.rollout.product_detail'),
siteMemberShip: gonReader('globalConf.rollout.siteMemberShip'),
noCategoryLimit: gonReader('globalConf.rollout.no_category_limit'),
}
class ProductsContainer extends React.Component {
componentWillMount() {
PortfolioManagerBridge.subPublishUrlChange((topic, data) => {
baseProps.publicUrl = data.url
this.forceUpdate()
})
PortfolioManagerBridge.subSiteStateChange((topic, data) => {
baseProps.siteState = data.state
this.forceUpdate()
})
}
render() {
return (
<Provider store={store}>
<ProductPanel
{...productProps}
canUseCategory={FeatureStore.canUse('portfolio_category')}
canSeeCategory={FeatureStore.canSee('portfolio_category')}
/>
</Provider>
)
}
}
function CategoryContainer(/* props */) {
return (
<Provider store={store}>
<CategoryManagerWrapper
{...productProps}
isPortfolio={true}
canUseCategory={FeatureStore.canUse('portfolio_category')}
canSeeCategory={FeatureStore.canSee('portfolio_category')}
/>
</Provider>
)
}
const CategoryContainerWithComponentKit = ComponentKitContext(CategoryContainer)
const siteIdString = gonReader('id').toString()
PortfolioManagerBridge.subSettingsChange((topic, data) => {
let settings =
store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']) || {}
if (settings.toJS) {
settings = settings.toJS()
}
store.dispatch({
type: UPDATE_SETTINGS_SUCCESS,
payload: {
settings: Object.assign(settings, data.settings),
},
meta: {
siteId: siteIdString,
},
})
})
const getState = () => ({
product: store
.getState()
.getIn(['entities', 'product', 'data', siteIdString]),
category: store.getState().getIn(['entities', 'category', 'data']),
settings: store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']),
})
const prevState = getState()
store.subscribe(() => {
const newState = getState()
if (newState.product !== prevState.product) {
prevState.product = newState.product
PortfolioManagerBridge.pubProductsChange(
_.flatten(_.toArray(newState.product.toJS())),
)
}
if (newState.settings !== prevState.settings) {
prevState.settings = newState.settings
PortfolioManagerBridge.pubSettingsChange(newState.settings.toJS())
}
if (newState.category !== prevState.category) {
prevState.category = newState.category
PortfolioManagerBridge.pubCategoriesChange(
_.flatten(_.toArray(newState.category.toJS())),
)
}
})
Promise.all([p1])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
const cloudinary = require('cloudinary')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
if (window.parent === window) {
let ImageAssetDialog = require('js/v4_bridge/react_app_bridge/ImageAssetDialogApp')
const EditorStore = require('js/stores/editor_store')
const ctx = EditorStore.init()
connectReduxStoreToBinding(store, ctx.getBinding())
ImageAssetDialog = ctx.bootstrap(ImageAssetDialog())
ReactDOM.render(
<Provider store={store}>
<ErrorBoundary>
<AppContainer>
<ImageAssetDialog />
</AppContainer>
</ErrorBoundary>
</Provider>,
document.getElementById('container'),
)
}
PortfolioManagerStore.init()
FeatureStore.hydrate($S.features)
const productsContext = PortfolioManagerStore.getProductsContext()
const WrapperedProductsContainer = productsContext.bootstrap(
ComponentKitContext(ProductsContainer),
)
ReactDOM.render(
<AppContainer>
<ErrorBoundary>
<WrapperedProductsContainer />
</ErrorBoundary>
</AppContainer>,
document.getElementById('items-container'),
)
ReactDOM.render(
<ErrorBoundary>
<CategoryContainerWithComponentKit />
</ErrorBoundary>,
document.getElementById('category-manager'),
)
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
# This is an example forman configure file for bobcat
# You can adjust the procs based on your personal development need
#
# 1. Install forego `brew install forego` https://github.com/ddollar/forego
# 2. Copy this file to Procfile
# 3. Configure the tasks
# 3. Start all procs by `forego start`
#
# NOTICE: 1. All the logs are under /log, you can simply check log by `tail -f log/sidekiq.log`.
# 2. The error messages should better be piped to stdout, that's what `2>&1 >file` for.
# 3. forego supports multiple processes invoke `forego start -c all=1, worker=2` this will start 2 worker process
# WEB server
# default way to start server
# server log is piped to stdout by default
# Server with hotload
web: env DISABLE_UNICORN_KILLER=1 REACT_HOT=1 PRODUCT_NAME=sxl RACK_ENV=none RAILS_ENV=development bundle exec unicorn -p 3000 -c unicorn_dev.rb
# Server with no hotload
# web: env DISABLE_UNICORN_KILLER=1 PRODUCT_NAME= RACK_ENV=none RAILS_ENV=development bundle exec unicorn -p 3000 -c unicorn_dev.rb
# WORKER, you can adjust the queues based on your need
worker: env PRODUCT_NAME= bundle exec sidekiq -C config/sidekiq.yml 2>&1 >log/worker.log
# THEME, watch mode
# page_data: strk_build_pages_data
# OpenResty
nginx: env CUSTOM_HOST= openresty -p devs/nginx -c nginx.conf
# If you need to bind custom host:
# nginx: env CUSTOM_HOST=192.168.75.128 openresty -p devs/nginx -c nginx.conf
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "app",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./app/section_selections/navbar/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./app/section_selections/info/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./app/section_selections/columns/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
import 'js/loadBugsnag'
import './init'
import React from 'react'
import ReactDOM from 'react-dom'
import $ from 'jquery'
import logger from 'js/utils/logger'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import bootstrapBlog from 'js/prerender/bootstrapBlog'
import ComponentKitContext from 'js/utils/ComponentKitContext'
if (typeof window.timerStart === 'undefined') {
window.timerStart = new Date().getTime()
}
window.timerCheck = function(label) {
const time = new Date().getTime() - window.timerStart
const msg = `${label} in ${time}ms`
logger.log(msg)
return msg
}
window.edit_page = require('js/v3_bridge/edit_page_bridge')
window.edit_page.isBlog = true
window.edit_page.isShowPage = true
// These two are direct reads so promises can be fired quicker
// const locale = $S.globalConf.locale
const themeName = $S.blogPostData.pageMeta.theme.name_with_v4_fallback
const forcedLocale = $S.blogPostData.pageMeta.forcedLocale
const p1 = import(`locales/${i18nHelper.getTranslationFile(
forcedLocale,
)}`)
const p2 = import(`manifests/themes/${themeName}.js`)
Promise.all([p1, p2])
.then(([poFile, manifest]) => {
const BlogBootstrap = ComponentKitContext(
bootstrapBlog({
poFile,
manifest,
}),
)
$(() => {
ReactDOM.render(
<BlogBootstrap />,
document.getElementById('s-blog-container'),
)
window.timerCheck('React has finished rendering')
})
})
.catch(e => console.error(e))
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "bright",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./bright/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./bright/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./bright/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./bright/section_selections/cta/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./bright/section_selections/info/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./bright/section_selections/hero/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
require('./init')
import $ from 'jquery'
import React from 'react'
import ReactDOM from 'react-dom'
import cloudinary from 'cloudinary'
import logger from 'js/utils/logger'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
// import {AppContainer} from 'react-hot-loader'
import * as RHL from 'react-hot-loader'
import { wrapComponentWithReduxStore } from 'js/utils/reduxUtil'
import * as editorStoreCreator from 'js/reducers/editorStoreCreator'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import wrapErrorBoundary from 'js/components/ErrorBoundary'
import 'js/reactInit.es6'
// These two are direct reads so promises can be fired quicker
const supportedVerticals = ['personal']
const themeName = $S.stores.pageMeta.theme.name
const AppContainer = wrapErrorBoundary(RHL.AppContainer)
// Load dynamic editor and theme style files on editor debug environment
if (__MODE__ === "'editor-debug'") {
require('v4/editor')
require(`themes/${themeName}/main_v4_editor`)
}
let verticalName = $S.stores.pageMeta.vertical
// only apply supported vertical
if (!supportedVerticals.includes(verticalName)) {
verticalName = themeName
}
require('./utils/extensions/native')
const p1 = import(`locales/${i18nHelper.getTranslationFile()}`)
const p2 = import(`manifests/themes/${themeName}.js`)
const p3 = import(`manifests/verticals/${verticalName}.js`)
Promise.all([p1, p2, p3])
.then(([poFile, manifest, vertical]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
const ConfStore = require('js/stores/conf_store')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
function injectVerticalData() {
const SectionSelectorStore = require('js/stores/section_selector_store')
// fill in dynamic default page data based on user or page meta (e.g email)
const FillInDynamicDefaultThemeData = require('js/utils/themes/FillInDynamicDefaultThemeData')
manifest = FillInDynamicDefaultThemeData(manifest)
for (const key in vertical.sectionSelections) {
const selector = vertical.sectionSelections[key]
_.merge(selector, manifest.sections[selector.content.template_name])
}
SectionSelectorStore.setSelectorData(vertical.sectionSelections)
}
if (__NATIVE_WEB__) {
// TODO: wrap it in another file
const NativeBridge = require('js/actions/NativeBridge')
window.NativeBridge = window.NativeBridge || {}
window.NativeBridge.setTarget = NativeBridge.setTarget
window.NativeBridge.sendMessageToWeb = NativeBridge.sendMessageToWeb
const EditorStore = require('./stores/editor_store')
const Ctx = EditorStore.init()
EditorStore.hydrate($S.stores)
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
injectVerticalData()
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Editor = require('js/components/editor')
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
} else if (__IFRAME_EDITOR__) {
const ctx = window.parent._ctx.copy()
// iframe editor will use the same redux store as it's parent
const editorStore = window.parent._editor_store
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Site = require('js/components/MobileViewSite')
const SiteWrapper = wrapComponentWithReduxStore(
ctx.bootstrap(EditorBootstrap.bootstrap(Site)),
editorStore,
)
const EditorWithContext = ComponentKitContext(SiteWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
} else {
window.timerStart = window.timerStart || new Date().getTime()
window.timerCheck = label => {
const time = new Date().getTime() - timerStart
const msg = `#{label} in ${time}ms`
logger.log(msg)
return msg
}
const EditorStore = require('./stores/editor_store')
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
injectVerticalData()
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Editor = require('js/components/editor')
const Ctx = EditorStore.init()
EditorStore.hydrate($S.stores)
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
window._ctx = EditorStore.getCtx()
// to iframe editor can use the same redux store as it's parent
window._editor_store = editorStoreCreator.getStore()
const PageMetaStore = require('./stores/page_meta_store')
const CurrentUserStore = require('./stores/current_user_store')
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
if (module.hot) {
module.hot.accept(['js/components/editor'], () => {
const Editor = require('js/components/editor')
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
}
const isStrikinglyReseller =
CurrentUserStore.isResellerAgent() && !ConfStore.getIsSxl()
if (
!ConfStore.getInIosApp() &&
!ConfStore.getInWeChat() &&
(!PageMetaStore.isSiteOfResellerClient() ||
isStrikinglyReseller ||
ConfStore.getRollout('show_reseller_support_widget'))
) {
const SupportWidget = require('js/components/support_widget/SupportWidget')
ReactDOM.render(
<RHL.AppContainer>
<SupportWidget />
</RHL.AppContainer>,
document.getElementById('s-support-widget-container'),
)
}
})
}
})
.catch(error => {
// catch errors and call console.error so that they are reported by Bugsnag
console.error('You had an error: ', error.stack)
throw error
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
// like manifest webpack plugin, but fit multiple configs environment of webpack
// generate entries of each config, and collect them into a manifest json file
const path = require('path')
const fse = require('fs-extra')
module.exports = class EntriesGenerationWebpackPlugin {
constructor(opts) {
this.opts = Object.assign(
{
fileName: 'manifest.json',
appendMode: true,
transformExtensions: /^(gz|map)$/i,
},
opts
)
}
getFileType(str) {
str = str.replace(/\?.*/, '')
const split = str.split('.')
let ext = split.pop()
if (this.opts.transformExtensions.test(ext)) {
ext = `${split.pop()}.${ext}`
}
return ext
}
apply(compiler) {
compiler.plugin('compilation', compilation => {
compiler.plugin('after-emit', (compilation, compileCallback) => {
const publicPath =
this.opts.publicPath || compilation.options.output.publicPath
let files = compilation.chunks.reduce(
(files, chunk) =>
chunk.files.reduce((files, path) => {
let name
if (chunk.name) {
name = this.opts.setEntryName
? this.opts.setEntryName(chunk.name)
: chunk.name
name = `${name}.${this.getFileType(path)}`
} else {
name = path
}
return files.concat({
path,
chunk,
name,
isInitial: chunk.isInitial ? chunk.isInitial() : chunk.isOnlyInitial(),
isChunk: true,
isAsset: false,
isModuleAsset: false,
})
}, files),
[]
)
if (publicPath) {
files = files
.filter(file => file.path.indexOf('hot-update') === -1)
.map(file => {
file.path = publicPath + file.path
return file
})
}
const manifest = files.reduce((manifest, file) => {
manifest[file.name] = file.path
return manifest
}, {})
let json = JSON.stringify(manifest, null, 2)
const outputFolder = compilation.options.output.path
const outputFile = path.resolve(
compilation.options.output.path,
this.opts.fileName
)
const outputName = path.relative(outputFolder, outputFile)
compilation.assets[outputName] = {
source() {
return json
},
size() {
return json.length
},
}
if (this.opts.appendMode && fse.existsSync(outputFile)) {
const previousJson = JSON.parse(
fse.readFileSync(outputFile).toString()
)
json = JSON.stringify(Object.assign(previousJson, manifest), null, 2)
}
fse.outputFileSync(outputFile, json)
compileCallback()
})
})
}
}
// SPEC
// Owner: Dafeng
// Feature name: Font store
// * user can update fonts from choosing style
// NOTE:
// Specs here are all technical implementation. Will be ignored for the current QA.
import _ from 'lodash'
import Immutable from 'immutable'
import { EventEmitter } from 'events'
import EditorDispatcher from 'js/dispatcher/editor_dispatcher'
import EditorConstants from 'js/constants/editor_constants'
import BindingHelper from 'js/utils/helpers/binding_helper'
import weakmapMemoize from 'js/utils/weakmapMemoize'
const getDefaultFontName = require('js/utils/getDefaultFontName').default
let _bHelper
let _pDataHelper // need the binding to pageData to update fonts
const _updateFont = function(fontType, v) {
const name = `${fontType}Font`
_pDataHelper.setData(name, v)
_clearPreviewFont()
}
const _updateFontPreset = function(preset) {
_pDataHelper.setData('fontPreset', preset.id)
;['title', 'heading', 'body'].map(textType =>
_updateFont(textType, preset.fonts[textType]),
)
}
// No longer update fonts from style!
// _updateFontsFromStyle = (styles) ->
// _.map ['heading','body','title'], (font) =>
// _updateFont(font, styles[font + "Font"])
const _updatePreviewFont = (fontType, v) =>
_bHelper.setData(`preview.${fontType}Font`, v)
const _updatePreviewFontPreset = preset =>
['title', 'heading', 'body'].map(textType =>
_updatePreviewFont(textType, preset.fonts[textType]),
)
const _clearPreviewFont = function() {
const preview = _bHelper.binding.sub('preview')
const transaction = preview.atomically()
const object = preview.get().toJS()
for (const key in object) {
const v = object[key]
transaction.set(key, undefined)
}
transaction.commit()
}
const FontStore = _.assign({}, EventEmitter.prototype, {
// fontPreset is currently for Shangxianle, which designates font setting of
// title, heading, and body
// TODO - Andy, added 22 March 2016
// Improve the font determination logic:
// - first check if fontPreset is present, if so, use the preset setting
// - if no fontPreset, use setting for titleFont, bodyFont etc.
// Currently, fontPreset is just a collection of setting of the other three
// fonts, which means, if we modify the style setting of a preset, the pages
// that use this preset will NOT get style updates, unless they choose another
// preset and switch back.
_allFonts: null,
_initialFonts: null,
_fontsSelectedOnPageLoad: [],
getDefault(pageData) {
return {
preview: {
titleFont: '',
bodyFont: '',
headingFont: '',
fontPreset: '',
},
data: {
// use for notifying rich text component
titleFont: pageData.titleFont,
bodyFont: pageData.bodyFont,
headingFont: pageData.headingFont,
fontPreset: pageData.fontPreset,
},
}
},
init(b, pDataB) {
_bHelper = new BindingHelper(b)
const ConfStore = require('js/stores/conf_store')
if (__IN_EDITOR__ && !ConfStore.getIsBlog()) {
_pDataHelper = new BindingHelper(pDataB)
// Manually synchronize side_menu and page_data
// to avoid unnecessary render for side menu
;['headingFont', 'bodyFont', 'titleFont', 'fontPreset'].map(font =>
_pDataHelper.binding.addListener(font, changes =>
_bHelper.setData(`data.${font}`, _pDataHelper.getData(font)),
),
)
}
return _bHelper.binding
},
_setBHelperForTests(bHelper) {
return (_bHelper = bHelper)
},
loadFontsIfNotLoaded() {
if (_bHelper.getData('isLoadingFonts')) {
return
}
_bHelper.setData('isLoadingFonts', true)
return import('../../../config/fonts.json')
.then(fonts => {
this._setAllFonts(fonts)
return _bHelper.setData('isLoadingFonts', false)
})
.catch(() => _bHelper.setData('isLoadingFonts', false))
},
hydrate(fonts, pageData, initFonts) {
this._initialFonts = initFonts
_bHelper.binding
.atomically()
.set(Immutable.fromJS(this.getDefault(pageData)))
.commit({ notify: false })
return (this._fontsSelectedOnPageLoad = this._getUsedFonts())
},
getData(k) {
return _bHelper.binding.get(k)
},
getBinding() {
return _bHelper.binding
},
getFontName(type, options = {}) {
let preview = false
if (options.preview != null) {
;({ preview } = options)
}
if (preview) {
return _bHelper.getData(`preview.${type}Font`)
} else {
return _bHelper.getData(`data.${type}Font`)
}
},
getAvailableFonts() {
if (this._allFonts) {
return this._allFonts
} else {
return this._initialFonts
}
},
search(fontUsage, searchTerm) {
let matchAtBeginning = []
let generalMatch = []
const normalizeFontName = name => name.toLowerCase().replace(/ /g, '')
searchTerm = normalizeFontName(searchTerm)
this.getAvailableFonts().forEach(f => {
if (fontUsage === 'body' && f.disableBody) {
return
}
if (f.hidden) {
return
}
const name = normalizeFontName(f.displayName)
if (name.slice(0, searchTerm.length) === searchTerm) {
return matchAtBeginning.push(f)
} else if (name.indexOf(searchTerm) !== -1) {
return generalMatch.push(f)
}
})
matchAtBeginning = _.sortBy(matchAtBeginning, f => f.name)
generalMatch = _.sortBy(generalMatch, f => f.name)
return matchAtBeginning.concat(generalMatch).slice(0, 20)
},
_getSuggestedFonts() {
return this.getVisibleFonts().filter(f => f.isSuggested)
},
_getUsedFonts() {
return _([
this.getFontName('title'),
this.getFontName('heading'),
this.getFontName('body'),
])
.compact()
.uniq()
.map(name => this.getFontByName(name))
.value()
},
getSuggestedFonts(fontUsage) {
const usedFontsForOtherTextTypes = this._getUsedFonts()
const defaultFontName = getDefaultFontName(fontUsage)
const defaultFont = this.getFontByName(defaultFontName)
let popular = this._getSuggestedFonts().concat(
this._fontsSelectedOnPageLoad,
)
popular = _(popular)
.filter(font => font.name !== defaultFontName)
.sortBy(font => font.name)
.value()
let fonts = usedFontsForOtherTextTypes.concat([defaultFont]).concat(popular)
fonts = _(fonts)
.reject(f => {
if (fontUsage === 'body' && f.disableBody) {
return true
}
if (f.hidden) {
return true
}
})
.uniq(f => f.name)
.value()
return fonts
},
_setAllFonts(fonts) {
this._allFonts = fonts
},
_getVisibleFonts: weakmapMemoize(availableFonts =>
availableFonts.filter(f => !f.hidden),
),
getVisibleFonts() {
return this._getVisibleFonts(this.getAvailableFonts())
},
getTitleFonts() {
return this.getVisibleFonts()
},
getHeadingFonts() {
return this.getVisibleFonts()
},
_getBodyFonts: weakmapMemoize(visibleFonts =>
_.select(visibleFonts, f => !f.disableBody),
),
getBodyFonts() {
return this._getBodyFonts(this.getVisibleFonts())
},
getFontByName(name) {
return _.find(
this.getAvailableFonts(),
f => f.name.toLowerCase() === name.toLowerCase(),
)
},
getFont(textType, options) {
return _.find(
this.getAvailableFonts(),
item => item.name === this.getFontName(textType, options),
)
},
getSelectedFontPresetName() {
return _pDataHelper.getData('fontPreset')
},
// Look into preview fonts and actual font to determine which font to show
// 1. if preview has value 'default', use theme default
// 2. if preview has other values, use that
// 3. otherwise, use font in page data
getFontStyle(textType) {
const previewFontName = this.getFontName(textType, { preview: true })
if (previewFontName === 'default') {
// fallback to theme defaults
return {}
} else {
const font =
this.getFont(textType, { preview: true }) ||
this.getFont(textType, { preview: false })
if (font != null) {
return {
fontFamily: font.cssValue,
}
} else {
return {}
}
}
},
getFontClassNames() {
// Class names on #s-content, used for both blog post and site in order to set font
const fontClassNames = ['heading', 'title', 'body'].map(type => {
const fontName =
this.getFontName(type, { preview: true }) ||
this.getFontName(type, { preview: false })
const slug = fontName ? fontName.toSlug() : undefined
if (slug) {
return `s-font-${type}-${slug}`
} else {
return `s-font-${type}-default`
}
})
return fontClassNames.join(' ')
},
})
EditorDispatcher.register(payload => {
switch (payload.actionType) {
case EditorConstants.ActionTypes.SELECT_FONT:
_updateFont(payload.fontType, payload.value)
break
case EditorConstants.ActionTypes.SELECT_FONT_PRESET:
_updateFontPreset(payload.preset)
break
case EditorConstants.ActionTypes.PREVIEW_FONT:
_updatePreviewFont(payload.fontType, payload.value)
break
case EditorConstants.ActionTypes.PREVIEW_FONT_PRESET:
_updatePreviewFontPreset(payload.preset)
break
case EditorConstants.ActionTypes.CLEAR_PREVIEW_FONT:
_clearPreviewFont()
break
default:
break
}
})
// No longer update fonts from style!
// when EditorConstants.ActionTypes.APPLY_STYLES # when hover or select a style
// _updateFontsFromStyle(payload.styles)
if (!__SERVER_RENDERING__) {
if (!window.DEBUG) {
window.DEBUG = {}
}
window.DEBUG.FontStore = FontStore
}
export default FontStore
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "fresh",
"sectionSelections": {
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./fresh/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./fresh/section_selections/title/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./fresh/section_selections/columns/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
const fs = require('fs')
const oldFontsJson = fs.readFileSync(`${__dirname}/old.json`).toString()
let result = ''
currentFonts = eval(oldFontsJson)
currentFonts.push({
name: 'helvetica',
displayName: 'Helvetica',
cssValue: 'helvetica, arial',
cssFallback: 'sans-serif',
disableBody: false,
fontType: 'system',
settings: null,
hidden: false,
isSuggested: false,
},
// These fonts were removed from the list provided by the google fonts API,
// but we still need them in the list because people are already using them
{
"name": "amatica sc",
"displayName": "Amatica SC",
"cssValue": "\"amatica sc\"",
"cssFallback": "sans-serif",
"disableBody": true,
"fontType": "google",
"settings": {
"google_embed_name": "Amatica SC"
},
"hidden": false,
"isSuggested": false
},
{
"name": "droid sans",
"displayName": "Droid Sans",
"cssValue": "\"droid sans\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "droid sans mono",
"displayName": "Droid Sans Mono",
"cssValue": "\"droid sans mono\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "droid serif",
"displayName": "Droid Serif",
"cssValue": "\"droid serif\"",
"cssFallback": "serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "ek mukta",
"displayName": "Ek Mukta",
"cssValue": "\"ek mukta\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
})
// Usually blacklisted because they have many similar variations
const blacklistedFonts = [
'Slabo 13px',
'Slabo 27px',
'Baloo Bhai',
'Baloo Bhaina',
'Baloo Chettan',
'Baloo Da',
'Baloo Paaji',
'Baloo Tamma',
'Baloo Thambi',
'Hind Guntur',
'Hind Madurai',
'Hind Siliguri',
'Hind Vadodara',
].map(n => n.toLowerCase())
const newSuggestedFonts = [
'Unna',
'Bad Script',
'IBM Plex Serif',
'IBM Plex Sans',
'Roboto Slab',
'PT Serif',
'PT Sans',
'Libre Baskerville',
'Vidaloka',
'Share Tech',
'Changa One',
'Zilla Slab Highlight',
'Limelight',
'Bungee'
]
const fontsToDisableInBody = [
'Vidaloka',
'Share Tech',
'Changa One',
'Zilla Slab Highlight',
'Limelight',
'Bungee'
]
const fontsToReenableAsBody = ['lato']
fontsToRemoveFromSuggested = ['dosis', 'arapey', 'quando', 'trocchi']
fontsToUnhide = ['arapey', 'quando', 'trocchi']
googleFonts = JSON.parse(fs.readFileSync(`${__dirname}/googleFonts.json`)).items
/* eslint-disable max-statements */
googleFonts.forEach((font, i) => {
const name = font.family.toLowerCase()
const existingFontItem = currentFonts.filter(f => f.name === name)[0]
if (existingFontItem) {
if (fontsToRemoveFromSuggested.indexOf(existingFontItem.name) !== -1) {
existingFontItem.isSuggested = false
}
if (fontsToUnhide.indexOf(existingFontItem.name) !== -1) {
existingFontItem.hidden = false
}
if (fontsToReenableAsBody.indexOf(existingFontItem.name) !== -1) {
existingFontItem.disableBody = false
}
// Keep value from existing manually curated list of fonts
return
}
if (blacklistedFonts.indexOf(font.family.toLowerCase()) !== -1) {
console.log('font blacklisted:', font.family)
return
}
if (font.subsets.indexOf('latin') === -1) {
// console.log("Excluding font b/c no latin", font.family)
return
}
const fallback = {
'sans-serif': 'sans-serif',
serif: 'serif',
handwriting: 'cursive',
display: 'sans-serif',
monospace: 'sans-serif',
}[font.category]
if (!fallback) {
console.log('No fallback for font: ', font)
}
const newFont = {
name,
displayName: font.family,
cssValue: `"${name}"`,
cssFallback: fallback,
disableBody: font.category === 'display' || fontsToDisableInBody.includes(font.family),
fontType: 'google',
settings: null,
hidden: false,
isSuggested: newSuggestedFonts.indexOf(font.family) !== -1,
// "category": font.category
}
if (font.variants.indexOf('regular') === -1) {
// some fonts don't have a regular font, and won't load
// if you don't specify a specific weight
if (!newFont.settings) {
newFont.settings = {}
}
newFont.settings.weight = font.variants.join(',')
console.log(font.family)
}
let canAutogenerateEmbedName = true
// We try to auto-generate the embed by capitalizing the first
// character of each word, but that doesn't work for fonts like
// "PT Something", so store the embed name explicitly
if (/[A-Z]{2,10}/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "Waiting for the Sunrise"
if (/ [a-z]/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "BenchNine"
if (/[A-Z][a-z]+[A-Z]/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "permanent marker"
if (/(^[^A-Z])|( [a-z])/.test(font.family)) {
canAutogenerateEmbedName = false
}
if (!canAutogenerateEmbedName) {
if (!newFont.settings) {
newFont.settings = {}
}
newFont.settings.google_embed_name = font.family
}
currentFonts.push(newFont)
})
/* eslint-enable max-statements */
result += JSON.stringify(currentFonts, null, 4)
fs.writeFileSync('./config/fonts.json', result)
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "glow",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./glow/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./glow/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./glow/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./glow/section_selections/blog/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./glow/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./glow/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./glow/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./glow/section_selections/signup_form/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./glow/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "ion",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./ion/section_selections/navbar/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./ion/section_selections/slider/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./ion/section_selections/banner/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "minimal",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./minimal/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./minimal/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./minimal/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./minimal/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./minimal/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./minimal/section_selections/html/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./minimal/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./minimal/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./minimal/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./minimal/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./minimal/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./minimal/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./minimal/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "onyx_new",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./onyx_new/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./onyx_new/section_selections/ecommerce/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./onyx_new/section_selections/cta/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./onyx_new/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./onyx_new/section_selections/icons/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./onyx_new/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./onyx_new/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
{
"private": true,
"name": "bobcat-monorepo",
"version": "0.0.0",
"description": "Manages the frontend dependencies",
"engines": {
"node": "^6.9"
},
"scripts": {
"test-ci": "jest --config jest-config.json --ci && jest r-jaguar/ --ci",
"test": "jest --config jest-config.json --verbose --watch",
"devServer": "bash pre-check && node ./devServer.js",
"dev:miniprogram": "bash pre-check && CONFIGS=miniprogram node devServer.js",
"fe": "bash pre-check && SOURCE_MAP=0 npm run dll && npm run start-all",
"dll": "webpack --config webpack.dll.js",
"dev": "bash pre-check && node pack-master.js && PACKMASTER=1 MODE=v4-style CONFIGS=app,site,v4-style,miniprogram,iframe-editor,native-web,native-ipad,component node --max-old-space-size=8192 ./devServer.js",
"start-all": "forego start -f Procfile.fe",
"editor-debug": "bash pre-check && node ./fe/editor-mockup-server/server.js & CONFIGS=editor-debug,site MODE=editor-debug ASSET_HOST=https://localhost:8080 node ./devServer.js",
"app": "changelog-reminder && bash pre-check && MODE=v4-style CONFIGS=app,site,v4-style,component node --max-old-space-size=8192 ./devServer.js",
"mobile": "bash pre-check && CONFIGS=iframe-editor,native-web,native-ipad node ./devServer.js",
"prerender": "bash pre-check && CONFIGS=prerender SERVER_RENDERING=1 node --max-old-space-size=8192 ./node_modules/.bin/webpack --config webpack.config.js --watch",
"prerender:server": "(cd ./r-jaguar && yarn install && yarn dev)",
"clean": "rm -rf app/assets/javascripts/v4 tmp/cache 2>/dev/null || true",
"smoke_test": "NODE_ENV=UAT PRODUCT_NAME=strikingly node_modules/.bin/protractor ./spec/frontend/protractor.conf.smoke_test.js",
"prestart": "npm run clean && webpack --config webpack.dll.js && CONFIGS=app,site,prerender node ./devServer.js --watch 2>&1 >log/webpack.log",
"precommit": "bash pre-check && webcube-precommit",
"precommit:custom": "lerna run precommit:custom && bash ./pre-commit",
"update": "npm run clear:deps && npm run clean && yarn install && echo 6.0 > .project-version",
"upgrade": "npm run clear:lock && npm run update",
"clear:lock": "rm yarn.lock package-lock.json 2>/dev/null || true",
"clear:deps": "lerna clean --yes 2>/dev/null || true && rm -rf ./node_modules 2>/dev/null || true && lerna exec --bail=false -- rm yarn.lock package-lock.json 2>/dev/null || true && rm .git/hooks/pre-commit 2>/dev/null || true",
"lint": "webcube-lint",
"lint:error": "webcube-lint-error",
"lint:miniprogram": "tslint --fix fe/nextgen/miniprogram/*/**.ts fe/nextgen/miniprogram/*/**.tsx",
"generate-i18n": "bash pre-check && NODE_ENV=i18n CONFIGS=app webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=site webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=prerender webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=mobile-editor webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=native-web webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=native-ipad webpack --config webpack.config.js"
},
"lint-staged": {
"*.{js,jsx,es6}": [
"npm run lint:error --",
"git add"
]
},
"config": {
"webcube": {
"monorepo": {
"root": "./"
}
},
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"workspaces": [
"fe-packages/*",
"component-kit/",
"fe-apps/*"
],
"keywords": [],
"author": "Dafeng Guo @dfguo",
"devDependencies": {
"@types/es6-shim": "^0.31.32",
"@types/immutable": "^3.8.7",
"@types/isomorphic-fetch": "^0.0.33",
"@types/jest": "^21.1.2",
"@types/lodash": "^4.14.61",
"@types/normalizr": "^2.0.18",
"@types/react": "^16.0.4",
"@types/react-dom": "^16.0.4",
"@types/react-native": "^0.42.14",
"@types/react-redux": "^4.4.38",
"@types/react-router": "^2.0.49",
"@types/react-test-renderer": "^16.0.1",
"@types/redux-form": "^6.3.6",
"@types/redux-immutable": "^3.0.33",
"@types/redux-logger": "^3.0.5",
"@types/redux-ui": "^0.0.9",
"@types/reselect": "^2.0.27",
"@types/webpack-env": "^1.13.1",
"autoprefixer": "^7.1.4",
"awesome-typescript-loader": "^5.2.0",
"babel-core": "^6.26.3",
"babel-jest": "^21.2.0",
"babel-loader": "^7.1.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dynamic-import-webpack": "^1.0.1",
"babel-plugin-es6-promise": "^1.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-react-transform": "^3.0.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-syntax-optional-chaining": "^7.0.0-alpha.13",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-function-bind": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-entries": "^1.0.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-constant-elements": "^6.5.0",
"babel-plugin-transform-react-inline-elements": "^6.6.5",
"babel-plugin-transform-react-remove-prop-types": "^0.2.6",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.24.1",
"babel-runtime": "^6.26.0",
"bundle-loader": "^0.5.6",
"cache-loader": "^1.2.2",
"chai": "^3.3.0",
"chai-as-promised": "^5.3.0",
"changelog-reminder": "^0.5.4",
"cjsx-loader": "^3.0.0",
"codeclimate-test-reporter": "^0.3.3",
"coffee-loader": "^0.7.2",
"coffee-react": "^2.4.1",
"coffee-react-transform": "4.0.0",
"coffee-script": "^1.12.4",
"cross-spawn": "5.0.1",
"css-loader": "^1.0.0",
"cssnano": "^4.0.0-rc.2",
"cz-conventional-changelog": "^2.0.0",
"expose-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"eyes.protractor": "0.0.52",
"eyes.sdk": "0.0.44",
"file-loader": "^1.1.11",
"glob": "^7.1.2",
"gulp-util": "^3.0.8",
"haml-coffee": "^1.14.1",
"hamlc-loader": "git://github.com/strikingly/hamlc-loader.git#ed5497b0d8e469bf9dfeecc3291e7a2410de989e",
"happypack": "^5.0.0",
"img-loader": "^3.0.0",
"imports-loader": "^0.6.5",
"inquirer": "^3.3.0",
"jed": "1.1.0",
"jest": "^21.2.1",
"js-yaml": "^3.10.0",
"json-loader": "0.5.1",
"json5-loader": "^1.0.1",
"jstransform-loader": "^0.2.0",
"jsx-loader": "^0.12.0",
"jsxgettext": "^0.10.2",
"memory-stats": "^1.0.2",
"mocha": "^2.3.3",
"mochawesome": "^1.3.4",
"po-loader": "0.2.1",
"po2json": "^0.4.1",
"pofile": "^1.0.8",
"postcss-loader": "^2.1.6",
"progress-bar-webpack-plugin": "^1.11.0",
"protractor": "^3.3.0",
"react-addons-perf": "~15.4",
"react-hot-loader": "3.0.0-beta.7",
"react-render-visualizer-decorator": "^0.3.0",
"react-templates": "git://github.com/strikingly/react-templates.git#fe2e253e7a5619c87930f733c745d2ab8a66b52a",
"react-templates-loader": "^0.3.0",
"react-test-renderer": "^16.2.0",
"redux-mock-store": "^1.5.1",
"run-sequence": "^2.2.0",
"sinon": "^1.17.1",
"style-loader": "^0.13.1",
"sw-precache-webpack-plugin": "^0.11.5",
"ts-jest": "^19.0.0",
"ts-loader": "^4.4.2",
"tslint": "^5.8.0",
"tslint-config-airbnb": "^5.2.1",
"url-loader": "^1.0.1",
"webpack": "^4.16.0",
"webpack-build-notifier": "^0.1.21",
"webpack-bundle-analyzer": "^2.1.1",
"webpack-dev-server": "^3.1.4",
"webpack-manifest-plugin": "^2.0.3",
"webpack-parallel-uglify-plugin": "^1.1.0",
"winston": "^2.4.0",
"yargs": "^9.0.1"
},
"dependencies": {
"accounting": "^0.4.1",
"axios": "^0.18.0",
"babel-plugin-emotion": "^7.3.2",
"bugsnag-js": "^4.4.0",
"classnames": "^2.2.0",
"cloudinary": "git://github.com/strikingly/cloudinary_npm.git#99781dc529f42edd6abda39f2fc463781da2d43c",
"component-kit": "0.0.0",
"concurrent-transform": "^1.0.0",
"console-polyfill": "^0.2.1",
"core-decorators": "^0.20.0",
"create-react-class": "^15.6.0",
"deasync": "^0.1.4",
"deepmerge": "^2.0.1",
"emotion": "^7.3.2",
"es5-shim": "^4.1.1",
"es6-shim": "^0.33.13",
"escape-html": "^1.0.3",
"eventproxy": "^0.3.2",
"fast-json-patch": "^0.5.4",
"fastclick": "^1.0.6",
"firebase": "^2.2.7",
"flux": "^2.0.1",
"flux-standard-action": "^1.0.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"fs-extra": "^4.0.2",
"gulp": "^3.9.1",
"gulp-awspublish": "^3.3.0",
"hifetch": "^2.1.3",
"history": "~1.13.1",
"hoist-non-react-statics": "^2.3.1",
"hypernova": "^2.0.0",
"hypernova-function": "git://github.com/strikingly/hypernova-function",
"hypernova-morearty": "git://github.com/strikingly/hypernova-morearty",
"hypernova-react": "^2.0.0",
"ie-version": "^0.1.0",
"immutable": "github:strikingly/immutable-js#6fecef1e909bfe8eba4908c3f5aa4f8e126ce163",
"in-viewport": "^3.4.1",
"invariant": "^2.2.1",
"is_js": "^0.7.3",
"isomorphic-fetch": "^2.2.1",
"isomorphic-style-loader": "^3.0.0",
"jquery-ui": "github:strikingly/jquery-ui#2f07621e04f92f1115d6385851df5b87605f60d0",
"js-cookie": "^2.0.2",
"json-stable-stringify": "^1.0.0",
"jsonschema": "^1.1.1",
"keymirror": "~0.1.0",
"lazysizes": "^3.0.0",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"lodash": "^3.3.1",
"lodash.unionby": "^4.6.0",
"md5": "^2.2.1",
"merge-stream": "^1.0.1",
"moment": "^2.10.2",
"morearty": "github:strikingly/moreartyjs#2a7d0defada0285b5f2f52e880401dbea7d3de02",
"mousetrap": "^1.5.3",
"mutexify": "^1.2.0",
"normalizr": "^2.1.0",
"object-assign": "^2.0.0",
"pako": "^1.0.5",
"parse-domain": "^1.1.0",
"pikaday": "^1.4.0",
"promise-loader": "^1.0.0",
"prop-types": "^15.6.1",
"prop-validation-mixin": "^0.1.0",
"qrcode.react": "^0.8.0",
"r-i18n": "0.0.10",
"ramda": "^0.24.1",
"rc-queue-anim": "^1.2.3",
"rc-tween-one": "^1.4.5",
"react": "^16.2.0",
"react-addons-pure-render-mixin": "^15.1",
"react-addons-shallow-compare": "^15.1",
"react-autocomplete": "^1.7.1",
"react-color": "^2.13.8",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "git://github.com/strikingly/react-dnd-html5-backend.git#a77e5a50ff7c08d3bef40b7de1796837be5e00be",
"react-dnd-touch-backend": "^0.3.3",
"react-dom": "^16.2.0",
"react-dom-factories": "^1.0.2",
"react-draggable": "^3.0.0",
"react-dropdown": "^1.1.0",
"react-emotion": "^7.3.2",
"react-helmet": "^5.2.0",
"react-highlight-words": "^0.8.0",
"react-immutable-proptypes": "^1.7.0",
"react-list": "^0.8.8",
"react-mixin": "^1.7.0",
"react-motion": "^0.4.5",
"react-navigation-controller": "^3.0.1",
"react-portal": "^3.0.0",
"react-redux": "^5.0.6",
"react-router": "^3.0.1",
"react-router-redux": "^4.0.0",
"react-select": "^1.0.0-beta14",
"react-slick": "git://github.com/strikingly/react-slick.git#7978c8f53ab554a590af94cdec859d5d9a9546bc",
"react-sortable-hoc": "git://github.com/dfguo/react-sortable-hoc.git#f99110e52dfd30eb44885139988b6558213fee29",
"react-sound": "github:Arthraim/react-sound#55052af354244b6271e16bd8b1596f5f998fbeda",
"react-tap-event-plugin": "^3.0.2",
"react-tappable": "^1.0.2",
"react-timer-mixin": "^0.13.3",
"react-transition-group": "^2.2.1",
"recompose": "^0.26.0",
"redux": "^3.7.2",
"redux-api-middleware": "^1.0.2",
"redux-cube": "^1.0.0-rc.12",
"redux-form": "^6.5.0",
"redux-immutable": "git://github.com/dfguo/redux-immutable.git#4a5230e1591a8de968ea7accb928bc0627b81b3d",
"redux-logger": "^3.0.6",
"redux-source-immutable": "^0.2.2",
"redux-thunk": "^2.2.0",
"redux-ui": "git://github.com/dfguo/redux-ui.git#79465231c03dfe34ce5ee62a9549e9ad0f44572e",
"reselect": "^3.0.1",
"rxjs": "^5.5.6",
"sha1": "^1.1.1",
"shallow-equal": "^1.0.0",
"sw-toolbox": "^3.6.0",
"typescript": "^2.2.2",
"uglify-js": "2.7.5",
"uuid": "^2.0.1",
"validator": "^9.1.1",
"walk": "^2.3.9",
"waypoints": "^4.0.0",
"webcube": "^1.0.0-alpha.37",
"webfontloader": "^1.6.27"
},
"optionalDependencies": {
"fsevents": "^1.1.1"
}
}
import 'js/loadBugsnag'
import './init'
import $ from 'jquery'
import React from 'react'
import ReactDOM from 'react-dom'
import logger from 'js/utils/logger'
import 'js/utils/extensions/native'
import bootstrapSite from 'js/prerender/bootstrapSite'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import * as RHL from 'react-hot-loader'
// not widely used, disable it for now
// require('./registerPWA.es6')
// These two are direct reads so promises can be fired quicker
const themeName = $S.stores.pageMeta.theme.name
const forcedLocale = $S.stores.pageMeta.forced_locale
// Load dynamic theme style files on editor debug environment
if (__MODE__ === "'editor-debug'") {
require('reset')
require(`themes/${themeName}/main_v4`)
}
const p1 = import(`locales/${i18nHelper.getTranslationFile(
forcedLocale,
)}`)
const p2 = import(`manifests/themes/${themeName}.js`)
const p3 = import(`manifests/verticals/${themeName}.js`)
Promise.all([p1, p2, p3])
.then((...args) => {
const [poFile, manifest, verticalData] = Array.from(args[0])
if (!window.timerStart) {
window.timerStart = new Date().getTime()
}
window.timerCheck = function(label) {
const msg = `${label} in ${new Date().getTime() - timerStart}ms`
console.log(msg)
}
const BootstrappedSite = bootstrapSite({
poFile,
manifest,
verticalData,
})
$(() => {
require('./v3_bridge')()
ReactDOM.render(
<BootstrappedSite />,
document.getElementById('s-page-container'),
)
const ConfStore = require('js/stores/conf_store')
if (ConfStore.isStrikinglyLiveChatEnabled()) {
const SupportWidget = require('js/components/support_widget/SupportWidget')
ReactDOM.render(
<RHL.AppContainer>
<SupportWidget />
</RHL.AppContainer>,
document.getElementById('s-support-widget-container'),
)
}
})
})
.catch(e => console.error(e))
// catch errors and call console.error so that they are reported by Bugsnag
let defaultExport = {}
if (__IN_EDITOR__ || __SERVER_RENDERING__) {
class PageAnalyticsEngine {
init() {}
logPageView() {}
}
defaultExport = new PageAnalyticsEngine()
} else {
const $ = require('jquery')
const gaq = require('gaq')
const Keen = require('js/vendor/keen-loader.js')
const _ = require('lodash')
const ConfStore = require('js/stores/conf_store')
const PageMetaStore = require('js/stores/page_meta_store')
const PageDataStore = require('js/stores/page_data_store')
const _b = require('js/utils/lodashb')
const logger = require('js/utils/logger')
const ScriptHelper = require('js/utils/helpers/ScriptHelper')
const VideoHelper = require('js/utils/helpers/video_helper')
const ReferrerParser = require('js/utils/referrer_parser')
const edit_page = require('js/v3_bridge/edit_page_bridge')
const EcommerceGoogleAnalytics = require('js/v3_bridge/EcommerceGoogleAnalytics')
const PageAnalyticsEngine = class PageAnalyticsEngine {
constructor() {
this.logPageView = this.logPageView.bind(this)
this.logSocialClicks = this.logSocialClicks.bind(this)
this.normalizedReferrer = this.normalizedReferrer.bind(this)
this.sendPbsImpression = this.sendPbsImpression.bind(this)
this.sendPbsConversion = this.sendPbsConversion.bind(this)
this.sendDataKeenIO = this.sendDataKeenIO.bind(this)
}
init(data) {
let isReturnVisit, visitorId
if ($.cookie('__strk_visitor_id')) {
visitorId = $.cookie('__strk_visitor_id')
isReturnVisit = true
} else {
// a common way to generate a random id
visitorId = 'visotor-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(
/[xy]/g,
c => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
},
)
isReturnVisit = false
$.cookie('__strk_visitor_id', visitorId, { expires: 365 })
}
this.baseData = data || {
pageId: PageMetaStore.getId(),
userId: PageMetaStore.getUser().get('id'),
permalink: PageMetaStore.getPermalink(),
membership: PageMetaStore.getUser().get('membership'),
createdAt: PageMetaStore.getCreatedAt(),
strikinglyBranding: PageMetaStore.getShowStrikinglyLogo(),
}
this.baseData = _.extend(this.baseData, {
visitorId,
isReturnVisit,
referrer: document.referrer,
})
if (ConfStore.getIsBlog()) {
this._internals = {
user: {
id: PageMetaStore.getUserId(),
membership: PageMetaStore.getMemberShip(),
},
page: {
id: PageMetaStore.getId(),
url() {
return window.location.href
},
googleAnalyticsTracker: PageMetaStore.getGoogleAnalyticsTracker(),
googleAnalyticsType: PageMetaStore.getGoogleAnalyticsType(),
facebookPixelId: PageMetaStore.getFacebookPixelId(),
},
}
} else {
this._internals = {
user: {
id: PageMetaStore.getUser().get('id'),
membership: PageMetaStore.getUser().get('membership'),
},
page: {
id: PageMetaStore.getId(),
url() {
return window.location.href
},
theme: PageMetaStore.getTheme().get('name'),
strk_upvt: PageMetaStore.getStrkUpvt(),
strkGaTracker: PageMetaStore.getStrkGaTracker(),
googleAnalyticsTracker: PageMetaStore.getGoogleAnalyticsTracker(),
googleAnalyticsType: PageMetaStore.getGoogleAnalyticsType(),
facebookPixelId: PageMetaStore.getFacebookPixelId(),
},
}
}
_b.traverseObj(this._internals, h => {
for (const k in h) {
const v = h[k]
if (_.isUndefined(v)) {
console.warn(`${k} is undefned`)
}
}
})
this.strikinglyGoogleAnalyticsEnabled = ConfStore.isGoogleAnalyticsEnabled()
this.strikinglyKeenAnalyticsEnabled = ConfStore.isKeenAnalyticsEnabled()
this.setupGA()
return this.setupFB()
}
setSocialShareTracking() {
// subscribe to facebook like event
edit_page.Event.subscribe('Site.facebook.edge.create', () =>
this.trackSocialMediaShare('facebook', 'like'),
)
// subscribe to linkedin share event
edit_page.Event.subscribe('Site.linkedin.share', () =>
this.trackSocialMediaShare('linkedin', 'share'),
)
// subscribe to twitter tweet event
edit_page.Event.subscribe('Site.twitter.tweet', () =>
this.trackSocialMediaShare('twitter', 'tweet'),
)
// subscribe to G+ +1 event
return edit_page.Event.subscribe('Site.gplus.plusone', () =>
this.trackSocialMediaShare('gplus', 'plusone'),
)
}
userGoogleAnalyticsEnabled() {
return Boolean(
this._internals != null
? this._internals.page.googleAnalyticsTracker
: undefined,
)
}
userUniversalAnalyticsEnabled() {
return this._internals.page.googleAnalyticsType === 'universal'
}
setupGA() {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log(
'[GA] Setup internal GA: ',
this._internals.page.strkGaTracker,
)
if (typeof __ga === 'function') {
__ga('create', this._internals.page.strkGaTracker, {
name: 'strk',
cookieDomain: 'auto',
})
__ga('strk.set', 'anonymizeIp', true)
}
}
if (this.userGoogleAnalyticsEnabled()) {
logger.log(
'[GA] Setup GA for user: ',
this._internals.page.googleAnalyticsTracker,
)
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Initialize Universal Analytics')
window.ga = __ga
if (typeof ga === 'function') {
ga('create', this._internals.page.googleAnalyticsTracker, 'auto')
ga('set', 'anonymizeIp', true)
}
} else {
logger.log('[GA] Initialize Classic Analytics')
gaq.push(['_setAccount', this._internals.page.googleAnalyticsTracker])
gaq.push(['_gat._anonymizeIp'])
}
}
}
setupFB() {
if (this._internals.page.facebookPixelId && !this._fbPixelLoaded) {
if (!window._strk_fbq) {
ScriptHelper.loadFB()
}
window._strk_fbq('init', this._internals.page.facebookPixelId)
this._fbPixelLoaded = true
return this._fbPixelLoaded
}
}
// TODO
// https://www.facebook.com/business/help/952192354843755
trackPageViewOnGA(path) {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log('[GA] Send page view to internal GA')
if (typeof __ga === 'function') {
__ga('strk.send', 'pageview', { 'anonymizeIp': true })
}
}
if (this.userGoogleAnalyticsEnabled()) {
if (path) {
const query = location.search || ''
path += query
}
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Send page view to user GA (Universal)')
if (path) {
if (typeof ga === 'function') {
ga('set', 'page', path)
}
}
if (typeof ga === 'function') {
ga('send', 'pageview', { 'anonymizeIp': true })
}
} else {
logger.log('[GA] Send page view to user GA (Classic)')
const params = ['_trackPageview']
if (path) {
params.push(path)
}
gaq.push(params)
}
}
}
trackPageEventOnGA(
category,
action,
label = null,
value = null,
eventData = {},
) {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log(
'[GA] Send page event to internal GA: ',
category,
action,
label,
value,
)
if (typeof __ga === 'function') {
__ga('strk.send', 'event', category, action, label, value, { 'anonymizeIp': true })
}
}
if (this.userGoogleAnalyticsEnabled()) {
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Send page event to user GA (Universal)')
if (typeof ga === 'function') {
ga('send', 'event', category, action, label, value, { 'anonymizeIp': true })
}
} else {
logger.log('[GA] Send page event to user GA (Classic)')
let i = 1
for (const k in eventData) {
const v = eventData[k]
gaq.push(['_setCustomVar', i, k, v, 3])
++i
}
gaq.push(['_trackEvent', category, action, label, value])
}
}
}
trackPageEventOnFB(name, options = {}) {
if (window._strk_fbq) {
window._strk_fbq('track', name, options)
}
}
// TODO
// https://www.facebook.com/business/help/952192354843755
trackPageEvent() {
const trackButton = (eventName, options) => {
const that = this
return function(event) {
const t = $(this)
const data = {
url: t.attr('href'),
target: t.attr('target'),
text: t.text(),
}
edit_page.Event.publish(eventName, data)
that.trackPageEventOnGA(
'Action',
options.gaEventName,
data.text,
null,
{
url: data.url,
text: data.text,
},
)
// no need to refresh page when link to section or page
const hash =
typeof data.url === 'string' && _.startsWith(data.url, '#')
const pageLink =
typeof data.url === 'string' &&
(t[0].hostname === window.location.hostname ||
_.startsWith(data.url, '/'))
const isVideoThatOpensInOverlay = VideoHelper.needToTriggerHelper(
data.url,
)
if (
data.url &&
data.target !== '_blank' &&
!hash &&
!pageLink &&
!isVideoThatOpensInOverlay
) {
event.preventDefault()
setTimeout(() => (window.location.href = data.url), 500)
}
}
}
$('.s-button .s-common-button').click(
trackButton('Site.button.click', { gaEventName: 'ButtonClick' }),
)
}
trackSocialMediaShare(channel, action, data = null) {
return this.trackUserPageEvent(
ConfStore.getKeenIoPageSocialShareCollection(),
{
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
channel,
action,
data,
},
)
}
trackPageFraming(domain) {
return this.trackUserPageEvent(
ConfStore.getKeenIoPageFramingCollection(),
{
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
category: this._internals.page.category,
},
},
)
}
logPageView(extraEventData) {
this.trackPageViewOnGA()
this.trackPageEventOnFB('PageView')
// fix for single page high bounce rate issue
// http://aroundanalytics.com/trick-to-solve-high-bounce-rate-of-google-analytics-in-a-one-page-site/
setTimeout(() => this.trackPageEventOnGA('Control', 'Bounce Rate'), 30000)
return this.sendDataKeenIO(_.extend({}, this.baseData, extraEventData))
}
logSocialClicks(channel) {
let data
return (data = _.extend(
{ eventName: 'SocialClicks', channel },
this.baseData,
))
}
normalizedReferrer(referrer_url) {
// TODO: rewrite referrer parser
const REFERRER_SOURCE = require('../data/referrer_source.json')
const referrerParser = new ReferrerParser(REFERRER_SOURCE, referrer_url)
// Don't use I18n here. We are writing data to keenio, use i18n here will
// fuck up the data.
return (
(referrerParser.referrer != null
? referrerParser.referrer.name
: undefined) ||
referrerParser.url ||
'Direct Traffic'
)
}
sendPbsImpression(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('[PBS] Impression', data)
return Keen.addEvent(ConfStore.getKeenIoPbsImpressionCollection(), data)
}
sendPbsConversion(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('[PBS] Conversion', data)
return Keen.addEvent(ConfStore.getKeenIoPbsConversionCollection(), data)
}
trackUserPageEvent(collection, data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('User Page Event Tracking', collection, data)
return Keen.addEvent(collection, data)
}
trackEcommerceBuyerEvent(name, data) {
data = _.extend(
{
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
},
data,
)
return this.trackUserPageEvent(name, data)
}
sendDataKeenIO(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
const referrerHost = data.referrer.split('/')[2]
let pageview = _.extend(
{
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
host: document.location.host,
// parse the referrer url to get the host name of referrer
referrer_host: referrerHost,
normalized_referrer: this.normalizedReferrer(data.referrer),
},
data,
)
// For multi-page analytics dashboard
// Never add these extra fields for blog
if (!ConfStore.getIsBlog()) {
pageview = _.extend(
{
is_multipage: PageDataStore.getIsMultiPage(),
page_uid: PageDataStore.getCurrentPageUID(),
},
pageview,
)
}
// Keen.addEvent($S.conf.keenio_collection_sharding, pageview)
// TODO: delete it
pageview.sharding = true
return Keen.addEvent($S.conf.keenio_collection, pageview)
}
trackFileDownload(fileID) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
const trackData = {
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
file_id: fileID,
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
}
logger.log('File Download', trackData)
return Keen.addEvent(
ConfStore.getKeenIoFileDownloadCollection(),
trackData,
)
}
}
PageAnalyticsEngine.prototype.pingInterval = 10000
_.extend(PageAnalyticsEngine.prototype, EcommerceGoogleAnalytics)
defaultExport = new PageAnalyticsEngine()
}
export default defaultExport
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "persona",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./persona/section_selections/navbar/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./persona/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./persona/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./persona/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./persona/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./persona/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./persona/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./persona/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./persona/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./persona/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./persona/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./persona/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./persona/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./persona/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./persona/section_selections/signup_form/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./persona/section_selections/block/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./persona/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "personal",
"sectionSelections": {
"contact": {
"sectionId": "contact_form",
"category": "suggested",
"displayName": "Contact",
"description": "Let viewers drop their name, email, and message.",
"position": 10,
"content": require("./personal/section_selections/contact/content.json"),
},
"bio": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Bio",
"description": "List experiences, schools, projects, or anything!",
"position": 2,
"content": require("./personal/section_selections/bio/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "suggested",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./personal/section_selections/social_feed/content.json"),
},
"intro": {
"sectionId": "title",
"category": "suggested",
"displayName": "Intro",
"description": "Write a blurb about yourself.",
"position": 1,
"content": require("./personal/section_selections/intro/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "suggested",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 7,
"content": require("./personal/section_selections/gallery/content.json"),
},
"projects": {
"sectionId": "media",
"category": "suggested",
"displayName": "Projects",
"description": "Show a big video or image. Or add many of them.",
"position": 5,
"content": require("./personal/section_selections/projects/content.json"),
},
"resume": {
"sectionId": "cta",
"category": "suggested",
"displayName": "Resume",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./personal/section_selections/resume/content.json"),
},
"education": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Education",
"description": "List experiences, schools, projects, or anything!",
"position": 4,
"content": require("./personal/section_selections/education/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "suggested",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"recommendations": {
"sectionId": "columns",
"category": "suggested",
"displayName": "Recommendations",
"description": "List your projects, clients, features, team, or anything!",
"position": 8,
"content": require("./personal/section_selections/recommendations/content.json"),
},
"experiences": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Experience",
"description": "List experience, schools, projects, or anything!",
"position": 3,
"content": require("./personal/section_selections/experiences/content.json"),
},
"skills": {
"sectionId": "info",
"category": "suggested",
"displayName": "Skills",
"description": "Show skills, stats, or tidbits.",
"position": 6,
"content": require("./personal/section_selections/skills/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./personal/section_selections/hero/content.json"),
},
"connect": {
"sectionId": "icons",
"category": "suggested",
"displayName": "Connect",
"description": "A list of small icons. Good for social media.",
"position": 9,
"content": require("./personal/section_selections/connect/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "perspective",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./perspective/section_selections/navbar/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./perspective/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./perspective/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./perspective/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./perspective/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./perspective/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./perspective/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./perspective/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./perspective/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./perspective/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./perspective/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./perspective/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./perspective/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./perspective/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./perspective/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./perspective/section_selections/signup_form/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./perspective/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "pitch_new",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./pitch_new/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./pitch_new/section_selections/ecommerce/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./pitch_new/section_selections/info/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "profile",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./profile/section_selections/navbar/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./profile/section_selections/title/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "s5-theme",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./s5-theme/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./s5-theme/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./s5-theme/section_selections/html/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./s5-theme/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./s5-theme/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./s5-theme/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./s5-theme/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "sleek",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./sleek/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./sleek/section_selections/ecommerce/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "spectre",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./spectre/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./spectre/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./spectre/section_selections/html/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./spectre/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./spectre/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./spectre/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./spectre/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "zine",
"sectionSelections": {
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("./base/section_selections/media/content.json"),
} }
};
import init from './init'
/* eslint-disable import/first */
init()
import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import 'js/reactInit.es6'
import Immutable from 'immutable'
import { createStore, applyMiddleware, compose } from 'redux'
import { combineReducers } from 'redux-immutable'
import { Provider } from 'react-redux'
import {
Router,
Route,
IndexRedirect,
IndexRoute,
browserHistory,
} from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { Iterable } from 'immutable'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import 'js/vendor/jquery/browser'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import reducers from 'nextgen/app/reducers'
import BlogManager from 'nextgen/blog/manager/Manager'
import BlogToWechatPreview from 'nextgen/blog/BlogToWechatPreview'
import SelectTemplate from 'nextgen/app/scenes/SelectTemplate'
import LiveChat from 'nextgen/app/scenes/LiveChat'
import Analytics from 'nextgen/app/scenes/Analytics'
import Preview from 'nextgen/app/scenes/Preview'
import ResellerDashboard from 'nextgen/app/scenes/ResellerDashboard'
import Domains from 'nextgen/app/scenes/Domains'
import Domain from 'nextgen/app/scenes/Domain'
import DomainPurchase from 'nextgen/app/scenes/DomainPurchase'
import DonationManager from 'nextgen/app/scenes/donation/DonationManager'
import ClientTab from 'nextgen/app/scenes/resellerDashboard/ClientTab'
import BillingTab from 'nextgen/app/scenes/resellerDashboard/BillingTab'
import AddCreditTab from 'nextgen/app/scenes/resellerDashboard/AddCreditTab'
import PartnerTab from 'nextgen/app/scenes/resellerDashboard/PartnerTab'
import SalesDashboard from 'nextgen/app/scenes/SalesDashboard'
import ResellerTab from 'nextgen/app/scenes/salesDashboard/ResellerTab'
import StatsTab from 'nextgen/app/scenes/salesDashboard/StatsTab'
import Audience from 'nextgen/app/scenes/audience'
import Miniprogram from 'nextgen/dashboard/miniprogram'
import ComboDashboard from 'nextgen/dashboard/Combo'
import MiniprogramDashboard from 'nextgen/dashboard/miniprogram/components/dashboard'
import MiniprogramSelectTemplate from 'nextgen/dashboard/miniprogram/components/selectTemplate'
/* eslint-enable import/first */
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (getState, action) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const middleware = [thunkMiddleware, loggerMiddleware]
// Add the reducer to your store on the `routing` key
const composeEnhancers =
(localStorage &&
localStorage.getItem('__strk_developer__') &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(...middleware)),
)
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: state => state.get('router').toJS(),
})
class RouterComponent extends React.Component {
static childContextTypes() {
PropTypes.object
}
static getChildContext() {
return { location: this.props.location }
}
render() {
return (
<Provider store={store}>
{/* Tell the Router to use our enhanced history */}
<Router history={history}>
<Route path="/s/sites/:siteId/blog/manage" component={BlogManager} />
<Route
path="/s/blog_posts/:blogPostId/wechat_preview"
component={BlogToWechatPreview}
/>
<Route path="/s/select_template" component={SelectTemplate} />
<Route path="/s/analytics/:siteId" component={Analytics} />
<Route path="/s/sites/:siteId/preview" component={Preview} />
<Route
path="/s/sites/:siteId/donation/manage"
component={DonationManager}
/>
<Route path="/s/reseller" component={ResellerDashboard}>
<IndexRedirect to="clients" />
<Route path="clients" component={ClientTab} />
<Route path="billing" component={BillingTab} />
<Route path="addcredit" component={AddCreditTab} />
<Route path="partners" component={PartnerTab} />
<Route path="select_template" component={SelectTemplate} />
<Route
path="select_mini_program_template"
component={props => (
<MiniprogramSelectTemplate
{...props}
skipCategorySelector={true}
/>
)}
/>
</Route>
<Route path="/s/sales" component={SalesDashboard}>
<IndexRedirect to="resellers" />
<Route path="resellers" component={ResellerTab} />
<Route path="stats" component={StatsTab} />
</Route>
<Route path="/s/v2_domains" component={Domains} />
<Route path="/s/v2_domains/purchase" component={DomainPurchase} />
<Route path="/s/v2_domains/:domainId" component={Domain} />
<Route path="/s/live_chat" component={LiveChat} />
<Route path="/s/audience/:id" component={Audience} />
<Route path="/s/miniprogram" component={Miniprogram}>
<IndexRoute component={MiniprogramDashboard} />
<Route
path="select_template"
component={MiniprogramSelectTemplate}
/>
</Route>
<Route path="/s/reseller/zhubajie/instances" component={ComboDashboard} />
</Router>
</Provider>
)
}
}
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1()])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
const cloudinary = require('cloudinary')
const ConfStore = require('js/stores/conf_store')
I18n.init(poFile)
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
const RouterComponentWithContext = ComponentKitContext(RouterComponent)
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<RouterComponentWithContext />
</AppContainer>
</ErrorBoundary>,
document.getElementById('container'),
)
})
.catch(e => {
console.error(e, e.stack)
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
// Provide webpack modules to v3
// Note: es6 imports will break - Andy Feb 2016
require('./PageSaver')
require('../init')
const I18n = require('js/utils/i18n')
const i18nHelper = require('js/utils/helpers/i18nHelper')
import { AppContainer } from 'react-hot-loader'
// Note: Intially, the following chunk was loaded asynchronously with
// require.ensure, but it breaks on uat of sxl and thus is removed
// Andy Feb 2016
// used in dashboard to send data to angular
import EditPage from 'js/v3_bridge/edit_page_bridge'
import ComponentKitContext from 'js/utils/ComponentKitContext'
let Event = null
window.edit_page = EditPage
try {
if (parent.window.edit_page && parent.window.edit_page.Event) {
Event = parent.window.edit_page.Event
EditPage.Event = Event
} else {
Event = EditPage.Event
if (parent.window.edit_page) {
parent.window.edit_page.Event = Event
} else {
parent.window.edit_page = {
Event,
}
}
}
} catch (e) {
if (window.edit_page && window.edit_page.Event) {
Event = window.edit_page.Event
EditPage.Event = Event
} else {
Event = EditPage.Event
if (window.edit_page) {
window.edit_page.Event = Event
} else {
window.edit_page = {
Event,
}
}
}
}
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1()])
.then(([poFile]) => {
I18n.init(poFile)
const $ = require('jquery')
const React = require('react')
const ReactDOM = require('react-dom')
const SupportWidget = require('js/components/support_widget/SupportWidget')
const PurchaseBridge = require('../../nextgen/domain/PurchaseBridge')
const SupportWidgetWithComponentKit = ComponentKitContext(SupportWidget)
const PublishManager = require('nextgen/subApps/publishManager')
PublishManager.bindToGlobal()
$(() => {
const supportWidgetContainer = document.getElementById(
's-support-widget-container',
)
if (supportWidgetContainer) {
ReactDOM.render(
<AppContainer>
<SupportWidgetWithComponentKit />
</AppContainer>,
supportWidgetContainer,
)
}
PurchaseBridge({
Event,
})
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
import _ from 'lodash'
import PageDataStore from 'js/stores/page_data_store'
import DeviceHelper from 'js/utils/helpers/device_helper'
import { getFormattedPrice } from 'js/utils/EcommerceUtils'
import { traverseObj } from 'common/utils/lodashb'
import * as Immutable from 'immutable'
import EcommerceConstants from 'js/constants/ecommerce_constants'
import { flatten, path, pathEq } from 'ramda'
const { ZERO_DECIMAL_CURRENCY_LIST } = EcommerceConstants
function _getSettings() {
const EcommerceStore = require('js/stores/ecommerce_store')
return EcommerceStore.getSettings()
}
function _getCart() {
const EcommerceBuyStore = require('js/stores/EcommerceBuyStore')
return EcommerceBuyStore.getCart()
}
function _getCurrentDevice() {
if (!DeviceHelper.isWechat() && DeviceHelper.isMobile()) {
return 'mobile'
}
if (DeviceHelper.isWechat()) {
return 'wechat'
}
return 'desktop'
}
function _getSuportedChannelsWithCurrentDevice(channels) {
const __hasProp = {}.hasOwnProperty
const deviceedChannels = {
wechat: [],
mobile: [],
desktop: [],
}
for (const key in channels) {
if (!__hasProp.call(channels, key)) {
continue
}
const value = channels[key]
if (value) {
switch (key) {
case 'stripe':
case 'alipay':
deviceedChannels.mobile.push(key)
deviceedChannels.desktop.push(key)
break
case 'paypal':
case 'offline':
deviceedChannels.mobile.push(key)
deviceedChannels.wechat.push(key)
deviceedChannels.desktop.push(key)
break
case 'wechatpay':
deviceedChannels.wechat.push(key)
deviceedChannels.desktop.push(key)
break
case 'pingppAlipayWap':
deviceedChannels.mobile.push(key)
break
case 'pingppAlipayQr':
case 'pingppWxPubQr':
deviceedChannels.desktop.push(key)
break
case 'pingppWxPub':
deviceedChannels.wechat.push(key)
break
default:
break
}
}
}
return deviceedChannels
}
// function _getProducts() {
// return EcommerceStore.getProducts()
// }
function _addCurrencySymbol(price) {
const settings = _getSettings()
return getFormattedPrice(price, settings.currencyData)
}
function _getCountryByCode(countryCode) {
return ($S.country_list || {})[countryCode]
}
function _removeHttpProtocol(url) {
if (!url) {
return ''
}
return url.replace('http:', '')
}
export default {
availableDevicesToPayment() {
const settings = _getSettings()
const deviceedChannels = _getSuportedChannelsWithCurrentDevice(
settings.paymentGateways,
)
const supportedDevices = []
if (deviceedChannels.wechat.length > 0) {
supportedDevices.push('wechat')
}
if (deviceedChannels.mobile.length > 0) {
supportedDevices.push('mobile')
}
if (deviceedChannels.desktop.length > 0) {
supportedDevices.push('desktop')
}
return supportedDevices
},
hasAvailablePaymentWithCurrentDevice() {
const channels = this.getAvailableChannelsWithCurrentDevice()
return channels.length > 0
},
getAvailableChannelsWithCurrentDevice() {
const settings = _getSettings()
let channels = []
if (PageDataStore.hasSection('ecommerce')) {
const device = _getCurrentDevice()
const deviceedChannels = _getSuportedChannelsWithCurrentDevice(
settings.paymentGateways,
)
channels = deviceedChannels[device]
}
return channels
},
getAvailableChannels(
channels = [
'paypal',
'stripe',
'alipay',
'pingppAlipayQr',
'pingppAlipayWap',
'pingppWxPub',
'pingppWxPubQr',
'wechatpay',
'offline',
'midtrans',
],
) {
const settings = _getSettings()
const availableChannels = []
const { currencyCode, paymentGateways } = settings
const self = this
channels.map(channel => {
const supportCurrencies = self.getSupportCurrencies(channel)
if (
supportCurrencies.includes(currencyCode.toString()) &&
paymentGateways[channel]
) {
availableChannels.push(channel)
}
})
return availableChannels
},
getAvailableChannelsCount(
channels = [
'paypal',
'stripe',
'alipay',
'pingppAlipayQr',
'pingppAlipayWap',
'pingppWxPub',
'pingppWxPubQr',
'wechatpay',
'offline',
'midtrans',
],
) {
return this.getAvailableChannels(channels).length
},
isPaymentAccountSet() {
if (PageDataStore.hasSection('ecommerce')) {
const paymentGatewaysCount = this.getAvailableChannelsCount()
return paymentGatewaysCount > 0
}
return true
},
pageHasCoupon() {
return _getSettings().hasCoupon
},
userHasCoupon(coupon = _getCart().coupon) {
if (coupon && coupon.category) {
return true
}
},
userHasCouponWithType(type, coupon = _getCart().coupon) {
if (this.userHasCoupon(coupon) && coupon.category === type) {
return true
}
},
isInCondition(condition, coupon = _getCart().coupon) {
if (this.userHasCoupon() && coupon.option.condition[condition]) {
return true
}
},
needToShowDiscountInfo(coupon = _getCart().coupon) {
return this.userHasCoupon()
},
addCurrencySymbol: _addCurrencySymbol,
getPriceScope(product) {
const priceList =
product && product.variations
? product.variations.map(variation => variation.price)
: [0]
const minPrice = Math.min(...priceList)
const maxPrice = Math.max(...priceList)
let rs
if (minPrice === maxPrice) {
rs = _addCurrencySymbol(minPrice)
} else {
rs = `${_addCurrencySymbol(minPrice)} - ${_addCurrencySymbol(maxPrice)}`
}
return rs
},
getDecimalNum(currency = _getSettings().currencyCode) {
let rs = 2
if (ZERO_DECIMAL_CURRENCY_LIST.indexOf(currency) !== -1) {
rs = 0
}
return rs
},
getDiscountItem() {
if (!this.userHasCoupon()) {
return null
}
const { items: itemsInCart, coupon } = _getCart()
const amount = coupon.option.amount
const discountProductId = coupon.option.condition.productId
const itemsInProduct = itemsInCart.filter(
item => String(item.productId) === String(discountProductId),
)
if (itemsInProduct.length) {
const maxPriceItem = itemsInProduct.reduce((acc, item) => {
if (
acc &&
Number(acc.orderItem.price) >= Number(item.orderItem.price)
) {
return acc
} else {
return item
}
}, null)
return maxPriceItem
} else {
return null
}
},
getDiscountForProductCondition() {
if (!this.userHasCoupon()) {
return 0
}
const discountItem = this.getDiscountItem()
if (discountItem) {
const { coupon } = _getCart()
const amount = coupon.option.amount
return (discountItem.orderItem.price * amount) / 100
} else {
return 0
}
},
getDiscountNum() {
const { coupon } = _getCart()
let number = 0
if (!this.needToShowDiscountInfo()) {
return number
}
if (this.userHasCoupon()) {
switch (coupon.category) {
case 'free_shipping':
number = this.getShippingFeeNum()
return number
case 'flat':
number = coupon.option.amount / 100
break
case 'percentage':
number = this.isInCondition('productId')
? this.getDiscountForProductCondition()
: (this.getTotalItemPriceNum() * coupon.option.amount) / 100
if (this.getDecimalNum() === 0) {
number = Math.round(number)
}
break
// no default
}
}
if (number >= this.getTotalItemPriceNum()) {
number = this.getTotalItemPriceNum()
}
return number
},
getShippingFeeNum() {
const settings = _getSettings()
const { orderData, shipping } = _getCart()
let shippingFee = 0
if (!orderData || !shipping || !settings.shippingRegions) {
return 0
}
if (!orderData.shipping && !Array.isArray(settings.shippingRegions)) {
const countryCode = shipping.country ? shipping.country.value : false
if (!countryCode) {
return 0
}
const feeData =
settings.shippingRegions[countryCode] ||
settings.shippingRegions[
_getCountryByCode(countryCode)
? _getCountryByCode(countryCode).continent
: undefined
] ||
settings.shippingRegions.default
if (!feeData) {
return 0
}
const decimalNum = this.getDecimalNum()
const feePerAdditionalItem = (
feeData.feePerAdditionalItem / 100.0
).toFixed(decimalNum)
const feePerOrder = (feeData.feePerOrder / 100.0).toFixed(decimalNum)
const { items } = _getCart()
if (_.all(items, i => !i.product.shippingInfo)) {
return 0
}
const additionalFee = items.reduce((total, next) => {
const fee = next.product.shippingInfo
? feePerAdditionalItem * next.orderItem.quantity
: 0
return total + fee
}, 0)
shippingFee = feePerOrder - feePerAdditionalItem + additionalFee
} else {
shippingFee = orderData.shipping / 100.0
}
return shippingFee
},
getTotalItemPriceNum() {
const { items, coupon } = _getCart()
let totalPrice = _.reduce(
items,
(total, next) => total + next.orderItem.price * next.orderItem.quantity,
0,
)
if (this.getDecimalNum() === 0) {
totalPrice = Math.round(totalPrice)
}
return totalPrice
},
getTotalNum() {
let amount = 0
if (!this.userHasCoupon()) {
return (
this.getShippingFeeNum() +
this.getTotalItemPriceNum() +
this.getTaxesNum()
)
}
if (this.userHasCouponWithType('free_shipping')) {
return this.getTotalItemPriceNum() + this.getTaxesNum()
}
amount = this.getTotalItemPriceNum() - this.getDiscountNum()
if (amount < 0) {
amount = 0
}
return amount + this.getShippingFeeNum() + this.getTaxesNum()
},
getTaxesNum() {
const settings = _getSettings()
const taxesRate = (settings.taxes || 0) / 100
const itemPrice = this.getTotalItemPriceNum()
const discountForTax = this.userHasCouponWithType('free_shipping')
? 0
: this.getDiscountNum()
let taxesNum = 0
if (!taxesRate) {
return taxesNum
}
taxesNum = ((itemPrice - discountForTax) * taxesRate) / (1 + taxesRate)
if (this.getDecimalNum() === 0) {
taxesNum = Math.round(taxesNum)
}
return taxesNum
},
getDiscountDescription(coupon = _getCart().coupon, options) {
let description = ''
if (this.userHasCouponWithType('free_shipping', coupon)) {
description = __('EcommerceCoupon|Free Shipping')
} else if (this.userHasCouponWithType('percentage', coupon)) {
if (this.isInCondition('productId')) {
const item = this.getDiscountItem()
description = __(
'EcommerceCoupon|%{amount} off for %{productCount} %{productName} ',
{
productCount: options ? options.productCount : 1,
productName: options ? options.productName : item.product.name,
amount: `${coupon.option.amount}%`,
},
)
} else {
description = __('EcommerceCoupon|%{amount} Off', {
amount: `${coupon.option.amount}%`,
})
}
} else if (this.userHasCouponWithType('flat', coupon)) {
const decimalNum = this.getDecimalNum()
const fee = (coupon.option.amount / 100).toFixed(decimalNum)
description = __('EcommerceCoupon|%{amount} Off', {
amount: this.addCurrencySymbol(fee),
})
}
return description
},
getDiscount() {
return `- ${this.addCurrencySymbol(this.getDiscountNum())}`
},
getShippingFee() {
return this.addCurrencySymbol(this.getShippingFeeNum())
},
getSubtotal() {
return this.addCurrencySymbol(this.getTotalItemPriceNum())
},
getTaxes() {
return this.addCurrencySymbol(this.getTaxesNum())
},
getSubtotalWithDiscount() {
if (this.userHasCouponWithType('free_shipping')) {
return this.addCurrencySymbol(this.getTotalItemPriceNum())
}
return this.addCurrencySymbol(
this.getTotalItemPriceNum() - this.getDiscountNum(),
)
},
getTotalPrice() {
return this.addCurrencySymbol(this.getTotalNum())
},
getTrackData() {
if (__PRODUCT_NAME__ === 'sxl') {
return {}
}
const cartData = _getCart()
const trackData = {
currency: _getSettings().currencyData.code,
content_type: 'product',
content_ids: [],
content_name: [],
value: this.getTotalNum(),
num_items: 0,
}
cartData.items.forEach(item => {
const { orderItem } = item
trackData.content_name.push((item.product && item.product.name) || '')
trackData.content_ids.push(item.product.id)
trackData.num_items += Number(orderItem.quantity)
})
return trackData
},
loadDistrictsAsync(selectedCode = '000000', callback = () => {}) {
if (__PRODUCT_NAME__ === 'sxl') {
require.ensure([], require => {
const asyncResult = require(`promise-loader?global!json-loader!../../data/china_districts/${selectedCode}.json`)
asyncResult().then(json => callback(json))
})
}
},
/**
* @param {conf} {string} example as following
* {
* "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
* "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
* "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
* "package" : "prepay_id=u802345jgfjsdfgsdg888"
* "signType" : "MD5", //微信签名方式
* "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
}
*/
useWechatpay(conf, cb) {
if (DeviceHelper.isWechat()) {
if (typeof WeixinJSBridge === 'undefined') {
alert('请确认您的网站已正确加载微信sdk')
return false
}
WeixinJSBridge.invoke('getBrandWCPayRequest', conf, res => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg, 将在用户支付成功后返回ok,但并不保证它绝对可靠
cb && cb()
}
})
} else {
// pay on desktop with wechat qr code scanning
const EcommerceActions = require('js/actions/EcommerceActions')
EcommerceActions.gotoEcommerceBuyDialog('paymentqr', true)
}
},
getInitialPaymentAccount(provider) {
switch (provider) {
case 'alipay':
return { pid: null, md5Key: null }
case 'wechatpay':
return { mchId: null, appId: null, apiKey: null, appSecret: null }
case 'paypal':
return { email: null }
case 'offline':
return { instructions: null, method: null }
case 'midtrans':
return { merchantId: null, clientKey: null, serverKey: null }
default:
return {}
}
},
getSupportCurrencies(provider) {
switch (provider) {
case 'alipay':
case 'wechatpay':
case 'pingppAlipayQr':
case 'pingppAlipayWap':
case 'pingppWxPub':
case 'pingppWxPubQr':
return ['CNY']
case 'paypal':
return [
'AUD',
'BRL',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'ILS',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PHP',
'PLN',
'RUB',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
]
case 'stripe':
return [
'AUD',
'BRL',
'CAD',
'CHF',
'CLP',
'DKK',
'EUR',
'GBP',
'HKD',
'IDR',
'INR',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PHP',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
'COP',
'VND',
'ZAR',
]
case 'offline':
return [
'AUD',
'BDT',
'BRL',
'CAD',
'CHF',
'CLP',
'CNY',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'IDR',
'ILS',
'INR',
'JPY',
'KRW',
'MXN',
'MYR',
'NOK',
'NZD',
'PEN',
'PHP',
'PLN',
'RUB',
'SEK',
'SGD',
'THB',
'TWD',
'USD',
'COP',
'VND',
'ZAR',
]
case 'midtrans':
return ['IDR']
default:
return []
}
},
getProviderNameMap(provider) {
switch (provider) {
// TODO: Server has two name for stripe. On disconnect and accounts.
case 'stripe':
return 'stripe_connect'
case 'stripe_connect':
return 'stripe'
default:
return provider
}
},
removeHttpProtocolForImageUrl(products) {
traverseObj(products, obj => {
if (obj.thumbnailUrl) {
obj.thumbnailUrl = _removeHttpProtocol(obj.thumbnailUrl)
}
if (obj.url) {
obj.url = _removeHttpProtocol(obj.url)
}
})
return products
},
needNarrowCurrencySymbol() {
const settings = _getSettings()
return settings.currencyCode == 'KRW'
},
isEmptyDimension(dimension) {
const dimen = Immutable.isImmutable(dimension)
? dimension.toJS()
: dimension
if (
dimen === undefined ||
dimen.name === '' ||
dimen.options === undefined ||
dimension.options === null ||
dimen.options.length === 0
) {
return true
} else {
return false
}
},
hasMutipleDimensions(dimensions) {
if (!dimensions) {
return false
}
const { dimension1, dimension2 } = Immutable.isImmutable(dimensions)
? dimensions.toJS()
: dimensions
return (
!this.isEmptyDimension(dimension1) && !this.isEmptyDimension(dimension2)
)
},
normalizeItemName(name) {
if (name && name.indexOf('_') !== -1) {
return name.split('_').join(' ')
} else {
return name
}
},
convertProductPrice(products) {
for (const product of Array.from(products)) {
if (!product.picture) {
product.picture = []
}
product.description = product.description.replace(/\n/g, '<br>')
const decimalNum = Array.from(ZERO_DECIMAL_CURRENCY_LIST).includes(
__PRODUCT_NAME__ === 'sxl' ? 'CNY' : 'USD', // _settings.currencyCode in EcommerceStore
)
? 0
: 2
product.variations = _.sortBy(product.variations, variant =>
Number(variant.id),
)
for (const variant of Array.from(product.variations)) {
/*
* hack price adapting if iframe wrapped whole site,
* productchange event will trigger twice,
* and secound time will re-use prevState,
* eg: input 99, will wrong final result 0.99
*/
if (!/\.\d{2}$/.test(variant.price)) {
variant.price = (variant.price / 100).toFixed(decimalNum)
}
}
}
return products
},
_formatCategoryOption(category) {
let label = ''
if (category.level === 1) {
label = category.name
} else if (category.level === 2) {
label = `\u00A0\u00A0\u00A0\u00A0\u00A0${category.name}`
}
return { value: category.name, label, id: category.id }
},
formattedCategoryOptions(categories, formatter = this._formatCategoryOption) {
const categoryOptions = []
const rawCategories = categories.toJS ? categories.toJS() : categories
rawCategories.forEach(category => {
categoryOptions.push(formatter(category))
})
return categoryOptions
},
addLabelForCategory(categories) {
const newCategories = categories.map(category => {
let label
if (!category.get('level') || category.get('level') === 1) {
label =
category.get('name') +
(category.get('products_count') ? category.get('products_count') : '')
} else if (category.get('level') === 2) {
label = `\u00A0\u00A0\u00A0\u00A0\u00A0${category.get(
'name',
)} (${category.get('products_count')})`
}
return category.set('label', label)
})
return newCategories
},
sortedCategories(categoryObjs, orderList) {
orderList = orderList || {}
let allSortedCategory = []
const sortedCategory = categoryObjs
.filter(category => category.level === 1)
.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex >= bIndex ? 1 : -1
})
sortedCategory.forEach(category => {
const subCategories =
(category.children &&
category.children.map(subCategoryId =>
categoryObjs.find(category => category.id === subCategoryId),
)) ||
[]
const sortedChildren = subCategories.sort((a, b) => {
const aIndex =
(category.children_category_order &&
category.children_category_order[a.id]) ||
-a.id
const bIndex =
(category.children_category_order &&
category.children_category_order[b.id]) ||
-b.id
return aIndex >= bIndex ? 1 : -1
})
allSortedCategory.push([category, ...sortedChildren])
})
allSortedCategory = flatten(allSortedCategory)
return allSortedCategory
},
isSubCategory(item) {
const category = item.toJS ? item.toJS() : item
if (!item) {
return false
}
return (
path(['payload', 'item', 'parent_id'])(category) &&
pathEq(['payload', 'item', 'level'], 2)(category)
)
},
sortedCategory(categories, orderList) {
orderList = orderList || {}
// param default value not accurate enough
// sortedCategory(categories, orderList = {}) => var orderList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // not check `null` param case here
const categoriesObj =
categories && categories.toJS ? categories.toJS() : categories
if (categoriesObj) {
return categoriesObj.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex >= bIndex ? 1 : -1
})
}
},
checkHasSubCategory(categories) {
return (
categories &&
categories.some(item => {
if (categories.toJS) {
return item.get('children') && item.get('children').size > 0
} else {
return item.children && item.children.length > 0
}
})
)
},
}
require('./init')
import $ from 'jquery'
import 'js/vendor/jquery/easing'
import React from 'react'
import ReactDOM from 'react-dom'
import { Iterable } from 'immutable'
import { tct } from 'r-i18n'
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import ManagerReducer from 'nextgen/ecommerce/manager/reducers/ManagerReducer'
import connectReduxStoreToBinding from 'js/utils/helpers/connectReduxStoreToBinding'
import EcommerceManagerStore from 'js/stores/EcommerceManagerStore'
import FeatureStore from 'js/stores/FeatureStore'
import ConfStore from 'js/stores/conf_store'
import 'js/reactInit.es6'
import * as UrlConstants from 'js/constants/url_constants'
import ProductPanel from 'nextgen/ecommerce/manager/components/productPanel'
import CategoryManagerWrapper from 'nextgen/ecommerce/manager/components/settings/categoryManagerWrapper'
import MembershipManager from 'nextgen/ecommerce/manager/components/settings/membershipManager'
import PaymentGatewaySettingsPanel from 'nextgen/ecommerce/manager/components/settings/paymentChannel/PaymentGatewaySettingsPanel'
// import Orders from 'nextgen/manager/Products'
// import Coupons from 'nextgen/manager/Products'
// import Settings from 'nextgen/manager/Products'
import gonReader from 'js/utils/gonReader'
import { getSupportCurrencies } from 'js/utils/helpers/EcommerceHelper'
import * as RHL from 'react-hot-loader'
import wrapErrorBoundary from 'js/components/ErrorBoundary'
import { UPDATE_SETTINGS_SUCCESS } from 'nextgen/ecommerce/manager/actions/entities/settings'
import { initializeAccountFromV3 } from 'nextgen/ecommerce/manager/actions/entities/accounts'
import EcommerceManagerBridge from 'js/v4_bridge/EcommerceManagerBridge'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import ComponentKitContext from 'js/utils/ComponentKitContext'
const AppContainer = wrapErrorBoundary(RHL.AppContainer)
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (getState, action) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const store = createStore(
ManagerReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware, // neat middleware that logs actions
),
)
const baseProps = {
siteId: gonReader('id'),
siteState: gonReader('state'),
publicUrl: gonReader('public_url'),
meta: gonReader('pageOwner.ecommerce'),
isStandAlone: window.parent === window,
isPro: ['pro', 'namecheap', 'sxlbiz'].includes(
gonReader('pageOwner.membership'),
),
isVip: gonReader('pageOwner.membership') === 'vip',
isSxl: Boolean(gonReader('globalConf.isSxl')),
isAdmin: Boolean(gonReader('pageOwner.isAdmin')),
isSupport: Boolean(gonReader('pageOwner.isSupport')),
supportedCurrency: gonReader('conf.SUPPORTED_CURRENCY'),
}
const productProps = {
...baseProps,
productLimit:
gonReader('pageOwner.ecommerce').productLimit ||
(gonReader('globalConf.isSxl') ? 5 : 1),
upgradeUrl: UrlConstants.PRICING.GOTO_AND_RETURN(
gonReader('id'),
'ecommerce',
),
productPageRollout: gonReader('globalConf.rollout.product_page'),
productDetailRollout: gonReader('globalConf.rollout.product_detail'),
siteMemberShip: gonReader('globalConf.rollout.siteMemberShip'),
noCategoryLimit: gonReader('globalConf.rollout.no_category_limit'),
}
const supportedChannels = () => {
const isSxl = Boolean(gonReader('globalConf.isSxl'))
const paypal = {
channel: 'paypal',
supportedCurrencies: getSupportCurrencies('paypal'),
features: [
__('Ecommerce|~2.9% + 30¢ per transaction'),
__('Ecommerce|Get funds in just a few minutes'),
tct(__('Ecommerce|Accepts [link]'), {
link: (
<span>
<i className="fa fa-cc-visa" aria-hidden="true" />
<i className="fa fa-cc-mastercard" aria-hidden="true" />
<i className="fa fa-cc-amex" aria-hidden="true" />
<i className="fa fa-cc-discover" aria-hidden="true" />
</span>
),
}),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/paypal.png',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more about PayPal.]'), {
link: <a target="_blank" href="https://www.paypal.com/" />,
}),
}
const stripe = {
channel: 'stripe',
supportedCurrencies: getSupportCurrencies('stripe'),
features: [
__('Ecommerce|~2.9% + 30¢ per transaction'),
__('Ecommerce|Get funds in about 7 days'),
tct(__('Ecommerce|Accepts [link]'), {
link: (
<span>
<i className="fa fa-cc-visa" aria-hidden="true" />
<i className="fa fa-cc-mastercard" aria-hidden="true" />
<i className="fa fa-cc-amex" aria-hidden="true" />
<i className="fa fa-cc-discover" aria-hidden="true" />
<i className="fa fa-cc-jcb" aria-hidden="true" />
</span>
),
}),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/stripe-logo.png',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more about Stripe.]'), {
link: <a target="_blank" href="https://stripe.com/" />,
}),
}
const alipay = {
channel: 'alipay',
supportedCurrencies: getSupportCurrencies('alipay'),
features: [
__('Ecommerce|Needs business bank account and ICP license'),
__('Ecommerce|Get funds immediately after payment'),
__('Ecommerce|0.6% per successful charge from Alipay'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/ic_alipay.png',
methods: __('Ecommerce|Instant Payment'),
additionalInfo: tct(
__(
'Ecommerce|Enter your PID and MD5 Key to enable your Alipay account.[link: Learn more about how to get PID and MD5 Key from Alipay manage platform.]',
),
{
link: (
<a
target="_blank"
href="http://help.sxl.cn/hc/zh-cn/articles/115000046301"
/>
),
},
),
}
const wechatpay = {
channel: 'wechatpay',
supportedCurrencies: getSupportCurrencies('wechatpay'),
features: [
__('Ecommerce|Needs business bank account and ICP license'),
__('Ecommerce|Get funds immediately after payment'),
__('Ecommerce|0.6% per successful charge from WeChat Pay'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/ic_wechat_pay.png',
methods: __('Ecommerce|In-App Web-based Payment'),
additionalInfo: tct(
__(
'Ecommerce|Enter your WeChat Pay account info below to enable your Alipay account.[link: Learn more about how to get WeChat Pay account info.]',
),
{
link: (
<a
target="_blank"
href="http://help.sxl.cn/hc/zh-cn/articles/115000046661"
/>
),
},
),
}
const offline = {
channel: 'offline',
supportedCurrencies: getSupportCurrencies('offline'),
features: [],
logo: '',
methods: '',
additionalInfo: tct(__('Ecommerce|[link: Learn more]'), {
link: (
<a
target="_blank"
href={
isSxl
? 'http://help.sxl.cn/hc/zh-cn/articles/115000100782'
: 'https://support.strikingly.com/hc/en-us/articles/115000100802'
}
/>
),
}),
description: __(
'Ecommerce|Customers will skip payment while placing the order. You must set up offline payment yourself!',
),
}
const midtrans = {
channel: 'midtrans',
supportedCurrencies: getSupportCurrencies('midtrans'),
features: [
__(
'Ecommerce|Accept card payment, bank transfer, direct debit, e-wallet, over the counter',
),
__('Ecommerce|Transaction fee varies with different payment methods'),
__('Ecommerce|No transaction fees from Strikingly'),
],
logo: '/images/ecommerce/midtrans.png',
methods: '',
}
let supportedChannelsConf = []
if (isSxl) {
supportedChannelsConf = [paypal, alipay, wechatpay]
} else {
supportedChannelsConf = [paypal, stripe]
}
// Rollout for Midtrans payments
if (!isSxl && ConfStore.getMidtransPayments()) {
supportedChannelsConf.push(midtrans)
}
// Rollout for offline payments
if (ConfStore.getOfflinePayments()) {
supportedChannelsConf.push(offline)
}
return supportedChannelsConf
}
const KitWrappedProductPanel = ComponentKitContext(ProductPanel)
class ProductsContainer extends React.Component {
componentWillMount() {
EcommerceManagerBridge.subPublishUrlChange((topic, data) => {
baseProps.publicUrl = data.url
this.forceUpdate()
})
EcommerceManagerBridge.subSiteStateChange((topic, data) => {
baseProps.siteState = data.state
this.forceUpdate()
})
}
render() {
return (
<Provider store={store}>
<KitWrappedProductPanel
{...productProps}
currentProductDetail={this.props.currentProductDetail}
canUseCategory={FeatureStore.canUse('ecommerce_category')}
canSeeCategory={FeatureStore.canSee('ecommerce_category')}
/>
</Provider>
)
}
}
class CategoryContainer extends React.Component {
render() {
return (
<Provider store={store}>
<CategoryManagerWrapper
{...productProps}
canUseCategory={FeatureStore.canUse('ecommerce_category')}
canSeeCategory={FeatureStore.canSee('ecommerce_category')}
/>
</Provider>
)
}
}
const CategoryContainerWithComponentKit = ComponentKitContext(CategoryContainer)
class MembershipContainer extends React.Component {
render() {
return (
<Provider store={store}>
<MembershipManager {...productProps} />
</Provider>
)
}
}
const PaymentSettingsContainer = () => (
<Provider store={store}>
<PaymentGatewaySettingsPanel
{...baseProps}
supportedChannels={supportedChannels()}
/>
</Provider>
)
EcommerceManagerBridge.subSettingsChange((topic, data) => {
const siteId = $S.id.toString()
let settings =
store
.getState()
.getIn(['entities', 'settings', 'data', siteId, 'settings']) || {}
if (settings.toJS) {
settings = settings.toJS()
}
store.dispatch({
type: UPDATE_SETTINGS_SUCCESS,
payload: {
settings: Object.assign(settings, data.settings),
},
meta: {
siteId,
},
})
store.dispatch(initializeAccountFromV3(settings.accounts))
})
const siteIdString = $S.id.toString()
const prevStates = {
product: store
.getState()
.getIn(['entities', 'product', 'data', siteIdString]),
settings: store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']),
category: store.getState().getIn(['entities', 'category', 'data']),
}
store.subscribe(a => {
const newProductsState = store
.getState()
.getIn(['entities', 'product', 'data', $S.id.toString()])
const newSettingsState = store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings'])
const newCategoryState = store
.getState()
.getIn(['entities', 'category', 'data'])
if (newProductsState !== prevStates.product) {
prevStates.product = newProductsState
EcommerceManagerBridge.pubProductsChange(_.flatten(newProductsState.toJS()))
}
if (newSettingsState !== prevStates.settings) {
prevStates.settings = newSettingsState
EcommerceManagerBridge.pubSettingsChange(newSettingsState.toJS())
}
if (newCategoryState !== prevStates.category) {
prevStates.category = newCategoryState
EcommerceManagerBridge.pubCategoriesChange(
_.flatten(newCategoryState.toJS()),
)
}
})
Promise.all([p1()])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
const cloudinary = require('cloudinary')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
if (window.parent === window) {
let ImageAssetDialog = require('js/v4_bridge/react_app_bridge/ImageAssetDialogApp')
const EditorStore = require('js/stores/editor_store')
const ctx = EditorStore.init()
connectReduxStoreToBinding(store, ctx.getBinding())
ImageAssetDialog = ctx.bootstrap(ImageAssetDialog())
ReactDOM.render(
<Provider store={store}>
<AppContainer>
<ImageAssetDialog />
</AppContainer>
</Provider>,
document.getElementById('container'),
)
}
EcommerceManagerStore.init()
FeatureStore.hydrate($S.features)
connectReduxStoreToBinding(store, EcommerceManagerStore.getBinding())
// render counpon part for ecommerce manager
const couponsCtx = EcommerceManagerStore.getCouponsContext()
const CouponManager = require('js/components/ecommerce/manager/coupon/CouponManager')
const WrappedCoupon = ComponentKitContext(
couponsCtx.bootstrap(CouponManager),
)
ReactDOM.render(
<Provider store={store}>
<AppContainer>
<WrappedCoupon />
</AppContainer>
</Provider>,
document.getElementById('coupon-container'),
)
/*
Add morearty binding to product container
currentProductDetail is a data container and binding path never get changed, only data can be set
*/
const productsContext = EcommerceManagerStore.getProductsContext()
const currentProductDetail = EcommerceManagerStore.getProductsBinding().sub(
'currentProductDetail',
)
const WrapperedProductsContainer = productsContext.bootstrap(
ProductsContainer,
)
ReactDOM.render(
<AppContainer>
<WrapperedProductsContainer
currentProductDetail={currentProductDetail}
/>
</AppContainer>,
document.getElementById('products-container'),
)
ReactDOM.render(
<AppContainer>
<PaymentSettingsContainer />
</AppContainer>,
document.getElementById('payment-manager'),
)
ReactDOM.render(
<CategoryContainerWithComponentKit />,
document.getElementById('category-manager'),
)
ReactDOM.render(
<AppContainer>
<MembershipContainer />
</AppContainer>,
document.getElementById('membership-manager'),
)
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
// SPEC
// Owner: Jerry
// Feature name: HtmlComponent
// * Hover over the component, expect the overlay to show.
// * Click on the component, expect the App Store popup to open.
// * After interaction with App Store, clicking 'Save' should cause popup to close and iframe to refresh/resize.
// * This should also fire an API call to /s/components to save the component data.
// * Remove the section containing the component, expect an API call to /s/components to destroy the component data.
// * On editor init, expect the iframe to resize itself to a proper height.
import React from 'react'
import PropTypes from 'prop-types'
import ConfStore from 'js/stores/conf_store'
function eachSeries(items, timeoutInMilliseconds, fn) {
if (items.length > 0) {
let nextCalled = false
const next = () => {
if (nextCalled) {
console.error('done function called after timeout')
return
}
clearTimeout(timeout)
nextCalled = true
eachSeries(items.slice(1), timeoutInMilliseconds, fn)
}
const timeout = setTimeout(next, timeoutInMilliseconds)
fn(items[0], next)
}
}
let HtmlComponent = null
if (__NATIVE_WEB__) {
const MobileDisabledNotice = require('js/components/MobileDisabledNotice')
HtmlComponent = () => (
<MobileDisabledNotice
disabledNotice={__(
'Mobile|App store is not yet editable on the mobile app.',
)}
/>
)
} else {
const ReactDOM = require('react-dom')
const _ = require('lodash')
const $ = require('jquery')
const ComponentFactory = require('js/utils/comp_factory')
const PageMetaStore = require('../stores/page_meta_store')
const EditorActions = require('../actions/editor_actions')
const ComponentDataUtils = require('../utils/apis/components_api_utils')
const MetaMixin = require('js/utils/mixins/meta_mixin')
const CustomPropTypes = require('../utils/custom_prop_types')
const AppStoreDialog = require('js/v3_bridge/app_store_dialog')
const IframeHelper = require('../utils/helpers/IframeHelper')
const logger = require('js/utils/logger')
const loadFancyBox = require('promise-loader?global!js/vendor/jquery/jquery.fancybox3.js')
const rt = require('./templates/html_component')
const _htmlFieldsToSave = [
'id',
'value',
'htmlValue',
'selected_app_name',
'page_id',
'render_as_iframe',
'app_list',
]
const _bobcatPropTypes = {
data: {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: CustomPropTypes.html,
render_as_iframe: PropTypes.bool,
selected_app_name: PropTypes.string,
app_list: PropTypes.string,
binding: PropTypes.object,
},
}
const _bobcatDefaultProps = () => ({
data: {
render_as_iframe: false,
app_list: '{}',
},
})
HtmlComponent = ComponentFactory.createPageComponent({
displayName: 'HtmlComponent',
mixins: [MetaMixin('editor')],
bobcatPropTypes: _bobcatPropTypes,
getBobcatDefaultProps: _bobcatDefaultProps,
componentWillMount() {
this.initMeta({
iframeSrcQ: 0,
canceled: false,
})
if (__IN_EDITOR__ && !this._hasId(this.props.id)) {
return this._getId()
}
// if (!__IN_EDITOR__ && this._isTypeForm() && !__SERVER_RENDERING__) {
// this._applyTypeformScrollTopHack()
// }
},
componentDidMount() {
IframeHelper.startTimer()
this._injectHtml()
this._resizeIFrame()
if (!__IN_EDITOR__ && this._isTypeForm()) {
loadFancyBox().then(() => {
this._initTypeForm()
})
}
},
componentDidUpdate(prevProps) {
if (__IN_EDITOR__) {
if (prevProps.value !== this.dtProps.value) {
const dataToSave = _.pick(this.dtProps, _htmlFieldsToSave)
this._saveComponent(dataToSave)
this._injectHtml()
return this._resizeIFrame()
}
}
},
componentWillUnmount() {
if (this.props.isBlog && window.Ecwid) {
window.Ecwid.destroy()
window.Ecwid = null
}
},
_initTypeForm() {
const $this = $(ReactDOM.findDOMNode(this)).find('.type-form-popup')
const basicClass = 'button-component'
const styleClass = 'type-form-button-style'
const $typeFormButton = $this.find('.type-form-button').eq(0)
if ($.fn.fancybox) {
const content = $typeFormButton.attr('data-iframe-content')
const $virtualEl = $(content)
const iframeSrc = $virtualEl.eq(0).attr('src')
$typeFormButton.attr('data-src', iframeSrc)
$typeFormButton.attr('data-href', 'javascript:;')
$typeFormButton.fancybox({
fullScreen: false,
slideClass: 's-fancybox-typeform',
iframe: {
scrolling: 'auto',
},
})
}
const originalBg = $this.find('.type-form-button').css('background')
$this.find('.type-form-button').removeClass(basicClass)
const newBg = $this.find('.type-form-button').css('background')
if (originalBg === newBg) {
$this.addClass(styleClass)
}
$this.find('.type-form-button').addClass(basicClass)
},
_applyTypeformScrollTopHack() {
// HACK: embed typeform iframe will scroll parent window to iframe position
// but we can not prevent the code execution or the form will not show
// so we manually check big jump
let lastTop = $(window).scrollTop()
let scrollCheck = null
if (!$B.TH.isMobile()) {
scrollCheck = function() {
const currentTop = $(window).scrollTop()
if (Math.abs(currentTop - lastTop) > 200) {
$(window).scrollTop(lastTop)
} else {
lastTop = currentTop
}
}
$(window).on('scroll', scrollCheck)
setTimeout(() => {
$(window).off('scroll', scrollCheck)
}, 15000)
}
},
_isTypeForm() {
return this.props.selected_app_name === 'TypeFormApp'
},
_hasId(id) {
return typeof id === 'number'
},
_getId() {
this._setCanceled(false)
return EditorActions[
`createComponent${this.props.isBlog ? 'InBlog' : ''}`
]({
data: {
component: {},
},
success: data => {
this.updateData({
id: data.data.component.id,
})
return this.savePage()
},
error: data => {
if (
window.confirm(
__(
"Uh oh! There's been an error creating this HTML component. Try again?",
),
)
) {
return this._getId()
}
return this._setCanceled(true)
},
})
},
_resizeIFrame() {
const $iframes = $(ReactDOM.findDOMNode(this)).find('iframe')
if ($iframes.length) {
return IframeHelper.resizeIFrames($iframes)
}
},
_injectHtml() {
if (!this.dtProps.render_as_iframe && !__IN_EDITOR__) {
// HACK: use try catch to avoid breaking the entire rendering
try {
const htmlInjectDiv = ReactDOM.findDOMNode(this.refs.htmlInject)
htmlInjectDiv.innerHTML = this._rawHtml()
// We could use jQuery html to execute the script tags on insertion,
// but we want to maintain their execution order and wait for each
// script to load one at a time
eachSeries(
$(htmlInjectDiv).find('script'),
4000, // need timeout because don't get a failure result from runScript
(script, done) => {
const rscriptType = /^$|\/(?:java|ecma)script/i // taken from jQuery .html source
if (script.src && rscriptType.test(script.type || '')) {
$.getScript(script.src).done(done)
} else {
const rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g // from jQuery
$.globalEval(script.textContent.replace(rcleanScript, ''))
return done()
}
},
)
} catch (error) {
// use console.log because user might want to see this error
logger.log(`Html section script error: ${error}`)
return $(ReactDOM.findDOMNode(this.refs.htmlInject)).append(
`Script error: ${error}`,
)
}
}
},
_hasContent() {
return this.dtProps.value.length > 0
},
_renderAsIframe() {
return this.dtProps.render_as_iframe
},
_rawHtml() {
return _.unescape(this.dtProps.value || '')
},
_onClickEditor() {
const cb = data => {
if (data.id === this.dtProps.id) {
const dataToSave = _.pick(data, _htmlFieldsToSave)
this.updateData(dataToSave)
return this.savePage()
}
return window.error(
__(
"Uh oh! There's been an error saving this HTML component. Try again.",
),
)
}
if (this.props.isBlog) {
var dialog = new AppStoreDialog(
_.extend({}, this.dtProps, {
htmlValue: this._rawHtml(),
page_id: PageMetaStore.getId(),
}),
data => {
cb(data)
return dialog.close()
},
() => dialog.close(),
)
// let dialog = new AppStoreDialog(
// _.extend({}, this.dtProps, {
// htmlValue: this._rawHtml(),
// page_id: PageMetaStore.getId(),
// }),
// (data) => {
// cb(data)
// dialog.close()
// }, () => {
// dialog.close()
// }
// )
} else {
return EditorActions.openAppStoreDialog(
_.extend({}, this.dtProps, {
htmlValue: this._rawHtml(),
page_id: PageMetaStore.getId(),
}),
cb,
)
}
},
_saveComponent(data) {
if (this.props.isBlog) {
ComponentDataUtils.update(this.dtProps.id, data, this._reloadIframe)
} else {
// TODO: Using promise instead of callback
return EditorActions.saveHTMLComponent(
this.dtProps.id,
data,
this._reloadIframe,
)
}
},
// ComponentDataUtils.update(@dtProps.id, data, @_reloadIframe)
_iframeSrcQ() {
return this.getMeta('iframeSrcQ')
},
_reloadIframe() {
return this.updateMeta({ iframeSrcQ: this.getMeta('iframeSrcQ') + 1 })
},
// for admin use only to fix component that have repeated id
_recreateComponent() {
if (
window.confirm(
'Recreating will delete any existing component! Make sure you understand what this does',
)
) {
this.updateData(_bobcatDefaultProps().data)
return this._getId()
}
},
render() {
if (this._getCanceled()) {
return (
<div
className="s-common-status"
style={{ cursor: 'pointer' }}
onClick={this._getId}>
{__('Click here to create HTML component again.')}
</div>
)
} else if (!this._hasId(this.props.id)) {
return __IN_EDITOR__ ? (
<div className="s-loading-wrapper">
<div className="s-loading" />
</div>
) : null
}
return rt.apply(this)
},
})
}
export default HtmlComponent
require('./init')
import React from 'react'
import $ from 'jquery'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import PhotoPage from './components/PhotoPage'
require('./utils/extensions/native')
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile(
'zh_CN',
)}`)
Promise.all([p1()]).then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<PhotoPage setType="background" />
</AppContainer>
</ErrorBoundary>,
document.getElementById('photo-page'),
)
})
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
require('./init')
import React from 'react'
import $ from 'jquery'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import { Provider } from 'react-redux'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import {
Router,
Route,
IndexRedirect,
IndexRoute,
hashHistory,
} from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import reducers from 'nextgen/app/reducers'
import MiniprogramDashboard from 'nextgen/dashboard/miniprogram/components/dashboard'
import Domains from 'nextgen/app/scenes/Domains'
import Domain from 'nextgen/app/scenes/Domain'
import DomainPurchase from 'nextgen/app/scenes/DomainPurchase'
const middleware = [thunkMiddleware]
const composeEnhancers =
(localStorage &&
localStorage.getItem('__strk_developer__') &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(...middleware)),
)
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(hashHistory, store, {
selectLocationState: state => state.get('router').toJS(),
})
function EmptyComponent() {
return <div />
}
function recordLatestViewedHash(nextState, replace) {
const highlightClass = {
miniprogram: '.my-miniprogram',
v2_domains: '.my-domains',
}[location.hash && location.hash.split('/')[1]]
$('.subnav-container .s-link')
.removeClass('current')
.filter(highlightClass)
.addClass('current')
if (localStorage && ['#/miniprogram'].indexOf(location.hash) !== -1) {
localStorage.setItem('dashboard_latest_viewed_hash', location.hash)
}
}
class DashboardRouter extends React.Component {
static childContextTypes() {
PropTypes.object
}
static getChildContext() {
return { location: this.props.location }
}
componentWillMount() {
$('.nav-menu .s-link')
.removeClass('current')
.filter('.my-sites')
.addClass('current')
}
render() {
return (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={EmptyComponent} />
<Route
path="/miniprogram"
component={MiniprogramDashboard}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains"
component={Domains}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains/purchase"
component={DomainPurchase}
onEnter={recordLatestViewedHash}
/>
<Route
path="/v2_domains/:domainId"
component={Domain}
onEnter={recordLatestViewedHash}
/>
</Router>
</Provider>
)
}
}
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
Promise.all([p1()])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
const DashboardRouterWithContext = ComponentKitContext(DashboardRouter)
$(() => {
ReactDOM.render(
<ErrorBoundary>
<AppContainer>
<Provider store={store}>
<DashboardRouterWithContext />
</Provider>
</AppContainer>
</ErrorBoundary>,
document.getElementById('mainDashboard'),
)
})
})
.catch(e => {
console.error(e, e.stack)
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
import React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import DOM from 'react-dom-factories'
import Immutable from 'immutable'
import Morearty from 'morearty'
import reactMixin from 'react-mixin'
import TimerMixin from 'react-timer-mixin'
import ReactTransitionGroup from 'react-transition-group/TransitionGroup'
import classNames from 'classnames'
import { tct } from 'r-i18n'
import { Icon } from 'component-kit'
import PremiumFeature from './PremiumFeature'
import EditorActions from 'js/actions/editor_actions'
import SiteConnectionActions from 'js/actions/SiteConnectionActions'
import EditorConstants from 'js/constants/editor_constants'
import EditorDispatcher from 'js/dispatcher/editor_dispatcher'
import ConfStore from 'js/stores/conf_store'
import CurrentUserStore from 'js/stores/current_user_store'
import CollaboratorsStore from 'js/stores/collaborators_store'
import PageMetaStore from 'js/stores/page_meta_store'
import UserMetaStore from 'js/stores/user_meta_store'
import TooltipMixin from 'js/utils/mixins/tooltip_mixin'
import PurchaseContainer from 'js/components/domain_purchase/PurchaseContainer'
import JQSlide from 'js/components/helpers/jqslide'
import StrikinglyOrSxl from 'js/components/StrikinglyOrSxl'
import _ from 'lodash'
import { openDialog } from 'nextgen/shared/actions/dialogsActions'
const parseDomainPromise = require('promise-loader?global!parse-domain')
// 1 has a recored
// 2 has forwarind
// 3 both or others
const _checkConfig = {
'1&1': 2,
'101 Domain': 1,
'123-reg': 2,
Bluehost: 2,
cPanel: 2,
CrazyDomains: 1,
Gandi: 2,
GoDaddy: 2,
'Microsoft 365': 1,
Namecheap: 2,
'Network Solutions': 1,
OVH: 1,
'Register.com': 2,
Wix: 1,
'Wordpress.com': 1,
'Yahoo!': 1,
Others: 3,
新网: 1,
阿里云万网: 1,
'Nic.cl': 1,
Amen: 1,
'Sakura Internet(さくらインターネット)': 1,
'Muu Muu (ムームードメイン)': 1,
'Value-Domain': 2,
XSERVER: 2,
}
@reactMixin.decorate(Morearty.Mixin)
@reactMixin.decorate(TooltipMixin.enableAfterUpdate())
@reactMixin.decorate(TimerMixin)
class DomainsTab extends React.Component {
static propTypes = {
permalink: PropTypes.string.isRequired,
customDomain: PropTypes.string,
isDirLink: PropTypes.bool,
binding: PropTypes.object.isRequired,
onSaveSettings: PropTypes.func.isRequired,
selected: PropTypes.bool,
currentTabName: PropTypes.string,
changeTabName: PropTypes.func,
showQrCode: PropTypes.func,
}
constructor(props) {
super(props)
this._onConnectDomainAfterRegister = this._onConnectDomainAfterRegister.bind(
this,
)
this._onRegisterNewDomain = this._onRegisterNewDomain.bind(this)
this._onRegisterNewDomainWithName = this._onRegisterNewDomainWithName.bind(
this,
)
this._onChangeStep = this._onChangeStep.bind(this)
this._onBackToTab = this._onBackToTab.bind(this)
this._removePurchaseSuccessMsg = this._removePurchaseSuccessMsg.bind(this)
this._onChangeCustomDomain = this._onChangeCustomDomain.bind(this)
}
componentWillMount() {
this.setStateWithForceUpdate(this.props)
}
componentDidMount() {
if (['editor', 'blogger'].includes(CollaboratorsStore.getRole())) {
return
}
// Hack: category and name can be updated from publish dialog, update states when open setting dialog
this._openPageSettingsToken = EditorDispatcher.register(payload => {
switch (payload.actionType) {
case EditorConstants.ActionTypes.OPEN_PAGE_SETTINGS:
this.setStateWithForceUpdate(this.props)
// refresh status when domain tab is selected after opened settings dialog
if (this.props.selected) {
this._refreshDomainStatus()
}
}
})
}
componentWillUnmount() {
EditorDispatcher.unregister(this._openPageSettingsToken)
}
componentDidUpdate(prevProps, prevState) {
// refresh status when swtich to domain tab
if (!prevProps.selected && this.props.selected) {
this._refreshDomainStatus()
}
}
getDefaultState() {
return Immutable.Map({
// global common tab state
isUpdating: false,
isSaved: false,
status: '',
// domains state
showRegisterBtn: true,
showPurchaseSuccessMsg: false,
customDomainSaved: false,
customDomainStatus: '',
customDomainMessage: '',
customDomainCheckRs: '',
domainProvider:
window.localStorage.getItem('_strk_domain_provider') || '',
currentStep: 'tab',
currentDomain: this.props.customDomain || '',
domainPurchaseStatus: CurrentUserStore.domainPurchaseProcessStatus(),
v2DomainSetting: (() => {
const ds = PageMetaStore.getUserV2DomainSetting()
return ds ? ds.toJS() : null
})(),
})
}
// because morearty's shouldComponentUpdate doesn't account for changes in state
setStateWithForceUpdate(h) {
this.setState(h)
this.forceUpdate()
}
_refreshDomainStatus() {
const customDomainStatus = this.getDefaultBinding().get(
'customDomainStatus',
)
let checkRs = this.getDefaultBinding().get('customDomainCheckRs')
if (!customDomainStatus && checkRs) {
checkRs = checkRs.toJS()
if (checkRs.correct === false) {
this._onCheckDomainStatus()
}
}
}
_onPermalinkChange(e) {
this.setStateWithForceUpdate({
permalink: e.target.value,
})
}
_onBackToTab() {
this._onChangeStep('tab')
this.props.changeTabName(__('EditorSettings|Domain'))
}
_onConnectDomainAfterRegister(newDomain) {
EditorActions.addDomainToPool(newDomain)
newDomain = `www.${newDomain}`
EditorActions.updateCustomDomain(newDomain)
this.getDefaultBinding().set('currentDomain', newDomain)
// TODO change that to connect / disconnect later
SiteConnectionActions.updateCustomDomain({
siteId: this.props.siteId,
domain: newDomain,
success: data => {
this.props.updateV2CustomDomain(data)
this.getDefaultBinding().set('customDomainStatus', 'success')
},
})
this.getDefaultBinding().set('showPurchaseSuccessMsg', true)
this.getDefaultBinding().set('domainPurchaseStatus', 'buy')
this.getDefaultBinding().set('justConnectedAfterRegister', true)
this.getDefaultBinding().set('showRegisterBtn', false)
this.setTimeout(() => {
this._onBackToTab()
}, 1500)
}
_removePurchaseSuccessMsg() {
this.getDefaultBinding().set('showPurchaseSuccessMsg', false)
}
_onSavePermalink = () => {
if (this.refs.permalink.value.length >= 63) {
this.getDefaultBinding().set(
'status',
__('EditorSettings|Domain name cannot be longer than 63 characters.'),
)
} else {
this.getDefaultBinding().set('status', '')
this.props.onSaveSettings({
permalink: this.refs.permalink.value,
})
}
}
_saveDomainProvider(provider) {
this.getDefaultBinding().set('domainProvider', provider)
window.localStorage.setItem('_strk_domain_provider', provider)
}
_clearDomainProvider() {
this.getDefaultBinding().set('domainProvider', 'default')
window.localStorage.removeItem('_strk_domain_provider')
}
_onRegisterNewDomain() {
this._onChangeStep('purchase')
this._initialDomainName = ''
// EditorActions.registerDomain()
}
_onRegisterNewDomainWithName() {
this._onChangeStep('purchase')
this._initialDomainName = this.getDefaultBinding().get('currentDomain')
}
_onChangeStep(step) {
this.getDefaultBinding().set('currentStep', step)
}
_onChangeCustomDomain(e) {
this.getDefaultBinding().set('currentDomain', e.target.value)
}
_onSaveCustomDomain = e => {
let currentDomain = this.getDefaultBinding()
.get('currentDomain')
.toLowerCase()
if (
ConfStore.getIsSxl() &&
!currentDomain &&
!window.confirm(
'若停止使用自定义域名,网站将临时下线。你需要发布审核,通过后即可访问。确定停止使用自定义域名?',
)
) {
return
}
this.getDefaultBinding().set('saveBtnClicked', true)
const customDomainStatus = this.getDefaultBinding().get(
'customDomainStatus',
)
const { v2Domains } = this.props
if (['updating', 'checking'].includes(customDomainStatus)) {
return
}
this._clearDomainProvider()
this.setState({ currentDomain })
// If current domain is empty, we don't even bother, it's a disconnect
// If current domain start with www., we don't need to test (unless it's connecting to www.com)
if (!currentDomain || currentDomain.startsWith('www.')) {
this._setUpdating(currentDomain)
this._updateV2CustomDomain(currentDomain)
return
}
const existingDomain = _.find(v2Domains, d => d.name == currentDomain)
if (!existingDomain) {
this.getDefaultBinding().merge(
Immutable.fromJS({
customDomainStatus: 'updating',
}),
)
Promise.all([parseDomainPromise()]).then(([parseDomain]) => {
const domainChunks = parseDomain(currentDomain)
if (domainChunks && !domainChunks.subdomain) {
currentDomain = `www.${currentDomain}`
}
this.getDefaultBinding().merge(
Immutable.fromJS({
currentDomain,
}),
)
this._updateV2CustomDomain(currentDomain)
})
return
}
if (existingDomain.is_wwwizered) {
currentDomain = `www.${currentDomain}`
}
this._setUpdating(currentDomain)
this._updateV2CustomDomain(currentDomain)
}
_setUpdating(currentDomain) {
this.getDefaultBinding().merge(
Immutable.fromJS({
customDomainStatus: 'updating',
currentDomain,
}),
)
}
_updateV2CustomDomain(fqdn) {
// TODO change that to connect / disconnect later
SiteConnectionActions.updateCustomDomain({
siteId: this.props.siteId,
domain: fqdn,
success: data => {
this.props.updateV2CustomDomain(data)
this.getDefaultBinding().set('customDomainStatus', 'success')
},
afterError: res => {
this.getDefaultBinding().set('customDomainStatus', '')
this.getDefaultBinding().set('currentDomain', '')
},
})
}
_onCheckDomainStatus() {
this.getDefaultBinding().set('customDomainStatus', 'checking')
EditorActions.checkDomainStatus()
}
_onClickshowRegisterBtn() {
this.getDefaultBinding().set('showRegisterBtn', true)
}
_onChangeProvider(e) {
this._saveDomainProvider(e.target.value)
}
_renderDomainSettingsBox(v2Domain = false) {
const status = this.getDefaultBinding().get('customDomainStatus')
let checkRs = this.getDefaultBinding().get('customDomainCheckRs')
let linkUrl = ''
if (!v2Domain) {
if (checkRs && checkRs.toJS) {
checkRs = checkRs.toJS()
}
if (
typeof checkRs !== 'object' ||
checkRs.correct ||
!checkRs.domainRegistered ||
status === 'updating'
) {
return {
renderRs: null,
showDomainSettingsStatus: false,
}
}
}
const domainProviders = ConfStore.getDomainSupportedProvider()
const currentProvider = this.getDefaultBinding().get('domainProvider')
const saveBtnClicked = this.getDefaultBinding().get('saveBtnClicked')
const currentProviderObj = domainProviders.find(
provider => provider.name === currentProvider,
)
const subdomainUrl = ConfStore.getIsSxl()
? 'http://help.sxl.cn/hc/zh-cn/articles/214721838'
: 'http://support.strikingly.com/hc/en-us/articles/214364738-Generic-Subdomain-Setup-Tutorial'
let checkType = 'arecord'
if (currentProviderObj) {
switch (_checkConfig[currentProviderObj.name]) {
case 2:
checkType = 'forwarding'
break
case 3:
checkType = 'forwarding_or_arecord'
break
// no default
}
}
function getCheckDetail(type, index) {
let text = ''
let success = true
if (checkRs.domainType === 'non_www_subdomain') {
if (
!checkRs.nonWwwSubdomainHasCname ||
!checkRs.nonWwwSubdomainHasCnameToRoot
) {
success = false
}
text = __(
'EditorSettings|Add a CNAME record pointing from <strong>%{subDomain}</strong> to <strong>dns.strikingly.com</strong>',
{ subDomain: checkRs.nonWwwSubdomain },
)
} else {
switch (type) {
case 'cname':
if (
!checkRs.wwwSubdomainHasCname ||
!checkRs.wwwSubdomainHasCnameToRoot
) {
success = false
}
text = __(
'EditorSettings|Add a CNAME record pointing from <strong>www</strong> to <strong>%{rootDomain}</strong>',
{ rootDomain: 'dns.strikingly.com' },
)
break
case 'arecord':
if (
!checkRs.rootDomainRedirected ||
checkRs.rootDomainHasHerokuIp ||
checkRs.wwwSubdomainHasHerokuIp
) {
success = false
}
text = __(
'EditorSettings|Add an A record pointing from <strong>@</strong> to <strong>54.183.102.22</strong>',
)
break
case 'forwarding':
if (!checkRs.rootDomainRedirected) {
success = false
}
text = __(
'EditorSettings|Forward %{rootDomain} to <strong>http://%{wwwSubdomain}</strong>',
{
rootDomain: checkRs.rootDomain,
wwwSubdomain: checkRs.wwwSubdomain,
},
)
break
case 'forwarding_or_arecord':
if (
!checkRs.rootDomainRedirected ||
checkRs.rootDomainHasHerokuIp ||
checkRs.wwwSubdomainHasHerokuIp
) {
success = false
}
text = __(
'EditorSettings|Forward %{rootDomain} to <strong>http://%{wwwSubdomain}</strong> <br> <strong>OR</strong> Add an A record pointing from <strong>@</strong> to <strong>54.183.102.22</strong>',
{
rootDomain: checkRs.rootDomain,
wwwSubdomain: checkRs.wwwSubdomain,
},
)
break
// no default
}
}
return (
<tr key={type}>
<td className="domain-check-result-item">
&#8226;&nbsp;
<span dangerouslySetInnerHTML={{ __html: text }} />
</td>
<td className="domain-check-result-tag">
{status === 'checking' ? (
<i className="fa fa-spinner fa-pulse" />
) : (
<span
className={classNames('s-box-tag', {
red: !success,
green: success,
})}>
{success
? __('EditorSettings|Complete')
: __('EditorSettings|Pending')}
</span>
)}
</td>
</tr>
)
}
if (currentProviderObj) {
linkUrl =
checkRs.domainType === 'non_www_subdomain'
? currentProviderObj.subdomain || subdomainUrl
: currentProviderObj.url
}
__('The page has an invalid domain.')
// if (v2Domain) {
// currentProviderObj = domainProviders.find((provider) => provider.name === 'Others')
// linkUrl = currentProviderObj.url
// }
return {
showDomainSettingsStatus: true,
renderRs: (
<ReactTransitionGroup>
<JQSlide component={DOM.div} className="domain-check-box s-box last">
<div>
<div className="field-title">
{saveBtnClicked
? __(
'EditorSettings|Almost done! You still need to configure your domain settings.',
)
: __(
'EditorSettings|Note: Make sure your domain is configured properly.',
)}
</div>
<select
onChange={this._onChangeProvider.bind(this)}
value={currentProvider}>
<option value="default" disabled={true}>
{__('EditorSettings|Where did you register this domain?')}
</option>
{domainProviders.map((item, index) => (
<option key={item.name} value={item.name}>
{item.name === 'Others' ? __('Domain|Others') : item.name}
</option>
))}
</select>
{currentProviderObj && (
<div>
<ReactTransitionGroup>
<JQSlide
component={DOM.div}
className="domain-check-result">
<div style={{ marginBottom: '12px' }} />
</JQSlide>
</ReactTransitionGroup>
<div>
<a
className="s-btn basic-blue"
target="_blank"
href={linkUrl}>
<i className="fa fa-book left-icon" />
{__('EditorSettings|Read Tutorial')}
</a>
<i
className="fa fa-question-circle"
rel="tooltip-right"
data-original-title={__(
'EditorSettings|Changes in domain settings usually take effect in a few hours, but can take up to 2 days.',
)}
/>
</div>
</div>
)}
</div>
</JQSlide>
</ReactTransitionGroup>
),
}
}
_renderDomainTab() {
const {
isUpdating,
status,
isSaved,
customDomainSaved,
customDomainStatus,
customDomainMessage,
customDomainCheckRs,
showRegisterBtn,
showPurchaseSuccessMsg,
domainPurchaseStatus,
justConnectedAfterRegister,
currentDomain,
} = this.getDefaultBinding()
.get()
.toJS()
const { permalink, isDirLink } = this.state
const { v2DomainSetting } = this.props
const domainSupported = /(.com|.net|.org|.me|.co)$/.test(currentDomain)
const isSxl = ConfStore.getIsSxl()
const v2DomainConnection = PageMetaStore.getV2DomainConnection()
const showConnectedMessage =
v2DomainConnection && v2DomainConnection.get('domain_id')
const forceShowKB =
v2DomainConnection && !v2DomainConnection.get('domain_id')
const isSiteOfResellerClient = PageMetaStore.isSiteOfResellerClient()
let registerDomainContainer
if (!isSxl) {
if (domainPurchaseStatus === 'free') {
registerDomainContainer = (
<div className="register-domain">
{tct(
__(
"Only premium users can register a new domain through our system. If you [link: purchase a yearly subscription plan] to Strikingly, we'll give you a domain for free!",
),
{
root: <div className="field-hint" />,
link: <a href={
UserMetaStore.isZbjUser()
? 'javascript: alert("八戒云站暂不支持升级,请添加新的套餐。")'
: '/s/pricing?ref=free_domain'
} />,
},
)}
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Check Availability')}
</div>
</div>
)
} else if (
!justConnectedAfterRegister &&
v2DomainSetting.entitledToFreeDomain
) {
registerDomainContainer = (
<div className="register-domain">
<div className="field-hint">
{__(
`You're on a yearly plan and can register a new domain for free!`,
)}
</div>
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Claim My Free Domain!')}
</div>
</div>
)
} else {
registerDomainContainer = (
<div className="register-domain">
<div className="field-hint">
{__(`Don't own a domain yet? Grab one here for $24.95/year.`)}
</div>
<div
className="s-btn big basic-blue"
onClick={this._onRegisterNewDomain}>
{__('Register New Domain')}
</div>
</div>
)
}
}
let permalinkInner
let completedPermalink
if (isDirLink) {
completedPermalink = `${
isSxl ? 'http://www.sxl.cn/' : 'http://www.strikingly.com/'
}${permalink}`
permalinkInner = (
<div className="permalink-inner">
{isSxl ? 'http://www.sxl.cn/' : 'http://www.strikingly.com/'}
<input
type="text"
ref="permalink"
value={permalink}
onChange={e => {
// permalink could be updated from prepublish dialog, shouldn't use defaultValue here.
this._onPermalinkChange(e)
}}
/>
</div>
)
} else {
completedPermalink = `http://${permalink}${
isSxl ? '.sxl.cn/' : '.strikingly.com/'
}`
permalinkInner = (
<div className="permalink-inner">
http://
<input
type="text"
ref="permalink"
value={permalink}
onChange={e => {
// permalink could be updated from prepublish dialog, shouldn't use defaultValue here.
this._onPermalinkChange(e)
}}
/>
{isSxl ? '.sxl.cn/' : '.strikingly.com/'}
</div>
)
}
const domainSettingsRs = this._renderDomainSettingsBox(forceShowKB)
const MoreartyInput = Morearty.DOM.input
return (
<div
key="first-step"
className="page-settings-content s-dialog-content domains-tab">
{
<PremiumFeature
featureName="custom_domain"
overlayStyle="fieldOverlay"
title={__(
'Domain|Register for or connect to a custom domain for this site.',
)}
hint={__(
'Upgrade your account to either Limited or Pro to access this feature!',
)}
source="cd">
{!isSxl && (
<div>
{showPurchaseSuccessMsg && (
<ReactTransitionGroup>
<JQSlide
component={DOM.div}
className="s-box green small fist">
<i className="fa fa-check" />
{tct(
__(
"Domain|[placeholderStrong: You just got a new domain!] And we've already connected it to your site. Remember, you must check your email to validate your domain.",
),
{
root: <span />,
placeholderStrong: <strong />,
},
)}
&nbsp;
<a
href={
v2DomainSetting ? '/s/v2_domains/' : '/s#/domains'
}
target="_blank">
{__('Domain|View your new domain in domain dashboard.')}
</a>
<div
className="close-btn"
onClick={this._removePurchaseSuccessMsg}>
×
</div>
</JQSlide>
</ReactTransitionGroup>
)}
{!isSiteOfResellerClient &&
(showRegisterBtn ? (
<div className="form-field">
<div className="field-title">
{__('Register A New Domain')}
</div>
{registerDomainContainer}
</div>
) : (
<a
onClick={this._onClickshowRegisterBtn.bind(this)}
href="javascript: void(0);">
{__('Register New Domain')}
</a>
))}
<div className="hr" />
</div>
)}
<div className="form-field custom-domain-field">
<div className="field-title">
{__('Custom Domain/Subdomain')}
<StrikinglyOrSxl>
<span className="s-pro-icon">
{__('EditorSettings|Pro / Limited')}
</span>
<span className="s-pro-icon">
{__('EditorSettings|Biz / Pro / Limited')}
</span>
</StrikinglyOrSxl>
</div>
{tct(
__(
'EditorSettings|If you already own a domain, enter it here. Empty it to disconnect.',
),
{
root: <div className="field-hint" />,
},
)}
<div className="custom-domain-update">
<MoreartyInput
type="text"
placeholder={__('e.g. www.mydomain.com')}
value={currentDomain}
onChange={this._onChangeCustomDomain}
/>
<div className="s-btn" onClick={this._onSaveCustomDomain}>
{__('Update')}
{['updating', 'checking'].includes(customDomainStatus) ? (
<i className="fa fa-spinner fa-pulse right-icon" />
) : null}
{customDomainSaved ? <i className="fa fa-check" /> : null}
</div>
{isSxl &&
currentDomain && (
<i
className="fa fa-qrcode qrcode-btn"
style={{ marginLeft: '10px' }}
onClick={() =>
this.props.showQrCode(`http://${currentDomain}`)
}
/>
)}
{showConnectedMessage && (
<div className="s-box-tag green">
{__('EditorSettings|Domain is connected!')}
</div>
)}
{isSxl &&
!this.props.customDomain && (
<div style={{ display: 'block' }}>
<ol className="connect-custom-domain-tips">
<li>
<Icon type="fa-check-circle-o" /> SEO 排名优化
</li>
<li>
<Icon type="fa-check-circle-o" /> 品牌更专业
</li>
<li>
<Icon type="fa-check-circle-o" /> 访问更快速
</li>
</ol>
<a href="https://wanwang.aliyun.com/" target="_blank">
还没有域名?前往万网购买
</a>
</div>
)}
</div>
{customDomainStatus !== 'updating' && domainSettingsRs.renderRs}
{customDomainStatus === 'error' && (
<div className="field-notice error">
<div>
<i className="entypo-info-circled" />
{customDomainMessage === 'Already taken'
? tct(
__(
'Domain|Sorry,[placeholderStrong: %{domain}] is already taken.',
{
domain: this.state.currentDomain,
},
),
{
root: <span />,
placeholderStrong: <strong />,
},
)
: customDomainMessage}
</div>
</div>
)}
{!['updating', 'checking'].includes(customDomainStatus) &&
customDomainCheckRs &&
customDomainCheckRs.domainRegistered === false && (
<div className="field-notice error">
<div>
<i className="entypo-info-circled" />
{domainSupported ? (
<span>
{__(
'EditorSettings|This domain is not registered yet! You can register it now.',
)}
&nbsp;
<StrikinglyOrSxl>
<a
href="javascript: void(0);"
onClick={this._onRegisterNewDomainWithName}>
{__('EditorSettings|Claim your Domain')}
</a>
{null}
</StrikinglyOrSxl>
</span>
) : (
<span>
{__(
'EditorSettings|This domain is not registered yet!',
)}
</span>
)}
</div>
</div>
)}
</div>
</PremiumFeature>
}
{<div className="hr" />}
<div className="form-field permalink">
<div className="field-title">{__('Strikingly.com URL')}</div>
<div className="field-hint">
{__(
'Enter a unique Strikingly.com URL. You may change this at any time.',
)}
</div>
{
<div>
<div className="permalink-container">
{permalinkInner}
<div
className="s-btn permalink-update-btn"
onClick={this._onSavePermalink}>
{__('Update')}
{isUpdating ? <i className="fa fa-spinner fa-pulse" /> : null}
{isSaved ? <i className="fa fa-check" /> : null}
</div>
{isSxl &&
permalink && (
<i
className="fa fa-qrcode qrcode-btn"
onClick={() => this.props.showQrCode(completedPermalink)}
/>
)}
</div>
<div className="tab-status">{status}</div>
</div>
}
</div>
</div>
)
}
render() {
const step = this.getDefaultBinding().get('currentStep')
const domainPurchaseStatus = this.getDefaultBinding().get(
'domainPurchaseStatus',
)
const v2DomainSetting = this.getDefaultBinding().get('v2DomainSetting')
const { currentTabName, changeTabName } = this.props
return (
<div>
{step === 'tab' ? (
this._renderDomainTab()
) : (
<PurchaseContainer
key="second-step"
inSettingsPanel={true}
domainPurchaseStatus={domainPurchaseStatus}
v2DomainSetting={v2DomainSetting}
currentTabName={currentTabName}
changeTabName={changeTabName}
backToTab={this._onBackToTab}
connectDomain={this._onConnectDomainAfterRegister}
initialDomainName={this._initialDomainName}
/>
)}
</div>
)
}
}
/*
Connected wrapper will use shadow equal for props checking by default.
So when binding content changed, connected wrapper will avoid to render, binding ref is not changed (binding is a mutable object).
Set pure option to false to let connected wrapper can re-render when it's parent calls re-render.
The extra cost for this change is the re-render for connected wrapper (HOC).
Won't introduce addtional cost (won't cause addtional render) for the original component (DomainsTab).
*/
export default connect(
null,
{
showQrCode: url => openDialog('urlQrCodeDialog', { url }),
},
null,
{
pure: false,
},
)(DomainsTab)
import './init'
import _ from 'lodash'
import $ from 'jquery'
import 'js/vendor/jquery/easing'
import React from 'react'
import ReactDOM from 'react-dom'
import { Iterable } from 'immutable'
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import ManagerReducer from 'nextgen/ecommerce/manager/reducers/ManagerReducer'
import connectReduxStoreToBinding from 'js/utils/helpers/connectReduxStoreToBinding'
import PortfolioManagerStore from 'js/stores/PortfolioManagerStore'
import FeatureStore from 'js/stores/FeatureStore'
import ConfStore from 'js/stores/conf_store'
import * as UrlConstants from 'js/constants/url_constants'
import ProductPanel from 'nextgen/portfolio/manager/components/productPanel'
import CategoryManagerWrapper from 'nextgen/ecommerce/manager/components/settings/categoryManagerWrapper'
import gonReader from 'js/utils/gonReader'
import { AppContainer } from 'react-hot-loader'
import { ErrorBoundary } from 'js/components/ErrorBoundary'
import { UPDATE_SETTINGS_SUCCESS } from 'nextgen/ecommerce/manager/actions/entities/settings'
import PortfolioManagerBridge from 'js/v4_bridge/PortfolioManagerBridge'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import ComponentKitContext from 'js/utils/ComponentKitContext'
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
const loggerMiddleware = createLogger({
collapsed: true,
predicate: (/* getState, action */) => process.env.NODE_ENV !== 'production',
stateTransformer: state => {
if (Iterable.isIterable(state)) {
return state.toJS()
}
return state
},
})
const store = createStore(
ManagerReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware, // neat middleware that logs actions
),
)
const baseProps = {
siteId: gonReader('id'),
siteState: gonReader('state'),
publicUrl: gonReader('public_url'),
meta: gonReader('pageOwner.portfolio'),
isStandAlone: window.parent === window,
isPro: ['pro', 'namecheap', 'sxlbiz'].includes(
gonReader('pageOwner.membership'),
),
isVip: gonReader('pageOwner.membership') === 'vip',
isSxl: Boolean(gonReader('globalConf.isSxl')),
isAdmin: Boolean(gonReader('pageOwner.isAdmin')),
isSupport: Boolean(gonReader('pageOwner.isSupport')),
}
const productProps = {
...baseProps,
productLimit:
gonReader('pageOwner.portfolio').productLimit ||
(gonReader('globalConf.isSxl') ? 6 : 1),
upgradeUrl: UrlConstants.PRICING.GOTO_AND_RETURN(
gonReader('id'),
'portfolio',
),
productPageRollout: gonReader('globalConf.rollout.product_page'),
productDetailRollout: gonReader('globalConf.rollout.product_detail'),
siteMemberShip: gonReader('globalConf.rollout.siteMemberShip'),
noCategoryLimit: gonReader('globalConf.rollout.no_category_limit'),
}
class ProductsContainer extends React.Component {
componentWillMount() {
PortfolioManagerBridge.subPublishUrlChange((topic, data) => {
baseProps.publicUrl = data.url
this.forceUpdate()
})
PortfolioManagerBridge.subSiteStateChange((topic, data) => {
baseProps.siteState = data.state
this.forceUpdate()
})
}
render() {
return (
<Provider store={store}>
<ProductPanel
{...productProps}
canUseCategory={FeatureStore.canUse('portfolio_category')}
canSeeCategory={FeatureStore.canSee('portfolio_category')}
/>
</Provider>
)
}
}
function CategoryContainer(/* props */) {
return (
<Provider store={store}>
<CategoryManagerWrapper
{...productProps}
isPortfolio={true}
canUseCategory={FeatureStore.canUse('portfolio_category')}
canSeeCategory={FeatureStore.canSee('portfolio_category')}
/>
</Provider>
)
}
const CategoryContainerWithComponentKit = ComponentKitContext(CategoryContainer)
const siteIdString = gonReader('id').toString()
PortfolioManagerBridge.subSettingsChange((topic, data) => {
let settings =
store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']) || {}
if (settings.toJS) {
settings = settings.toJS()
}
store.dispatch({
type: UPDATE_SETTINGS_SUCCESS,
payload: {
settings: Object.assign(settings, data.settings),
},
meta: {
siteId: siteIdString,
},
})
})
const getState = () => ({
product: store
.getState()
.getIn(['entities', 'product', 'data', siteIdString]),
category: store.getState().getIn(['entities', 'category', 'data']),
settings: store
.getState()
.getIn(['entities', 'settings', 'data', siteIdString, 'settings']),
})
const prevState = getState()
store.subscribe(() => {
const newState = getState()
if (newState.product !== prevState.product) {
prevState.product = newState.product
PortfolioManagerBridge.pubProductsChange(
_.flatten(_.toArray(newState.product.toJS())),
)
}
if (newState.settings !== prevState.settings) {
prevState.settings = newState.settings
PortfolioManagerBridge.pubSettingsChange(newState.settings.toJS())
}
if (newState.category !== prevState.category) {
prevState.category = newState.category
PortfolioManagerBridge.pubCategoriesChange(
_.flatten(_.toArray(newState.category.toJS())),
)
}
})
Promise.all([p1()])
.then(([poFile]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
$(() => {
const cloudinary = require('cloudinary')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
if (window.parent === window) {
let ImageAssetDialog = require('js/v4_bridge/react_app_bridge/ImageAssetDialogApp')
const EditorStore = require('js/stores/editor_store')
const ctx = EditorStore.init()
connectReduxStoreToBinding(store, ctx.getBinding())
ImageAssetDialog = ctx.bootstrap(ImageAssetDialog())
ReactDOM.render(
<Provider store={store}>
<ErrorBoundary>
<AppContainer>
<ImageAssetDialog />
</AppContainer>
</ErrorBoundary>
</Provider>,
document.getElementById('container'),
)
}
PortfolioManagerStore.init()
FeatureStore.hydrate($S.features)
const productsContext = PortfolioManagerStore.getProductsContext()
const WrapperedProductsContainer = productsContext.bootstrap(
ComponentKitContext(ProductsContainer),
)
ReactDOM.render(
<AppContainer>
<ErrorBoundary>
<WrapperedProductsContainer />
</ErrorBoundary>
</AppContainer>,
document.getElementById('items-container'),
)
ReactDOM.render(
<ErrorBoundary>
<CategoryContainerWithComponentKit />
</ErrorBoundary>,
document.getElementById('category-manager'),
)
})
})
.catch(e => console.error(e))
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
# This is an example forman configure file for bobcat
# You can adjust the procs based on your personal development need
#
# 1. Install forego `brew install forego` https://github.com/ddollar/forego
# 2. Copy this file to Procfile
# 3. Configure the tasks
# 3. Start all procs by `forego start`
#
# NOTICE: 1. All the logs are under /log, you can simply check log by `tail -f log/sidekiq.log`.
# 2. The error messages should better be piped to stdout, that's what `2>&1 >file` for.
# 3. forego supports multiple processes invoke `forego start -c all=1, worker=2` this will start 2 worker process
# WEB server
# default way to start server
# server log is piped to stdout by default
# Server with hotload
web: env DISABLE_UNICORN_KILLER=1 REACT_HOT=1 PRODUCT_NAME=sxl RACK_ENV=none RAILS_ENV=development bundle exec unicorn -p 3000 -c unicorn_dev.rb
# Server with no hotload
# web: env DISABLE_UNICORN_KILLER=1 PRODUCT_NAME= RACK_ENV=none RAILS_ENV=development bundle exec unicorn -p 3000 -c unicorn_dev.rb
# WORKER, you can adjust the queues based on your need
worker: env PRODUCT_NAME= bundle exec sidekiq -C config/sidekiq.yml 2>&1 >log/worker.log
# THEME, watch mode
theme: bundle exec strk_build_manifests --watch 2>&1 >log/theme.log
# page_data: strk_build_pages_data
# OpenResty
nginx: env CUSTOM_HOST= openresty -p devs/nginx -c nginx.conf
# If you need to bind custom host:
# nginx: env CUSTOM_HOST=192.168.75.128 openresty -p devs/nginx -c nginx.conf
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "app",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./app/section_selections/navbar/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./app/section_selections/info/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./app/section_selections/columns/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
import 'js/loadBugsnag'
import './init'
import React from 'react'
import ReactDOM from 'react-dom'
import $ from 'jquery'
import logger from 'js/utils/logger'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import bootstrapBlog from 'js/prerender/bootstrapBlog'
import ComponentKitContext from 'js/utils/ComponentKitContext'
if (typeof window.timerStart === 'undefined') {
window.timerStart = new Date().getTime()
}
window.timerCheck = function(label) {
const time = new Date().getTime() - window.timerStart
const msg = `${label} in ${time}ms`
logger.log(msg)
return msg
}
window.edit_page = require('js/v3_bridge/edit_page_bridge')
window.edit_page.isBlog = true
window.edit_page.isShowPage = true
// These two are direct reads so promises can be fired quicker
// const locale = $S.globalConf.locale
const themeName = $S.blogPostData.pageMeta.theme.name_with_v4_fallback
const forcedLocale = $S.blogPostData.pageMeta.forcedLocale
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile(
forcedLocale,
)}`)
const p2 = require(`promise-loader?global!manifests/themes/${themeName}.js`)
Promise.all([p1(), p2()])
.then(([poFile, manifest]) => {
const BlogBootstrap = ComponentKitContext(
bootstrapBlog({
poFile,
manifest,
}),
)
$(() => {
ReactDOM.render(
<BlogBootstrap />,
document.getElementById('s-blog-container'),
)
window.timerCheck('React has finished rendering')
})
})
.catch(e => console.error(e))
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "bright",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./bright/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./bright/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./bright/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./bright/section_selections/cta/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./bright/section_selections/info/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./bright/section_selections/hero/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
require('./init')
import $ from 'jquery'
import React from 'react'
import ReactDOM from 'react-dom'
import cloudinary from 'cloudinary'
import logger from 'js/utils/logger'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
// import {AppContainer} from 'react-hot-loader'
import * as RHL from 'react-hot-loader'
import { wrapComponentWithReduxStore } from 'js/utils/reduxUtil'
import * as editorStoreCreator from 'js/reducers/editorStoreCreator'
import ComponentKitContext from 'js/utils/ComponentKitContext'
import wrapErrorBoundary from 'js/components/ErrorBoundary'
import 'js/reactInit.es6'
// These two are direct reads so promises can be fired quicker
const supportedVerticals = ['personal']
const themeName = $S.stores.pageMeta.theme.name
const AppContainer = wrapErrorBoundary(RHL.AppContainer)
// Load dynamic editor and theme style files on editor debug environment
if (__MODE__ === "'editor-debug'") {
require('v4/editor')
require(`themes/${themeName}/main_v4_editor`)
}
let verticalName = $S.stores.pageMeta.vertical
// only apply supported vertical
if (!supportedVerticals.includes(verticalName)) {
verticalName = themeName
}
require('./utils/extensions/native')
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile()}`)
const p2 = require(`promise-loader?global!manifests/themes/${themeName}.js`)
const p3 = require(`promise-loader?global!manifests/verticals/${verticalName}.js`)
Promise.all([p1(), p2(), p3()])
.then(([poFile, manifest, vertical]) => {
const I18n = require('js/utils/i18n')
I18n.init(poFile)
const ConfStore = require('js/stores/conf_store')
cloudinary.config('cloud_name', ConfStore.getCloudinaryCloudName())
function injectVerticalData() {
const SectionSelectorStore = require('js/stores/section_selector_store')
// fill in dynamic default page data based on user or page meta (e.g email)
const FillInDynamicDefaultThemeData = require('js/utils/themes/FillInDynamicDefaultThemeData')
manifest = FillInDynamicDefaultThemeData(manifest)
for (const key in vertical.sectionSelections) {
const selector = vertical.sectionSelections[key]
_.merge(selector, manifest.sections[selector.content.template_name])
}
SectionSelectorStore.setSelectorData(vertical.sectionSelections)
}
if (__NATIVE_WEB__) {
// TODO: wrap it in another file
const NativeBridge = require('js/actions/NativeBridge')
window.NativeBridge = window.NativeBridge || {}
window.NativeBridge.setTarget = NativeBridge.setTarget
window.NativeBridge.sendMessageToWeb = NativeBridge.sendMessageToWeb
const EditorStore = require('./stores/editor_store')
const Ctx = EditorStore.init()
EditorStore.hydrate($S.stores)
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
injectVerticalData()
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Editor = require('js/components/editor')
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
} else if (__IFRAME_EDITOR__) {
const ctx = window.parent._ctx.copy()
// iframe editor will use the same redux store as it's parent
const editorStore = window.parent._editor_store
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Site = require('js/components/MobileViewSite')
const SiteWrapper = wrapComponentWithReduxStore(
ctx.bootstrap(EditorBootstrap.bootstrap(Site)),
editorStore,
)
const EditorWithContext = ComponentKitContext(SiteWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
} else {
window.timerStart = window.timerStart || new Date().getTime()
window.timerCheck = label => {
const time = new Date().getTime() - timerStart
const msg = `#{label} in ${time}ms`
logger.log(msg)
return msg
}
const EditorStore = require('./stores/editor_store')
require('./v3_bridge')()
require('js/utils/component_registration')
$(() => {
injectVerticalData()
const ThemeStore = require('js/stores/theme_store')
ThemeStore.buildAndRegister(manifest)
const EditorBootstrap = require('js/components/EditorBootstrap')
const Editor = require('js/components/editor')
const Ctx = EditorStore.init()
EditorStore.hydrate($S.stores)
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
window._ctx = EditorStore.getCtx()
// to iframe editor can use the same redux store as it's parent
window._editor_store = editorStoreCreator.getStore()
const PageMetaStore = require('./stores/page_meta_store')
const CurrentUserStore = require('./stores/current_user_store')
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
if (module.hot) {
module.hot.accept(['js/components/editor'], () => {
const Editor = require('js/components/editor')
const EditorWrapper = wrapComponentWithReduxStore(
EditorBootstrap.bootstrap(Ctx.bootstrap(Editor)),
editorStoreCreator.getStore(),
)
const EditorWithContext = ComponentKitContext(EditorWrapper)
ReactDOM.render(
<AppContainer>
<EditorWithContext />
</AppContainer>,
document.getElementById('s-editor-container'),
)
})
}
const isStrikinglyReseller =
CurrentUserStore.isResellerAgent() && !ConfStore.getIsSxl()
if (
!ConfStore.getInIosApp() &&
!ConfStore.getInWeChat() &&
(!PageMetaStore.isSiteOfResellerClient() ||
isStrikinglyReseller ||
ConfStore.getRollout('show_reseller_support_widget'))
) {
const SupportWidget = require('js/components/support_widget/SupportWidget')
ReactDOM.render(
<RHL.AppContainer>
<SupportWidget />
</RHL.AppContainer>,
document.getElementById('s-support-widget-container'),
)
}
})
}
})
.catch(error => {
// catch errors and call console.error so that they are reported by Bugsnag
console.error('You had an error: ', error.stack)
throw error
})
if (module.hot) {
module.hot.accept(err => {
if (err) {
console.error(err)
}
})
}
// like manifest webpack plugin, but fit multiple configs environment of webpack
// generate entries of each config, and collect them into a manifest json file
const path = require('path')
const fse = require('fs-extra')
module.exports = class EntriesGenerationWebpackPlugin {
constructor(opts) {
this.opts = Object.assign(
{
fileName: 'manifest.json',
appendMode: true,
transformExtensions: /^(gz|map)$/i,
},
opts
)
}
getFileType(str) {
str = str.replace(/\?.*/, '')
const split = str.split('.')
let ext = split.pop()
if (this.opts.transformExtensions.test(ext)) {
ext = `${split.pop()}.${ext}`
}
return ext
}
apply(compiler) {
compiler.plugin('compilation', compilation => {
compiler.plugin('after-emit', (compilation, compileCallback) => {
const publicPath =
this.opts.publicPath || compilation.options.output.publicPath
let files = compilation.chunks.reduce(
(files, chunk) =>
chunk.files.reduce((files, path) => {
let name
if (chunk.name) {
name = this.opts.setEntryName
? this.opts.setEntryName(chunk.name)
: chunk.name
name = `${name}.${this.getFileType(path)}`
} else {
name = path
}
return files.concat({
path,
chunk,
name,
isInitial: chunk.isInitial ? chunk.isInitial() : chunk.initial,
isChunk: true,
isAsset: false,
isModuleAsset: false,
})
}, files),
[]
)
if (publicPath) {
files = files
.filter(file => file.path.indexOf('hot-update') === -1)
.map(file => {
file.path = publicPath + file.path
return file
})
}
const manifest = files.reduce((manifest, file) => {
manifest[file.name] = file.path
return manifest
}, {})
let json = JSON.stringify(manifest, null, 2)
const outputFolder = compilation.options.output.path
const outputFile = path.resolve(
compilation.options.output.path,
this.opts.fileName
)
const outputName = path.relative(outputFolder, outputFile)
compilation.assets[outputName] = {
source() {
return json
},
size() {
return json.length
},
}
if (this.opts.appendMode && fse.existsSync(outputFile)) {
const previousJson = JSON.parse(
fse.readFileSync(outputFile).toString()
)
json = JSON.stringify(Object.assign(previousJson, manifest), null, 2)
}
fse.outputFileSync(outputFile, json)
compileCallback()
})
})
}
}
// SPEC
// Owner: Dafeng
// Feature name: Font store
// * user can update fonts from choosing style
// NOTE:
// Specs here are all technical implementation. Will be ignored for the current QA.
import _ from 'lodash'
import Immutable from 'immutable'
import { EventEmitter } from 'events'
import EditorDispatcher from 'js/dispatcher/editor_dispatcher'
import EditorConstants from 'js/constants/editor_constants'
import BindingHelper from 'js/utils/helpers/binding_helper'
import weakmapMemoize from 'js/utils/weakmapMemoize'
import loadFonts from 'promise-loader?global!json5-loader!../../../config/fonts.json'
const getDefaultFontName = require('js/utils/getDefaultFontName').default
let _bHelper
let _pDataHelper // need the binding to pageData to update fonts
const _updateFont = function(fontType, v) {
const name = `${fontType}Font`
_pDataHelper.setData(name, v)
_clearPreviewFont()
}
const _updateFontPreset = function(preset) {
_pDataHelper.setData('fontPreset', preset.id)
;['title', 'heading', 'body'].map(textType =>
_updateFont(textType, preset.fonts[textType]),
)
}
// No longer update fonts from style!
// _updateFontsFromStyle = (styles) ->
// _.map ['heading','body','title'], (font) =>
// _updateFont(font, styles[font + "Font"])
const _updatePreviewFont = (fontType, v) =>
_bHelper.setData(`preview.${fontType}Font`, v)
const _updatePreviewFontPreset = preset =>
['title', 'heading', 'body'].map(textType =>
_updatePreviewFont(textType, preset.fonts[textType]),
)
const _clearPreviewFont = function() {
const preview = _bHelper.binding.sub('preview')
const transaction = preview.atomically()
const object = preview.get().toJS()
for (const key in object) {
const v = object[key]
transaction.set(key, undefined)
}
transaction.commit()
}
const FontStore = _.assign({}, EventEmitter.prototype, {
// fontPreset is currently for Shangxianle, which designates font setting of
// title, heading, and body
// TODO - Andy, added 22 March 2016
// Improve the font determination logic:
// - first check if fontPreset is present, if so, use the preset setting
// - if no fontPreset, use setting for titleFont, bodyFont etc.
// Currently, fontPreset is just a collection of setting of the other three
// fonts, which means, if we modify the style setting of a preset, the pages
// that use this preset will NOT get style updates, unless they choose another
// preset and switch back.
_allFonts: null,
_initialFonts: null,
_fontsSelectedOnPageLoad: [],
getDefault(pageData) {
return {
preview: {
titleFont: '',
bodyFont: '',
headingFont: '',
fontPreset: '',
},
data: {
// use for notifying rich text component
titleFont: pageData.titleFont,
bodyFont: pageData.bodyFont,
headingFont: pageData.headingFont,
fontPreset: pageData.fontPreset,
},
}
},
init(b, pDataB) {
_bHelper = new BindingHelper(b)
const ConfStore = require('js/stores/conf_store')
if (__IN_EDITOR__ && !ConfStore.getIsBlog()) {
_pDataHelper = new BindingHelper(pDataB)
// Manually synchronize side_menu and page_data
// to avoid unnecessary render for side menu
;['headingFont', 'bodyFont', 'titleFont', 'fontPreset'].map(font =>
_pDataHelper.binding.addListener(font, changes =>
_bHelper.setData(`data.${font}`, _pDataHelper.getData(font)),
),
)
}
return _bHelper.binding
},
_setBHelperForTests(bHelper) {
return (_bHelper = bHelper)
},
loadFontsIfNotLoaded() {
if (_bHelper.getData('isLoadingFonts')) {
return
}
_bHelper.setData('isLoadingFonts', true)
return loadFonts()
.then(fonts => {
this._setAllFonts(fonts)
return _bHelper.setData('isLoadingFonts', false)
})
.catch(() => _bHelper.setData('isLoadingFonts', false))
},
hydrate(fonts, pageData, initFonts) {
this._initialFonts = initFonts
_bHelper.binding
.atomically()
.set(Immutable.fromJS(this.getDefault(pageData)))
.commit({ notify: false })
return (this._fontsSelectedOnPageLoad = this._getUsedFonts())
},
getData(k) {
return _bHelper.binding.get(k)
},
getBinding() {
return _bHelper.binding
},
getFontName(type, options = {}) {
let preview = false
if (options.preview != null) {
;({ preview } = options)
}
if (preview) {
return _bHelper.getData(`preview.${type}Font`)
} else {
return _bHelper.getData(`data.${type}Font`)
}
},
getAvailableFonts() {
if (this._allFonts) {
return this._allFonts
} else {
return this._initialFonts
}
},
search(fontUsage, searchTerm) {
let matchAtBeginning = []
let generalMatch = []
const normalizeFontName = name => name.toLowerCase().replace(/ /g, '')
searchTerm = normalizeFontName(searchTerm)
this.getAvailableFonts().forEach(f => {
if (fontUsage === 'body' && f.disableBody) {
return
}
if (f.hidden) {
return
}
const name = normalizeFontName(f.displayName)
if (name.slice(0, searchTerm.length) === searchTerm) {
return matchAtBeginning.push(f)
} else if (name.indexOf(searchTerm) !== -1) {
return generalMatch.push(f)
}
})
matchAtBeginning = _.sortBy(matchAtBeginning, f => f.name)
generalMatch = _.sortBy(generalMatch, f => f.name)
return matchAtBeginning.concat(generalMatch).slice(0, 20)
},
_getSuggestedFonts() {
return this.getVisibleFonts().filter(f => f.isSuggested)
},
_getUsedFonts() {
return _([
this.getFontName('title'),
this.getFontName('heading'),
this.getFontName('body'),
])
.compact()
.uniq()
.map(name => this.getFontByName(name))
.value()
},
getSuggestedFonts(fontUsage) {
const usedFontsForOtherTextTypes = this._getUsedFonts()
const defaultFontName = getDefaultFontName(fontUsage)
const defaultFont = this.getFontByName(defaultFontName)
let popular = this._getSuggestedFonts().concat(
this._fontsSelectedOnPageLoad,
)
popular = _(popular)
.filter(font => font.name !== defaultFontName)
.sortBy(font => font.name)
.value()
let fonts = usedFontsForOtherTextTypes.concat([defaultFont]).concat(popular)
fonts = _(fonts)
.reject(f => {
if (fontUsage === 'body' && f.disableBody) {
return true
}
if (f.hidden) {
return true
}
})
.uniq(f => f.name)
.value()
return fonts
},
_setAllFonts(fonts) {
this._allFonts = fonts
},
_getVisibleFonts: weakmapMemoize(availableFonts =>
availableFonts.filter(f => !f.hidden),
),
getVisibleFonts() {
return this._getVisibleFonts(this.getAvailableFonts())
},
getTitleFonts() {
return this.getVisibleFonts()
},
getHeadingFonts() {
return this.getVisibleFonts()
},
_getBodyFonts: weakmapMemoize(visibleFonts =>
_.select(visibleFonts, f => !f.disableBody),
),
getBodyFonts() {
return this._getBodyFonts(this.getVisibleFonts())
},
getFontByName(name) {
return _.find(
this.getAvailableFonts(),
f => f.name.toLowerCase() === name.toLowerCase(),
)
},
getFont(textType, options) {
return _.find(
this.getAvailableFonts(),
item => item.name === this.getFontName(textType, options),
)
},
getSelectedFontPresetName() {
return _pDataHelper.getData('fontPreset')
},
// Look into preview fonts and actual font to determine which font to show
// 1. if preview has value 'default', use theme default
// 2. if preview has other values, use that
// 3. otherwise, use font in page data
getFontStyle(textType) {
const previewFontName = this.getFontName(textType, { preview: true })
if (previewFontName === 'default') {
// fallback to theme defaults
return {}
} else {
const font =
this.getFont(textType, { preview: true }) ||
this.getFont(textType, { preview: false })
if (font != null) {
return {
fontFamily: font.cssValue,
}
} else {
return {}
}
}
},
getFontClassNames() {
// Class names on #s-content, used for both blog post and site in order to set font
const fontClassNames = ['heading', 'title', 'body'].map(type => {
const fontName =
this.getFontName(type, { preview: true }) ||
this.getFontName(type, { preview: false })
const slug = fontName ? fontName.toSlug() : undefined
if (slug) {
return `s-font-${type}-${slug}`
} else {
return `s-font-${type}-default`
}
})
return fontClassNames.join(' ')
},
})
EditorDispatcher.register(payload => {
switch (payload.actionType) {
case EditorConstants.ActionTypes.SELECT_FONT:
_updateFont(payload.fontType, payload.value)
break
case EditorConstants.ActionTypes.SELECT_FONT_PRESET:
_updateFontPreset(payload.preset)
break
case EditorConstants.ActionTypes.PREVIEW_FONT:
_updatePreviewFont(payload.fontType, payload.value)
break
case EditorConstants.ActionTypes.PREVIEW_FONT_PRESET:
_updatePreviewFontPreset(payload.preset)
break
case EditorConstants.ActionTypes.CLEAR_PREVIEW_FONT:
_clearPreviewFont()
break
default:
break
}
})
// No longer update fonts from style!
// when EditorConstants.ActionTypes.APPLY_STYLES # when hover or select a style
// _updateFontsFromStyle(payload.styles)
if (!__SERVER_RENDERING__) {
if (!window.DEBUG) {
window.DEBUG = {}
}
window.DEBUG.FontStore = FontStore
}
export default FontStore
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "fresh",
"sectionSelections": {
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./fresh/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./fresh/section_selections/title/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./fresh/section_selections/columns/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
const fs = require('fs')
const oldFontsJson = fs.readFileSync(`${__dirname}/old.json`).toString()
let result = `/*
# This file is auto-generated! To update run:
# node fe/scripts/fonts/generateFontsJson.js
#
# We're keeping the generator around rather than just replacing fonts.json,
# because we might need to use more data from the Google webfonts json
# in the future. We can get rid of the fe/scripts/fonts stuff if we decide
# that fonts.json is stable.
#
# If we ever want to save file size on this we could remove cssFallback
# and always use category to determine what fallback to use.
#
# name: always lowercase. universal font identifier.
# displayName: what the user sees. must capitalize.
# cssValue: what gets set into font-family property. specify fallbacks!
# settings: weights for google, etc
# hidden: some old/bad fonts are deprecated - which means they are loaded but no longer selectable
# isSuggested: whether the font shows up in the initial list or only in search
# ===> be careful with adding new suggested fonts! v3 editor needs to
# load the webfont file
*/
`
currentFonts = eval(oldFontsJson)
currentFonts.push({
name: 'helvetica',
displayName: 'Helvetica',
cssValue: 'helvetica, arial',
cssFallback: 'sans-serif',
disableBody: false,
fontType: 'system',
settings: null,
hidden: false,
isSuggested: false,
},
// These fonts were removed from the list provided by the google fonts API,
// but we still need them in the list because people are already using them
{
"name": "amatica sc",
"displayName": "Amatica SC",
"cssValue": "\"amatica sc\"",
"cssFallback": "sans-serif",
"disableBody": true,
"fontType": "google",
"settings": {
"google_embed_name": "Amatica SC"
},
"hidden": false,
"isSuggested": false
},
{
"name": "droid sans",
"displayName": "Droid Sans",
"cssValue": "\"droid sans\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "droid sans mono",
"displayName": "Droid Sans Mono",
"cssValue": "\"droid sans mono\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "droid serif",
"displayName": "Droid Serif",
"cssValue": "\"droid serif\"",
"cssFallback": "serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
},
{
"name": "ek mukta",
"displayName": "Ek Mukta",
"cssValue": "\"ek mukta\"",
"cssFallback": "sans-serif",
"disableBody": false,
"fontType": "google",
"settings": null,
"hidden": false,
"isSuggested": false
})
// Usually blacklisted because they have many similar variations
const blacklistedFonts = [
'Slabo 13px',
'Slabo 27px',
'Baloo Bhai',
'Baloo Bhaina',
'Baloo Chettan',
'Baloo Da',
'Baloo Paaji',
'Baloo Tamma',
'Baloo Thambi',
'Hind Guntur',
'Hind Madurai',
'Hind Siliguri',
'Hind Vadodara',
].map(n => n.toLowerCase())
const newSuggestedFonts = [
'Unna',
'Bad Script',
'IBM Plex Serif',
'IBM Plex Sans',
'Roboto Slab',
'PT Serif',
'PT Sans',
'Libre Baskerville',
'Vidaloka',
'Share Tech',
'Changa One',
'Zilla Slab Highlight',
'Limelight',
'Bungee'
]
const fontsToDisableInBody = [
'Vidaloka',
'Share Tech',
'Changa One',
'Zilla Slab Highlight',
'Limelight',
'Bungee'
]
const fontsToReenableAsBody = ['lato']
fontsToRemoveFromSuggested = ['dosis', 'arapey', 'quando', 'trocchi']
fontsToUnhide = ['arapey', 'quando', 'trocchi']
googleFonts = JSON.parse(fs.readFileSync(`${__dirname}/googleFonts.json`)).items
/* eslint-disable max-statements */
googleFonts.forEach((font, i) => {
const name = font.family.toLowerCase()
const existingFontItem = currentFonts.filter(f => f.name === name)[0]
if (existingFontItem) {
if (fontsToRemoveFromSuggested.indexOf(existingFontItem.name) !== -1) {
existingFontItem.isSuggested = false
}
if (fontsToUnhide.indexOf(existingFontItem.name) !== -1) {
existingFontItem.hidden = false
}
if (fontsToReenableAsBody.indexOf(existingFontItem.name) !== -1) {
existingFontItem.disableBody = false
}
// Keep value from existing manually curated list of fonts
return
}
if (blacklistedFonts.indexOf(font.family.toLowerCase()) !== -1) {
console.log('font blacklisted:', font.family)
return
}
if (font.subsets.indexOf('latin') === -1) {
// console.log("Excluding font b/c no latin", font.family)
return
}
const fallback = {
'sans-serif': 'sans-serif',
serif: 'serif',
handwriting: 'cursive',
display: 'sans-serif',
monospace: 'sans-serif',
}[font.category]
if (!fallback) {
console.log('No fallback for font: ', font)
}
const newFont = {
name,
displayName: font.family,
cssValue: `"${name}"`,
cssFallback: fallback,
disableBody: font.category === 'display' || fontsToDisableInBody.includes(font.family),
fontType: 'google',
settings: null,
hidden: false,
isSuggested: newSuggestedFonts.indexOf(font.family) !== -1,
// "category": font.category
}
if (font.variants.indexOf('regular') === -1) {
// some fonts don't have a regular font, and won't load
// if you don't specify a specific weight
if (!newFont.settings) {
newFont.settings = {}
}
newFont.settings.weight = font.variants.join(',')
console.log(font.family)
}
let canAutogenerateEmbedName = true
// We try to auto-generate the embed by capitalizing the first
// character of each word, but that doesn't work for fonts like
// "PT Something", so store the embed name explicitly
if (/[A-Z]{2,10}/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "Waiting for the Sunrise"
if (/ [a-z]/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "BenchNine"
if (/[A-Z][a-z]+[A-Z]/.test(font.family)) {
canAutogenerateEmbedName = false
}
// e.g. "permanent marker"
if (/(^[^A-Z])|( [a-z])/.test(font.family)) {
canAutogenerateEmbedName = false
}
if (!canAutogenerateEmbedName) {
if (!newFont.settings) {
newFont.settings = {}
}
newFont.settings.google_embed_name = font.family
}
currentFonts.push(newFont)
})
/* eslint-enable max-statements */
result += JSON.stringify(currentFonts, null, 4)
fs.writeFileSync('./config/fonts.json', result)
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "glow",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./glow/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./glow/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./glow/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./glow/section_selections/blog/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./glow/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./glow/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./glow/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./glow/section_selections/signup_form/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./glow/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "ion",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./ion/section_selections/navbar/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./ion/section_selections/slider/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./ion/section_selections/banner/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "minimal",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./minimal/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./minimal/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./minimal/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./minimal/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./minimal/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./minimal/section_selections/html/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./minimal/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./minimal/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./minimal/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./minimal/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./minimal/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./minimal/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./minimal/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "onyx_new",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./onyx_new/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./onyx_new/section_selections/ecommerce/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./onyx_new/section_selections/cta/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./onyx_new/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./onyx_new/section_selections/icons/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./onyx_new/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./onyx_new/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
{
"private": true,
"name": "bobcat-monorepo",
"version": "0.0.0",
"description": "Manages the frontend dependencies",
"engines": {
"node": "^6.9"
},
"scripts": {
"test-ci": "jest --config jest-config.json --ci && jest r-jaguar/ --ci",
"test": "jest --config jest-config.json --verbose --watch",
"devServer": "bash pre-check && node ./devServer.js",
"dev:miniprogram": "bash pre-check && CONFIGS=miniprogram node devServer.js",
"fe": "bash pre-check && SOURCE_MAP=0 npm run dll && npm run start-all",
"dll": "webpack --config webpack.dll.js",
"dev": "bash pre-check && node pack-master.js && PACKMASTER=1 MODE=v4-style CONFIGS=app,site,v4-style,miniprogram,iframe-editor,native-web,native-ipad,component node --max-old-space-size=8192 ./devServer.js",
"start-all": "forego start -f Procfile.fe",
"editor-debug": "bash pre-check && node ./fe/editor-mockup-server/server.js & CONFIGS=editor-debug,site MODE=editor-debug ASSET_HOST=https://localhost:8080 node ./devServer.js",
"app": "changelog-reminder && bash pre-check && MODE=v4-style CONFIGS=app,site,v4-style,component node --max-old-space-size=8192 ./devServer.js",
"mobile": "bash pre-check && CONFIGS=iframe-editor,native-web,native-ipad node ./devServer.js",
"prerender": "bash pre-check && CONFIGS=prerender SERVER_RENDERING=1 node --max-old-space-size=8192 ./node_modules/.bin/webpack --config webpack.config.js --watch",
"prerender:server": "(cd ./r-jaguar && yarn install && yarn dev)",
"clean": "rm -rf app/assets/javascripts/v4 tmp/cache 2>/dev/null || true",
"smoke_test": "NODE_ENV=UAT PRODUCT_NAME=strikingly node_modules/.bin/protractor ./spec/frontend/protractor.conf.smoke_test.js",
"prestart": "npm run clean && webpack --config webpack.dll.js && CONFIGS=app,site,prerender node ./devServer.js --watch 2>&1 >log/webpack.log",
"precommit": "bash pre-check && webcube-precommit",
"precommit:custom": "lerna run precommit:custom && bash ./pre-commit",
"update": "npm run clear:deps && npm run clean && yarn install && echo 6.0 > .project-version",
"upgrade": "npm run clear:lock && npm run update",
"clear:lock": "rm yarn.lock package-lock.json 2>/dev/null || true",
"clear:deps": "lerna clean --yes 2>/dev/null || true && rm -rf ./node_modules 2>/dev/null || true && lerna exec --bail=false -- rm yarn.lock package-lock.json 2>/dev/null || true && rm .git/hooks/pre-commit 2>/dev/null || true",
"lint": "webcube-lint",
"lint:error": "webcube-lint-error",
"lint:miniprogram": "tslint --fix fe/nextgen/miniprogram/*/**.ts fe/nextgen/miniprogram/*/**.tsx",
"generate-i18n": "bash pre-check && NODE_ENV=i18n CONFIGS=app webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=site webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=prerender webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=mobile-editor webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=native-web webpack --config webpack.config.js && NODE_ENV=i18n CONFIGS=native-ipad webpack --config webpack.config.js"
},
"lint-staged": {
"*.{js,jsx,es6}": [
"npm run lint:error --",
"git add"
]
},
"config": {
"webcube": {
"monorepo": {
"root": "./"
}
},
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"workspaces": [
"fe-packages/*",
"component-kit/",
"fe-apps/*"
],
"keywords": [],
"author": "Dafeng Guo @dfguo",
"devDependencies": {
"@types/es6-shim": "^0.31.32",
"@types/immutable": "^3.8.7",
"@types/isomorphic-fetch": "^0.0.33",
"@types/jest": "^21.1.2",
"@types/lodash": "^4.14.61",
"@types/normalizr": "^2.0.18",
"@types/react": "^16.0.4",
"@types/react-dom": "^16.0.4",
"@types/react-native": "^0.42.14",
"@types/react-redux": "^4.4.38",
"@types/react-router": "^2.0.49",
"@types/react-test-renderer": "^16.0.1",
"@types/redux-form": "^6.3.6",
"@types/redux-immutable": "^3.0.33",
"@types/redux-logger": "^3.0.5",
"@types/redux-ui": "^0.0.9",
"@types/reselect": "^2.0.27",
"@types/webpack-env": "^1.13.1",
"autoprefixer": "^7.1.4",
"awesome-typescript-loader": "^3.2.1",
"babel-core": "^6.26.0",
"babel-jest": "^21.2.0",
"babel-loader": "^7.1.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dynamic-import-webpack": "^1.0.1",
"babel-plugin-es6-promise": "^1.1.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-react-transform": "^3.0.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-syntax-optional-chaining": "^7.0.0-alpha.13",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-function-bind": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-entries": "^1.0.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-constant-elements": "^6.5.0",
"babel-plugin-transform-react-inline-elements": "^6.6.5",
"babel-plugin-transform-react-remove-prop-types": "^0.2.6",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.24.1",
"babel-runtime": "^5.8.3",
"bundle-loader": "0.5.4",
"cache-loader": "^1.0.3",
"chai": "^3.3.0",
"chai-as-promised": "^5.3.0",
"changelog-reminder": "^0.5.4",
"cjsx-loader": "^3.0.0",
"codeclimate-test-reporter": "^0.3.3",
"coffee-loader": "^0.7.2",
"coffee-react": "^2.4.1",
"coffee-react-transform": "4.0.0",
"coffee-script": "^1.12.4",
"cross-spawn": "5.0.1",
"css-loader": "^0.23.1",
"cssnano": "^4.0.0-rc.2",
"cz-conventional-changelog": "^2.0.0",
"expose-loader": "^0.7.0",
"extract-text-webpack-plugin": "3.0.0",
"eyes.protractor": "0.0.52",
"eyes.sdk": "0.0.44",
"file-loader": "0.8.1",
"glob": "^7.1.2",
"gulp-util": "^3.0.8",
"haml-coffee": "^1.14.1",
"hamlc-loader": "git://github.com/strikingly/hamlc-loader.git#ed5497b0d8e469bf9dfeecc3291e7a2410de989e",
"happypack": "^4.0.0",
"img-loader": "^1.3.1",
"imports-loader": "^0.6.5",
"inquirer": "^3.3.0",
"jed": "1.1.0",
"jest": "^21.2.1",
"js-yaml": "^3.10.0",
"json-loader": "0.5.1",
"json5-loader": "^1.0.1",
"jstransform-loader": "^0.2.0",
"jsx-loader": "^0.12.0",
"jsxgettext": "^0.10.2",
"memory-stats": "^1.0.2",
"mocha": "^2.3.3",
"mochawesome": "^1.3.4",
"po-loader": "0.2.1",
"po2json": "^0.4.1",
"pofile": "^1.0.8",
"postcss-loader": "^2.0.6",
"progress-bar-webpack-plugin": "^1.10.0",
"protractor": "^3.3.0",
"react-addons-perf": "~15.4",
"react-hot-loader": "3.0.0-beta.7",
"react-render-visualizer-decorator": "^0.3.0",
"react-templates": "git://github.com/strikingly/react-templates.git#fe2e253e7a5619c87930f733c745d2ab8a66b52a",
"react-templates-loader": "^0.3.0",
"react-test-renderer": "^16.2.0",
"redux-mock-store": "^1.5.1",
"run-sequence": "^2.2.0",
"sinon": "^1.17.1",
"style-loader": "^0.13.1",
"sw-precache-webpack-plugin": "^0.11.4",
"ts-jest": "^19.0.0",
"ts-loader": "^2.0.2",
"tslint": "^5.8.0",
"tslint-config-airbnb": "^5.2.1",
"url-loader": "^0.5.7",
"webpack": "^3.5.5",
"webpack-build-notifier": "^0.1.21",
"webpack-bundle-analyzer": "^2.1.1",
"webpack-dev-server": "^2.7.1",
"webpack-manifest-plugin": "^1.3.2",
"webpack-parallel-uglify-plugin": "^0.4.2",
"winston": "^2.4.0",
"yargs": "^9.0.1"
},
"dependencies": {
"accounting": "^0.4.1",
"axios": "^0.18.0",
"babel-plugin-emotion": "^7.3.2",
"bugsnag-js": "^4.4.0",
"classnames": "^2.2.0",
"cloudinary": "git://github.com/strikingly/cloudinary_npm.git#99781dc529f42edd6abda39f2fc463781da2d43c",
"component-kit": "0.0.0",
"concurrent-transform": "^1.0.0",
"console-polyfill": "^0.2.1",
"core-decorators": "^0.20.0",
"create-react-class": "^15.6.0",
"deasync": "^0.1.4",
"deepmerge": "^2.0.1",
"emotion": "^7.3.2",
"es5-shim": "^4.1.1",
"es6-shim": "^0.33.13",
"escape-html": "^1.0.3",
"eventproxy": "^0.3.2",
"fast-json-patch": "^0.5.4",
"fastclick": "^1.0.6",
"firebase": "^2.2.7",
"flux": "^2.0.1",
"flux-standard-action": "^1.0.0",
"friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^4.0.2",
"gulp": "^3.9.1",
"gulp-awspublish": "^3.3.0",
"hifetch": "^2.1.3",
"history": "~1.13.1",
"hoist-non-react-statics": "^2.3.1",
"hypernova": "^2.0.0",
"hypernova-function": "git://github.com/strikingly/hypernova-function",
"hypernova-morearty": "git://github.com/strikingly/hypernova-morearty",
"hypernova-react": "^2.0.0",
"ie-version": "^0.1.0",
"immutable": "github:strikingly/immutable-js#6fecef1e909bfe8eba4908c3f5aa4f8e126ce163",
"in-viewport": "^3.4.1",
"invariant": "^2.2.1",
"is_js": "^0.7.3",
"isomorphic-fetch": "^2.2.1",
"isomorphic-style-loader": "^3.0.0",
"jquery-ui": "github:strikingly/jquery-ui#2f07621e04f92f1115d6385851df5b87605f60d0",
"js-cookie": "^2.0.2",
"json-stable-stringify": "^1.0.0",
"jsonschema": "^1.1.1",
"keymirror": "~0.1.0",
"lazysizes": "^3.0.0",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"lodash": "^3.3.1",
"lodash.unionby": "^4.6.0",
"md5": "^2.2.1",
"merge-stream": "^1.0.1",
"moment": "^2.10.2",
"morearty": "github:strikingly/moreartyjs#2a7d0defada0285b5f2f52e880401dbea7d3de02",
"mousetrap": "^1.5.3",
"mutexify": "^1.2.0",
"normalizr": "^2.1.0",
"object-assign": "^2.0.0",
"pako": "^1.0.5",
"parse-domain": "^1.1.0",
"pikaday": "^1.4.0",
"promise-loader": "^1.0.0",
"prop-types": "^15.6.1",
"prop-validation-mixin": "^0.1.0",
"qrcode.react": "^0.8.0",
"r-i18n": "0.0.10",
"ramda": "^0.24.1",
"rc-queue-anim": "^1.2.3",
"rc-tween-one": "^1.4.5",
"react": "^16.2.0",
"react-addons-pure-render-mixin": "^15.1",
"react-addons-shallow-compare": "^15.1",
"react-autocomplete": "^1.7.1",
"react-color": "^2.13.8",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "git://github.com/strikingly/react-dnd-html5-backend.git#a77e5a50ff7c08d3bef40b7de1796837be5e00be",
"react-dnd-touch-backend": "^0.3.3",
"react-dom": "^16.2.0",
"react-dom-factories": "^1.0.2",
"react-draggable": "^3.0.0",
"react-dropdown": "^1.1.0",
"react-emotion": "^7.3.2",
"react-helmet": "^5.2.0",
"react-highlight-words": "^0.8.0",
"react-immutable-proptypes": "^1.7.0",
"react-list": "^0.8.8",
"react-mixin": "^1.7.0",
"react-motion": "^0.4.5",
"react-navigation-controller": "^3.0.1",
"react-portal": "^3.0.0",
"react-redux": "^5.0.6",
"react-router": "^3.0.1",
"react-router-redux": "^4.0.0",
"react-select": "^1.0.0-beta14",
"react-slick": "git://github.com/strikingly/react-slick.git#7978c8f53ab554a590af94cdec859d5d9a9546bc",
"react-sortable-hoc": "git://github.com/dfguo/react-sortable-hoc.git#f99110e52dfd30eb44885139988b6558213fee29",
"react-sound": "github:Arthraim/react-sound#55052af354244b6271e16bd8b1596f5f998fbeda",
"react-tap-event-plugin": "^3.0.2",
"react-tappable": "^1.0.2",
"react-timer-mixin": "^0.13.3",
"react-transition-group": "^2.2.1",
"recompose": "^0.26.0",
"redux": "^3.7.2",
"redux-api-middleware": "^1.0.2",
"redux-cube": "^1.0.0-rc.12",
"redux-form": "^6.5.0",
"redux-immutable": "git://github.com/dfguo/redux-immutable.git#4a5230e1591a8de968ea7accb928bc0627b81b3d",
"redux-logger": "^3.0.6",
"redux-source-immutable": "^0.2.2",
"redux-thunk": "^2.2.0",
"redux-ui": "git://github.com/dfguo/redux-ui.git#79465231c03dfe34ce5ee62a9549e9ad0f44572e",
"reselect": "^3.0.1",
"rxjs": "^5.5.6",
"sha1": "^1.1.1",
"shallow-equal": "^1.0.0",
"sw-toolbox": "^3.6.0",
"typescript": "^2.2.2",
"uglify-js": "2.7.5",
"uuid": "^2.0.1",
"validator": "^9.1.1",
"walk": "^2.3.9",
"waypoints": "^4.0.0",
"webcube": "^1.0.0-alpha.37",
"webfontloader": "^1.6.27"
},
"optionalDependencies": {
"fsevents": "^1.1.1"
}
}
import 'js/loadBugsnag'
import './init'
import $ from 'jquery'
import React from 'react'
import ReactDOM from 'react-dom'
import logger from 'js/utils/logger'
import 'js/utils/extensions/native'
import bootstrapSite from 'js/prerender/bootstrapSite'
import * as i18nHelper from 'js/utils/helpers/i18nHelper'
import * as RHL from 'react-hot-loader'
// not widely used, disable it for now
// require('./registerPWA.es6')
// These two are direct reads so promises can be fired quicker
const themeName = $S.stores.pageMeta.theme.name
const forcedLocale = $S.stores.pageMeta.forced_locale
// Load dynamic theme style files on editor debug environment
if (__MODE__ === "'editor-debug'") {
require('reset')
require(`themes/${themeName}/main_v4`)
}
const p1 = require(`promise-loader?global!locales/${i18nHelper.getTranslationFile(
forcedLocale,
)}`)
const p2 = require(`promise-loader?global!manifests/themes/${themeName}.js`)
const p3 = require(`promise-loader?global!manifests/verticals/${themeName}.js`)
Promise.all([p1(), p2(), p3()])
.then((...args) => {
const [poFile, manifest, verticalData] = Array.from(args[0])
if (!window.timerStart) {
window.timerStart = new Date().getTime()
}
window.timerCheck = function(label) {
const msg = `${label} in ${new Date().getTime() - timerStart}ms`
console.log(msg)
}
const BootstrappedSite = bootstrapSite({
poFile,
manifest,
verticalData,
})
$(() => {
require('./v3_bridge')()
ReactDOM.render(
<BootstrappedSite />,
document.getElementById('s-page-container'),
)
const ConfStore = require('js/stores/conf_store')
if (ConfStore.isStrikinglyLiveChatEnabled()) {
const SupportWidget = require('js/components/support_widget/SupportWidget')
ReactDOM.render(
<RHL.AppContainer>
<SupportWidget />
</RHL.AppContainer>,
document.getElementById('s-support-widget-container'),
)
}
})
})
.catch(e => console.error(e))
// catch errors and call console.error so that they are reported by Bugsnag
let defaultExport = {}
if (__IN_EDITOR__ || __SERVER_RENDERING__) {
class PageAnalyticsEngine {
init() {}
logPageView() {}
}
defaultExport = new PageAnalyticsEngine()
} else {
const $ = require('jquery')
const gaq = require('gaq')
const Keen = require('js/vendor/keen-loader.js')
const _ = require('lodash')
const ConfStore = require('js/stores/conf_store')
const PageMetaStore = require('js/stores/page_meta_store')
const PageDataStore = require('js/stores/page_data_store')
const _b = require('js/utils/lodashb')
const logger = require('js/utils/logger')
const ScriptHelper = require('js/utils/helpers/ScriptHelper')
const VideoHelper = require('js/utils/helpers/video_helper')
const ReferrerParser = require('js/utils/referrer_parser')
const edit_page = require('js/v3_bridge/edit_page_bridge')
const EcommerceGoogleAnalytics = require('js/v3_bridge/EcommerceGoogleAnalytics')
const PageAnalyticsEngine = class PageAnalyticsEngine {
constructor() {
this.logPageView = this.logPageView.bind(this)
this.logSocialClicks = this.logSocialClicks.bind(this)
this.normalizedReferrer = this.normalizedReferrer.bind(this)
this.sendPbsImpression = this.sendPbsImpression.bind(this)
this.sendPbsConversion = this.sendPbsConversion.bind(this)
this.sendDataKeenIO = this.sendDataKeenIO.bind(this)
}
init(data) {
let isReturnVisit, visitorId
if ($.cookie('__strk_visitor_id')) {
visitorId = $.cookie('__strk_visitor_id')
isReturnVisit = true
} else {
// a common way to generate a random id
visitorId = 'visotor-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(
/[xy]/g,
c => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
},
)
isReturnVisit = false
$.cookie('__strk_visitor_id', visitorId, { expires: 365 })
}
this.baseData = data || {
pageId: PageMetaStore.getId(),
userId: PageMetaStore.getUser().get('id'),
permalink: PageMetaStore.getPermalink(),
membership: PageMetaStore.getUser().get('membership'),
createdAt: PageMetaStore.getCreatedAt(),
strikinglyBranding: PageMetaStore.getShowStrikinglyLogo(),
}
this.baseData = _.extend(this.baseData, {
visitorId,
isReturnVisit,
referrer: document.referrer,
})
if (ConfStore.getIsBlog()) {
this._internals = {
user: {
id: PageMetaStore.getUserId(),
membership: PageMetaStore.getMemberShip(),
},
page: {
id: PageMetaStore.getId(),
url() {
return window.location.href
},
googleAnalyticsTracker: PageMetaStore.getGoogleAnalyticsTracker(),
googleAnalyticsType: PageMetaStore.getGoogleAnalyticsType(),
facebookPixelId: PageMetaStore.getFacebookPixelId(),
},
}
} else {
this._internals = {
user: {
id: PageMetaStore.getUser().get('id'),
membership: PageMetaStore.getUser().get('membership'),
},
page: {
id: PageMetaStore.getId(),
url() {
return window.location.href
},
theme: PageMetaStore.getTheme().get('name'),
strk_upvt: PageMetaStore.getStrkUpvt(),
strkGaTracker: PageMetaStore.getStrkGaTracker(),
googleAnalyticsTracker: PageMetaStore.getGoogleAnalyticsTracker(),
googleAnalyticsType: PageMetaStore.getGoogleAnalyticsType(),
facebookPixelId: PageMetaStore.getFacebookPixelId(),
},
}
}
_b.traverseObj(this._internals, h => {
for (const k in h) {
const v = h[k]
if (_.isUndefined(v)) {
console.warn(`${k} is undefned`)
}
}
})
this.strikinglyGoogleAnalyticsEnabled = ConfStore.isGoogleAnalyticsEnabled()
this.strikinglyKeenAnalyticsEnabled = ConfStore.isKeenAnalyticsEnabled()
this.setupGA()
return this.setupFB()
}
setSocialShareTracking() {
// subscribe to facebook like event
edit_page.Event.subscribe('Site.facebook.edge.create', () =>
this.trackSocialMediaShare('facebook', 'like'),
)
// subscribe to linkedin share event
edit_page.Event.subscribe('Site.linkedin.share', () =>
this.trackSocialMediaShare('linkedin', 'share'),
)
// subscribe to twitter tweet event
edit_page.Event.subscribe('Site.twitter.tweet', () =>
this.trackSocialMediaShare('twitter', 'tweet'),
)
// subscribe to G+ +1 event
return edit_page.Event.subscribe('Site.gplus.plusone', () =>
this.trackSocialMediaShare('gplus', 'plusone'),
)
}
userGoogleAnalyticsEnabled() {
return Boolean(
this._internals != null
? this._internals.page.googleAnalyticsTracker
: undefined,
)
}
userUniversalAnalyticsEnabled() {
return this._internals.page.googleAnalyticsType === 'universal'
}
setupGA() {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log(
'[GA] Setup internal GA: ',
this._internals.page.strkGaTracker,
)
if (typeof __ga === 'function') {
__ga('create', this._internals.page.strkGaTracker, {
name: 'strk',
cookieDomain: 'auto',
})
__ga('strk.set', 'anonymizeIp', true)
}
}
if (this.userGoogleAnalyticsEnabled()) {
logger.log(
'[GA] Setup GA for user: ',
this._internals.page.googleAnalyticsTracker,
)
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Initialize Universal Analytics')
window.ga = __ga
if (typeof ga === 'function') {
ga('create', this._internals.page.googleAnalyticsTracker, 'auto')
ga('set', 'anonymizeIp', true)
}
} else {
logger.log('[GA] Initialize Classic Analytics')
gaq.push(['_setAccount', this._internals.page.googleAnalyticsTracker])
gaq.push(['_gat._anonymizeIp'])
}
}
}
setupFB() {
if (this._internals.page.facebookPixelId && !this._fbPixelLoaded) {
if (!window._strk_fbq) {
ScriptHelper.loadFB()
}
window._strk_fbq('init', this._internals.page.facebookPixelId)
this._fbPixelLoaded = true
return this._fbPixelLoaded
}
}
// TODO
// https://www.facebook.com/business/help/952192354843755
trackPageViewOnGA(path) {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log('[GA] Send page view to internal GA')
if (typeof __ga === 'function') {
__ga('strk.send', 'pageview', { 'anonymizeIp': true })
}
}
if (this.userGoogleAnalyticsEnabled()) {
if (path) {
const query = location.search || ''
path += query
}
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Send page view to user GA (Universal)')
if (path) {
if (typeof ga === 'function') {
ga('set', 'page', path)
}
}
if (typeof ga === 'function') {
ga('send', 'pageview', { 'anonymizeIp': true })
}
} else {
logger.log('[GA] Send page view to user GA (Classic)')
const params = ['_trackPageview']
if (path) {
params.push(path)
}
gaq.push(params)
}
}
}
trackPageEventOnGA(
category,
action,
label = null,
value = null,
eventData = {},
) {
if (this.strikinglyGoogleAnalyticsEnabled) {
logger.log(
'[GA] Send page event to internal GA: ',
category,
action,
label,
value,
)
if (typeof __ga === 'function') {
__ga('strk.send', 'event', category, action, label, value, { 'anonymizeIp': true })
}
}
if (this.userGoogleAnalyticsEnabled()) {
if (this.userUniversalAnalyticsEnabled()) {
logger.log('[GA] Send page event to user GA (Universal)')
if (typeof ga === 'function') {
ga('send', 'event', category, action, label, value, { 'anonymizeIp': true })
}
} else {
logger.log('[GA] Send page event to user GA (Classic)')
let i = 1
for (const k in eventData) {
const v = eventData[k]
gaq.push(['_setCustomVar', i, k, v, 3])
++i
}
gaq.push(['_trackEvent', category, action, label, value])
}
}
}
trackPageEventOnFB(name, options = {}) {
if (window._strk_fbq) {
window._strk_fbq('track', name, options)
}
}
// TODO
// https://www.facebook.com/business/help/952192354843755
trackPageEvent() {
const trackButton = (eventName, options) => {
const that = this
return function(event) {
const t = $(this)
const data = {
url: t.attr('href'),
target: t.attr('target'),
text: t.text(),
}
edit_page.Event.publish(eventName, data)
that.trackPageEventOnGA(
'Action',
options.gaEventName,
data.text,
null,
{
url: data.url,
text: data.text,
},
)
// no need to refresh page when link to section or page
const hash =
typeof data.url === 'string' && _.startsWith(data.url, '#')
const pageLink =
typeof data.url === 'string' &&
(t[0].hostname === window.location.hostname ||
_.startsWith(data.url, '/'))
const isVideoThatOpensInOverlay = VideoHelper.needToTriggerHelper(
data.url,
)
if (
data.url &&
data.target !== '_blank' &&
!hash &&
!pageLink &&
!isVideoThatOpensInOverlay
) {
event.preventDefault()
setTimeout(() => (window.location.href = data.url), 500)
}
}
}
$('.s-button .s-common-button').click(
trackButton('Site.button.click', { gaEventName: 'ButtonClick' }),
)
}
trackSocialMediaShare(channel, action, data = null) {
return this.trackUserPageEvent(
ConfStore.getKeenIoPageSocialShareCollection(),
{
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
channel,
action,
data,
},
)
}
trackPageFraming(domain) {
return this.trackUserPageEvent(
ConfStore.getKeenIoPageFramingCollection(),
{
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
category: this._internals.page.category,
},
},
)
}
logPageView(extraEventData) {
this.trackPageViewOnGA()
this.trackPageEventOnFB('PageView')
// fix for single page high bounce rate issue
// http://aroundanalytics.com/trick-to-solve-high-bounce-rate-of-google-analytics-in-a-one-page-site/
setTimeout(() => this.trackPageEventOnGA('Control', 'Bounce Rate'), 30000)
return this.sendDataKeenIO(_.extend({}, this.baseData, extraEventData))
}
logSocialClicks(channel) {
let data
return (data = _.extend(
{ eventName: 'SocialClicks', channel },
this.baseData,
))
}
normalizedReferrer(referrer_url) {
// TODO: rewrite referrer parser
const REFERRER_SOURCE = require('json-loader!../data/referrer_source.json')
const referrerParser = new ReferrerParser(REFERRER_SOURCE, referrer_url)
// Don't use I18n here. We are writing data to keenio, use i18n here will
// fuck up the data.
return (
(referrerParser.referrer != null
? referrerParser.referrer.name
: undefined) ||
referrerParser.url ||
'Direct Traffic'
)
}
sendPbsImpression(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('[PBS] Impression', data)
return Keen.addEvent(ConfStore.getKeenIoPbsImpressionCollection(), data)
}
sendPbsConversion(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('[PBS] Conversion', data)
return Keen.addEvent(ConfStore.getKeenIoPbsConversionCollection(), data)
}
trackUserPageEvent(collection, data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
logger.log('User Page Event Tracking', collection, data)
return Keen.addEvent(collection, data)
}
trackEcommerceBuyerEvent(name, data) {
data = _.extend(
{
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
},
data,
)
return this.trackUserPageEvent(name, data)
}
sendDataKeenIO(data) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
const referrerHost = data.referrer.split('/')[2]
let pageview = _.extend(
{
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
host: document.location.host,
// parse the referrer url to get the host name of referrer
referrer_host: referrerHost,
normalized_referrer: this.normalizedReferrer(data.referrer),
},
data,
)
// For multi-page analytics dashboard
// Never add these extra fields for blog
if (!ConfStore.getIsBlog()) {
pageview = _.extend(
{
is_multipage: PageDataStore.getIsMultiPage(),
page_uid: PageDataStore.getCurrentPageUID(),
},
pageview,
)
}
// Keen.addEvent($S.conf.keenio_collection_sharding, pageview)
// TODO: delete it
pageview.sharding = true
return Keen.addEvent($S.conf.keenio_collection, pageview)
}
trackFileDownload(fileID) {
if (!this.strikinglyKeenAnalyticsEnabled) {
return
}
const trackData = {
keen: {
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'ip_address',
},
output: 'ip_geo_info',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'user_agent',
},
output: 'parsed_user_agent',
},
],
},
ip_address: '${keen.ip}',
user_agent: '${keen.user_agent}',
file_id: fileID,
user: {
id: this._internals.user.id,
membership: this._internals.user.membership,
},
page: {
id: this._internals.page.id,
url: this._internals.page.url(),
category: this._internals.page.category,
theme: this._internals.page.theme,
},
}
logger.log('File Download', trackData)
return Keen.addEvent(
ConfStore.getKeenIoFileDownloadCollection(),
trackData,
)
}
}
PageAnalyticsEngine.prototype.pingInterval = 10000
_.extend(PageAnalyticsEngine.prototype, EcommerceGoogleAnalytics)
defaultExport = new PageAnalyticsEngine()
}
export default defaultExport
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "persona",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./persona/section_selections/navbar/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./persona/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./persona/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./persona/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./persona/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./persona/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./persona/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./persona/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./persona/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./persona/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./persona/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./persona/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./persona/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./persona/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./persona/section_selections/signup_form/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./persona/section_selections/block/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./persona/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "personal",
"sectionSelections": {
"contact": {
"sectionId": "contact_form",
"category": "suggested",
"displayName": "Contact",
"description": "Let viewers drop their name, email, and message.",
"position": 10,
"content": require("json-loader!./personal/section_selections/contact/content.json"),
},
"bio": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Bio",
"description": "List experiences, schools, projects, or anything!",
"position": 2,
"content": require("json-loader!./personal/section_selections/bio/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "suggested",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./personal/section_selections/social_feed/content.json"),
},
"intro": {
"sectionId": "title",
"category": "suggested",
"displayName": "Intro",
"description": "Write a blurb about yourself.",
"position": 1,
"content": require("json-loader!./personal/section_selections/intro/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "suggested",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 7,
"content": require("json-loader!./personal/section_selections/gallery/content.json"),
},
"projects": {
"sectionId": "media",
"category": "suggested",
"displayName": "Projects",
"description": "Show a big video or image. Or add many of them.",
"position": 5,
"content": require("json-loader!./personal/section_selections/projects/content.json"),
},
"resume": {
"sectionId": "cta",
"category": "suggested",
"displayName": "Resume",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./personal/section_selections/resume/content.json"),
},
"education": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Education",
"description": "List experiences, schools, projects, or anything!",
"position": 4,
"content": require("json-loader!./personal/section_selections/education/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "suggested",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"recommendations": {
"sectionId": "columns",
"category": "suggested",
"displayName": "Recommendations",
"description": "List your projects, clients, features, team, or anything!",
"position": 8,
"content": require("json-loader!./personal/section_selections/recommendations/content.json"),
},
"experiences": {
"sectionId": "rows",
"category": "suggested",
"displayName": "Experience",
"description": "List experience, schools, projects, or anything!",
"position": 3,
"content": require("json-loader!./personal/section_selections/experiences/content.json"),
},
"skills": {
"sectionId": "info",
"category": "suggested",
"displayName": "Skills",
"description": "Show skills, stats, or tidbits.",
"position": 6,
"content": require("json-loader!./personal/section_selections/skills/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./personal/section_selections/hero/content.json"),
},
"connect": {
"sectionId": "icons",
"category": "suggested",
"displayName": "Connect",
"description": "A list of small icons. Good for social media.",
"position": 9,
"content": require("json-loader!./personal/section_selections/connect/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "perspective",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./perspective/section_selections/navbar/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./perspective/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./perspective/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./perspective/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./perspective/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./perspective/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./perspective/section_selections/gallery/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./perspective/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./perspective/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./perspective/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./perspective/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./perspective/section_selections/icons/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./perspective/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./perspective/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./perspective/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./perspective/section_selections/signup_form/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./perspective/section_selections/media/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "pitch_new",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./pitch_new/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./pitch_new/section_selections/ecommerce/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./pitch_new/section_selections/info/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "profile",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./profile/section_selections/navbar/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./profile/section_selections/title/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "s5-theme",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./s5-theme/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./s5-theme/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./s5-theme/section_selections/html/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./s5-theme/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./s5-theme/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./s5-theme/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./s5-theme/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "sleek",
"sectionSelections": {
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./sleek/section_selections/navbar/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./sleek/section_selections/ecommerce/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "spectre",
"sectionSelections": {
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./spectre/section_selections/title/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./spectre/section_selections/cta/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./spectre/section_selections/html/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./spectre/section_selections/info/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./spectre/section_selections/text/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./spectre/section_selections/hero/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./spectre/section_selections/signup_form/content.json"),
},
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* This file is auto generated
* You can modify it to test some changes but they won't be persisted
*
* You shoud modify directly files in your verticals source directory
* and re-generate this one afterwards.
*
* Docs:
*
* Source: https://github.com/strikingly/theme-utils
* Binary strk_tmb:
* RDoc:
*
*/
module.exports = {
"internal": "zine",
"sectionSelections": {
"donation": {
"sectionId": "donation",
"category": "action",
"displayName": "Donation",
"description": "Receive donations right on your site!",
"position": 2,
"content": require("json-loader!./base/section_selections/donation/content.json"),
},
"navbar": {
"sectionId": "navbar",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/navbar/content.json"),
},
"footer": {
"sectionId": "footer",
"category": null,
"displayName": null,
"description": null,
"position": null,
"content": require("json-loader!./base/section_selections/footer/content.json"),
},
"social_feed": {
"sectionId": "social_feed",
"category": "action",
"displayName": "Social Feed",
"description": "Display your latest social media posts beautifully.",
"position": 4,
"content": require("json-loader!./base/section_selections/social_feed/content.json"),
},
"ecommerce": {
"sectionId": "ecommerce",
"category": "action",
"displayName": "Simple Store",
"description": "Sell products right on your site! Manage orders, payments, and more.",
"position": 1,
"content": require("json-loader!./base/section_selections/ecommerce/content.json"),
},
"title": {
"sectionId": "title",
"category": "content",
"displayName": "Title",
"description": "A big background with a title and tagline.",
"position": 5,
"content": require("json-loader!./base/section_selections/title/content.json"),
},
"contact_form": {
"sectionId": "contact_form",
"category": "content",
"displayName": "Contact Form",
"description": "Let viewers drop their name, email, and message. Show a map and business info.",
"position": 15,
"content": require("json-loader!./base/section_selections/contact_form/content.json"),
},
"cta": {
"sectionId": "cta",
"category": "content",
"displayName": "Button",
"description": "A big call-to-action. Supports an external link or a file download!",
"position": 6,
"content": require("json-loader!./base/section_selections/cta/content.json"),
},
"gallery": {
"sectionId": "gallery",
"category": "content",
"displayName": "Gallery",
"description": "Image and video thumbnails that open in a full view.",
"position": 11,
"content": require("json-loader!./base/section_selections/gallery/content.json"),
},
"slider": {
"sectionId": "slider",
"category": "content",
"displayName": "Text + Button Slider",
"description": "Swipeable slider with text, button, and image/video. Background image optional.",
"position": 18,
"content": require("json-loader!./base/section_selections/slider/content.json"),
},
"html": {
"sectionId": "html",
"category": "action",
"displayName": "App Store & HTML",
"description": "Embed a map, a calendar, a document, a form or any HTML code!",
"position": 3,
"content": require("json-loader!./base/section_selections/html/content.json"),
},
"blog": {
"sectionId": "blog",
"category": "action",
"displayName": "Simple Blog",
"description": "Write beautiful blog posts that open in a new page.",
"position": 2,
"content": require("json-loader!./base/section_selections/blog/content.json"),
},
"rows": {
"sectionId": "rows",
"category": "content",
"displayName": "Content in Rows",
"description": "List your features, projects, team members, or anything!",
"position": 8,
"content": require("json-loader!./base/section_selections/rows/content.json"),
},
"info": {
"sectionId": "info",
"category": "content",
"displayName": "Info Boxes",
"description": "Show boxes of steps, stats, or tidbits.",
"position": 13,
"content": require("json-loader!./base/section_selections/info/content.json"),
},
"icons": {
"sectionId": "icons",
"category": "content",
"displayName": "Contact",
"description": "A list of small icons. Good for social media.",
"position": 12,
"content": require("json-loader!./base/section_selections/icons/content.json"),
},
"portfolio": {
"sectionId": "portfolio",
"category": "action",
"displayName": "Product Showcase",
"description": "An advanced product portfolio. Each product opens in its own page, were you can add a price, gallery, and detailed description.",
"position": 1,
"content": require("json-loader!./base/section_selections/portfolio/content.json"),
},
"text": {
"sectionId": "text",
"category": "content",
"displayName": "Plain Text",
"description": "Just paragraphs of text with titles.",
"position": 14,
"content": require("json-loader!./base/section_selections/text/content.json"),
},
"columns": {
"sectionId": "columns",
"category": "content",
"displayName": "Content in Columns",
"description": "List your projects, clients, features, team, or anything!",
"position": 9,
"content": require("json-loader!./base/section_selections/columns/content.json"),
},
"hero": {
"sectionId": "hero",
"category": "content",
"displayName": "Hero",
"description": "Great for the top of a page. Add images, a button, or even a sign-up form.",
"position": 7,
"content": require("json-loader!./base/section_selections/hero/content.json"),
},
"banner": {
"sectionId": "slider",
"category": "content",
"displayName": "Banner Image Slider",
"description": "Swipeable and linkable image slider. Great for promos and heavy graphical content.",
"position": 17,
"content": require("json-loader!./base/section_selections/banner/content.json"),
},
"signup_form": {
"sectionId": "signup_form",
"category": "content",
"displayName": "Sign-Up Form",
"description": "Let visitors sign up for a newsletter or a service.",
"position": 16,
"content": require("json-loader!./base/section_selections/signup_form/content.json"),
},
"process": {
"sectionId": "process",
"category": "content",
"displayName": "Process",
"description": "A numbered list of steps. Explain how your service works!",
"position": 13,
"content": require("json-loader!./base/section_selections/process/content.json"),
},
"block": {
"sectionId": "block",
"category": "action",
"displayName": "Make Your Own Section",
"description": "Want more control over layouts? Arrange components yourself!",
"position": 100,
"content": require("json-loader!./base/section_selections/block/content.json"),
},
"grid": {
"sectionId": "grid",
"category": "content",
"displayName": "Grid",
"description": "Linkable panels of images & text. Customize layout, size, and spacing. A very visual way to display categories, testimonials, or features!",
"position": 101,
"content": require("json-loader!./base/section_selections/grid/content.json"),
},
"media": {
"sectionId": "media",
"category": "content",
"displayName": "Big Media",
"description": "Show a big video or image. Or add many of them.",
"position": 10,
"content": require("json-loader!./base/section_selections/media/content.json"),
} }
};
const _ = require('lodash') const LEGACY_CODES = require('../codes/manifest.json')
const TEMPLATES = ['.babelrc', 'webpack.common.js', 'webpack.config.js'] const TEMPLATES = ['.babelrc', 'webpack.common.js', 'webpack.config.js']
...@@ -27,102 +27,8 @@ const PACKAGES = [ ...@@ -27,102 +27,8 @@ const PACKAGES = [
{ name: 'webpack-manifest-plugin', version: '^2.0.3' } { name: 'webpack-manifest-plugin', version: '^2.0.3' }
] ]
/**
* some legacy codes us promise-loader and json-loader to dynamic import json file
* now it can be achieved by simply using import()
* fonts.json uses C style muliline comment, which violates JSON spec
* gerateFontsJSON.js is the criminal who generates illegal JSON
*/
const multiLineCommentRegex = /\/\*[\s\S]*?\*\/$/gm
const multiLineCommentStringRegex = /`\/\*[\s\S]*?\*\/\n`$/gm
// use regex instead of plain string for replacing all occurances
const requirePromisLoaderRegex = new RegExp(
_.escapeRegExp('require(`promise-loader?global!'),
'g'
)
const LEGACY_CODES = [
{
name: 'config/fonts.json',
update: content => content.replace(multiLineCommentRegex, '')
},
{
name: 'fe/js/BlogEditor.es6',
update: content =>
content
.replace(requirePromisLoaderRegex, 'import(`')
.replace('Promise.all([p1(), p2()])', 'Promise.all([p1, p2])')
},
{
name: 'fe/js/blog.client.es6',
update: content =>
content
.replace(requirePromisLoaderRegex, 'import(`')
.replace('Promise.all([p1(), p2()])', 'Promise.all([p1, p2])')
},
{
name: 'fe/js/stores/font_store.es6',
update: content =>
content
.replace(
`import loadFonts from 'promise-loader?global!json5-loader!../../../config/fonts.json'`,
''
)
.replace(
'return loadFonts()',
`return import('../../../config/fonts.json')`
)
},
{
name: 'fe/js/utils/helpers/EcommerceHelper.es6',
update: content =>
content.replace('require(`promise-loader?global!json-loader!', 'import(`')
},
{
name: 'fe/js/v3_bridge/page_analytics_engine.es6',
update: content => content.replace('json-loader!', '')
},
{
name: 'fe/lib/webpack/entries-generation-webpack-plugin.js',
update: content =>
content.replace(
`isInitial: chunk.isInitial ? chunk.isInitial() : chunk.initial`,
`isInitial: chunk.isInitial ? chunk.isInitial() : chunk.isOnlyInitial()`
)
},
{
name: 'fe/nextgen/app.es6',
update: content =>
content
.replace('require(`promise-loader?global!', 'import(`')
.replace('Promise.all([p1()])', 'Promise.all([p1])')
},
{
name: 'fe/scripts/fonts/generateFontsJson.js',
update: content => content.replace(multiLineCommentStringRegex, `''`)
}
]
const BACKUP_FILES = [
'.babelrc',
'package.json',
'webpack.common.js',
'webpack.config.js',
'yarn.lock',
'config/fonts.json',
'fe/js/BlogEditor.es6',
'fe/js/blog.client.es6',
'fe/js/stores/font_store.es6',
'fe/js/utils/helpers/EcommerceHelper.es6',
'fe/js/v3_bridge/page_analytics_engine.es6',
'fe/lib/webpack/entries-generation-webpack-plugin.js',
'fe/nextgen/app.es6',
'fe/scripts/fonts/generateFontsJson.js'
]
module.exports = { module.exports = {
TEMPLATES, TEMPLATES,
PACKAGES, PACKAGES,
LEGACY_CODES, LEGACY_CODES
BACKUP_FILES,
} }
{
"plugins": [
"emotion",
"add-module-exports",
"transform-decorators-legacy",
],
"presets": [
[
"env",
{
"targets": {
"browsers": [
"ie>=11",
"Chrome>=31",
"Firefox>=52",
"Edge>=13",
"Safari>=8",
"Android>=4"
]
}
}
],
"react",
"stage-0",
],
"env": {
"production": {
"plugins": [
"transform-react-inline-elements",
"transform-react-constant-elements",
"transform-react-remove-prop-types"
]
},
"development": {
"plugins": [
"react-hot-loader/babel"
]
}
}
}
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const { CheckerPlugin } = require('awesome-typescript-loader')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
let aliasMap = {
locales: path.join(__dirname, 'fe', 'locales'),
lib: path.join(__dirname, 'fe', 'lib'),
js: path.join(__dirname, 'fe', 'js'),
nextgen: path.join(__dirname, 'fe', 'nextgen'),
miniprogram: path.join(__dirname, 'miniprogram', 'src'),
manifests: path.join(__dirname, 'fe', 'manifests'),
v3vendor: path.join(__dirname, 'vendor', 'assets', 'javascripts'),
common: path.join(__dirname, 'common'),
root: path.join(__dirname, 'bower_components'),
}
if (['v4-style', 'editor-debug'].indexOf(process.env.MODE) >= 0) {
const styleSheetsPath = path.join(__dirname, 'fe', 'styles')
aliasMap = Object.assign(
{
fonts: path.join(__dirname, 'fe', 'fonts'),
images: path.join(__dirname, 'fe', 'images'),
fancybox3: path.join(styleSheetsPath, 'fancybox3'),
strikingly_shared: path.join(styleSheetsPath, 'strikingly_shared'),
support_widget: path.join(styleSheetsPath, 'support_widget'),
themes: path.join(styleSheetsPath, 'themes'),
typefaces: path.join(styleSheetsPath, 'typefaces'),
v4: path.join(styleSheetsPath, 'v4'),
ecommerce: path.join(styleSheetsPath, 'ecommerce'),
portfolio: path.join(styleSheetsPath, 'portfolio'),
miniprogram: path.join(styleSheetsPath, 'miniprogram'),
},
{
// Is not typical usage of webpack alias. However, it needs this currently,
// because alots of stylesheets that coming from different layers of folder have to import them
// 'css-domain-purchase': path.join(styleSheetsPath, 'domain-purchase.less'),
'css-layers': path.join(styleSheetsPath, 'layers.less'),
reset: path.join(styleSheetsPath, '_reset.less'),
// fancybox: path.join(__dirname, 'vendor', 'assets', 'stylesheets', 'fancybox.less'),
'css-tooltip': path.join(
__dirname,
'vendor',
'assets',
'stylesheets',
'tooltip.less'
),
'css-pikaday': path.join(
__dirname,
'vendor',
'assets',
'stylesheets',
'pikaday.css'
),
},
aliasMap
)
}
let shim = '\n'
shim += 'if (!this._babelPolyfill) {\n'
shim += fs.readFileSync(
path.join(__dirname, 'node_modules', 'babel-polyfill', 'browser.js')
)
shim += '}\n'
const plugins = [
new webpack.PrefetchPlugin('react'),
// new webpack.ResolverPlugin(
// new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
// ),
// new webpack.optimize.DedupePlugin(),
new webpack.ProvidePlugin({
__: 'js/views_helpers/i18n/__',
}),
new webpack.ContextReplacementPlugin(
/moment[\\/]locale$/,
/^\.\/(en|es|fr|ja|zh-cn|zh-tw)$/
),
new CheckerPlugin(),
new ProgressBarPlugin(),
new FriendlyErrorsWebpackPlugin({
clearConsole: false,
}),
]
if (!process.env.SERVER_RENDERING && process.env.MODE !== 'v4-style') {
// plugins.push(new webpack.BannerPlugin(shim, { raw: true, entryOnly: true }))
plugins.push(
new webpack.BannerPlugin({ banner: shim, raw: true, entryOnly: true })
)
}
const webcubeBabelrc = JSON.parse(
fs.readFileSync(path.join(__dirname, './node_modules/webcube/.babelrc'))
)
webcubeBabelrc.plugins.splice(
webcubeBabelrc.plugins.indexOf('add-module-exports'),
1
)
const cacheLoader = {
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve(__dirname, './tmp/cache/cache-loader'),
},
}
const config = {
context: __dirname,
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015', 'env'],
},
},
include: [path.join(__dirname, 'miniprogram', 'src')],
},
{
test: /\.tsx?/,
use: [cacheLoader, 'babel-loader?cacheDirectory'],
include: [
path.join(__dirname, 'fe'),
path.join(__dirname, 'common'),
path.join(__dirname, 'component-kit', 'src'),
],
},
{
test: /\.tsx?/,
use: ['awesome-typescript-loader?transpileOnly=true'],
include: [path.join(__dirname, 'fe'), path.join(__dirname, 'common')],
},
{
test: /\.tsx?/,
use: ['ts-loader'],
include: [path.join(__dirname, 'component-kit', 'src')],
},
{
test: /\.css$/,
use: [cacheLoader, 'style-loader', 'css-loader'],
include: [
path.join(__dirname, 'fe'),
path.join(__dirname, 'common'),
path.join(__dirname, 'vendor', 'assets', 'stylesheets'),
path.join(__dirname, 'component-kit', 'src'),
path.join(__dirname, 'node_modules', 'react-select'),
],
},
{
test: /\.po/,
use: [cacheLoader, 'json-loader', 'po-loader?format=jed1.x'],
include: [path.join(__dirname, 'fe', 'locales')],
},
{
test: /\.hrt/,
use: [
cacheLoader,
'react-templates-loader?modules=commonjs&targetVersion=0.14.0',
'hamlc-loader',
],
include: [path.join(__dirname, 'fe')],
},
{
test: /\.cjsx$/,
use: [
cacheLoader,
'babel-loader?cacheDirectory',
'coffee-loader',
'cjsx-loader',
],
include: [path.join(__dirname, 'fe')],
},
{
test: /\.es6$/,
use: [cacheLoader, 'babel-loader?cacheDirectory'],
include: [path.join(__dirname, 'fe'), path.join(__dirname, 'common')],
},
{
test: /\.js$/,
use: [cacheLoader, 'babel-loader?cacheDirectory'],
include: [path.join(__dirname, 'miniprogram', 'src')],
},
{
test: /\.jsx?$/,
use: [
cacheLoader,
{
loader: 'babel-loader',
options: Object.assign(webcubeBabelrc, {
babelrc: false,
cacheDirectory: true,
}),
},
],
include: [
path.join(__dirname, 'fe-packages', 'webcube'),
path.join(__dirname, 'fe-packages', 'redux-cube'),
path.join(__dirname, 'fe-packages', 'redux-cube-with-immutable'),
path.join(__dirname, 'fe-packages', 'redux-cube-with-persist'),
path.join(__dirname, 'fe-packages', 'redux-cube-with-router'),
path.join(__dirname, 'fe-packages', 'redux-cube-with-router-legacy'),
path.join(__dirname, 'fe-packages', 'redux-source-utils'),
path.join(__dirname, 'fe-packages', 'redux-source'),
path.join(__dirname, 'fe-packages', 'redux-source-immutable'),
path.join(__dirname, 'fe-packages', 'redux-source-connect'),
path.join(__dirname, 'fe-packages', 'redux-source-connect-immutable'),
path.join(__dirname, 'fe-packages', 'redux-source-with-notify'),
path.join(__dirname, 'fe-packages', 'redux-source-with-block-ui'),
path.join(__dirname, 'fe-packages', 'hifetch'),
],
},
{
test: /\.coffee$/,
use: [cacheLoader, 'babel-loader?cacheDirectory', 'coffee-loader'],
include: [path.join(__dirname, 'fe')],
},
{
test: require.resolve('react'),
use: 'expose-loader?React',
},
{
test: /cloudinary\.js$/, // disable AMD for cloudinary otherwise it will not be defined as a jquery plugin
use: [cacheLoader, 'imports-loader?define=>false'],
include: [path.join(__dirname, 'node_modules', 'cloudinary')],
},
{
test: /\.less$/,
use: [
cacheLoader,
'isomorphic-style-loader',
'css-loader',
'less-loader',
],
include: [
path.join(__dirname, 'fe/js/components/amp'),
path.join(__dirname, 'component-kit/src'),
],
},
{
test: /\.less$/,
use: [
cacheLoader,
'style-loader',
'css-loader',
'less-loader?strictMath&noIeCompat',
],
include: [path.join(__dirname, 'fe')],
exclude: [
path.join(__dirname, 'fe/js/components/amp'),
path.join(__dirname, 'fe/styles'),
],
},
{
test: /\.less$/,
loaders: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [{ loader: 'happypack/loader?id=less' }],
}),
include: [
path.join(__dirname, 'fe/styles'),
path.join(__dirname, 'vendor', 'assets', 'stylesheets'),
],
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name:
process.env.NODE_ENV === 'production'
? '/images/[name].[hash].[ext]'
: 'images/[name].[ext]', //
},
},
],
include: [path.join(__dirname, 'fe'), path.join(__dirname, 'public')],
},
{
test: /\.(eot|svg|cur|woff2?|ttf)$/,
loader: 'file-loader',
options: {
name:
process.env.NODE_ENV === 'production'
? '/fonts/[name].[hash].[ext]'
: 'fonts/[name].[ext]', //
},
},
],
},
resolve: {
unsafeCache: true, // for speedup http://stackoverflow.com/questions/29267084/how-to-improve-webpack-babel-build-performance-without-using-the-watch-feature
extensions: [
'.hrt',
'.cjsx',
'.coffee',
'.js',
'.jsx',
'.es6',
'.ts',
'.tsx',
'.less',
],
alias: aliasMap,
symlinks: true,
},
externals: {
// require("jquery") is external and available
// on the global var jQuery
BackEndI18n: 'I18n',
routes: 'Routes',
jquery: '$',
$S: '$S',
analytics: 'analytics',
gaq: '_gaq',
Keen: 'Keen',
bugsnag: 'Bugsnag',
pingpp: 'pingpp',
CKEDITOR: 'CKEDITOR',
recurly: 'recurly',
},
plugins,
node: {
fs: 'empty',
},
// addVendor: function(name, path) {
// this.resolve.alias[name] = path
// this.module.noParse.push(new RegExp(name))
// },
stats: {
assets: false,
version: false,
timings: true,
hash: true,
chunks: false,
chunkModules: false,
errorDetails: true,
reasons: false,
colors: true,
},
}
// config.addVendor('lodash', __dirname + '/fe/js/vendor/lodash.custom.js')
module.exports = config
const path = require('path')
const fs = require('fs')
const webpack = require('webpack')
const WebpackBuildNotifierPlugin = require('webpack-build-notifier')
const _ = require('lodash')
const yaml = require('js-yaml')
const DEV = process.env.NODE_ENV !== 'production'
const ConfigBuilder = require(path.resolve(
__dirname,
'fe',
'lib',
'webpack',
'config_builder'
))
const EntriesGenerationWebpackPlugin = require('./fe/lib/webpack/entries-generation-webpack-plugin')
const ParentWindowPlugin = require(path.resolve(
__dirname,
'fe',
'lib',
'webpack',
'ParentWindowPlugin'
))
const ManifestPlugin = require('webpack-manifest-plugin')
const HappyPack = require('happypack')
const SuppressExtractedTextChunksWebpackPlugin = require('./fe/lib/webpack/suppress-entry-chunks-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractLESS = new ExtractTextPlugin({
filename: DEV ? '[name].bundle.css' : '[name].[contenthash:20].bundle.css',
disable: process.env.MODE !== 'v4-style',
})
const { PACKMASTER, MODE } = process.env
const { ENABLE_WEBPACK_NOTIFIER } = process.env
/**
* @typedef MacroFlags
* @type {object}
* @property {boolean} __IN_EDITOR__ - in editor or in site
* @property {boolean} __NATIVE_WEB__ - webiew editor in native app
* @property {boolean} DEBUGUI - turn on the mode to see all rendering path, deprecated
* @property {boolean} __SERVER_RENDERING__ - is at server rendering
* @property {'sxl' | 'strikingly'} __PRODUCT_NAME__ - product name
* @property {boolean} __EXTERNAL_EDITOR__ - when component is being clicked on, instead of editing
* the component in place, send the component states out to an external editor
*
* @type {MacroFlags}
*/
const defaultVars = {
__IN_EDITOR__: false,
__NATIVE_WEB__: false, // new mobile app, test with ?native_web_editor=1
__NATIVE_IPAD__: false, // new mobile app on ipad
__IFRAME_EDITOR__: false, // mobile view in web editor
DEBUGUI: false,
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
VERBOSE: JSON.stringify(process.env.VERBOSE),
},
__SERVER_RENDERING__: false,
__PRODUCT_NAME__: JSON.stringify(process.env.PRODUCT_NAME || 'strikingly'),
__LOCALE__: 'en',
__EXTERNAL_EDITOR__: false,
__MODE__: JSON.stringify(MODE || ''),
}
const assetHost = process.env.ASSET_HOST || ''
const hotReload = process.env.REACT_HOT === '1'
const outputPath = path.join(__dirname, 'public', 'webpack')
const V4StylesheetsOutputPath = path.join(__dirname, 'public')
const hostName =
assetHost ||
(defaultVars.__PRODUCT_NAME__ === '"strikingly"'
? 'https://assets.strikingly.io:1443/assets/v4/'
: 'https://assets.shangxianle.me:1443/assets/v4/')
// function getPoFilePath() {
// return path.resolve(
// __dirname,
// 'fe',
// 'locales',
// defaultVars.__LOCALE__,
// `${JSON.parse(defaultVars.__PRODUCT_NAME__)}.po`
// )
// }
function getOutputPath(name) {
return {
path: outputPath,
filename: DEV
? `[name]-${name}-bundle.js`
: `[name]-${name}-bundle.[hash].js`,
publicPath: DEV ? hostName : `${hostName}/webpack/`,
chunkFilename: DEV
? `[id]-${name}-bundle.js`
: `[id].[hash]-${name}-bundle.js`,
devtoolModuleFilenameTemplate: DEV
? '/[absolute-resource-path]'
: undefined,
}
}
const withLocales = function(locales, configFn) {
const configs = configFn('en')
return configs
}
const DllReferences = {
app: new webpack.DllReferencePlugin({
context: path.resolve(__dirname, 'fe'),
manifest: require('./fe/js/vendor/dll/app-manifest.json'),
}),
site: new webpack.DllReferencePlugin({
context: path.resolve(__dirname, 'fe'),
manifest: require('./fe/js/vendor/dll/site-manifest.json'),
}),
}
// const locales =
// defaultVars.__PRODUCT_NAME__ === '"sxl"'
// ? ['sxl', 'zh_CN']
// : ['en', 'es', 'fr', 'ja', 'zh_CN', 'zh_TW']
const EXPOSE_TO_IFRAME = /(actions\/)|(stores\/)|(dispatcher\/)/
const themesPath = path.join(__dirname, 'fe/styles/themes')
const packMasterData = yaml.safeLoad(
fs.readFileSync(path.join(__dirname, './pack-master.config.yaml'), 'utf8')
)
const masterPackConfigs =
DEV && PACKMASTER
? require(path.join(__dirname, './pack-master.history.json'))
: packMasterData.default
const styleEntries = masterPackConfigs['v4-style'] || {}
const projectNames = masterPackConfigs.projects || ['editor']
const packMasterRange = Object.keys(packMasterData.default)
function addThemeToStyleEntry(includeShowPage, includeEditor) {
// Add the theme entry files to collection in editor project
fs.readdirSync(themesPath).forEach(fileName => {
const filePath = path.join(themesPath, fileName)
const stat = fs.statSync(filePath)
// Search each directory
if (stat && stat.isDirectory()) {
if (includeShowPage) {
styleEntries[`themes/${fileName}/main_v4`] = path.join(
filePath,
'main_v4.less'
) // For show page
}
// TODO: cannot ignore the condition that below files are non-existent
if (includeEditor) {
styleEntries[`themes/${fileName}/main_v4_editor`] = path.join(
filePath,
'main_v4_editor.less'
) // For editor page
}
}
})
}
addThemeToStyleEntry(
!DEV || projectNames.includes('show-page'),
!DEV || projectNames.includes('editor')
)
const webpackConfig = withLocales(/* locales */ ['en'], locale => {
defaultVars.__LOCALE__ = locale
return [
{
name: 'v4-style',
entry: styleEntries,
output: {
// In production environment, output stylesheet bundle files to public folder.
// In development environment, outputPath is as same as 'app' config due to
// devServer just receives the identical config path in multiple configs mode.
path: !DEV ? V4StylesheetsOutputPath : outputPath,
filename: '[name]-app-bundle.js',
chunkFilename: DEV ? '[id]-app-bundle.js' : '[id].[hash]-app-bundle.js',
},
plugins: [
extractLESS,
new SuppressExtractedTextChunksWebpackPlugin(),
new ManifestPlugin({
// `.sprickets-manifest` prefix is to avoid to be removed by bash scripts in production environment
fileName: '.sprockets-manifest-v4-manifest.json',
publicPath: !DEV ? `${assetHost}/` : '/assets/v4/',
writeToFileEmit: true,
}),
new HappyPack({
id: 'less',
threads: 4,
loaders: [
'css-loader?importLoaders=1',
'postcss-loader',
'less-loader',
],
}),
],
},
{
name: 'editor-debug',
entry: {
editor: './fe/js/editor.es6',
},
output: getOutputPath('app', hotReload),
plugins: [
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'app',
entry: masterPackConfigs.app,
output: getOutputPath('app', hotReload),
plugins: [
new ParentWindowPlugin(EXPOSE_TO_IFRAME, {
parent: true,
}),
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__LOCALE__: locale,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'miniprogram',
entry: masterPackConfigs.miniprogram,
output: getOutputPath('miniprogram', hotReload),
plugins: [
new ParentWindowPlugin(EXPOSE_TO_IFRAME, {
parent: true,
}),
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__LOCALE__: locale,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'iframe-editor',
entry: masterPackConfigs['iframe-editor'],
output: getOutputPath('iframe-editor', hotReload),
plugins: [
new ParentWindowPlugin(EXPOSE_TO_IFRAME, {}),
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__NATIVE_WEB__: false,
__IFRAME_EDITOR__: true,
__EXTERNAL_EDITOR__: true,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'native-web',
entry: masterPackConfigs['native-web'],
output: getOutputPath('native-web', hotReload),
plugins: [
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__NATIVE_WEB__: true,
__EXTERNAL_EDITOR__: true,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'native-ipad',
entry: masterPackConfigs['native-ipad'],
output: getOutputPath('native-ipad', hotReload),
plugins: [
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__NATIVE_WEB__: true,
__NATIVE_IPAD__: true,
__EXTERNAL_EDITOR__: true,
})
),
],
strikingly: {
hotReload,
},
},
{
name: 'site',
entry: masterPackConfigs.site,
output: getOutputPath('site', hotReload),
plugins: [
DllReferences.site,
new webpack.DefinePlugin(_.assign({}, defaultVars, {})),
// new I18nPlugin(getPoFilePath())
],
strikingly: {
hotReload,
},
},
{
name: 'prerender',
target: 'node',
entry: {
index: './fe/js/prerender/index.es6',
},
externals: {
// load these things from r-jaugar global
routes: 'Routes',
$S: '$S',
analytics: 'analytics',
gaq: '_gaq',
Keen: 'Keen',
bugsnag: 'Bugsnag',
pingpp: 'pingpp',
BackEndI18n: 'I18n',
lodash: 'lodash',
React: 'react',
jquery: 'jquery',
ReactDOM: 'react-dom',
},
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts', 'v4'),
filename: '[name]-prerender-bundle.js',
publicPath: assetHost ? `${assetHost}/webpack/` : hostName,
chunkFilename: DEV
? '[id]-prerender-bundle.js'
: '[id].[hash]-prerender-bundle.js',
devtoolModuleFilenameTemplate: DEV
? '/[absolute-resource-path]'
: undefined,
libraryTarget: 'commonjs2',
},
plugins: [
new webpack.DefinePlugin(
Object.assign({}, defaultVars, {
__SERVER_RENDERING__: true,
})
),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
},
{
name: 'landing',
entry: '',
},
{
name: 'component',
entry: masterPackConfigs.component,
output: {
filename: DEV
? `[name]-component-bundle.js`
: `[name]-component-bundle.[hash].js`,
path: outputPath,
publicPath: DEV ? hostName : `${hostName}/webpack/`,
libraryTarget: 'umd',
library: ['ReactComponent', '[name]'],
},
plugins: [
new ParentWindowPlugin(EXPOSE_TO_IFRAME, {
parent: true,
}),
DllReferences.app,
new webpack.DefinePlugin(
_.assign({}, defaultVars, {
__IN_EDITOR__: true,
__LOCALE__: locale,
})
),
],
strikingly: {
hotReload,
},
},
]
})
const chosenConfigs = process.env.CONFIGS || 'app,site,prerender'
// When current config reaches one of below conditions, keep it.
// 1. masterPackConfigs object includes it
// 2. environment is production
// 3. packMasterRange array doesn't include it. That means, this config is not in pack-master business range.
const h = _.reduce(
chosenConfigs.split(','),
(p, confName) => {
const conf = _.select(
webpackConfig,
c =>
// disable locale
// return c.name.indexOf("-" + confName) !== -1
c.name.indexOf(confName) !== -1 &&
(masterPackConfigs[c.name] ||
!DEV ||
packMasterRange.indexOf(c.name) === -1)
)
return p.concat(conf)
},
[]
)
const configs = new ConfigBuilder(h).getConfig()
configs.forEach(config => {
// Apply the entriesGenerationWebpackPlugin for all javascript configs
if (config.name !== 'v4-style') {
config.plugins = config.plugins.concat(
new EntriesGenerationWebpackPlugin({
fileName: '.sprockets-manifest-webpack-js-manifest.json',
setEntryName: prevName => `${prevName}-${config.name}-bundle`,
publicPath: !DEV ? `${assetHost}/webpack/` : '/assets/v4/',
})
)
}
// enable a build notification in non-prod env
if (DEV && ENABLE_WEBPACK_NOTIFIER) {
config.plugins = config.plugins.concat(
new WebpackBuildNotifierPlugin({
title: `Bobcat: ${config.name}`,
})
)
}
})
// hack to change externals in prerender to alias without fiddling too much into config_builder
// const conf = _.find(configs, c => c.name.indexOf('lambda') !== -1)
// if (conf) {
// _.map(conf.externals, function(v, k) {
// conf.resolve.alias[k] = __dirname + '/fe/js/prerender/externals/empty.es6'
// })
// conf.externals = []
// }
module.exports = configs
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment