From 7f67ae8eb0a49cb800a39f8398f123358ca6ca1b Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 8 Sep 2021 11:26:31 +0200 Subject: [PATCH] Merge pull request #4956 from overleaf/jpa-jk-contact-form-de-ng [web] de-ng contact form GitOrigin-RevId: 8a92b37163555d6466e4b8c565f1ef490f73d49a --- services/web/app/views/layout-marketing.pug | 2 +- .../js/features/algolia-search/search-wiki.js | 19 ++++ .../js/features/contact-form/index.js | 12 +++ .../js/features/contact-form/search.js | 86 +++++++++++++++++++ .../js/features/form-helpers/hydrate-form.js | 20 ++++- services/web/frontend/js/marketing.js | 1 + 6 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 services/web/frontend/js/features/algolia-search/search-wiki.js create mode 100644 services/web/frontend/js/features/contact-form/index.js create mode 100644 services/web/frontend/js/features/contact-form/search.js diff --git a/services/web/app/views/layout-marketing.pug b/services/web/app/views/layout-marketing.pug index 34300b625d..3c31cdabbf 100644 --- a/services/web/app/views/layout-marketing.pug +++ b/services/web/app/views/layout-marketing.pug @@ -121,7 +121,7 @@ html( if (typeof(suppressFooter) == "undefined") include layout/footer-marketing - != moduleIncludes("contactModal", locals) + != moduleIncludes("contactModal-marketing", locals) block foot-scripts each file in entrypointScripts(entrypoint) diff --git a/services/web/frontend/js/features/algolia-search/search-wiki.js b/services/web/frontend/js/features/algolia-search/search-wiki.js new file mode 100644 index 0000000000..9b8dac1686 --- /dev/null +++ b/services/web/frontend/js/features/algolia-search/search-wiki.js @@ -0,0 +1,19 @@ +import _ from 'lodash' +import AlgoliaSearch from 'algoliasearch' +import getMeta from '../../utils/meta' + +let wikiIdx +export async function searchWiki(...args) { + if (!wikiIdx) { + const algoliaConfig = getMeta('ol-algolia') + const wikiIndex = _.get(algoliaConfig, 'indexes.wiki') + if (wikiIndex) { + const client = AlgoliaSearch(algoliaConfig.appId, algoliaConfig.apiKey) + wikiIdx = client.initIndex(wikiIndex) + } + } + if (!wikiIdx) { + return { hits: [], nbHits: 0, nbPages: 0 } + } + return wikiIdx.search(...args) +} diff --git a/services/web/frontend/js/features/contact-form/index.js b/services/web/frontend/js/features/contact-form/index.js new file mode 100644 index 0000000000..f6ccae61b6 --- /dev/null +++ b/services/web/frontend/js/features/contact-form/index.js @@ -0,0 +1,12 @@ +import { setupSearch } from './search' + +document + .querySelectorAll('[data-ol-contact-form-with-search]') + .forEach(setupSearch) + +document.querySelectorAll('a[ng-click="contactUsModal()"]').forEach(el => { + el.addEventListener('click', function (e) { + e.preventDefault() + $('[data-ol-contact-form-modal]').modal() + }) +}) diff --git a/services/web/frontend/js/features/contact-form/search.js b/services/web/frontend/js/features/contact-form/search.js new file mode 100644 index 0000000000..64f43e9252 --- /dev/null +++ b/services/web/frontend/js/features/contact-form/search.js @@ -0,0 +1,86 @@ +import _ from 'lodash' +import { searchWiki } from '../algolia-search/search-wiki' +import { sendMB } from '../../infrastructure/event-tracking' + +function formatHit(hit) { + const pageUnderscored = hit.pageName.replace(/\s/g, '_') + const pageSlug = encodeURIComponent(pageUnderscored) + const pagePath = hit.kb ? 'how-to' : 'latex' + + let pageAnchor = '' + let pageName = hit._highlightResult.pageName.value + if (hit.sectionName) { + pageAnchor = `#${hit.sectionName.replace(/\s/g, '_')}` + pageName += ' - ' + hit.sectionName + } + + const url = `/learn/${pagePath}/${pageSlug}${pageAnchor}` + return { url, pageName } +} + +export function setupSearch(formEl) { + const inputEl = formEl.querySelector('[name="subject"]') + const resultsEl = formEl.querySelector('[data-ol-search-results]') + const wrapperEl = formEl.querySelector('[data-ol-search-results-wrapper]') + + let lastValue = '' + function hideResults() { + wrapperEl.setAttribute('hidden', '') + } + function showResults() { + wrapperEl.removeAttribute('hidden') + } + + async function handleChange() { + const value = inputEl.value + if (value === lastValue) return + lastValue = value + if (value.length < 3) { + hideResults() + return + } + + try { + const { hits, nbHits } = await searchWiki(value, { + hitsPerPage: 3, + typoTolerance: 'strict', + }) + resultsEl.innerText = '' + + for (const hit of hits) { + const { url, pageName } = formatHit(hit) + const liEl = document.createElement('li') + + const linkEl = document.createElement('a') + linkEl.className = 'contact-suggestion-list-item' + linkEl.href = url + linkEl.target = '_blank' + liEl.append(linkEl) + + const contentEl = document.createElement('span') + contentEl.innerHTML = pageName + linkEl.append(contentEl) + + const iconEl = document.createElement('i') + iconEl.className = 'fa fa-angle-right' + iconEl.setAttribute('aria-hidden', 'true') + linkEl.append(contentEl) + + resultsEl.append(liEl) + } + if (nbHits > 0) { + showResults() + sendMB('contact-form-suggestions-shown') + } else { + hideResults() + } + } catch (e) { + hideResults() + } + } + + inputEl.addEventListener('input', _.debounce(handleChange, 350)) + + // display initial results + handleChange() +} diff --git a/services/web/frontend/js/features/form-helpers/hydrate-form.js b/services/web/frontend/js/features/form-helpers/hydrate-form.js index f0174d26c2..0e44c061df 100644 --- a/services/web/frontend/js/features/form-helpers/hydrate-form.js +++ b/services/web/frontend/js/features/form-helpers/hydrate-form.js @@ -26,6 +26,7 @@ function formSubmitHelper(formEl) { const captchaResponse = await validateCaptcha(formEl) const data = await sendFormRequest(formEl, captchaResponse) + formEl.dispatchEvent(new Event('sent')) // Handle redirects. From poking around, this still appears to be the // "correct" way of handling redirects with fetch @@ -110,16 +111,27 @@ function formInflightHelper(el) { disabledEl.disabled = false toggleDisplay(showWhenInflightEl, showWhenNotInflightEl) }) +} - function toggleDisplay(hideEl, showEl) { - hideEl.setAttribute('hidden', '') - showEl.removeAttribute('hidden') - } +function formSentHelper(el) { + const showWhenPending = el.querySelector('[data-ol-not-sent]') + const showWhenDone = el.querySelector('[data-ol-sent]') + if (!showWhenDone) return + + el.addEventListener('sent', () => { + toggleDisplay(showWhenPending, showWhenDone) + }) +} + +function toggleDisplay(hideEl, showEl) { + hideEl.setAttribute('hidden', '') + showEl.removeAttribute('hidden') } export function hydrateForm(el) { formSubmitHelper(el) formInflightHelper(el) + formSentHelper(el) } document.querySelectorAll(`[data-ol-form]`).forEach(form => hydrateForm(form)) diff --git a/services/web/frontend/js/marketing.js b/services/web/frontend/js/marketing.js index 8ff6884920..d1ab0dc361 100644 --- a/services/web/frontend/js/marketing.js +++ b/services/web/frontend/js/marketing.js @@ -2,5 +2,6 @@ import './utils/webpack-public-path' import 'jquery' import 'bootstrap' import './features/form-helpers/hydrate-form' +import './features/contact-form' $('[data-ol-lang-selector-tooltip]').tooltip({ trigger: 'hover' })