Skip to content

Commit

Permalink
issue #351
Browse files Browse the repository at this point in the history
- fixed unreliable processing of *selection* within smart snippet
  • Loading branch information
RealRaven2000 committed Jan 6, 2025
1 parent d2d2e63 commit 824cee2
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 88 deletions.
1 change: 1 addition & 0 deletions chrome/content/scripts/smartTemplate-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pref("extensions.smartTemplate4.debug.timeZones", false);
pref("extensions.smartTemplate4.debug.timeStrings", false);
pref("extensions.smartTemplate4.debug.headers", false);
pref("extensions.smartTemplate4.debug.sandbox", false);
pref("extensions.smartTemplate4.debug.snippets", false);
pref("extensions.smartTemplate4.debug.notifications", false);
pref("extensions.smartTemplate4.debug.notifications.menus", false);
pref("extensions.smartTemplate4.debug.premium.licenser", false);
Expand Down
295 changes: 221 additions & 74 deletions chrome/content/smartTemplate-compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -1896,89 +1896,236 @@ SmartTemplate4.classSmartTemplate = function() {
editor.enableUndo(true);
}
};

function logSelectionHtml(selection) {
// Grab the content of the selection as a DocumentFragment
if(!selection) {
console.log("Nothing selected before processing!");
return;
}
try {
const range = selection.getRangeAt(0);
const fragment = range.cloneContents();

// Create a temporary container to hold the fragment's content
const tempDiv = gMsgCompose.editor.document.createElement("div");
tempDiv.appendChild(fragment);

// Log the HTML as a string
console.log("Selected HTML before processing:", tempDiv.innerHTML);
} catch(ex) {
SmartTemplate4.Util.logException("logSelectionHtml", ex);
}

}

// returns html code from selection in composer.
function unpackSelection(selection) {
let html = "";
const isDebug = SmartTemplate4.Preferences.isDebugOption("snippets");

for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
const { startContainer, endContainer, startOffset, endOffset } = range;

if (isDebug) {
logSelectionHtml(selection);
console.log({
startContainer,
endContainer,
ancestor: range.commonAncestorContainer,
startOffset,
endOffset,
});
}

// Handle selection within a single container
if (startContainer === endContainer) {
// Handle element nodes and text nodes inside the same container
switch (startContainer.nodeType) {
case 1: // ELEMENT_NODE
for (let i = 0; i < startContainer.childNodes.length; i++) {
if (i < startOffset || i > endOffset) continue;
const node = startContainer.childNodes[i];
if (node.nodeType === 1) {
// Handle element nodes (including <img> tags)
if (["IMG", "VIDEO", "AUDIO"].includes(node.tagName)) {
html += node.outerHTML;
} else {
html += node.outerHTML; // Other element nodes
}
} else if (node.nodeType === 3) {
// Handle text nodes
html += node.textContent;
}
}
break;
case 3: // TEXT_NODE
html += startContainer.textContent.substring(startOffset, endOffset);
break;
default:
break;
}
continue; // Skip to next range
}

const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ALL, {
acceptNode: (node) => NodeFilter.FILTER_ACCEPT,
});

const processedNodes = []; // Array to track processed nodes
let node = walker.currentNode;

while (node) {
const insideRange = range.isPointInRange(node, 0);

if (insideRange) {
if (node === endContainer) {
// If we've reached the endContainer, stop processing
if (node.nodeType === Node.TEXT_NODE) {
if (!processedNodes.includes(node.parentNode)) {
const partialText = node.textContent.substring(0, endOffset);
if (isDebug) console.log("Partial text added:", partialText);
html += partialText; // Add the remaining part of the text node
} else {
if (isDebug)
console.log(
"Skipping text node inside already processed parent:",
node.parentNode
);
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === "BR") {
if (isDebug) console.log("Skipping extra <br> node at endContainer.");
} else {
if (isDebug) console.log("Element node content (outerHTML):", node.outerHTML);
html += node.outerHTML; // Add the last element node
processedNodes.push(node); // Mark this element as processed
}
}
if (isDebug) console.log("HTML after endContainer:", html);
break; // Exit the loop after processing the endContainer
}

// Process element nodes
if (node.nodeType === Node.ELEMENT_NODE) {
html += node.outerHTML; // Add the element's HTML
processedNodes.push(node); // Mark this element as processed
}

// Process text nodes
if (node.nodeType === Node.TEXT_NODE) {
if (!processedNodes.includes(node.parentNode)) {
html += node.textContent; // Add the text content
} else {
if (isDebug){
console.log("Skipping text node inside already processed parent:", node.parentNode);
}
}
}
}
// Move to the next node
node = walker.nextNode();
}

}
if (isDebug) {
console.log("unpackSelection() created the following markup:\n", html);
}
return html.trim();
}




// Helper function to process a range inside an element node
function processElementRange(range) {
let html = "";
const container = range.startContainer;

// Iterate over child nodes within the range
container.childNodes.forEach((node, index) => {
if (index < range.startOffset || index >= range.endOffset) {
return; // Skip nodes outside the range
}
html += node.nodeType === Node.ELEMENT_NODE
? node.outerHTML
: node.textContent;
});

return html;

}

// returns html code from selection in composer.
function unpackSelection(selection) {
// debugger;
let aOf, fOf;
let isFocusDifferent = false;
let range = selection.getRangeAt(0);
if (!range.startContainer)
return range.toString();

// Helper function to process a range inside a text node
function processTextRange(range) {
const { startContainer, endContainer, startOffset, endOffset } = range;

if (startContainer === endContainer) {
// Single text node
return startContainer.textContent.substring(startOffset, endOffset);
}

let html = "";
let started = false;

// Iterate through nodes within the common ancestor container
const ancestor = range.commonAncestorContainer;
ancestor.childNodes.forEach((node) => {
if (!started && node === startContainer) {
// Start from the start offset
started = true;
html += node.textContent.substring(startOffset);
} else if (started) {
if (node === endContainer) {
// Stop at the end offset
html += node.textContent.substring(0, endOffset);
return;
}

// Append full content of intermediate nodes
html += node.nodeType === Node.ELEMENT_NODE
? node.outerHTML
: node.textContent;
}
});

return html;
}

// Helper function to process a range inside a text node
function unpackSelection_legacy(selection) {
if (!selection || selection.rangeCount === 0) {
return ""; // No selection
}

let html = "";
let ranges = [];
for(let i = 0; i < selection.rangeCount; i++) {
let r = selection.getRangeAt(i);
ranges.push(r);
// we assume start of selection has same nodeType as end
// so we can span across text nodes or surround elements with an outer element
switch (r.startContainer.nodeType) {
case 1: // ELEMENT_NODE
for (let i=0; i<r.startContainer.childNodes.length; i++) {
if (i<r.startOffset || i>r.endOffset) continue;
if (r.startContainer.childNodes[i].nodeType == 1) {
html += r.startContainer.childNodes[i].outerHTML;
}
else if (r.startContainer.childNodes[i].nodeType == 3) {
html += r.startContainer.childNodes[i].textContent;
}
}
break;
case 3: // TEXT_NODE
if (r.endContainer==r.startContainer) {
if (r.endOffset) {
html += r.startContainer.textContent.substring(r.startOffset, r.endOffset);
}
else {
html += r.startContainer.textContent.substring(r.startOffset);
}
}
else {
html += r.startContainer.textContent.substring(r.startOffset);
let ns = r.startContainer.nextSibling;
if (ns) {
switch (ns.nodeType) {
case 1: // ELEMENT_NODE
html += ns.outerHTML;
break;
case 3: // TEXT_NODE
html += ns.textContent;
break;
}
}
if (r.endOffset) {
html += r.endContainer.textContent.substring(0,r.endOffset);
}
else
html += r.endContainer.textContent;
}
aOf = 0;
break;

// Process each range in the selection
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);

if (!range.startContainer) {
html += range.toString();
continue;
}
}
/*
aOf = selection.anchorOffset;
if (selection.focusNode != selection.anchorNode) {
isFocusDifferent = true;
fOf = selection.focusOffset;
}

if (isFocusDifferent) {
switch (selection.focusNode.nodeType) {
case 1: // ELEMENT_NODE
html += selection.focusNode.outerHTML;
// Handle text and element nodes
switch (range.startContainer.nodeType) {
case Node.ELEMENT_NODE:
html += processElementRange(range);
break;
case 3: // TEXT_NODE
html += selection.focusNode.textContent.substring(0,fOf);
aOf = 0;
case Node.TEXT_NODE:
html += processTextRange(range);
break;
default:
console.warn("Unsupported node type:", range.startContainer.nodeType);
}
}
*/
return html;

return html.trim(); // Return the combined HTML
}


// -----------------------------------
// Constructor
Expand Down
38 changes: 25 additions & 13 deletions chrome/content/smartTemplate-fileTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,34 +1757,46 @@ SmartTemplate4.fileTemplates = {
flags.isFragment = true;
flags.isFileTemplate = true;
if (!flags.filePaths) flags.filePaths = [];
SmartTemplate4.Util.logDebugOptional("fileTemplates", `insertFileEntryInComposer: Add file to template stack: ${theFileTemplate.path}`);
SmartTemplate4.Util.logDebugOptional(
"fileTemplates",
`insertFileEntryInComposer: Add file to template stack: ${theFileTemplate.path}`
);
flags.filePaths.push(theFileTemplate.path); // remember the path. let's put it on a stack.
let idkey = SmartTemplate4.Util.getIdentityKey(document);
const ignoreHTML = true;
let code;
if (isFormatCSS) { // [issue 238]
if (isFormatCSS) {
// [issue 238]
// create a style block!
let lastUnixPos = theFileTemplate.path.lastIndexOf('/'),
lastWindowsPos = theFileTemplate.path.lastIndexOf('\\'),
pos = Math.max(lastUnixPos, lastWindowsPos),
name = theFileTemplate.path.substring(pos+1);
let lastUnixPos = theFileTemplate.path.lastIndexOf("/"),
lastWindowsPos = theFileTemplate.path.lastIndexOf("\\"),
pos = Math.max(lastUnixPos, lastWindowsPos),
name = theFileTemplate.path.substring(pos + 1);
code = `
<!-- ${name} -->
<style>
${html}
</style>
`;
} else {
code = await SmartTemplate4.smartTemplate.getProcessedText(
html,
idkey,
SmartTemplate4.Util.getComposeType(),
ignoreHTML
);
}
else {
code = await SmartTemplate4.smartTemplate.getProcessedText(html, idkey, SmartTemplate4.Util.getComposeType(), ignoreHTML);
}
gMsgCompose.editor.insertHTML(code);
// we should probably place the cursor at the end of the inserted HTML afterwards!

gMsgCompose.editor.insertHTML(code);

let popped = flags.filePaths.pop();
SmartTemplate4.Util.logDebugOptional("fileTemplates", `insertFileEntryInComposer: Removed file from template stack: ${popped}`);
// we should probably place the cursor at the end of the inserted HTML afterwards!

let popped = flags.filePaths.pop();
SmartTemplate4.Util.logDebugOptional(
"fileTemplates",
`insertFileEntryInComposer: Removed file from template stack: ${popped}`
);
}
} ,

Expand Down
3 changes: 2 additions & 1 deletion chrome/content/smartTemplate-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,11 @@ END LICENSE BLOCK
# [issue 331] Add features to insert unquoted email and remove styles using %quotePlaceHolder% (implemented in 4.9)
# [issue 340] Fixed: Common settings are not stored
# [issue 343] Improved Capitalization for double-barrelled names (such as Tyler-Smith)
# [issue ] deal with removal of MozElements.NotificationBox.shown()
# [issue ] Compatibility: deal with removal of MozElements.NotificationBox.shown()
# [issue 344] %header.set(subject,clipboard)% and %matchTextFromBody(..,toclipboard)% fail at commas
# [issue 349] Support "composite" ST variables in sandboxed script (e.g. header.set)
# [issue 350] Support multiple parameters in sandboxed script (e.g. %from(name,uppercase)% )
# [issue 351] Fixed: *selection* truncates content in text nodes
=========================
Expand Down

0 comments on commit 824cee2

Please sign in to comment.