diff --git a/build-system/app-index/amphtml-helpers.js b/build-system/app-index/amphtml-helpers.js new file mode 100644 index 0000000000000..eab96541858cd --- /dev/null +++ b/build-system/app-index/amphtml-helpers.js @@ -0,0 +1,106 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable amphtml-internal/html-template */ + +const html = require('./html'); +const {joinFragments} = require('./html-helpers'); + + +const componentTagNameRegex = /\<(amp-[^\s\>]+)/g; +const templateTagTypeRegex = /\]+type="?([^\s"\>]+)/g; + +// TODO(alanorozco): Expand +const formTypes = ['input', 'select', 'form']; + + +const forEachMatch = (regex, subject, callback) => { + let match = regex.exec(subject); + while (match != null) { + callback(match); + match = regex.exec(subject); + } +}; + + +const ExtensionScript = ({name, version, isTemplate}) => + html``; + + +const AmpState = (id, state) => html` + + + `; + + +const ternaryExpr = (condition, onTrue, onFalse) => + `${condition} ? ${onTrue} : ${onFalse}`; + + +const containsExpr = (haystack, needle, onTrue, onFalse) => + ternaryExpr(`${haystack}.indexOf(${needle}) > -1`, onTrue, onFalse); + + +const addRequiredExtensionsToHead = (docStr, extensionConf = { + 'amp-mustache': {version: '0.2'}, +}) => { + const extensions = {}; + + const addExtension = (name, defaultConf = {}) => + extensions[name] = {name, ...defaultConf, ...(extensionConf[name] || {})}; + + forEachMatch(componentTagNameRegex, docStr, ([unusedFullMatch, tagName]) => { + if (tagName == 'amp-state') { + addExtension('amp-bind'); + return; + } + addExtension(tagName); + }); + + forEachMatch(templateTagTypeRegex, docStr, ([unusedFullMatch, type]) => { + addExtension(type, {isTemplate: true}); + }); + + // TODO(alanorozco): Too greedy. Parse "on" attributes instead. + if (docStr.indexOf('AMP.setState') >= 0) { + addExtension('amp-bind'); + } + + for (let i = 0; i < formTypes.length; i++) { + const tagName = formTypes[i]; + if (docStr.search(new RegExp(`\<${tagName}(\s|\>)`))) { + addExtension('amp-form'); + break; + } + } + + return docStr.replace(/(\<\/head\>)/i, (_, headClosingTag) => + joinFragments(Object.values(extensions), ExtensionScript) + headClosingTag); +}; + + +module.exports = { + AmpState, + addRequiredExtensionsToHead, + containsExpr, + ternaryExpr, +}; diff --git a/build-system/app-index/html-helpers.js b/build-system/app-index/html-helpers.js new file mode 100644 index 0000000000000..bfc62831dfb9f --- /dev/null +++ b/build-system/app-index/html-helpers.js @@ -0,0 +1,31 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const thru = a => a; + + +/** + * Takes a set of HTML fragments and concatenates them. + * @param {!Array} fragments + * @param {function(T):string=} opt_renderer + * @return {string} + * @template T + */ +const joinFragments = (fragments, opt_renderer) => + fragments.map(opt_renderer || thru).join(''); + + +module.exports = {joinFragments}; diff --git a/build-system/app-index/template.js b/build-system/app-index/template.js index 52497d0438e55..d6cf959d99d77 100644 --- a/build-system/app-index/template.js +++ b/build-system/app-index/template.js @@ -23,9 +23,39 @@ const boilerPlate = require('./boilerplate'); const documentModes = require('./document-modes'); const html = require('./html'); const ProxyForm = require('./proxy-form'); +const { + AmpState, + addRequiredExtensionsToHead, + containsExpr, +} = require('./amphtml-helpers'); +const {joinFragments} = require('./html-helpers'); const {KeyValueOptions} = require('./form'); const {SettingsModal, SettingsOpenButton} = require('./settings'); +const fileListEndpointPrefix = '/dashboard/api/listing'; + +const examplesPathRegex = /^\/examples\//; +const htmlDocRegex = /\.html$/; + +const leadingSlashRegex = /^\//; + +const replaceLeadingSlash = (subject, replacement) => + subject.replace(leadingSlashRegex, replacement); + +const fileListEndpoint = query => + fileListEndpointPrefix + '?' + + Object.keys(query).map(k => `${k}=${query[k]}`).join('&'); + +const ampStateKey = (...keys) => keys.join('.'); + +const selectModeStateId = 'documentMode'; +const selectModeStateKey = 'selectModePrefix'; +const selectModeKey = ampStateKey(selectModeStateId, selectModeStateKey); + +const fileListEndpointStateId = 'fileListEndpoint'; +const fileListEndpointStateKey = 'src'; +const fileListEndpointKey = ampStateKey( + fileListEndpointStateId, fileListEndpointStateKey); const headerLinks = [ { @@ -57,24 +87,6 @@ const headerLinks = [ ]; -const requiredExtensions = [ - {name: 'amp-bind'}, - {name: 'amp-form'}, - {name: 'amp-lightbox'}, - {name: 'amp-selector'}, - {name: 'amp-mustache', version: '0.2'}, - {name: 'amp-list'}, -]; - - -const ExtensionScript = ({name, version}) => - html``; - - const HeaderLink = ({name, href, divider}) => html`
  • @@ -90,24 +102,27 @@ const Header = ({isMainPage, links}) => html` ${!isMainPage ? HeaderBackToMainLink() : ''}
      - ${links.map(({name, href, divider}, i) => + ${joinFragments(links, ({name, href, divider}, i) => HeaderLink({ divider: divider || i == links.length - 1, name, href, - })).join('')} + }))}
    • ${SettingsOpenButton()}
    `; -const FileListSearch = ({basepath}) => - html` html` + `; @@ -118,8 +133,8 @@ const ExamplesDocumentModeSelect = () => html` Document mode: