How to insert content at the end of a specific section in Word Add-in using Office.js?
Question:
I'm developing a Word Add-in using Office.js and inserting reference links under a "References" heading.
Issue: Each new reference is being inserted at the top of the "References" section, causing the order to be reversed:
5. Latest inserted
4. Previous
3. ...
Expected Behavior: I want new references to be appended at the bottom of the "References" section so the list appears as:
1. First
2. Second
3. ...
What I’ve tried: I'm traversing the document to find the "References" heading and inserting the new reference after the last non-empty paragraph in that section. However, the new reference still appears at the top.
Question: How can I ensure the content is inserted after the last reference paragraph under a specific heading like "References" using Office.js?
Current complete function code - JS
const insertReference = (editor, title, url, skipInlineTag = false) => {
if (!url || !title) {
alert('Both Title and URL are required!');
return;
}
const model = editor.model;
model.change(writer => {
const root = model.document.getRoot();
let existingNumbers = new Set();
let referencesStarted = false;
let otherResourcesStarted = false;
let referenceSectionStart = null;
let otherResourcesSectionStart = null;
let insertAfterElement = null;
for (const child of root.getChildren()) {
if (child.is('element', 'paragraph')) {
const paraText = Array.from(child.getChildren())
.filter(node => node.is('text'))
.map(node => node.data)
.join('')
.trim();
// Check if it's a heading
if (/^references$/i.test(paraText)) {
referencesStarted = true;
otherResourcesStarted = false;
referenceSectionStart = child;
continue;
} else if (/^other resources$/i.test(paraText)) {
otherResourcesStarted = true;
referencesStarted = false;
otherResourcesSectionStart = child;
continue;
} else if (/^[A-Z][a-z]+( [A-Z][a-z]+)*$/.test(paraText)) {
// This regex detects other section titles like "Introduction", "Appendix" etc.
referencesStarted = false;
otherResourcesStarted = false;
}
if (referencesStarted && !skipInlineTag) {
const numberedMatches = [...paraText.matchAll(/^(\d+)\.\s/g)];
numberedMatches.forEach(m => existingNumbers.add(parseInt(m[1])));
}
// Identify where to insert
if ((referencesStarted && !skipInlineTag) || (otherResourcesStarted && skipInlineTag)) {
const paraText = Array.from(child.getChildren())
.filter(node => node.is('text'))
.map(node => node.data)
.join('')
.replace(/\u00A0/g, ' ') // Convert to space
.trim();
// Only update insertAfterElement if it's not empty/whitespace-only
if (paraText.length > 0) {
insertAfterElement = child;
}
}
}
}
const nextRef = existingNumbers.size ? Math.max(...existingNumbers) + 1 : 1;
const refId = `ref-${nextRef}`;
// Insert inline [n] tag
if (!skipInlineTag) {
const refText = writer.createText(`[${nextRef}]`, {
linkHref: `#${refId}`
});
editor.model.insertContent(refText);
}
const targetSection = skipInlineTag ? "Other Resources" : "References";
// Insert section heading if missing
const hasSection = [...root.getChildren()].some(child => {
if (child.is('element', 'paragraph')) {
const text = Array.from(child.getChildren())
.filter(n => n.is('text'))
.map(n => n.data.toLowerCase().trim())
.join('');
return text === targetSection.toLowerCase();
}
return false;
});
if (!hasSection) {
const headerHtml = `<p style="font-weight:bold">${targetSection}</p>`;
const viewHeader = editor.data.processor.toView(headerHtml);
const modelHeader = editor.data.toModel(viewHeader);
writer.insert(modelHeader, root, 'end');
}
// Create the reference HTML block
const referenceHtml = skipInlineTag
? `<p><a href="${url}" target="_blank"><span style="font-size:12px;"><u>${title}</u></span></a></p>`
: `<p><a name="${refId}"></a><span style="font-size:12px;">${nextRef}. </span><a href="${url}" target="_blank"><span style="color:#0000ff;"><span style="font-size:12px;"><u>${title}</u></span></span></a></p>`;
const viewFragment = editor.data.processor.toView(referenceHtml);
const modelFragment = editor.data.toModel(viewFragment);
// Insert at proper position
if (insertAfterElement) {
writer.insert(modelFragment, insertAfterElement, 'after');
} else {
// Fallback to inserting at end if section not found
writer.insert(modelFragment, root, 'end');
}
});
};