Migrate front-end to Nuxt app (#284)
* wip * Managed to load a page * Stuck at changing routes * Fixed the router, and editable div * WIP * Fix app loader * WIP * Fix check-auth middleware * Started to refactor input components * WIP * Added select input, v-click-outside for vselect * update vselect & phone input * Fixed the mixin * input component updates * Fix signature input import * input component updates in vue3 * image input in vue3 * small fixes * fix useFormInput watcher * scale input in vue3 * Vue3: migrating from vuex to Pinia (#249) * Vue3: migrating from vuex to Pinia * toggle input fixes * update configureCompat --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * support vue3 query builder * Refactor inpus * fix: Vue3 Query Builder - Logic Editor (#251) * support vue3 query builder * upgrade * remove local from middleware * Submission table pagination & migrate chart to vue3 (#254) * Submission table Pagination in background * migrate chart to vue3 * Form submissions pagination * Form submissions * Fix form starts * Fix openSelect key issue --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> Co-authored-by: Julien Nahum <julien@nahum.net> * Vue 3 better animation (#257) * vue-3-better-animation * Working on migration to vueuse/motion * Form sidebar animations * Clean code * Added animations for modal * Finished implementing better animations --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Work in progress * Migrating amplitude and crisp plugin/composable * Started to refactor pages * WIP * vue3-scroll-shadow-fixes (#260) * WIP * WIP * WIP * Figured out auth & middlewares * WI * Refactoring stores and templates pages to comp. api * Finishing the templates pages * fix collapsible * Finish reworking most templates pages * Reworked workspaces store * Working on home page and modal * Fix dropdown * Fix modal * Fixed form creation * Fixed most of the form/show pages * Updated cors dependency * fix custom domain warning * NuxtLink migration (#262) Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Tiny fixes + start pre-rendering * migrate-to-nuxt-useappconfig (#263) * migrate-to-nuxt-useappconfig * defineAppConfig --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> * Working on form/show and editor * Globally import form inputs to fix resolve * Remove vform - working on form public page * Remove initform mixin * Work in progress for form create guess user * Nuxt Migration notifications (#265) * Nuxt Migration notifications * @input to @update:model-value * change field type fixes * @update:model-value * Enable form-block-logic-editor * vue-confetti migration * PR request changes * useAlert in setup * Migrate to nuxt settings page AND remove axios (#266) * Settings pages migration * remove axios and use opnFetch * Make created form reactive (#267) * Remove verify pages and axios lib --------- Co-authored-by: Julien Nahum <julien@nahum.net> * Fix alert styling + bug fixes and cleaning * Refactor notifications + add shadow * Fix vselect issue * Working on page pre-rendering * Created NotionPages store * Added sitemap on nuxt side * Sitemap done, working on aws amplify * Adding missing module * Remove axios and commit backend changes to sitemap * Fix notifications * fix guestpage editor (#269) Co-authored-by: Julien Nahum <julien@nahum.net> * Remove appconfig in favor of runtimeconfig * Fixed amplitude bugs, and added staging environment * Added amplify file * Change basdirectory amplify * Fix loading bar position * Fix custom redirect (#273) * Dirty form handling - nuxt migration (#272) * SEO meta nuxt migration (#274) * SEO meta nuxt migration * Polish seo metas, add defaults for OG and twitter --------- Co-authored-by: Julien Nahum <julien@nahum.net> * migrate to nuxt useClipboard (#268) * Set middleware on pages (#278) * Se middleware on pages * Se middleware on account page * add robots.txt (#276) * 404 page migration (#277) * Templates pages migration (#275) * NuxtImg Migration (#279) Co-authored-by: Julien Nahum <julien@nahum.net> * Update package json * Fix build script * Add loglevel param * Disable page pre-rendering * Attempt to allow svgs * Fix SVGs with NuxtImage * Add .env file at AWS build time * tRGIGGER deploy * Fix issue * ANother attrempt * Fix typo * Fix env? * Attempt to simplify build * Enable swr caching instead of prerenderign * Better image compression * Last attempt at nuxt images efficiency * Improve image optimization again * Remove NuxtImg for non asset files * Restore templates pages cache * Remove useless images + fix templates show page * image optimization caching + fix hydratation issue form template page * URL generation (front&back) + fixed authJWT for SSR * Fix composable issue * Fix form share page * Embeddable form as a nuxt middleware * Fix URL for embeddable middleware * Debugging embeddable on amplify * Add custom domain support * No follow for non-production env * Fix sentry nuxt and custom domain redirect * remove api prefix from routes (#280) * remove api prefix from routes * PR changes --------- Co-authored-by: Julien Nahum <julien@nahum.net> * nuxt migration -file upload - WIP (#271) Co-authored-by: Julien Nahum <julien@nahum.net> * Fix local file upload * Fix file submissions preview * API redirect to back-end from nuxt * API redirect to back-end from nuxt * Remove old JS app, update deploy script * Fix tests, added gh action nuxt step * Updated package-lock.json * Setup node in GH Nuxt action * Setup client directory for GH workflow --------- Co-authored-by: Forms Dev <chirag+new@notionforms.io> Co-authored-by: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com> Co-authored-by: Rishi Raj Jain <rishi18304@iiitd.ac.in> Co-authored-by: formsdev <136701234+formsdev@users.noreply.github.com>
This commit is contained in:
37
client/lib/file-uploads.js
vendored
Normal file
37
client/lib/file-uploads.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
async function storeLocalFile(file, options={}) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const response = await opnFetch('/upload-file', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
response.extension = file.name.split('.').pop()
|
||||
return response
|
||||
}
|
||||
|
||||
export const storeFile = async (file, options = {}) => {
|
||||
if(!useRuntimeConfig().public.s3Enabled) return storeLocalFile(file, options)
|
||||
|
||||
const response = await opnFetch(options.signedStorageUrl || 'vapor/signed-storage-url', {
|
||||
method: 'POST',
|
||||
body: options.data,
|
||||
bucket: options.bucket || '',
|
||||
content_type: options.contentType || file.type,
|
||||
expires: options.expires || '',
|
||||
visibility: options.visibility || '',
|
||||
baseURL: options.baseURL || null,
|
||||
headers: options.headers || {},
|
||||
...options.options
|
||||
})
|
||||
|
||||
// Upload to S3
|
||||
await useFetch(response.url,{
|
||||
method: 'PUT',
|
||||
body: file,
|
||||
})
|
||||
|
||||
response.extension = file.name.split('.').pop()
|
||||
|
||||
return response
|
||||
}
|
||||
325
client/lib/forms/FormLogicConditionChecker.js
vendored
Normal file
325
client/lib/forms/FormLogicConditionChecker.js
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
export function conditionsMet (conditions, formData) {
|
||||
if (conditions === undefined || conditions === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If it's not a group, just a single condition
|
||||
if (conditions.operatorIdentifier === undefined) {
|
||||
return propertyConditionMet(conditions.value, conditions.value ? formData[conditions.value.property_meta.id] : null)
|
||||
}
|
||||
|
||||
if (conditions.operatorIdentifier === 'and') {
|
||||
let isvalid = true
|
||||
conditions.children.forEach(childrenCondition => {
|
||||
if (!conditionsMet(childrenCondition, formData)) {
|
||||
isvalid = false
|
||||
}
|
||||
})
|
||||
return isvalid
|
||||
} else if (conditions.operatorIdentifier === 'or') {
|
||||
let isvalid = false
|
||||
conditions.children.forEach(childrenCondition => {
|
||||
if (conditionsMet(childrenCondition, formData)) {
|
||||
isvalid = true
|
||||
}
|
||||
})
|
||||
return isvalid
|
||||
}
|
||||
|
||||
throw new Error('Unexcepted operatorIdentifier:' + conditions.operatorIdentifier)
|
||||
}
|
||||
|
||||
function propertyConditionMet (propertyCondition, value) {
|
||||
if (!propertyCondition) {
|
||||
return false
|
||||
}
|
||||
switch (propertyCondition.property_meta.type) {
|
||||
case 'text':
|
||||
case 'url':
|
||||
case 'email':
|
||||
case 'phone_number':
|
||||
return textConditionMet(propertyCondition, value)
|
||||
case 'number':
|
||||
return numberConditionMet(propertyCondition, value)
|
||||
case 'checkbox':
|
||||
return checkboxConditionMet(propertyCondition, value)
|
||||
case 'select':
|
||||
return selectConditionMet(propertyCondition, value)
|
||||
case 'date':
|
||||
return dateConditionMet(propertyCondition, value)
|
||||
case 'multi_select':
|
||||
return multiSelectConditionMet(propertyCondition, value)
|
||||
case 'files':
|
||||
return filesConditionMet(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkEquals (condition, fieldValue) {
|
||||
return condition.value === fieldValue
|
||||
}
|
||||
|
||||
function checkContains (condition, fieldValue) {
|
||||
return (fieldValue) ? fieldValue.includes(condition.value) : false
|
||||
}
|
||||
|
||||
function checkListContains (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
|
||||
if (Array.isArray(condition.value)) {
|
||||
return condition.value.every(r => fieldValue.includes(r))
|
||||
} else {
|
||||
return fieldValue.includes(condition.value)
|
||||
}
|
||||
}
|
||||
|
||||
function checkStartsWith (condition, fieldValue) {
|
||||
return fieldValue.startsWith(condition.value)
|
||||
}
|
||||
|
||||
function checkendsWith (condition, fieldValue) {
|
||||
return fieldValue && fieldValue.endsWith(condition.value)
|
||||
}
|
||||
|
||||
function checkIsEmpty (condition, fieldValue) {
|
||||
return (!fieldValue || fieldValue.length === 0)
|
||||
}
|
||||
|
||||
function checkGreaterThan (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && parseFloat(fieldValue) > parseFloat(condition.value))
|
||||
}
|
||||
|
||||
function checkGreaterThanEqual (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && parseFloat(fieldValue) >= parseFloat(condition.value))
|
||||
}
|
||||
|
||||
function checkLessThan (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && parseFloat(fieldValue) < parseFloat(condition.value))
|
||||
}
|
||||
|
||||
function checkLessThanEqual (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && parseFloat(fieldValue) <= parseFloat(condition.value))
|
||||
}
|
||||
|
||||
function checkBefore (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && fieldValue > condition.value)
|
||||
}
|
||||
|
||||
function checkAfter (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && fieldValue < condition.value)
|
||||
}
|
||||
|
||||
function checkOnOrBefore (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && fieldValue >= condition.value)
|
||||
}
|
||||
|
||||
function checkOnOrAfter (condition, fieldValue) {
|
||||
return (condition.value && fieldValue && fieldValue <= condition.value)
|
||||
}
|
||||
|
||||
function checkPastWeek (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7))
|
||||
}
|
||||
|
||||
function checkPastMonth (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()))
|
||||
}
|
||||
|
||||
function checkPastYear (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()))
|
||||
}
|
||||
|
||||
function checkNextWeek (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7))
|
||||
}
|
||||
|
||||
function checkNextMonth (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear(), today.getMonth() + 1, today.getDate()))
|
||||
}
|
||||
|
||||
function checkNextYear (condition, fieldValue) {
|
||||
if (!fieldValue) return false
|
||||
const fieldDate = new Date(fieldValue)
|
||||
const today = new Date()
|
||||
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear() + 1, today.getMonth(), today.getDate()))
|
||||
}
|
||||
|
||||
function checkLength (condition, fieldValue, operator = '===') {
|
||||
if(!fieldValue || fieldValue.length === 0) return false;
|
||||
switch (operator) {
|
||||
case '===':
|
||||
return fieldValue.length === parseInt(condition.value)
|
||||
case '!==':
|
||||
return fieldValue.length !== parseInt(condition.value)
|
||||
case '>':
|
||||
return fieldValue.length > parseInt(condition.value)
|
||||
case '>=':
|
||||
return fieldValue.length >= parseInt(condition.value)
|
||||
case '<':
|
||||
return fieldValue.length < parseInt(condition.value)
|
||||
case '<=':
|
||||
return fieldValue.length <= parseInt(condition.value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function textConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'equals':
|
||||
return checkEquals(propertyCondition, value)
|
||||
case 'does_not_equal':
|
||||
return !checkEquals(propertyCondition, value)
|
||||
case 'contains':
|
||||
return checkContains(propertyCondition, value)
|
||||
case 'does_not_contain':
|
||||
return !checkContains(propertyCondition, value)
|
||||
case 'starts_with':
|
||||
return checkStartsWith(propertyCondition, value)
|
||||
case 'ends_with':
|
||||
return checkendsWith(propertyCondition, value)
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'is_not_empty':
|
||||
return !checkIsEmpty(propertyCondition, value)
|
||||
case 'content_length_equals':
|
||||
return checkLength(propertyCondition, value, '===')
|
||||
case 'content_length_does_not_equal':
|
||||
return checkLength(propertyCondition, value, '!==')
|
||||
case 'content_length_greater_than':
|
||||
return checkLength(propertyCondition, value, '>')
|
||||
case 'content_length_greater_than_or_equal_to':
|
||||
return checkLength(propertyCondition, value, '>=')
|
||||
case 'content_length_less_than':
|
||||
return checkLength(propertyCondition, value, '<')
|
||||
case 'content_length_less_than_or_equal_to':
|
||||
return checkLength(propertyCondition, value, '<=')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function numberConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'equals':
|
||||
return checkEquals(propertyCondition, value)
|
||||
case 'does_not_equal':
|
||||
return !checkEquals(propertyCondition, value)
|
||||
case 'greater_than':
|
||||
return checkGreaterThan(propertyCondition, value)
|
||||
case 'less_than':
|
||||
return checkLessThan(propertyCondition, value)
|
||||
case 'greater_than_or_equal_to':
|
||||
return checkGreaterThanEqual(propertyCondition, value)
|
||||
case 'less_than_or_equal_to':
|
||||
return checkLessThanEqual(propertyCondition, value)
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'is_not_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'content_length_equals':
|
||||
return checkLength(propertyCondition, value, '===')
|
||||
case 'content_length_does_not_equal':
|
||||
return checkLength(propertyCondition, value, '!==')
|
||||
case 'content_length_greater_than':
|
||||
return checkLength(propertyCondition, value, '>')
|
||||
case 'content_length_greater_than_or_equal_to':
|
||||
return checkLength(propertyCondition, value, '>=')
|
||||
case 'content_length_less_than':
|
||||
return checkLength(propertyCondition, value, '<')
|
||||
case 'content_length_less_than_or_equal_to':
|
||||
return checkLength(propertyCondition, value, '<=')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkboxConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'equals':
|
||||
return checkEquals(propertyCondition, value)
|
||||
case 'does_not_equal':
|
||||
return !checkEquals(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function selectConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'equals':
|
||||
return checkEquals(propertyCondition, value)
|
||||
case 'does_not_equal':
|
||||
return !checkEquals(propertyCondition, value)
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'is_not_empty':
|
||||
return !checkIsEmpty(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function dateConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'equals':
|
||||
return checkEquals(propertyCondition, value)
|
||||
case 'before':
|
||||
return checkBefore(propertyCondition, value)
|
||||
case 'after':
|
||||
return checkAfter(propertyCondition, value)
|
||||
case 'on_or_before':
|
||||
return checkOnOrBefore(propertyCondition, value)
|
||||
case 'on_or_after':
|
||||
return checkOnOrAfter(propertyCondition, value)
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'past_week':
|
||||
return checkPastWeek(propertyCondition, value)
|
||||
case 'past_month':
|
||||
return checkPastMonth(propertyCondition, value)
|
||||
case 'past_year':
|
||||
return checkPastYear(propertyCondition, value)
|
||||
case 'next_week':
|
||||
return checkNextWeek(propertyCondition, value)
|
||||
case 'next_month':
|
||||
return checkNextMonth(propertyCondition, value)
|
||||
case 'next_year':
|
||||
return checkNextYear(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function multiSelectConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'contains':
|
||||
return checkListContains(propertyCondition, value)
|
||||
case 'does_not_contain':
|
||||
return !checkListContains(propertyCondition, value)
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'is_not_empty':
|
||||
return !checkIsEmpty(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function filesConditionMet (propertyCondition, value) {
|
||||
switch (propertyCondition.operator) {
|
||||
case 'is_empty':
|
||||
return checkIsEmpty(propertyCondition, value)
|
||||
case 'is_not_empty':
|
||||
return !checkIsEmpty(propertyCondition, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
60
client/lib/forms/FormLogicPropertyResolver.js
vendored
Normal file
60
client/lib/forms/FormLogicPropertyResolver.js
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import { conditionsMet } from './FormLogicConditionChecker'
|
||||
class FormLogicPropertyResolver {
|
||||
conditionsMet = conditionsMet;
|
||||
property = null;
|
||||
formData = null;
|
||||
logic = false;
|
||||
|
||||
constructor (property, formData) {
|
||||
this.property = property
|
||||
this.formData = formData
|
||||
this.logic = (property.logic !== undefined) ? property.logic : false
|
||||
}
|
||||
|
||||
isHidden () {
|
||||
if (!this.logic) {
|
||||
return this.property.hidden
|
||||
}
|
||||
|
||||
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||
if (conditionsMet && this.property.hidden && this.logic.actions.length > 0 && this.logic.actions.includes('show-block')) {
|
||||
return false
|
||||
} else if (conditionsMet && !this.property.hidden && this.logic.actions.length > 0 && this.logic.actions.includes('hide-block')) {
|
||||
return true
|
||||
} else {
|
||||
return this.property.hidden
|
||||
}
|
||||
}
|
||||
|
||||
isRequired () {
|
||||
if (!this.logic) {
|
||||
return this.property.required
|
||||
}
|
||||
|
||||
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||
if (conditionsMet && this.property.required && this.logic.actions.length > 0 && this.logic.actions.includes('make-it-optional')) {
|
||||
return false
|
||||
} else if (conditionsMet && !this.property.required && this.logic.actions.length > 0 && this.logic.actions.includes('require-answer')) {
|
||||
return true
|
||||
} else {
|
||||
return this.property.required
|
||||
}
|
||||
}
|
||||
|
||||
isDisabled () {
|
||||
if (!this.logic) {
|
||||
return this.property.disabled
|
||||
}
|
||||
|
||||
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||
if (conditionsMet && this.property.disabled && this.logic.actions.length > 0 && this.logic.actions.includes('enable-block')) {
|
||||
return false
|
||||
} else if (conditionsMet && !this.property.disabled && this.logic.actions.length > 0 && this.logic.actions.includes('disable-block')) {
|
||||
return true
|
||||
} else {
|
||||
return this.property.disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FormLogicPropertyResolver
|
||||
123
client/lib/forms/FormPropertyLogicRule.js
vendored
Normal file
123
client/lib/forms/FormPropertyLogicRule.js
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
import OpenFilters from '../../data/open_filters.json'
|
||||
class FormPropertyLogicRule {
|
||||
property = null
|
||||
logic = null
|
||||
isConditionCorrect = true
|
||||
isActionCorrect = true
|
||||
ACTIONS_VALUES = [
|
||||
'show-block',
|
||||
'hide-block',
|
||||
'make-it-optional',
|
||||
'require-answer',
|
||||
'enable-block',
|
||||
'disable-block'
|
||||
]
|
||||
CONDITION_MAPPING = OpenFilters
|
||||
|
||||
constructor (property) {
|
||||
this.property = property
|
||||
this.logic = (property.logic !== undefined && property.logic) ? property.logic : null
|
||||
}
|
||||
|
||||
isValid () {
|
||||
if (this.logic && this.logic['conditions']) {
|
||||
this.checkConditions(this.logic['conditions'])
|
||||
this.checkActions((this.logic && this.logic['actions']) ? this.logic['actions'] : null)
|
||||
}
|
||||
|
||||
return this.isConditionCorrect && this.isActionCorrect
|
||||
}
|
||||
|
||||
checkConditions (conditions) {
|
||||
if (conditions && conditions['operatorIdentifier']) {
|
||||
if ((conditions['operatorIdentifier'] !== 'and') && (conditions['operatorIdentifier'] !== 'or')) {
|
||||
this.isConditionCorrect = false
|
||||
return
|
||||
}
|
||||
|
||||
if (conditions['operatorIdentifier']['children'] !== undefined || !Array.isArray(conditions['children'])) {
|
||||
this.isConditionCorrect = false
|
||||
return
|
||||
}
|
||||
|
||||
conditions['children'].forEach(childrenCondition => {
|
||||
this.checkConditions(childrenCondition)
|
||||
})
|
||||
} else if (conditions && conditions['identifier']) {
|
||||
this.checkBaseCondition(conditions)
|
||||
}
|
||||
}
|
||||
|
||||
checkBaseCondition (condition) {
|
||||
if (condition['value'] === undefined ||
|
||||
condition['value']['property_meta'] === undefined ||
|
||||
condition['value']['property_meta']['type'] === undefined ||
|
||||
condition['value']['operator'] === undefined ||
|
||||
condition['value']['value'] === undefined
|
||||
) {
|
||||
this.isConditionCorrect = false
|
||||
return
|
||||
}
|
||||
|
||||
const typeField = condition['value']['property_meta']['type']
|
||||
const operator = condition['value']['operator']
|
||||
const value = condition['value']['value']
|
||||
|
||||
if (this.CONDITION_MAPPING[typeField] === undefined ||
|
||||
this.CONDITION_MAPPING[typeField]['comparators'][operator] === undefined
|
||||
) {
|
||||
this.isConditionCorrect = false
|
||||
return
|
||||
}
|
||||
|
||||
const type = this.CONDITION_MAPPING[typeField]['comparators'][operator]['expected_type']
|
||||
if (Array.isArray(type)) {
|
||||
let foundCorrectType = false
|
||||
type.forEach(subtype => {
|
||||
if (this.valueHasCorrectType(subtype, value)) {
|
||||
foundCorrectType = true
|
||||
}
|
||||
})
|
||||
if (!foundCorrectType) {
|
||||
this.isConditionCorrect = false
|
||||
}
|
||||
} else {
|
||||
if (!this.valueHasCorrectType(type, value)) {
|
||||
this.isConditionCorrect = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valueHasCorrectType (type, value) {
|
||||
if (
|
||||
(type === 'string' && typeof value !== 'string') ||
|
||||
(type === 'boolean' && typeof value !== 'boolean') ||
|
||||
(type === 'number' && typeof value !== 'number') ||
|
||||
(type === 'object' && !Array.isArray(value))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
checkActions (conditions) {
|
||||
if (Array.isArray(conditions) && conditions.length > 0) {
|
||||
conditions.forEach(val => {
|
||||
if (this.ACTIONS_VALUES.indexOf(val) === -1 ||
|
||||
(['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image'].indexOf(this.property["type"]) > -1 && ['hide-block', 'show-block'].indexOf(val) === -1) ||
|
||||
(this.property["hidden"] !== undefined && this.property["hidden"] && ['show-block', 'require-answer'].indexOf(val) === -1) ||
|
||||
(this.property["required"] !== undefined && this.property["required"] && ['make-it-optional', 'hide-block', 'disable-block'].indexOf(val) === -1) ||
|
||||
(this.property["disabled"] !== undefined && this.property["disabled"] && ['enable-block', 'require-answer', 'make-it-optional'].indexOf(val) === -1)
|
||||
) {
|
||||
this.isActionCorrect = false
|
||||
return
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.isActionCorrect = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FormPropertyLogicRule
|
||||
123
client/lib/forms/form-themes.js
vendored
Normal file
123
client/lib/forms/form-themes.js
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
Input classes for each supported form themes
|
||||
*/
|
||||
export const themes = {
|
||||
default: {
|
||||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-4 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg filter hover:brightness-110'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-4 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
button: 'cursor-pointer text-gray-700 inline-block rounded-lg border-gray-300 px-4 py-2 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white hover:bg-gray-50 border',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded-lg',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded-lg shadow-sm max-w-[10rem]'
|
||||
}
|
||||
},
|
||||
simple: {
|
||||
default: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'transition ease-in duration-200 text-center font-semibold focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'relative w-full flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-2 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||
button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||
unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
|
||||
help: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light shadow-sm max-w-[10rem]'
|
||||
}
|
||||
},
|
||||
notion: {
|
||||
default: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
Button: {
|
||||
body: 'rounded-md transition ease-in duration-200 text-center font-semibold shadow shadow-inner-notion focus:outline-none focus:ring-2 focus:ring-offset-2 filter hover:brightness-110'
|
||||
},
|
||||
SelectInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded relative w-full border-transparent flex-1 appearance-none bg-notion-input-background shadow-inner-notion w-full px-2 text-gray-900 placeholder-gray-400 dark:bg-notion-dark-light dark:placeholder-gray-500 text-base focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-focus-notion',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
CodeInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded shadow-inner-notion border border-gray-300 dark:border-gray-600 overflow-hidden',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
RichTextAreaInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
ScaleInput: {
|
||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||
button: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
|
||||
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
|
||||
help: 'text-notion-input-help dark:text-gray-500'
|
||||
},
|
||||
fileInput: {
|
||||
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded bg-notion-input-background',
|
||||
inputHover: {
|
||||
light: 'bg-neutral-50',
|
||||
dark: 'bg-notion-dark-light'
|
||||
},
|
||||
uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded shadow-sm max-w-[10rem]'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
client/lib/forms/public-page.js
vendored
Normal file
55
client/lib/forms/public-page.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Handle form public pages dark mode and transparent mode
|
||||
*/
|
||||
export function handleDarkMode (darkMode, elem = null) {
|
||||
if (process.server) return
|
||||
const darkModeNodeParent = elem ?? document.body
|
||||
|
||||
// Dark mode
|
||||
if (['dark', 'light'].includes(darkMode)) {
|
||||
return handleDarkModeToggle(darkMode === 'dark')
|
||||
}
|
||||
|
||||
// Case auto
|
||||
handleDarkModeToggle(window.matchMedia('(prefers-color-scheme: dark)').matches, darkModeNodeParent)
|
||||
|
||||
// Create listener
|
||||
window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.addEventListener('change', handleDarkModeToggle)
|
||||
}
|
||||
|
||||
function handleDarkModeToggle (enabled, darkModeNodeParent) {
|
||||
if (enabled !== false && enabled !== true) {
|
||||
// if we received an event
|
||||
enabled = enabled.matches
|
||||
}
|
||||
enabled ? darkModeNodeParent.classList.add('dark') : darkModeNodeParent.classList.remove('dark')
|
||||
}
|
||||
|
||||
export function disableDarkMode () {
|
||||
if (process.server) return
|
||||
const body = document.body
|
||||
body.classList.remove('dark')
|
||||
// Remove event listener
|
||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', handleDarkModeToggle)
|
||||
}
|
||||
|
||||
export function handleTransparentMode (transparentModeEnabled) {
|
||||
if (process.server) return
|
||||
if (!useIsIframe() || !transparentModeEnabled) return
|
||||
|
||||
const app = document.getElementById('app')
|
||||
app.classList.remove('bg-white')
|
||||
app.classList.remove('dark:bg-notion-dark')
|
||||
app.classList.add('bg-transparent')
|
||||
}
|
||||
|
||||
export function focusOnFirstFormElement() {
|
||||
if (process.server) return
|
||||
for (const ele of document.querySelectorAll('input,button,textarea,[role="button"]')) {
|
||||
if (ele.offsetWidth !== 0 || ele.offsetHeight !== 0) {
|
||||
ele.focus()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
76
client/lib/utils.js
vendored
Normal file
76
client/lib/utils.js
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
export const hash = (str, seed = 0) => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Url and domain related utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the appUrl with the given path appended.
|
||||
* @param path
|
||||
* @returns {string}
|
||||
*/
|
||||
export const appUrl = (path = '/') => {
|
||||
let baseUrl = useRuntimeConfig().public.appUrl
|
||||
if (!baseUrl) {
|
||||
console.warn('appUrl not set in runtimeConfig')
|
||||
return path
|
||||
}
|
||||
|
||||
if (path.startsWith('/')) {
|
||||
path = path.substring(1)
|
||||
}
|
||||
|
||||
if (!baseUrl.endsWith('/')) {
|
||||
baseUrl += '/'
|
||||
}
|
||||
|
||||
return baseUrl + path
|
||||
}
|
||||
|
||||
/**
|
||||
* SSR compatible function to get current host
|
||||
* @param path
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getHost = function () {
|
||||
if (process.server) {
|
||||
return getDomain(useNuxtApp().ssrContext?.event.context.siteConfigNitroOrigin) || useNuxtApp().ssrContext?.event.node.req.headers.host
|
||||
} else {
|
||||
return window.location.host
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract domain from url
|
||||
* @param url
|
||||
* @returns {*}
|
||||
*/
|
||||
export const getDomain = function (url) {
|
||||
if (url.includes('localhost')) return 'localhost'
|
||||
return (new URL(url)).hostname
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the app is running on a custom domain, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const customDomainUsed = function() {
|
||||
const config = useRuntimeConfig()
|
||||
const appDomain = getDomain(config.public.appUrl)
|
||||
const host = getHost()
|
||||
|
||||
return host !== appDomain && getDomain(host) !== appDomain
|
||||
}
|
||||
Reference in New Issue
Block a user