diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..79a16af7ee --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 \ No newline at end of file diff --git a/fec/fec/settings/base.py b/fec/fec/settings/base.py index 31bcd18995..e7b1c13dc9 100644 --- a/fec/fec/settings/base.py +++ b/fec/fec/settings/base.py @@ -69,6 +69,7 @@ 'aggregatetotals': bool(env.get_credential('FEC_FEATURE_AGGR_TOTS', '')), 'barcharts': bool(env.get_credential('FEC_FEATURE_HOME_BARCHARTS', '')), 'contributionsbystate': bool(env.get_credential('FEC_FEATURE_CONTRIBUTIONS_BY_STATE', '')), + 'contact_app': True, # bool(env.get_credential('FEC_FEATURE_CONTACT_APP', '')), 'debts': bool(env.get_credential('FEC_FEATURE_DEBTS', '')), # TODO: debts dates 'map': bool(env.get_credential('FEC_FEATURE_HOME_MAP', '')), 'pac_party': bool(env.get_credential('FEC_FEATURE_PAC_PARTY', '')), @@ -79,6 +80,8 @@ 'house_senate_overview_summary': bool(env.get_credential('FEC_FEATURE_HOUSE_SENATE_OVERVIEW_SUMMARY', '')), 'house_senate_overview_totals': bool(env.get_credential('FEC_FEATURE_HOUSE_SENATE_OVERVIEW_TOTALS', '')), } +# In html templates, FEATURES is in the settings object. ex: settings.FEATURES.feature_flag_var +# Jinja templates use the FEATURES object directly. ex: FEATURES.feature_flag_var # Set feature flags to True for local if FEC_CMS_ENVIRONMENT == ENVIRONMENTS['local']: @@ -87,6 +90,7 @@ FEATURES['aggregatetotals'] = True FEATURES['barcharts'] = True FEATURES['contributionsbystate'] = True + FEATURES['contact_app'] = True FEATURES['debts'] = True FEATURES['map'] = True FEATURES['pac_party'] = True diff --git a/fec/fec/static/js/pages/contact-app.js b/fec/fec/static/js/pages/contact-app.js new file mode 100755 index 0000000000..b1f5943716 --- /dev/null +++ b/fec/fec/static/js/pages/contact-app.js @@ -0,0 +1,1928 @@ +/** + * + * Note: k${'e'}y is because the Git commit security tools don't like when we use words like k-e-y, + * even if we're using it to sort things and (because we're in a Vue template string), + * we can't exempt the whole line + */ + +import Vue from 'vue/dist/vue.esm.js'; + +Vue.config.devtools = true; + +/** + * The Vue component + */ +Vue.component('Recaptcha', { + props: { + TESTSHOULDFAIL: { + type: Boolean, + required: true + }, + recaptchaShow: { + type: Boolean, + required: true + }, + recaptchaApproved: { + type: Boolean, + required: true + } + }, + emits: ['testing-change'], + mounted: function() { + window.addEventListener('load', this.handleWindowLoad); + this.$emit('recaptcha-event', 'windowloaded'); + this.$emit('recaptcha-event', 'mounted'); + }, + data: function() { + return { + theRecaptcha: null + }; + }, + methods: { + handleTestingChange(e) { + this.$emit('testing-change', e.target.value == 'true'); + } + // handleWindowLoad: function(e) { + // console.log(' grecaptcha: ', grecaptcha); + // this.theRecaptcha = grecaptcha.render('gov-fec-contact-recaptcha', { + // sitek_e_y: '', // (Google's universal generic loc + // callback: this.handleRecapSuccess, + // 'expired-callback': this.handleRecapExpired, + // 'error-callback': this.handleRecapError + // }); + // console.log(' this.theRecaptcha: ', this.theRecaptcha); + // }, + // handleRecapSuccess: function(x, y, z) { + // console.log('handleRecapSuccess(x,y,z): ', x, y, z); + // console.log(' getResponse: ', grecaptcha.getResponse(this.theRecaptcha)); + // }, + // handleRecapExpired: function(x, y, z) { + // console.log('handleRecapExpired(x,y,z): ', x, y, z); + // }, + // handleRecapError: function(x, y, z) { + // console.log('handleRecapError(x,y,z): ', x, y, z); + // } + }, + template: ` +
+
+ + +
+ + +
+ +
+
` + + // +}); + +/** + * The Vue component + */ +Vue.component('BottomNav', { + props: { + canNavBack: { + type: Number, + required: true + }, + canNavNext: { + type: Number, + required: true + }, + canNavRestart: { + type: Number, + required: true + }, + canNavSubmit: { + type: Number, + required: true + }, + isSubmitting: { + type: Boolean, + required: true + } + }, + template: ` +
+ + + + +
+
+ `, + methods: { + bottomNavClass: function(buttonID) { + return { + // style classes: + 'button--back': buttonID == 'Back', + 'button--alt': buttonID == 'Back', + 'button--cta': buttonID == 'Next' || buttonID == 'Submit', + 'button--go': buttonID == 'Next' || buttonID == 'Submit', + 'button--standard': buttonID == 'Restart', + // behavior classes: (set by looking at frames' canNavBack, canNavNext, canNavRestart, and canNavSubmit values) + // full appearance and action are when canNavBack, canNavNext, etc === 2 + 'is-disabled': this[`canNav${buttonID}`] === 1, + hidden: this[`canNav${buttonID}`] === 0 + }; + }, + handleClick: function(id, e) { + this.$emit('handle-click', id, e); + } + } +}); + +/** + * The Vue component + */ +Vue.component('FramesHolder', { + props: { + currentFrameNum: { + type: Number, + required: true + }, + frames: { + type: Array, + required: true + }, + recaptchaValidated: { + type: Boolean, + required: true + }, + teams: { + type: Array, + required: true + }, + selectedTeam: { + type: String, + required: true + }, + selectedTopic1: { + type: String, + required: true + }, + selectedTopic2: { + type: String, + required: true + }, + submissionMessages: { + required: true + }, + userCity: { + type: String + }, + userCommittee: { + type: String + }, + userEmail: { + type: String + }, + userPubs: { + type: Array, + default: [] + }, + userMessage: { + type: String + }, + userName: { + type: String + }, + userState: { + type: String + }, + userStreet1: { + type: String + }, + userStreet2: { + type: String + }, + userSubject: { + type: String + }, + userZip: { + type: String + } + }, + watch: { + currentFrameNum: function(newVal, oldVal) { + if (newVal != oldVal) { + this.updateNavOptions(); + } + }, + selectedTopic1: function() { + //console.log('changed selectedTopic1 from ', oldVal, ' to ', newVal); + // TODO: do we need this? + }, + selectedTopic2: function() { + // console.log('changed selectedTopic2 from ', oldVal, ' to ', newVal); + // TODO: do we need this? + }, + submissionMessages: { + handler() { + // console.log('submissionMessages changed: ', val); + // TODO: do we need this? + }, + deep: true + } + }, + updated: function() { + let newHeight = this.framesHeight; + // If we're not on the teamFields page (where publications are selected) + // just return the default heigth and be done + // if (this.frames[this.currentFrameNum].frameId != 'userFields') return newHeight; + // Otherwise, we have enough room for two publications, so count 38 more pixels for every row after that + // else newHeight += (this.userPubs.length - Math.min(0, 2)) * 38; + const currentFrameDiv = document.querySelector('.frame.current'); + newHeight = currentFrameDiv.scrollHeight + 20; + newHeight = Math.max(300, newHeight); + + this.framesHeight = newHeight; + }, + computed: { + teamInfo: function() { + return this.teams[this.selectedTeam]; + }, + userPubs_neverEmpty: function() { + // return userPubs or a default array + const emptyPubsArray = [{ label: '', qty: 1 }]; + return this.userPubs && this.userPubs.length > 0 ? this.userPubs : emptyPubsArray; + } + }, + template: ` +
+
+ + + + + + +
+
+ `, //gitleaks:allow + methods: { + canAddAnotherPublication: function() { + // If there are no userPubs so far, easy, can't add another for every possible publication, nope, can't add another + if (this.userPubs.length == this.teamInfo.fields.subject_pubs.length) return false; + + for (let i = 0; i < this.userPubs.length; i++) { + const pub = this.userPubs[i]; + const hasLabel = pub.label && pub.label != ''; + const hasQty = pub.qty && pub.qty >= 1; + + if (!hasLabel || !hasQty) return false; + } + return true; + }, + canGoToNextFrame: function() { + const currentFrame = this.frames[this.currentFrameNum]; + // 'intro' never shows the Next button + let toReturn = false; + + // if we're on 'teams' but there's already a value for it + if (currentFrame.frameId == 'teams' && this.selectedTeam) toReturn = true; + + // if we're on topics1 but it already has a value + else if (currentFrame.frameId == 'topics1' && this.selectedTopic1) toReturn = true; + + // if we're on topics2 but it already has a value + else if (currentFrame.frameId == 'topics2' && this.selectedTopic2) toReturn = true; + + // if we're on teamFields, we're only showing Next for publications + else if (currentFrame.frameId == 'teamFields' && this.userSubject == 'publications' && this.userPubs.length >= 1) + toReturn = true; + + return toReturn; + }, + /** + * @property {object} d - the data object from frames.options + */ + getRadioLabel: function(d) { + let toReturn = ''; + if (d.label) toReturn = d.label; + else if (d.vModel == 'selectedTeam') { + const thisTeam = this.teams[d.value]; + toReturn = thisTeam.name; + if (thisTeam.nameDisclaimer) toReturn += ` ${thisTeam.nameDisclaimer}`; + } + return toReturn; + }, + frameClass: function(frameIndex, additionalClasses) { + // this.validateCurrentFrame(); // THIS CAUSES AN INFINITE LOOP + + return [ + 'frame', + { + previous: frameIndex < this.currentFrameNum, + current: frameIndex == this.currentFrameNum, + 'next off-screen': frameIndex > this.currentFrameNum + }, + additionalClasses + ]; + }, + handleButtonClick: function(id, e) { + this.$emit('button-click', id, e); + this.updateNavOptions(); + }, + handleRadioClick: function(opt, e) { + this.$emit('radio-click', opt, e); + this.updateNavOptions(); + }, + handleFieldChange: function(e) { + // Handles when selects change and inputs lose focus + this.validateField(e.target); + this.$emit('field-change', e); + this.validateCurrentFrame(); + }, + handleFieldInput: function(e) { + // Triggers when someone types into an input + // If the field was previously invalid, do a validation now + if (e.target.getAttribute('aria-invalid') == 'true') this.validateField(e.target); + }, + radioValue: function(opt) { + // if (opt.teamSubject) return opt.teamSubject; + if (opt.value) return opt.value; + else if (opt.label) return opt.label; + return 'ERROR'; + }, + updateNavOptions: function(obj) { + const currentFrame = this.frames[this.currentFrameNum]; + + let newCanNext = 1; + // Set the default to showing the button, but not active + // If there's no next frame, don't show 'Next' ever + if (!currentFrame.nextFrame) newCanNext = 0; + // If required fields don't have values, stay here + else if (obj && obj.needValues === true) newCanNext = 1; + // Otherwise, if the fields are all validated, cool, let's let them move forward + else if (obj && obj.valid === true) newCanNext = 2; + // Of if they've already been to the next frame, they can go forward to it + else if (this.canGoToNextFrame()) newCanNext = 2; + + let newCanBack = 2; + + let newCanRestart = 0; + + let newCanSubmit = 0; + + // For the frames that can submit + // + // If we're on the Intro + if (currentFrame.frameId == 'intro') { + newCanBack = 0; + newCanNext = 0; + newCanRestart = 0; + newCanSubmit = 0; + // If we're currently showing the team info + } else if (currentFrame.frameId == 'teamFields') { + // If there are no fields to show, we're at a stopping point + if (!this.teams[this.selectedTeam].fields) { + newCanNext = 0; + newCanRestart = 2; + newCanSubmit = 0; + // If there are no other fields to show, we're done + } else if (this.selectedTeam && this.teams[this.selectedTeam].fields && this.teams[this.selectedTeam].fields.length === 0) { + newCanNext = 0; + newCanRestart = 2; + newCanSubmit = 0; + // If they've chosen publications, they can't submit but maybe they can Next + } else if (this.userSubject == 'publications') { + newCanNext = this.canAddAnotherPublication() ? 2 : 1; + newCanSubmit = 0; + // TODO: handle when they can't add another because they've already added them all, + // TODO: but they should be able proceed because there's a quantity for all of them + // If they haven't chosen publications, they can't Next but they might submit + } else { + newCanNext = 0; + // newCanSubmit = obj && obj.valid && !obj.needValues ? 2 : 1; + newCanSubmit = 2; + } + } else if (currentFrame.frameId == 'uspsFields') { + newCanNext = 2; + newCanRestart = 0; + newCanSubmit = 0; + } else if (currentFrame.frameId == 'orderReview') { + // console.log('orderReview! this.recaptchaValidated: ', this.recaptchaValidated); + newCanNext = 0; + newCanRestart = 0; + newCanSubmit = 2; //this.recaptchaValidated ? 2 : 1; + } else if (currentFrame.frameId == 'acknowledgeSubmission') { + newCanBack = 0; + newCanNext = 0; + newCanRestart = 2; + newCanSubmit = 0; + } + + this.$parent.updateNavOptions( + { Back: newCanBack, Next: newCanNext, Restart: newCanRestart, Submit: newCanSubmit } + ); + + }, + validateField: function(el) { + el.setAttribute('aria-invalid', !el.checkValidity()); + }, + validateCurrentFrame: function(forceShowAllErrors) { + const currentFrameEl = document.querySelector('.frame.current'); + let frameFormElements; + + if (!currentFrameEl) { + console.log('NO CURRENT FRAME'); // eslint-disable-line no-console + this.$emit('form-validation', false); + return; + } else { + frameFormElements = currentFrameEl.querySelectorAll('input, select, textarea'); + // console.log(' frameFormElements: ', frameFormElements); // eslint-disable-line no-console + } + + if (frameFormElements) { + // debugger; + let allAreValid = true; + let needValues = false; + + frameFormElements.forEach(el => { + if ((!el.value || el.value == '') && el.required) needValues = true; + + if (el.id.indexOf('u_pub') === 0 && this.userPubs.length < 1) allAreValid = false; + else if (!el.checkValidity()) { + allAreValid = false; + if (forceShowAllErrors) this.validateField(el); + } + }); + + this.$emit('form-validation', allAreValid && !needValues); + this.updateNavOptions({ valid: allAreValid, needValues: needValues }); + } + }, + pubOptionDisabledState: function(requestedLabel) { + + let toReturn = false; + this.userPubs.forEach(pub => { + if (pub.label === requestedLabel) toReturn = true; + }); + + return toReturn; + } + }, + data: function() { + return { + framesHeight: 475, + states: [ + { label: 'Alabama', abbrev: 'AL' }, { label: 'Alaska', abbrev: 'AK' }, + { label: 'American Samoa', abbrev: 'AS' }, { label: 'Arizona', abbrev: 'AZ' }, + { label: 'Arkansas', abbrev: 'AR' }, { label: 'California', abbrev: 'CA' }, + { label: 'Colorado', abbrev: 'CO' }, { label: 'Connecticut', abbrev: 'CT' }, + { label: 'Delaware', abbrev: 'DE' }, { label: 'District of Columbia', abbrev: 'DC' }, + { label: 'Florida', abbrev: 'FL' }, { label: 'Georgia', abbrev: 'GA' }, + { label: 'Guam', abbrev: 'GU' }, { label: 'Hawaii', abbrev: 'HI' }, + { label: 'Idaho', abbrev: 'ID' }, { label: 'Illinois', abbrev: 'IL' }, + { label: 'Indiana', abbrev: 'IN' }, { label: 'Iowa', abbrev: 'IA' }, + { label: 'Kansas', abbrev: 'KS' }, { label: 'Kentucky', abbrev: 'KY' }, + { label: 'Louisiana', abbrev: 'LA' }, { label: 'Maine', abbrev: 'ME' }, + { label: 'Maryland', abbrev: 'MD' }, { label: 'Massachusetts', abbrev: 'MA' }, + { label: 'Michigan', abbrev: 'MI' }, { label: 'Minnesota', abbrev: 'MN' }, + { label: 'Mississippi', abbrev: 'MS' }, { label: 'Missouri', abbrev: 'MO' }, + { label: 'Montana', abbrev: 'MT' }, { label: 'Nebraska', abbrev: 'NE' }, + { label: 'Nevada', abbrev: 'NV' }, { label: 'New Hampshire', abbrev: 'NH' }, + { label: 'New Jersey', abbrev: 'NJ' }, { label: 'New Mexico', abbrev: 'NM' }, + { label: 'New York', abbrev: 'NY' }, { label: 'North Carolina', abbrev: 'NC' }, + { label: 'North Dakota', abbrev: 'ND' }, { label: 'Northern Mariana Islands', abbrev: 'MP' }, + { label: 'Ohio', abbrev: 'OH' }, { label: 'Oklahoma', abbrev: 'OK' }, + { label: 'Oregon', abbrev: 'OR' }, { label: 'Pennsylvania', abbrev: 'PA' }, + { label: 'Puerto Rico', abbrev: 'PR' }, { label: 'Rhode Island', abbrev: 'RI' }, + { label: 'South Carolina', abbrev: 'SC' }, { label: 'South Dakota', abbrev: 'SD' }, + { label: 'Tennessee', abbrev: 'TN' }, { label: 'Texas', abbrev: 'TX' }, + { label: 'U.S. Virgin Islands', abbrev: 'VI' }, { label: 'Utah', abbrev: 'UT' }, + { label: 'Vermont', abbrev: 'VT' }, { label: 'Virginia', abbrev: 'VA' }, + { label: 'Washington', abbrev: 'WA' }, { label: 'West Virginia', abbrev: 'WV' }, + { label: 'Wisconsin', abbrev: 'WI' }, { label: 'Wyoming', abbrev: 'WY' } + ] + }; + } +}); + +/** + * The main data for the contact app + * \xa0 is the JavaScript escape for   + */ +new Vue({ + el: '#gov-fec-contact-app', + template: ` +
+ + + + +
+ `, + data: function() { + return { + TESTSHOULDFAIL: false, + canNavBack: 0, + canNavNext: 0, + canNavSubmit: 0, + canNavRestart: 0, + currentFrameNum: 0, //int + formCanSubmit: false, + isSubmitting: false, + postUrl: '/contact-submission/', + selectedTeam: '', + selectedTopic1: '', + selectedTopic2: '', + submissionMessages: {}, + u_city: '', + u_committee: '', + u_email: '', + u_pubs: [], + u_message: '', + u_name: '', + u_state: '', + u_street1: '', + u_street2: '', + u_subject: '', + u_zip: '', + teams: { + congress: { + name: 'Congressional, Legislative and Governmental Affairs', + summary: `Congressional, Legislative and Governmental Affairs responds to inquiries from Congressional staff + and other agencies. It is also responsible for keeping Members of Congress apprised of Commission decisions, + and provides general advice and information about campaign finance laws to lawmakers and their committees.`, + formPrompt: 'Contact Congressional, Legislative and Governmental Affairs via telephone or email.', + phoneExt: '1006', + ePrefix: 'congress', + fields: '' + }, + efo: { + name: 'Electronic Filing Office', + summary: `The Electronic Filing Office helps individuals, candidates, committees and other + entities with FECFile passwords and other technical FECFile issues. EFO also provides limited + support for third-party filing software.`, + formPrompt: 'Contact the Electronic Filing Office via telephone or send a message.', + phoneMenu: 4, // 1-800-424-9530, menu option # + phoneExt: 1307, // 202-694-#### + success: 'The Electronic Filing Office will get back to you within 2-4 hours.', + successMore: [ + { + label: 'Get help with passwords >', + href: 'https://www.fec.gov' + } + ], + fields: { + email: true, + name: true, + committeeId: true, + subject: [ + { label: 'Get password help', + ePrefix: 'eFiletechsupport' }, + { label: 'Answer a question about setting up FECFile', + ePrefix: 'eFiletechsupport' }, + { label: 'Answer a question about 3rd-party filing software', + ePrefix: 'eFiletechsupport' }, + { label: 'Help troubleshoot a problem with FECFile', + ePrefix: 'eFiletechsupport' } + ], + message: true + } + }, + fec: { + summary: `We're here to help! The Federal Election Commission is an independent regulatory agency + that was created to protect the integrity of the campaign finance process.`, + formPrompt: 'Contact us via telephone or send a message.', + phoneMenu: 6, + phoneExt: 1100, + success: 'The FEC will respond within 5 business days', + fields: { + email: true, + message: true + } + }, + info: { + name: 'Information Division', + summary: `The Information Division answers questions about campaign finance law, conducts training on the law, + processes requests for public speakers and provides copies of FEC forms and publications.`, + formPrompt: 'Contact the Information Division via telephone or send them a message.', + phoneMenu: 6, + phoneExt: 1100, + success: 'The Information Division will get back to you within 5 business days.', + fields: { + email: true, + subject: [ + { label: 'A question about campaign finance law', + ePrefix: 'info' }, + { label: 'A question about an upcoming training', + ePrefix: 'conferences' }, + { label: 'Help me sign up for an upcoming training', + ePrefix: 'conferences' }, + { label: 'Send me a copy of an FEC form or guide', + value: 'publications', + email: 'info' }, + { label: 'Help me schedule a group speaker', + ePrefix: 'speaker' }, + { label: 'Something else', + ePrefix: 'info' } + ], + message: true, + subject_pubs: [ + { label: 'Congressional candidates and their committees campaign guide', qty: 1 }, + { label: 'Political party committees campaign guide', qty: 1 }, + { label: 'Corporations and labor organizations campaign guide', qty: 1 }, + { label: 'Nonconnected committees campaign guide', qty: 1 }, + { label: 'Combined Federal/State Disclosure and Election Directory', qty: 1 }, + { label: 'Form 1: Statement of Organization', qty: 1 }, + { label: 'Form 2: Statement of Candidacy', qty: 1 }, + { label: 'Form 3: Report of Receipts and Disbursements (House and Senate candidates)', qty: 1 }, + { label: 'Form 3X: Report of Receipts and Disbursements (PACs and political party committees)', qty: 1 }, + { label: 'Presidential packet (forms and guidebook)', qty: 1 }, + { label: 'Congressional packet (forms and guidebook)', qty: 1 }, + { label: 'Party packet (form and guidebook)', qty: 1 }, + { label: 'Nonconnected PAC packet (form and guidebook)', qty: 1 }, + { label: 'Corporate and Labor PAC packet (form and guidebook)', qty: 1 } + ] + } + }, + oig: { + name: 'Office of Inspector General', + summary: `The Office of Inspector General handles reports of fraud, waste and abuse at the FEC + and related inquiries.`, + formPrompt: 'Contact the Office of Inspector General through their hotline portal to submit a complaint.', + externalLinks: [ + { + label: 'Submit a complaint', + href: 'https://fecoig.ains.com/', + icon: 'i-question-circle' + } + ], + fields: '' + }, + press: { + name: 'Press Office', + nameDisclaimer: '(reporters or journalists only)', + summary: 'The Press Office responds to questions from the media.', + formPrompt: 'Contact the Press Office via telephone, email or send them a message.', + phoneMenu: 1, // 1-800-424-9530, menu option # + phoneExt: 1220, // 202-694-#### + ePrefix: 'press', + success: 'The Press Office will get back to you within 24 hours.', + fields: { + email: true, + subject: [ + { label: 'Assist with a media query', + ePrefix: 'press' }, + { label: 'Answer a question about a Commission meeting', + ePrefix: 'press' } + ], + message: true + } + }, + rad: { + name: 'Reports Analysis Division', + summary: `The Reports Analysis Division (RAD) helps registered committees and their representatives + complete and file their campaign finance reports.`, + formPrompt: `If you are a representative of a registered committee, contact RAD via telephone or use the Contact your Analyst form.`, + phoneMenu: 5, + phoneExt: 1130, + externalLinks: [ + { + type: 'a', + label: 'Use the Contact your Analyst form', + href: '/help-candidates-and-committees/question-rad/' + } + ], + fields: '' + }, + records: { + name: 'Public Records', + summary: `Public Records helps individuals research FEC data and other public documents.`, + formPrompt: 'Contact Public Records via telephone or send a message.', + phoneMenu: 2, // 1-800-424-9530, menu option # + phoneExt: 1120, // 202-694-#### + success: 'The Office of Public Records will get back to you within 2 business days.', + fields: { + email: true, + subject: [ + { label: 'Help research public documents', + ePrefix: 'pubrec' }, + { label: 'Help research campaign finance data', + ePrefix: 'pubrec' } + ], + message: true + } + } + }, + frames: [ + { + frameId: 'intro', + title: '', + class: 'intro', + autoAdvance: false, + nextFrame: true, + content: [ + { + type: 'card', + class: '', + icon: 'i-check-circle', + label: `I know who I need to contact for help ›`, + actionId: 'start-know-who' + }, + { + type: 'card', + class: '', + icon: 'i-question-circle', + label: `I need help, but I’m not sure who to contact ›`, + actionId: 'start-help-with-who' + } + ] + }, + { + frameId: 'teams', + title: `I need help from the…`, + autoAdvance: true, + nextFrame: 'teamFields', + options: [ + { + type: 'radio', + vModel: 'selectedTeam', + value: 'info' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'rad' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'efo' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'records' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'press' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'congress' + }, + { + type: 'radio', + vModel: 'selectedTeam', + value: 'oig' + } + ] + }, + { + frameId: 'topics1', + title: 'I need help with…', + autoAdvance: true, + nextFrame: 'teamFields', + options: [ + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Making a press inquiry (reporters or journalists only)', + team: 'press', + teamSubject: 'Assist with a media query' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Accessing campaign finance records and other public documents', + team: 'records', + teamSubject: '' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Getting a copy of an FEC form or publication', + team: 'info', + teamSubject: 'publications' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Technical issues with filing my electronic report or password help', + team: 'efo', + teamSubject: '' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Filing reports, RFAIs, amendments or specific transactions', + team: 'rad', + teamSubject: '' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + label: 'Questions about campaign finance law, including committee registration and reporting requirements, and contribution limits and prohibitions', + team: 'info', + teamSubject: 'A question about campaign finance law' + }, + { + type: 'radio', + vModel: 'selectedTopic1', + value: 'NEXT', + label: 'More options' + } + ] + }, + { + frameId: 'topics2', + title: 'I need help with…', + autoAdvance: true, + nextFrame: 'teamFields', + options: [ + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'An upcoming FEC training program', + value: 'upcoming-training', + team: 'info', + teamSubject: 'A question about an upcoming training' + }, + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'An upcoming Commission meeting', + team: 'press', + teamSubject: 'Answer a question about a Commission meeting' + }, + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'A request for a speaker', + team: 'info', + teamSubject: 'Help me schedule a group speaker' + }, + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'Congressional and intergovernmental communications', + team: 'congress', + teamSubject: '' + }, + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'Reporting or inquiring about waste, fraud and abuse at the FEC', + team: 'oig', + teamSubject: '' + }, + { + type: 'radio', + vModel: 'selectedTopic2', + label: 'Something else', + team: 'fec', + teamSubject: '' + } + ] + }, + { + frameId: 'teamFields', + title: 'Team Fields Frame', + autoAdvance: false, + nextFrame: ['submit', 'forms'], + fields: {} + }, + { + frameId: 'uspsFields', + title: 'USPS Fields Frame', + autoAdvance: false, + nextFrame: 'orderReview', + fields: {} + }, + { + frameId: 'orderReview', + title: '', + class: '', + autoAdvance: false, + nextFrame: 'acknowledgeSubmission', + fields: {} + }, + { + frameId: 'acknowledgeSubmission', + title: 'Success! Your message has been submitted', + class: '', + autoAdvance: false, + nextFrame: false, + fields: {} + } + ] + }; + }, + mounted: function() { + // Add the transition listeners so frames disappear while out of sight + this.startWatchingTransitions(); + window.addEventListener('beforeunload', this.handleBeforeUnload, { capture: true }); + }, + computed: { + recaptchaShow: function() { + // return false; + let toReturn = false; + + if(this.selectedTeam && !this.teams[this.selectedTeam].fields) // If there are no fields to show, the + toReturn = false; + else if (this.frames[this.currentFrameNum].frameId == 'teamFields' && this.u_subject != 'publications') + toReturn = true; + else if (this.frames[this.currentFrameNum].frameId == 'orderReview') + toReturn = true; + + return toReturn; + }, + recaptchaValidated: function() { + // return false; + return true; + // console.log('grecaptcha: ', grecaptcha); + // console.log('window.grecaptcha: ', window.grecaptcha); + } + }, + methods: { + handleTestingChange(val) { + this.TESTSHOULDFAIL = val; + }, + /** + * Called on the browser's back button. + * If we're showing the intro, nothing happens; + * otherwise will prompt the user that they will lose changes. + * OK: brower will go to previous page. + * Cancel: the app will do its own 'back' functionality + * @param {BeforeUnloadEvent} e + */ + handleBeforeUnload: function(e) { + if (this.currentFrameNum && this.currentFrameNum > 0) { + e.returnValue = 'Using the browser back button will reset this form'; + this.goToFrame('back'); + + } else { + window.removeEventListener('beforeunload', this.handleBeforeUnload, { capture: true }); + } + }, + /** + * + * @param {string} buttonType + * @param {*} e + */ + handleButtonClick: function(buttonType, e) { + e.preventDefault(); + if (buttonType == 'start-know-who') this.goToFrame('teams'); + else if (buttonType == 'start-help-with-who') this.goToFrame('topics1'); + else if (buttonType == 'Next') this.validateThenNext(); + else if (buttonType == 'Back') this.goToFrame('back'); + else if (buttonType == 'Restart') this.restart(); + else if (buttonType == 'Submit') this.validateThenStartSubmission(); + else if (buttonType == 'pubs-remove') { + const theIndex = parseInt(e.target.dataset.index); + const newUPubs = [...this.u_pubs]; + newUPubs[theIndex] = { label: '', qty: 1 }; + + newUPubs.splice(theIndex, 1); + this.u_pubs = [...newUPubs]; + + } else if (buttonType == 'pubs-add') { + this.u_pubs.push({ label: '', qty: 1 }); + } + }, + handleFieldChange: function(e) { + e.preventDefault(); + + // Which variable? + const varToChange = e.target.id; + + // If a publication order label was changed, + if (e.target.id.indexOf('u_pub_label') === 0) { + const theIndex = parseInt(e.target.dataset.index); + const newUPubs = [...this.u_pubs]; + + if (newUPubs[theIndex]) + newUPubs[theIndex].label = e.target.value; + else + newUPubs[theIndex] = { label: e.target.value, qty: 1 }; + + this.u_pubs = [...newUPubs]; + + // If a publication order quantity was changed, + } else if (e.target.id.indexOf('u_pub_qty') === 0) { + const theIndex = parseInt(e.target.dataset.index); + const newUPubs = [...this.u_pubs]; + + if (newUPubs[theIndex]) + newUPubs[theIndex].qty = e.target.value; + else + newUPubs[theIndex] = { label: '', qty: e.target.value }; + + this.u_pubs = [...newUPubs]; + + // Else if the changed var exists + // && we have a new value for it + // && it's not already that value, + } else if (e.target.value && this[varToChange] != e.target.value) { + // If the field has data-uppercase="true", cap the value + this[varToChange] = e.target.dataset.uppercase == 'true' ? e.target.value.toUpperCase() : e.target.value; + } else { + console.log('ELSE FOR SOME REASON'); // eslint-disable-line no-console + console.log(' e.target.value: ', e.target.value); // eslint-disable-line no-console + console.log(' this[varToChange]: ', this[varToChange]); // eslint-disable-line no-console + } + }, + handleFormValidation(val) { + this.formCanSubmit = val; + }, + getFrameNumById: function(requestedID) { + for (let i = 0; i < this.frames.length; i++) { + if (this.frames[i].frameId == requestedID) return i; + } + return 0; + }, + handleRecaptchaEvent: function(e, f) { + console.log('App.handleRecaptchaEvent(e): ', e, f); //eslint-disable-line no-console + }, + goToFrame: function(frameId) { + /* + Forward flow: + + intro → teams → → → teamFields + intro → topics1 → → → teamFields + intro → topics1 → topics 2 → teamFields + … + teamFields → → → → → → → acknowledgeSubmission + teamFields → uspsFields → orderReview → confirmSend → acknowledgeSubmission + + */ + let nextFrameNum = 0; + let currentFrameId = this.frames[this.currentFrameNum].frameId; + + if (frameId == 'next') { + + if (currentFrameId == 'intro') { + if (this.selectedTopic2 != '') { + nextFrameNum = this.getFrameNumById('topics2'); + + } else if (this.selectedTopic1 != '') { + nextFrameNum = this.getFrameNumById('topics1'); + + } else if (this.selectedTeam != '') { + nextFrameNum = this.getFrameNumById('teams'); + } + } else if (currentFrameId == 'teams') { + nextFrameNum = this.getFrameNumById('teamFields'); + + } else if (currentFrameId == 'topics2') { + nextFrameNum = this.getFrameNumById('teamFields'); + + } else if (currentFrameId == 'topics1') { + if (this.selectedTopic1 == 'NEXT') + nextFrameNum = this.getFrameNumById('topics2'); + else + nextFrameNum = this.getFrameNumById('teamFields'); + + } else if (currentFrameId == 'teamFields') { + if (this.u_subject == 'publications') + nextFrameNum = this.getFrameNumById('uspsFields'); + + else { + // TODO: submit or do nothing? + } + } else if (currentFrameId == 'uspsFields') { + nextFrameNum = this.getFrameNumById('orderReview'); + + } else if (currentFrameId == 'orderReview') { + // TODO: SUBMIT + } + } else if (frameId == 'back') { + if (currentFrameId == 'acknowledgeSubmission') { + // No going back—can only restart + } + + if (currentFrameId == 'orderReview') { + nextFrameNum = this.getFrameNumById('uspsFields'); + + } else if (currentFrameId == 'uspsFields') { + nextFrameNum = this.getFrameNumById('teamFields'); + + } else if (currentFrameId == 'teamFields') { + if (this.selectedTopic2) + nextFrameNum = this.getFrameNumById('topics2'); + else if (this.selectedTopic1) + nextFrameNum = this.getFrameNumById('topics1'); + else + nextFrameNum = this.getFrameNumById('teams'); + + } else if (currentFrameId == 'topics2') { + nextFrameNum = this.getFrameNumById('topics1'); + + } else if (currentFrameId == 'topic1') { + nextFrameNum = this.getFrameNumById('intro'); + + } else if (currentFrameId == 'teams') { + nextFrameNum = this.getFrameNumById('intro'); + } + + } else if (typeof frameId == 'number') { + // If we're jumping to a frame number, do it + nextFrameNum = frameId; + + } else if (typeof frameId == 'string') { + // if we're jumping to some other frame id, do it + nextFrameNum = this.getFrameNumById(frameId); + } + + // console.log('recaptcha response: ', grecaptcha.getResponse(0)); + + this.currentFrameNum = nextFrameNum; + }, + handleRadioClick: function(opt) { + if (opt.vModel == 'selectedTeam') { + this.selectedTeam = opt.value; + this.goToFrame('teamFields'); + + } else if (opt.vModel == 'selectedTopic1' || opt.vModel == 'selectedTopic2') { + // Set the selected topic + if (opt.vModel == 'selectedTopic1') { + this.selectedTopic1 = opt.value || opt.label; + this.selectedTopic2 = null; + // For some reason, Vue doesn't want to uncheck the radios for selectedTopic2, + // so we're going to have ES6 do it. Not ideal + let theSelectedTopic2Inputs = document.querySelectorAll('#gov-fec-contact-app input[name="elem_selectedTopic2"]:checked'); + theSelectedTopic2Inputs.forEach(el => { + el.checked = false; + }); + + this.userSubject = null; + this.selectedTeam = opt.value == 'NEXT' ? null : opt.team; + + } else if (opt.vModel == 'selectedTopic2') { + this.selectedTopic2 = opt.value || opt.label; + this.userSubject = null; + this.selectedTeam = opt.team; + + } + + // Should we also pre-select the subject? + if (opt.teamSubject && opt.teamSubject != '' && this.selectedTeam) { + try { + const teamSubjects = this.teams[this.selectedTeam].fields.subject; + let newSubject = ''; + for (let i = 0; i < teamSubjects.length; i++) { + const testSub = teamSubjects[i]; + if (testSub.value == opt.teamSubject || testSub.label == opt.teamSubject) { + newSubject = opt.teamSubject; + break; + } + } + if (newSubject != '') this.u_subject = newSubject; + } catch(e) { + console.log('HANDLERADIOCLICK catch! e: ', e); // eslint-disable-line no-console + } + } else { + console.log('HANDLERADIOCLICK else'); // eslint-disable-line no-console + } + // Show the frame + if (opt.vModel == 'selectedTopic1' && this.selectedTopic1 == 'NEXT') this.goToFrame('topics2'); + else this.goToFrame('teamFields'); + } + }, + restart: function() { + // reset vars + this.currentFrameNum = 0; + this.TESTSHOULDFAIL = false; + this.u_pubs = []; + ['submissionMessages', 'selectedTeam', 'selectedTopic1', 'selectedTopic2', 'u_city', + 'u_committee', 'u_email', 'u_message', 'u_name', 'u_state', 'u_street1', 'u_street2', + 'u_subject', 'u_zip'].forEach(varName => { + this[varName] = ''; + }); + + let theCheckedInputElements = document.querySelectorAll('#gov-fec-contact-app input:checked'); + theCheckedInputElements.forEach(el => { + el.checked = false; + }); + // TODO - a better way to reset all the form values? + let theForm = document.querySelector('form.frames'); + theForm.reset(); + }, + finishSubmission: function(responseOrData, submissionBody) { + console.log('finishSubmission(responseOrData, submissionBody): ', responseOrData, submissionBody); // eslint-disable-line no-console + // If there's a responseOrData.status, it's an error + const currentTeam = this.teams[this.selectedTeam]; + this.submissionMessages = ''; + + if (!responseOrData.status) { + this.submissionMessages = { + success: true, + headline: 'Success! Your message has been submitted.', + message: currentTeam.success || 'We will get back to you as soon as possible.' + }; + if (currentTeam.successMore) { + this.submissionMessages.successMore = currentTeam.successMore; + } + } else { + + // this.selectedTeam; + let recipient = currentTeam.ePrefix; + + for (let i = 0; i < currentTeam.fields.subject.length; i++) { + const thisSubject = currentTeam.fields.subject[i]; + if (thisSubject.label == this.userSubject) { + recipient = thisSubject.ePrefix; + break; + } + } + if (!recipient) recipient = 'info'; + recipient += '@fec.gov'; + + let readableMessage = ''; + if (this.u_name) readableMessage += `NAME:\n${this.u_name}
`; + readableMessage += `EMAIL ADDRESS:\n${this.u_email}
`; + if (this.u_committee) readableMessage += `COMMITTEE ID:\n${this.u_committee}
`; + if (this.u_subject == 'publications') { + readableMessage += `MAILING ADDRESS:
${this.u_street1}
`; + if (this.u_street2) readableMessage += `${this.u_street2}
`; + readableMessage += `${this.u_city} ${this.u_state} ${this.u_zip}

`; + readableMessage += 'REQUESTED PUBLICATIONS:
'; + for (let i = 0; i < this.u_pubs.length; i++) { + readableMessage += this.u_pubs[i].qty; + readableMessage += this.u_pubs[i].qty == 1 ? ' copy of ' : ' copies of '; + readableMessage += `'${this.u_pubs[i].label}'
`; + } + // readableMessage += '
'; + } + if (this.u_message) readableMessage += `MESSAGE:
${this.u_message}`; + + const subject = `Request from fec.gov: ${submissionBody.u_category}`; + const emailLinkSubject = `Assistance Request: ${submissionBody.u_category}`; + const scrubbedMessage = readableMessage.replace(/|<\/strong>/g, '').replace(/
/g, '\n'); + const emailLinkBody = `I'd like help from the ${this.teams[this.selectedTeam].name} team.\n\n${scrubbedMessage}`; + + const linkEmail = `mailto://${recipient}&subject=${emailLinkSubject}&body=${emailLinkBody}`; + const linkGmail = `https://mail.google.com/mail/?view=cm&fs=1&su=${emailLinkSubject}&body=${emailLinkBody}&to=${recipient}`; + const linkYahoo = `http://compose.mail.yahoo.com/?subj=${emailLinkSubject}&body=${emailLinkBody}&to=${recipient}`; + const linkLive = `https://outlook.live.com/default.aspx?rru=compose&subject=${emailLinkSubject}&body=${emailLinkBody}&to=${recipient}`; + const linkAol = `http://mail.aol.com/mail/compose-message.aspx?subject=${emailLinkSubject}&body=${emailLinkBody}&to=${recipient}`; + + this.submissionMessages = { + success: false, + headline: 'Unable to send your message at this time.', + message: `Submitting your message has failed. You’re welcome to try again, call the FEC offices, + or use the content below to send your message through another platform.

+ To: ${recipient}

+ Subject: ${subject}

+ ${readableMessage}`, + recipient: recipient, + emailLinks: [ + { + label: 'e', + href: encodeURI(linkEmail) + }, + { + label: 'G', + href: encodeURI(linkGmail) + }, + { + label: 'Y!', + href: encodeURI(linkYahoo) + }, + { + label: 'H', + href: encodeURI(linkLive) + }, + { + label: 'A', + href: encodeURI(linkAol) + } + ] + }; + } + + this.currentFrameNum = this.getFrameNumById('acknowledgeSubmission'); + // console.log('App.finishSubmission(var1): ', e, submissionBody); + this.isSubmitting = false; + // Scroll the page to show the Vue element + this.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, + validateThenNext: function() { + this.$refs.framesHolder.validateCurrentFrame(true); + + if (this.formCanSubmit === true) this.goToFrame('next'); + }, + validateThenStartSubmission: function() { + this.$refs.framesHolder.validateCurrentFrame(true); + + if (this.formCanSubmit === true) this.startSubmission(); + }, + startSubmission: function() { + this.isSubmitting = true; + + const submissionBody = { + // selectedTeam: this.selectedTeam, + // selectedTopic: this.selectedTopic, + // u_pubs: this.u_pubs, + committee_name: this.u_committee, + u_contact_email: this.u_email, + u_contact_first_name: this.u_name.split(' ')[0] || '', + u_contact_last_name: this.u_name.substr(this.u_name.indexOf(' ')) || '', + u_contact_title: '', + u_committee: this.u_committee, + u_category: this.u_subject, + u_description: this.u_message, + u_contact_city: this.u_city, + u_contact_street1: this.u_street1, + u_contact_street2: this.u_street2, + u_contact_state: this.u_state, + u_contact_zip: this.u_zip + }; + + // fetch(this.postUrl, { + // cache: 'no-cache', + // credentials: 'same-origin', + // method: 'POST', + // mode: 'cors', + // headers: { 'Content-Type': 'application/json' }, + // redirect: 'follow', + // body: JSON.stringify(submissionBody) + // }) + // .then(response => { + // console.log('response: ', response); + // if (response.status != 200) { + // this.finishSubmission(response, submissionBody); + // throw new Error(response); + // } + // return response.json(); + // }) + // .then(data => { + // console.log(' data: ', data); + // this.finishSubmission(data, submissionBody); + // }) + // .catch(e => { + // console.log(' CATCH e: ', e); + // }); + if (this.TESTSHOULDFAIL) { + setTimeout(this.finishSubmission, 2000, { status: 999 }, submissionBody); + } else { + setTimeout(this.finishSubmission, 2000, { success: true }); + } + }, + updateNavOptions: function(obj) { + this.canNavBack = obj.Back; + this.canNavNext = obj.Next; + this.canNavRestart = obj.Restart; + this.canNavSubmit = obj.Submit; + }, + startWatchingTransitions: function() { + const frames = document.querySelectorAll('.frame'); + + // Add off-screen to all non-intro frames + for (let i = 0; i < frames.length; i++) { + // + if (!frames[i].classList.contains('intro')) { + frames[i].classList.add('off-screen'); + } + + frames[i].addEventListener('transitionstart', function(e) { + if (e.target.classList.contains('frame')) { + e.target.classList.remove('off-screen'); + } + }); + + frames[i].addEventListener('transitionend', function(e) { + if ( + e.target.classList.contains('frame') && + !e.target.classList.contains('current') + ) + e.target.classList.add('off-screen'); + }); + } + } + } +}); diff --git a/fec/fec/static/scss/base.scss b/fec/fec/static/scss/base.scss index f0a9c69396..d3eae28c57 100644 --- a/fec/fec/static/scss/base.scss +++ b/fec/fec/static/scss/base.scss @@ -41,4 +41,5 @@ @import "components/richtext"; @import "components/calc-admin-fines"; +@import "components/contact-app"; @import "components/fec-offices"; diff --git a/fec/fec/static/scss/components/_buttons.scss b/fec/fec/static/scss/components/_buttons.scss index 673026eb0f..fe76f089a1 100644 --- a/fec/fec/static/scss/components/_buttons.scss +++ b/fec/fec/static/scss/components/_buttons.scss @@ -582,3 +582,8 @@ } } +.button--plus { + @include u-icon-bg($plus, $inverse); + background-position: u(1rem) 50%; + padding-left: u(4rem); +} diff --git a/fec/fec/static/scss/components/_cards.scss b/fec/fec/static/scss/components/_cards.scss index 38b4a8602b..e1bec4a6d6 100644 --- a/fec/fec/static/scss/components/_cards.scss +++ b/fec/fec/static/scss/components/_cards.scss @@ -553,6 +553,17 @@ background-color: $primary; background-size: 50%; } + &.i-check-circle { + @include u-icon-bg($check-circle, $federal-blue); + // background-color: $inverse; + background-size: 80%; + } + &.i-question-circle { + @include u-icon-bg($question-circle, $federal-blue); + // background-color: $inverse; + background-size: 80%; + } + } } diff --git a/fec/fec/static/scss/components/_contact-app.scss b/fec/fec/static/scss/components/_contact-app.scss new file mode 100755 index 0000000000..a1c1c622d0 --- /dev/null +++ b/fec/fec/static/scss/components/_contact-app.scss @@ -0,0 +1,734 @@ +#vue-component { + width: 100%; + max-width: 1024px; +} + +// Vue strips the attributes from its DOM element +#gov-fec-contact-app { + font-family: $sans-serif; + font-size: 1.6rem; + overflow: hidden; + position: relative; + + &.loading { + padding-top: 50px; + text-align: center; + width: 100%; + } + + @include media($med) { + min-height: calc(100% - 5rem); + } + + .frames { + background-color: $inverse; + display: block; + overflow: hidden; + position: relative; + width: 100%; + transition: height ease-in-out .75s; + } + .frame { + display: block; + float: left; + left: 100%; + pointer-events: none; + position: absolute; + top: 0; + transition: left .25s, height 1s ease-in-out; + width: 100% !important; + + @include media($med) { + padding-left: 0; + } + + &.previous { + left: -100%; + transition: left .25s; + * { + pointer-events: none; + user-select: none; + } + } + &.next { + * { + pointer-events: none; + user-select: none; + } + } + &.current { + display: block; // (only need to define this because .next.off-screen will jump to transition complete before animation can start) + left: 0; + pointer-events: all; + transition: left .5s; + } + &.intro { + text-align: center; + + .grid__item { + &:first-child { + margin-bottom: 2rem; + + @include media($med) { + margin-bottom: 0; + } + } + .card { + cursor: pointer; + border: 2px solid transparent; + transition: border-color .5s; + + &:hover { + border-color: $gray; + transition: border-color .5s; + } + + .card__image__container { + height: 80px; + width: 80px; + + .card__icon { + background-size: 100%; + height: 100%; + width: 100%; + } + } + } + } + @include media($med) { + // padding-right: 2em; + } + + div { + margin: 0 auto; + + @include media($med) { + max-width: 80%; + } + } + .button--cta { + margin-top: 1.2rem; // a little extra space above the button + } + } + &.off-screen * { + display: none; + } + } // end .frame + address { + font-style: normal; + line-height: 1.25em; + } + label { + clear: both; + line-height: 1.25; + + span { // a way to have non-bold inside the bold labels + font-weight: normal; + } + } + button { + &.is-disabled { + pointer-events: none; + cursor: default; + } + &.button--back.button--alt { + background-image: url("data:image/svg+xml;charset=utf8, %3Csvg%20%20fill%3D%27%23212121%27%20width%3D%2212%22%20height%3D%2210%22%20viewBox%3D%220%200%2012%2010%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M.418%203.809L6.652.124a.851.851%200%200%201%201.294.727v7.382a.852.852%200%200%201-1.284.733L.418%205.276a.852.852%200%200%201%200-1.467z%22%2F%3E%3C%2Fsvg%3E"); + } + &.tooltip__trigger { + margin: .5em 0 .5rem 1rem; + position: relative; + } + &.tooltip__trigger + p { + float: left; + display: inline-block; + width: auto; + } + &.hidden { + display: none !important; + } + &.button--close--base { + width: 2.5rem; + height: 2.5rem; + background-size: 100%; + background-position: right 4px; + padding: 0; + } + } + + .fields-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + + .field-wrapper { + + &.col-3, &.col-5, &.col-8, &.col-9, &.col-12 { + width: 100%; + } + @include media($med) { + &.col-3 { + margin-right: .5rem; + width: calc(8.33% * 3 - .5rem); + + &:last-child { + margin-right: 0; + } + } + &.col-5 { + margin-right: .5rem; + width: calc(8.33% * 5 - .5rem); + + &:last-child { + margin-right: 0; + } + } + &.col-8 { + width: calc(8.33% * 8 - .5rem); + margin-right: .5rem; + + &:last-child { + margin-right: 0; + } + } + &.col-9 { + width: calc(8.33% * 9 - .5rem); + margin-right: .5rem; + + &:last-child { + margin-right: 0; + } + } + } + } + + + + .col-l { + margin-right: .5rem; + width: calc(55% - .5rem); + } + .col-r { + margin-left: .5rem; + width: calc(45% - .5rem); + } + .col-rl { + width: calc(22.5% - .5rem); + margin-right: .5rem; + } + .col-rr { + width: calc(22.5% - .5rem); + } + + input#u_street1 { + order: 3; + } + label[for="u_street1"] { + order: 1; + } + input#u_street2 { + order: 4; + } + label[for="u_street2"] { + order: 2; + } + + input#u_city { + order: 8; + } + label[for="u_city"] { + order: 5; + } + select#u_state { + order: 9; + } + label[for="u_state"] { + order: 6; + } + input#u_zip { + order: 10; + } + label[for="u_zip"] { + order: 7; + } + + } + + .message { + padding: 2rem; + + h2.message__title { + margin-left: 3rem; + + @include media($med) { + margin-left: 0; + } + } + + } + + @include media($med) { + .message { + margin-top: 10rem; + padding: 5rem 2rem 2rem 2rem; + } + } + + .question-type-block { + position: relative; + display: inline-block; + float: left; + clear: both; + } + + [type=radio]+label { + background-color: transparent; + border-color: $inverse; + float: left; + transition: border-color .5s; + } + [type=radio]+label:hover { + background-color: transparent; + border-color: #aeb0b5; + transition: border-color .5s; + } + [type=radio]:checked+label { + background-color: transparent; + border: 1px solid #aeb0b5; + transition: border-color .5s; + } + [type=number] { + display: inline-block; + float: left; + margin: 0 0 .5rem .25rem; + width: 15rem; + } + [type=number]+label { + display: inline-block; + padding: .4rem .8rem .4rem 1rem; + position: relative; + } + [type=number].indented { + margin-left: 3.5rem; + + &.search__example { + clear: none; + } + } + + span.clear { + clear: both; + display: block; + height: 0px; + margin-bottom: 1rem; + width: 100%; + } + + h4 { + float: left; + margin-top: .5rem; + padding-right: .5rem; + + &.indented { + font-size: 1.4rem; + max-width: 70%; + padding-left: 3.5rem; + padding-top: .5rem; + } + &.subhead { + text-transform: uppercase; + } + + &.search_example { + margin-bottom: 0; + } + } + input.label-headline { + float: left; + clear: both; + } + label { + &.indented { + float: left; + + &.search__example { + clear: none; + padding-top: 1rem; + } + } + &.label-headline { + float: left; + font-size: 1.4rem; + font-weight: bold; + letter-spacing: -0.3px; + margin-top: .2rem; + text-transform: uppercase; + } + } + p { + font-size: 1.4rem; + line-height: 1.25em; + margin-bottom: 1.2rem; + width: 100%; + + &.indented { + clear: both; + float: left; + padding-left: 3.5rem; + max-width: 70%; + + @include media($med) { + max-width: none; + } + } + &.summary { + clear: both; + display: block; + font-size: 1.4rem; + font-weight: 700; + letter-spacing: -0.3px; + line-height: 1.8em; + margin-top: .2rem; + text-transform: uppercase; + + + span { + display: block; + font-size: 2.4rem; + letter-spacing: -0.51px; + } + &.total-fine span { + color: $error; + } + } + } + .contact-item { + display: block; + margin-bottom: 0; + padding-top: .5em; + padding-left: 2em; + + @include media($med) { + padding-left: 9.5rem; + } + + &:before { + height: 1.5em; + margin-left: -2em; + width: 1.5em; + position: absolute; + } + } + .nonbreaking { + display: inline-block; + } + .search__example { + clear: both; + float: left; + margin-top: 0; + } + span.t-note.indented { + margin-left: 3.5rem; + } + + .team-contact-form > *:not(.outdent) { + margin-left: 5rem; + } + + + input, select, textarea { + margin-bottom: 26.25px; // label height + width: 100%; + + & + label.field__message--error { + display: none; + } + + &[aria-invalid="true"] { + border-color: $error !important; + margin-bottom: 0; + + & + label.field__message--error { + display: block; + } + } + + } + textarea { + height: 90px; + } + // We can have an error-group after , etc. but also after + label.field__message--error-group { + display: none; + } + *[aria-invalid="true"] { + margin-bottom: 0; + & ~ label.field__message--error-group { + display: block; + } + } + + + .field__message--error, .field__message--error-group { + // borrowed from .filter__message + font-family: $sans-serif; + font-size: u(1.4rem); + padding-bottom: u(1rem); + clear: both; + + // borrowed from .filter__message--error + font-size: u(1.3rem); + color: $error; + } + .field__message--error-group { + margin-top: -2rem; + } + + .recaptcha-holder { + height: 78px; + margin-bottom: 2rem; + padding-left: 3rem; + position: relative; + + @include media($med) { + padding-left: 9.5rem; + } + + div:first-child:before { + content: "::TESTING:: Every submission will fail"; + color: red; + display: block; + font-size: 1.5rem; + font-weight: bold; + left: 0; + line-height: 1em; + margin-top: -2rem; + padding-top: 0.75rem; + position: absolute; + top: 0; + + @include media($med) { + margin-top: 0; + width: 10rem; + } + } + } + .publications-order-form { + margin-bottom: 26.25px; // label height + width: 100%; + + col { + width: 11rem; + + @include media($med) { + width: 14rem; + } + + &:first-child { + width: auto; + } + &:last-child { + width: 2.5rem; + } + } + + th { + font-size: 1.5rem; + line-height: 1.1em; + } + + td { + padding-bottom: .25rem; + } + + select, [type=number] { + margin-bottom: 0; + width: calc(100% - 0.5rem); + } + } + + .publications-order-summary { + display: flex; + flex-direction: row; + flex-wrap: wrap; + line-height: 1.25em; + margin: 2rem 0; + + div { + padding: .5rem; + + &:not(:last-child):not(:nth-last-child(2)) { + border-bottom: thin solid lightgrey; + } + + &:nth-child(odd) { + width: 75%; + } + &:nth-child(even) { + width: 25%; + } + + @include media($med) { + &:nth-child(odd) { + width: 80%; + } + &:nth-child(even) { + width: 20%; + } + } + } + } + + @keyframes loadingAnimation { + 0% { + // swell + left: 1rem; + height: 0rem; + top: 1rem; + width: 0rem; + z-index: 1; + } + 16% { + // done swelling, start slide + left: 0rem; + height: 2rem; + top: 0rem; + width: 2rem; + z-index: 5; + } + 50% { + // finish slide, start to shrink + height: 2rem; + left: 6rem; + top: 0rem; + width: 2rem; + z-index: 1; + } + 66% { + // done shrinking, start the return + height: 0rem; + left: 7rem; + top: 1rem; + width: 0rem; + z-index: 1; + } + 100% { + // back to 0 + height: 0rem; + left: 1rem; + top: 1rem; + width: 0rem; + z-index: 1; + } + } + + .loading-animation { + display: inline-block; + height: 2rem; + margin-top: .25rem; + position: relative; + width: 8rem; + + .icon { + content: ""; + border-radius: 50%; + display: block; + position: absolute; + top: 1rem; + z-index: 0; + + &:nth-child(1) { + background: #112e51; + } + &:nth-child(2) { + background: #35bdbb; + animation-delay: -.5s; + } + &:nth-child(3) { + background: #112e51; + animation-delay: -1s; + } + &:nth-child(4) { + background: #35bdbb; + animation-delay: -1.5s; + } + animation-name: loadingAnimation; + animation-duration: 2s; + animation-iteration-count: infinite; + } + } +} + +// Style overrides specifically for container queries +$minWidthS: '430px'; +$minWidthM: '675px'; +$minWidthL: '700px'; +$minWidthXL: '860px'; +#gov-fec-contact-app.cq-container { + container: contactapp / inline-size; + + .col-3, &.col-5, &.col-8, &.col-9, &.col-12 { + width: 100cqw; + } +} +@container contactapp (min-width: $minWidthS) { + #gov-fec-contact-app.cq-container { + .grid.grid--2-wide { + max-width: 80cqw; + } + // .contact-item { + // padding-left: 3.5rem; + // } + .recaptcha-holder { + padding-left: 3rem; + } + } +} +@container contactapp (min-width: $minWidthM) { + #gov-fec-contact-app.cq-container { + &.col-3 { + width: calc(8.33% * 3 - .5rem); + + &:not(:last-child) { + margin-right: .5rem; + } + } + &.col-5 { + width: calc(8.33% * 5 - .5rem); + + &:not(:last-child) { + margin-right: .5rem; + } + } + &.col-8 { + width: calc(8.33% * 8 - .5rem); + + &:not(:last-child) { + margin-right: .5rem; + } + } + &.col-9 { + width: calc(8.33% * 9 - .5rem); + + &:not(:last-child) { + margin-right: .5rem; + } + } + .contact-item { + padding-left: 9.5rem; + } + .recaptcha-holder { + padding-left: 9.5rem; + } + } +} +@container contactapp (min-width: $minWidthL) { + #gov-fec-contact-app.cq-container { + // + } +} +@container contactapp (min-width: $minWidthXL) { + #gov-fec-contact-app.cq-container { + // + } +} diff --git a/fec/fec/static/scss/components/_contact-items.scss b/fec/fec/static/scss/components/_contact-items.scss index c3eda367e9..92e486d030 100644 --- a/fec/fec/static/scss/components/_contact-items.scss +++ b/fec/fec/static/scss/components/_contact-items.scss @@ -60,6 +60,12 @@ padding-top: u(1rem); } +.contact-item--check { + &::before { + @include u-icon-circle($check-circle, $inverse, $primary, 3.4rem); + } +} + .contact-item--phone { &::before { @include u-icon-circle($telephone-circle, $inverse, $primary, 3.4rem); diff --git a/fec/fec/static/scss/components/_type-styles.scss b/fec/fec/static/scss/components/_type-styles.scss index b13da66be7..a8f344c30f 100644 --- a/fec/fec/static/scss/components/_type-styles.scss +++ b/fec/fec/static/scss/components/_type-styles.scss @@ -70,6 +70,10 @@ display: inline; } +.t-caps { + text-transform: uppercase; +} + .t-sans { font-family: $sans-serif; letter-spacing: -0.3px; @@ -86,6 +90,9 @@ .t-bold { font-weight: bold; } +.t-unbold { + font-weight: initial; +} .t-underline { text-decoration: underline; diff --git a/fec/fec/static/scss/vendor/bourbon/addons/_triangle.scss b/fec/fec/static/scss/vendor/bourbon/addons/_triangle.scss index 26cfed136e..76863c13b4 100644 --- a/fec/fec/static/scss/vendor/bourbon/addons/_triangle.scss +++ b/fec/fec/static/scss/vendor/bourbon/addons/_triangle.scss @@ -1,4 +1,5 @@ @use 'sass:math'; + @mixin triangle($size, $color, $direction) { $width: nth($size, 1); $height: nth($size, length($size)); diff --git a/fec/fec/static/scss/vendor/bourbon/functions/_px-to-em.scss b/fec/fec/static/scss/vendor/bourbon/functions/_px-to-em.scss index 822453ae77..f96ffc5c8d 100644 --- a/fec/fec/static/scss/vendor/bourbon/functions/_px-to-em.scss +++ b/fec/fec/static/scss/vendor/bourbon/functions/_px-to-em.scss @@ -2,6 +2,7 @@ // eg. for a relational value of 12px write em(12) when the parent is 16px // if the parent is another value say 24px write em(12, 24) @use 'sass:math'; + @function em($pxval, $base: $em-base) { @if not unitless($pxval) { $pxval: strip-units($pxval); diff --git a/fec/fec/static/scss/vendor/bourbon/functions/_px-to-rem.scss b/fec/fec/static/scss/vendor/bourbon/functions/_px-to-rem.scss index 423b7b5132..17c0ae0ea8 100644 --- a/fec/fec/static/scss/vendor/bourbon/functions/_px-to-rem.scss +++ b/fec/fec/static/scss/vendor/bourbon/functions/_px-to-rem.scss @@ -1,8 +1,8 @@ // Convert pixels to rems // eg. for a relational value of 12px write rem(12) // Assumes $em-base is the font-size of - @use 'sass:math'; + @function rem($pxval) { @if not unitless($pxval) { $pxval: strip-units($pxval); diff --git a/fec/fec/static/scss/vendor/neat/grid/_private.scss b/fec/fec/static/scss/vendor/neat/grid/_private.scss index af6562bb5d..2c8a6f4da0 100644 --- a/fec/fec/static/scss/vendor/neat/grid/_private.scss +++ b/fec/fec/static/scss/vendor/neat/grid/_private.scss @@ -1,10 +1,11 @@ +@use 'sass:math'; + $parent-columns: $grid-columns !default; $fg-column: $column; $fg-gutter: $gutter; $fg-max-columns: $grid-columns; $container-display-table: false !default; $layout-direction: LTR !default; -@use 'sass:math'; @function flex-grid($columns, $container-columns: $fg-max-columns) { $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; diff --git a/fec/home/migrations/0125_officepage.py b/fec/home/migrations/0125_officepage.py index 1c2994e819..e07a742a94 100644 --- a/fec/home/migrations/0125_officepage.py +++ b/fec/home/migrations/0125_officepage.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OfficePage', fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), #gitleaks:allow ('offices', wagtail.fields.StreamField([('office', wagtail.blocks.StructBlock([('office_title', wagtail.blocks.CharBlock(blank=True, help_text='Required', null=True, required=True)), ('hide_title', wagtail.blocks.BooleanBlock(help_text='Should the offfice title be displayed?', required=False)), ('office_description', wagtail.blocks.RichTextBlock(blank=True)), ('more_info', wagtail.blocks.StreamBlock([('html', wagtail.blocks.RawHTMLBlock(blank=True)), ('internal_button', wagtail.blocks.StructBlock([('internal_page', wagtail.blocks.PageChooserBlock()), ('text', wagtail.blocks.CharBlock())], blank=True)), ('external_button', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock()), ('text', wagtail.blocks.CharBlock())], blank=True)), ('document', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock()), ('document', wagtail.documents.blocks.DocumentChooserBlock())], blank=True, template='blocks/simple-document.html'))], blank=True, help_text='Use for internal/external btns or document-links', required=False)), ('employee', wagtail.blocks.StructBlock([('employee_name', wagtail.blocks.CharBlock(blank=True, required=False)), ('employee_title', wagtail.blocks.StructBlock([('title', wagtail.blocks.StreamBlock([('html_title', wagtail.blocks.RawHTMLBlock(blank=True, help_text='For footnote on title, use html block with <sup>1</sup>', required=False)), ('text_title', wagtail.blocks.CharBlock(blank=True, required=False))], blank=True, required=False))], blank=True, help_text='For footnote on title, use html block with <sup>1</sup>', required=False)), ('employee_image', wagtail.images.blocks.ImageChooserBlock(blank=True, required=False)), ('employee_bio', wagtail.blocks.RichTextBlock(blank=True, required=False))], blank=True, default=[], null=True, required=False)), ('contact_info', wagtail.blocks.StructBlock([('label', wagtail.blocks.CharBlock(icon='title', required=False)), ('contact_items', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('item_label', wagtail.blocks.CharBlock(required=False)), ('item_icon', wagtail.blocks.ChoiceBlock(choices=[('email', 'Email'), ('fax', 'Fax'), ('hand', 'Hand delivery'), ('phone', 'Phone'), ('mail', 'Mail'), ('github', 'Github'), ('question-bubble', 'Question')])), ('item_info', wagtail.blocks.RichTextBlock(required=True))])))], blank=True)), ('extra_info', wagtail.blocks.StreamBlock([('html', wagtail.blocks.RawHTMLBlock(blank=True, help_text='For footnote, use <sup>1</sup>', required=False)), ('text', wagtail.blocks.RichTextBlock(blank=True, required=False))], blank=True, help_text='Use for sub-offices, staff-lists, footnotes or any extra info appearing at bottom of office section
For footnote, use html block with <sup>1</sup>', null=True, required=False))], blank=True, null=True))], blank=True, null=True, use_json_field=True)), ], options={ diff --git a/fec/home/migrations/0126_contactpage_show_contact_app.py b/fec/home/migrations/0126_contactpage_show_contact_app.py new file mode 100644 index 0000000000..9b406d5485 --- /dev/null +++ b/fec/home/migrations/0126_contactpage_show_contact_app.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.18 on 2023-03-28 17:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0125_officepage'), + ] + + operations = [ + migrations.AddField( + model_name='contactpage', + name='show_contact_app', + field=models.BooleanField(default=False, help_text='True: the main column should be the snippet and the contact/help app.\n False: the main column will be the fields after this one.'), + ), + ] diff --git a/fec/home/models.py b/fec/home/models.py index 1d333d9f22..f9e786dc6a 100644 --- a/fec/home/models.py +++ b/fec/home/models.py @@ -15,6 +15,7 @@ from wagtail import blocks from wagtail.admin.panels import ( FieldPanel, + HelpPanel, InlinePanel, MultiFieldPanel, PageChooserPanel, @@ -1224,6 +1225,7 @@ class ContactPage(Page): 'home.EmbedSnippet', required=False, template='blocks/embed-info-message.html', icon='warning')), ], null=True, blank=True) + show_contact_app = models.BooleanField(default=False) services_title = models.TextField() services = StreamField([ ('services', blocks.RichTextBlock()) @@ -1232,6 +1234,23 @@ class ContactPage(Page): content_panels = Page.content_panels + [ StreamFieldPanel('contact_items'), StreamFieldPanel('info_message', heading='Informational message'), + MultiFieldPanel( + [ + FieldPanel( + 'show_contact_app', + heading='Show contact/help app', + ), + HelpPanel( + content='NOTE: Checking this will show the contact/help app in the main column \ + of the site and will not include any content from the fields after this one.
\ + Checked: show the contact/help app, but skip the rest of the content on this \ + page.
\ + Unchecked: show the rest of the content on this page, but not the \ + contact/help app.' + ), + ], + heading='Contact/help app or text content?', + ), FieldPanel('services_title'), StreamFieldPanel('services'), ] diff --git a/fec/home/templates/home/contact_page.html b/fec/home/templates/home/contact_page.html old mode 100644 new mode 100755 index c3677b8c20..8655a63808 --- a/fec/home/templates/home/contact_page.html +++ b/fec/home/templates/home/contact_page.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load wagtailcore_tags %} -{% load static %} +{% load static filters %} {% block body_class %}template-{{ self.get_verbose_name | slugify }}{% endblock %} {% block content %} @@ -17,22 +17,32 @@

{{ self.title }}

{% for block in self.contact_items %} {{ block }} {% endfor %} - -
+
{{ self.info_message }} + {% if self.show_contact_app %} {# feature flag for whether we show the Vue contact app… #} +

Contact the FEC

+
animation indicating that the contact app is still loading its components
+ {% else %} {# …or show the content from Wagtail #}

{{ self.services_title }}

- {% for block in self.services %} + {% for block in self.services %}
- {% if block.block_type == 'paragraph' %} + {% if block.block_type == 'paragraph' %}
{{ block }}
- {% else %} + {% else %} {{ block }} - {% endif %} + {% endif %}
- {% endfor %} + {% endfor %} + {% endif %}{# end feature flag #}
{% endblock %} + +{% block extra_js %} +{# Override this in templates to add extra javascript #} + + +{% endblock %} diff --git a/package-lock.json b/package-lock.json index d1e0a8ab96..b1dcdf7830 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,6 +113,7 @@ "npm-watch": "^0.11.0", "promise-polyfill": "^8.1.3", "puppeteer": "^18.2.1", + "sass": "^1.58.0", "scrollmonitor": "^1.2.3", "sinon": "^1.17.2", "sinon-chai": "^2.8.0", @@ -122,7 +123,7 @@ "vinyl-buffer": "^1.0.1", "vue": "^2.6.11", "watchify": "^3.11.1", - "webpack": "^3.5.5", + "webpack": "^3.12.0", "webpack-bundle-analyzer": "^3.3.2", "webpack-manifest-plugin": "^1.3.1" }, @@ -253,9 +254,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.0.tgz", - "integrity": "sha512-GqL+Z0d7B7ADlQBMXlJgvXEbtt5qlqd1YQ5fr12hTSfh7O/vgrEIvJxU2e7aSVrEUn75zTZ6Nd0s8tthrlZnrQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -3837,12 +3838,6 @@ "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-0.0.2.tgz", "integrity": "sha1-kPocRBtDar8b+7NSxLWf11480ZQ=" }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -4141,9 +4136,9 @@ } }, "node_modules/concurrently/node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -4283,9 +4278,9 @@ } }, "node_modules/core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz", + "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -4990,6 +4985,24 @@ "react-dom": "^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0" } }, + "node_modules/draft-js-plugins-editor": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/draft-js-plugins-editor/-/draft-js-plugins-editor-2.1.1.tgz", + "integrity": "sha512-fKGe71irNvFHJ5L/lUrh+3vPkBNq0de6x+cgiZUJ9zQERc5KPBtGXIFiarLFVHyrRTCPq+K6xmgfFSAERaFHPw==", + "deprecated": "use @draft-js-plugins/editor >=v4 instead", + "dependencies": { + "decorate-component-with-props": "^1.0.2", + "find-with-regex": "^1.1.3", + "immutable": "~3.7.4", + "prop-types": "^15.5.8", + "union-class-names": "^1.0.0" + }, + "peerDependencies": { + "draft-js": "^0.10.1", + "react": "^15.5.0 || ^16.0.0-rc", + "react-dom": "^15.5.0 || ^16.0.0-rc" + } + }, "node_modules/draft-js/node_modules/core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", @@ -5028,32 +5041,6 @@ "react-dom": "^16.0.0" } }, - "node_modules/draftail/node_modules/draft-js-plugins-editor": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/draft-js-plugins-editor/-/draft-js-plugins-editor-2.1.1.tgz", - "integrity": "sha512-fKGe71irNvFHJ5L/lUrh+3vPkBNq0de6x+cgiZUJ9zQERc5KPBtGXIFiarLFVHyrRTCPq+K6xmgfFSAERaFHPw==", - "deprecated": "use @draft-js-plugins/editor >=v4 instead", - "dependencies": { - "decorate-component-with-props": "^1.0.2", - "find-with-regex": "^1.1.3", - "immutable": "~3.7.4", - "prop-types": "^15.5.8", - "union-class-names": "^1.0.0" - }, - "peerDependencies": { - "draft-js": "^0.10.1", - "react": "^15.5.0 || ^16.0.0-rc", - "react-dom": "^15.5.0 || ^16.0.0-rc" - } - }, - "node_modules/draftail/node_modules/draft-js-plugins-editor/node_modules/find-with-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz", - "integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw==", - "peerDependencies": { - "draft-js": "^0.10.5" - } - }, "node_modules/draftjs-conductor": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-1.2.0.tgz", @@ -7231,6 +7218,14 @@ "node": ">=6" } }, + "node_modules/find-with-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz", + "integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw==", + "peerDependencies": { + "draft-js": "^0.10.5" + } + }, "node_modules/findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -7474,6 +7469,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7621,6 +7617,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9319,6 +9316,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9353,6 +9351,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -11920,9 +11919,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -12152,9 +12151,9 @@ } }, "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -12389,9 +12388,9 @@ } }, "node_modules/npm-watch/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -13286,6 +13285,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -13553,21 +13553,27 @@ } }, "node_modules/postcss": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.14.tgz", - "integrity": "sha512-+jD0ZijcvyCqPQo/m/CW0UcARpdFylq04of+Q7RKX6f/Tu+dvpUI/9Sp81+i6/vJThnOBX09Quw0ZLOVwpzX3w==", + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.22", - "source-map": "^0.6.1" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-selector-parser": { @@ -14580,9 +14586,10 @@ } }, "node_modules/sass": { - "version": "1.57.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz", - "integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==", + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", + "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -14599,6 +14606,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -14611,6 +14619,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -14619,6 +14628,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -14630,6 +14640,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", @@ -14656,6 +14667,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -14666,12 +14678,14 @@ "node_modules/sass/node_modules/immutable": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", - "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==" + "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", + "dev": true }, "node_modules/sass/node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -14683,6 +14697,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -14691,6 +14706,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -14699,6 +14715,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -14710,6 +14727,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -15370,6 +15388,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -16932,7 +16951,7 @@ "node_modules/union-class-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-class-names/-/union-class-names-1.0.0.tgz", - "integrity": "sha512-u7qYld8H+xWZZvb1Y8BhkD0fVmY+ytlm1skpdeYb6+DrSn8jrOC8zY3KMfmkcO3Mdwu/+CyiZrXXpQy0Up+SUA==" + "integrity": "sha1-kllgitrMOQlKKwz+FseOYgBheEc=" }, "node_modules/union-value": { "version": "1.0.1", @@ -18605,9 +18624,9 @@ } }, "@babel/parser": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.0.tgz", - "integrity": "sha512-GqL+Z0d7B7ADlQBMXlJgvXEbtt5qlqd1YQ5fr12hTSfh7O/vgrEIvJxU2e7aSVrEUn75zTZ6Nd0s8tthrlZnrQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", "dev": true }, "@babel/plugin-syntax-object-rest-spread": { @@ -21689,12 +21708,6 @@ "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-0.0.2.tgz", "integrity": "sha1-kPocRBtDar8b+7NSxLWf11480ZQ=" }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -21930,9 +21943,9 @@ "dev": true }, "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -22055,9 +22068,9 @@ } }, "core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==" + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz", + "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==" }, "core-util-is": { "version": "1.0.2", @@ -22671,6 +22684,18 @@ } } }, + "draft-js-plugins-editor": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/draft-js-plugins-editor/-/draft-js-plugins-editor-2.1.1.tgz", + "integrity": "sha512-fKGe71irNvFHJ5L/lUrh+3vPkBNq0de6x+cgiZUJ9zQERc5KPBtGXIFiarLFVHyrRTCPq+K6xmgfFSAERaFHPw==", + "requires": { + "decorate-component-with-props": "^1.0.2", + "find-with-regex": "^1.1.3", + "immutable": "~3.7.4", + "prop-types": "^15.5.8", + "union-class-names": "^1.0.0" + } + }, "draftail": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/draftail/-/draftail-1.4.1.tgz", @@ -22680,28 +22705,6 @@ "draft-js-plugins-editor": "^2.1.1", "draftjs-conductor": "^1.0.0", "draftjs-filters": "^2.2.3" - }, - "dependencies": { - "draft-js-plugins-editor": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/draft-js-plugins-editor/-/draft-js-plugins-editor-2.1.1.tgz", - "integrity": "sha512-fKGe71irNvFHJ5L/lUrh+3vPkBNq0de6x+cgiZUJ9zQERc5KPBtGXIFiarLFVHyrRTCPq+K6xmgfFSAERaFHPw==", - "requires": { - "decorate-component-with-props": "^1.0.2", - "find-with-regex": "^1.1.3", - "immutable": "~3.7.4", - "prop-types": "^15.5.8", - "union-class-names": "^1.0.0" - }, - "dependencies": { - "find-with-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz", - "integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw==", - "requires": {} - } - } - } } }, "draftjs-conductor": { @@ -24502,6 +24505,12 @@ "locate-path": "^3.0.0" } }, + "find-with-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz", + "integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw==", + "requires": {} + }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -24703,6 +24712,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "optional": true }, "fullcalendar": { @@ -24818,6 +24828,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -26201,7 +26212,8 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, "is-finite": { "version": "1.0.2", @@ -26227,6 +26239,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -28334,9 +28347,9 @@ "optional": true }, "nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, "nanomatch": { @@ -28529,9 +28542,9 @@ "dev": true }, "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -28705,9 +28718,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -29408,7 +29421,8 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true }, "pify": { "version": "3.0.0", @@ -29607,14 +29621,14 @@ "dev": true }, "postcss": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.14.tgz", - "integrity": "sha512-+jD0ZijcvyCqPQo/m/CW0UcARpdFylq04of+Q7RKX6f/Tu+dvpUI/9Sp81+i6/vJThnOBX09Quw0ZLOVwpzX3w==", + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.22", - "source-map": "^0.6.1" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" } }, "postcss-selector-parser": { @@ -30434,9 +30448,10 @@ } }, "sass": { - "version": "1.57.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz", - "integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==", + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", + "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -30447,6 +30462,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -30455,12 +30471,14 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -30469,6 +30487,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -30484,6 +30503,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -30491,12 +30511,14 @@ "immutable": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", - "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==" + "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", + "dev": true }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -30504,17 +30526,20 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -30523,6 +30548,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -31100,7 +31126,8 @@ "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true }, "source-map-resolve": { "version": "0.5.2", @@ -32343,7 +32370,7 @@ "union-class-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-class-names/-/union-class-names-1.0.0.tgz", - "integrity": "sha512-u7qYld8H+xWZZvb1Y8BhkD0fVmY+ytlm1skpdeYb6+DrSn8jrOC8zY3KMfmkcO3Mdwu/+CyiZrXXpQy0Up+SUA==" + "integrity": "sha1-kllgitrMOQlKKwz+FseOYgBheEc=" }, "union-value": { "version": "1.0.1", diff --git a/package.json b/package.json index bf5d43d961..65bfb5b3d1 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "npm-watch": "^0.11.0", "promise-polyfill": "^8.1.3", "puppeteer": "^18.2.1", + "sass": "^1.58.0", "scrollmonitor": "^1.2.3", "sinon": "^1.17.2", "sinon-chai": "^2.8.0", @@ -136,7 +137,7 @@ "vinyl-buffer": "^1.0.1", "vue": "^2.6.11", "watchify": "^3.11.1", - "webpack": "^3.5.5", + "webpack": "^3.12.0", "webpack-bundle-analyzer": "^3.3.2", "webpack-manifest-plugin": "^1.3.1" }, diff --git a/tasks.py b/tasks.py index 25ccd9adf6..99b30cf9c5 100644 --- a/tasks.py +++ b/tasks.py @@ -77,6 +77,7 @@ def _detect_space(repo, branch=None, yes=False): ('dev', lambda _, branch: branch == 'develop'), # Uncomment below and adjust branch name to deploy desired feature branch to the feature space # ('feature', lambda _, branch: branch == '[BRANCH NAME]'), + # ('dev', lambda _, branch: branch == 'feature/5520-contact-form'), )