Skip to content

Instantly share code, notes, and snippets.

@gregwiechec
Created October 22, 2015 09:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregwiechec/fed65b5d9d77395340cd to your computer and use it in GitHub Desktop.
Save gregwiechec/fed65b5d9d77395340cd to your computer and use it in GitHub Desktop.
EPiServer compare with markup
ins {
background-color: #cfc;
text-decoration: none;
}
del {
color: #999;
background-color:#FEC8C8;
}
.epi-form-container__section__row__editor--extendedCompare {
display: inline-block;
margin: 0 0 10px 0;
}
using EPiServer.Core;
using EPiServer.ServiceLocation;
using EPiServer.Shell;
namespace ExtendingCompareVersion.Business.ViewConfiguration
{
[ServiceConfiguration(typeof(EPiServer.Shell.ViewConfiguration))]
public class CompareWithMarkup : ViewConfiguration<IContentData>
{
public CompareWithMarkup()
{
this.Key = "compareWithMarkup";
this.ControllerType = "epi-cms/compare/views/CompareView";
this.ViewType = "alloy/editors/compareWithMarkupView";
this.IconClass = "epi-iconCompare";
this.HideFromViewMenu = true;
}
}
}
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/topic",
"epi/shell/widget/Iframe",
"epi-cms/compare/views/SideBySideCompareView",
"epi/shell/_ContextMixin"
],
function (
declare,
lang,
topic,
Iframe,
SideBySideCompareView,
_ContextMixin
) {
return declare([SideBySideCompareView, _ContextMixin], {
//?first=48012_1425533&second=48012_1424347&forceLanguage=no
_setRightVersionUrlAttr: function (url) {
this._rightIframe.load("/Plugins/VisualPageCompare.aspx?first=" + this.model.leftVersion.contentLink + "&second=" + this.model.rightVersion.contentLink);
},
_setupComparePanes: function() {
this.inherited(arguments);
var orientation = this.model.orientation;
this._rightIframe.set('style', orientation === "vertical" ? "width: 100%;" : "height: 100%");
this.mainLayoutContainer.layout();
},
contextChanged: function (ctx, callerData) {
this.set('RightVersionUrl', '');
}
});
});
define("epi-cms/compare/command/CompareCommandProvider", [
"dojo/_base/declare",
"dojo/when",
"epi-cms/component/command/_GlobalToolbarCommandProvider",
"epi/dependency",
"./CompareSettingsModel",
"./CompareViewSelection",
"epi/shell/command/ToggleCommand",
"epi-cms/command/_NonEditViewCommandMixin",
"epi/i18n!epi/cms/nls/episerver.cms.compare.command"
], function (
declare,
when,
_GlobalToolbarCommandProvider,
dependency,
CompareSettingsModel,
CompareViewSelection,
ToggleCommand,
_NonEditViewCommandMixin,
resources
) {
var NonEditViewToggleCommand = declare([ToggleCommand, _NonEditViewCommandMixin]);
return declare([_GlobalToolbarCommandProvider], {
// summary:
// A command provider providing compare commands to the global toolbar
constructor: function() {
this.profile = this.profile || dependency.resolve("epi.shell.Profile");
},
postscript: function () {
this.inherited(arguments);
var model = new CompareSettingsModel();
/* ********************************************************************************************************************************** */
/* Code in this section is different than original file */
/* ********************************************************************************************************************************** */
when(this.profile.get("epi.selected-comparemode-id"), function (mode) {
model.set("mode", "sidebysidecompare");
});
model.modeOptions.push(
{ label: "Extended All properties Compare", value: "extendedAllPropertiesCompare", iconClass: "epi-iconForms" });
model.modeOptions.push(
{ label: "Compare with markup", value: "compareWithMarkup", iconClass: "epi-iconForms" });
/* ********************************************************************************************************************************** */
// Need to have a settings object as well, since the global menu builder looks a this for category
var settings = { category: "compare" };
this.add("commands", new NonEditViewToggleCommand({
category: "compare",
settings: settings,
label: resources.togglecompare.label,
iconClass: "epi-iconCompare",
model: model,
property: "enabled"
}));
this.add("commands", new CompareViewSelection({
category: "compare",
settings: settings,
model: model,
label: resources.compareviewselection.label,
optionsLabel: resources.compareviewselection.label,
optionsProperty: "modeOptions",
property: "mode"
}));
}
});
});
define("epi/shell/widget/WidgetSwitcher", [
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/kernel",
"dojo/_base/lang",
"dojo/Deferred",
"dojo/topic",
"dojo/when",
"epi/shell/_ContextMixin",
"epi/shell/layout/AnimatedCardContainer",
"epi/shell/TypeDescriptorManager"
], function (
array,
declare,
kernel,
lang,
Deferred,
topic,
when,
_ContextMixin,
AnimatedCardContainer,
TypeDescriptorManager
) {
return declare([AnimatedCardContainer, _ContextMixin], {
// summary:
// A container that handles showing a child widget based on either a
// specific view request or a change in the current context.
// tags:
// internal
// componentConstructorArgs: Object
// Arguments that must be passed to constructor when new component is created.
componentConstructorArgs: null,
// supportedContextTypes: Array
// String array of supported context data types.
// This option should be defined if we need to react only on context changes with specific data type.
// Context changes are ignored if set to empty array.
supportedContextTypes: null,
constructor: function () {
this._viewHistory = [];
},
postMixInProperties: function () {
// summary:
// Verifies and fixes the list of supported context data types.
// tags:
// protected
this.inherited(arguments);
if (this.supportedContextTypes) {
if (!lang.isArray(this.supportedContextTypes)) {
this.supportedContextTypes = [this.supportedContextTypes];
}
if (!array.every(this.supportedContextTypes, function (type) { return typeof type == "string"; })) {
throw new Error("supportedContextTypes must be string, array of strings or null.");
}
}
},
postCreate: function () {
// summary:
// If the current context is available it loads the initial view component for it. Also subscribes
// to view component change requests.
// tags:
// protected
this.inherited(arguments);
when(this.getCurrentContext(), lang.hitch(this, this.contextChanged));
this.own(
topic.subscribe("/epi/shell/action/changeview", lang.hitch(this, "viewComponentChangeRequested")),
topic.subscribe("/epi/shell/action/changeview/back", lang.hitch(this, "_viewComponentBackRequested")),
topic.subscribe("/epi/shell/action/changeview/deactivate", lang.hitch(this, "_viewComponentDeactivate")),
topic.subscribe("/epi/shell/action/changeview/updatestate", lang.hitch(this, "_changeViewState"))
);
},
contextChanged: function (/*Object*/context, data) {
// summary:
// Called when the current context changes.
// tags:
// protected
if (!this._isContextTypeSupported(context)) {
return;
}
var view = context.customViewType || (!!data ? data.viewType : null);
var currentWidgetInfo = this._getCurrentWidgetInfo();
//NOTE: when we change so that we can route to different views this should be rewritten.
if (data && data.contextIdSyncChange && currentWidgetInfo) {
view = currentWidgetInfo.type;
data = lang.mixin({}, data, {
viewName: currentWidgetInfo.viewName,
availableViews: currentWidgetInfo.availableViews
});
}
if (!view && context.dataType) {
// If there is no custom view type, fall back to the widget for the type.
// For backwards compatibility, check the obsoleted property mainWidgetType
view = TypeDescriptorManager.getValue(context.dataType, "mainWidgetType");
}
if (view) {
this._getObject(view, null, context, data);
} else {
var suggestedView = TypeDescriptorManager.getValue(context.dataType, "defaultView"),
availableViews = this._getAvailableViews(context.dataType),
requestedViewName = data ? data.viewName : null;
var viewsArr = [
"view",
"sidebysidecompare",
"allpropertiescompare"
];
/* ********************************************************************************************************************************** */
/* Code in this section is different than original file */
/* ********************************************************************************************************************************** */
viewsArr.push("compareWithMarkup");
viewsArr.push("extendedAllPropertiesCompare");
/* ********************************************************************************************************************************** */
// TODO: consider removing reference to "view"(preview) when refactoring the view rerouting
if (currentWidgetInfo && viewsArr.indexOf(currentWidgetInfo.viewName) >= 0) {
// if we're viewing the preview we should keep that view as long as it's available.
array.some(availableViews, function (availableView) {
if (availableView.key == currentWidgetInfo.viewName) {
suggestedView = currentWidgetInfo.viewName;
return true;
}
return false;
});
}
var viewToLoad = this._getViewByKey(requestedViewName || suggestedView, availableViews);
if (!viewToLoad) {
throw "No default view found for " + context.dataType;
}
this._loadViewComponentByConfiguration(viewToLoad, availableViews, null, context, data);
}
},
_getViewByKey: function (key, availableViews) {
return array.filter(availableViews, function (availableView) {
return availableView.key === key;
})[0];
},
_getAvailableViews: function (dataType) {
var availableViews = TypeDescriptorManager.getAndConcatenateValues(dataType, "availableViews"),
disabledViews = TypeDescriptorManager.getValue(dataType, "disabledViews");
var filteredViews = array.filter(availableViews, function (availableView) {
return array.every(disabledViews, function (disabledView) {
return availableView.key !== disabledView;
});
});
return filteredViews.sort(function (x, y) {
return x.sortOrder < y.sortOrder ? -1 : 1;
});
},
_loadViewComponentByConfiguration: function (view, availableViews, args, context, data) {
data = lang.mixin({}, data, {
viewName: view.key,
availableViews: availableViews
});
this._getObject(view.controllerType, args, context, data);
},
viewComponentChangeRequested: function (/*String*/type, /*Object*/args, /*Object*/data) {
// summary:
// Called when a new view component is requested.
// type: String
// Specify component widget type or a key which refers to a view configuration.
// args: Object
// Arguments used to instantiate component widget.
// data: Object
// data passed to the view when calling updateView.
// tags:
// protected
when(this.getCurrentContext(), lang.hitch(this, function (currentContext) {
var dataType = currentContext ? currentContext.dataType : null;
// If no view or widget type is supplied, then use the default view for the content type
if (!type && dataType) {
type = TypeDescriptorManager.getValue(dataType, "defaultView");
}
var availableViews = this._getAvailableViews(dataType),
view = this._getViewByKey(type, availableViews);
if (view) {
// A view configuration found, means that new view is being requested by key
this._loadViewComponentByConfiguration(view, availableViews, args, currentContext, data);
} else {
// Otherwise, by component type. (Backward compatibility)
this._getObject(type, args, currentContext, data);
}
}));
},
_viewComponentBackRequested: function (forceReload) {
var previousWidgetInfo = this._getPreviousWidgetInfo();
if (previousWidgetInfo) {
if (typeof forceReload === "undefined") {
// forceReload should be false by default, which means that we will skip updating view.
forceReload = false;
}
var data = {
skipUpdateView: !forceReload,
availableViews: previousWidgetInfo.availableViews,
viewName: previousWidgetInfo.viewName
};
when(this.getCurrentContext(), lang.hitch(this, function (currentContext) {
this._getObject(previousWidgetInfo.type, null, currentContext, data);
}));
}
},
_viewComponentDeactivate: function (view) {
kernel.deprecated("topic: /epi/shell/action/changeview/deactivate");
this._viewHistory = this._viewHistory.filter(function (element) {
return element.viewName !== view;
});
},
_changeViewState: function (state) {
var currentWidgetInfo = this._getCurrentWidgetInfo();
if (currentWidgetInfo) {
lang.mixin(currentWidgetInfo, state);
}
},
_getPreviousWidgetInfo: function () {
var history = this._viewHistory;
return (history.length > 1) ? history[history.length - 2] : null;
},
_getCurrentWidgetInfo: function () {
var history = this._viewHistory;
return (history.length > 0) ? history[history.length - 1] : null;
},
_isContextTypeSupported: function (context) {
// summary:
// Verifies if data type of specified context is supported by this instance
// tags:
// private
if (!context) {
return false;
}
if (!this.supportedContextTypes) {
return true;
}
return array.some(this.supportedContextTypes, function (type) { return context.type === type; });
},
_getObject: function (type, args, context, data) {
// summary:
// Gets an instance for the given type if it is different from
// the current view component type.
// tags:
// private
if (!type) {
return;
}
var current = this.selectedChildWidget;
var func = lang.hitch(this, function () {
when(this._testComponentInstanceOf(current, type), lang.hitch(this, function (result) {
if (result) {
this._updateHistory(type, data, context);
this._updateView(current, context, data);
this._onViewChanged(type, args, data);
}
else {
this._changeViewComponent(type, args, context, data);
}
}));
});
if (current && current.savePendingChanges) {
when(current.savePendingChanges(), func);
} else {
func();
}
},
_changeViewComponent: function (type, args, context, data) {
// summary:
// Remove the current view component and add the new one.
// tags:
// private
when(this._getChildByType(type), lang.hitch(this, function (component) {
var current = this.selectedChildWidget;
// suspend the current one before changing to the next
if (current) {
current.set("isActive", false);
}
if (component) {
// activate the component
component.set("isActive", true);
if (data && data.treatAsSecondaryView) {
this.selectSecondaryChild(component);
} else {
this.selectChild(component);
}
this._updateHistory(type, data, context);
this._updateView(component, context, data, true);
this._onViewChanged(type, args, data);
}
else {
require([type], lang.hitch(this, function (componentClass) {
var mixedArgs = lang.mixin(lang.mixin({}, this.componentConstructorArgs), args);
component = new componentClass(mixedArgs);
//component.transitionMode = "reveal";
component.fitContainer = true;
this.addChild(component);
var select;
if (data && data.treatAsSecondaryView) {
select = this.selectSecondaryChild(component);
} else {
select = this.selectChild(component);
}
when(select, lang.hitch(this, function () {
this._updateHistory(type, data, context);
this._updateView(component, context, data, true);
this._onViewChanged(type, args, data);
}));
}));
}
}));
},
_updateView: function (component, context, data, widgetResumed) {
if (component && !component._started && typeof component.startup === "function") {
// Ensure component ready.
component.startup();
}
if (component && typeof component.updateView === "function") {
component.updateView(data, context, { widgetResumed: widgetResumed });
}
},
_getChildByType: function (type) {
var def = new Deferred();
require([type], lang.hitch(this, function (componentClass) {
var components = array.filter(this.getChildren(), function (child) {
return child.constructor === componentClass;
}, this);
def.resolve(components.length ? components[0] : null);
}));
return def;
},
_testComponentInstanceOf: function (component, type) {
var def = new Deferred();
if (component) {
if (typeof type === "string") {
require([type], function (componentClass) {
def.resolve(component && component.constructor === componentClass);
});
} else {
def.resolve(component && component.constructor === type.constructor);
}
} else {
def.resolve(false);
}
return def;
},
_onViewChanged: function (type, args, data) {
topic.publish("/epi/shell/action/viewchanged", type, args, data);
},
_updateHistory: function (/*string*/type, data, context) {
// summary:
// Add data to widget history store
// Keep total items in store always 2
// tags:
// private
var history = this._viewHistory,
length = history.length,
item = this.createStoreItem(type, data, context);
if (length && history[length - 1].id === item.id) {
return;
}
if (length === 3) {
history.shift();
}
history.push(item);
},
createStoreItem: function (/*string*/type, data, context) {
// summary:
// Create new object that will be put to store
// tags:
// protected
var storeItem = { type: type, id: type };
if (data) {
var availableViews = data.availableViews || (context && this._getAvailableViews(context.dataType));
var viewName = data.viewName || this._getViewName(availableViews, context);
storeItem = lang.mixin(storeItem, {
availableViews: availableViews,
viewName: viewName
});
storeItem.id += "#" + viewName;
}
return storeItem;
},
_getViewName: function (availableViews, context) {
if (!context) {
return null;
}
var selectedView = this._getViewByKey(TypeDescriptorManager.getValue(context.dataType, "defaultView"), availableViews);
if (!selectedView) {
return null;
}
return selectedView.key;
}
});
});
using EPiServer.Core;
using EPiServer.ServiceLocation;
using EPiServer.Shell;
namespace ExtendingCompareVersion.Business.ViewConfiguration
{
[ServiceConfiguration(typeof(EPiServer.Shell.ViewConfiguration))]
public class ExtendedAllPropertiesCompare : ViewConfiguration<IContentData>
{
public ExtendedAllPropertiesCompare()
{
this.Key = "extendedAllPropertiesCompare";
this.ControllerType = "epi-cms/compare/views/CompareView";
this.ViewType = "alloy/editors/extendedAllPropertiesCompareView";
this.IconClass = "epi-iconCompare";
this.HideFromViewMenu = true;
}
}
}
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/Deferred",
"dojo/when",
"epi-cms/compare/views/AllPropertiesCompareView",
"epi/shell/widget/FormContainer",
"./extendedAllPropertiesTransformer",
"./extendedFormField"
], function (
declare,
lang,
Deferred,
when,
AllPropertiesCompareView,
FormContainer,
ExtendedAllPropertiesTransformer,
ExtendedFormField
) {
return declare("alloy.editors.extendedAllPropertiesCompareView", [AllPropertiesCompareView], {
_createForm: function () {
// summary:
// Setup the edit form.
// tags:
// private
return new FormContainer(lang.mixin({
readOnly: !this.viewModel.canChangeContent(),
metadata: this.viewModel.metadata,
metadataTransformer: new ExtendedAllPropertiesTransformer({ model: this.model }),
baseClass: "epi-cmsEditingForm epi-form-container epi-form-container--compare"
}, this.formSettings));
}
});
});
define([
"dojo/_base/declare",
"dojo/_base/lang",
"epi-cms/compare/AllPropertiesTransformer"
],
function (
declare,
lang,
AllPropertiesTransformer
) {
return declare([AllPropertiesTransformer], {
_createPropertyWidget: function() {
var editor = this.inherited(arguments);
editor[0].settings._hint = "extendedCompare";
editor[1].settings._type = "extendedCompare";
return editor;
}
});
});
define([
// dojo
"dojo/_base/declare",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/when",
"dojo/dom-style",
"dojo/query",
"dijit/form/Button",
"dijit/Dialog",
"epi/dependency",
"epi-cms/compare/FormField",
"epi/shell/form/formFieldRegistry",
"xstyle/css!./compare.css"
],
function (
// dojo
declare,
array,
lang,
when,
domStyle,
query,
Button,
Dialog,
dependency,
FormField,
registry
) {
var module = declare([FormField], {
buildRendering: function () {
this.inherited(arguments);
this.own(this._compareButton = new Button({
"class": "epi-form-container__section__row__copy-button epi-chromeless",
iconClass: "epi-icon-compare--copy",
label: "Compare",
title: "Compare two properties"
}).placeAt(this.domNode, "last"));
domStyle.set(this._compareButton.domNode, {
"margin-right": '100px'
});
this.own(this.resultDialog = new Dialog({
title: 'Compare properties - ' + this.label,
content: '',
style: "width: 800px"
}));
this._compareButton.on("click", lang.hitch(this, function () {
var registry = dependency.resolve("epi.storeregistry");
var store = registry.get("compare.htmlDiff");
var left = this.getPropertyValue(this.model.rightMetadata.properties, this.name, '.epi-form-container__section__row__editor--extendedCompare');
var right = this.getPropertyValue(this.model.leftMetadata.properties, this.name, '.epi-form-container__section__row__editor--field');
when(store.add({ left: left, right: right, modelType: this.getModelType() }), lang.hitch(this, function (result) {
var html = result.data;
this.resultDialog.set('content', html);
this.resultDialog.show();
}));
}));
},
getModelType: function() {
var name = this.name.toLowerCase();
var modelType = array.filter(this.model.leftMetadata.properties, function (item) {
return item.name.toLowerCase() == name;
})[0].modelType;
if (modelType == 'EPiServer.Core.ContentArea') {
return 2;
}
return 1;
},
getPropertyValue: function (properties, name, widgetSelector) {
name = name.toLowerCase();
var property = array.filter(properties, function (item) {
return item.name.toLowerCase() == name;
})[0];
if (property.modelType == "EPiServer.Core.ContentArea") {
var result = [];
var caItems = query(widgetSelector + ' div.dijitTreeNode div.epi-tree-mngr--block', this.domNode);
for (var i = 0; i < caItems.length; i++) {
result.push(caItems[i].outerHTML);
}
return result;
}
var value = property.initialValue;
// for text area split use br to join lines
if (property.modelType == "System.String[]") {
value = encodeURIComponent(value.join('<br/>'));
}
return [value];
}
});
function fieldFactory(widget, parent) {
var wrapper = new module({
labelTarget: widget.checkbox ? widget.checkbox.id : widget.id,
label: widget.label,
tooltip: widget.tooltip,
name: widget.name,
groupName: widget.groupName.toLowerCase(),
readonlyIconDisplay: widget.readOnly,
hasFullWidthValue: widget.useFullWidth
});
wrapper.own(widget.watch("readOnly", function (name, oldValue, newValue) {
wrapper.set("readonlyIconDisplay", newValue);
}));
return wrapper;
}
registry.add([
{
type: registry.type.field,
hint: "extendedCompare",
factory: fieldFactory
},
{
type: "extendedCompare",
hint: "",
factory: registry.get('compare', '')
}
]);
return module;
});
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using EPiServer.Legacy.UI.Edit.PageCompare.Core.Providers;
using EPiServer.Shell.Services.Rest;
using HtmlAgilityPack;
using NuGet;
namespace ExtendingCompareVersion.Business
{
[RestStore("htmlDiffStore")]
public class HtmlDiffStore : RestControllerBase
{
[HttpPost]
public ActionResult Post(CompareParameter compareParameter)
{
string compareResult;
compareParameter.Left = ClearText(compareParameter.Left);
compareParameter.Right = ClearText(compareParameter.Right);
if (compareParameter.ModelType == CompareModelType.ContentArea)
{
compareResult = new ContentAreaComparer().Compare(compareParameter);
}
else
{
compareResult = CompareStrings(compareParameter);
}
return Rest(new HtmlCompareResult()
{
Data = compareResult
});
}
private string CompareStrings(CompareParameter compareParameter)
{
var htmlDiff = new HtmlDiff(compareParameter.Left.First(), compareParameter.Right.First());
return htmlDiff.Build();
}
private IEnumerable<string> ClearText(IEnumerable<string> values)
{
if (values == null)
{
return Enumerable.Empty<string>();
}
return values.Select(ClearText).ToList();
}
private string ClearText(string value)
{
if (string.IsNullOrEmpty(value))
{
return "";
}
value = System.Web.HttpUtility.UrlDecode(value);
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(value);
SortAttributes(htmlDocument.DocumentNode);
value = htmlDocument.DocumentNode.OuterHtml;
value = value.Replace("dijitTextBoxReadOnly ", "");
value = value.Replace("dijitTextAreaReadOnly ", "");
return value;
}
private static void SortAttributes(HtmlNode documentNode)
{
var attributes = documentNode.Attributes.OrderBy(a => a.Name).ToList();
documentNode.Attributes.RemoveAll();
documentNode.Attributes.AddRange(attributes);
foreach (var childNode in documentNode.ChildNodes)
{
if (childNode is HtmlTextNode)
{
continue;
}
SortAttributes(childNode);
}
}
}
[Serializable]
public class CompareParameter
{
public IEnumerable<string> Left { get; set; }
public IEnumerable<string> Right { get; set; }
public CompareModelType ModelType { get; set; }
}
public enum CompareModelType
{
String = 1,
ContentArea = 2
}
[Serializable]
public class HtmlCompareResult
{
public string Data { get; set; }
}
public class ContentAreaComparer
{
public string Compare(CompareParameter compareParameter)
{
var leftItems = this.InitList(compareParameter.Left);
var rightItems = this.InitList(compareParameter.Right);
var listsComparer = new ListsComparer();
var listItems = listsComparer.Compare(rightItems, leftItems).ToList();
var result = new StringBuilder();
for (int index = 0; index < listItems.Count; index++)
{
var listItem = listItems[index];
var html = ((ContentAreaElement) listItem.Value).Html;
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(html);
var rootNode = htmlDocument.DocumentNode.SelectSingleNode("//div[contains(@class, 'dijitTreeNode')]");
rootNode.Attributes["id"].Value = "compareResult" + index;
rootNode.Attributes["widgetId"].Remove();
if (listItem.State == CompareState.Equal)
{
result.AppendLine(htmlDocument.DocumentNode.OuterHtml);
continue;
}
var textNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='dijitTreeLabel']");
if (listItem.State == CompareState.Deleted)
{
textNode.InnerHtml = string.Format("<ins class='diffins'>{0}</ins>", textNode.InnerText);
}
else
{
textNode.InnerHtml = string.Format("<del class='diffdel'>{0}</ins>", textNode.InnerText);
//$"<del class='diffdel'>{textNode.InnerText}</ins>";
}
result.AppendLine(htmlDocument.DocumentNode.OuterHtml);
}
return result.ToString();
}
private IEnumerable<IComparable> InitList(IEnumerable<string> list)
{
var contentAreaElements = new List<ContentAreaElement>();
foreach (var item in list)
{
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(item);
var textNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='dijitTreeLabel']");
var contentAreaElement = new ContentAreaElement(item, textNode == null ? "" : textNode.InnerText);
contentAreaElements.Add(contentAreaElement);
}
return contentAreaElements;
}
public class ContentAreaElement : IComparable
{
public ContentAreaElement(string html, string compareKey)
{
this.Html = html;
this.CompareKey = compareKey;
}
public string Html { get; set; }
public string CompareKey { get; set; }
public int CompareTo(object obj)
{
var otherKey = ((ContentAreaElement)obj).CompareKey;
return string.Compare(otherKey, this.CompareKey, StringComparison.Ordinal);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ExtendingCompareVersion.Business
{
public class ListsComparer
{
public IEnumerable<ListItem> Compare(IEnumerable<IComparable> source, IEnumerable<IComparable> target )
{
var sourceList = source.ToList();
var targetList = target.ToList();
var listItems = new List<ListItem>();
var sourceIndex = 0;
var targetIndex = 0;
while (sourceIndex < sourceList.Count && targetIndex < targetList.Count)
{
var sourceElement = sourceList[sourceIndex];
var targetElement = targetList[targetIndex];
// items are equal
if (sourceElement.CompareTo(targetElement) == 0)
{
listItems.Add(CraeteElement(sourceList[sourceIndex], CompareState.Equal));
sourceIndex++;
targetIndex++;
continue;
}
// source index was removed
if (sourceIndex < sourceList.Count - 1)
{
var found = false;
var deletedItems = new List<ListItem>();
deletedItems.Add(CraeteElement(sourceList[sourceIndex], CompareState.Deleted));
for (var newSourceIndex = sourceIndex + 1; newSourceIndex < sourceList.Count; newSourceIndex++)
{
if (sourceList[newSourceIndex].CompareTo(targetElement) == 0)
{
sourceIndex = newSourceIndex;
listItems.AddRange(deletedItems);
found = true;
break;
}
else
{
deletedItems.Add(CraeteElement(sourceList[newSourceIndex], CompareState.Deleted));
}
}
if (found)
{
continue;
}
}
// target index was added
if (targetIndex < targetList.Count - 1)
{
var found = false;
var addedItems = new List<ListItem>();
addedItems.Add(CraeteElement(targetList[targetIndex], CompareState.Added));
for (var newTargetIndex = targetIndex + 1; newTargetIndex < targetList.Count; newTargetIndex++)
{
if (targetList[newTargetIndex].CompareTo(sourceElement) == 0)
{
targetIndex = newTargetIndex;
listItems.AddRange(addedItems);
found = true;
break;
}
else
{
addedItems.Add(CraeteElement(targetList[newTargetIndex], CompareState.Added));
}
}
if (found)
{
continue;
}
}
// both elements changed
listItems.Add(CraeteElement(sourceElement, CompareState.Deleted));
listItems.Add(CraeteElement(targetElement, CompareState.Added));
sourceIndex++;
targetIndex++;
}
// all items in source list that left should be added as Deleted
listItems.AddRange(GetRemainingElements(sourceIndex, sourceList, CompareState.Deleted));
// add all remaining items from target list with status Added
listItems.AddRange(GetRemainingElements(targetIndex, targetList, CompareState.Added));
return listItems;
}
private IEnumerable<ListItem> GetRemainingElements(int currentIndex, List<IComparable> inputList, CompareState compareState)
{
var listItems = new List<ListItem>();
if (currentIndex < inputList.Count)
{
for (var i = currentIndex; i < inputList.Count; i++)
{
listItems.Add(CraeteElement(inputList[i], compareState));
}
}
return listItems;
}
private ListItem CraeteElement( IComparable element, CompareState compareState)
{
return new ListItem()
{
Value = element,
State = compareState
};
}
}
public class ListItem
{
public IComparable Value { get; set; }
public CompareState State { get; set; }
}
public enum CompareState
{
Undefined,
Equal,
Deleted,
Added
}
}
<?xml version="1.0" encoding="utf-8"?>
<module>
<dojo>
<paths>
<add name="compare" path="widget" />
</paths>
</dojo>
<clientModule initializer="compare.ModuleInitializer">
<moduleDependencies>
<add dependency="CMS" type="RunAfter" />
</moduleDependencies>
</clientModule>
<clientResources>
<!-- Inject our custom Display Option selector -->
<add name="epi-cms.widgets.base"
path="~/modules/compare/EpiServerOverridenLibs/CustomCompareCommandProvider.js"
resourceType="Script"
/>
<add name="epi-cms.widgets.base"
path="~/modules/compare/EpiServerOverridenLibs/CustomWidgetSwitcher.js"
resourceType="Script"
/>
</clientResources>
</module>
define([
// Dojo
"dojo",
"dojo/_base/declare",
//CMS
"epi/_Module",
"epi/dependency",
"epi/routes"
], function (
// Dojo
dojo,
declare,
//CMS
_Module,
dependency,
routes
) {
return declare("compare.ModuleInitializer", [_Module], {
// summary: Module initializer for the default module.
initialize: function () {
this.inherited(arguments);
var registry = this.resolveDependency("epi.storeregistry");
//Register the store
registry.create("compare.htmlDiff", this._getRestPath("htmlDiffStore"));
},
_getRestPath: function (name) {
return routes.getRestPath({ moduleArea: "App", storeName: name });
}
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment