How to copy ListItems from one Google Document to another while preserving numbering? - google-apps-script

The accepted answer to How to copy content and formatting between Google Docs? indicates that we have to add conditional code just to copy elements. But I cannot get it to work for ListItem types, because the target document shows the list items without the original numbering.
var source_doc = DocumentApp.getActiveDocument();
var selection = source_doc.getSelection();
if (!selection) {
var ui = DocumentApp.getUi();
ui.alert('Please make a selection first.');
return;
}
var target_doc = DocumentApp.create('CopyOf'+DocumentApp.getActiveDocument().getName());
var target_body = target_doc.getBody();
var elements = selection.getRangeElements();
for (var i = 1; i < elements.length; i++) {
var source_element = elements[i].getElement();
var copy_element = source_element.copy();
if (copy_element.getType() == DocumentApp.ElementType.PARAGRAPH) {
target_body.appendParagraph(copy_element);
} else if (copy_element.getType() == DocumentApp.ElementType.LIST_ITEM) {
// This does not keep the numbering on the list item. Why?
target_body.appendListItem(copy_element);
// And playing games with setListId doesn't work either:
// copy_element.setListId(source_element);
// target_body.appendListItem(copy_element);
}
// TODO: Handle the other elements here.
}
The source document displays like this:
Target document renders like this:
How do I preserve ListItem formatting?
This seems much much harder than it should be: What I really want is to copy the users selection verbatim into a new document preserving all formatting, and from a google script.
It would seem that this could be done at a higher level. I can manually copy and paste and preserve the formatting, just not from the script.

I'm guessing that the cause of this is that there's a problem with using a Selection. Reading from a document directly seems to work fine.
Try appending the ListItem as text as a workaround.
target_body.appendListItem(copy_element.getText());
This will only copy the text though, not the formatting. You can also try to implement it by making a new list instead of copying the element directly. Here's a sample SO that might help.

I was having a similar problem (but not using a selection). It was being copied as a list but without any actual bullets. I just re-set the bullets manually like this:
target_body.appendListItem(copy_element).setGlyphType(DocumentApp.GlyphType.NUMBER)

Related

How to convert elements to the same size as the first selected element?

I want to set the size of all elements as the size of the first selected element. but seems something wrong with my code , somehow first element size not working for others.
Please see attached script
/*
* Make all elements same size
*/
function sameSizeElements() {
var selection = SlidesApp.getActivePresentation().getSelection();
var selectionType = selection.getSelectionType();
var pageElements = selection.getPageElementRange().getPageElements();
//iterate the selected page elements to grab the values of each positiion
for (var i = 0; i < pageElements.length; i++) {
if(i != 0){
pageElements[i].setWidth(pageElements[0].getWidth());
pageElements[i].setHeight(pageElements[0].getHeight());
}
}
}
Here is the full code you can put in ScriptEditor > code.js default file and refresh slide. it will work as you want to debug.
Updated
We are in the discussion with Google App Script issue tracking team - https://issuetracker.google.com/issues/162545277
As the current workaround, how about this method?
Issue and workaround:
In the current stage, unfortunately, it seems that this bug is still not resolved. By this, in your case, the 1st selected image cannot be retrieved. I think that this is the current reason of your issue.
In order to achieve your goal, as the current workaround, I would like to propose the following flow.
Select an image which is used as the basic size and script is run.
The object ID of image is saved to the PropertiesService.
Select the images you want to resize and the script is run.
When your script is modified for this flow, it becomes as follows.
Modified script:
function allMenu(){
var slideUi = SlidesApp.getUi();
slideUi.createMenu('LAK')
.addSeparator()
.addSubMenu(slideUi.createMenu('Sizes')
.addItem('Select base image', 'selectBaseImage') // Added
.addItem('Same Size', 'sameSizeElements'))
.addToUi();
}
// 1. At first, it saves an image which is used as the base image.
function selectBaseImage() {
var selection = SlidesApp.getActivePresentation().getSelection();
var pageElements = selection.getPageElementRange().getPageElements();
if (pageElements.length == 1) {
PropertiesService.getScriptProperties().setProperty("baseImage", pageElements[0].getObjectId());
} else {
throw new Error("Select one image.");
}
}
// 2. As the next step, the selected images are resized using the saved image.
function sameSizeElements() {
var prop = PropertiesService.getScriptProperties();
var objectId = prop.getProperty("baseImage");
if (objectId != "") {
var selection = SlidesApp.getActivePresentation().getSelection();
var baseImage = selection.getCurrentPage().getPageElementById(objectId);
var pageElements = selection.getPageElementRange().getPageElements();
for (var i = 0; i < pageElements.length; i++) {
pageElements[i].setWidth(baseImage.getWidth());
pageElements[i].setHeight(baseImage.getHeight());
}
prop.deleteProperty("baseImage");
} else {
throw new Error("Base image was not found.");
}
}
Result:
References:
getSelection()
Properties Service
Additional context to the issue:
when a server-side function is invoked, a ctx object is sent to the /invoke endpoint that, among other info (session id ssid, document id docId, revision number rev, and application type app), contains data about currently selected PageElements in property value with key sel.
The value is an array of arrays of strings and numbers, of which only one of the elements contains strings - if you look closely, these strings are object Ids (you can confirm with getObjectId method) in order of selection.
At least this confirms that the order of selection can be respected, but the way how the abovementioned list is managed server-side changes that order. Unfortunately, I could not confirm any particular order of how the elements (apart from grouping of Ids of form g8edc625556_0_0) are returned by .getSelection().getPageElementRange().getPageElements() chain of methods:

How to make a function to extract the new rich text links in Google Sheets while referencing specific ranges?

Some time ago, Google Sheets changed the adding of links to rich text, and now links cannot be found at the formula anymore. Personally I dislike this change very much because I use a custom function that extracts hyperlinks from cells with the old formula, and with the new change I just cannot find a way of doing this. I am not very good with functions yet, mainly the reason why I wrote my question title as detailed as possible.
What I need to do is to extract hyperlinks from cells using a custom formula, since it will need to be used among many other vanilla formulas. How can I set up a custom function/formula to extract the new hyperlinks based on range?
Here are the sheets where I want to extract links:
https://docs.google.com/spreadsheets/d/1JnSKQ7nd4J3NPRH4uSsOCYms-DF16j1pkCAuJeikToo/edit#gid=317867416
I would like to extract links from the games that are being posted, because I need to use those links elsewhere and I'd also like to have them ready to be imported if ever needed.
I need to specify a formula in another cell which will extract those links. For example =GETURL(B6) which would extract the new rich text hyperlinks based on a range that I insert for it.
Alternatively, is it possible to configure the document so that it makes links in the old format whenever inserted? This way I could try to workaround the new settings and future inserted links would go into the =HYPERLINK formula instead.
Many thanks!
I think this script would come in handy.
It gives the possibility to retrieve back the URL from a hyperlink formula.
Go to script editor, and create a new project.
Save the file.
Head up to Run > linkURL to run the script. This will create a new function in Sheets.
Let’s say cell A1 has the hyperlink in it. Go to any cell and type =linkURL(A1), and then hit Enter.
function linkURL(reference) {
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=w+((.*))/i);
try {
var range = sheet.getRange(args[1]);
}
catch(e) {
throw new Error(args[1] + ' is not a valid range');
}
var formulas = range.getFormulas();
var output = [];
for (var i = 0; i < formulas.length; i++) {
var row = [];
for (var j = 0; j < formulas[0].length; j++) {
var url = formulas[i][j].match(/=hyperlink("([^"]+)"/i);
row.push(url ? url[1] : '');
}
output.push(row);
}
return output
}

Copying comments along with specific elements in Google Apps Script

I am writing a google apps script that places the paragraphs of one document into a dictionary, then inserts them into a set of other documents based on certain criteria. The paragraphs are extracted like so:
var totalElements = doc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = doc.getChild(j).copy();
var type = element.getType();
// logic to add element to dictionary
}
Later, once the location a paragraph should be inserted is found, it is inserted via the insertParagraph() function:
body.insertParagraph(paragraphIndex, element);
This works fine, except for the fact that some paragraphs have comments attached to them that need to be copied along with them, and this method does not keep comments attached to their elements. Is there any way to implement this? Thanks for any help!

Formatting in replaceText()

I've got doc.getBody().replaceText(oldregex,newstring) working fine in a Google Document script at the minute, and was hoping to set some bold/italic on newstring. This looks harder than I thought it would be. Has anyone found a tidy way to do this?
I'm currently thinking I'll need to...
Build newtext as a range with rangeBuilder
Find oldtext and select it as a range (somehow...)
Clear the oldtext range and insert the newtext range at the find location
This seems like a lot of work for something that would be trivial with HTML-like tags. I'm definitely missing something. Would really appreciate any suggestions.
Since replaceText only changes the plain text content, leaving formatting in place, the goal can be achieved by applying formatting before the replacement. First, findText goes through the text and sets bold to every match; then replaceText performs the replacement.
There are two cases to consider: only a part of text in an element is matched (which is typical) and entire element is matched. The property isPartial of RangeElement class distinguishes between these.
function replaceWithBold(pattern, newString) {
var body = DocumentApp.getActiveDocument().getBody();
var found = body.findText(pattern);
while (found) {
var elem = found.getElement();
if (found.isPartial()) {
var start = found.getStartOffset();
var end = found.getEndOffsetInclusive();
elem.setBold(start, end, true);
}
else {
elem.setBold(true);
}
found = body.findText(pattern, newString);
}
body.replaceText(pattern, newString);
}
This seems like a lot of work for something that would be trivial
This is both correct and typical for working with Google Documents using Apps Script.

Selecting text with google app script in Docs

Is it possible for an app script to highlight (as in select) text? I want to run the script from the menu and then have all matching instances of some text selected so they can be formatted in one go.
Specifically, I want to write a script to highlight all footnotes in a Google Doc so that they can be formatted simultaneously. I am the creator of the Footnote Stylist add on for Docs, which allows users to style footnotes. But I want to include the option of using any formatting, without having to include every available formatting choice in the add on itself.
How about skip the highlighting portion and just format them direct? The code below searches for the word "Testing" and bolds it & highlights it yellow. Hope this helps.
function bold() {
var body = DocumentApp.getActiveDocument().getBody();
var foundElement = body.findText("Testing");
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
// Where in the element is the found text?
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
// Set Bold
foundText.setBold(start, end, true);
// Change the background color to yellow
foundText.setBackgroundColor(start, end, "#FCFC00");
// Find the next match
foundElement = body.findText("Testing", foundElement);
}
}