Merge from docusealco/wip
This commit is contained in:
@@ -33,9 +33,15 @@ module Api
|
|||||||
else
|
else
|
||||||
http_cache_forever public: true do
|
http_cache_forever public: true do
|
||||||
response.headers['Accept-Ranges'] = 'bytes'
|
response.headers['Accept-Ranges'] = 'bytes'
|
||||||
response.headers['Content-Length'] = blob.byte_size.to_s
|
|
||||||
|
|
||||||
send_blob_stream blob, disposition: params[:disposition]
|
if request.head?
|
||||||
|
response.headers['Content-Type'] = blob.content_type_for_serving
|
||||||
|
head :ok
|
||||||
|
else
|
||||||
|
send_blob_stream blob, disposition: params[:disposition]
|
||||||
|
end
|
||||||
|
|
||||||
|
response.headers['Content-Length'] = blob.byte_size.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -57,8 +63,6 @@ module Api
|
|||||||
return if !require_ttl && !require_auth
|
return if !require_ttl && !require_auth
|
||||||
end
|
end
|
||||||
|
|
||||||
Rollbar.error('Blob unauthorized') if defined?(Rollbar)
|
|
||||||
|
|
||||||
raise CanCan::AccessDenied
|
raise CanCan::AccessDenied
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -203,10 +203,15 @@ module Api
|
|||||||
|
|
||||||
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
|
||||||
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
|
||||||
|
|
||||||
if submitter_preferences.key?('require_phone_2fa')
|
if submitter_preferences.key?('require_phone_2fa')
|
||||||
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
|
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if submitter_preferences.key?('require_email_2fa')
|
||||||
|
submitter.preferences['require_email_2fa'] = submitter_preferences['require_email_2fa']
|
||||||
|
end
|
||||||
|
|
||||||
if submitter_preferences.key?('go_to_last')
|
if submitter_preferences.key?('go_to_last')
|
||||||
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PreviewDocumentPageController < ActionController::API
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_document_tempfile_path(attachment)
|
def find_or_create_document_tempfile_path(attachment)
|
||||||
file_path = "#{Dir.tmpdir}/#{attachment.uuid}"
|
file_path = "#{Dir.tmpdir}/attachment-#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}"
|
||||||
|
|
||||||
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
|
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
|
||||||
f.flock(File::LOCK_EX)
|
f.flock(File::LOCK_EX)
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RevealAccessTokenController < ApplicationController
|
class RevealAccessTokenController < ApplicationController
|
||||||
|
rate_limit to: 4, within: 1.minute, only: %i[create], by: -> { current_user.id }, with: lambda {
|
||||||
|
Rollbar.error('Rate limit api key') if defined?(Rollbar)
|
||||||
|
|
||||||
|
render turbo_stream: turbo_stream.replace(:modal, template: 'reveal_access_token/show',
|
||||||
|
locals: { error_message: I18n.t(:too_many_attempts) }),
|
||||||
|
status: :unprocessable_content
|
||||||
|
}
|
||||||
|
|
||||||
def show
|
def show
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
template = Template.find_by!(slug: params[:template_slug])
|
template = Template.find_by!(slug: params[:template_slug])
|
||||||
|
|
||||||
@submitter =
|
@submitter =
|
||||||
Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase)
|
Submitter.completed.where(submission: template.submissions).find_by(email: params[:email].to_s.downcase)
|
||||||
elsif params[:submission_slug]
|
elsif params[:submission_slug]
|
||||||
submission = Submission.find_by(slug: params[:submission_slug])
|
submission = Submission.find_by(slug: params[:submission_slug])
|
||||||
|
|
||||||
@@ -27,9 +27,11 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
if @submitter
|
||||||
|
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
|
||||||
|
|
||||||
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! if can_send?(@submitter)
|
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! if can_send?(@submitter)
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html { render :success }
|
f.html { render :success }
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TemplatesController < ApplicationController
|
class TemplatesController < ApplicationController
|
||||||
|
TEMPLATE_FIELDS = %i[id author_id folder_id external_id name slug
|
||||||
|
schema fields submitters variables_schema preferences
|
||||||
|
shared_link source archived_at created_at updated_at].freeze
|
||||||
|
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -33,10 +37,11 @@ class TemplatesController < ApplicationController
|
|||||||
).call
|
).call
|
||||||
|
|
||||||
@template_data =
|
@template_data =
|
||||||
@template.as_json.merge(
|
@template.as_json(only: TEMPLATE_FIELDS).merge(
|
||||||
documents: @template.schema_documents.as_json(
|
documents: @template.schema_documents.as_json(
|
||||||
|
only: %i[id uuid],
|
||||||
methods: %i[metadata signed_key],
|
methods: %i[metadata signed_key],
|
||||||
include: { preview_images: { methods: %i[url metadata filename] } }
|
include: { preview_images: { only: %i[id], methods: %i[url metadata filename] } }
|
||||||
)
|
)
|
||||||
).to_json
|
).to_json
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class TestingAccountsController < ApplicationController
|
class TestingAccountsController < ApplicationController
|
||||||
skip_authorization_check only: :destroy
|
skip_authorization_check only: :destroy
|
||||||
|
|
||||||
def show
|
def create
|
||||||
authorize!(:manage, current_account)
|
authorize!(:manage, current_account)
|
||||||
authorize!(:manage, current_user)
|
authorize!(:manage, current_user)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.canvas) return
|
if (!this.canvas) return
|
||||||
|
if (!this.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.canvas
|
const { width, height } = this.canvas
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && this.canvas.width > 0 && this.canvas.height > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.canvas) return
|
if (!this.canvas) return
|
||||||
|
if (!this.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.canvas
|
const { width, height } = this.canvas
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && this.canvas.width > 0 && this.canvas.height > 0) {
|
||||||
const sx = this.canvas.width / oldWidth
|
const sx = this.canvas.width / oldWidth
|
||||||
const sy = this.canvas.height / oldHeight
|
const sy = this.canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,25 @@ export default {
|
|||||||
const areas = {}
|
const areas = {}
|
||||||
|
|
||||||
this.field.areas?.forEach((area) => {
|
this.field.areas?.forEach((area) => {
|
||||||
areas[area.attachment_uuid + area.page] ||= area
|
areas[area.attachment_uuid] ||= []
|
||||||
|
areas[area.attachment_uuid].push(area)
|
||||||
})
|
})
|
||||||
|
|
||||||
return Object.values(areas).slice(0, 6)
|
const sortedAreas = Object.values(areas).reduce((acc, group) => {
|
||||||
|
const seen = {}
|
||||||
|
const sortedGroup = [...group].sort((a, b) => a.page - b.page)
|
||||||
|
|
||||||
|
sortedGroup.forEach((area) => {
|
||||||
|
if (!seen[area.page]) {
|
||||||
|
seen[area.page] = true
|
||||||
|
acc.push(area)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return sortedAreas.slice(0, 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -544,6 +544,7 @@ export default {
|
|||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.$refs.canvas) return
|
if (!this.$refs.canvas) return
|
||||||
|
if (!this.$refs.canvas.parentNode?.clientWidth) return
|
||||||
|
|
||||||
const { width, height } = this.$refs.canvas
|
const { width, height } = this.$refs.canvas
|
||||||
|
|
||||||
@@ -586,7 +587,7 @@ export default {
|
|||||||
redrawCanvas (oldWidth, oldHeight) {
|
redrawCanvas (oldWidth, oldHeight) {
|
||||||
const canvas = this.$refs.canvas
|
const canvas = this.$refs.canvas
|
||||||
|
|
||||||
if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) {
|
if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0 && canvas.width > 0 && canvas.height > 0) {
|
||||||
const sx = canvas.width / oldWidth
|
const sx = canvas.width / oldWidth
|
||||||
const sy = canvas.height / oldHeight
|
const sy = canvas.height / oldHeight
|
||||||
|
|
||||||
|
|||||||
@@ -1012,6 +1012,13 @@ export default {
|
|||||||
fieldsDragFieldRef: () => ref(),
|
fieldsDragFieldRef: () => ref(),
|
||||||
customDragFieldRef: () => ref(),
|
customDragFieldRef: () => ref(),
|
||||||
selectedAreasRef: () => ref([]),
|
selectedAreasRef: () => ref([]),
|
||||||
|
attachmentUuidsIndex () {
|
||||||
|
return this.template.schema.reduce((acc, e, index) => {
|
||||||
|
acc[e.attachment_uuid] = index
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
language () {
|
language () {
|
||||||
return this.locale.split('-')[0].toLowerCase()
|
return this.locale.split('-')[0].toLowerCase()
|
||||||
},
|
},
|
||||||
@@ -1663,33 +1670,26 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
compareAreas (a, b) {
|
||||||
|
const aAttIdx = this.attachmentUuidsIndex[a.attachment_uuid]
|
||||||
|
const bAttIdx = this.attachmentUuidsIndex[b.attachment_uuid]
|
||||||
|
|
||||||
|
if (aAttIdx !== bAttIdx) return aAttIdx - bAttIdx
|
||||||
|
if (a.page !== b.page) return a.page - b.page
|
||||||
|
|
||||||
|
const aY = a.y + a.h
|
||||||
|
const bY = b.y + b.h
|
||||||
|
|
||||||
|
if (Math.abs(aY - bY) < 0.01) return a.x - b.x
|
||||||
|
if (a.h < b.h ? a.y >= b.y && aY <= bY : b.y >= a.y && bY <= aY) return a.x - b.x
|
||||||
|
|
||||||
|
return aY - bY
|
||||||
|
},
|
||||||
findFieldInsertIndex (field) {
|
findFieldInsertIndex (field) {
|
||||||
if (!field.areas?.length) return -1
|
if (!field.areas?.length) return -1
|
||||||
|
|
||||||
const area = field.areas[0]
|
const area = field.areas[0]
|
||||||
|
|
||||||
const attachmentUuidsIndex = this.template.schema.reduce((acc, e, index) => {
|
|
||||||
acc[e.attachment_uuid] = index
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const compareAreas = (a, b) => {
|
|
||||||
const aAttIdx = attachmentUuidsIndex[a.attachment_uuid]
|
|
||||||
const bAttIdx = attachmentUuidsIndex[b.attachment_uuid]
|
|
||||||
|
|
||||||
if (aAttIdx !== bAttIdx) return aAttIdx - bAttIdx
|
|
||||||
if (a.page !== b.page) return a.page - b.page
|
|
||||||
|
|
||||||
const aY = a.y + a.h
|
|
||||||
const bY = b.y + b.h
|
|
||||||
|
|
||||||
if (Math.abs(aY - bY) < 0.01) return a.x - b.x
|
|
||||||
if (a.h < b.h ? a.y >= b.y && aY <= bY : b.y >= a.y && bY <= aY) return a.x - b.x
|
|
||||||
|
|
||||||
return aY - bY
|
|
||||||
}
|
|
||||||
|
|
||||||
let closestBeforeIndex = -1
|
let closestBeforeIndex = -1
|
||||||
let closestBeforeArea = null
|
let closestBeforeArea = null
|
||||||
let closestAfterIndex = -1
|
let closestAfterIndex = -1
|
||||||
@@ -1698,15 +1698,15 @@ export default {
|
|||||||
this.template.fields.forEach((f, index) => {
|
this.template.fields.forEach((f, index) => {
|
||||||
if (f.submitter_uuid === field.submitter_uuid) {
|
if (f.submitter_uuid === field.submitter_uuid) {
|
||||||
(f.areas || []).forEach((a) => {
|
(f.areas || []).forEach((a) => {
|
||||||
const cmp = compareAreas(a, area)
|
const cmp = this.compareAreas(a, area)
|
||||||
|
|
||||||
if (cmp < 0) {
|
if (cmp < 0) {
|
||||||
if (!closestBeforeArea || (compareAreas(a, closestBeforeArea) > 0 && closestBeforeIndex < index)) {
|
if (!closestBeforeArea || (this.compareAreas(a, closestBeforeArea) > 0 && closestBeforeIndex < index)) {
|
||||||
closestBeforeIndex = index
|
closestBeforeIndex = index
|
||||||
closestBeforeArea = a
|
closestBeforeArea = a
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!closestAfterArea || (compareAreas(a, closestAfterArea) < 0 && closestAfterIndex > index)) {
|
if (!closestAfterArea || (this.compareAreas(a, closestAfterArea) < 0 && closestAfterIndex > index)) {
|
||||||
closestAfterIndex = index
|
closestAfterIndex = index
|
||||||
closestAfterArea = a
|
closestAfterArea = a
|
||||||
}
|
}
|
||||||
@@ -1729,6 +1729,17 @@ export default {
|
|||||||
this.template.fields.push(field)
|
this.template.fields.push(field)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
insertArea (field, area) {
|
||||||
|
field.areas ||= []
|
||||||
|
|
||||||
|
const insertIndex = field.areas.findIndex((a) => this.compareAreas(a, area) > 0)
|
||||||
|
|
||||||
|
if (insertIndex === -1) {
|
||||||
|
field.areas.push(area)
|
||||||
|
} else {
|
||||||
|
field.areas.splice(insertIndex, 0, area)
|
||||||
|
}
|
||||||
|
},
|
||||||
insertDetectedField (field) {
|
insertDetectedField (field) {
|
||||||
if (!this.withDetectExistingFields || !field.name) {
|
if (!this.withDetectExistingFields || !field.name) {
|
||||||
this.insertField(field)
|
this.insertField(field)
|
||||||
@@ -1744,7 +1755,7 @@ export default {
|
|||||||
|
|
||||||
if (existingField) {
|
if (existingField) {
|
||||||
existingField.areas = existingField.areas || []
|
existingField.areas = existingField.areas || []
|
||||||
existingField.areas.push(...(field.areas || []))
|
field.areas.forEach((area) => this.insertArea(existingField, area))
|
||||||
} else {
|
} else {
|
||||||
const customField = this.detectCustomFieldsIndex[indexKey] || this.detectCustomFieldsIndex[nameKey]
|
const customField = this.detectCustomFieldsIndex[indexKey] || this.detectCustomFieldsIndex[nameKey]
|
||||||
|
|
||||||
@@ -2249,7 +2260,7 @@ export default {
|
|||||||
|
|
||||||
fieldUuidIndex[field.uuid] = newField
|
fieldUuidIndex[field.uuid] = newField
|
||||||
|
|
||||||
newField.areas.push(newArea)
|
this.insertArea(newField, newArea)
|
||||||
newAreas.push(newArea)
|
newAreas.push(newArea)
|
||||||
|
|
||||||
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
||||||
@@ -2362,17 +2373,7 @@ export default {
|
|||||||
area.y -= area.h / 2
|
area.y -= area.h / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawField.areas ||= []
|
this.insertArea(this.drawField, area)
|
||||||
|
|
||||||
const insertBeforeAreaIndex = this.drawField.areas.findIndex((a) => {
|
|
||||||
return a.attachment_uuid === area.attachment_uuid && a.page > area.page
|
|
||||||
})
|
|
||||||
|
|
||||||
if (insertBeforeAreaIndex !== -1) {
|
|
||||||
this.drawField.areas.splice(insertBeforeAreaIndex, 0, area)
|
|
||||||
} else {
|
|
||||||
this.drawField.areas.push(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.template.fields.indexOf(this.drawField) === -1) {
|
if (this.template.fields.indexOf(this.drawField) === -1) {
|
||||||
this.insertField(this.drawField)
|
this.insertField(this.drawField)
|
||||||
@@ -2513,9 +2514,7 @@ export default {
|
|||||||
delete field.height
|
delete field.height
|
||||||
}
|
}
|
||||||
|
|
||||||
field.areas ||= []
|
this.insertArea(field, fieldArea)
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
|
||||||
|
|
||||||
if (this.selectedAreasRef.value.length < 2) {
|
if (this.selectedAreasRef.value.length < 2) {
|
||||||
this.selectedAreasRef.value = [fieldArea]
|
this.selectedAreasRef.value = [fieldArea]
|
||||||
@@ -2585,7 +2584,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field.areas.push(fieldArea)
|
this.insertArea(field, fieldArea)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const fieldArea = {
|
const fieldArea = {
|
||||||
|
|||||||
@@ -786,17 +786,27 @@ export default {
|
|||||||
},
|
},
|
||||||
copyToAllPages (field) {
|
copyToAllPages (field) {
|
||||||
const areaString = JSON.stringify(field.areas[0])
|
const areaString = JSON.stringify(field.areas[0])
|
||||||
|
const newAreas = []
|
||||||
|
const existingAreasIndex = field.areas.reduce((acc, area) => {
|
||||||
|
acc[`${area.attachment_uuid}-${area.page}`] = area
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
this.template.schema.forEach((item) => {
|
||||||
|
const attachment = this.template.documents.find((d) => d.uuid === item.attachment_uuid)
|
||||||
|
|
||||||
this.template.documents.forEach((attachment) => {
|
|
||||||
const numberOfPages = attachment.metadata?.pdf?.number_of_pages || attachment.preview_images.length
|
const numberOfPages = attachment.metadata?.pdf?.number_of_pages || attachment.preview_images.length
|
||||||
|
|
||||||
for (let page = 0; page <= numberOfPages - 1; page++) {
|
for (let page = 0; page <= numberOfPages - 1; page++) {
|
||||||
if (!field.areas.find((area) => area.attachment_uuid === attachment.uuid && area.page === page)) {
|
const existing = existingAreasIndex[`${attachment.uuid}-${page}`]
|
||||||
field.areas.push({ ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page })
|
|
||||||
}
|
newAreas.push(existing || { ...JSON.parse(areaString), attachment_uuid: attachment.uuid, page })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
field.areas = newAreas
|
||||||
|
|
||||||
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
this.$emit('scroll-to', this.field.areas[this.field.areas.length - 1])
|
||||||
|
|
||||||
this.$emit('save')
|
this.$emit('save')
|
||||||
|
|||||||
@@ -143,8 +143,8 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name="state"
|
name="oauth_data"
|
||||||
:value="oauthState"
|
:value="oauthData"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -334,7 +334,7 @@ export default {
|
|||||||
authenticityToken () {
|
authenticityToken () {
|
||||||
return document.querySelector('meta[name="csrf-token"]')?.content
|
return document.querySelector('meta[name="csrf-token"]')?.content
|
||||||
},
|
},
|
||||||
oauthState () {
|
oauthData () {
|
||||||
const params = new URLSearchParams('')
|
const params = new URLSearchParams('')
|
||||||
|
|
||||||
params.set('redir', document.location.href)
|
params.set('redir', document.location.href)
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export default {
|
|||||||
'https://www.googleapis.com/auth/userinfo.email',
|
'https://www.googleapis.com/auth/userinfo.email',
|
||||||
'https://www.googleapis.com/auth/drive.file'
|
'https://www.googleapis.com/auth/drive.file'
|
||||||
].join(' '),
|
].join(' '),
|
||||||
state: new URLSearchParams({
|
oauth_data: new URLSearchParams({
|
||||||
redir: `/templates/${this.templateId}/edit?google_drive_open=1`
|
redir: `/templates/${this.templateId}/edit?google_drive_open=1`
|
||||||
}).toString()
|
}).toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,13 +101,18 @@ class Submission < ApplicationRecord
|
|||||||
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
.and(Submitter.arel_table[:declined_at].not_eq(nil))).select(1).arel.exists)
|
||||||
}
|
}
|
||||||
scope :expired, -> { pending.where(expire_at: ..Time.current) }
|
scope :expired, lambda {
|
||||||
|
where(expire_at: ..Time.current)
|
||||||
|
.where(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
|
||||||
|
.and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists)
|
||||||
|
}
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
invite: 'invite',
|
invite: 'invite',
|
||||||
bulk: 'bulk',
|
bulk: 'bulk',
|
||||||
api: 'api',
|
api: 'api',
|
||||||
embed: 'embed',
|
embed: 'embed',
|
||||||
|
mcp: 'mcp',
|
||||||
link: 'link'
|
link: 'link'
|
||||||
}, scope: false, prefix: true
|
}, scope: false, prefix: true
|
||||||
|
|
||||||
|
|||||||
@@ -18,24 +18,7 @@
|
|||||||
<%= f.button button_title(title: t(:sign_in), disabled_with: t(:signing_in)), class: 'base-button' %>
|
<%= f.button button_title(title: t(:sign_in), disabled_with: t(:signing_in)), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if devise_mapping.omniauthable? %>
|
<%= render 'omniauthable' %>
|
||||||
<div class="space-y-4">
|
|
||||||
<% if User.omniauth_providers.include?(:google_oauth2) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :google_oauth2), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query %>
|
|
||||||
<%= f.button button_title(title: t('sign_in_with_google'), icon: svg_icon('brand_google', class: 'w-6 h-6')), class: 'white-button w-full mt-4' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if User.omniauth_providers.include?(:microsoft_office365) %>
|
|
||||||
<%= form_for '', url: omniauth_authorize_path(resource_name, :microsoft_office365), data: { turbo: false }, method: :post do |f| %>
|
|
||||||
<set-timezone data-input-id="state_microsoft" data-params="true"></set-timezone>
|
|
||||||
<%= hidden_field_tag :state, { redir: params[:redir].to_s }.compact_blank.to_query, id: 'state_microsoft' %>
|
|
||||||
<%= f.button button_title(title: t('sign_in_with_microsoft'), icon: svg_icon('brand_microsoft', class: 'w-6 h-6')), class: 'white-button w-full' %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= render 'extra_links' %>
|
<%= render 'extra_links' %>
|
||||||
<%= render 'devise/shared/links' %>
|
<%= render 'devise/shared/links' %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,9 +20,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<% if params[:modal].present? %>
|
<% if params[:modal].present? %>
|
||||||
<% url_params = Rails.application.routes.recognize_path(params[:modal], method: :get) %>
|
<% modal_uri = Addressable::URI.parse(params[:modal]) %>
|
||||||
|
<% url_params = Rails.application.routes.recognize_path(modal_uri.path, method: :get) %>
|
||||||
<% if url_params[:action] == 'new' %>
|
<% if url_params[:action] == 'new' %>
|
||||||
<open-modal src="<%= url_for(url_params) %>"></open-modal>
|
<open-modal src="<%= url_for(**url_params, params: modal_uri.query_values) %>"></open-modal>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<turbo-frame id="modal"></turbo-frame>
|
<turbo-frame id="modal"></turbo-frame>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
<a target="_blank" href="<%= Docuseal::GITHUB_URL %>" rel="noopener noreferrer nofollow" class="relative flex items-center rounded-full px-2 py-0.5 text-xs leading-4 mt-1 text-base-content border border-base-300 tooltip tooltip-bottom" data-tip="Give a star on GitHub">
|
||||||
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
<span class="flex items-center justify-between space-x-0.5 font-medium">
|
||||||
<%= svg_icon('start', class: 'h-3 w-3') %>
|
<%= svg_icon('start', class: 'h-3 w-3') %>
|
||||||
<span>11k</span>
|
<span>12k</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full py-1' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full py-1' } do |f| %>
|
||||||
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
<label class="flex items-center pl-6 pr-4 py-2 border-y border-base-300 -ml-2 -mr-2" for="testing_toggle">
|
||||||
<submit-form data-on="change" class="flex">
|
<submit-form data-on="change" class="flex">
|
||||||
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, style: 'height: 0.885rem; width: 1.35rem; --handleoffset: 0.395rem; margin-left: -2px; margin-right: 8px' %>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'shared/settings_nav_extra2' %>
|
<%= render 'shared/settings_nav_extra2' %>
|
||||||
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
<% if (can?(:manage, EncryptedConfig) && current_user == true_user) || (current_user != true_user && current_account.testing?) %>
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'w-full' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'w-full' } do |f| %>
|
||||||
<li>
|
<li>
|
||||||
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
|
||||||
<span class="mr-2 w-full">
|
<span class="mr-2 w-full">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
<% if can?(:manage, EncryptedConfig) || (current_user != true_user && current_account.testing?) %>
|
||||||
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
|
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :post, html: { class: 'flex' } do |f| %>
|
||||||
<label class="flex items-center justify-between" for="testing_toggle">
|
<label class="flex items-center justify-between" for="testing_toggle">
|
||||||
<span class="mr-2 text-lg">
|
<span class="mr-2 text-lg">
|
||||||
<%= t('test_mode') %>
|
<%= t('test_mode') %>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<% font = field.dig('preferences', 'font') %>
|
<% font = field.dig('preferences', 'font') %>
|
||||||
<% font_type = field.dig('preferences', 'font_type') %>
|
<% font_type = field.dig('preferences', 'font_type') %>
|
||||||
<% font_size_px = (field.dig('preferences', 'font_size').presence || Submissions::GenerateResultAttachments::FONT_SIZE).to_i * local_assigns.fetch(:font_scale) { 1000.0 / PdfUtils::US_LETTER_W } %>
|
<% font_size_px = (field.dig('preferences', 'font_size').presence || Submissions::GenerateResultAttachments::FONT_SIZE).to_i * local_assigns.fetch(:font_scale) { 1000.0 / PdfUtils::US_LETTER_W } %>
|
||||||
<field-value dir="auto" aria-hidden="true" class="flex absolute <%= 'font-courier' if font == 'Courier' %> <%= 'font-times' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? %><%= "background: #{bg_color}; " if bg_color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; font-size: <%= fs = "clamp(1pt, #{font_size_px / 10}vw, #{font_size_px}px)" %>; line-height: calc(<%= fs %> * 1.3); font-size: <%= fs = "#{font_size_px / 10}cqmin" %>; line-height: calc(<%= fs %> * 1.3)">
|
<field-value dir="auto" aria-hidden="true" class="flex absolute <%= 'font-courier' if font == 'Courier' %> <%= 'font-times' if font == 'Times' %> <%= 'font-bold' if font_type == 'bold' || font_type == 'bold_italic' %> <%= 'italic' if font_type == 'italic' || font_type == 'bold_italic' %> <%= align == 'right' ? 'text-right' : (align == 'center' ? 'text-center' : '') %>" style="<%= "color: #{color}; " if color.present? && color.match?(Templates::COLOR_REGEXP) %><%= "background: #{bg_color}; " if bg_color.present? && bg_color.match?(Templates::COLOR_REGEXP) %>width: <%= area['w'].to_f * 100 %>%; height: <%= area['h'].to_f * 100 %>%; left: <%= area['x'].to_f * 100 %>%; top: <%= area['y'].to_f * 100 %>%; font-size: <%= fs = "clamp(1pt, #{font_size_px / 10}vw, #{font_size_px}px)" %>; line-height: calc(<%= fs %> * 1.3); font-size: <%= fs = "#{font_size_px / 10}cqmin" %>; line-height: calc(<%= fs %> * 1.3)">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<% is_narrow = area['h'].positive? && ((area['w'] * local_assigns[:page_width]).to_f / (area['h'] * local_assigns[:page_height])) > 4.5 %>
|
<% is_narrow = area['h'].to_f.positive? && ((area['w'].to_f * local_assigns[:page_width]) / (area['h'].to_f * local_assigns[:page_height])) > 4.5 %>
|
||||||
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'flex-row' : 'flex-col' %>">
|
<div class="flex justify-between w-full h-full gap-1 <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'flex-row' : 'flex-col' %>">
|
||||||
<div class="flex overflow-hidden <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
<div class="flex overflow-hidden <%= is_narrow && (local_assigns[:with_signature_id] || field.dig('preferences', 'reason_field_uuid').present?) ? 'w-1/2' : 'flex-grow' %>" style="min-height: 50%">
|
||||||
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
<img class="object-contain mx-auto" src="<%= attachments_index[value].url %>" alt="<%= field['name'].presence || field['title'].presence || field['type'] %>">
|
||||||
@@ -62,9 +62,9 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% elsif field['type'] == 'cells' && area['cell_w'].to_f > 0.0 %>
|
<% elsif field['type'] == 'cells' && area['cell_w'].to_f > 0.0 %>
|
||||||
<% cell_width = area['cell_w'] / area['w'] * 100 %>
|
<% cell_width = area['cell_w'].to_f / area['w'] * 100 %>
|
||||||
<div class="w-full flex <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %> <%= 'justify-end' if align == 'right' %>">
|
<div class="w-full flex <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %> <%= 'justify-end' if align == 'right' %>">
|
||||||
<% (0..(area['w'] / area['cell_w']).ceil).each do |index| %>
|
<% (0..(area['w'].to_f / area['cell_w']).ceil).each do |index| %>
|
||||||
<% if value[index] %>
|
<% if value[index] %>
|
||||||
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
<div class="text-center flex-none" style="width: <%= cell_width %>%;"><%= value[index] %></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -83,14 +83,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'strikethrough' %>
|
<% elsif field['type'] == 'strikethrough' %>
|
||||||
<div class="w-full h-full flex items-center justify-center">
|
<div class="w-full h-full flex items-center justify-center">
|
||||||
<% if (((1000.0 / local_assigns[:page_width]) * local_assigns[:page_height]) * area['h']) < 40 %>
|
<% if (((1000.0 / local_assigns[:page_width]) * local_assigns[:page_height]) * area['h'].to_f) < 40 %>
|
||||||
<svg width="100%" height="100%">
|
<svg width="100%" height="100%">
|
||||||
<line x1="0" y1="50%" x2="100%" y2="50%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
<line x1="0" y1="50%" x2="100%" y2="50%" stroke="<%= color.present? && color.match?(Templates::COLOR_REGEXP) ? color : 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
||||||
</svg>
|
</svg>
|
||||||
<% else %>
|
<% else %>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="overflow: visible; width: calc(100% - 6px); height: calc(100% - 6px); width: calc(100% - 0.6cqmin); height: calc(100% - 0.6cqmin)">
|
<svg xmlns="http://www.w3.org/2000/svg" style="overflow: visible; width: calc(100% - 6px); height: calc(100% - 6px); width: calc(100% - 0.6cqmin); height: calc(100% - 0.6cqmin)">
|
||||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="<%= color.present? && color.match?(Templates::COLOR_REGEXP) ? color : 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
||||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="<%= field.dig('preferences', 'color').presence || 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="<%= color.present? && color.match?(Templates::COLOR_REGEXP) ? color : 'red' %>" style="stroke-width: clamp(0px, 0.5vw, 6px); stroke-width: 0.6cqmin"></line>
|
||||||
</svg>
|
</svg>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
<% submitters_order_index ||= (@submission.template_submitters || @submission.template.submitters).each_with_index.to_h { |s, i| [s['uuid'], i] } %>
|
<% submitters_order_index ||= (@submission.template_submitters || @submission.template.submitters).each_with_index.to_h { |s, i| [s['uuid'], i] } %>
|
||||||
<% submitter_index = submitters_order_index[submitter.uuid] %>
|
<% submitter_index = submitters_order_index[submitter.uuid] %>
|
||||||
<% bg_class = bg_classes[submitter_index % bg_classes.size] %>
|
<% bg_class = bg_classes[submitter_index % bg_classes.size] %>
|
||||||
<div class="absolute overflow-visible" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%;">
|
<div class="absolute overflow-visible" style="width: <%= area['w'].to_f * 100 %>%; height: <%= area['h'].to_f * 100 %>%; left: <%= area['x'].to_f * 100 %>%; top: <%= area['y'].to_f * 100 %>%;">
|
||||||
<div class="flex h-full w-full bg-opacity-80 justify-center items-center <%= bg_class %>">
|
<div class="flex h-full w-full bg-opacity-80 justify-center items-center <%= bg_class %>">
|
||||||
<%= svg_icon(SubmissionsController::FIELD_ICONS.fetch(field['type'], 'text_size'), class: 'max-h-10 w-full h-full stroke-2 opacity-50') %>
|
<%= svg_icon(SubmissionsController::FIELD_ICONS.fetch(field['type'], 'text_size'), class: 'max-h-10 w-full h-full stroke-2 opacity-50') %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,9 +25,11 @@
|
|||||||
<div class="flex gap-2 mt-3">
|
<div class="flex gap-2 mt-3">
|
||||||
<div class="relative flex-grow">
|
<div class="relative flex-grow">
|
||||||
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug, host: form_link_host) %>" class="base-input w-full pr-10" autocomplete="off" readonly>
|
<input id="embedding_url" type="text" value="<%= start_form_url(slug: @template.slug, host: form_link_host) %>" class="base-input w-full pr-10" autocomplete="off" readonly>
|
||||||
<a href="<%= template_share_link_qr_path(@template) %>" target="_blank" rel="noopener" class="absolute top-1/2 -translate-y-1/2 right-2 flex items-center justify-center tooltip text-base-content/70 hover:text-base-content bg-white rounded px-1 py-0.5" data-tip="<%= t('qr_code') %>" aria-label="<%= t('qr_code') %>">
|
<% if @template.variables_schema.blank? && !@template.preferences&.dig('require_email_2fa') && !@template.preferences&.dig('require_phone_2fa') %>
|
||||||
<%= svg_icon('qrcode', class: 'w-6 h-6') %>
|
<a href="<%= template_share_link_qr_path(@template) %>" target="_blank" rel="noopener" class="absolute top-1/2 -translate-y-1/2 right-2 flex items-center justify-center tooltip text-base-content/70 hover:text-base-content bg-white rounded px-1 py-0.5" data-tip="<%= t('qr_code') %>" aria-label="<%= t('qr_code') %>">
|
||||||
</a>
|
<%= svg_icon('qrcode', class: 'w-6 h-6') %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<check-on-click data-element-id="template_shared_link">
|
<check-on-click data-element-id="template_shared_link">
|
||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug, host: form_link_host), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug, host: form_link_host), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
|
|||||||
@@ -942,6 +942,7 @@ en: &en
|
|||||||
embed: Embedding
|
embed: Embedding
|
||||||
invite: Invite
|
invite: Invite
|
||||||
link: Link
|
link: Link
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email bounced</b> %{submitter_name}'
|
bounce_email_html: '<b>Email bounced</b> %{submitter_name}'
|
||||||
@@ -1982,6 +1983,7 @@ es: &es
|
|||||||
embed: Integración
|
embed: Integración
|
||||||
invite: Invitación
|
invite: Invitación
|
||||||
link: Enlace
|
link: Enlace
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||||
bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}'
|
bounce_email_html: '<b>Correo electrónico rebotado</b> %{submitter_name}'
|
||||||
@@ -3023,6 +3025,7 @@ it: &it
|
|||||||
embed: Incorporamento
|
embed: Incorporamento
|
||||||
invite: Invito
|
invite: Invito
|
||||||
link: Link
|
link: Link
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email inviata</b> a %{submitter_name}'
|
send_email_to_html: '<b>Email inviata</b> a %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email respinta</b> %{submitter_name}'
|
bounce_email_html: '<b>Email respinta</b> %{submitter_name}'
|
||||||
@@ -4060,6 +4063,7 @@ fr: &fr
|
|||||||
embed: Embedding
|
embed: Embedding
|
||||||
invite: Invitation
|
invite: Invitation
|
||||||
link: Lien
|
link: Lien
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}"
|
send_email_to_html: "<b>E‑mail envoyé</b> à %{submitter_name}"
|
||||||
bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}"
|
bounce_email_html: "<b>E‑mail rejeté</b> %{submitter_name}"
|
||||||
@@ -5100,6 +5104,7 @@ pt: &pt
|
|||||||
embed: Incorporação
|
embed: Incorporação
|
||||||
invite: Convite
|
invite: Convite
|
||||||
link: Link
|
link: Link
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email enviado</b> para %{submitter_name}'
|
send_email_to_html: '<b>Email enviado</b> para %{submitter_name}'
|
||||||
bounce_email_html: '<b>Email não entregue</b> %{submitter_name}'
|
bounce_email_html: '<b>Email não entregue</b> %{submitter_name}'
|
||||||
@@ -6140,6 +6145,7 @@ de: &de
|
|||||||
embed: Einbettung
|
embed: Einbettung
|
||||||
invite: Einladung
|
invite: Einladung
|
||||||
link: Link
|
link: Link
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||||
bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}'
|
bounce_email_html: '<b>E-Mail unzustellbar</b> %{submitter_name}'
|
||||||
@@ -7581,6 +7587,7 @@ nl: &nl
|
|||||||
embed: Insluiten
|
embed: Insluiten
|
||||||
invite: Uitnodiging
|
invite: Uitnodiging
|
||||||
link: Link
|
link: Link
|
||||||
|
mcp: MCP
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}"
|
send_email_to_html: "<b>E-mail verzonden</b> naar %{submitter_name}"
|
||||||
bounce_email_html: "<b>E-mail gebounced</b> %{submitter_name}"
|
bounce_email_html: "<b>E-mail gebounced</b> %{submitter_name}"
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ Rails.application.routes.draw do
|
|||||||
resources :console_redirect, only: %i[index]
|
resources :console_redirect, only: %i[index]
|
||||||
resources :upgrade, only: %i[index], controller: 'console_redirect'
|
resources :upgrade, only: %i[index], controller: 'console_redirect'
|
||||||
resources :manage, only: %i[index], controller: 'console_redirect'
|
resources :manage, only: %i[index], controller: 'console_redirect'
|
||||||
resource :testing_account, only: %i[show destroy]
|
resource :testing_account, only: %i[create destroy]
|
||||||
resources :testing_api_settings, only: %i[index]
|
resources :testing_api_settings, only: %i[index]
|
||||||
resources :submitters_autocomplete, only: %i[index]
|
resources :submitters_autocomplete, only: %i[index]
|
||||||
resources :submitters_resubmit, only: %i[update]
|
resources :submitters_resubmit, only: %i[update]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ module Mcp
|
|||||||
readOnlyHint: false,
|
readOnlyHint: false,
|
||||||
destructiveHint: false,
|
destructiveHint: false,
|
||||||
idempotentHint: false,
|
idempotentHint: false,
|
||||||
openWorldHint: false
|
openWorldHint: true
|
||||||
}
|
}
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ module Mcp
|
|||||||
account:,
|
account:,
|
||||||
author: current_user,
|
author: current_user,
|
||||||
folder: account.default_template_folder,
|
folder: account.default_template_folder,
|
||||||
|
source: :mcp,
|
||||||
name: arguments['name'].to_s.presence || 'New Template',
|
name: arguments['name'].to_s.presence || 'New Template',
|
||||||
fields: [],
|
fields: [],
|
||||||
schema: []
|
schema: []
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ module Mcp
|
|||||||
},
|
},
|
||||||
annotations: {
|
annotations: {
|
||||||
readOnlyHint: false,
|
readOnlyHint: false,
|
||||||
destructiveHint: false,
|
destructiveHint: true,
|
||||||
idempotentHint: false,
|
idempotentHint: false,
|
||||||
openWorldHint: true
|
openWorldHint: true
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ module Mcp
|
|||||||
submissions = Submissions.create_from_submitters(
|
submissions = Submissions.create_from_submitters(
|
||||||
template:,
|
template:,
|
||||||
user: current_user,
|
user: current_user,
|
||||||
source: :api,
|
source: :mcp,
|
||||||
submitters_order: 'random',
|
submitters_order: 'random',
|
||||||
submissions_attrs: { submitters: submitters },
|
submissions_attrs: { submitters: submitters },
|
||||||
params: { 'send_email' => true, 'submitters' => submitters }
|
params: { 'send_email' => true, 'submitters' => submitters }
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Puma::Plugin.create do
|
|||||||
Dir.chdir(ENV.fetch('WORKDIR', nil)) unless ENV['WORKDIR'].to_s.empty?
|
Dir.chdir(ENV.fetch('WORKDIR', nil)) unless ENV['WORKDIR'].to_s.empty?
|
||||||
|
|
||||||
exec('redis-server', '--requirepass', Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}"),
|
exec('redis-server', '--requirepass', Digest::SHA1.hexdigest("redis#{ENV.fetch('SECRET_KEY_BASE', '')}"),
|
||||||
out: '/dev/null')
|
'--loglevel', 'warning')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ module SearchEntries
|
|||||||
end
|
end
|
||||||
|
|
||||||
[sql, number, number.length > 1 ? number.delete_prefix('0') : number, keyword]
|
[sql, number, number.length > 1 ? number.delete_prefix('0') : number, keyword]
|
||||||
elsif keyword.match?(/[^\p{L}\d&@.-]/) || keyword.match?(/\A['"].*['"]\z/) || keyword.match?(/[.-]{2,}/)
|
elsif keyword.start_with?('@') || keyword.match?(/[^\p{L}\d&@.-]/) ||
|
||||||
|
keyword.match?(/\A['"].*['"]\z/) || keyword.match?(/[.-]{2,}/)
|
||||||
['tsvector @@ plainto_tsquery(?)', TextUtils.transliterate(keyword.downcase)]
|
['tsvector @@ plainto_tsquery(?)', TextUtils.transliterate(keyword.downcase)]
|
||||||
else
|
else
|
||||||
keyword = TextUtils.transliterate(keyword.downcase).squish
|
keyword = TextUtils.transliterate(keyword.downcase).squish
|
||||||
|
|||||||
+1
-1
@@ -162,7 +162,7 @@ module Submissions
|
|||||||
return email.downcase.sub(/@gmail?\z/i, '@gmail.com') if email.match?(/@gmail?\z/i)
|
return email.downcase.sub(/@gmail?\z/i, '@gmail.com') if email.match?(/@gmail?\z/i)
|
||||||
|
|
||||||
return email.downcase if email.include?(',') ||
|
return email.downcase if email.include?(',') ||
|
||||||
email.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
email.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie|ed\.jp)\z/i) ||
|
||||||
email.exclude?('.')
|
email.exclude?('.')
|
||||||
|
|
||||||
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
fixed_email = EmailTypo.call(email.delete_prefix('<'))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Templates
|
module Templates
|
||||||
|
COLOR_REGEXP = /\A(#(?:[0-9a-f]{3}|[0-9a-f]{6})|[a-z]+)\z/i
|
||||||
|
|
||||||
EXPIRATION_DURATIONS = {
|
EXPIRATION_DURATIONS = {
|
||||||
one_day: 1.day,
|
one_day: 1.day,
|
||||||
two_days: 2.days,
|
two_days: 2.days,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module Templates
|
|||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(original_template, author:, external_id: nil, name: nil, folder_name: nil)
|
def call(original_template, author:, external_id: nil, name: nil, folder_name: nil)
|
||||||
template = original_template.account.templates.new
|
template = author.account.templates.new
|
||||||
|
|
||||||
template.external_id = external_id
|
template.external_id = external_id
|
||||||
template.shared_link = original_template.shared_link
|
template.shared_link = original_template.shared_link
|
||||||
@@ -16,8 +16,10 @@ module Templates
|
|||||||
|
|
||||||
if folder_name.present?
|
if folder_name.present?
|
||||||
template.folder = TemplateFolders.find_or_create_by_name(author, folder_name)
|
template.folder = TemplateFolders.find_or_create_by_name(author, folder_name)
|
||||||
else
|
elsif author.account_id == original_template.account_id
|
||||||
template.folder_id = original_template.folder_id
|
template.folder_id = original_template.folder_id
|
||||||
|
else
|
||||||
|
template.folder = author.account.default_template_folder
|
||||||
end
|
end
|
||||||
|
|
||||||
template.submitters, template.fields, template.schema, template.preferences =
|
template.submitters, template.fields, template.schema, template.preferences =
|
||||||
|
|||||||
Reference in New Issue
Block a user