mirror of https://github.com/docusealco/docuseal
- Create PdfTextToHtml heuristic parser (ALL_CAPS→h2, numbered→h3, bullets→ul, body→p) - Create document-tabs custom element (ARIA APG tab pattern, roving tabindex, localStorage persistence) - Register document-tabs element in application.js - Add tab switcher to submissions/show and submit_form/show when all pages have extracted text - Add text panel with per-page sections to both views - Fix role="region" bug on sr-only page text divs (excess ARIA landmarks) - Add 5 new i18n keys: pdf_view, text_view, document_view_options, text_view_disclaimer, signing_fields_below Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>pull/599/head
parent
778a379086
commit
929bb13f8e
@ -0,0 +1,45 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this._tabs = Array.from(this.querySelectorAll('[role="tab"]'))
|
||||||
|
this._panels = Array.from(this.querySelectorAll('[role="tabpanel"]'))
|
||||||
|
|
||||||
|
this._tabs.forEach((tab) => {
|
||||||
|
tab.addEventListener('click', () => this._selectTab(tab))
|
||||||
|
tab.addEventListener('keydown', (e) => this._onKeydown(e))
|
||||||
|
})
|
||||||
|
|
||||||
|
const saved = localStorage.getItem('docuseal_document_view')
|
||||||
|
const savedTab = saved && this._tabs.find((t) => t.id === saved)
|
||||||
|
this._selectTab(savedTab || this._tabs[0], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectTab (selectedTab, save = true) {
|
||||||
|
this._tabs.forEach((tab) => {
|
||||||
|
const isSelected = tab === selectedTab
|
||||||
|
tab.setAttribute('aria-selected', isSelected ? 'true' : 'false')
|
||||||
|
tab.setAttribute('tabindex', isSelected ? '0' : '-1')
|
||||||
|
tab.classList.toggle('border-primary', isSelected)
|
||||||
|
tab.classList.toggle('text-primary', isSelected)
|
||||||
|
tab.classList.toggle('border-transparent', !isSelected)
|
||||||
|
tab.classList.toggle('text-base-content/60', !isSelected)
|
||||||
|
})
|
||||||
|
this._panels.forEach((panel) => {
|
||||||
|
panel.hidden = panel.id !== selectedTab.getAttribute('aria-controls')
|
||||||
|
})
|
||||||
|
if (save) localStorage.setItem('docuseal_document_view', selectedTab.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onKeydown (e) {
|
||||||
|
const tabs = this._tabs
|
||||||
|
const idx = tabs.indexOf(e.currentTarget)
|
||||||
|
let next
|
||||||
|
if (e.key === 'ArrowRight') next = tabs[(idx + 1) % tabs.length]
|
||||||
|
else if (e.key === 'ArrowLeft') next = tabs[(idx - 1 + tabs.length) % tabs.length]
|
||||||
|
else if (e.key === 'Home') next = tabs[0]
|
||||||
|
else if (e.key === 'End') next = tabs[tabs.length - 1]
|
||||||
|
else return
|
||||||
|
e.preventDefault()
|
||||||
|
this._selectTab(next)
|
||||||
|
next.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PdfTextToHtml
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def call(page_text)
|
||||||
|
output = +''
|
||||||
|
current_list = nil
|
||||||
|
|
||||||
|
page_text.split(/\r?\n/).each do |line|
|
||||||
|
stripped = line.strip
|
||||||
|
if stripped.empty?
|
||||||
|
output << close_list(current_list) if current_list
|
||||||
|
current_list = nil
|
||||||
|
next
|
||||||
|
end
|
||||||
|
current_list = process_line(stripped, output, current_list)
|
||||||
|
end
|
||||||
|
|
||||||
|
output << close_list(current_list)
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_line(stripped, output, current_list)
|
||||||
|
if numbered_heading?(stripped)
|
||||||
|
output << close_list(current_list)
|
||||||
|
output << "<h3>#{ERB::Util.html_escape(stripped)}</h3>"
|
||||||
|
nil
|
||||||
|
elsif all_caps_heading?(stripped)
|
||||||
|
output << close_list(current_list)
|
||||||
|
output << "<h2>#{ERB::Util.html_escape(stripped)}</h2>"
|
||||||
|
nil
|
||||||
|
elsif (match = stripped.match(/\A[•*-]\s+(.+)/))
|
||||||
|
output << close_list(current_list) << '<ul>' unless current_list == :ul
|
||||||
|
output << "<li>#{ERB::Util.html_escape(match[1])}</li>"
|
||||||
|
:ul
|
||||||
|
else
|
||||||
|
output << close_list(current_list)
|
||||||
|
output << %(<p dir="auto">#{ERB::Util.html_escape(stripped)}</p>)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def numbered_heading?(line)
|
||||||
|
line.length <= 80 && line.match?(/\A\d+\.\s+[A-Z]/) && !line.match?(/[.!?,;]\z/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_caps_heading?(line)
|
||||||
|
line.length >= 3 && !line.match?(/[.!?,;]\z/) &&
|
||||||
|
line == line.upcase && line.match?(/[A-Z]/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_list(current_list)
|
||||||
|
case current_list
|
||||||
|
when :ol then '</ol>'
|
||||||
|
when :ul then '</ul>'
|
||||||
|
else ''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in new issue