' string, in this case the parent to be reconstructed goes to level higher
so in this function we will figure out if we are closing parent tags and accordingly go higher in order.
for this we will write a stack to figure out if we closing tags that we did not open
Eg: src="1.png">
asdfasdfasdf
The regex will create >,
![]()
,,
,
,
The first for loop will try fixing up '>', if an opening tag is found before '>', we will delete it
hence after first loop: >,
![]()
,
![]()
& ]+|>/g);
var matchArray = str.match(re);
if (matchArray) {
var index;
for (index = 1; index < matchArray.length; ++index) {
var matchStr = matchArray[index];
var matchPrev = matchArray[index - 1];
if (matchStr === ">" && matchPrev.charAt(0) === '<') {
matchArray.splice(index, 1);
--index;
}
}
for (index = 1; index < matchArray.length; ++index) {
if (matchArray[index].indexOf("/") !== -1) {
var startMatch = matchArray[index].toLowerCase().replace(/\//g, '');
var revIndex;
for (revIndex = index - 1; revIndex >= 0; --revIndex) {
var match = matchArray[revIndex].toLowerCase();
if (match === startMatch) {
var diff = index - revIndex + 1;
matchArray.splice(revIndex, diff);
index = revIndex - 1;
break;
}
}
}
}
for (index = 0; index < matchArray.length; ++index) {
var currentMatch = matchArray[index];
if (currentMatch.indexOf("/") !== -1 || currentMatch === ">") {
++count;
}
}
}
}
return count;
},
findPartialDOM: function (startOffset, endOffset, editString) {
'use strict';
var commonParent = DW_PR_Utils.getParentForOffset(startOffset, endOffset);
var mismatch = DW_PR_Utils.getOrphanClosingTagCount(editString);
while (commonParent && mismatch > 0) {
commonParent = commonParent.parent;
--mismatch;
}
return commonParent;
},
buildNodeMap : function (root) {
'use strict';
var nodeMap = {};
function walk(node) {
if (node.tagID) {
nodeMap[node.tagID] = node;
}
if (node.isElement()) {
node.children.forEach(walk);
}
}
walk(root);
root.nodeMap = nodeMap;
},
handleDeletions : function (nodeMap, oldSubtreeMap, newSubtreeMap) {
'use strict';
var deletedIDs = [];
Object.keys(oldSubtreeMap).forEach(function (key) {
if (!newSubtreeMap.hasOwnProperty(key)) {
deletedIDs.push(key);
delete nodeMap[key];
}
});
},
//it adds the delta to start and end offsets if the offset is greater than the pivot
//keep pivot=0 if you want it to apply for all offsets
updateOffsetsWithDelta : function (root, delta, pivot) {
'use strict';
var nodeMap = root.nodeMap;
var nodeID = null;
for (nodeID in nodeMap) {
if (nodeMap.hasOwnProperty(nodeID)) {
var node = nodeMap[nodeID];
if (node.start !== undefined && node.start >= pivot) {
node.start += delta;
}
if (node.end !== undefined && node.end >= pivot) {
node.end += delta;
}
}
}
}
};
//called as soon as the doc is loaded, it takes in the entire code source as parameter
//this acts as a base upon which all edits are applied
//here we construct the SimpleDOM wrt to the source and keep it
function DW_LiveEdit_SetupDoc(source) {
'use strict';
source = source.replace(/\r/g, '');
DW_LiveEdit_LiveSource = source;
DW_LiveEdit_IDManagerInstance = new DW_LiveEdit_IDManager();
var Builder = window.DW_HTMLSimpleDOM().Builder;
var builder = new Builder(DW_LiveEdit_LiveSource, 0, null);
builder.getID = function (newTag) {
return DW_PR_Utils.findID(newTag, DW_LiveEdit_LiveSource, 0, true);
};
var markCache = {};
DW_LiveEdit_CurrentSimpleDOM = builder.build(false, markCache);
if (!DW_LiveEdit_CurrentSimpleDOM) {
//Inform Dreamweaver to keep a note that this document failed to Partial refresh
var argObj = {};
console.error('Could not create SImpleDOM structure mostly cause of malformed HTML');
}
//window.DWNotifyPartialRefreshComplete(); //this serves dual purpose, in MAC the AJAX call messes up selection sync info when we ask for the HTML doc.
//Also when we do a full refresh, we need to resync the source of LiveDoc and recreate the stylesheet. This call we do the same.
}
//Upon an edit, we pass the startOffset and endOffset of the edit wrt to the base string prior to the edit
//also we pass the new string if any. First we construct the new string and its corresponding SimpleDOM
//compare it with the earlier DOM to generate the edits and finally pass it along
function DW_LiveEdit_DoEdit(startOffset, endOffset, editString, shouldOnlyUpdate, fullRefreshIfFailed) {
'use strict';
var newString = "";
if (DW_LiveEdit_LiveSource) {
newString = DW_LiveEdit_LiveSource.slice(0, startOffset) + editString + DW_LiveEdit_LiveSource.slice(endOffset); //modified document string
}
if (!DW_LiveEdit_CurrentSimpleDOM || DW_LiveEdit_LiveSource.length === 0) {
//Should never reach here, just in case we do let us refresh the browser
// Notify Dreamweaver to put the files on server before reloading the page.
if (!shouldOnlyUpdate) {
if (fullRefreshIfFailed) {
window.DWApplyDOMEditsHasInstruction();
} else if (DW_LiveEdit_LiveSource && newString) {
//if we are typing and we do not have a structure
//keep tryin if we can get to correct structure
//once you do do a refresh so that all structures are reset
var TempBuilder = window.DW_HTMLSimpleDOM().Builder;
var tempInstance = new TempBuilder(newString, 0, null);
tempInstance.getID = function (newTag) {
return DW_PR_Utils.findID(newTag, newString, 0, true);
};
var tempDOM = tempInstance.build(true, {});
if (tempDOM) {
window.DWApplyDOMEditsHasInstruction(); //refresh call
}
DW_LiveEdit_LiveSource = newString;
}
}
return;
}
var delta = endOffset - startOffset - editString.length; //length adjustment of edit
var endBoundary = endOffset - delta; //for the first time that we store fail offsets
if (DW_LiveEdit_Failed_Offsets.start !== -1 && DW_LiveEdit_Failed_Offsets.end !== -1 && DW_LiveEdit_LastSucessString) {
//Assume we failed to sync code view due to errors, we need to retry the same code next time we get an edit.
//Hence we need to expand the offset to include prior failures
startOffset = startOffset < DW_LiveEdit_Failed_Offsets.start ? startOffset : DW_LiveEdit_Failed_Offsets.start; //everything before startOffset is original string
endBoundary = (endOffset > DW_LiveEdit_Failed_Offsets.end ? endOffset : DW_LiveEdit_Failed_Offsets.end) - delta; //everything beyond endBoundary is original string
var endMatchLength = newString.length - endBoundary;
//following are the adjusted values as per the original success string
endOffset = DW_LiveEdit_LastSucessString.length - endMatchLength;
editString = newString.slice(startOffset, endBoundary);
delta = endOffset - startOffset - editString.length;
}
if (DW_LiveEdit_PartialRefreshIsSuspended) {
if (DW_LiveEdit_Failed_Offsets.start === -1 && DW_LiveEdit_Failed_Offsets.end === -1) {
DW_LiveEdit_LastSucessString = DW_LiveEdit_LiveSource; //we will store it on first failure only
}
DW_LiveEdit_LiveSource = newString; // we are not updating, but we need to save the changes
DW_LiveEdit_ReplacedDOM = null;
DW_LiveEdit_Failed_Offsets = { "start": startOffset, "end" : endBoundary};
return;
}
DW_LiveEdit_IDManagerInstance.updateID(startOffset, endOffset, -delta);
var prevSubDOM = DW_PR_Utils.findPartialDOM(startOffset, endOffset, editString);
var diff = null,
incrementalSuccess = false, /*bool to check if we were successful in doing an incremental update*/
updateSuccess = false; /*bool to check if we were able to create an updated SimpleDOM structure using the current edit and apply the same*/
var Builder = window.DW_HTMLSimpleDOM().Builder;
var updateStructures = null;
if (prevSubDOM && prevSubDOM.parent) {
var newSubString = newString.slice(prevSubDOM.start, prevSubDOM.end - delta);
var parent = prevSubDOM.parent;
var incBuilder = new Builder(newSubString, 0, null);
incBuilder.getID = function (newTag) {
return DW_PR_Utils.findID(newTag, newSubString, prevSubDOM.start, shouldOnlyUpdate);
};
var nextSubDOM = incBuilder.build(!fullRefreshIfFailed, {});
if (parent && nextSubDOM) {
var childIndex = parent.children.indexOf(prevSubDOM);
if (childIndex === -1) {
// This should never happen...
console.error("couldn't locate old subtree in tree");
} else {
//update offsets as per the edit
DW_PR_Utils.updateOffsetsWithDelta(nextSubDOM, prevSubDOM.start, 0);
// Build a local nodeMap for the old subtree so the differ can
// use it.
DW_PR_Utils.buildNodeMap(prevSubDOM);
updateStructures = function () {
// Swap the new subtree in place of the old subtree.
prevSubDOM.parent = null;
nextSubDOM.parent = parent;
parent.children[childIndex] = nextSubDOM;
//update offsets as per the edit
DW_PR_Utils.updateOffsetsWithDelta(DW_LiveEdit_CurrentSimpleDOM, -delta, startOffset);
// Overwrite any node mappings in the parent DOM with the
// mappings for the new subtree. We keep the nodeMap around
// on the new subtree so that the differ can use it later.
$dwJQuery.extend(DW_LiveEdit_CurrentSimpleDOM.nodeMap, nextSubDOM.nodeMap);
// Clean up the info for any deleted nodes that are no longer in
// the new tree.
DW_PR_Utils.handleDeletions(DW_LiveEdit_CurrentSimpleDOM.nodeMap, prevSubDOM.nodeMap, nextSubDOM.nodeMap);
// Update the signatures for all parents of the new subtree.
var curParent = parent;
while (curParent) {
curParent.update();
curParent = curParent.parent;
}
};
if (!shouldOnlyUpdate) {
diff = window.DW_HTMLDOMDiff().domdiff(prevSubDOM, nextSubDOM);
}
incrementalSuccess = true;
updateSuccess = true;
DW_LiveEdit_ReplacedDOM = prevSubDOM;
}
}
}
//incase we couldn't success fully apply an inceremental update, rebuild the DOM and do a complete diff though exoensive
if (!incrementalSuccess) {
var domStart = DW_LiveEdit_CurrentSimpleDOM.start + DW_LiveEdit_CurrentSimpleDOM.tag.length; //considering start of tag including name
var domEnd = DW_LiveEdit_CurrentSimpleDOM.end - (DW_LiveEdit_CurrentSimpleDOM.tag.length + 3); // considering entire end tags, 3 represents the characters ">"
if (startOffset < domStart || endOffset > domEnd) {
//This happens when the source is broken and we are having a tree that does not contain things fully
//In this case we can find edits of children of the main tree only
// Notify Dreamweaver to put the files on server before reloading the page.
if (fullRefreshIfFailed) {
if (shouldOnlyUpdate) {
DW_LiveEdit_CurrentSimpleDOM = null; //Partial Refresh has failed, but ELV operation has happened fine
//however before next partial refresh we want a full refresh
} else {
// Notify Dreamweaver to put the files on server before reloading the page.
window.DWApplyDOMEditsHasInstruction();
}
}
return;
}
var fullBuilder = new Builder(newString, 0, null);
fullBuilder.getID = function (newTag) {
return DW_PR_Utils.findID(newTag, newString, 0, shouldOnlyUpdate);
};
var newDOM = fullBuilder.build(!fullRefreshIfFailed, {});
if (newDOM) {
if (!shouldOnlyUpdate) {
diff = window.DW_HTMLDOMDiff().domdiff(DW_LiveEdit_CurrentSimpleDOM, newDOM);
}
DW_LiveEdit_ReplacedDOM = DW_LiveEdit_CurrentSimpleDOM;
updateStructures = function () {
DW_LiveEdit_CurrentSimpleDOM = newDOM;
};
updateSuccess = true;
}
}
if (diff) {
//We successfully generated edits implies update has been successful so far
updateSuccess = window.DW_RemoteFunctions().applyDOMEdits(diff); //we will get a false we were unable to find the element upon which the edit had to be done
}
if (updateSuccess) {
if (updateStructures) {
updateStructures();
}
DW_LiveEdit_IDManagerInstance.clearTemp();
DW_LiveEdit_LiveSource = newString; // we were able to update our structures hence it is safe to update the source string
DW_LiveEdit_ReplacedDOM = null;
DW_LiveEdit_Failed_Offsets = { "start": -1, "end" : -1};
DW_LiveEdit_LastSucessString = null;
} else if (fullRefreshIfFailed) {
if (window.DW_RemoteFunctions().applyDOMEditsHasInstruction()) {
window.DW_RemoteFunctions().resetApplyDOMEditsHasInstruction();
}
if (shouldOnlyUpdate) {
DW_LiveEdit_CurrentSimpleDOM = null; //Partial Refresh has failed, but ELV operation has happened fine
//however before next partial refresh we want a full refresh
} else {
// Notify Dreamweaver to put the files on server before reloading the page.
window.DWApplyDOMEditsHasInstruction();
}
} else {
if (DW_LiveEdit_Failed_Offsets.start === -1 && DW_LiveEdit_Failed_Offsets.end === -1) {
DW_LiveEdit_LastSucessString = DW_LiveEdit_LiveSource; //we will store it on first failure only
}
DW_LiveEdit_IDManagerInstance.clearTemp();
DW_LiveEdit_LiveSource = newString; // we failed to update, but we need to save the changes
DW_LiveEdit_ReplacedDOM = null;
DW_LiveEdit_Failed_Offsets = { "start": startOffset, "end" : endBoundary};
}
}
function DW_LiveEdit_SuspendPartialRefresh() {
'use strict';
DW_LiveEdit_PartialRefreshIsSuspended = true;
//word of caution for using this function, it is caller's responsibilty to resume!
}
function DW_LiveEdit_ResumePartialRefreshIfSuspended() {
'use strict';
DW_LiveEdit_PartialRefreshIsSuspended = false;
if (DW_LiveEdit_Failed_Offsets.start !== -1 && DW_LiveEdit_Failed_Offsets.end !== -1 && DW_LiveEdit_LastSucessString) {
window.DW_LiveEdit_DoEdit(DW_LiveEdit_Failed_Offsets.start, DW_LiveEdit_Failed_Offsets.start, "", false, true); //Dummy call, we apply edits received in middle
}
//On resume we will try committing the changes and if we fail, we will trigger a full refresh, don't use this if that is not what you want.
}
var DW_RefreshCSS = {
//Recursive logic that finds the appropriate stysheet based on the path
findStyleSheetImpl : function (styleObject, path) {
'use strict';
if (styleObject.href && path) {
var stylePath = styleObject.href.replace(/:/g, '|').replace(/\|/g, '%7C');
var rulePath = path.replace(/:/g, '|').replace(/\|/g, '%7C');
if (rulePath.indexOf(stylePath) !== -1) {
return styleObject;
}
}
if (!styleObject.cssRules) {
return null;
}
var index = 0;
for (index = 0; index < styleObject.cssRules.length; ++index) {
var retVal = null, rule = styleObject.cssRules[index];
if (rule.type === CSSRule.IMPORT_RULE) {
retVal = DW_RefreshCSS.findStyleSheetImpl(rule.styleSheet, path);
}
if (retVal) {
return retVal;
}
}
},
//wrapper for the above recursive function
findStyleSheet : function (path) {
'use strict';
var currentStyleSheet = null, index = 0;
for (index = 0; index < document.styleSheets.length; ++index) {
currentStyleSheet = DW_RefreshCSS.findStyleSheetImpl(document.styleSheets[index], path);
if (currentStyleSheet) {
break;
}
}
return currentStyleSheet;
},
//CEF is nuts! when we apply a media query and ask it back for the string, it jumbles it up. Hence we also give it our version and get it back
sanitizeMediaString : function (mQuery) {
'use strict';
var ret = "";
var cssNode = document.createElement('style');
cssNode.type = 'text/css';
cssNode.rel = 'stylesheet';
document.head.appendChild(cssNode);
var styleObj = cssNode.sheet;
if (styleObj && styleObj.insertRule && styleObj.deleteRule && styleObj.cssRules) {
var ruleText = "@media " + mQuery + " {}";
styleObj.insertRule(ruleText, 0);
ret = styleObj.cssRules[0].media.mediaText;
styleObj.deleteRule(0);
}
document.head.removeChild(cssNode);
return ret;
},
compareFullRule : function (string1, string2) {
'use strict';
var ret = false;
if (string1 && string2) {
string1 = string1.toLowerCase();
string2 = string2.toLowerCase();
//incase we have url, we would want to compare only the file names, as rest might be mangled
var urlRegex = /url.*[\\\/]/g;
string1 = string1.replace(urlRegex, "url(");
string2 = string2.replace(urlRegex, "url(");
//Now for the rest we ignore space,",',;,rgb and hex color values
var regexPattern = /\s|;|"|'|rgb\(.*\)|(#[0-9a-f]*)(?![\s\S]*\{)/g;
ret = string1.replace(regexPattern, "") === string2.replace(regexPattern, "");
}
return ret;
},
compareRuleStrings : function (string1, string2) {
'use strict';
var ret = false;
if (string1 && string2) {
//clear out the spaces
string1 = string1.replace(/\s/g, '');
string2 = string2.replace(/\s/g, '');
if (string1.length > 0 && string2.length > 0) {
if (string1.charAt(0) === '@' && string2.charAt(0) === '@') {
//could be @import, @fontface, @charset, need to compare fully
ret = DW_RefreshCSS.compareFullRule(string1, string2);
} else {
//it is a normal style rule, kick out everything in between the curly braces and compare only selectorText
var regexPattern = /\{[\s\S]*\}/g;
ret = string1.replace(regexPattern, '') === string2.replace(regexPattern, '');
}
}
}
return ret;
},
//if our selector is in a media rule, we try to find the media rule first
findMediaRule : function (mQuery, styleObj) {
'use strict';
var ret = null;
if (mQuery && mQuery.length > 0 && styleObj && styleObj.cssRules) {
var index = 0;
var rules = styleObj.cssRules;
var modQuery = DW_RefreshCSS.sanitizeMediaString(mQuery);
for (index = 0; index < rules.length; ++index) {
if (rules[index].type === CSSRule.MEDIA_RULE && rules[index].media && rules[index].media.mediaText === modQuery) {
ret = rules[index];
break;
}
}
}
return ret;
},
//the important function, given a css Selector, we add the style.
insertRule : function (mediaText, ruleText, path) {
'use strict';
var sheet = DW_RefreshCSS.findStyleSheet(path);
if (sheet) {
var parent = null;
if (mediaText && mediaText.length > 0) {
parent = DW_RefreshCSS.findMediaRule(mediaText, sheet);
if (!parent) {
parent = sheet;
ruleText = '@media ' + mediaText + ' { ' + ruleText + ' }'; //if the selector is in a media query we should do so!
}
} else {
parent = sheet;
}
if (parent && parent.cssRules) {
var insertIndex = ruleText.indexOf("@import") !== -1 ? 0 : parent.cssRules.length;//if we are adding an import statement, add it at start, otherwise end
parent.insertRule(ruleText, insertIndex);
if (navigator && navigator.userAgent &&
navigator.userAgent.toLowerCase().indexOf('safari') !== -1 &&
navigator.userAgent.toLowerCase().indexOf('chrome') === -1) {
//this is a hack for safari where, sometimes adding the style doesn't reflect right away, jus flip the disabled attribute to get this working
parent.disabled = !parent.disabled;
parent.disabled = !parent.disabled;
}
}
}
},
//the important function, given a css Selector, we delete the style.
deleteRule : function (mediaText, ruleText, path) {
'use strict';
var sheet = DW_RefreshCSS.findStyleSheet(path);
if (sheet) {
var parent = null;
if (mediaText && mediaText.length > 0) {
parent = DW_RefreshCSS.findMediaRule(mediaText, sheet);
} else {
parent = sheet;
}
if (parent && parent.cssRules) {
var matches = []; //we will store all possible matches for the rule, if there are more than one we will decide which to delete later.
var index = 0;
var rules = parent.cssRules;
for (index = 0; index < rules.length; ++index) {
if (rules[index].type !== CSSRule.MEDIA_RULE && DW_RefreshCSS.compareRuleStrings(rules[index].cssText, ruleText)) {
matches.push(index);
}
}
if (matches.length === 1) {
parent.deleteRule(matches[0]);
} else if (matches.length > 1) {
for (index = 0; index < matches.length; ++index) {
if (DW_RefreshCSS.compareFullRule(rules[matches[index]].cssText, ruleText)) {
parent.deleteRule(matches[index]);
break;
}
}
}
}
}
}
};
function DWApplyDOMEditsHasInstruction() {
'use strict';
window.location.reload(true);
}