Insert OOXML comment with track changes - ms-word

In my Word add-in I am inserting comments by replacing the selected text with OOXML that contains the comment.
With "Track Changes" turned on Word registers this as 3 actions: delete+insert+comment. It even inserts a paragraph break but I am unsure if that is related.
Is there a way to only have it register as a comment action like when inserting a comment using Word functionality?
Using rangeObject.insertOoxml I tried to insert at the beginning and at the end of the string without any luck since the two OOXML inserts does not seem to relate (which makes sense):
Word.run(function (context) {
var range = context.document.getSelection();
var preBody = '<?xml version="1.0" encoding="UTF-8"?><pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"><pkg:part pkg:name="/_rels/.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml" pkg:padding="512"><pkg:xmlData><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" /></Relationships></pkg:xmlData></pkg:part><pkg:part pkg:name="/word/_rels/document.xml.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml" pkg:padding="256"><pkg:xmlData><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="comments.xml" /></Relationships></pkg:xmlData></pkg:part><pkg:part pkg:name="/word/document.xml" pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"><pkg:xmlData><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p>';
var postBody = '</w:p></w:body></w:document></pkg:xmlData></pkg:part><pkg:part pkg:name="/word/comments.xml" pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"><pkg:xmlData><w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:comment w:id="0" w:author="Some User" w:date="2016-10-26T10:11:05" w:initials="SU"><w:p><w:r><w:t>My comment</w:t></w:r></w:p></w:comment></w:comments></pkg:xmlData></pkg:part></pkg:package>';
var before = preBody + '<w:commentRangeStart w:id="0" />' + postBody;
var afterBody = '<w:commentRangeEnd w:id="0" /><w:r><w:commentReference w:id="0" /></w:r>';
var after = preBody + afterBody + postBody;
range.insertOoxml(before, Word.InsertLocation.start);
range.insertOoxml(after, Word.InsertLocation.end);
return context.sync().then(function () {
console.log('OOXML added to the beginning and the end of the range.');
});
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});

This is a great question, thanks for asking it. When you use the insertOoxml method you are actually writing in the document's body, hence the behavior you observe when track changes is active, that's by design. There is no workaround for this, as of now. This issue will be solved when we support comments as a main feature of the API (and not inserting ooxml as workaround). Make sure to add your request or vote for an existing on in our uservoice! https://officespdev.uservoice.com/forums/224641-feature-requests-and-feedback/category/163566-add-in-word
Btw there was a bug we fixed a few months ago that the insertOoxml method was inserting a extra paragraph, make sure to update your Word to get the latest bug fixes. If this is still happening please share your Office Version number.

Related

What ending marks should be used to extend a range to the end of the paragraph?

I am coding a word add-in and am not clear how to use the getNextTextRange(endingMarks, trimSpacing) method of the Range class.
Specifically I want to select a new Range starting from the currently selected range and going to the end of the paragraph.
The API for for the method states
endingMarks string[]
Required. The punctuation marks and/or other
ending marks as an array of strings
That's clear enough if you want to select up to the next comma, period or even space. But what ending marks should you use for a paragraph, a line break, or the end of the document?
I have tried using '\n', '^p' and '¶' but none of these seem to work.
var nr = selection.getNextTextRange(['¶'],true);
nr.load("isEmpty,text");
await context.sync();
console.log('nr='+nr.text);
} catch(e) {
console.log("error, soz");
console.log(e);
}
Given a document consisting of one paragraph of text with a blank paragraph after it, and the first word of the paragraph highlighted, this add-in throws a RichApi.Error
We couldn't find the item you requested.
I would expect it to instead print out the remainder of the paragraph.
If I understand your scenario, you can work with the ParagraphCollection.getFirst() method. Please install the Script Lab tool. Open the sample called "Get paragraph from insertion point" for an example.
Let me expand on rick-kirkham's answer in case it helps anyone else in my situation. This is basically the same answer as given here https://stackoverflow.com/a/51160690/4114053
Ok, here is my sample word document:
The rain in Spain falls. Mainly on the plain.
Alice stepped through the looking glass. What did she see?
And there endeth the lesson. Amen.
The user selects "stepped" in the second paragraph and I want to know what the text for the rest of the paragraph, from that word, says. I also want to know what the text up to that point says.
var doc = context.document;
var selection = doc.getSelection();
selection.load("isEmpty,text");
await context.sync();
console.log(selection.text); //prints stepped
var startRange = selection.getRange("start");
var endRange = selection.paragraphs.getLast().getRange("start");
var deltaRange = startRange.expandTo(endRange);
context.load(deltaRange);
await context.sync();
console.log(deltaRange.text); //prints "Alice"
startRange = selection.getRange("end");
endRange = selection.paragraphs.getLast().getRange("end");
deltaRange = startRange.expandTo(endRange);
context.load(deltaRange);
await context.sync();
console.log(deltaRange.text); // prints "through the looking glass. What did she see?"
My mistake was to get too caught up in trying to work out what "ending marks" might mean and how to use them to achieve this. (Although I still would like that spelled out in the API specification.)

Microsoft Office add-in javascript: search by matchPrefix:true- how to get the full word for the matched prefix

I am trying to follow the article here
Also, adding the code here, since links can always move or get modified or go down.
// Run a batch operation against the Word object model.
Word.run(function (context) {
// Queue a command to search the document based on a prefix.
var searchResults = context.document.body.search('pattern', {matchPrefix: true});
// Queue a command to load the search results and get the font property values.
context.load(searchResults, 'font');
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
console.log('Found count: ' + searchResults.items.length);
// Queue a set of commands to change the font for each found item.
for (var i = 0; i < searchResults.items.length; i++) {
searchResults.items[i].font.color = 'purple';
searchResults.items[i].font.highlightColor = '#FFFF00'; //Yellow
searchResults.items[i].font.bold = true;
}
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync();
});
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
It works fine, but I don't get the full word back.
So, if the word is patternABCDEFGH, the matched word in searchResults by doing
var text = searchResults.items[i].text;
console.log('Matching text:' + text);
All I get back is pattern, how do I get the full word back ?
The option MatchPrefix doesn't search every word that starts with that combination of letters - it searches only that combination of letters when it is at the beginning of a word. So it will only find the characters "pattern" in this case and not the entire "patternMatching" or "patternABCDEFGH".
As Juan mentioned, in order to get entire words that begin with a certain letter combination you need Word's variation of regex: wildcard search.
The wildcard pattern would look like this: [P,p]attern*>
This assumes you want both upper and lower case "p", followed by any character until the end of a word.
var searchResults = context.document.body.search('[P,p]attern*>', {matchWildcards: true});

How to replace text with a MS Word web add-in by preserving the formatting?

I'm working on a simple grammar correction web add-in for MS Word. Basically, I want to get the selected text, make minimal changes and update the document with the corrected text. Currently, if I use 'text' as the coercion type, I lose formatting. If there is a table or image in the selected text, they are also gone!
As I understand from the investigation I've been doing so far, openxml is the way to go. But I couldn't find any useful example on the web. How can I manipulate text by preserving the original formatting data? How can I ignore non-text paragraphs? I want to be able to do this with the Office JavaScript API:
I would do something like this:
// get data as OOXML
Office.context.document.getSelectedDataAsync(Office.CoercionType.Ooxml, function (result) {
if (result.status === "succeeded") {
var selectionAsOOXML = result.value;
var bodyContentAsOOXML = selectionAsOOXML.match(/<w:body.*?>(.*?)<\/w:body>/)[1];
// perform manipulations to the body
// it can be difficult to do to OOXML but with som regexps it should be possible
bodyContentAsOOXML = bodyContentAsOOXML.replace(/error/g, 'rorre'); // reverse the word 'error'
// insert the body back in to the OOXML
selectionAsOOXML = selectionAsOOXML.replace(/(<w:body.*?>)(.*?)<\/w:body>/, '$1' + bodyContentAsOOXML + '<\/w:body>');
// replace the selected text with the new OOXML
Office.context.document.setSelectedDataAsync(selectionAsOOXML, { coercionType: Office.CoercionType.Ooxml }, function (asyncResult) {
if (asyncResult.status === "failed") {
console.log("Action failed with error: " + asyncResult.error.message);
}
});
}
});

Add trigger to form

I have been working on a simple "Time off request" form using Google Apps. The I have associated a script with the form which detects the respondents' supervisor and sends them a link to a new form for their approval.
Here is the code from the script I used to create the new form.
//create approval form as part of the Message Creation process.
var approvalForm = FormApp.create("Approve Days off Request")
approvalForm.setTitle("Approval form for " + user.Name)
// truncated ...
var approvalItem = approvalForm.addMultipleChoiceItem(); //Item on First Page
var disapproveReasonPage = approvalForm.addPageBreakItem(); //Page Break Item
var disapproveReason = approvalForm.addParagraphTextItem(); //Item on Second Page
// truncated ...
//Question 1, Page 1
approvalItem.setTitle(user.Name + " has requested " + daysOff + " day(s) off starting on " + firstDate+".")
approvalItem.setHelpText("Would you like to approve this request?")
approvalItem.setRequired(true);
approvalItem.setChoices ([
approvalItem.createChoice('Yes', FormApp.PageNavigationType.SUBMIT),
approvalItem.createChoice('No', FormApp.PageNavigationType.CONTINUE)
]);
//Question 2, Page 2
disapproveReason.setTitle("Would you like to give a reason?");
disapproveReason.setHelpText("Optional")
disapproveReasonPage.setGoToPage(FormApp.PageNavigationType.SUBMIT)
Then a link is generated and sent to the supervisor to approve the request.
I am wanting to add a trigger to the form created above (approvalForm). I would imagine that there is a way, but haven't been able to find the greatest documentation on it. The closest I've come is TriggerBuilder Class. I tried creating one associated to the target form with
var approveScript = ScriptApp.newTrigger('Approval')
.forForm(approvalForm.getId())
.onFormSubmit()
.create();
but couldn't get it to work the way I wanted it. I did have a function named Approval just to see if I could manipulate the form with it
function Approval(){
$form.setAcceptingResponses(false);
}
Any help would be appreciated. Thanks in advance.
The script was originally being added as a container bound script. Changing it to a Standalone installable script fixed this problem. Hopefully this answer will be to someone else having a similar issue.

Alternative or fix for "\n" in google script

First time I put up a question here (been searching before posting) so please bear with possible mistakes done on my end.
To the problem:
I am currently working on a website with google sites. Made some forms there and am adding a script to those forms to get the info input there emailed away once the form is submitted and saved on the spreadsheet, which works just fine, but the message inside the email that arrives is pretty messed up.
All the "\n" expressions in the code get simply ignored.
I got the base for the code from www.labnol.org and just edited it a little.
For the start, the code:
function sendFormByEmail(e)
{
//I took the two mail addresses out here, but they are working in the original
var email = "first mail address";
var email2 = "second mail address";
var subject = "New Announce your visit form submitted";
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
var message = "A new 'Announce your visit' form has been submitted on the website: \n\n" + "\n\n";
for(var i in headers) {
message = message + "\n \n";
message += headers[i] + ' = '+ e.namedValues[headers[i]].toString() + "\n\n";
}
var senderEmail = e.namedValues[headers[6]].toString();
MailApp.sendEmail(email, senderEmail, subject, message);
MailApp.sendEmail(email2, senderEmail, subject, message);
}
As you can see, I have been trying around a lot to place the \n in different places as alternative, though it gets ignored no matter where I place it.
the original loop looked like this:
for(var i in headers)
message += headers[i] + ' = '+ e.namedValues[headers[i]].toString() + "\n\n";
Before I did some modifications to the code it worked just fine. But even though I didn't touch these lines, nor any of the other parts of the code that contribute to getting the message, the \n stopped working.
I kept trying to fix it (as the messed up \n placing above shows), but without success.
So now I am trying to find a way to fix them, or at least a work-around and hoped that any of you might know what is going on with the \n's or how to get them working again.
Thanks in advance.
ps: if you need any more information on it, just let me know
A solution is to use the GmailApp.sendEmail method instead of the MailApp.sendEmail method. The GmailApp's sendMail handles \n correctly.
Here's an alternate solution to what you're doing: HtmlTemplate
Some sample code:
function sendEmailWithTemplateExample() {
var t = HtmlService.createTemplateFromFile("body.html");
t.someValue = "some dynamic value";
var emailBody = t.evaluate().getContent();
MailApp.sendEmail("your#email.here", "test email", emailBody);
}
And here's the corresponding template code in body.html (Click "File -> New -> Html File"):
Body goes here
<?= someValue ?>