@@ -5,20 +5,47 @@ import { mergeRelationshipElements } from '../../relationship-helpers.js';
55
66const RELS_XMLNS = 'http://schemas.openxmlformats.org/package/2006/relationships' ;
77const FOOTNOTES_RELS_PATH = 'word/_rels/footnotes.xml.rels' ;
8+ const ENDNOTES_RELS_PATH = 'word/_rels/endnotes.xml.rels' ;
9+
10+ const FOOTNOTES_CONFIG = {
11+ notesPath : 'word/footnotes.xml' ,
12+ relsPath : FOOTNOTES_RELS_PATH ,
13+ rootName : 'w:footnotes' ,
14+ noteName : 'w:footnote' ,
15+ refName : 'w:footnoteRef' ,
16+ refStyle : 'FootnoteReference' ,
17+ relationshipType : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes' ,
18+ relationshipTarget : 'footnotes.xml' ,
19+ // Footnotes own the settings.xml export side-effects (footnoteProperties +
20+ // viewSetting). The endnote path skips them so we don't double-apply.
21+ applySettingsSideEffects : true ,
22+ } ;
23+
24+ const ENDNOTES_CONFIG = {
25+ notesPath : 'word/endnotes.xml' ,
26+ relsPath : ENDNOTES_RELS_PATH ,
27+ rootName : 'w:endnotes' ,
28+ noteName : 'w:endnote' ,
29+ refName : 'w:endnoteRef' ,
30+ refStyle : 'EndnoteReference' ,
31+ relationshipType : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes' ,
32+ relationshipTarget : 'endnotes.xml' ,
33+ applySettingsSideEffects : false ,
34+ } ;
835
936const paragraphHasFootnoteRef = ( node ) => {
1037 if ( ! node ) return false ;
11- if ( node . name === 'w:footnoteRef' ) return true ;
38+ if ( node . name === 'w:footnoteRef' || node . name === 'w:endnoteRef' ) return true ;
1239 const children = Array . isArray ( node . elements ) ? node . elements : [ ] ;
1340 return children . some ( ( child ) => paragraphHasFootnoteRef ( child ) ) ;
1441} ;
1542
16- const insertFootnoteRefIntoParagraph = ( paragraph ) => {
43+ const insertFootnoteRefIntoParagraph = ( paragraph , config ) => {
1744 if ( ! paragraph || paragraph . name !== 'w:p' ) return ;
1845 if ( ! Array . isArray ( paragraph . elements ) ) paragraph . elements = [ ] ;
1946 if ( paragraphHasFootnoteRef ( paragraph ) ) return ;
2047
21- const footnoteRef = { type : 'element' , name : 'w:footnoteRef' , elements : [ ] } ;
48+ const footnoteRef = { type : 'element' , name : config . refName , elements : [ ] } ;
2249 const footnoteRefRun = {
2350 type : 'element' ,
2451 name : 'w:r' ,
@@ -27,7 +54,7 @@ const insertFootnoteRefIntoParagraph = (paragraph) => {
2754 type : 'element' ,
2855 name : 'w:rPr' ,
2956 elements : [
30- { type : 'element' , name : 'w:rStyle' , attributes : { 'w:val' : 'FootnoteReference' } } ,
57+ { type : 'element' , name : 'w:rStyle' , attributes : { 'w:val' : config . refStyle } } ,
3158 { type : 'element' , name : 'w:vertAlign' , attributes : { 'w:val' : 'superscript' } } ,
3259 ] ,
3360 } ,
@@ -40,11 +67,11 @@ const insertFootnoteRefIntoParagraph = (paragraph) => {
4067 paragraph . elements . splice ( insertAt , 0 , footnoteRefRun ) ;
4168} ;
4269
43- const ensureFootnoteRefMarker = ( elements ) => {
70+ const ensureFootnoteRefMarker = ( elements , config ) => {
4471 if ( ! Array . isArray ( elements ) ) return ;
4572 const firstParagraphIndex = elements . findIndex ( ( el ) => el ?. name === 'w:p' ) ;
4673 if ( firstParagraphIndex >= 0 ) {
47- insertFootnoteRefIntoParagraph ( elements [ firstParagraphIndex ] ) ;
74+ insertFootnoteRefIntoParagraph ( elements [ firstParagraphIndex ] , config ) ;
4875 return ;
4976 }
5077
@@ -53,7 +80,7 @@ const ensureFootnoteRefMarker = (elements) => {
5380 name : 'w:p' ,
5481 elements : [ ] ,
5582 } ;
56- insertFootnoteRefIntoParagraph ( paragraph ) ;
83+ insertFootnoteRefIntoParagraph ( paragraph , config ) ;
5784 elements . unshift ( paragraph ) ;
5885} ;
5986
@@ -74,7 +101,7 @@ const translateFootnoteContent = (content, exportContext) => {
74101 return translated ;
75102} ;
76103
77- export const createFootnoteElement = ( footnote , exportContext ) => {
104+ export const createFootnoteElement = ( footnote , exportContext , config = FOOTNOTES_CONFIG ) => {
78105 if ( ! footnote ) return null ;
79106
80107 const { id, content, type, originalXml } = footnote ;
@@ -93,14 +120,14 @@ export const createFootnoteElement = (footnote, exportContext) => {
93120 // in their footnote content - the custom symbol appears in the document body instead.
94121 const originalHadFootnoteRef = originalXml ? paragraphHasFootnoteRef ( originalXml ) : true ;
95122 if ( originalHadFootnoteRef ) {
96- ensureFootnoteRefMarker ( translatedContent ) ;
123+ ensureFootnoteRefMarker ( translatedContent , config ) ;
97124 }
98125
99126 const base = originalXml
100127 ? carbonCopy ( originalXml )
101128 : {
102129 type : 'element' ,
103- name : 'w:footnote' ,
130+ name : config . noteName ,
104131 attributes : { } ,
105132 elements : [ ] ,
106133 } ;
@@ -157,10 +184,10 @@ const applyViewSettingToSettings = (converter, convertedXml) => {
157184 return { ...convertedXml , 'word/settings.xml' : updatedSettings } ;
158185} ;
159186
160- const buildFootnotesRelsXml = ( converter , convertedXml , relationships ) => {
187+ const buildFootnotesRelsXml = ( converter , convertedXml , relationships , relsPath = FOOTNOTES_RELS_PATH ) => {
161188 if ( ! relationships . length ) return null ;
162189
163- const existingRels = convertedXml [ FOOTNOTES_RELS_PATH ] ;
190+ const existingRels = convertedXml [ relsPath ] ;
164191 const existingRoot = existingRels ?. elements ?. find ( ( el ) => el . name === 'Relationships' ) ;
165192 const existingElements = Array . isArray ( existingRoot ?. elements ) ? existingRoot . elements : [ ] ;
166193 const merged = mergeRelationshipElements ( existingElements , relationships ) ;
@@ -180,14 +207,25 @@ const buildFootnotesRelsXml = (converter, convertedXml, relationships) => {
180207 return relsXml ;
181208} ;
182209
183- export const prepareFootnotesXmlForExport = ( { footnotes, editor, converter, convertedXml } ) => {
184- let updatedXml = applyFootnotePropertiesToSettings ( converter , convertedXml ) ;
185- // NOTE: applyViewSettingToSettings lives here because this function already
186- // modifies settings.xml during export. If the footnotes export path is ever
187- // refactored, this call must move to wherever settings.xml is written.
188- updatedXml = applyViewSettingToSettings ( converter , updatedXml ) ;
210+ const createNotesXmlDefinition = ( config ) => {
211+ const base = carbonCopy ( FOOTNOTES_XML_DEF ) ;
212+ if ( base . elements ?. [ 0 ] ) {
213+ base . elements [ 0 ] . name = config . rootName ;
214+ }
215+ return base ;
216+ } ;
217+
218+ const prepareNotesXmlForExport = ( { notes, editor, converter, convertedXml, config } ) => {
219+ // Settings.xml side-effects (re-emitting w:footnotePr and w:view) belong to
220+ // the footnotes path only. The endnote path skips them so we don't redo the
221+ // same idempotent work twice per export.
222+ let updatedXml = convertedXml ;
223+ if ( config . applySettingsSideEffects ) {
224+ updatedXml = applyFootnotePropertiesToSettings ( converter , updatedXml ) ;
225+ updatedXml = applyViewSettingToSettings ( converter , updatedXml ) ;
226+ }
189227
190- if ( ! footnotes || ! Array . isArray ( footnotes ) || footnotes . length === 0 ) {
228+ if ( ! notes || ! Array . isArray ( notes ) || notes . length === 0 ) {
191229 return { updatedXml, relationships : [ ] , media : { } } ;
192230 }
193231
@@ -201,15 +239,15 @@ export const prepareFootnotesXmlForExport = ({ footnotes, editor, converter, con
201239 media : footnoteMedia ,
202240 } ;
203241
204- const footnoteElements = footnotes . map ( ( fn ) => createFootnoteElement ( fn , exportContext ) ) . filter ( Boolean ) ;
242+ const footnoteElements = notes . map ( ( fn ) => createFootnoteElement ( fn , exportContext , config ) ) . filter ( Boolean ) ;
205243
206244 if ( footnoteElements . length === 0 ) {
207245 return { updatedXml, relationships : [ ] , media : footnoteMedia } ;
208246 }
209247
210- let footnotesXml = updatedXml [ 'word/footnotes.xml' ] ;
248+ let footnotesXml = updatedXml [ config . notesPath ] ;
211249 if ( ! footnotesXml ) {
212- footnotesXml = carbonCopy ( FOOTNOTES_XML_DEF ) ;
250+ footnotesXml = createNotesXmlDefinition ( config ) ;
213251 } else {
214252 footnotesXml = carbonCopy ( footnotesXml ) ;
215253 }
@@ -218,12 +256,12 @@ export const prepareFootnotesXmlForExport = ({ footnotes, editor, converter, con
218256 footnotesXml . elements [ 0 ] . elements = footnoteElements ;
219257 }
220258
221- updatedXml = { ...updatedXml , 'word/footnotes.xml' : footnotesXml } ;
259+ updatedXml = { ...updatedXml , [ config . notesPath ] : footnotesXml } ;
222260
223261 if ( footnoteRelationships . length > 0 ) {
224- const footnotesRelsXml = buildFootnotesRelsXml ( converter , updatedXml , footnoteRelationships ) ;
262+ const footnotesRelsXml = buildFootnotesRelsXml ( converter , updatedXml , footnoteRelationships , config . relsPath ) ;
225263 if ( footnotesRelsXml ) {
226- updatedXml = { ...updatedXml , [ FOOTNOTES_RELS_PATH ] : footnotesRelsXml } ;
264+ updatedXml = { ...updatedXml , [ config . relsPath ] : footnotesRelsXml } ;
227265 }
228266 }
229267
@@ -232,11 +270,29 @@ export const prepareFootnotesXmlForExport = ({ footnotes, editor, converter, con
232270 type : 'element' ,
233271 name : 'Relationship' ,
234272 attributes : {
235- Type : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes' ,
236- Target : 'footnotes.xml' ,
273+ Type : config . relationshipType ,
274+ Target : config . relationshipTarget ,
237275 } ,
238276 } ,
239277 ] ;
240278
241279 return { updatedXml, relationships, media : footnoteMedia } ;
242280} ;
281+
282+ export const prepareFootnotesXmlForExport = ( { footnotes, editor, converter, convertedXml } ) =>
283+ prepareNotesXmlForExport ( {
284+ notes : footnotes ,
285+ editor,
286+ converter,
287+ convertedXml,
288+ config : FOOTNOTES_CONFIG ,
289+ } ) ;
290+
291+ export const prepareEndnotesXmlForExport = ( { endnotes, editor, converter, convertedXml } ) =>
292+ prepareNotesXmlForExport ( {
293+ notes : endnotes ,
294+ editor,
295+ converter,
296+ convertedXml,
297+ config : ENDNOTES_CONFIG ,
298+ } ) ;
0 commit comments