<commit_before>import React from 'react';

import { Zerotorescue, tsabo, Gebuz, Aelexe, Tyndi, Zeboot } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 10, 20), <>Added <SpellLink id={SPELLS.INVIGORATING_SHADOWDUST.id} /> Legendary</>, [Tyndi]),
  change(date(2020, 10, 19), <>Added <SpellLink id={SPELLS.ESSENCE_OF_BLOODFANG.id} /> Legendary</>, [Tyndi]),
  change(date(2020, 10, 18), 'Converted legacy listeners to new event filters', Zeboot),
  change(date(2020, 10, 16), <>Added <SpellLink id={SPELLS.THE_ROTTEN.id} /> Legendary. And Removed Nightblade.</>, [Tyndi]),
  change(date(2020, 10, 2), 'Added Akaari\'s Soul Fragment Legendary', [Tyndi]),
  change(date(2019, 1, 27), 'Improvements to the Nightblade analysis.', [tsabo]),
  change(date(2019, 1, 19), 'Fix Shuriken Storm CP waste. Any Shuriken Storm that generates at least 3CPs will not be considered waste, otherwise waste will be limited by the CP pool size.', [tsabo]),
  change(date(2018, 12, 28), 'Updates for 8.1, minor fixes.', [tsabo]),
  change(date(2018, 11, 15), <>Fixed <SpellLink id={SPELLS.ARCANE_TORRENT_ENERGY.id} /> GCD.</>, [Aelexe]),
  change(date(2018, 11, 13), <>Fixed cooldown tracking for <SpellLink id={SPELLS.MARKED_FOR_DEATH_TALENT.id} /> when targets die with the debuff.</>, [Aelexe]),
  change(date(2018, 11, 11), <>Added suggestion for Sharpened Blades stack wastage.</>, [Aelexe]),
  change(date(2018, 11, 5), 'Updated resource tracking to display percent instead of per minute, and added spenders to the energy tab.', [Gebuz]),
  change(date(2018, 8, 12), 'Initial Checklist.', [tsabo]),
  change(date(2018, 8, 2), 'Added natural energy regen.', [tsabo]),
  change(date(2018, 7, 15), 'Find Weakness usage analysis. Stealth ability usage analysis.', [tsabo]),
  change(date(2018, 7, 7), 'Update analysis for PrePatch', [tsabo]),
  change(date(2018, 6, 27), 'Update all abilities to new BFA values.', [Zerotorescue]),
];
<commit_msg>Update CHANGELOG<commit_after>import React from 'react';

import { Zerotorescue, tsabo, Gebuz, Aelexe, Tyndi, Zeboot } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 10, 27), 'Converted modules to TypeScript; Updated Statistic Boxes', Tyndi),
  change(date(2020, 10, 20), <>Added <SpellLink id={SPELLS.INVIGORATING_SHADOWDUST.id} /> Legendary</>, [Tyndi]),
  change(date(2020, 10, 19), <>Added <SpellLink id={SPELLS.ESSENCE_OF_BLOODFANG.id} /> Legendary</>, [Tyndi]),
  change(date(2020, 10, 18), 'Converted legacy listeners to new event filters', Zeboot),
  change(date(2020, 10, 16), <>Added <SpellLink id={SPELLS.THE_ROTTEN.id} /> Legendary. And Removed Nightblade.</>, [Tyndi]),
  change(date(2020, 10, 2), 'Added Akaari\'s Soul Fragment Legendary', [Tyndi]),
  change(date(2019, 1, 27), 'Improvements to the Nightblade analysis.', [tsabo]),
  change(date(2019, 1, 19), 'Fix Shuriken Storm CP waste. Any Shuriken Storm that generates at least 3CPs will not be considered waste, otherwise waste will be limited by the CP pool size.', [tsabo]),
  change(date(2018, 12, 28), 'Updates for 8.1, minor fixes.', [tsabo]),
  change(date(2018, 11, 15), <>Fixed <SpellLink id={SPELLS.ARCANE_TORRENT_ENERGY.id} /> GCD.</>, [Aelexe]),
  change(date(2018, 11, 13), <>Fixed cooldown tracking for <SpellLink id={SPELLS.MARKED_FOR_DEATH_TALENT.id} /> when targets die with the debuff.</>, [Aelexe]),
  change(date(2018, 11, 11), <>Added suggestion for Sharpened Blades stack wastage.</>, [Aelexe]),
  change(date(2018, 11, 5), 'Updated resource tracking to display percent instead of per minute, and added spenders to the energy tab.', [Gebuz]),
  change(date(2018, 8, 12), 'Initial Checklist.', [tsabo]),
  change(date(2018, 8, 2), 'Added natural energy regen.', [tsabo]),
  change(date(2018, 7, 15), 'Find Weakness usage analysis. Stealth ability usage analysis.', [tsabo]),
  change(date(2018, 7, 7), 'Update analysis for PrePatch', [tsabo]),
  change(date(2018, 6, 27), 'Update all abilities to new BFA values.', [Zerotorescue]),
];
<|endoftext|>


<commit_before>/***
|''Name''|TiddlySpaceRevisionView|
|''Description''|Show tiddler revisions in a stack of cards view|
|''Author''|BenGillies|
|''Version''|0.2.0|
|''Status''|beta|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor|
!Usage
The viewRevisions macro can be attached to any element, which should be passed
in as a parameter.

For example:

<<viewRevisions page:10 link:"<<view modified date>>">>

would show the revisions "stack of cards" view, 10 at a time, when the modified
date is clicked.
!Code
***/
//{{{
(function($) {

var me = config.macros.viewRevisions = {
	revisionTemplate: "RevisionTemplate",
	revSuffix: " [rev. #%0]", // text to append to each tiddler title
	defaultPageSize: 5, // default number of revisions to show
	defaultLinkText: "View Revisions", // when there's nothing else to use
	offsetTop: 30, // in px
	offsetLeft: 10, // in px
	shiftDownDelay: 50, // in ms
	visibleSlideAmount: 20, // amount of revisions to show on left hand edge after sliding
	zIndex: 100, // default z-index
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		params = paramString.parseParams(null, null, true)[0];
		var tiddlerElem = story.findContainingTiddler(place);

		var revButton;
		var pageSize = parseInt(params.page[0], 10) || me.defaultPageSize;
		var linkObj = params.link ? params.link[0] || me.defaultLinkText : false;
		if(linkObj) {
			 revButton = $('')
				.appendTo(place);
			wikify(linkObj, revButton[0], null, tiddler);
		} else {
			revButton = place;
		}

		$(revButton).click(function() {
			if (!$(tiddlerElem).hasClass("revisions")) {
				me.showRevisions(tiddlerElem, tiddler, pageSize);
			} else {
				me.closeRevisions(tiddlerElem);
			}
		});
	},

	// initialisation for revision view
	showRevisions: function(tiddlerElem, tiddler, pageSize) {
		var context = {
			host: tiddler.fields["server.host"],
			workspace: tiddler.fields["server.workspace"]
		};
		$(tiddlerElem).addClass("revisions").attr("revName", tiddler.title);
		// ensure toolbar commands deactivate RevisionsView
		$("a", ".toolbar", tiddlerElem).each(function(index, btn) {
			var _onclick = btn.onclick;
			btn.onclick = function(e) {
				me.closeRevisions(tiddlerElem);
				_onclick.apply(this, arguments);
			};
		});
		// ensure default action deactivates RevisionsView
		var _ondblclick = tiddlerElem.ondblclick;
		tiddlerElem.ondblclick = function(e) {
			me.closeRevisions(tiddlerElem);
			_ondblclick.apply(this, arguments);
		};
		var type = tiddler.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var userParams = {
			tiddlerElem: tiddlerElem,
			pageSize: pageSize,
			title: tiddler.title
		};
		me.createCloak(tiddlerElem);
		adaptor.getTiddlerRevisionList(tiddler.title, null, context, userParams,
				function(context, userParams) {
					// strip the current revision
					context.revisions.shift();
					me.expandStack(context, userParams);
				});
	},

	// fetch the actual revision and put it in the tiddler div
	showRevision: function(place, revision, callback) {
		var context = {
			host: revision.fields["server.host"],
			workspace: revision.fields["server.workspace"]
		};
		var userParams = {
			revElem: place
		};
		var type = revision.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var revNo = revision.fields["server.page.revision"];
		adaptor.getTiddlerRevision(revision.title, revNo, context, userParams,
			function(context, userParams) {
				var tiddler = context.tiddler;
				tiddler.title += me.revSuffix
					.format([$(place).attr("revision")]);
				tiddler.fields.doNotSave = true;
				if (store.getTiddler(tiddler.title)) {
					store.deleteTiddler(tiddler.title);
				}
				store.addTiddler(tiddler);

				//now, populate the existing div
				var revElem = userParams.revElem;
				$(revElem).attr("id", story.tiddlerId(tiddler.title));
				$(revElem).attr("refresh", "tiddler");
				var getTemplate = function() {
					var themeName = config.options.txtTheme;
					if (themeName) {
						return store.getTiddlerSlice(themeName,
							me.revisionTemplate) || me.revisionTemplate ||
							"ViewTemplate";
					} else {
						return (store.getTiddler(me.revisionTemplate)) ?
							me.revisionTemplate : "ViewTemplate";
					}
				};
				var template = getTemplate();
				story.refreshTiddler(tiddler.title, template, true);
				callback(tiddler);
			});
	},

	createCloak: function(promoteElem) {
		var el = $(promoteElem);
		// cache styles for resetting later
		el.data({
			top: el.css("top"),
			left: el.css("left"),
			zIndex: el.css("z-index")
		});

		$('
').css("z-index", me.zIndex)
			.click(function() {
				me.closeRevisions(promoteElem);
			})
			.appendTo(document.body);

		el.css("z-index", me.zIndex + 1);
	},

	// clean up, removing all evidence of revision view
	closeRevisions: function(promoteElem) {
		var el = $(promoteElem);
		// revert the original tiddler back to its previous state
		el.removeAttr("revName").removeClass("revisions").css({
			top: el.data("top"),
			left: el.data("left"),
			zIndex: el.data("zIndex")
		});

		// remove any revisions still in the store
		var revisions = $(".revisions");
		revisions.each(function(index, revision) {
			var revAttributes = revision.attributes;
			if ((revAttributes.revname) &&
					(revAttributes.revision)) {
				var revName = revAttributes.revname.value;
				var revNo = revAttributes.revision.value;
				var title = revName + me.revSuffix.format([revNo]);

				if (store.getTiddler(title)) {
					store.deleteTiddler(title);
				}
			}
		});

		// delete the previous revisions
		revisions.remove();

		// remove the cloak
		$(".revisionCloak").remove();
	},

	// calback from getting list of revisions
	expandStack: function(context, userParams) {
		var pageSize = userParams.pageSize;

		var from = userParams.from || 0;
		var tiddlerElem = userParams.tiddlerElem;

		userParams.defaultHeight = $(tiddlerElem).height();
		userParams.defaultWidth = $(tiddlerElem).width();
		if (from < context.revisions.length) {
			me.displayNextRevision(tiddlerElem, userParams, context, from,
				from + pageSize - 1);
		}
	},

	// place the next div above and behind the previous one
	displayNextRevision: function(tiddlerElem, userParams, context, from, to) {
		var revision = context.revisions[from];
		var callback = function() {
			var revText = revBtn.getRevisionText(tiddlerElem, revision);
			tiddlerElem = me.createRevisionObject(tiddlerElem, context,
				userParams, revText);
			$(tiddlerElem)
				.attr("revision", (context.revisions.length - from));
			if ((from < to) && ((from + 1) < context.revisions.length)){
				me.displayNextRevision(tiddlerElem, userParams, context,
					from + 1, to);
			} else if ((context.revisions.length - 1) > to) {
				me.showMoreButton(tiddlerElem, context, userParams, to + 1);
			}
		}
		me.shiftVisibleDown(userParams.title, callback);
	},

	createRevisionObject: function(tiddlerElem, context, userParams, text) {
		var newPosition = me.calculatePosition(tiddlerElem, context);
		return $('
')
			.css({
				position: "absolute",
				top: newPosition.top,
				left: newPosition.left,
				"z-index": me.zIndex + 1,
				height: userParams.defaultHeight,
				width: userParams.defaultWidth
			})
			.attr("revName", userParams.title)
			.append(text)
			.insertBefore(tiddlerElem);
	},

	// move the already present revisions down by 1 to fit the next one in
	shiftVisibleDown: function(title, callback) {
		var revisions = $("[revName=%0].revisions".format([title]));
		var revisionCount = revisions.length;

		$(revisions).animate({top: "+=" + me.offsetTop},
				me.shiftDownDelay, function() {
					revisionCount -= 1;
					if ((callback) && (!revisionCount)) {
						callback();
					}
				});
	},

	// where we put the new revision
	calculatePosition: function(elem, context) {
		var offset = $(elem).offset();
		var currentPosition = $(elem).position();
		var newPosition = {
			top: currentPosition.top - me.offsetTop
		};
		if ((context.restrictLeft) ||
				((offset.left - me.offsetLeft) <
				$("#contentWrapper").offset().left)) {
			newPosition.left = $(elem).position().left;
			context.restrictLeft = true;
		} else {
			newPosition.left = currentPosition.left - me.offsetLeft;
		}
		return newPosition;
	},

	// equivalent of displayNextRevision, but for the more button
	showMoreButton: function(tiddlerElem, context, userParams, moreIndex) {
		userParams.from = moreIndex + 1;
		me.shiftVisibleDown(userParams.title, function() {
			var btn = me.createRevisionObject(tiddlerElem, context, userParams,
				"");

			var more = createTiddlyButton(btn[0], "more...", "show more revisions",
				function() {
					if ($(".viewRevision").length) {
						return;
					}
					userParams.tiddlerElem = btn[0];
					$(btn).text("")
						.append(revBtn
							.getRevisionText(btn[0], context.revisions[moreIndex]))
						.attr("revision", context.revisions.length - moreIndex);
					me.expandStack(context, userParams);
				});
			$(more).css("float", "right");
		});
	},

	stripRevFromTitle: function(revisionTitle) {
		return revisionTitle.split(/ ?\[rev\. #[0-9]+\]$/)[0];
	},

	onClickRevision: function(revElem, revision, callback) {
		// don't do anything if we are still loading
		if ($(".revisions").hasClass("loading")) {
			return null;
		}

		var origTitle = me.stripRevFromTitle(revision.title);
		if ($(revElem).hasClass("viewRevision")) {
			$(".revisions").addClass("loading");
			me.slideIn(revElem, revision, origTitle, function() {
				store.deleteTiddler(revision.title);
				revision.title = origTitle;
				$(revElem).text("").append(revBtn.getRevisionText(revElem,
						revision))
					.removeAttr("tags").removeAttr("tiddler")
					.removeAttr("refresh").removeAttr("template")
					.removeAttr("id");
				$(".revisions").removeClass("loading");
				if (callback) {
					callback();
				}
			});
			$(revElem).removeAttr("prevPos").removeClass("viewRevision");
		} else {
			var viewRevision = function() {
				var prevPos = $(revElem).position().left;
				$(revElem).addClass("viewRevision").attr("prevPos", prevPos);
				$(".revisions").addClass("loading");
				me.showRevision(revElem, revision, function(rev) {
					me.slideOut(revElem, rev, origTitle, function() {
						$(".revisions").removeClass("loading");
					});
				});
			};
			// make sure another revision isn't already out
			if ($(".viewRevision").length) {
				var newRevElem = $(".viewRevision")[0];
				var newRevision = store.getTiddler($(newRevElem)
					.attr("tiddler"));
				me.onClickRevision(newRevElem, newRevision, viewRevision);
			} else {
				viewRevision();
			}
		}
	},

	slideOut: function(revElem, revision, title, callback) {
		var leftMostPos = $("[revName=%0].revisions".format([title]))
			.offset().left;
		var width = $(revElem).width();
		var originalLeftPos = $(story.getTiddler(title))
			.position().left;

		var slideAmount = leftMostPos + width - me.visibleSlideAmount;
		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "-=" + slideAmount}, 1000);
		$(revElem)
			.attr("baseHeight", $(revElem).css("height"))
			.css("height", "auto")
			.animate({left: originalLeftPos}, 1000, callback);
	},

	slideIn: function(revElem, revision, title, callback) {
		var slideAmount = $(revElem).offset().left -
			$(story.getTiddler(title)).offset().left;
		var origRevPos = $(revElem).attr("prevPos");

		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "+=" + slideAmount}, 1000);
		$(revElem).animate({left: origRevPos}, 1000, function() {
			$(revElem)
				.css("height", $(revElem).attr("baseHeight"))
				.removeAttr("baseHeight");
			callback();
		});
	}
};

var revBtn;
config.macros.slideRevision = revBtn = {
	btnText: "created by %0 at %1 on %2",
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var btn = revBtn.getRevisionText(place, tiddler);
		$(place).append(btn);
	},

	getRevisionText: function(place, revision) {
		var text = revBtn.btnText.format([revision.modifier,
			revision.modified.formatString("0hh:0mm"),
			revision.modified.formatString("0DD MMM YYYY")]);
		var btn = $('')
			.text(text)
			.click(function() {
				var revElem = story.findContainingTiddler(this);
				me.onClickRevision(revElem, revision);
			});
		return btn;
	}
};

})(jQuery);
//}}}
<commit_msg>fix #864 by quoting the tiddler title in the expression:<commit_after>/***
|''Name''|TiddlySpaceRevisionView|
|''Description''|Show tiddler revisions in a stack of cards view|
|''Author''|BenGillies|
|''Version''|0.2.0|
|''Status''|beta|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor|
!Usage
The viewRevisions macro can be attached to any element, which should be passed
in as a parameter.

For example:

<<viewRevisions page:10 link:"<<view modified date>>">>

would show the revisions "stack of cards" view, 10 at a time, when the modified
date is clicked.
!Code
***/
//{{{
(function($) {

var me = config.macros.viewRevisions = {
	revisionTemplate: "RevisionTemplate",
	revSuffix: " [rev. #%0]", // text to append to each tiddler title
	defaultPageSize: 5, // default number of revisions to show
	defaultLinkText: "View Revisions", // when there's nothing else to use
	offsetTop: 30, // in px
	offsetLeft: 10, // in px
	shiftDownDelay: 50, // in ms
	visibleSlideAmount: 20, // amount of revisions to show on left hand edge after sliding
	zIndex: 100, // default z-index
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		params = paramString.parseParams(null, null, true)[0];
		var tiddlerElem = story.findContainingTiddler(place);

		var revButton;
		var pageSize = parseInt(params.page[0], 10) || me.defaultPageSize;
		var linkObj = params.link ? params.link[0] || me.defaultLinkText : false;
		if(linkObj) {
			 revButton = $('')
				.appendTo(place);
			wikify(linkObj, revButton[0], null, tiddler);
		} else {
			revButton = place;
		}

		$(revButton).click(function() {
			if (!$(tiddlerElem).hasClass("revisions")) {
				me.showRevisions(tiddlerElem, tiddler, pageSize);
			} else {
				me.closeRevisions(tiddlerElem);
			}
		});
	},

	// initialisation for revision view
	showRevisions: function(tiddlerElem, tiddler, pageSize) {
		var context = {
			host: tiddler.fields["server.host"],
			workspace: tiddler.fields["server.workspace"]
		};
		$(tiddlerElem).addClass("revisions").attr("revName", tiddler.title);
		// ensure toolbar commands deactivate RevisionsView
		$("a", ".toolbar", tiddlerElem).each(function(index, btn) {
			var _onclick = btn.onclick;
			btn.onclick = function(e) {
				me.closeRevisions(tiddlerElem);
				_onclick.apply(this, arguments);
			};
		});
		// ensure default action deactivates RevisionsView
		var _ondblclick = tiddlerElem.ondblclick;
		tiddlerElem.ondblclick = function(e) {
			me.closeRevisions(tiddlerElem);
			_ondblclick.apply(this, arguments);
		};
		var type = tiddler.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var userParams = {
			tiddlerElem: tiddlerElem,
			pageSize: pageSize,
			title: tiddler.title
		};
		me.createCloak(tiddlerElem);
		adaptor.getTiddlerRevisionList(tiddler.title, null, context, userParams,
				function(context, userParams) {
					// strip the current revision
					context.revisions.shift();
					me.expandStack(context, userParams);
				});
	},

	// fetch the actual revision and put it in the tiddler div
	showRevision: function(place, revision, callback) {
		var context = {
			host: revision.fields["server.host"],
			workspace: revision.fields["server.workspace"]
		};
		var userParams = {
			revElem: place
		};
		var type = revision.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var revNo = revision.fields["server.page.revision"];
		adaptor.getTiddlerRevision(revision.title, revNo, context, userParams,
			function(context, userParams) {
				var tiddler = context.tiddler;
				tiddler.title += me.revSuffix
					.format([$(place).attr("revision")]);
				tiddler.fields.doNotSave = true;
				if (store.getTiddler(tiddler.title)) {
					store.deleteTiddler(tiddler.title);
				}
				store.addTiddler(tiddler);

				//now, populate the existing div
				var revElem = userParams.revElem;
				$(revElem).attr("id", story.tiddlerId(tiddler.title));
				$(revElem).attr("refresh", "tiddler");
				var getTemplate = function() {
					var themeName = config.options.txtTheme;
					if (themeName) {
						return store.getTiddlerSlice(themeName,
							me.revisionTemplate) || me.revisionTemplate ||
							"ViewTemplate";
					} else {
						return (store.getTiddler(me.revisionTemplate)) ?
							me.revisionTemplate : "ViewTemplate";
					}
				};
				var template = getTemplate();
				story.refreshTiddler(tiddler.title, template, true);
				callback(tiddler);
			});
	},

	createCloak: function(promoteElem) {
		var el = $(promoteElem);
		// cache styles for resetting later
		el.data({
			top: el.css("top"),
			left: el.css("left"),
			zIndex: el.css("z-index")
		});

		$('
').css("z-index", me.zIndex)
			.click(function() {
				me.closeRevisions(promoteElem);
			})
			.appendTo(document.body);

		el.css("z-index", me.zIndex + 1);
	},

	// clean up, removing all evidence of revision view
	closeRevisions: function(promoteElem) {
		var el = $(promoteElem);
		// revert the original tiddler back to its previous state
		el.removeAttr("revName").removeClass("revisions").css({
			top: el.data("top"),
			left: el.data("left"),
			zIndex: el.data("zIndex")
		});

		// remove any revisions still in the store
		var revisions = $(".revisions");
		revisions.each(function(index, revision) {
			var revAttributes = revision.attributes;
			if ((revAttributes.revname) &&
					(revAttributes.revision)) {
				var revName = revAttributes.revname.value;
				var revNo = revAttributes.revision.value;
				var title = revName + me.revSuffix.format([revNo]);

				if (store.getTiddler(title)) {
					store.deleteTiddler(title);
				}
			}
		});

		// delete the previous revisions
		revisions.remove();

		// remove the cloak
		$(".revisionCloak").remove();
	},

	// calback from getting list of revisions
	expandStack: function(context, userParams) {
		var pageSize = userParams.pageSize;

		var from = userParams.from || 0;
		var tiddlerElem = userParams.tiddlerElem;

		userParams.defaultHeight = $(tiddlerElem).height();
		userParams.defaultWidth = $(tiddlerElem).width();
		if (from < context.revisions.length) {
			me.displayNextRevision(tiddlerElem, userParams, context, from,
				from + pageSize - 1);
		}
	},

	// place the next div above and behind the previous one
	displayNextRevision: function(tiddlerElem, userParams, context, from, to) {
		var revision = context.revisions[from];
		var callback = function() {
			var revText = revBtn.getRevisionText(tiddlerElem, revision);
			tiddlerElem = me.createRevisionObject(tiddlerElem, context,
				userParams, revText);
			$(tiddlerElem)
				.attr("revision", (context.revisions.length - from));
			if ((from < to) && ((from + 1) < context.revisions.length)){
				me.displayNextRevision(tiddlerElem, userParams, context,
					from + 1, to);
			} else if ((context.revisions.length - 1) > to) {
				me.showMoreButton(tiddlerElem, context, userParams, to + 1);
			}
		}
		me.shiftVisibleDown(userParams.title, callback);
	},

	createRevisionObject: function(tiddlerElem, context, userParams, text) {
		var newPosition = me.calculatePosition(tiddlerElem, context);
		return $('
')
			.css({
				position: "absolute",
				top: newPosition.top,
				left: newPosition.left,
				"z-index": me.zIndex + 1,
				height: userParams.defaultHeight,
				width: userParams.defaultWidth
			})
			.attr("revName", userParams.title)
			.append(text)
			.insertBefore(tiddlerElem);
	},

	// move the already present revisions down by 1 to fit the next one in
	shiftVisibleDown: function(title, callback) {
		var revisions = $("[revName='%0'].revisions".format([title]));
		var revisionCount = revisions.length;

		$(revisions).animate({top: "+=" + me.offsetTop},
				me.shiftDownDelay, function() {
					revisionCount -= 1;
					if ((callback) && (!revisionCount)) {
						callback();
					}
				});
	},

	// where we put the new revision
	calculatePosition: function(elem, context) {
		var offset = $(elem).offset();
		var currentPosition = $(elem).position();
		var newPosition = {
			top: currentPosition.top - me.offsetTop
		};
		if ((context.restrictLeft) ||
				((offset.left - me.offsetLeft) <
				$("#contentWrapper").offset().left)) {
			newPosition.left = $(elem).position().left;
			context.restrictLeft = true;
		} else {
			newPosition.left = currentPosition.left - me.offsetLeft;
		}
		return newPosition;
	},

	// equivalent of displayNextRevision, but for the more button
	showMoreButton: function(tiddlerElem, context, userParams, moreIndex) {
		userParams.from = moreIndex + 1;
		me.shiftVisibleDown(userParams.title, function() {
			var btn = me.createRevisionObject(tiddlerElem, context, userParams,
				"");

			var more = createTiddlyButton(btn[0], "more...", "show more revisions",
				function() {
					if ($(".viewRevision").length) {
						return;
					}
					userParams.tiddlerElem = btn[0];
					$(btn).text("")
						.append(revBtn
							.getRevisionText(btn[0], context.revisions[moreIndex]))
						.attr("revision", context.revisions.length - moreIndex);
					me.expandStack(context, userParams);
				});
			$(more).css("float", "right");
		});
	},

	stripRevFromTitle: function(revisionTitle) {
		return revisionTitle.split(/ ?\[rev\. #[0-9]+\]$/)[0];
	},

	onClickRevision: function(revElem, revision, callback) {
		// don't do anything if we are still loading
		if ($(".revisions").hasClass("loading")) {
			return null;
		}

		var origTitle = me.stripRevFromTitle(revision.title);
		if ($(revElem).hasClass("viewRevision")) {
			$(".revisions").addClass("loading");
			me.slideIn(revElem, revision, origTitle, function() {
				store.deleteTiddler(revision.title);
				revision.title = origTitle;
				$(revElem).text("").append(revBtn.getRevisionText(revElem,
						revision))
					.removeAttr("tags").removeAttr("tiddler")
					.removeAttr("refresh").removeAttr("template")
					.removeAttr("id");
				$(".revisions").removeClass("loading");
				if (callback) {
					callback();
				}
			});
			$(revElem).removeAttr("prevPos").removeClass("viewRevision");
		} else {
			var viewRevision = function() {
				var prevPos = $(revElem).position().left;
				$(revElem).addClass("viewRevision").attr("prevPos", prevPos);
				$(".revisions").addClass("loading");
				me.showRevision(revElem, revision, function(rev) {
					me.slideOut(revElem, rev, origTitle, function() {
						$(".revisions").removeClass("loading");
					});
				});
			};
			// make sure another revision isn't already out
			if ($(".viewRevision").length) {
				var newRevElem = $(".viewRevision")[0];
				var newRevision = store.getTiddler($(newRevElem)
					.attr("tiddler"));
				me.onClickRevision(newRevElem, newRevision, viewRevision);
			} else {
				viewRevision();
			}
		}
	},

	slideOut: function(revElem, revision, title, callback) {
		var leftMostPos = $("[revName='%0'].revisions".format([title]))
			.offset().left;
		var width = $(revElem).width();
		var originalLeftPos = $(story.getTiddler(title))
			.position().left;

		var slideAmount = leftMostPos + width - me.visibleSlideAmount;
		$("[revName='%0'].revisions:not(.viewRevision)".format([title]))
			.animate({left: "-=" + slideAmount}, 1000);
		$(revElem)
			.attr("baseHeight", $(revElem).css("height"))
			.css("height", "auto")
			.animate({left: originalLeftPos}, 1000, callback);
	},

	slideIn: function(revElem, revision, title, callback) {
		var slideAmount = $(revElem).offset().left -
			$(story.getTiddler(title)).offset().left;
		var origRevPos = $(revElem).attr("prevPos");

		$("[revName='%0'].revisions:not(.viewRevision)".format([title]))
			.animate({left: "+=" + slideAmount}, 1000);
		$(revElem).animate({left: origRevPos}, 1000, function() {
			$(revElem)
				.css("height", $(revElem).attr("baseHeight"))
				.removeAttr("baseHeight");
			callback();
		});
	}
};

var revBtn;
config.macros.slideRevision = revBtn = {
	btnText: "created by %0 at %1 on %2",
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var btn = revBtn.getRevisionText(place, tiddler);
		$(place).append(btn);
	},

	getRevisionText: function(place, revision) {
		var text = revBtn.btnText.format([revision.modifier,
			revision.modified.formatString("0hh:0mm"),
			revision.modified.formatString("0DD MMM YYYY")]);
		var btn = $('')
			.text(text)
			.click(function() {
				var revElem = story.findContainingTiddler(this);
				me.onClickRevision(revElem, revision);
			});
		return btn;
	}
};

})(jQuery);
//}}}
<|endoftext|>


<commit_before>import levenshtein from 'fast-levenshtein'
import { PROXY_IS_MOUNTED, PROXY_KEY, UNWRAP_PROXY } from '../proxy'
import { getIdByType, updateProxyById } from './proxies'
import {
  updateInstance,
  getComponentDisplayName,
  isFragmentNode,
} from '../internal/reactUtils'
import reactHotLoader from '../reactHotLoader'
import logger from '../logger'

// some `empty` names, React can autoset display name to...
const UNDEFINED_NAMES = {
  Unknown: true,
  Component: true,
}

let renderStack = []

const stackReport = () => {
  const rev = renderStack.slice().reverse()
  logger.warn('in', rev[0].name, rev)
}

const areNamesEqual = (a, b) =>
  a === b || (UNDEFINED_NAMES[a] && UNDEFINED_NAMES[b])
const isReactClass = fn => fn && !!fn.render
const isFunctional = fn => typeof fn === 'function'
const isArray = fn => Array.isArray(fn)
const asArray = a => (isArray(a) ? a : [a])
const getTypeOf = type => {
  if (isReactClass(type)) return 'ReactComponent'
  if (isFunctional(type)) return 'StatelessFunctional'
  return 'Fragment' // ?
}

const filterNullArray = a => {
  if (!a) return []
  return a.filter(x => !!x)
}

const unflatten = a =>
  a.reduce((acc, a) => {
    if (Array.isArray(a)) {
      acc.push(...unflatten(a))
    } else {
      acc.push(a)
    }
    return acc
  }, [])

const getElementType = child =>
  child.type[UNWRAP_PROXY] ? child.type[UNWRAP_PROXY]() : child.type

const haveTextSimilarity = (a, b) =>
  // equal or slight changed
  a === b || levenshtein.get(a, b) < a.length * 0.2

const equalClasses = (a, b) => {
  const prototypeA = a.prototype
  const prototypeB = Object.getPrototypeOf(b.prototype)

  let hits = 0
  let misses = 0
  Object.getOwnPropertyNames(prototypeA).forEach(key => {
    if (typeof prototypeA[key] === 'function' && key !== 'constructor') {
      if (
        haveTextSimilarity(String(prototypeA[key]), String(prototypeB[key]))
      ) {
        hits++
      } else {
        misses++
        if (key === 'render') {
          misses++
        }
      }
    }
  })
  // allow to add or remove one function
  return hits > 0 && misses <= 1
}

const isSwappable = (a, b) => {
  // both are registered components
  if (getIdByType(b) && getIdByType(a) === getIdByType(b)) {
    return true
  }
  if (getTypeOf(a) !== getTypeOf(b)) {
    return false
  }
  if (isReactClass(a.prototype)) {
    return (
      areNamesEqual(getComponentDisplayName(a), getComponentDisplayName(b)) &&
      equalClasses(a, b)
    )
  }

  if (isFunctional(a)) {
    const nameA = getComponentDisplayName(a)
    return (
      (areNamesEqual(nameA, getComponentDisplayName(b)) &&
        nameA !== 'Component') ||
      haveTextSimilarity(String(a), String(b))
    )
  }
  return false
}

const render = component => {
  if (!component) {
    return []
  }
  if (isReactClass(component)) {
    // not calling real render method to prevent call recursion.
    // stateless components does not have hotComponentRender
    return component.hotComponentRender
      ? component.hotComponentRender()
      : component.render()
  }
  if (isArray(component)) {
    return component.map(render)
  }
  if (component.children) {
    return component.children
  }

  return []
}

const NO_CHILDREN = { children: [] }
const mapChildren = (children, instances) => ({
  children: children.filter(c => c).map((child, index) => {
    if (typeof child !== 'object') {
      return child
    }
    const instanceLine = instances[index] || {}
    const oldChildren = asArray(instanceLine.children || [])

    if (Array.isArray(child)) {
      return {
        type: null,
        ...mapChildren(child, oldChildren),
      }
    }

    const newChildren = asArray(
      (child.props && child.props.children) || child.children || [],
    )
    const nextChildren =
      oldChildren.length && mapChildren(newChildren, oldChildren)

    return {
      nextProps: child.props,
      ...instanceLine,
      // actually child merge is needed only for "HTML TAG"s, and usually don't work for Components.
      // the children from an instance or rendered children
      // while children from a props is just a props.
      // they could not exists. they could differ.
      ...(nextChildren || {}),
      type: child.type,
    }
  }),
})

const mergeInject = (a, b, instance) => {
  if (a && !Array.isArray(a)) {
    return mergeInject([a], b)
  }
  if (b && !Array.isArray(b)) {
    return mergeInject(a, [b])
  }

  if (!a || !b) {
    return NO_CHILDREN
  }
  if (a.length === b.length) {
    return mapChildren(a, b)
  }

  // in some cases (no confidence here) B could contain A except null children
  // in some cases - could not.
  // this depends on React version and the way you build component.

  const nonNullA = filterNullArray(a)
  if (nonNullA.length === b.length) {
    return mapChildren(nonNullA, b)
  }

  const flatA = unflatten(nonNullA)
  const flatB = unflatten(b)
  if (flatA.length === flatB.length) {
    return mapChildren(flatA, flatB)
  }
  if (
    flatB.length === 0 &&
    flatA.length === 1 &&
    typeof flatA[0] !== 'object'
  ) {
    // terminal node
  } else {
    logger.warn(
      `React-hot-loader: unable to merge `,
      a,
      'and children of ',
      instance,
    )
    stackReport()
  }
  return NO_CHILDREN
}

const transformFlowNode = flow =>
  flow.reduce((acc, node) => {
    if (isFragmentNode(node) && node.props && node.props.children) {
      return [...acc, ...node.props.children]
    }
    return [...acc, node]
  }, [])

let scheduledUpdates = []
let scheduledUpdate = 0

export const flushScheduledUpdates = () => {
  const instances = scheduledUpdates
  scheduledUpdates = []
  scheduledUpdate = 0
  instances.forEach(
    instance => instance[PROXY_IS_MOUNTED] && updateInstance(instance),
  )
}

export const unscheduleUpdate = instance => {
  scheduledUpdates = scheduledUpdates.filter(inst => inst !== instance)
}

const scheduleInstanceUpdate = instance => {
  scheduledUpdates.push(instance)
  if (!scheduledUpdate) {
    scheduledUpdate = setTimeout(flushScheduledUpdates)
  }
}

const hotReplacementRender = (instance, stack) => {
  if (isReactClass(instance)) {
    const type = getElementType(stack)
    renderStack.push({
      name: getComponentDisplayName(type),
      type,
      props: stack.props,
    })
  }
  const flow = transformFlowNode(filterNullArray(asArray(render(instance))))

  const { children } = stack

  flow.forEach((child, index) => {
    const stackChild = children[index]
    const next = instance => {
      // copy over props as long new component may be hidden inside them
      // child does not have all props, as long some of them can be calculated on componentMount.
      const nextProps = {
        ...instance.props,
        ...(child.nextProps || {}),
        ...(child.props || {}),
      }

      if (isReactClass(instance) && instance.componentWillUpdate) {
        // Force-refresh component (bypass redux renderedComponent)
        instance.componentWillUpdate(nextProps, instance.state)
      }
      instance.props = nextProps
      hotReplacementRender(instance, stackChild)
    }

    // text node
    if (typeof child !== 'object' || !stackChild || !stackChild.instance) {
      return
    }

    if (typeof child.type !== typeof stackChild.type) {
      // Portals could generate undefined !== null
      if (child.type && stackChild.type) {
        logger.warn(
          'React-hot-loader: got ',
          child.type,
          'instead of',
          stackChild.type,
        )
        stackReport()
      }
      return
    }

    if (typeof child.type !== 'function') {
      next(
        // move types from render to the instances of hydrated tree
        mergeInject(
          asArray(child.props ? child.props.children : child.children),
          stackChild.instance.children,
          stackChild.instance,
        ),
      )
    } else {
      // unwrap proxy
      const childType = getElementType(child)
      if (!stackChild.type[PROXY_KEY]) {
        /* eslint-disable no-console */
        logger.error(
          'React-hot-loader: fatal error caused by ',
          stackChild.type,
          ' - no instrumentation found. ',
          'Please require react-hot-loader before React. More in troubleshooting.',
        )
        stackReport()
        throw new Error('React-hot-loader: wrong configuration')
      }

      if (child.type === stackChild.type) {
        next(stackChild.instance)
      } else if (isSwappable(childType, stackChild.type)) {
        // they are both registered, or have equal code/displayname/signature

        // update proxy using internal PROXY_KEY
        updateProxyById(stackChild.type[PROXY_KEY], childType)

        next(stackChild.instance)
      } else {
        logger.warn(
          `React-hot-loader: a ${getComponentDisplayName(
            childType,
          )} was found where a ${getComponentDisplayName(
            stackChild,
          )} was expected.
          ${childType}`,
        )
        stackReport()
      }

      scheduleInstanceUpdate(stackChild.instance)
    }
  })

  if (isReactClass(instance)) {
    renderStack.pop()
  }
}

export default (instance, stack) => {
  try {
    // disable reconciler to prevent upcoming components from proxying.
    reactHotLoader.disableProxyCreation = true
    renderStack = []
    hotReplacementRender(instance, stack)
  } catch (e) {
    logger.warn('React-hot-loader: reconcilation failed due to error', e)
  } finally {
    reactHotLoader.disableProxyCreation = false
  }
}
<commit_msg>fix SFC props assembling<commit_after>import levenshtein from 'fast-levenshtein'
import { PROXY_IS_MOUNTED, PROXY_KEY, UNWRAP_PROXY } from '../proxy'
import { getIdByType, updateProxyById } from './proxies'
import {
  updateInstance,
  getComponentDisplayName,
  isFragmentNode,
} from '../internal/reactUtils'
import reactHotLoader from '../reactHotLoader'
import logger from '../logger'

// some `empty` names, React can autoset display name to...
const UNDEFINED_NAMES = {
  Unknown: true,
  Component: true,
}

let renderStack = []

const stackReport = () => {
  const rev = renderStack.slice().reverse()
  logger.warn('in', rev[0].name, rev)
}

const areNamesEqual = (a, b) =>
  a === b || (UNDEFINED_NAMES[a] && UNDEFINED_NAMES[b])
const isReactClass = fn => fn && !!fn.render
const isFunctional = fn => typeof fn === 'function'
const isArray = fn => Array.isArray(fn)
const asArray = a => (isArray(a) ? a : [a])
const getTypeOf = type => {
  if (isReactClass(type)) return 'ReactComponent'
  if (isFunctional(type)) return 'StatelessFunctional'
  return 'Fragment' // ?
}

const filterNullArray = a => {
  if (!a) return []
  return a.filter(x => !!x)
}

const unflatten = a =>
  a.reduce((acc, a) => {
    if (Array.isArray(a)) {
      acc.push(...unflatten(a))
    } else {
      acc.push(a)
    }
    return acc
  }, [])

const getElementType = child =>
  child.type[UNWRAP_PROXY] ? child.type[UNWRAP_PROXY]() : child.type

const haveTextSimilarity = (a, b) =>
  // equal or slight changed
  a === b || levenshtein.get(a, b) < a.length * 0.2

const equalClasses = (a, b) => {
  const prototypeA = a.prototype
  const prototypeB = Object.getPrototypeOf(b.prototype)

  let hits = 0
  let misses = 0
  Object.getOwnPropertyNames(prototypeA).forEach(key => {
    if (typeof prototypeA[key] === 'function' && key !== 'constructor') {
      if (
        haveTextSimilarity(String(prototypeA[key]), String(prototypeB[key]))
      ) {
        hits++
      } else {
        misses++
        if (key === 'render') {
          misses++
        }
      }
    }
  })
  // allow to add or remove one function
  return hits > 0 && misses <= 1
}

const isSwappable = (a, b) => {
  // both are registered components
  if (getIdByType(b) && getIdByType(a) === getIdByType(b)) {
    return true
  }
  if (getTypeOf(a) !== getTypeOf(b)) {
    return false
  }
  if (isReactClass(a.prototype)) {
    return (
      areNamesEqual(getComponentDisplayName(a), getComponentDisplayName(b)) &&
      equalClasses(a, b)
    )
  }

  if (isFunctional(a)) {
    const nameA = getComponentDisplayName(a)
    return (
      (areNamesEqual(nameA, getComponentDisplayName(b)) &&
        nameA !== 'Component') ||
      haveTextSimilarity(String(a), String(b))
    )
  }
  return false
}

const render = component => {
  if (!component) {
    return []
  }
  if (isReactClass(component)) {
    // not calling real render method to prevent call recursion.
    // stateless components does not have hotComponentRender
    return component.hotComponentRender
      ? component.hotComponentRender()
      : component.render()
  }
  if (isArray(component)) {
    return component.map(render)
  }
  if (component.children) {
    return component.children
  }

  return []
}

const NO_CHILDREN = { children: [] }
const mapChildren = (children, instances) => ({
  children: children.filter(c => c).map((child, index) => {
    if (typeof child !== 'object' || child.isMerged) {
      return child
    }
    const instanceLine = instances[index] || {}
    const oldChildren = asArray(instanceLine.children || [])

    if (Array.isArray(child)) {
      return {
        type: null,
        ...mapChildren(child, oldChildren),
      }
    }

    const newChildren = asArray(
      (child.props && child.props.children) || child.children || [],
    )
    const nextChildren =
      child.type !== 'function' &&
      oldChildren.length &&
      mapChildren(newChildren, oldChildren)

    return {
      nextProps: child.props,
      isMerged: true,
      ...instanceLine,
      // actually child merge is needed only for "HTML TAG"s, and usually don't work for Components.
      // the children from an instance or rendered children
      // while children from a props is just a props.
      // they could not exists. they could differ.
      ...(nextChildren || {}),
      type: child.type,
    }
  }),
})

const mergeInject = (a, b, instance) => {
  if (a && !Array.isArray(a)) {
    return mergeInject([a], b)
  }
  if (b && !Array.isArray(b)) {
    return mergeInject(a, [b])
  }

  if (!a || !b) {
    return NO_CHILDREN
  }
  if (a.length === b.length) {
    return mapChildren(a, b)
  }

  // in some cases (no confidence here) B could contain A except null children
  // in some cases - could not.
  // this depends on React version and the way you build component.

  const nonNullA = filterNullArray(a)
  if (nonNullA.length === b.length) {
    return mapChildren(nonNullA, b)
  }

  const flatA = unflatten(nonNullA)
  const flatB = unflatten(b)
  if (flatA.length === flatB.length) {
    return mapChildren(flatA, flatB)
  }
  if (
    flatB.length === 0 &&
    flatA.length === 1 &&
    typeof flatA[0] !== 'object'
  ) {
    // terminal node
  } else {
    logger.warn(
      `React-hot-loader: unable to merge `,
      a,
      'and children of ',
      instance,
    )
    stackReport()
  }
  return NO_CHILDREN
}

const transformFlowNode = flow =>
  flow.reduce((acc, node) => {
    if (isFragmentNode(node) && node.props && node.props.children) {
      return [...acc, ...node.props.children]
    }
    return [...acc, node]
  }, [])

let scheduledUpdates = []
let scheduledUpdate = 0

export const flushScheduledUpdates = () => {
  const instances = scheduledUpdates
  scheduledUpdates = []
  scheduledUpdate = 0
  instances.forEach(
    instance => instance[PROXY_IS_MOUNTED] && updateInstance(instance),
  )
}

export const unscheduleUpdate = instance => {
  scheduledUpdates = scheduledUpdates.filter(inst => inst !== instance)
}

const scheduleInstanceUpdate = instance => {
  scheduledUpdates.push(instance)
  if (!scheduledUpdate) {
    scheduledUpdate = setTimeout(flushScheduledUpdates)
  }
}

const hotReplacementRender = (instance, stack) => {
  if (isReactClass(instance)) {
    const type = getElementType(stack)
    renderStack.push({
      name: getComponentDisplayName(type),
      type,
      props: stack.instance.props,
    })
  }
  const flow = transformFlowNode(filterNullArray(asArray(render(instance))))

  const { children } = stack

  flow.forEach((child, index) => {
    const stackChild = children[index]
    const next = instance => {
      // copy over props as long new component may be hidden inside them
      // child does not have all props, as long some of them can be calculated on componentMount.
      const nextProps = {
        ...instance.props,
        ...(child.nextProps || {}),
        ...(child.props || {}),
      }

      if (isReactClass(instance) && instance.componentWillUpdate) {
        // Force-refresh component (bypass redux renderedComponent)
        instance.componentWillUpdate(nextProps, instance.state)
      }
      instance.props = nextProps
      hotReplacementRender(instance, stackChild)
    }

    // text node
    if (typeof child !== 'object' || !stackChild || !stackChild.instance) {
      return
    }

    if (typeof child.type !== typeof stackChild.type) {
      // Portals could generate undefined !== null
      if (child.type && stackChild.type) {
        logger.warn(
          'React-hot-loader: got ',
          child.type,
          'instead of',
          stackChild.type,
        )
        stackReport()
      }
      return
    }

    if (typeof child.type !== 'function') {
      next(
        // move types from render to the instances of hydrated tree
        mergeInject(
          asArray(child.props ? child.props.children : child.children),
          stackChild.instance.children,
          stackChild.instance,
        ),
      )
    } else {
      // unwrap proxy
      const childType = getElementType(child)
      if (!stackChild.type[PROXY_KEY]) {
        /* eslint-disable no-console */
        logger.error(
          'React-hot-loader: fatal error caused by ',
          stackChild.type,
          ' - no instrumentation found. ',
          'Please require react-hot-loader before React. More in troubleshooting.',
        )
        stackReport()
        throw new Error('React-hot-loader: wrong configuration')
      }

      if (child.type === stackChild.type) {
        next(stackChild.instance)
      } else if (isSwappable(childType, stackChild.type)) {
        // they are both registered, or have equal code/displayname/signature

        // update proxy using internal PROXY_KEY
        updateProxyById(stackChild.type[PROXY_KEY], childType)

        next(stackChild.instance)
      } else {
        logger.warn(
          `React-hot-loader: a ${getComponentDisplayName(
            childType,
          )} was found where a ${getComponentDisplayName(
            stackChild,
          )} was expected.
          ${childType}`,
        )
        stackReport()
      }

      scheduleInstanceUpdate(stackChild.instance)
    }
  })

  if (isReactClass(instance)) {
    renderStack.pop()
  }
}

export default (instance, stack) => {
  try {
    // disable reconciler to prevent upcoming components from proxying.
    reactHotLoader.disableProxyCreation = true
    renderStack = []
    hotReplacementRender(instance, stack)
  } catch (e) {
    logger.warn('React-hot-loader: reconcilation failed due to error', e)
  } finally {
    reactHotLoader.disableProxyCreation = false
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import Formsy from 'formsy-react';
import _ from 'lodash';
import { Checkbox, Input, Select, Textarea } from 'formsy-react-components';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import './Editor.scss';

const DragHandle = SortableHandle(() => ); // This can be any component you want

const SortableItem = SortableElement(({ value }) =>

    <DragHandle />
    {value}

);

const SortableItems = SortableContainer(({ collection, items }) =>

    { items.map((value, index) => <SortableItem collection={collection} key={index} index={index} value={value} />) }

,
);

class Editor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeTab: 1,
      useQuotas: true,
      useOpenQuota: false,
      eventData: {
        questions: [],
        quotas: [],
      },
    };

    this.changeTab = this.changeTab.bind(this);
    this.updateState = this.updateState.bind(this);
    this.updateOrder = this.updateOrder.bind(this);
    this.toggleQuotas = this.toggleQuotas.bind(this);
    this.addQuota = this.addQuota.bind(this);
    this.removeQuota = this.removeQuota.bind(this);
    this.toggleOpenQuota = this.toggleOpenQuota.bind(this);
    this.addQuestion = this.addQuestion.bind(this);
    this.removeQuestion = this.removeQuestion.bind(this);
    this.submitForm = this.submitForm.bind(this);
  }

  changeTab(id) {
    const state = this.state;
    state.activeTab = id;
    this.setState(state);
  }

  updateState(dataType, itemId, field, value) {
    const state = this.state;
    switch (dataType) {
      case 'question':
        (_.find(state.eventData.questions, { id: itemId }))[field] = value;
        break;
      case 'quota':
        (_.find(state.eventData.quotas, { id: itemId }))[field] = value;
        break;
      default:
        break;
    }
    this.setState(state);
  }

  toggleQuotas() {
    const state = this.state;
    state.useQuotas = !state.useQuotas;
    this.setState(state);
  }

  addQuota() {
    const state = this.state;
    state.eventData.quotas.push({
      id: (_.max(state.eventData.quotas.map(n => n.id)) || 0) + 1,
      existsInDb: false,
      title: '',
      max_attendees: 10,
      registration_opens: new Date(),
      registration_closes: new Date(),
    });
    this.setState(state);
  }

  removeQuota(id) {
    const state = this.state;
    delete state.eventData.quotas[id];
    this.setState(state);
  }

  addQuestion() {
    const state = this.state;
    state.eventData.questions.push({
      id: (_.max(state.eventData.questions.map(n => n.id)) || 0) + 1,
      existsInDb: false,
      title: '',
      type: 'text',
    });
    this.setState(state);
  }

  removeQuestion(id) {
    const state = this.state;
    delete state.eventData.questions[id];
    this.setState(state);
  }

  toggleOpenQuota() {
    const state = this.state;
    state.useOpenQuota = !state.useOpenQuota;
    this.setState(state);
  }

  updateOrder(args) {
    // General method for moving array items
    const moveElement = (array, from, to) => {
      const elementToMove = array[from];
      array.splice(from, 1);
      array.splice(to, 0, elementToMove);
      return array;
    };

    const state = this.state;

    // Move quota or question to new position
    switch (args.collection) {
      case 'quotas':
        state.eventData.quotas = moveElement(state.eventData.quotas, args.oldIndex, args.newIndex);
        break;
      case 'questions':
        state.eventData.questions = moveElement(state.eventData.questions, args.oldIndex, args.newIndex);
        break;
      default:
        break;
    }

    this.setState(state);
  }

  submitForm(model) {
    console.log(model);
  }

  render() {
    const quotas = this.state.eventData.quotas.map((item, index) =>

          <Input
            name={`quota-${item.id}-title`}
            value={item.title}
            label="Kiintiön nimi"
            type="text"
            required
            onChange={(field, value) => this.updateState('quota', item.id, 'title', value)}
          />
          <Input
            name={`quota-${item.id}-max-attendees`}
            value={item.max_attendees}
            label="Kiintiön koko"
            type="number"
            required
            onChange={(field, value) => this.updateState('quota', item.id, 'max_attendees', value)}
          />

           this.removeQuota(index)}>Poista

);

    const questionTypes = [
      { value: 'text', label: 'Teksti (lyhyt)' },
      { value: 'textarea', label: 'Teksti (pitkä)' },
      { value: 'number', label: 'Numero' },
      { value: 'select', label: 'Monivalinta (voi valita yhden)' },
      { value: 'checkbox', label: 'Monivalinta (voi valita monta)' },
    ];

    const questions = this.state.eventData.questions.map((item, index) =>

          <Input
            name={`question-${item.id}-title`}
            value={item.title}
            label="Kysymys"
            type="text"
            required
            onChange={(field, value) => this.updateState('question', item.id, 'title', value)}
          />
          <Select
            name={`question-${item.id}-type`}
            value={item.type}
            label="Tyyppi"
            options={questionTypes}
            onChange={(field, value) => this.updateState('question', item.id, 'type', value)}
            required
          />

          <Checkbox
            name={`question-${item.id}-required`}
            value={false}
            label="Pakollinen"
            onChange={(field, value) => this.updateState('question', item.id, 'required', value)}
          />
          <Checkbox
            name={`question-${item.id}-public`}
            value={false}
            label="Julkinen"
            onChange={(field, value) => this.updateState('question', item.id, 'public', value)}
          />
           this.removeQuestion(index)}>Poista

);

    return (
      <Formsy.Form
        onSubmit={this.submitForm}
        className="event-editor form-horizontal col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">

Luo uusi tapahtuma

        <input className="btn btn-success pull-right" formNoValidate type="submit" defaultValue="Tallenna" />

          <li className={`${(this.state.activeTab === 1 ? 'active' : '')}`}>
             this.changeTab(1)}>Perustiedot

          <li className={`${(this.state.activeTab === 2 ? 'active' : '')}`}>
             this.changeTab(2)}>Ilmoittautumisasetukset

          <li className={`${(this.state.activeTab === 3 ? 'active' : '')}`}>
             this.changeTab(3)}>Kysymykset

          <li className={`${(this.state.activeTab === 4 ? 'active' : '')}`}>
             this.changeTab(4)}>Vahvistusviestit

          <div className={`tab-pane ${(this.state.activeTab === 1 ? 'active' : '')}`}>
            <Input name="title" value="" label="Tapahtuman nimi" type="text" required />
            <Input name="start_date" value="" label="Alkamisajankohta" type="datetime-local" />
            <Input name="end_date" value="" label="Päättymisajankohta" type="datetime-local" />
            <Input name="webpage_url" value="" label="Kotisivujen osoite" type="text" />
            <Input name="facebook_url" value="" label="Facebook-tapahtuma" type="text" />
            <Input name="location" value="" label="Paikka" type="text" />
            <Textarea rows={10} name="description" value="" label="Kuvaus" />

          <div className={`tab-pane ${(this.state.activeTab === 2 ? 'active' : '')}`}>
            <Input name="registration_start_date" value="" label="Ilmo alkaa" type="datetime-local" required />
            <Input name="registration_end_date" value="" label="Ilmo päättyy" type="datetime-local" required />

            <Checkbox name="quotas"
              value={false}
              label="Ilmoittautumisessa käytetään kiintiöitä"
              rowLabel="Kiintiöt"
              onChange={this.toggleQuotas} />
            {
              (!this.state.useQuotas ||

                  <SortableItems collection="quotas" items={quotas} onSortEnd={this.updateOrder} useDragHandle />
                  Lisää kiintiö

                  <Checkbox name="quotas"
                    value={false}
                    label="Käytä lisäksi yhteistä kiintiötä"
                    rowLabel="asd"
                    onChange={this.toggleOpenQuota} />
                  {
                    (!this.state.useOpenQuota ||
                      <Input name="open-quota-size" label="Avoimen kiintiön koko" type="number" required />
                    )
                  }

)
            }

          <div className={`tab-pane ${(this.state.activeTab === 3 ? 'active' : '')}`}>

Kaikilta osallistujilta kerätään aina nimi ja sähköpostiosoite.

              <SortableItems collection="questions" items={questions} onSortEnd={this.updateOrder} useDragHandle />
              Lisää kysymys

          <div className={`tab-pane ${(this.state.activeTab === 4 ? 'active' : '')}`}>
            <Textarea rows={10} name="verification" value="" label="Vahvistusviesti sähköpostiin" />

      </Formsy.Form>
    );
  }
}

export default Editor;
<commit_msg>Mockup submission to console.log<commit_after>import React from 'react';
import Formsy from 'formsy-react';
import _ from 'lodash';
import { Checkbox, Input, Select, Textarea } from 'formsy-react-components';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import './Editor.scss';

const DragHandle = SortableHandle(() => ); // This can be any component you want

const SortableItem = SortableElement(({ value }) =>

    <DragHandle />
    {value}

);

const SortableItems = SortableContainer(({ collection, items }) =>

    { items.map((value, index) => <SortableItem collection={collection} key={index} index={index} value={value} />) }

,
);

class Editor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeTab: 1,
      useQuotas: true,
      useOpenQuota: false,
      eventData: {
        questions: [],
        quotas: [],
      },
    };

    this.changeTab = this.changeTab.bind(this);
    this.updateState = this.updateState.bind(this);
    this.updateOrder = this.updateOrder.bind(this);
    this.toggleQuotas = this.toggleQuotas.bind(this);
    this.addQuota = this.addQuota.bind(this);
    this.removeQuota = this.removeQuota.bind(this);
    this.toggleOpenQuota = this.toggleOpenQuota.bind(this);
    this.addQuestion = this.addQuestion.bind(this);
    this.removeQuestion = this.removeQuestion.bind(this);
    this.submitForm = this.submitForm.bind(this);
  }

  changeTab(id) {
    const state = this.state;
    state.activeTab = id;
    this.setState(state);
  }

  updateState(dataType, itemId, field, value) {
    const state = this.state;
    switch (dataType) {
      case 'question':
        (_.find(state.eventData.questions, { id: itemId }))[field] = value;
        break;
      case 'quota':
        (_.find(state.eventData.quotas, { id: itemId }))[field] = value;
        break;
      default:
        break;
    }
    this.setState(state);
  }

  toggleQuotas() {
    const state = this.state;
    state.useQuotas = !state.useQuotas;
    this.setState(state);
  }

  addQuota() {
    const state = this.state;
    state.eventData.quotas.push({
      id: (_.max(state.eventData.quotas.map(n => n.id)) || 0) + 1,
      existsInDb: false,
      title: '',
      max_attendees: 10,
      registration_opens: new Date(),
      registration_closes: new Date(),
    });
    this.setState(state);
  }

  removeQuota(id) {
    const state = this.state;
    delete state.eventData.quotas[id];
    this.setState(state);
  }

  addQuestion() {
    const state = this.state;
    state.eventData.questions.push({
      id: (_.max(state.eventData.questions.map(n => n.id)) || 0) + 1,
      existsInDb: false,
      title: '',
      type: 'text',
    });
    this.setState(state);
  }

  removeQuestion(id) {
    const state = this.state;
    delete state.eventData.questions[id];
    this.setState(state);
  }

  toggleOpenQuota() {
    const state = this.state;
    state.useOpenQuota = !state.useOpenQuota;
    this.setState(state);
  }

  updateOrder(args) {
    // General method for moving array items
    const moveElement = (array, from, to) => {
      const elementToMove = array[from];
      array.splice(from, 1);
      array.splice(to, 0, elementToMove);
      return array;
    };

    const state = this.state;

    // Move quota or question to new position
    switch (args.collection) {
      case 'quotas':
        state.eventData.quotas = moveElement(state.eventData.quotas, args.oldIndex, args.newIndex);
        break;
      case 'questions':
        state.eventData.questions = moveElement(state.eventData.questions, args.oldIndex, args.newIndex);
        break;
      default:
        break;
    }

    this.setState(state);
  }

  submitForm(model) {
    const data = model;
    data.quotas = this.state.eventData.quotas;
    data.questions = this.state.eventData.questions;

    console.log(data);
  }

  render() {
    const quotas = this.state.eventData.quotas.map((item, index) =>

          <Input
            name={`quota-${item.id}-title`}
            value={item.title}
            label="Kiintiön nimi"
            type="text"
            required
            onChange={(field, value) => this.updateState('quota', item.id, 'title', value)}
          />
          <Input
            name={`quota-${item.id}-max-attendees`}
            value={item.max_attendees}
            label="Kiintiön koko"
            type="number"
            required
            onChange={(field, value) => this.updateState('quota', item.id, 'max_attendees', value)}
          />

           this.removeQuota(index)}>Poista

);

    const questionTypes = [
      { value: 'text', label: 'Teksti (lyhyt)' },
      { value: 'textarea', label: 'Teksti (pitkä)' },
      { value: 'number', label: 'Numero' },
      { value: 'select', label: 'Monivalinta (voi valita yhden)' },
      { value: 'checkbox', label: 'Monivalinta (voi valita monta)' },
    ];

    const questions = this.state.eventData.questions.map((item, index) =>

          <Input
            name={`question-${item.id}-title`}
            value={item.title}
            label="Kysymys"
            type="text"
            required
            onChange={(field, value) => this.updateState('question', item.id, 'title', value)}
          />
          <Select
            name={`question-${item.id}-type`}
            value={item.type}
            label="Tyyppi"
            options={questionTypes}
            onChange={(field, value) => this.updateState('question', item.id, 'type', value)}
            required
          />

          <Checkbox
            name={`question-${item.id}-required`}
            value={false}
            label="Pakollinen"
            onChange={(field, value) => this.updateState('question', item.id, 'required', value)}
          />
          <Checkbox
            name={`question-${item.id}-public`}
            value={false}
            label="Julkinen"
            onChange={(field, value) => this.updateState('question', item.id, 'public', value)}
          />
           this.removeQuestion(index)}>Poista

);

    return (
      <Formsy.Form
        onSubmit={this.submitForm}
        className="event-editor form-horizontal col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">

Luo uusi tapahtuma

        <input className="btn btn-success pull-right" formNoValidate type="submit" defaultValue="Tallenna" />

          <li className={`${(this.state.activeTab === 1 ? 'active' : '')}`}>
             this.changeTab(1)}>Perustiedot

          <li className={`${(this.state.activeTab === 2 ? 'active' : '')}`}>
             this.changeTab(2)}>Ilmoittautumisasetukset

          <li className={`${(this.state.activeTab === 3 ? 'active' : '')}`}>
             this.changeTab(3)}>Kysymykset

          <li className={`${(this.state.activeTab === 4 ? 'active' : '')}`}>
             this.changeTab(4)}>Vahvistusviestit

          <div className={`tab-pane ${(this.state.activeTab === 1 ? 'active' : '')}`}>
            <Input name="title" value="" label="Tapahtuman nimi" type="text" required />
            <Input name="start_date" value="" label="Alkamisajankohta" type="datetime-local" />
            <Input name="end_date" value="" label="Päättymisajankohta" type="datetime-local" />
            <Input name="webpage_url" value="" label="Kotisivujen osoite" type="text" />
            <Input name="facebook_url" value="" label="Facebook-tapahtuma" type="text" />
            <Input name="location" value="" label="Paikka" type="text" />
            <Textarea rows={10} name="description" value="" label="Kuvaus" />

          <div className={`tab-pane ${(this.state.activeTab === 2 ? 'active' : '')}`}>
            <Input name="registration_start_date" value="" label="Ilmo alkaa" type="datetime-local" required />
            <Input name="registration_end_date" value="" label="Ilmo päättyy" type="datetime-local" required />

            <Checkbox name="quotas"
              value={false}
              label="Ilmoittautumisessa käytetään kiintiöitä"
              rowLabel="Kiintiöt"
              onChange={this.toggleQuotas} />
            {
              (!this.state.useQuotas ||

                  <SortableItems collection="quotas" items={quotas} onSortEnd={this.updateOrder} useDragHandle />
                  Lisää kiintiö

                  <Checkbox name="quotas"
                    value={false}
                    label="Käytä lisäksi yhteistä kiintiötä"
                    rowLabel="asd"
                    onChange={this.toggleOpenQuota} />
                  {
                    (!this.state.useOpenQuota ||
                      <Input name="open-quota-size" label="Avoimen kiintiön koko" type="number" required />
                    )
                  }

)
            }

          <div className={`tab-pane ${(this.state.activeTab === 3 ? 'active' : '')}`}>

Kaikilta osallistujilta kerätään aina nimi ja sähköpostiosoite.

              <SortableItems collection="questions" items={questions} onSortEnd={this.updateOrder} useDragHandle />
              Lisää kysymys

          <div className={`tab-pane ${(this.state.activeTab === 4 ? 'active' : '')}`}>
            <Textarea rows={10} name="verification" value="" label="Vahvistusviesti sähköpostiin" />

      </Formsy.Form>
    );
  }
}

export default Editor;
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.ObjectAttribute.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function(jQuery, library, Control) {
	"use strict";

	/**
	 * Constructor for a new ObjectAttribute.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * The ObjectAttribute control displays a text field that can be normal or active. The ObjectAttribute fires a press event when the user selects active text.
	 * @extends sap.ui.core.Control
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.12
	 * @alias sap.m.ObjectAttribute
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var ObjectAttribute = Control.extend("sap.m.ObjectAttribute", /** @lends sap.m.ObjectAttribute.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * Defines the ObjectAttribute title.
			 */
			title : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Defines the ObjectAttribute text.
			 */
			text : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Indicates if the ObjectAttribute text is selectable for the user.
			 */
			active : {type : "boolean", group : "Misc", defaultValue : null},

			/**
			 * Determines the direction of the text, not including the title.
			 * Available options for the text direction are LTR (left-to-right) and RTL (right-to-left). By default the control inherits the text direction from its parent control.
			 */
			textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : sap.ui.core.TextDirection.Inherit}
		},
		aggregations : {

			/**
			 * When the aggregation is set, it replaces the text, active and textDirection properties. This also ignores the press event. The provided control is displayed as an active link.
			 */
			customContent : {type : "sap.ui.core.Control", multiple : false},

			/**
			 * Text control to display title and text property.
			 */
			_textControl : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}
		},
		events : {

			/**
			 * Fires when the user clicks on active text.
			 */
			press : {
				parameters : {

					/**
					 * DOM reference of the ObjectAttribute's text to be used for positioning.
					 */
					domRef : {type : "string"}
				}
			}
		}
	}});

	ObjectAttribute.MAX_LINES = {
		SINGLE_LINE: 1,
		MULTI_LINE: 2
	};

	/**
	 *  Initializes member variables.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.init = function() {
		this.setAggregation('_textControl', new sap.m.Text());
	};

	/**
	 * Delivers text control with updated title, text and maxLines properties.
	 *
	 * @private
	 */
	ObjectAttribute.prototype._getUpdatedTextControl = function() {
		var oAttrAggregation = this.getAggregation('customContent') || this.getAggregation('_textControl'),
			sTitle = this.getTitle(),
			sText = this.getAggregation('customContent') ? this.getAggregation('customContent').getText() : this.getText(),
			sTextDir = this.getTextDirection(),
			oParent = this.getParent(),
			bPageRTL = sap.ui.getCore().getConfiguration().getRTL(),
			iMaxLines = ObjectAttribute.MAX_LINES.MULTI_LINE,
			bWrap = true,
			oppositeDirectionMarker = '';

		if (sTextDir === sap.ui.core.TextDirection.LTR && bPageRTL) {
			oppositeDirectionMarker = '\u200e';
		}
		if (sTextDir === sap.ui.core.TextDirection.RTL && !bPageRTL) {
			oppositeDirectionMarker = '\u200f';
		}
		sText = oppositeDirectionMarker + sText + oppositeDirectionMarker;
		if (sTitle) {
			sText = sTitle + ": " + sText;
		}
		oAttrAggregation.setProperty('text', sText, true);

		//if attribute is used inside responsive ObjectHeader or in ObjectListItem - only 1 line
		if (oParent instanceof sap.m.ObjectListItem) {
			bWrap = false;
			iMaxLines = ObjectAttribute.MAX_LINES.SINGLE_LINE;
		}

		this._setControlWrapping(oAttrAggregation, bWrap, iMaxLines);

		return oAttrAggregation;
	};

	/**
	 * Sets the appropriate property to the customContent aggregation.
	 *
	 * @private
	 */
	ObjectAttribute.prototype._setControlWrapping = function(oAttrAggregation, bWrap, iMaxLines) {
		if (oAttrAggregation instanceof sap.m.Link) {
			oAttrAggregation.setProperty('wrapping', bWrap, true);
		}
		if (oAttrAggregation instanceof sap.m.Text) {
			oAttrAggregation.setProperty('maxLines', iMaxLines, true);
		}
	};

	/**
	 * @private
	 */
	ObjectAttribute.prototype.ontap = function(oEvent) {
		//event should only be fired if the click is on the text
		if (this._isSimulatedLink() && (oEvent.target.id != this.getId())) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapenter = function(oEvent) {
		if (this._isSimulatedLink()) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapspace = function(oEvent) {
		this.onsapenter(oEvent);
	};

	/**
	 * Checks if ObjectAttribute is empty.
	 *
	 * @private
	 * @returns {boolean} true if ObjectAttribute's text is empty or only consists of whitespaces
	 */
	ObjectAttribute.prototype._isEmpty = function() {
		if (this.getAggregation('customContent') && !(this.getAggregation('customContent') instanceof sap.m.Link || this.getAggregation('customContent') instanceof sap.m.Text)) {
			jQuery.sap.log.warning("Only sap.m.Link or sap.m.Text are allowed in \"sap.m.ObjectAttribute.customContent\" aggregation");
			return true;
		}

		return !(this.getText().trim() || this.getTitle().trim());
	};

	/**
	 * Called when the control is touched.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.ontouchstart = function(oEvent) {
		if (this._isSimulatedLink()) {
			// for control who need to know if they should handle events from the ObjectAttribute control
			oEvent.originalEvent._sapui_handledByControl = true;
		}
	};

	/**
	 * Defines to which DOM reference the Popup should be docked.
	 *
	 * @protected
	 * @return {DomNode} The DOM reference that Popup should dock to
	 */
	ObjectAttribute.prototype.getPopupAnchorDomRef = function() {
		return this.getDomRef("text");
	};

	ObjectAttribute.prototype._isSimulatedLink = function () {
		return this.getActive() && !this.getAggregation('customContent');
	};

	return ObjectAttribute;

}, /* bExport= */ true);
<commit_msg>[INTERNAL][FIX] sap.m.ObjectAttribute: Documentation is updated.<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.ObjectAttribute.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function(jQuery, library, Control) {
	"use strict";

	/**
	 * Constructor for a new ObjectAttribute.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * The ObjectAttribute control displays a text field that can be normal or active. The ObjectAttribute fires a press event when the user selects active text.
	 * @extends sap.ui.core.Control
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.12
	 * @alias sap.m.ObjectAttribute
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var ObjectAttribute = Control.extend("sap.m.ObjectAttribute", /** @lends sap.m.ObjectAttribute.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * Defines the ObjectAttribute title.
			 */
			title : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Defines the ObjectAttribute text.
			 */
			text : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Indicates if the ObjectAttribute text is selectable for the user.
			 */
			active : {type : "boolean", group : "Misc", defaultValue : null},

			/**
			 * Determines the direction of the text, not including the title.
			 * Available options for the text direction are LTR (left-to-right) and RTL (right-to-left). By default the control inherits the text direction from its parent control.
			 */
			textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : sap.ui.core.TextDirection.Inherit}
		},
		aggregations : {

			/**
			 * When the aggregation is set, it replaces the text, active and textDirection properties. This also ignores the press event. The provided control is displayed as an active link.
			 * <b>Note:</b> It will only allow sap.m.Text and sap.m.Link controls.
			 */
			customContent : {type : "sap.ui.core.Control", multiple : false},

			/**
			 * Text control to display title and text property.
			 */
			_textControl : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}
		},
		events : {

			/**
			 * Fires when the user clicks on active text.
			 */
			press : {
				parameters : {

					/**
					 * DOM reference of the ObjectAttribute's text to be used for positioning.
					 */
					domRef : {type : "string"}
				}
			}
		}
	}});

	ObjectAttribute.MAX_LINES = {
		SINGLE_LINE: 1,
		MULTI_LINE: 2
	};

	/**
	 *  Initializes member variables.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.init = function() {
		this.setAggregation('_textControl', new sap.m.Text());
	};

	/**
	 * Delivers text control with updated title, text and maxLines properties.
	 *
	 * @private
	 */
	ObjectAttribute.prototype._getUpdatedTextControl = function() {
		var oAttrAggregation = this.getAggregation('customContent') || this.getAggregation('_textControl'),
			sTitle = this.getTitle(),
			sText = this.getAggregation('customContent') ? this.getAggregation('customContent').getText() : this.getText(),
			sTextDir = this.getTextDirection(),
			oParent = this.getParent(),
			bPageRTL = sap.ui.getCore().getConfiguration().getRTL(),
			iMaxLines = ObjectAttribute.MAX_LINES.MULTI_LINE,
			bWrap = true,
			oppositeDirectionMarker = '';

		if (sTextDir === sap.ui.core.TextDirection.LTR && bPageRTL) {
			oppositeDirectionMarker = '\u200e';
		}
		if (sTextDir === sap.ui.core.TextDirection.RTL && !bPageRTL) {
			oppositeDirectionMarker = '\u200f';
		}
		sText = oppositeDirectionMarker + sText + oppositeDirectionMarker;
		if (sTitle) {
			sText = sTitle + ": " + sText;
		}
		oAttrAggregation.setProperty('text', sText, true);

		//if attribute is used inside responsive ObjectHeader or in ObjectListItem - only 1 line
		if (oParent instanceof sap.m.ObjectListItem) {
			bWrap = false;
			iMaxLines = ObjectAttribute.MAX_LINES.SINGLE_LINE;
		}

		this._setControlWrapping(oAttrAggregation, bWrap, iMaxLines);

		return oAttrAggregation;
	};

	/**
	 * Sets the appropriate property to the customContent aggregation.
	 *
	 * @private
	 */
	ObjectAttribute.prototype._setControlWrapping = function(oAttrAggregation, bWrap, iMaxLines) {
		if (oAttrAggregation instanceof sap.m.Link) {
			oAttrAggregation.setProperty('wrapping', bWrap, true);
		}
		if (oAttrAggregation instanceof sap.m.Text) {
			oAttrAggregation.setProperty('maxLines', iMaxLines, true);
		}
	};

	/**
	 * @private
	 */
	ObjectAttribute.prototype.ontap = function(oEvent) {
		//event should only be fired if the click is on the text
		if (this._isSimulatedLink() && (oEvent.target.id != this.getId())) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapenter = function(oEvent) {
		if (this._isSimulatedLink()) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapspace = function(oEvent) {
		this.onsapenter(oEvent);
	};

	/**
	 * Checks if ObjectAttribute is empty.
	 *
	 * @private
	 * @returns {boolean} true if ObjectAttribute's text is empty or only consists of whitespaces
	 */
	ObjectAttribute.prototype._isEmpty = function() {
		if (this.getAggregation('customContent') && !(this.getAggregation('customContent') instanceof sap.m.Link || this.getAggregation('customContent') instanceof sap.m.Text)) {
			jQuery.sap.log.warning("Only sap.m.Link or sap.m.Text are allowed in \"sap.m.ObjectAttribute.customContent\" aggregation");
			return true;
		}

		return !(this.getText().trim() || this.getTitle().trim());
	};

	/**
	 * Called when the control is touched.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.ontouchstart = function(oEvent) {
		if (this._isSimulatedLink()) {
			// for control who need to know if they should handle events from the ObjectAttribute control
			oEvent.originalEvent._sapui_handledByControl = true;
		}
	};

	/**
	 * Defines to which DOM reference the Popup should be docked.
	 *
	 * @protected
	 * @return {DomNode} The DOM reference that Popup should dock to
	 */
	ObjectAttribute.prototype.getPopupAnchorDomRef = function() {
		return this.getDomRef("text");
	};

	ObjectAttribute.prototype._isSimulatedLink = function () {
		return this.getActive() && !this.getAggregation('customContent');
	};

	return ObjectAttribute;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.ObjectAttribute.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function(jQuery, library, Control) {
	"use strict";

	/**
	 * Constructor for a new ObjectAttribute.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * ObjectAttribute displays a text field that can be normal or active. Object attribute fires a press event when the user selects active text.
	 * @extends sap.ui.core.Control
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.12
	 * @alias sap.m.ObjectAttribute
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var ObjectAttribute = Control.extend("sap.m.ObjectAttribute", /** @lends sap.m.ObjectAttribute.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * The object attribute title.
			 */
			title : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * The object attribute text.
			 */
			text : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Indicates if the object attribute text is selectable by the user.
			 */
			active : {type : "boolean", group : "Misc", defaultValue : null},

			/**
			 * Determines the direction of the text, not including the title.
			 * Available options for the text direction are LTR (left-to-right) and RTL (right-to-left). By default the control inherits the text direction from its parent control.
			 */
			textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : sap.ui.core.TextDirection.Inherit}
		},
		aggregations : {

			/**
			 * Text control to display title and text property
			 */
			_textControl : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}
		},
		events : {

			/**
			 * Event is fired when the user clicks active text
			 */
			press : {
				parameters : {

					/**
					 * Dom reference of the object attributes' text to be used for positioning.
					 */
					domRef : {type : "string"}
				}
			}
		}
	}});

	///**
	// * This file defines behavior for the control,
	// */

	/**
	 *  Initialize member variables
	 *
	 * @private
	 */
	ObjectAttribute.prototype.init = function() {
		this.setAggregation('_textControl', new sap.m.Text());
	};

	/**
	 * Delivers text control with updated title, text and maxLines property
	 *
	 * @private
	 */
	ObjectAttribute.prototype._getUpdatedTextControl = function() {
		var oTextControl = this.getAggregation('_textControl');
		var sTextDir = this.getTextDirection();
		var oParent = this.getParent();
		var bPageRTL = sap.ui.getCore().getConfiguration().getRTL();
		var oMaxLinesConst = {
			singleLine : 1,
			multiLine : 2
		};
		var iMaxLines = oMaxLinesConst.multiLine;
		var oppositeDirectionMarker = '';
		if (sTextDir === sap.ui.core.TextDirection.LTR && bPageRTL) {
			oppositeDirectionMarker = '\u200e';
		}
		if (sTextDir === sap.ui.core.TextDirection.RTL && !bPageRTL) {
			oppositeDirectionMarker = '\u200f';
		}

		oTextControl.setProperty('text', (this.getTitle() ? this.getTitle() + ": " : "") + oppositeDirectionMarker + this.getText() + oppositeDirectionMarker, true);
		//if attribute is used inside responsive ObjectHeader or in ObjectListItem - only 1 line
		if (oParent && ((oParent instanceof sap.m.ObjectHeader && oParent.getResponsive()) || oParent instanceof sap.m.ObjectListItem)) {
			iMaxLines = oMaxLinesConst.singleLine;
		}
		oTextControl.setProperty('maxLines', iMaxLines, true);
		return oTextControl;
	};

	/**
	 * @private
	 */
	ObjectAttribute.prototype.ontap = function(oEvent) {
		//event should only be fired if the click is on the text
		if (!!this.getActive() && (oEvent.target.id != this.getId())) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapenter = function(oEvent) {
		if (!!this.getActive()) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapspace = function(oEvent) {
		this.onsapenter(oEvent);
	};

	/**
	 * See 'return'.
	 *
	 * @private
	 * @returns {boolean} true if attribute's text is empty or only consists of whitespaces.
	 */
	ObjectAttribute.prototype._isEmpty = function() {
		return !(this.getText().trim() || this.getTitle().trim());
	};

	/**
	 * Function is called when the control is touched.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.ontouchstart = function(oEvent) {
		if (!!this.getActive()) {
			// for control who need to know if they should handle events from the ObjectAttribute control
			oEvent.originalEvent._sapui_handledByControl = true;
		}
	};

	return ObjectAttribute;

}, /* bExport= */ true);
<commit_msg>[FIX] sap.m.ObjectAttribute: Popover position corrected when opened from ObjectAttribute active text<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.ObjectAttribute.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function(jQuery, library, Control) {
	"use strict";

	/**
	 * Constructor for a new ObjectAttribute.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * ObjectAttribute displays a text field that can be normal or active. Object attribute fires a press event when the user selects active text.
	 * @extends sap.ui.core.Control
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.12
	 * @alias sap.m.ObjectAttribute
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var ObjectAttribute = Control.extend("sap.m.ObjectAttribute", /** @lends sap.m.ObjectAttribute.prototype */ { metadata : {

		library : "sap.m",
		properties : {

			/**
			 * The object attribute title.
			 */
			title : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * The object attribute text.
			 */
			text : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Indicates if the object attribute text is selectable by the user.
			 */
			active : {type : "boolean", group : "Misc", defaultValue : null},

			/**
			 * Determines the direction of the text, not including the title.
			 * Available options for the text direction are LTR (left-to-right) and RTL (right-to-left). By default the control inherits the text direction from its parent control.
			 */
			textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : sap.ui.core.TextDirection.Inherit}
		},
		aggregations : {

			/**
			 * Text control to display title and text property
			 */
			_textControl : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}
		},
		events : {

			/**
			 * Event is fired when the user clicks active text
			 */
			press : {
				parameters : {

					/**
					 * Dom reference of the object attributes' text to be used for positioning.
					 */
					domRef : {type : "string"}
				}
			}
		}
	}});

	///**
	// * This file defines behavior for the control,
	// */

	/**
	 *  Initialize member variables
	 *
	 * @private
	 */
	ObjectAttribute.prototype.init = function() {
		this.setAggregation('_textControl', new sap.m.Text());
	};

	/**
	 * Delivers text control with updated title, text and maxLines property
	 *
	 * @private
	 */
	ObjectAttribute.prototype._getUpdatedTextControl = function() {
		var oTextControl = this.getAggregation('_textControl');
		var sTextDir = this.getTextDirection();
		var oParent = this.getParent();
		var bPageRTL = sap.ui.getCore().getConfiguration().getRTL();
		var oMaxLinesConst = {
			singleLine : 1,
			multiLine : 2
		};
		var iMaxLines = oMaxLinesConst.multiLine;
		var oppositeDirectionMarker = '';
		if (sTextDir === sap.ui.core.TextDirection.LTR && bPageRTL) {
			oppositeDirectionMarker = '\u200e';
		}
		if (sTextDir === sap.ui.core.TextDirection.RTL && !bPageRTL) {
			oppositeDirectionMarker = '\u200f';
		}

		oTextControl.setProperty('text', (this.getTitle() ? this.getTitle() + ": " : "") + oppositeDirectionMarker + this.getText() + oppositeDirectionMarker, true);
		//if attribute is used inside responsive ObjectHeader or in ObjectListItem - only 1 line
		if (oParent && ((oParent instanceof sap.m.ObjectHeader && oParent.getResponsive()) || oParent instanceof sap.m.ObjectListItem)) {
			iMaxLines = oMaxLinesConst.singleLine;
		}
		oTextControl.setProperty('maxLines', iMaxLines, true);
		return oTextControl;
	};

	/**
	 * @private
	 */
	ObjectAttribute.prototype.ontap = function(oEvent) {
		//event should only be fired if the click is on the text
		if (!!this.getActive() && (oEvent.target.id != this.getId())) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapenter = function(oEvent) {
		if (!!this.getActive()) {
			this.firePress({
				domRef : this.getDomRef()
			});
		}
	};

	/**
	 * @private
	 */
	sap.m.ObjectAttribute.prototype.onsapspace = function(oEvent) {
		this.onsapenter(oEvent);
	};

	/**
	 * See 'return'.
	 *
	 * @private
	 * @returns {boolean} true if attribute's text is empty or only consists of whitespaces.
	 */
	ObjectAttribute.prototype._isEmpty = function() {
		return !(this.getText().trim() || this.getTitle().trim());
	};

	/**
	 * Function is called when the control is touched.
	 *
	 * @private
	 */
	ObjectAttribute.prototype.ontouchstart = function(oEvent) {
		if (!!this.getActive()) {
			// for control who need to know if they should handle events from the ObjectAttribute control
			oEvent.originalEvent._sapui_handledByControl = true;
		}
	};

	/**
	 * Defines to which DOM reference the Popup should be docked
	 *
	 * @protected
	 * @return {DomNode} the DOM reference that Popup should dock to
	 */
	ObjectAttribute.prototype.getPopupAnchorDomRef = function() {
		return this.getDomRef("text");
	};

	return ObjectAttribute;

}, /* bExport= */ true);
<|endoftext|>