How to clear formatting on a selection in TLF? - actionscript-3

I'm trying to remove the formatting of the selection and what I have so far only removes the formatting on a selection when the selection is inside a paragraph. If the selection extends to another paragraph the formatting is not removed.
Here is what I have so far:
var currentFormat:TextLayoutFormat;
var currentParagraphFormat:TextLayoutFormat;
var containerFormat:TextLayoutFormat;
var selectionStart:int;
var selectionEnd:int;
var operationState:SelectionState;
var editManager:IEditManager;
if (richEditableText.textFlow && richEditableText.textFlow.interactionManager is IEditManager) {
editManager = IEditManager(richEditableText.textFlow.interactionManager);
selectionStart = Math.min(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
selectionEnd = Math.max(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
if (operationState == null) {
operationState = new SelectionState(richEditableText.textFlow, selectionStart, selectionEnd);
}
currentFormat = editManager.getCommonCharacterFormat(operationState);
currentParagraphFormat = editManager.getCommonParagraphFormat(operationState);
containerFormat = editManager.getCommonContainerFormat(operationState);
editManager.clearFormat(currentFormat, currentParagraphFormat, containerFormat);
}

It seems that SelectionManager.getCommonCharacterFormat() doesn't quite do what I was thinking it was doing. I need to get the format of the characters that are selected and that function doesn't seem to do that.
If I get a ElementRange and then loop through it I can create a TextLayoutFormat that contains the formats on all the leaves in the element range.
var currentFormat:TextLayoutFormat;
var currentParagraphFormat:TextLayoutFormat;
var containerFormat:TextLayoutFormat;
var selectionStart:int;
var selectionEnd:int;
var operationState:SelectionState;
var editManager:IEditManager;
if (richEditableText.textFlow && richEditableText.textFlow.interactionManager is IEditManager) {
editManager = IEditManager(richEditableText.textFlow.interactionManager);
selectionStart = Math.min(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
selectionEnd = Math.max(richEditableText.selectionActivePosition, richEditableText.selectionAnchorPosition);
if (operationState == null) {
operationState = new SelectionState(richEditableText.textFlow, selectionStart, selectionEnd);
}
// following lines were change
elementRange = ElementRange.createElementRange(richEditableText.textFlow, selectionStart, selectionEnd);
currentFormat = getElementRangeFormat(elementRange);
editManager.clearFormat(currentFormat, currentParagraphFormat, containerFormat);
}
// method to get format of the selected range
public static function getElementRangeFormat(elementRange:ElementRange):TextLayoutFormat {
var leaf:FlowLeafElement = elementRange.firstLeaf;
var attr:TextLayoutFormat = new TextLayoutFormat(leaf.computedFormat);
for (;;)
{
if (leaf == elementRange.lastLeaf)
break;
leaf = leaf.getNextLeaf();
attr.concatInheritOnly(leaf.computedFormat);
}
return Property.extractInCategory(TextLayoutFormat, TextLayoutFormat.description, attr, Category.CHARACTER, false) as TextLayoutFormat;
}

Related

Tabulator - Dependent Select with Custom Editor

I need to show the list in the select box based on the another input.
On click on the SubCategory column, its showing the selected value in the dropdown. But its not showing in the table.
You can see the table in the below image. First image its not showing the data in the display, in the second image its showing the value selected
Anything missed. Any help please..
Attached the working code.
var comboEditor = function (cell, onRendered, success, cancel, editorParams) {
//Getting the other select value, based on the value this select need to show the list
let otherCellValue = cell.getData().ForumCourt;
cboData = []; //Store last values based on another cell value
var currentlyAdded = [];
var editor = document.createElement("select");
arrayOfValues2 = caselocationData.filter(function(r){return true;});
var filteredArrayOfValues = arrayOfValues2.filter(function(r){ return r[0]=== otherCellValue});
// addUniqueOptionsToDropdownList(select2_Sub, filteredArrayOfValues,1);
filteredArrayOfValues.forEach(function(r){
if(currentlyAdded.indexOf(r[0]) === -1) {
currentlyAdded.push(r[1]);
var item = {};
item.key = r;
item.name = r[1];
cboData.push(item);
}
});
for (var i = 0; i < cboData.length; i++) {
var opt = document.createElement('option');
opt.value = cboData[i].key;
opt.innerHTML = cboData[i].name;
editor.appendChild(opt);
}
editor.style.padding = "0px";
editor.style.width = "100%";
editor.style.boxSizing = "border-box";
editor.value = cell.getValue();
onRendered(function () {
editor.focus();
editor.style.css = "100%";
});
function successFunc() {
success(editor.value);
}
editor.addEventListener("change", successFunc);
editor.addEventListener("blur", successFunc);
return editor;
};

Google Apps Script: insert and update texts by ID/name in Google Docs

I want to be able to insert, in Google Docs using Google Apps Script, custom texts with a given ID, so that afterwards I'd be able to update them (any number of times). The insertion should work with cursor placement as well as with replacing any selected elements.
I have a code that works pretty well for this (based partly on this answer), see below. I use "named ranges" for IDing the inserted/updated texts. The only problem is, when I have several such inserted texts immediately next to each other, and I update both repeatedly, suddenly the preceding one "absorbs" the following one (i.e., deletes it). So clearly it is a problem of the named ranges somehow expanding into each other, but I cannot figure out why.
// function for inserting text
insertAny = (textToInsert, textName = null, range = null) => {
var doc = DocumentApp.getActiveDocument();
var cursor = doc.getCursor();
var rangeBuilder = null;
if (cursor && (range === null)) {
// Attempt to insert text at the cursor position. If the insertion returns null, the cursor's
// containing element doesn't allow insertions, so show the user an error message.
var cElement = cursor.insertText(textToInsert);
if (!cElement) {
textName = null
DocumentApp.getUi().alert('Cannot insert text here.');
} else {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(cElement);
}
} else {
var selection;
if (range === null) {
selection = DocumentApp.getActiveDocument().getSelection();
} else {
selection = range;
}
if (!selection) {
textName = null
DocumentApp.getUi().alert('Insertion omitted: A cursor placed in the text or a selected text is needed to indicate the position of the insertion.');
} else {
var elements = selection.getRangeElements();
var replace = true;
for (var i = 0; i < elements.length; i++) {
if (elements[i].isPartial()) {
var tElement = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
var text = tElement.getText().substring(startIndex, endIndex + 1);
tElement.deleteText(startIndex, endIndex);
if (replace) {
tElement.insertText(startIndex, textToInsert);
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(tElement, startIndex, startIndex + textToInsert.length - 1);
}
replace = false;
}
} else {
var eElement = elements[i].getElement();
// if not specified as "any", throws type errors for some reason
if (replace && eElement.editAsText) {
eElement.clear().asText().setText(textToInsert);
replace = false;
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(eElement);
}
} else {
if (replace && i === elements.length - 1) {
var parent = eElement.getParent();
parent[parent.insertText ? 'insertText' : 'insertParagraph'](parent.getChildIndex(eElement), textToInsert);
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(eElement);
}
replace = false; //not really necessary since it's the last one
}
eElement.removeFromParent();
}
}
}
}
}
if (textName !== null && rangeBuilder !== null) {
doc.addNamedRange(textName, rangeBuilder.build());
}
}
// function for updating text
const updateNamedRange = (textName, newText) => {
var doc = DocumentApp.getActiveDocument();
var myNamedRanges = doc.getNamedRanges(textName);
for (var i = 0; i < myNamedRanges.length; i++) {
var range = myNamedRanges[i].getRange();
insertAny(newText, textName, range);
}
}
Any ideas (or better solutions)?
Okay, so it seems that the reason is that if I insert text immediately next to a named range, it will automatically belong to that range. (Hence subsequent updates affected these unrelated parts too.)
My really hacky solution is to temporarily insert a placeholder character to separate the new text from any potential named ranges... It makes me laugh, but nothing else I tried works as well. This seems to be robust to all the tricky scenarios I can think of. My final code is below.
const insertAny = (textToInsert, textName = null, range = null) => {
var doc = DocumentApp.getActiveDocument();
var cursor = doc.getCursor();
var rangeBuilder = null;
if (cursor && (range === null)) {
// Attempt to insert text at the cursor position. If the insertion returns null, the cursor's
// containing element doesn't allow insertions, so show the user an error message.
var cElement = cursor.insertText(textToInsert);
if (!cElement) {
textName = null
DocumentApp.getUi().alert('Cannot insert text here.');
} else {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(cElement);
}
} else {
var selection;
if (range === null) {
selection = DocumentApp.getActiveDocument().getSelection();
} else {
selection = range;
}
if (!selection) {
textName = null
DocumentApp.getUi().alert('Insertion omitted: A cursor placed in the text or a selected text is needed to indicate the position of the insertion.');
} else {
var elements = selection.getRangeElements();
if (range !== null) {
elements.length = 1;
}
var replace = true;
for (var i = 0; i < elements.length; i++) {
if (elements[i].isPartial()) {
var tElement = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
var text = tElement.getText().substring(startIndex, endIndex + 1);
if (replace) {
tElement.insertText(endIndex + 1, 'x');
tElement.deleteText(startIndex, endIndex);
tElement.insertText(startIndex + 1, textToInsert);
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(tElement, startIndex + 1, startIndex + 1 + textToInsert.length - 1);
}
replace = false;
tElement.deleteText(startIndex, startIndex);
} else {
tElement.deleteText(startIndex, endIndex);
}
} else {
var eElement = elements[i].getElement();
// if not specified as "any", throws type errors for some reason
if (replace && eElement.editAsText) {
eElement.clear().asText().setText(textToInsert);
replace = false;
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(eElement);
}
} else {
if (replace && i === elements.length - 1) {
var parent = eElement.getParent();
parent[parent.insertText ? 'insertText' : 'insertParagraph'](parent.getChildIndex(eElement), textToInsert);
if (rangeBuilder === null) {
rangeBuilder = doc.newRange();
rangeBuilder.addElement(eElement);
}
replace = false; //not really necessary since it's the last one
}
eElement.removeFromParent();
}
}
}
}
}
if (textName !== null && rangeBuilder !== null) {
doc.addNamedRange(textName, rangeBuilder.build());
}
}
const updateNamedRange = (textName, newText) => {
var doc = DocumentApp.getActiveDocument();
var myNamedRanges = doc.getNamedRanges(textName);
for (var i = 0; i < myNamedRanges.length; i++) {
var range = myNamedRanges[i].getRange();
myNamedRanges[i].remove();
insertAny(newText, textName, range);
}
}

Google Apps Script. Get all links from document

Hi all) I need to get all links from google document. I found that general approach:
function getAllLinks(element) {
var links = [];
element = element || DocumentApp.getActiveDocument().getBody();
if (element.getType() === DocumentApp.ElementType.TEXT) {
var textObj = element.editAsText();
var text = element.getText();
Logger.log("text " + text);
var inUrl = false;
for (var ch=0; ch < text.length; ch++) {
var url = textObj.getLinkUrl(ch);
if (url != null) {
if (!inUrl) {
// We are now!
inUrl = true;
var curUrl = {};
curUrl.element = element;
curUrl.url = String( url ); // grab a copy
curUrl.startOffset = ch;
}
else {
curUrl.endOffsetInclusive = ch;
}
}
else {
if (inUrl) {
// Not any more, we're not.
inUrl = false;
links.push(curUrl); // add to links
curUrl = {};
}
}
}
}
else {
var numChildren = element.getNumChildren();
for (var i=0; i<numChildren; i++) {
links = links.concat(getAllLinks(element.getChild(i)));
}
}
Logger.log(links);
}
It works perfectly fine if i, for example, type url in text, but if add link via menu ("Insert" -> "Link") it doesn't work, function getLinkUrl() returns null. Documentation contains info about Link class, i thought all links represented by it, but don't understand why i can't get link inserted via menu.
I thought maybe i can use some regular expression on text of document element, but if i add link via menu item i can specify custom label for link, which may not contain url in it.
Have anyone faced this scenario? What i missed?

Retrieve input values from dinamically genrated HTMLElement form?

I'm currently on an Angular project (I'm a begineer) and I created a function to create a HTML form depending on the content of a JSON (the form then allows me to push new data in the JSON). It works, displays the form, but I didn't think ahead, and don't know how to retrieve the input values from the user.
createNewLineForm() {
if (this.allowForm) {
let indiceList = 0;
this.formOn = true;
for (let jsonCol of this.columnData) {
if (jsonCol.visible === true && jsonCol.type === "Boolean") {
var content = document.getElementById("formcontent");
var listTitle = document.createElement("header");
listTitle.textContent = jsonCol.name;
content.appendChild(listTitle);
var selectList = document.createElement("select");
selectList.id = "trueFalse";
content.appendChild(selectList);
var option = document.createElement("option");
option.value = "true";
option.text = "true";
selectList.appendChild(option);
var option2 = document.createElement("option");
option2.value = "false";
option2.text = "false";
selectList.appendChild(option2)
}
if (jsonCol.visible === true && jsonCol.type === "String" && jsonCol.dropdownList === null) {
var content = document.getElementById("formcontent");
var inputTitle = document.createElement("header");
inputTitle.textContent = jsonCol.name;
content.appendChild(inputTitle);
var formInput = document.createElement("input");
formInput.type = "text";
formInput.placeholder = jsonCol.name;
formInput.name = "text";
content.appendChild(formInput);
}
if (jsonCol.visible === true && jsonCol.type === "String" && jsonCol.dropdownList != null) {
var content = document.getElementById("formcontent");
var listTitle = document.createElement("header");
listTitle.textContent = jsonCol.name;
content.appendChild(listTitle);
var selectListLong = document.createElement("select");
selectListLong.name ="longList";
indiceList++;
content.appendChild(selectListLong);
for (const options of jsonCol.dropdownList) {
var option = document.createElement("option");
option.value = options;
option.text = options;
selectListLong.appendChild(option);
}
}
}
var submit = document.createElement("input");
submit.type = "submit";
content.appendChild(submit);
}
}
It's then conditionnally called in my HTML window with a simple <div id="formcontent"> </div>
I tried using document.getElementByName('...').value, doesn't seem to work.
Btw, if you have another idea on how to make that form, I'd like hearing it as well, this method seems a bit messy I think.
var content = document.getElementById("formcontent").value;
You can try it by ID

How to insert a paragraph object in listItem preserving the formating of each word of the paragraph?

Following the documentation sample, I'm trying to create a function that search for a numerated list in a google document and, if it finds it, adds a new item to the list. My code works well (thanks to #Serge insas for previous help) with strings, but not with paragraphs objects. I know I could get the paragraph text and add it to listItem, but then I lose the formating. Is there a way to insert a paragraph preserving all it's formating? (I know I could use var newElement = child.getParent().insertListItem(childIndex, elementContent.getText()) do insert text without words formating)
Here the code:
function test() {
var targetDocId = "1A02VhxOWLUIdl8LTV1tt2S1yASDbOq77VbsUpxPa6vk";
var targetDoc = DocumentApp.openById(targetDocId);
var body = targetDoc.getBody();
var elementContent = targetDoc.getChild(2); // a paragraph with its formating
var childIndex = 0;
for (var p= 0; p< targetDoc.getNumChildren(); p++) {
var child = targetDoc.getChild(p);
if (child.getType() == DocumentApp.ElementType.LIST_ITEM){
while(child.getType() == DocumentApp.ElementType.LIST_ITEM){
child = targetDoc.getChild(p)
Logger.log("child = " + child.getText())
childIndex = body.getChildIndex(child);
Logger.log(childIndex)
p++
}
child = targetDoc.getChild(p-2);
var listId = child.getListId();
if (child.getText() == '') {
childIndex = childIndex -1;
}
Logger.log(childIndex)
var newElement = child.getParent().insertListItem(childIndex, elementContent);
newElement.setListId(child);
var lastEmptyItem = targetDoc.getChild(childIndex +1).removeFromParent();
break;
}
Here a screen shot of my targetDoc (note the second item, Paragraph):
I know this question is old, but I've come up with a solution and will leave here for anyone that may need it. It is not complete, as I have yet to find a way to copy any Inline Drawing and Equation to a new element...
Anyways, here is my code, it will work well if the paragraph you want to convert to a list item only has text and Inline Images.
function parToList() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
//gets the paragraph at index 1 on body -> can be changed to what you want
var par = body.getChild(1);
var childs = [];
for (var i = 0; i<par.getNumChildren(); i++) {
var child = par.getChild(0);
childs.push(child);
child.removeFromParent();
};
par.removeFromParent();
//puts the list item on index 1 of body -> can be changed to the wanted position
var li = body.insertListItem(1, "");
childs.reverse();
for (var j in childs) {
var liChild = childs[j];
var childType = liChild.getType();
if (childType == DocumentApp.ElementType.EQUATION) {
//still need to find a way to append an equation
} else if (childType == DocumentApp.ElementType.INLINE_DRAWING) {
//still need to find a way to append an inlineDrawing
} else if (childType == DocumentApp.ElementType.INLINE_IMAGE) {
li.appendInlineImage(liChild);
} else if (childType == DocumentApp.ElementType.TEXT) {
li.appendText(liChild);
};
};
};
Cheers