11import { Fragment } from 'prosemirror-model' ;
2+ import { fieldAnnotationHelpers } from '@extensions/index.js' ;
23
34/**
45 * Get the field attributes based on the field type and value
@@ -7,39 +8,59 @@ import { Fragment } from 'prosemirror-model';
78 * @param {Object } value The value we want to annotate the field with
89 * @returns
910 */
10- export const getFieldAttrs = ( field , value ) => {
11+ export const getFieldAttrs = ( field , value , input ) => {
1112 const { type } = field . attrs ;
1213 const annotatorHandlers = {
1314 html : annotateHtml ,
1415 text : annotateText ,
1516 checkbox : annotateCheckbox ,
1617 image : annotateImage ,
18+ link : annotateLink ,
19+ yesno : annotateYesNo ,
20+ date : annotateDate ,
1721 }
1822
1923 const handler = annotatorHandlers [ type ] ;
2024 if ( ! handler ) return { } ;
2125
2226 // Run the handler to get the annotated field attributes
23- return handler ( value ) ;
27+ return handler ( value , input ) ;
2428} ;
2529
2630const annotateHtml = ( value ) => ( { rawHtml : value } ) ;
2731const annotateText = ( value ) => ( { displayLabel : value } ) ;
2832const annotateImage = ( value ) => ( { imageSrc : value } ) ;
2933const annotateCheckbox = ( value ) => ( { displayLabel : value } ) ;
3034
35+ const annotateDate = ( value , input ) => {
36+ const formatted = getFormattedDate ( value , input . input_format ) ;
37+ return { displayLabel : formatted } ;
38+ } ;
39+
40+ const annotateLink = ( value ) => {
41+ if ( ! value . startsWith ( 'http' ) ) value = `http://${ value } ` ;
42+ return { linkUrl : value } ;
43+ } ;
44+
45+ const annotateYesNo = ( value ) => {
46+ const yesNoValues = {
47+ 'YES' : 'Yes' ,
48+ 'NO' : 'No' ,
49+ }
50+ const parsedValue = yesNoValues [ value [ 0 ] . toUpperCase ( ) ] ;
51+ return { displayLabel : parsedValue } ;
52+ } ;
53+
3154/**
3255 * Pre-process tables in the document to generate rows from annotations if necessary
3356 *
3457 * @param {Object } param0 The editor instance and annotation values
3558 * @param {Object } param0.editor The editor instance
3659 * @param {Array } param0.annotationValues The annotation values to process
3760 */
38- export const processTables = ( { editor, annotationValues } ) => {
61+ export const processTables = ( { editor, tr , annotationValues } ) => {
3962 const { state } = editor ;
4063 const { doc } = state ;
41- const { tr } = state ;
42- const { dispatch } = editor . view ;
4364
4465 // Get all tables in the document
4566 const tables = [ ] ;
@@ -50,8 +71,10 @@ export const processTables = ({ editor, annotationValues }) => {
5071 tables . reverse ( ) . forEach ( ( table ) => {
5172 generateTableIfNecessary ( { tableNode : table , annotationValues, tr, editor } ) ;
5273 } ) ;
53- dispatch ( tr ) ;
74+
75+ return tr ;
5476} ;
77+
5578const generateTableIfNecessary = ( { tableNode, annotationValues, tr, editor } ) => {
5679 let rowNodeToGenerate = null ;
5780 let currentRow = null ;
@@ -137,7 +160,118 @@ const getAnnotationValue = (id, annotationValues) => {
137160 return annotationValues . find ( ( value ) => value . input_id === id ) ?. input_value || null ;
138161} ;
139162
163+ export const annotateDocument = ( {
164+ annotationValues = [ ] ,
165+ hiddenFieldIds = [ ] ,
166+ schema,
167+ tr,
168+ } ) => {
169+
170+ const annotations = [ ] ;
171+ const FieldType = schema . nodes . fieldAnnotation ;
172+ tr . doc . descendants ( ( node , pos ) => {
173+ if ( node . type === FieldType ) {
174+ annotations . push ( { node, pos, size : node . nodeSize } ) ;
175+ }
176+ } ) ;
177+
178+ const toDelete = new Set ( ) ;
179+
180+ if ( hiddenFieldIds . length ) {
181+ for ( const { node, pos } of annotations ) {
182+ if ( hiddenFieldIds . includes ( node . attrs . fieldId ) ) {
183+ toDelete . add ( pos ) ;
184+ }
185+ }
186+ }
187+
188+ // For each annotation, either queue it for deletion or queue an update
189+ for ( const { node, pos } of annotations ) {
190+ const { type, fieldType, fieldId } = node . attrs ;
191+ if ( toDelete . has ( pos ) ) continue ;
192+
193+ let newValue = null ;
194+ const input = annotationValues . find ( i => i . input_id === fieldId ) ;
195+
196+ if ( ! input ) {
197+ const checkboxInputs = annotationValues . filter (
198+ i => i . input_field_type === 'CHECKBOXINPUT'
199+ ) ;
200+ inputsLoop:
201+ for ( const cb of checkboxInputs ) {
202+ for ( const opt of cb . input_options ) {
203+ if ( opt . itemid === fieldId ) {
204+ newValue = cb . input_link_value [ opt . itemid ] || ' ' ;
205+ break inputsLoop;
206+ }
207+ }
208+ }
209+ }
210+ newValue = newValue || input ?. input_value || null ;
211+
212+ // skip table-generator placeholders
213+ if ( Array . isArray ( newValue ) && node . attrs . generatorIndex != null ) {
214+ continue ;
215+ }
216+
217+ if ( type === 'checkbox' || fieldType === 'CHECKBOXINPUT' ) {
218+ const isEmptyOrSquare = ! newValue
219+ || ( typeof newValue === 'string' && newValue . codePointAt ( 0 ) === 0x2610 ) ;
220+ if ( isEmptyOrSquare ) newValue = ' ' ;
221+ }
222+
223+ // queue delete or update
224+ if ( ! newValue ) {
225+ toDelete . add ( pos ) ;
226+ } else {
227+ const attrs = getFieldAttrs ( node , newValue , input ) ;
228+ tr = tr . setNodeMarkup ( pos , undefined , {
229+ ...node . attrs ,
230+ ...attrs
231+ } ) ;
232+ }
233+ }
234+
235+ // perform deletes all in one go (descending positions)
236+ Array . from ( toDelete )
237+ . sort ( ( a , b ) => b - a )
238+ . forEach ( pos => {
239+ const ann = annotations . find ( a => a . pos === pos ) ;
240+ if ( ! ann ) return ;
241+ tr = tr . delete ( pos , pos + ann . node . nodeSize ) ;
242+ } ) ;
243+
244+ return tr ;
245+ } ;
246+
247+ /**
248+ * Format the date to the given format
249+ *
250+ * @param {String } input The date value
251+ * @param {String } format The date format
252+ */
253+ const getFormattedDate = ( input = null , format = '' ) => {
254+ // 1. Parse: if input is falsy, use "now"; otherwise let Date handle it.
255+ const date = input ? new Date ( input ) : new Date ( ) ;
256+
257+ // 2. If invalid, just return what you got.
258+ if ( isNaN ( date . getTime ( ) ) ) {
259+ return input ;
260+ }
261+
262+ // 3. If a custom format was requested, use the dateFormat lib:
263+ if ( format ) return dateFormat ( date , format ) ;
264+
265+ // 4. Otherwise, do a single toLocaleDateString call:
266+ return date . toLocaleDateString ( 'en-US' , {
267+ month : 'short' , // e.g. “May”
268+ day : '2-digit' , // e.g. “05”
269+ year : 'numeric' // e.g. “2025”
270+ } ) ;
271+ } ;
272+
140273export const AnnotatorServices = {
141274 getFieldAttrs,
142275 processTables,
276+ annotateDocument,
143277} ;
0 commit comments