')
+ users.forEach(sessionId => {
+ $('
').append($('
').text(sessions[sessionId].user.username).on('click', function (evt) {
+ evt.preventDefault()
+ revealUser(sessions[sessionId].location, true)
+ userCountTooltip.close()
+ })).appendTo(content)
+ })
+ return content
+ },
+ null,
+ true
+ )
+
+ const updateUserCount = function () {
+ const maxShown = 2
+ const children = tray.children()
+ children.each(function (index, element) {
+ const i = users.length - index
+ if (i > maxShown) {
+ $(this).hide()
+ } else if (i >= 0) {
+ $(this).show()
+ }
+ })
+ if (users.length < maxShown + 1) {
+ userCountIcon.hide()
+ } else {
+ userCountSpan.text('+'+(users.length - maxShown))
+ userCountIcon.show()
+ }
+ }
+ workspaceTrays[workspaceId] = {
+ attached: false,
+ tray,
+ users,
+ userIcons,
+ addUser: function (sessionId) {
+ if (users.indexOf(sessionId) === -1) {
+ // console.log(`addUser ws:${workspaceId} session:${sessionId}`)
+ users.push(sessionId)
+ const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
+ const userLocationIcon = $(``)
+ RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
+ userLocationIcon.prependTo(tray)
+ RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
+ userIcons[sessionId] = userLocationIcon
+ updateUserCount()
+ }
+ },
+ removeUser: function (sessionId) {
+ // console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
+ const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
+ const index = users.indexOf(sessionId)
+ if (index > -1) {
+ users.splice(index, 1)
+ userIcons[sessionId].remove()
+ delete userIcons[sessionId]
+ }
+ updateUserCount()
+ },
+ updateUserCount
+ }
+ }
+ const trayDef = workspaceTrays[workspaceId]
+ if (!trayDef.attached) {
+ const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
+ if (workspaceTab.length > 0) {
+ trayDef.attached = true
+ trayDef.tray.appendTo(workspaceTab)
+ trayDef.users.forEach(sessionId => {
+ trayDef.userIcons[sessionId].on('click', function (evt) {
+ revealUser(sessions[sessionId].location, true)
+ })
+ })
+ }
+ }
+ return workspaceTrays[workspaceId]
+ }
+ function attachWorkspaceTrays () {
+ let viewTouched = false
+ for (let sessionId of Object.keys(sessions)) {
+ const location = sessions[sessionId].location
+ if (location) {
+ if (location.workspace) {
+ getWorkspaceTray(location.workspace).updateUserCount()
+ }
+ if (location.node) {
+ addUserToNode(sessionId, location.node)
+ viewTouched = true
+ }
+ }
+ }
+ if (viewTouched) {
+ RED.view.redraw()
+ }
+ }
+
+ function addUserToNode(sessionId, nodeId) {
+ const node = RED.nodes.node(nodeId)
+ if (node) {
+ if (!node._multiplayer) {
+ node._multiplayer = {
+ users: [sessionId]
+ }
+ node._multiplayer_refresh = true
+ } else {
+ if (node._multiplayer.users.indexOf(sessionId) === -1) {
+ node._multiplayer.users.push(sessionId)
+ node._multiplayer_refresh = true
+ }
+ }
+ }
+ }
+ function removeUserFromNode(sessionId, nodeId) {
+ const node = RED.nodes.node(nodeId)
+ if (node && node._multiplayer) {
+ const i = node._multiplayer.users.indexOf(sessionId)
+ if (i > -1) {
+ node._multiplayer.users.splice(i, 1)
+ }
+ if (node._multiplayer.users.length === 0) {
+ delete node._multiplayer
+ } else {
+ node._multiplayer_refresh = true
+ }
+ }
+
+ }
+
+ function removeUserLocation (sessionId) {
+ updateUserLocation(sessionId, {})
+ }
+ function updateUserLocation (sessionId, location) {
+ let viewTouched = false
+ const oldLocation = sessions[sessionId].location
+ if (location) {
+ if (oldLocation.workspace !== location.workspace) {
+ // console.log('removing', sessionId, oldLocation.workspace)
+ workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
+ }
+ if (oldLocation.node !== location.node) {
+ removeUserFromNode(sessionId, oldLocation.node)
+ viewTouched = true
+ }
+ sessions[sessionId].location = location
+ } else {
+ location = sessions[sessionId].location
+ }
+ // console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
+ if (location.workspace) {
+ getWorkspaceTray(location.workspace).addUser(sessionId)
+ }
+ if (location.node) {
+ addUserToNode(sessionId, location.node)
+ viewTouched = true
+ }
+ if (viewTouched) {
+ RED.view.redraw()
+ }
+ }
+
+ // function refreshUserLocations () {
+ // for (const session of Object.keys(sessions)) {
+ // if (session !== activeSessionId) {
+ // updateUserLocation(session)
+ // }
+ // }
+ // }
+
+ return {
+ init: function () {
+
+ function createAnnotationUser(user) {
+
+ const group = document.createElementNS("http://www.w3.org/2000/svg","g");
+ const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
+ const radius = 20
+ badge.setAttribute("cx",radius/2);
+ badge.setAttribute("cy",radius/2);
+ badge.setAttribute("r",radius/2);
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
+ group.appendChild(badge)
+ if (user && user.profileColor !== undefined) {
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
+ }
+ if (user && user.image) {
+ const image = document.createElementNS("http://www.w3.org/2000/svg","image");
+ image.setAttribute("width", radius)
+ image.setAttribute("height", radius)
+ image.setAttribute("href", user.image)
+ image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
+ group.appendChild(image)
+ } else if (user && user.anonymous) {
+ const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
+ anonIconHead.setAttribute("cx", radius/2)
+ anonIconHead.setAttribute("cy", radius/2 - 2)
+ anonIconHead.setAttribute("r", 2.4)
+ anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
+ group.appendChild(anonIconHead)
+ const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
+ anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
+ // anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
+ anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
+ group.appendChild(anonIconBody)
+ } else {
+ const labelText = user.username ? user.username.substring(0,2) : user
+ const label = document.createElementNS("http://www.w3.org/2000/svg","text");
+ if (user.username) {
+ label.setAttribute("class","red-ui-multiplayer-annotation-label");
+ label.textContent = user.username.substring(0,2)
+ } else {
+ label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
+ label.textContent = user
+ }
+ label.setAttribute("text-anchor", "middle")
+ label.setAttribute("x",radius/2);
+ label.setAttribute("y",radius/2 + 3);
+ group.appendChild(label)
+ }
+ const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
+ border.setAttribute("cx",radius/2);
+ border.setAttribute("cy",radius/2);
+ border.setAttribute("r",radius/2);
+ border.setAttribute("class", "red-ui-multiplayer-annotation-border")
+ group.appendChild(border)
+
+
+
+ return group
+ }
+
+ RED.view.annotations.register("red-ui-multiplayer",{
+ type: 'badge',
+ align: 'left',
+ class: "red-ui-multiplayer-annotation",
+ show: "_multiplayer",
+ refresh: "_multiplayer_refresh",
+ element: function(node) {
+ const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
+ containerGroup.setAttribute("transform","translate(0,-4)")
+ if (node._multiplayer) {
+ let y = 0
+ for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
+ const user = sessions[node._multiplayer.users[i]].user
+ const group = createAnnotationUser(user)
+ group.setAttribute("transform","translate("+y+",0)")
+ y += 15
+ containerGroup.appendChild(group)
+ }
+ if (node._multiplayer.users.length > 2) {
+ const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
+ group.setAttribute("transform","translate("+y+",0)")
+ y += 12
+ containerGroup.appendChild(group)
+ }
+
+ }
+ return containerGroup;
+ },
+ tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
+ });
+
+
+ // activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
+ // if (!activeSessionId) {
+ activeSessionId = RED.nodes.id()
+ // RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
+ // log('Session ID (new)', activeSessionId)
+ // } else {
+ log('Session ID', activeSessionId)
+ // }
+
+ headerWidget = $('').prependTo('.red-ui-header-toolbar')
+
+ RED.comms.on('connect', () => {
+ const location = getLocation()
+ const connectInfo = {
+ session: activeSessionId
+ }
+ if (location.workspace !== 0) {
+ connectInfo.location = location
+ }
+ RED.comms.send('multiplayer/connect', connectInfo)
+ })
+ RED.comms.subscribe('multiplayer/#', (topic, msg) => {
+ log('recv', topic, msg)
+ if (topic === 'multiplayer/init') {
+ // We have just reconnected, runtime has sent state to
+ // initialise the world
+ sessions = {}
+ users = {}
+ $('#red-ui-multiplayer-user-list').empty()
+
+ msg.sessions.forEach(session => {
+ addUserSession(session)
+ })
+ } else if (topic === 'multiplayer/connection-added') {
+ addUserSession(msg)
+ } else if (topic === 'multiplayer/connection-removed') {
+ removeUserSession(msg.session, msg.disconnected)
+ } else if (topic === 'multiplayer/location') {
+ const session = msg.session
+ delete msg.session
+ updateUserLocation(session, msg)
+ }
+ })
+
+ RED.events.on('workspace:change', (event) => {
+ getWorkspaceTray(event.workspace)
+ publishLocation()
+ })
+ RED.events.on('editor:open', () => {
+ publishLocation()
+ })
+ RED.events.on('editor:close', () => {
+ publishLocation()
+ })
+ RED.events.on('editor:change', () => {
+ publishLocation()
+ })
+ RED.events.on('login', () => {
+ publishLocation()
+ })
+ RED.events.on('flows:loaded', () => {
+ attachWorkspaceTrays()
+ })
+ RED.events.on('workspace:close', (event) => {
+ // A subflow tab has been closed. Need to mark its tray as detached
+ if (workspaceTrays[event.workspace]) {
+ workspaceTrays[event.workspace].attached = false
+ }
+ })
+ RED.events.on('logout', () => {
+ const disconnectInfo = {
+ session: activeSessionId
+ }
+ RED.comms.send('multiplayer/disconnect', disconnectInfo)
+ RED.settings.removeLocal('multiplayer:sessionId')
+ })
+ }
+ }
+
+ function log() {
+ if (RED.multiplayer.DEBUG) {
+ console.log('[multiplayer]', ...arguments)
+ }
+ }
+})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js
index e33672342..f8130a686 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/red.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/red.js
@@ -298,6 +298,7 @@ var RED = (function() {
RED.workspaces.show(workspaces[0]);
}
}
+ RED.events.emit('flows:loaded')
} catch(err) {
console.warn(err);
RED.notify(
@@ -839,6 +840,10 @@ var RED = (function() {
RED.nodes.init();
RED.runtime.init()
+
+ if (RED.settings.theme("multiplayer.enabled",false)) {
+ RED.multiplayer.init()
+ }
RED.comms.connect();
$("#red-ui-main-container").show();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
index 9ddd3d866..381bb9d3a 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
@@ -211,7 +211,7 @@ RED.popover = (function() {
closePopup(true);
});
}
- if (trigger === 'hover' && options.interactive) {
+ if (/*trigger === 'hover' && */options.interactive) {
div.on('mouseenter', function(e) {
clearTimeout(timer);
active = true;
@@ -445,9 +445,12 @@ RED.popover = (function() {
return {
create: createPopover,
- tooltip: function(target,content, action) {
+ tooltip: function(target,content, action, interactive) {
var label = function() {
var label = content;
+ if (typeof content === 'function') {
+ label = content()
+ }
if (action) {
var shortcut = RED.keyboard.getShortcut(action);
if (shortcut && shortcut.key) {
@@ -463,6 +466,7 @@ RED.popover = (function() {
size: "small",
direction: "bottom",
content: label,
+ interactive,
delay: { show: 750, hide: 50 }
});
popover.setContent = function(newContent) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js
index abb76e622..d9dc4b289 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js
@@ -365,7 +365,10 @@ RED.tabs = (function() {
var thisTabA = thisTab.find("a");
if (options.onclick) {
- options.onclick(tabs[thisTabA.attr('href').slice(1)]);
+ options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
+ if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
+ return false
+ }
}
activateTab(thisTabA);
if (fireSelectionChanged) {
@@ -548,6 +551,8 @@ RED.tabs = (function() {
ul.find("li.red-ui-tab a")
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
.on("mouseup",onTabClick)
+ // prevent browser-default middle-click behaviour
+ .on("auxclick", function(evt) { evt.preventDefault() })
.on("click", function(evt) {evt.preventDefault(); })
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
@@ -816,6 +821,8 @@ RED.tabs = (function() {
}
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
link.on("mouseup",onTabClick);
+ // prevent browser-default middle-click behaviour
+ link.on("auxclick", function(evt) { evt.preventDefault() })
link.on("click", function(evt) { evt.preventDefault(); })
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
index a09fdeb01..90857e865 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
@@ -34,6 +34,8 @@ RED.deploy = (function() {
var currentDiff = null;
+ var activeBackgroundDeployNotification;
+
function changeDeploymentType(type) {
deploymentType = type;
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
@@ -61,7 +63,7 @@ RED.deploy = (function() {
'
'+
''+
''+
- ''+
+ ''+
'').prependTo(".red-ui-header-toolbar");
const mainMenuItems = [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
@@ -112,53 +114,80 @@ RED.deploy = (function() {
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
}
-
+ window.addEventListener('beforeunload', function (event) {
+ if (RED.nodes.dirty()) {
+ event.preventDefault();
+ event.stopImmediatePropagation()
+ event.returnValue = RED._("deploy.confirm.undeployedChanges");
+ return
+ }
+ })
RED.events.on('workspace:dirty',function(state) {
+ if (RED.settings.user?.permissions === 'read') {
+ return
+ }
if (state.dirty) {
- window.onbeforeunload = function() {
- return RED._("deploy.confirm.undeployedChanges");
- }
+ // window.onbeforeunload = function() {
+ // return
+ // }
$("#red-ui-header-button-deploy").removeClass("disabled");
} else {
- window.onbeforeunload = null;
+ // window.onbeforeunload = null;
$("#red-ui-header-button-deploy").addClass("disabled");
}
});
- var activeNotifyMessage;
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
- if (!activeNotifyMessage) {
- var currentRev = RED.nodes.version();
- if (currentRev === null || deployInflight || currentRev === msg.revision) {
- return;
- }
- var message = $('
').text(RED._('deploy.confirm.backgroundUpdate'));
- activeNotifyMessage = RED.notify(message,{
- modal: true,
- fixed: true,
- buttons: [
- {
- text: RED._('deploy.confirm.button.ignore'),
- click: function() {
- activeNotifyMessage.close();
- activeNotifyMessage = null;
- }
- },
- {
- text: RED._('deploy.confirm.button.review'),
- class: "primary",
- click: function() {
- activeNotifyMessage.close();
- var nns = RED.nodes.createCompleteNodeSet();
- resolveConflict(nns,false);
- activeNotifyMessage = null;
- }
+ var currentRev = RED.nodes.version();
+ if (currentRev === null || deployInflight || currentRev === msg.revision) {
+ return;
+ }
+ if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
+ activeBackgroundDeployNotification.showNotification()
+ return
+ }
+ const message = $('
').text(RED._('deploy.confirm.backgroundUpdate'));
+ const options = {
+ id: 'background-update',
+ type: 'compact',
+ modal: false,
+ fixed: true,
+ timeout: 10000,
+ buttons: [
+ {
+ text: RED._('deploy.confirm.button.review'),
+ class: "primary",
+ click: function() {
+ activeBackgroundDeployNotification.hideNotification();
+ var nns = RED.nodes.createCompleteNodeSet();
+ resolveConflict(nns,false);
}
- ]
- });
+ }
+ ]
+ }
+ if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
+ activeBackgroundDeployNotification = RED.notify(message, options)
+ } else {
+ activeBackgroundDeployNotification.update(message, options)
}
});
+
+
+ updateLockedState()
+ RED.events.on('login', updateLockedState)
+ }
+
+ function updateLockedState() {
+ if (RED.settings.user?.permissions === 'read') {
+ $(".red-ui-deploy-button-group").addClass("readOnly");
+ $("#red-ui-header-button-deploy").addClass("disabled");
+ } else {
+ $(".red-ui-deploy-button-group").removeClass("readOnly");
+ if (RED.nodes.dirty()) {
+ $("#red-ui-header-button-deploy").removeClass("disabled");
+ }
+ }
}
function getNodeInfo(node) {
@@ -213,7 +242,11 @@ RED.deploy = (function() {
class: "primary disabled",
click: function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
- RED.diff.showRemoteDiff();
+ RED.diff.showRemoteDiff(null, {
+ onmerge: function () {
+ activeBackgroundDeployNotification.close()
+ }
+ });
conflictNotification.close();
}
}
@@ -226,6 +259,7 @@ RED.deploy = (function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff);
conflictNotification.close();
+ activeBackgroundDeployNotification.close()
}
}
}
@@ -238,6 +272,7 @@ RED.deploy = (function() {
click: function() {
save(true,activeDeploy);
conflictNotification.close();
+ activeBackgroundDeployNotification.close()
}
})
}
@@ -248,21 +283,17 @@ RED.deploy = (function() {
buttons: buttons
});
- var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
- var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
- setTimeout(function() {
- conflictCheck.hide();
- var d = Object.keys(diff.conflicts);
- if (d.length === 0) {
- conflictAutoMerge.show();
- $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
- } else {
- conflictManualMerge.show();
- }
- $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
- },ellapsed);
+ conflictCheck.hide();
+ var d = Object.keys(diff.conflicts);
+ if (d.length === 0) {
+ conflictAutoMerge.show();
+ $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
+ } else {
+ conflictManualMerge.show();
+ }
+ $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
})
}
function cropList(list) {
@@ -612,7 +643,10 @@ RED.deploy = (function() {
}
});
RED.nodes.eachSubflow(function (subflow) {
- subflow.changed = false;
+ if (subflow.changed) {
+ subflow.changed = false;
+ RED.events.emit("subflows:change", subflow);
+ }
});
RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js
index 3f73e29aa..ebdf683e3 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js
@@ -1,5 +1,4 @@
RED.diff = (function() {
-
var currentDiff = {};
var diffVisible = false;
var diffList;
@@ -62,12 +61,14 @@ RED.diff = (function() {
addedCount:0,
deletedCount:0,
changedCount:0,
+ movedCount:0,
unchangedCount: 0
},
remote: {
addedCount:0,
deletedCount:0,
changedCount:0,
+ movedCount:0,
unchangedCount: 0
},
conflicts: 0
@@ -138,7 +139,7 @@ RED.diff = (function() {
$(this).parent().toggleClass('collapsed');
});
- createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
+ createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
selectState = "";
if (conflicts[tab.id]) {
flowStats.conflicts++;
@@ -208,19 +209,26 @@ RED.diff = (function() {
var localStats = $('',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
$('').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
- if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
+ if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
$(' [ ').appendTo(localStats);
if (flowStats.conflicts > 0) {
$(' '+flowStats.conflicts+'').appendTo(localStats);
}
if (flowStats.local.addedCount > 0) {
- $(' '+flowStats.local.addedCount+'').appendTo(localStats);
+ const cell = $(' '+flowStats.local.addedCount+'').appendTo(localStats);
+ RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.local.changedCount > 0) {
- $(' '+flowStats.local.changedCount+'').appendTo(localStats);
+ const cell = $(' '+flowStats.local.changedCount+'').appendTo(localStats);
+ RED.popover.tooltip(cell, RED._('diff.type.changed'))
+ }
+ if (flowStats.local.movedCount > 0) {
+ const cell = $(' '+flowStats.local.movedCount+'').appendTo(localStats);
+ RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.local.deletedCount > 0) {
- $(' '+flowStats.local.deletedCount+'').appendTo(localStats);
+ const cell = $(' '+flowStats.local.deletedCount+'').appendTo(localStats);
+ RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$(' ] ').appendTo(localStats);
}
@@ -246,19 +254,26 @@ RED.diff = (function() {
}
var remoteStats = $('',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
$('').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
- if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
+ if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
$(' [ ').appendTo(remoteStats);
if (flowStats.conflicts > 0) {
$(' '+flowStats.conflicts+'').appendTo(remoteStats);
}
if (flowStats.remote.addedCount > 0) {
- $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats);
+ const cell = $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats);
+ RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.remote.changedCount > 0) {
- $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats);
+ const cell = $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats);
+ RED.popover.tooltip(cell, RED._('diff.type.changed'))
+ }
+ if (flowStats.remote.movedCount > 0) {
+ const cell = $(' '+flowStats.remote.movedCount+'').appendTo(remoteStats);
+ RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.remote.deletedCount > 0) {
- $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats);
+ const cell = $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats);
+ RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$(' ] ').appendTo(remoteStats);
}
@@ -293,7 +308,7 @@ RED.diff = (function() {
if (options.mode === "merge") {
diffPanel.addClass("red-ui-diff-panel-merge");
}
- var diffList = createDiffTable(diffPanel, diff);
+ var diffList = createDiffTable(diffPanel, diff, options);
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -516,7 +531,6 @@ RED.diff = (function() {
var hasChanges = false; // exists in original and local/remote but with changes
var unChanged = true; // existing in original,local,remote unchanged
- var localChanged = false;
if (localDiff.added[node.id]) {
stats.local.addedCount++;
@@ -535,12 +549,20 @@ RED.diff = (function() {
unChanged = false;
}
if (localDiff.changed[node.id]) {
- stats.local.changedCount++;
+ if (localDiff.positionChanged[node.id]) {
+ stats.local.movedCount++
+ } else {
+ stats.local.changedCount++;
+ }
hasChanges = true;
unChanged = false;
}
if (remoteDiff && remoteDiff.changed[node.id]) {
- stats.remote.changedCount++;
+ if (remoteDiff.positionChanged[node.id]) {
+ stats.remote.movedCount++
+ } else {
+ stats.remote.changedCount++;
+ }
hasChanges = true;
unChanged = false;
}
@@ -605,27 +627,32 @@ RED.diff = (function() {
localNodeDiv.addClass("red-ui-diff-status-moved");
var localMovedMessage = "";
if (node.z === localN.z) {
- localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
+ const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
+ const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
+ localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
} else {
- localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
+ const movedToNodeTab = localDiff.newConfig.all[localN.z]
+ const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
+ localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$(' '+localMovedMessage+'').appendTo(localNodeDiv);
}
- localChanged = true;
} else if (localDiff.deleted[node.z]) {
localNodeDiv.addClass("red-ui-diff-empty");
- localChanged = true;
} else if (localDiff.deleted[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-deleted");
$(' ').appendTo(localNodeDiv);
- localChanged = true;
} else if (localDiff.changed[node.id]) {
if (localDiff.newConfig.all[node.id].z !== node.z) {
localNodeDiv.addClass("red-ui-diff-empty");
} else {
- localNodeDiv.addClass("red-ui-diff-status-changed");
- $(' ').appendTo(localNodeDiv);
- localChanged = true;
+ if (localDiff.positionChanged[node.id]) {
+ localNodeDiv.addClass("red-ui-diff-status-moved");
+ $(' ').appendTo(localNodeDiv);
+ } else {
+ localNodeDiv.addClass("red-ui-diff-status-changed");
+ $(' ').appendTo(localNodeDiv);
+ }
}
} else {
if (localDiff.newConfig.all[node.id].z !== node.z) {
@@ -646,9 +673,13 @@ RED.diff = (function() {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
var remoteMovedMessage = "";
if (node.z === remoteN.z) {
- remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
+ const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
+ const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
+ remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
} else {
- remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
+ const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
+ const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
+ remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$(' '+remoteMovedMessage+'').appendTo(remoteNodeDiv);
}
@@ -661,8 +692,13 @@ RED.diff = (function() {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
remoteNodeDiv.addClass("red-ui-diff-empty");
} else {
- remoteNodeDiv.addClass("red-ui-diff-status-changed");
- $(' ').appendTo(remoteNodeDiv);
+ if (remoteDiff.positionChanged[node.id]) {
+ remoteNodeDiv.addClass("red-ui-diff-status-moved");
+ $(' ').appendTo(remoteNodeDiv);
+ } else {
+ remoteNodeDiv.addClass("red-ui-diff-status-changed");
+ $(' ').appendTo(remoteNodeDiv);
+ }
}
} else {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
@@ -788,7 +824,7 @@ RED.diff = (function() {
$("
",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
localCell = $(" | ",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) {
- localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
+ localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
$(''+(localChanged?'':'')+'').appendTo(localCell);
element = $('').appendTo(localCell);
var localPosition = {x:localNode.x,y:localNode.y};
@@ -813,7 +849,7 @@ RED.diff = (function() {
if (remoteNode !== undefined) {
remoteCell = $(" | ",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
- remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
+ remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
if (remoteNode) {
$(''+(remoteChanged?'':'')+'').appendTo(remoteCell);
element = $('').appendTo(remoteCell);
@@ -1099,11 +1135,11 @@ RED.diff = (function() {
// var diff = generateDiff(originalFlow,nns);
// showDiff(diff);
// }
- function showRemoteDiff(diff) {
- if (diff === undefined) {
- getRemoteDiff(showRemoteDiff);
+ function showRemoteDiff(diff, options = {}) {
+ if (!diff) {
+ getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
} else {
- showDiff(diff,{mode:'merge'});
+ showDiff(diff,{...options, mode:'merge'});
}
}
function parseNodes(nodeList) {
@@ -1144,23 +1180,53 @@ RED.diff = (function() {
}
}
function generateDiff(currentNodes,newNodes) {
- var currentConfig = parseNodes(currentNodes);
- var newConfig = parseNodes(newNodes);
- var added = {};
- var deleted = {};
- var changed = {};
- var moved = {};
+ const currentConfig = parseNodes(currentNodes);
+ const newConfig = parseNodes(newNodes);
+ const added = {};
+ const deleted = {};
+ const changed = {};
+ const positionChanged = {};
+ const moved = {};
Object.keys(currentConfig.all).forEach(function(id) {
- var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
+ const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
if (!newConfig.all.hasOwnProperty(id)) {
deleted[id] = true;
- } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
+ return
+ }
+ const currentConfigJSON = JSON.stringify(currentConfig.all[id])
+ const newConfigJSON = JSON.stringify(newConfig.all[id])
+
+ if (currentConfigJSON !== newConfigJSON) {
changed[id] = true;
-
if (currentConfig.all[id].z !== newConfig.all[id].z) {
moved[id] = true;
+ } else if (
+ currentConfig.all[id].x !== newConfig.all[id].x ||
+ currentConfig.all[id].y !== newConfig.all[id].y ||
+ currentConfig.all[id].w !== newConfig.all[id].w ||
+ currentConfig.all[id].h !== newConfig.all[id].h
+ ) {
+ // This node's position on its parent has changed. We want to
+ // check if this is the *only* change for this given node
+ const currentNodeClone = JSON.parse(currentConfigJSON)
+ const newNodeClone = JSON.parse(newConfigJSON)
+
+ delete currentNodeClone.x
+ delete currentNodeClone.y
+ delete currentNodeClone.w
+ delete currentNodeClone.h
+ delete newNodeClone.x
+ delete newNodeClone.y
+ delete newNodeClone.w
+ delete newNodeClone.h
+
+ if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
+ // Only the position has changed - everything else is the same
+ positionChanged[id] = true
+ }
}
+
}
});
Object.keys(newConfig.all).forEach(function(id) {
@@ -1169,13 +1235,14 @@ RED.diff = (function() {
}
});
- var diff = {
- currentConfig: currentConfig,
- newConfig: newConfig,
- added: added,
- deleted: deleted,
- changed: changed,
- moved: moved
+ const diff = {
+ currentConfig,
+ newConfig,
+ added,
+ deleted,
+ changed,
+ positionChanged,
+ moved
};
return diff;
}
@@ -1240,12 +1307,14 @@ RED.diff = (function() {
return diff;
}
- function showDiff(diff,options) {
+ function showDiff(diff, options) {
if (diffVisible) {
return;
}
options = options || {};
var mode = options.mode || 'merge';
+
+ options.hidePositionChanges = true
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -1315,6 +1384,9 @@ RED.diff = (function() {
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
refreshConflictHeader(diff);
mergeDiff(diff);
+ if (options.onmerge) {
+ options.onmerge()
+ }
RED.tray.close();
}
}
@@ -1345,6 +1417,7 @@ RED.diff = (function() {
var newConfig = [];
var node;
var nodeChangedStates = {};
+ var nodeMovedStates = {};
var localChangedStates = {};
for (id in localDiff.newConfig.all) {
if (localDiff.newConfig.all.hasOwnProperty(id)) {
@@ -1352,12 +1425,14 @@ RED.diff = (function() {
if (resolutions[id] === 'local') {
if (node) {
nodeChangedStates[id] = node.changed;
+ nodeMovedStates[id] = node.moved;
}
newConfig.push(localDiff.newConfig.all[id]);
} else if (resolutions[id] === 'remote') {
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
if (node) {
nodeChangedStates[id] = node.changed;
+ nodeMovedStates[id] = node.moved;
}
localChangedStates[id] = 1;
newConfig.push(remoteDiff.newConfig.all[id]);
@@ -1381,8 +1456,9 @@ RED.diff = (function() {
}
return {
config: newConfig,
- nodeChangedStates: nodeChangedStates,
- localChangedStates: localChangedStates
+ nodeChangedStates,
+ nodeMovedStates,
+ localChangedStates
}
}
@@ -1393,6 +1469,7 @@ RED.diff = (function() {
var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates;
+ var nodeMovedStates = appliedDiff.nodeMovedStates;
var localChangedStates = appliedDiff.localChangedStates;
var isDirty = RED.nodes.dirty();
@@ -1401,33 +1478,56 @@ RED.diff = (function() {
t:"replace",
config: RED.nodes.createCompleteNodeSet(),
changed: nodeChangedStates,
+ moved: nodeMovedStates,
+ complete: true,
dirty: isDirty,
rev: RED.nodes.version()
}
RED.history.push(historyEvent);
- var originalFlow = RED.nodes.originalFlow();
- // originalFlow is what the editor things it loaded
- // - add any newly added nodes from remote diff as they are now part of the record
- for (var id in diff.remoteDiff.added) {
- if (diff.remoteDiff.added.hasOwnProperty(id)) {
- if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
- originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
- }
- }
- }
+ // var originalFlow = RED.nodes.originalFlow();
+ // // originalFlow is what the editor thinks it loaded
+ // // - add any newly added nodes from remote diff as they are now part of the record
+ // for (var id in diff.remoteDiff.added) {
+ // if (diff.remoteDiff.added.hasOwnProperty(id)) {
+ // if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
+ // originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
+ // }
+ // }
+ // }
RED.nodes.clear();
var imported = RED.nodes.import(newConfig);
- // Restore the original flow so subsequent merge resolutions can properly
- // identify new-vs-old
- RED.nodes.originalFlow(originalFlow);
+ // // Restore the original flow so subsequent merge resolutions can properly
+ // // identify new-vs-old
+ // RED.nodes.originalFlow(originalFlow);
+
+ // Clear all change flags from the import
+ RED.nodes.dirty(false);
+
+ const flowsToLock = new Set()
+ function ensureUnlocked(id) {
+ const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
+ const isLocked = flow ? flow.locked : false;
+ if (flow && isLocked) {
+ flow.locked = false;
+ flowsToLock.add(flow)
+ }
+ }
imported.nodes.forEach(function(n) {
- if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
+ if (nodeChangedStates[n.id]) {
+ ensureUnlocked(n.z)
n.changed = true;
}
+ if (nodeMovedStates[n.id]) {
+ ensureUnlocked(n.z)
+ n.moved = true;
+ }
+ })
+ flowsToLock.forEach(flow => {
+ flow.locked = true
})
RED.nodes.version(diff.remoteDiff.rev);
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
index 1b73430dc..894ff3e0f 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
@@ -341,8 +341,9 @@ RED.editor = (function() {
nodeValue = node[property]
}
- const buttonId = `${prefix}-lookup-${property}`
- const selectId = prefix + '-' + property
+ const addBtnId = `${prefix}-btn-${property}-add`;
+ const editBtnId = `${prefix}-btn-${property}-edit`;
+ const selectId = prefix + '-' + property;
const input = $(`#${selectId}`);
if (input.length === 0) {
return;
@@ -365,40 +366,68 @@ RED.editor = (function() {
select.css({
'flex-grow': 1
});
+
updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
- const disableButton = function(disabled) {
- btn.prop( "disabled", !!disabled)
- btn.toggleClass("disabled", !!disabled)
- }
+
// create the edit button
- const btn = $('')
+ const editButton = $('')
.css({ "margin-left": "10px" })
.appendTo(outerWrap);
+ RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
+
+ // create the add button
+ const addButton = $('')
+ .css({ "margin-left": "10px" })
+ .appendTo(outerWrap);
+ RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
+
+ const disableButton = function(button, disabled) {
+ $(button).prop("disabled", !!disabled)
+ $(button).toggleClass("disabled", !!disabled)
+ };
+
// add the click handler
- btn.on("click", function (e) {
+ addButton.on("click", function (e) {
+ if (addButton.prop("disabled")) { return }
+ showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
+ e.preventDefault();
+ });
+ editButton.on("click", function (e) {
const selectedOpt = select.find(":selected")
if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
- if (btn.prop("disabled")) { return }
+ if (editButton.prop("disabled")) { return }
showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
e.preventDefault();
});
// dont permit the user to click the button if the selected option is an env var
select.on("change", function () {
- const selectedOpt = select.find(":selected")
+ const selectedOpt = select.find(":selected");
+ const optionsLength = select.find("option").length;
if (selectedOpt?.data('env')) {
- disableButton(true)
+ disableButton(addButton, true);
+ disableButton(editButton, true);
+ // disable the edit button if no options available
+ } else if (optionsLength === 1 && selectedOpt.val() === "_ADD_") {
+ disableButton(addButton, false);
+ disableButton(editButton, true);
+ } else if (selectedOpt.val() === "") {
+ disableButton(addButton, false);
+ disableButton(editButton, true);
} else {
- disableButton(false)
+ disableButton(addButton, false);
+ disableButton(editButton, false);
}
});
+
var label = "";
var configNode = RED.nodes.node(nodeValue);
if (configNode) {
label = RED.utils.getNodeLabel(configNode, configNode.id);
}
+
input.val(label);
}
@@ -892,7 +921,12 @@ RED.editor = (function() {
}
}
- select.append('');
+ if (!configNodes.length) {
+ select.append('');
+ } else {
+ select.append('');
+ }
+
window.setTimeout(function() { select.trigger("change");},50);
}
}
@@ -1687,8 +1721,8 @@ RED.editor = (function() {
}
if (!isSameObj(old_env, new_env)) {
- editing_node.env = new_env;
editState.changes.env = editing_node.env;
+ editing_node.env = new_env;
editState.changed = true;
}
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
index d034af3d7..cbeecd512 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
@@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() {
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
} catch (error) {
- console.warn("monaco - Error setting up json options", err)
+ console.warn("monaco - Error setting up json options", error)
}
}
@@ -526,7 +526,7 @@ RED.editor.codeEditor.monaco = (function() {
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
} catch (error) {
- console.warn("monaco - Error setting up html options", err)
+ console.warn("monaco - Error setting up html options", error)
}
}
@@ -546,7 +546,7 @@ RED.editor.codeEditor.monaco = (function() {
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
} catch (error) {
- console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
+ console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
}
}
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js b/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js
index 30dcc4bd5..d68d03d3f 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js
@@ -221,12 +221,12 @@ RED.notifications = (function() {
if (newType) {
n.className = "red-ui-notification red-ui-notification-"+newType;
}
-
+ newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
if (!fixed || newOptions.fixed === false) {
- newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
+ newTimeout = newTimeout || 5000
}
if (newOptions.buttons) {
- var buttonSet = $('').appendTo(nn)
+ var buttonSet = $('').appendTo(nn)
newOptions.buttons.forEach(function(buttonDef) {
var b = $(' |