Initial commit
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
/**
|
||||
* type property should match the value given from the API.
|
||||
*/
|
||||
var ALL_OPTIONS = [{
|
||||
privacy: 'PUBLIC',
|
||||
title: _t('components.modals.publish.privacy.public.title'),
|
||||
desc: _t('components.modals.publish.privacy.public.body'),
|
||||
alwaysEnabled: true,
|
||||
cssClass: 'green'
|
||||
}, {
|
||||
privacy: 'LINK',
|
||||
title: _t('components.modals.publish.privacy.link.title'),
|
||||
desc: _t('components.modals.publish.privacy.link.body'),
|
||||
cssClass: 'orange'
|
||||
}, {
|
||||
privacy: 'PASSWORD',
|
||||
title: _t('components.modals.publish.privacy.password.title'),
|
||||
desc: _t('components.modals.publish.privacy.password.body'),
|
||||
cssClass: 'orange-dark'
|
||||
}, {
|
||||
privacy: 'PRIVATE',
|
||||
title: _t('components.modals.publish.privacy.private.title'),
|
||||
desc: _t('components.modals.publish.privacy.private.body'),
|
||||
cssClass: 'red'
|
||||
}];
|
||||
|
||||
module.exports = function (visDefinitionModel, userModel) {
|
||||
var isVisualization = visDefinitionModel.isVisualization();
|
||||
var actions = userModel.get('actions');
|
||||
var canSelectPremiumOptions = actions[ isVisualization ? 'private_maps' : 'private_tables' ];
|
||||
var currentPrivacy = visDefinitionModel.get('privacy');
|
||||
var availableOptions = visDefinitionModel.privacyOptions();
|
||||
|
||||
return _.chain(ALL_OPTIONS)
|
||||
.filter(function (option) {
|
||||
return _.contains(availableOptions, option.privacy);
|
||||
})
|
||||
.map(function (option) {
|
||||
// Set state that depends on vis and user attrs, they should not vary during the lifecycle of this collection
|
||||
return _.defaults({
|
||||
selected: option.privacy === currentPrivacy,
|
||||
disabled: !(option.alwaysEnabled || canSelectPremiumOptions)
|
||||
}, option);
|
||||
})
|
||||
.value();
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
var linkIconTemplate = require('./icon-link.tpl');
|
||||
var embedIconTemplate = require('./icon-embed.tpl');
|
||||
|
||||
module.exports = function (visDefinitionModel) {
|
||||
return [{
|
||||
createIcon: function () {
|
||||
return linkIconTemplate();
|
||||
},
|
||||
type: 'get-link',
|
||||
content: visDefinitionModel.embedURL(),
|
||||
private: visDefinitionModel.get('privacy') === 'PRIVATE'
|
||||
}, {
|
||||
createIcon: function () {
|
||||
return embedIconTemplate();
|
||||
},
|
||||
type: 'embed',
|
||||
content: '<iframe width="100%" height="520" frameborder="0" src="' + encodeURI(visDefinitionModel.embedURL()) + '" allowfullscreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen></iframe>',
|
||||
url: encodeURI(visDefinitionModel.embedURL()),
|
||||
private: visDefinitionModel.get('privacy') === 'PRIVATE'
|
||||
}];
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
var Backbone = require('backbone');
|
||||
var PrivacyModel = require('./privacy-model');
|
||||
var PasswordModel = require('./privacy-password-model');
|
||||
|
||||
/**
|
||||
* Collection that holds the different privacy options.
|
||||
*/
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
model: function (attrs, options) {
|
||||
if (attrs.privacy === 'PASSWORD') {
|
||||
return new PasswordModel(attrs, options);
|
||||
} else {
|
||||
return new PrivacyModel(attrs, options);
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.bind('change:selected', this._deselectLastSelected, this);
|
||||
},
|
||||
|
||||
searchByPrivacy: function (privacy) {
|
||||
return this.findWhere({privacy: privacy});
|
||||
},
|
||||
|
||||
selectedOption: function () {
|
||||
return this.find(function (option) {
|
||||
return option.get('selected');
|
||||
});
|
||||
},
|
||||
|
||||
passwordOption: function () {
|
||||
return this.find(function (option) {
|
||||
return option.get('privacy') === 'PASSWORD';
|
||||
});
|
||||
},
|
||||
|
||||
_deselectLastSelected: function (m, isSelected) {
|
||||
if (isSelected) {
|
||||
this.each(function (option) {
|
||||
if (option !== m) {
|
||||
option.set({selected: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
privacy: 'PUBLIC',
|
||||
disabled: false,
|
||||
selected: false,
|
||||
password: undefined
|
||||
},
|
||||
|
||||
validate: function (attrs) {
|
||||
if (attrs.disabled && attrs.selected) {
|
||||
return 'Option can not be disabled and selected at the same time';
|
||||
}
|
||||
},
|
||||
|
||||
classNames: function () {
|
||||
return _.chain(['disabled', 'selected'])
|
||||
.map(function (attr) { return this.attributes[attr] !== undefined ? 'is-' + attr : undefined; }, this)
|
||||
.compact().value().join(' ');
|
||||
},
|
||||
|
||||
canSave: function () {
|
||||
return !this.get('disabled');
|
||||
},
|
||||
|
||||
isPassword: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
isSelected: function () {
|
||||
return this.get('selected') === true;
|
||||
},
|
||||
|
||||
saveToVis: function (vis, callbacks) {
|
||||
return vis.save(this._attrsToSave(), _.extend({ wait: true }, callbacks));
|
||||
},
|
||||
|
||||
_attrsToSave: function () {
|
||||
return _.pick(this.attributes, 'privacy', 'password');
|
||||
}
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var _ = require('underscore');
|
||||
var PrivacyModel = require('./privacy-model');
|
||||
|
||||
var FAKE_PASSWORD = '!@#!@#';
|
||||
|
||||
/**
|
||||
* View model for the special privacy option representing a password protected map.
|
||||
* It handles the logic related to the password that needs to be set for the option.
|
||||
*/
|
||||
module.exports = PrivacyModel.extend({
|
||||
|
||||
initialize: function () {
|
||||
PrivacyModel.prototype.initialize.apply(this, arguments);
|
||||
this.set('password', FAKE_PASSWORD);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override OptionModel.attrsToSave
|
||||
*/
|
||||
_attrsToSave: function () {
|
||||
var attrs = PrivacyModel.prototype._attrsToSave.call(this);
|
||||
|
||||
if (attrs.password === FAKE_PASSWORD) {
|
||||
delete attrs.password;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
canSave: function () {
|
||||
return !this.get('disabled') && !_.isEmpty(this.get('password'));
|
||||
},
|
||||
|
||||
isPassword: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
var $ = require('jquery');
|
||||
var moment = require('moment');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var PrivacyWarningView = require('builder/components/modals/privacy-warning/privacy-warning-view');
|
||||
var ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
var VisDefinitionModel = require('builder/data/vis-definition-model');
|
||||
var template = require('./publish-button.tpl');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'visDefinitionModel',
|
||||
'mapcapsCollection',
|
||||
'configModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-button': '_onUpdate'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (opts[item] === undefined) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
// We recreate this modal stack to be able to
|
||||
// have two modals stacked on screen
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
status: 'idle'
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var publishedOn = this._mapcapsCollection.length > 0
|
||||
? _t('components.modals.publish.share.last-published', { date:
|
||||
this.model.get('status') === 'updated'
|
||||
? moment(this._mapcapsCollection.first().get('created_at')).fromNow()
|
||||
: moment(this._mapcapsCollection.first().get('created_at')).format('Do MMMM YYYY, HH:mm')
|
||||
})
|
||||
: _t('components.modals.publish.share.unpublished');
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
isPublished: this._isPublished(),
|
||||
isLoading: this._isLoading(),
|
||||
isDisabled: this._isDisabled(),
|
||||
publishedOn: publishedOn
|
||||
}));
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._mapcapsCollection.on('reset', this.render, this);
|
||||
this.add_related_model(this._mapcapsCollection);
|
||||
this.model.on('change:status', this.render, this);
|
||||
},
|
||||
|
||||
_isPublished: function () {
|
||||
return this._mapcapsCollection.length > 0;
|
||||
},
|
||||
|
||||
_isLoading: function () {
|
||||
return this.model.get('status') === 'loading';
|
||||
},
|
||||
|
||||
_isDisabled: function () {
|
||||
return this.model.get('status') === 'updated';
|
||||
},
|
||||
|
||||
_onUpdate: function () {
|
||||
var self = this;
|
||||
var url = this._visDefinitionModel.mapcapsURL();
|
||||
var data = {
|
||||
api_key: this._configModel.get('api_key')
|
||||
};
|
||||
|
||||
if (this._isDisabled()) return false;
|
||||
|
||||
if (this._shouldShowPrivacyWarning()) {
|
||||
this._checkPrivacyChange(
|
||||
this._visDefinitionModel.get('privacy'),
|
||||
function () { self._createMapCap(url, data); }
|
||||
);
|
||||
} else {
|
||||
this._createMapCap(url, data);
|
||||
}
|
||||
},
|
||||
|
||||
_checkPrivacyChange: function (newPrivacyStatus, confirmCallback, dismissCallback) {
|
||||
this._modals.create(function (modalModel) {
|
||||
return new PrivacyWarningView({
|
||||
modalModel: modalModel,
|
||||
privacyType: newPrivacyStatus,
|
||||
onConfirm: confirmCallback,
|
||||
onDismiss: dismissCallback
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_createMapCap: function (url, data) {
|
||||
var self = this;
|
||||
this.model.set({status: 'loading'});
|
||||
|
||||
$.post(url, data)
|
||||
.done(function (data) {
|
||||
self._visDefinitionModel.set('visChanges', 0);
|
||||
self._mapcapsCollection.add(data, {at: 0});
|
||||
self.model.set({status: 'updated'});
|
||||
})
|
||||
.fail(function () {
|
||||
self.model.set({status: 'error'});
|
||||
});
|
||||
},
|
||||
|
||||
_shouldShowPrivacyWarning: function () {
|
||||
return this._userModel.canSelectPremiumOptions(this._visDefinitionModel) &&
|
||||
!this._isPublished() &&
|
||||
VisDefinitionModel.isPubliclyAvailable(this._visDefinitionModel.get('privacy'));
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._modals.destroy();
|
||||
CoreView.prototype.clean.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<button class="Publish-modalButton CDB-Button CDB-Button--loading CDB-Button--primary CDB-Button--small u-rSpace js-button
|
||||
<% if (isDisabled) {%> is-disabled<% } %>
|
||||
<% if (isLoading) {%> is-loading<% } %>
|
||||
">
|
||||
<div class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
<% if (isPublished) { %>
|
||||
<%- _t('components.modals.publish.update-btn') %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.publish.publish-btn') %>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="CDB-Button-loader CDB-LoaderIcon CDB-LoaderIcon--small is-white">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
</button> <%- publishedOn %>
|
||||
@@ -0,0 +1,200 @@
|
||||
var moment = require('moment');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./publish.tpl');
|
||||
var TabPaneTemplate = require('./tab-pane-submenu.tpl');
|
||||
var createTextLabelsTabPane = require('builder/components/tab-pane/create-text-labels-tab-pane');
|
||||
var PublishView = require('./publish/publish-view');
|
||||
var ShareView = require('./share/share-view');
|
||||
var PrivacyDropdown = require('builder/components/privacy-dropdown/privacy-dropdown-view');
|
||||
var PublishButton = require('./publish-button-view');
|
||||
var ShareWith = require('./share-with-view');
|
||||
var UpgradeView = require('./upgrade-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'visDefinitionModel',
|
||||
'privacyCollection',
|
||||
'userModel',
|
||||
'configModel',
|
||||
'isOwner'
|
||||
];
|
||||
|
||||
var MODE_FULL = 'full';
|
||||
var MODE_SHARE = 'share';
|
||||
var MODE_PUBLISH = 'publish';
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Publish-modal',
|
||||
|
||||
events: {
|
||||
'click .js-done': '_onDone'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
// Optional options
|
||||
this._mapcapsCollection = opts.mapcapsCollection;
|
||||
this.mode = opts.mode || MODE_FULL;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
name: this._visDefinitionModel.get('name'),
|
||||
isSimple: !this._hasTabs(),
|
||||
hasShareStats: this._hasShareStats()
|
||||
}));
|
||||
|
||||
this._initViews();
|
||||
|
||||
if (this.mode === MODE_FULL) {
|
||||
if (!this._hasOrganization()) {
|
||||
this._makePublishView();
|
||||
} else {
|
||||
this._initTabsViews();
|
||||
}
|
||||
} else if (this.mode === MODE_SHARE) {
|
||||
this._makeShareView();
|
||||
} else if (this.mode === MODE_PUBLISH) {
|
||||
this._makePublishView();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_hasShareStats: function () {
|
||||
return this.mode !== MODE_PUBLISH && this._hasOrganization();
|
||||
},
|
||||
|
||||
_hasOrganization: function () {
|
||||
return this._userModel.isInsideOrg();
|
||||
},
|
||||
|
||||
_hasTabs: function () {
|
||||
var hasOrganization = this._hasOrganization();
|
||||
return hasOrganization && this.mode === MODE_FULL;
|
||||
},
|
||||
|
||||
_makePublishView: function () {
|
||||
var view = this._createPublishView();
|
||||
this.$('.js-panes').append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_makeShareView: function () {
|
||||
var view = this._createShareView();
|
||||
this.$('.js-panes').append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var dropdown;
|
||||
var publishButton;
|
||||
var shareWith;
|
||||
var upgradeView;
|
||||
var publishedOn;
|
||||
|
||||
dropdown = new PrivacyDropdown({
|
||||
privacyCollection: this._privacyCollection,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
isOwner: this._isOwner
|
||||
});
|
||||
|
||||
this.$('.js-dropdown').append(dropdown.render().el);
|
||||
this.addView(dropdown);
|
||||
|
||||
if (this._mapcapsCollection !== undefined) {
|
||||
publishButton = new PublishButton({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
configModel: this._configModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
|
||||
this.$('.js-update').append(publishButton.render().el);
|
||||
this.addView(publishButton);
|
||||
} else {
|
||||
publishedOn = _t('components.modals.publish.share.last-published', { date:
|
||||
moment(this._visDefinitionModel.get('updated_at')).format('Do MMMM YYYY, HH:mm')
|
||||
});
|
||||
|
||||
this.$('.js-update').html(publishedOn);
|
||||
}
|
||||
|
||||
if (this._hasShareStats()) {
|
||||
shareWith = new ShareWith({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
userModel: this._userModel,
|
||||
avatarClass: 'Share-user--big',
|
||||
separationClass: 'u-rSpace--xl'
|
||||
});
|
||||
this.$('.js-share-users').append(shareWith.render().el);
|
||||
this.addView(shareWith);
|
||||
}
|
||||
|
||||
if (!this._hasOrganization()) {
|
||||
upgradeView = new UpgradeView();
|
||||
this.$('.js-upgrade').append(upgradeView.render().el);
|
||||
this.addView(upgradeView);
|
||||
}
|
||||
},
|
||||
|
||||
_initTabsViews: function () {
|
||||
var self = this;
|
||||
|
||||
var tabPaneTabs = [{
|
||||
name: 'share',
|
||||
label: _t('components.modals.publish.menu.share'),
|
||||
createContentView: self._createShareView.bind(self)
|
||||
}, {
|
||||
name: 'publish',
|
||||
label: _t('components.modals.publish.menu.publish'),
|
||||
createContentView: self._createPublishView.bind(self)
|
||||
}];
|
||||
|
||||
var tabPaneOptions = {
|
||||
tabPaneOptions: {
|
||||
template: TabPaneTemplate,
|
||||
tabPaneItemOptions: {
|
||||
tagName: 'li',
|
||||
klassName: 'CDB-NavSubmenu-item'
|
||||
}
|
||||
},
|
||||
tabPaneItemLabelOptions: {
|
||||
tagName: 'button',
|
||||
className: 'CDB-NavSubmenu-link u-upperCase Publish-modalLink'
|
||||
}
|
||||
};
|
||||
|
||||
this._layerTabPaneView = createTextLabelsTabPane(tabPaneTabs, tabPaneOptions);
|
||||
this.$('.js-panes').append(this._layerTabPaneView.render().$el);
|
||||
this.addView(this._layerTabPaneView);
|
||||
},
|
||||
|
||||
_createShareView: function () {
|
||||
return new ShareView({
|
||||
className: 'Share-wrapper',
|
||||
currentUserId: this._userModel.id,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
organization: this._userModel._organizationModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
},
|
||||
|
||||
_createPublishView: function () {
|
||||
return new PublishView({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
userModel: this._userModel
|
||||
});
|
||||
},
|
||||
|
||||
_onDone: function () {
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="Publish-modalHeader">
|
||||
<div class="u-inner">
|
||||
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis u-bSpace--m"><%- name %></div>
|
||||
<ul class="u-flex u-alignCenter">
|
||||
<li class="js-dropdown u-rSpace--xl"></li>
|
||||
<% if (hasShareStats) { %>
|
||||
<li class="js-share-users">
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="CDB-Text CDB-Size-medium u-altTextColor js-update">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Publish-modalBody js-panes
|
||||
<% if (isSimple) { %>is-simple<% } %>
|
||||
">
|
||||
<% if (isSimple) { %>
|
||||
<div class="Publish-modalShadow"></div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="Dialog-footer Dialog-footer--expanded CreateDialog-footer Publish-modalFooter">
|
||||
<div>
|
||||
<div class="CreateDialog-footerShadow"></div>
|
||||
<div class="CreateDialog-footerLine"></div>
|
||||
<div class="CreateDialog-footerInner ">
|
||||
<div class="CreateDialog-footerInfo"></div>
|
||||
<div class="CreateDialog-footerActions js-footerActions u-flex u-justifySpace u-grow">
|
||||
<div class="js-upgrade"></div>
|
||||
<button class="CDB-Button CDB-Button--secondary js-done">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.publish.done-btn') %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
var linkIconTemplate = require('./icon-link.tpl');
|
||||
var embedIconTemplate = require('./icon-embed.tpl');
|
||||
|
||||
module.exports = function (visDefinitionModel) {
|
||||
return [{
|
||||
createIcon: function () {
|
||||
return linkIconTemplate();
|
||||
},
|
||||
type: 'get-link',
|
||||
content: visDefinitionModel.embedURL(),
|
||||
private: visDefinitionModel.get('privacy') === 'PRIVATE'
|
||||
}, {
|
||||
createIcon: function () {
|
||||
return embedIconTemplate();
|
||||
},
|
||||
type: 'embed',
|
||||
content: '<iframe width="100%" height="520" frameborder="0" src="' + encodeURI(visDefinitionModel.embedURL()) + '" allowfullscreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen></iframe>',
|
||||
url: encodeURI(visDefinitionModel.embedURL()),
|
||||
private: visDefinitionModel.get('privacy') === 'PRIVATE'
|
||||
}];
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
<svg width="25px" height="25px" viewBox="682 469 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M697.141667,478.48556 C695.774797,477.117815 695.774797,474.90231 697.141553,473.535553 L697.495051,473.182056 L697.141609,472.828502 L693.960609,469.646502 L693.607124,469.292907 L693.253515,469.646378 L690.685515,472.213378 L691.039,472.567 L691.469343,472.312431 C691.271611,471.978171 691.043454,471.678347 690.778553,471.413447 C689.020272,469.655166 686.171603,469.655166 684.414333,471.41356 C682.657184,473.170709 682.657184,476.020291 684.414447,477.777553 C684.682252,478.044267 684.982655,478.273359 685.314094,478.468734 L685.568,478.038 L685.214447,477.684447 L682.646447,480.252447 L682.292893,480.606 L682.646447,480.959553 L685.828447,484.141553 L686.181887,484.494994 L686.53544,484.141667 C687.903185,482.774797 690.11869,482.774797 691.485447,484.141553 C692.852184,485.508291 692.852184,487.724709 691.485447,489.091447 L691.131893,489.445 L691.485447,489.798553 L694.667447,492.980553 L695.021,493.334107 L695.374553,492.980553 L697.941553,490.413553 L697.588,490.06 L697.157928,490.315025 C697.3561,490.649221 697.584149,490.948256 697.849447,491.213553 C699.60664,492.971872 702.456485,492.971872 704.21278,491.213327 C705.970872,489.45636 705.970872,486.606515 704.212327,484.85022 C703.948015,484.584823 703.647051,484.355236 703.312906,484.158266 L703.059,484.589 L703.412553,484.942553 L705.979553,482.375553 L706.333107,482.022 L705.979553,481.668447 L702.797553,478.486447 L702.444,478.132893 L702.090447,478.486447 C700.725536,479.851357 698.508943,479.851087 697.141327,478.48522 L697.141667,478.48556 Z M696.434673,479.19278 C698.192747,480.948604 701.042155,480.948952 702.797553,479.193553 L702.090447,479.193553 L705.272447,482.375553 L705.272447,481.668447 L702.705447,484.235447 L702.248976,484.691917 L702.805094,485.019734 C703.068228,485.174844 703.301587,485.35286 703.504724,485.55683 C704.873128,486.923485 704.873128,489.13964 703.50556,490.506333 C702.139515,491.874128 699.92336,491.874128 698.556667,490.50656 C698.35252,490.302414 698.175052,490.069704 698.018072,489.804975 L697.689693,489.2512 L697.234447,489.706447 L694.667447,492.273447 L695.374553,492.273447 L692.192553,489.091447 L692.192553,489.798553 C693.949816,488.041291 693.949816,485.191709 692.192553,483.434447 C690.43531,481.677203 687.586815,481.677203 685.82856,483.434333 L686.535553,483.434447 L683.353553,480.252447 L683.353553,480.959553 L685.921553,478.391553 L686.378024,477.935083 L685.821906,477.607266 C685.560783,477.453341 685.327004,477.275057 685.12083,477.069724 C683.754816,475.703709 683.754816,473.487291 685.121553,472.120553 C686.488397,470.752834 688.703728,470.752834 690.071447,472.120553 C690.274695,472.323801 690.452152,472.557 690.608657,472.821569 L690.936742,473.376188 L691.392485,472.920622 L693.960485,470.353622 L693.253391,470.353498 L696.434391,473.535498 L696.434447,472.828447 C694.677203,474.58569 694.677203,477.434185 696.434333,479.19244 L696.434673,479.19278 Z" id="Embed-it" stroke="none" fill="#AAAAAA" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
+3
@@ -0,0 +1,3 @@
|
||||
<svg width="23px" height="23px" viewBox="683 470 23 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M696.399947,482.106803 L696.485947,482.192803 C697.462209,483.169066 699.044791,483.169066 700.021053,482.192803 L703.986009,478.228848 C705.34724,476.86645 705.34724,474.642218 703.986356,473.279 L702.227953,471.521596 C700.866908,470.159384 698.640092,470.159384 697.278795,471.521848 L693.313991,475.485652 C692.337722,476.461921 692.337722,478.044329 693.31372,479.021577 L693.40202,479.108865 L694.10498,478.397635 L694.01898,478.312635 C693.435278,477.728171 693.435278,476.778579 694.021053,476.192803 L697.986009,472.228848 C698.956908,471.257116 700.550092,471.257116 701.520795,472.228652 L703.279047,473.985904 C704.24976,474.958282 704.24976,476.55005 703.278795,477.521848 L699.313991,481.485652 C698.728209,482.071434 697.778791,482.071434 697.193053,481.485697 L697.107053,481.399697 L696.399947,482.106803 L696.399947,482.106803 Z M692.10498,480.397635 L692.01898,480.312635 C691.044791,479.338434 689.462209,479.338434 688.485947,480.314697 L684.520991,484.278652 C683.159735,485.641076 683.159735,487.866424 684.520795,489.228652 L686.279047,490.985904 C687.640092,492.348116 689.866908,492.348116 691.228205,490.985652 L695.193009,487.021848 C696.169278,486.045579 696.169278,484.463171 695.19328,483.485923 L695.107053,483.399697 L694.399947,484.106803 L694.485947,484.192803 C695.071722,484.779329 695.071722,485.728921 694.485947,486.314697 L690.520991,490.278652 C689.550092,491.250384 687.956908,491.250384 686.986205,490.278848 L685.227953,488.521596 C684.257265,487.550076 684.257265,485.957424 685.228205,484.985652 L689.193009,481.021848 C689.778791,480.436066 690.728209,480.436066 691.313947,481.021803 L691.40202,481.108865 L692.10498,480.397635 L692.10498,480.397635 Z M690.718053,485.495803 L698.496053,477.717803 L697.788947,477.010697 L690.010947,484.788697 L690.718053,485.495803 L690.718053,485.495803 Z" id="Get-link" stroke="none" fill="#AAAAAA" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
+13
@@ -0,0 +1,13 @@
|
||||
<svg width="16px" height="26px" viewBox="517 468 16 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="sdk" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(518.000000, 469.000000)">
|
||||
<path d="M13,21.5 C13,22.3278576 12.3278576,23 11.5,23 L2.5,23 C1.67214237,23 1,22.3278576 1,21.5 L1,2.5 C1,1.67214237 1.67214237,1 2.5,1 L11.5,1 C12.3278576,1 13,1.67214237 13,2.5 L13,21.5 L13,21.5 Z M14,21.5 L14,2.5 C14,1.11985763 12.8801424,0 11.5,0 L2.5,0 C1.11985763,0 0,1.11985763 0,2.5 L0,21.5 C0,22.8801424 1.11985763,24 2.5,24 L11.5,24 C12.8801424,24 14,22.8801424 14,21.5 L14,21.5 Z" id="Shape" fill="#AAAAAA"></path>
|
||||
<polygon id="Path" fill="#AAAAAA" points="4.5 2 4.5 3 9.5 3 9.5 2"></polygon>
|
||||
<path d="M7,21.501 C7,21.5008576 6.99985763,21.501 7,21.501 C7.00014237,21.501 7,21.5008576 7,21.501 C7,21.5011424 7.00014237,21.501 7,21.501 C6.99985763,21.501 7,21.5011424 7,21.501 L7,21.501 Z M8,21.501 C8,20.9488576 7.55214237,20.501 7,20.501 C6.44785763,20.501 6,20.9488576 6,21.501 C6,22.0531424 6.44785763,22.501 7,22.501 C7.55214237,22.501 8,22.0531424 8,21.501 L8,21.501 Z" id="Shape" fill="#AAAAAA"></path>
|
||||
<path d="M5.15013923,9.64279216 L3.10813923,11.6427922 L2.74343002,12 L3.10813923,12.3572078 L5.15013923,14.3572078 L5.84986077,13.6427922 L3.80786077,11.6427922 L3.80786077,12.3572078 L5.84986077,10.3572078 L5.15013923,9.64279216 L5.15013923,9.64279216 Z M8.15013923,10.3572078 L10.1921392,12.3572078 L10.1921392,11.6427922 L8.15013923,13.6427922 L8.84986077,14.3572078 L10.8918608,12.3572078 L11.25657,12 L10.8918608,11.6427922 L8.84986077,9.64279216 L8.15013923,10.3572078 L8.15013923,10.3572078 Z" id="Shape" fill="#AAAAAA"></path>
|
||||
<polygon id="Path" fill="#AAAAAA" points="4.5 3 9.5 3 9.5 2 4.5 2"></polygon>
|
||||
<polygon id="Path" fill="#AAAAAA" points="13.5 20 13.5 19 0.5 19 0.5 20"></polygon>
|
||||
<polygon id="Path" fill="#AAAAAA" points="13.5 19 0.5 19 0.5 20 13.5 20"></polygon>
|
||||
<polygon id="Path" fill="#AAAAAA" points="13.5 5 13.5 4 0.5 4 0.5 5"></polygon>
|
||||
<polygon id="Path" fill="#AAAAAA" points="13.5 4 0.5 4 0.5 5 13.5 5"></polygon>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
+71
@@ -0,0 +1,71 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var CreateShareOptions = require('./create-share-options');
|
||||
var ShareCollection = require('./share-collection');
|
||||
var ShareItemView = require('./share-item-view');
|
||||
var template = require('./publish.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'visDefinitionModel',
|
||||
'mapcapsCollection',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Publish-modalShare u-inner',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.hasOrganization = this._userModel.isInsideOrg();
|
||||
|
||||
var shareOptions = CreateShareOptions(this._visDefinitionModel);
|
||||
this._shareCollection = new ShareCollection(shareOptions);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var isPublished = this._mapcapsCollection.length > 0;
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
isPublished: isPublished
|
||||
}));
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var renderItemView = this._renderItemView.bind(this);
|
||||
this._shareCollection.each(renderItemView);
|
||||
},
|
||||
|
||||
_renderItemView: function (model) {
|
||||
var self = this;
|
||||
var isPublished = this._mapcapsCollection.length > 0;
|
||||
var view = new ShareItemView({
|
||||
model: model,
|
||||
isPublished: isPublished,
|
||||
hasOrganization: self.hasOrganization
|
||||
});
|
||||
|
||||
this.$('.js-list').append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_revampPrivacyOptions: function () {
|
||||
var shareOptions = CreateShareOptions(this._visDefinitionModel);
|
||||
this._shareCollection.reset(shareOptions);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._visDefinitionModel.on('sync', this._revampPrivacyOptions, this);
|
||||
this.add_related_model(this._visDefinitionModel);
|
||||
|
||||
this._shareCollection.on('reset', this.render, this);
|
||||
this.add_related_model(this._shareCollection);
|
||||
|
||||
this._mapcapsCollection.on('reset add', this.render, this);
|
||||
this.add_related_model(this._mapcapsCollection);
|
||||
}
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<ul class="Publish-optionsList u-flex u-justifySpace js-list"></ul>
|
||||
<% if (!isPublished) { %>
|
||||
<div class="Share-info">
|
||||
<div>
|
||||
<h2 class="CDB-Text CDB-Size-large u-secondaryTextColor u-bSpace is-light"><%- _t('components.modals.publish.share.unpublished-header') %></h2>
|
||||
<p class="CDB-Text CDB-Size-medium is-light"><%- _t('components.modals.publish.share.unpublished-subheader') %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
var Backbone = require('backbone');
|
||||
var ShareModel = require('./share-model');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
model: ShareModel
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Clipboard = require('clipboard');
|
||||
var template = require('./share-item.tpl');
|
||||
var browserDetect = require('builder/helpers/browser-detect');
|
||||
|
||||
var PRIVATE_LABEL_TEMPLATE = _.template("<span class='CDB-Text CDB-Size-small is-semibold u-errorTextColor'><%- private %></span>.<br>")({
|
||||
private: _t('components.modals.publish.share.private')
|
||||
});
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Card',
|
||||
tagName: 'li',
|
||||
|
||||
events: {
|
||||
'click .js-change-privacy': '_onChangePrivacyClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.model) throw new Error('model is required');
|
||||
if (opts.isPublished == null) throw new Error('isPublished is required');
|
||||
|
||||
this._isPublished = opts.isPublished;
|
||||
this._type = this.model.type();
|
||||
this._hasOrganization = opts.hasOrganization;
|
||||
this._title = _t('components.modals.publish.share.' + this._type + '.title');
|
||||
|
||||
if (this.model.isPrivate()) {
|
||||
this._body = _t('components.modals.publish.share.' + this._type + '.private.body', {
|
||||
private: PRIVATE_LABEL_TEMPLATE
|
||||
});
|
||||
} else {
|
||||
this._body = _t('components.modals.publish.share.' + this._type + '.body');
|
||||
}
|
||||
|
||||
this._link = _t('components.modals.publish.share.' + this._type + '.link');
|
||||
this._copy = this._getButtonLabel();
|
||||
this.$el.toggleClass('is-disabled', this.model.isPrivate());
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
if (!this.model.isPrivate() && this._isPublished) {
|
||||
this._initClipboard();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_getButtonLabel: function () {
|
||||
var browser = browserDetect();
|
||||
var label = _t('components.modals.publish.share.' + this._type + '.copy');
|
||||
if (browser.name === 'Safari') {
|
||||
label = _t('components.modals.publish.share.' + this._type + '.select');
|
||||
}
|
||||
|
||||
return label;
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return this.$('.js-input').val();
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var view = template({
|
||||
id: this.cid,
|
||||
isPrivate: this.model.isPrivate(),
|
||||
type: this._type,
|
||||
title: this._title,
|
||||
body: this._body,
|
||||
content: this.model.content(),
|
||||
link: this._link,
|
||||
copy: this._copy,
|
||||
url: this.model.url(),
|
||||
isPublished: this._isPublished
|
||||
});
|
||||
|
||||
this.$el.append(view);
|
||||
|
||||
var iconTemplate = this.model.createIcon();
|
||||
this.$('.js-icon').append(iconTemplate());
|
||||
},
|
||||
|
||||
_initClipboard: function () {
|
||||
if (this._clipboard) {
|
||||
this._clipboard.destroy();
|
||||
}
|
||||
|
||||
var btn = this.$('.js-copy');
|
||||
this._clipboard = new Clipboard(btn.get(0));
|
||||
},
|
||||
|
||||
_onChangePrivacyClick: function (e) {
|
||||
this.killEvent(e);
|
||||
this.options.onChangePrivacy && this.options.onChangePrivacy();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._clipboard && this._clipboard.destroy();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<div class="Card-icon u-bSpace--xl js-icon"></div>
|
||||
|
||||
<div class="Card-body u-bSpace--xl">
|
||||
<% if (isPrivate) { %>
|
||||
<h3 class="CDB-Text CDB-Size-large u-altTextColor u-bSpace--m"><%- title %></h3>
|
||||
<% } else { %>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace--m"><%- title %></h3>
|
||||
<% } %>
|
||||
<div class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%= body %>
|
||||
<br/>
|
||||
<% if (url && !isPrivate) { %>
|
||||
<a href="<%- url %>" class="Share-link js-link"><%- link %></a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (!isPrivate && isPublished) { %>
|
||||
<div class="Share-input">
|
||||
<input type="text" id="<%- id %>" value="<%- content %>" class="Share-input-field CDB-InputText is-disabled CDB-Text CDB-Size-medium u-ellipsis js-input" readonly>
|
||||
<button class="Share-copy CDB-Button CDB-Button--small js-copy" data-clipboard-target="#<%- id %>">
|
||||
<span class="CDB-Button-Text CDB-Text CDB-Size-small u-actionTextColor"><%- copy %></span>
|
||||
</button>
|
||||
</div>
|
||||
<% } %>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
createIcon: function () {
|
||||
return this.get('createIcon');
|
||||
},
|
||||
|
||||
type: function () {
|
||||
return this.get('type');
|
||||
},
|
||||
|
||||
content: function () {
|
||||
return this.get('content');
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return this.get('url');
|
||||
},
|
||||
|
||||
isPrivate: function () {
|
||||
return this.get('private');
|
||||
}
|
||||
});
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./share-with.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'visDefinitionModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Share-with u-flex u-alignCenter',
|
||||
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.separationClass = opts.separationClass || '';
|
||||
this.avatarClass = opts.avatarClass || '';
|
||||
|
||||
if (this.options.clickPrivacyAction) {
|
||||
this._clickPrivacyAction = this.options.clickPrivacyAction;
|
||||
}
|
||||
|
||||
this._acl = this._visDefinitionModel.getPermissionModel().acl;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var people = this._getPeople();
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
avatar: this._userModel.get('avatar_url'),
|
||||
people: people,
|
||||
avatarClass: this.avatarClass,
|
||||
separationClass: this.separationClass,
|
||||
hasAction: !!this._clickPrivacyAction
|
||||
}));
|
||||
this.$el.toggleClass('is-pointer', !!this._clickPrivacyAction);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_getPeople: function () {
|
||||
var types = this._acl.pluck('type');
|
||||
if (types.indexOf('org') > -1) {
|
||||
return this._userModel.getOrganization().get('user_count') - 1;
|
||||
} else {
|
||||
var users = [];
|
||||
|
||||
// Grab all ids from every group
|
||||
_.each(this._acl.where({type: 'group'}), function (group) {
|
||||
var entity = group.get('entity');
|
||||
Array.prototype.push.apply(users, entity.users.pluck('id'));
|
||||
}, this);
|
||||
|
||||
// Grab all ids from every user
|
||||
_.each(this._acl.where({type: 'user'}), function (group) {
|
||||
var entity = group.get('entity');
|
||||
users.push(entity.get('id'));
|
||||
}, this);
|
||||
|
||||
// remove current user id because it can be part of a group
|
||||
users = _.without(users, this._userModel.id);
|
||||
|
||||
// counts unique ids
|
||||
return _.unique(users).length;
|
||||
}
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._acl.on('fetch', this.render, this);
|
||||
this.add_related_model(this._acl);
|
||||
},
|
||||
|
||||
_onClick: function () {
|
||||
this._clickPrivacyAction && this._clickPrivacyAction();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<% if (people > 0) { %>
|
||||
<div class="Share-user <%- avatarClass %> u-rSpace u-iBlock" style="background-image: url(<%- avatar %>)"></div>
|
||||
<span class="Share-user-text u-secondaryTextColor CDB-Text CDB-Size-small <%- separationClass %>">+ <%- people %></span>
|
||||
<% } else { %>
|
||||
<% if (hasAction) { %>
|
||||
<button class="CDB-Text CDB-Size-small u-upperCase u-actionTextColor <%- separationClass %>"><%- _t('components.modals.publish.share.add-people') %></button>
|
||||
<% } %>
|
||||
<% } %>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var PermissionView = require('./share-permission-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'collection',
|
||||
'currentUserId',
|
||||
'organization',
|
||||
'hasOrganization',
|
||||
'sharePermissionModel',
|
||||
'isVisualization'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Share-list',
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (opts[item] === undefined) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
// To avoid jumps in rendering when the user gets permission
|
||||
// we sort the collection only the first time
|
||||
this._sortedCollection = this._sortCollection();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._sharePermissionModel.acl.on('add remove reset change', this.render, this);
|
||||
this.add_related_model(this._sharePermissionModel);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
if (this._hasOrganization) {
|
||||
this._renderOrganization();
|
||||
}
|
||||
|
||||
_.each(this._sortedCollection, this._renderItem, this);
|
||||
},
|
||||
|
||||
_sortCollection: function () {
|
||||
// sharing first
|
||||
var sharing = this._collection.filter(function (model) {
|
||||
var permission = this._sharePermissionModel;
|
||||
var hasWriteAccess = permission.hasWriteAccess(model);
|
||||
var hasReadAccess = permission.hasReadAccess(model);
|
||||
return hasWriteAccess || hasReadAccess;
|
||||
}, this);
|
||||
|
||||
var noSharing = this._collection.filter(function (model) {
|
||||
var permission = this._sharePermissionModel;
|
||||
var hasWriteAccess = permission.hasWriteAccess(model);
|
||||
var hasReadAccess = permission.hasReadAccess(model);
|
||||
return !hasWriteAccess && !hasReadAccess;
|
||||
}, this);
|
||||
|
||||
return sharing.concat(noSharing);
|
||||
},
|
||||
|
||||
_renderOrganization: function () {
|
||||
var self = this;
|
||||
var organization = this._organization;
|
||||
var permission = this._sharePermissionModel;
|
||||
var hasWriteAccessAvailable = !self._isVisualization;
|
||||
var canChangeWriteAccess = permission.canChangeWriteAccess(organization);
|
||||
var hasWriteAccess = permission.hasWriteAccess(organization);
|
||||
var canChangeReadAccess = permission.canChangeReadAccess(organization);
|
||||
var hasReadAccess = permission.hasReadAccess(organization);
|
||||
|
||||
var view = new PermissionView({
|
||||
model: organization,
|
||||
permission: permission,
|
||||
avatar: organization.get('avatar_url'),
|
||||
description: _t('components.modals.publish.share.organization.desc'),
|
||||
name: _t('components.modals.publish.share.organization.title'),
|
||||
canChangeReadAccess: canChangeReadAccess,
|
||||
hasReadAccess: hasReadAccess,
|
||||
hasWriteAccessAvailable: hasWriteAccessAvailable,
|
||||
canChangeWriteAccess: canChangeWriteAccess,
|
||||
hasWriteAccess: hasWriteAccess,
|
||||
isSelected: hasReadAccess || hasWriteAccess
|
||||
});
|
||||
|
||||
this.$el.append(view.render().$el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_renderItem: function (model) {
|
||||
var self = this;
|
||||
var view;
|
||||
var o = model.get('model');
|
||||
var permission = this._sharePermissionModel;
|
||||
var hasWriteAccessAvailable = !self._isVisualization;
|
||||
var canChangeWriteAccess = permission.canChangeWriteAccess(model);
|
||||
var hasWriteAccess = permission.hasWriteAccess(model);
|
||||
var canChangeReadAccess = permission.canChangeReadAccess(model);
|
||||
var hasReadAccess = permission.hasReadAccess(model);
|
||||
var role = this._getRole(o);
|
||||
|
||||
if (model.id !== this._currentUserId) {
|
||||
view = new PermissionView({
|
||||
model: model,
|
||||
permission: permission,
|
||||
name: model.get('name') || model.get('username'),
|
||||
avatar: model.get('avatar_url'),
|
||||
role: role,
|
||||
users: o.users,
|
||||
canChangeReadAccess: canChangeReadAccess,
|
||||
hasReadAccess: hasReadAccess,
|
||||
hasWriteAccessAvailable: hasWriteAccessAvailable,
|
||||
canChangeWriteAccess: canChangeWriteAccess,
|
||||
hasWriteAccess: hasWriteAccess,
|
||||
isSelected: hasReadAccess || hasWriteAccess
|
||||
});
|
||||
|
||||
this.$el.append(view.render().$el);
|
||||
this.addView(view);
|
||||
}
|
||||
},
|
||||
|
||||
_getRole: function (d) {
|
||||
if (d.viewer !== undefined) {
|
||||
return d.viewer === true ? _t('components.modals.publish.share.role.viewer')
|
||||
: _t('components.modals.publish.share.role.builder');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./share-permission.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view.js');
|
||||
var $ = require('jquery');
|
||||
var UsersGroup = require('./users-group-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'model',
|
||||
'permission',
|
||||
'name',
|
||||
'canChangeReadAccess',
|
||||
'hasReadAccess',
|
||||
'hasWriteAccessAvailable',
|
||||
'canChangeWriteAccess',
|
||||
'hasWriteAccess',
|
||||
'isSelected'
|
||||
];
|
||||
|
||||
var OPTIONALS_OPT = [
|
||||
'description',
|
||||
'role',
|
||||
'avatar',
|
||||
'users'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Share-permission',
|
||||
|
||||
events: {
|
||||
'change .js-read': '_onChangeRead',
|
||||
'change .js-write': '_onChangeWrite',
|
||||
'mouseover .js-toggler.is-disabled': '_onHoverDisabledToggler',
|
||||
'mouseout .js-toggler': '_destroyTooltip',
|
||||
'mouseleave .js-toggler': '_destroyTooltip'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (opts[item] === undefined) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
_.each(OPTIONALS_OPT, function (item) {
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
name: this._name,
|
||||
avatar: this._avatar,
|
||||
role: this._role,
|
||||
users: this._users,
|
||||
description: this._description,
|
||||
canChangeReadAccess: this._canChangeReadAccess,
|
||||
hasReadAccess: this._hasReadAccess,
|
||||
hasWriteAccessAvailable: this._hasWriteAccessAvailable,
|
||||
canChangeWriteAccess: this._canChangeWriteAccess,
|
||||
hasWriteAccess: this._hasWriteAccess
|
||||
}));
|
||||
|
||||
if (this._users) {
|
||||
this._renderUsers();
|
||||
}
|
||||
|
||||
this.$el.toggleClass('is-selected', this._isSelected);
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderUsers: function () {
|
||||
var view = new UsersGroup({
|
||||
className: 'u-flex u-alignCenter',
|
||||
users: this._users
|
||||
});
|
||||
|
||||
this.$('.js-users').append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_onChangeWrite: function () {
|
||||
var p = this._permission;
|
||||
if (p.canChangeWriteAccess(this._model)) {
|
||||
if (p.hasWriteAccess(this._model)) {
|
||||
p.revokeWriteAccess(this._model);
|
||||
} else {
|
||||
p.grantWriteAccess(this._model);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onChangeRead: function () {
|
||||
var p = this._permission;
|
||||
if (p.canChangeReadAccess(this._model)) {
|
||||
if (p.hasReadAccess(this._model)) {
|
||||
p.revokeAccess(this._model);
|
||||
} else {
|
||||
p.grantReadAccess(this._model);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onHoverDisabledToggler: function (e) {
|
||||
var aclItem = this._permission.findRepresentableAclItem(this._model);
|
||||
var msg;
|
||||
var $el;
|
||||
|
||||
if (aclItem && !aclItem.isOwn(this._model)) {
|
||||
msg = this._inheritedAccessTooltipText(aclItem);
|
||||
$el = $(e.currentTarget);
|
||||
|
||||
this._tooltip = this._createTooltip({
|
||||
$el: $el,
|
||||
msg: msg
|
||||
});
|
||||
this._tooltip.showTipsy();
|
||||
}
|
||||
},
|
||||
|
||||
_createTooltip: function (opts) {
|
||||
return new TipsyTooltipView({
|
||||
el: opts.$el,
|
||||
title: function () {
|
||||
return opts.msg;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_destroyTooltip: function () {
|
||||
if (this._tooltip) {
|
||||
this._tooltip.hideTipsy();
|
||||
this._tooltip.destroyTipsy();
|
||||
}
|
||||
},
|
||||
|
||||
_inheritedAccessTooltipText: function (aclItem) {
|
||||
var type = aclItem.get('type');
|
||||
|
||||
switch (type) {
|
||||
case 'group':
|
||||
return _t('components.modals.publish.share.tooltip.group', {
|
||||
name: aclItem.get('entity').get('name')
|
||||
});
|
||||
case 'org':
|
||||
return _t('components.modals.publish.share.tooltip.org');
|
||||
default:
|
||||
console.error('Trying to display inherited access for an unrecognized type ' + type);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<div class="Share-permissionInfo">
|
||||
<div class="Share-permissionIcon">
|
||||
<% if (avatar) { %>
|
||||
<div class="Share-user Share-user--huge" style="background-image: url(<%- avatar %>)"></div>
|
||||
<% } else { %>
|
||||
<i class="CDB-IconFont CDB-IconFont-people"></i>
|
||||
<% } %>
|
||||
</div>
|
||||
<div>
|
||||
<div class="CDB-Text u-mainTextColor CDB-Size-medium is-semibold u-ellipsis">
|
||||
<%- name %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="CDB-Text u-mainTextColor u-tSpace CDB-Size-medium u-ellipsis">
|
||||
<% if (role) { %>
|
||||
<i class="Tag Tag--outline Tag--<%- role %> CDB-Text CDB-Size-small u-upperCase"><%- role %></i>
|
||||
<% } %>
|
||||
|
||||
<% if (users) { %>
|
||||
<div class="js-users"></div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
|
||||
<% if (description) { %>
|
||||
<div class="CDB-Text u-mainTextColor u-tSpace CDB-Size-medium u-ellipsis">
|
||||
<%- description %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Share-togglers">
|
||||
<% if (hasWriteAccessAvailable) { %>
|
||||
<div class="CDB-Text CDB-Size-medium u-rSpace--xl Share-toggler js-toggler <% if (!canChangeWriteAccess) { %>is-disabled<% } %>">
|
||||
<input class="CDB-Toggle u-iBlock js-write" type="checkbox"
|
||||
<% if (!canChangeWriteAccess) { %>disabled="disabled"<% } %>
|
||||
<% if (hasWriteAccess) { %> checked <% } %>
|
||||
/>
|
||||
<span class="u-iBlock CDB-ToggleFace"></span>
|
||||
<label class="u-iBlock u-altTextColor "><%- _t('components.modals.publish.share.toggle.write') %></label>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="CDB-Text CDB-Size-medium Share-toggler js-toggler <% if (!canChangeReadAccess) { %>is-disabled<% } %>">
|
||||
<input class="CDB-Toggle u-iBlock js-read" type="checkbox"
|
||||
<% if (hasReadAccess) { %> checked <% } %>
|
||||
<% if (!canChangeReadAccess) { %>disabled="disabled"<% } %>
|
||||
/>
|
||||
<span class="u-iBlock CDB-ToggleFace"></span>
|
||||
<label class="u-iBlock u-altTextColor "><%- _t('components.modals.publish.share.toggle.read') %></label>
|
||||
</div>
|
||||
</div>
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./share.tpl');
|
||||
var GrantableCollection = require('builder/data/grantables-collection');
|
||||
var SearchPaginationView = require('builder/components/pagination-search/pagination-search-view');
|
||||
var ListView = require('./share-list-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'currentUserId',
|
||||
'organization',
|
||||
'visDefinitionModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (!opts[item]) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
this._grantablesCollection = new GrantableCollection([], {
|
||||
currentUserId: this._currentUserId,
|
||||
organization: this._organization,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._sharePermissionModel = this._visDefinitionModel.getPermissionModel().clone();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
name: this._visDefinitionModel.get('name')
|
||||
}));
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._sharePermissionModel.acl.on('add remove reset change', this._onSave, this);
|
||||
this.add_related_model(this._sharePermissionModel.acl);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
|
||||
this._searchPaginationView = new SearchPaginationView({
|
||||
className: 'u-inner',
|
||||
listCollection: this._grantablesCollection,
|
||||
createContentView: function (opts) {
|
||||
return new ListView(_.extend({}, opts, {
|
||||
collection: self._grantablesCollection,
|
||||
currentUserId: self._currentUserId,
|
||||
sharePermissionModel: self._sharePermissionModel,
|
||||
organization: self._organization,
|
||||
isVisualization: self._visDefinitionModel.isVisualization()
|
||||
}));
|
||||
}
|
||||
});
|
||||
this.addView(this._searchPaginationView);
|
||||
this.$('.js-body').html(this._searchPaginationView.render().el);
|
||||
},
|
||||
|
||||
_onCancel: function () {
|
||||
this._modalModel.destroy();
|
||||
},
|
||||
|
||||
_onBack: function () {
|
||||
this._modalModel.destroy();
|
||||
this._onBack();
|
||||
},
|
||||
|
||||
_onSave: function () {
|
||||
var permission = this._visDefinitionModel.getPermissionModel();
|
||||
permission.overwriteAcl(this._sharePermissionModel);
|
||||
// Show loading here
|
||||
permission.save()
|
||||
.fail(this._searchPaginationView.showError.bind(this._searchPaginationView));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="js-body">
|
||||
|
||||
</div>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./user.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'username',
|
||||
'avatar'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (opts[item] === undefined) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
avatar: this._avatar,
|
||||
username: this._username
|
||||
}));
|
||||
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-avatar'),
|
||||
title: function () {
|
||||
return $(this).attr('data-title');
|
||||
}
|
||||
});
|
||||
|
||||
this.addView(tooltip);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<div class="Share-user u-rSpace js-avatar" style="background-image: url(<%- avatar %>)" data-title="<%- username %>"></div>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var template = require('./users-group.tpl');
|
||||
var UserView = require('./user-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'users'
|
||||
];
|
||||
|
||||
var MAX_USERS_TO_SHOW = 3;
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (!opts[item]) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
people: this._getRemainsPeople()
|
||||
}));
|
||||
this._renderUsers();
|
||||
return this;
|
||||
},
|
||||
|
||||
_getRemainsPeople: function () {
|
||||
return Math.max(0, this._users.length - MAX_USERS_TO_SHOW);
|
||||
},
|
||||
|
||||
_renderUsers: function () {
|
||||
_.each(this._users.slice(0, MAX_USERS_TO_SHOW), this._renderUser, this);
|
||||
},
|
||||
|
||||
_renderUser: function (user) {
|
||||
var view = new UserView({
|
||||
username: user.username,
|
||||
avatar: user.avatar_url
|
||||
});
|
||||
|
||||
this.$('.js-content').append(view.render().el);
|
||||
this.addView(view);
|
||||
}
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<div class="u-flex js-content"></div>
|
||||
<% if (people > 0) { %>
|
||||
<div class="u-secondaryTextColor CDB-Text CDB-Size-small u-lSpace">+ <%- people %></div>
|
||||
<% } %>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<div class="Publish-modalMenu u-tSpace-xl">
|
||||
<nav class="CDB-NavMenu u-inner">
|
||||
<ul class="CDB-Size-medium CDB-Text is-semibold js-menu"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="Tab-paneContent Publish-modalContent js-content"></div>
|
||||
@@ -0,0 +1,27 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./upgrade.tpl');
|
||||
|
||||
var CONTACT_LINK_TEMPLATE = _.template("<a href='mailto:<%- mail %>'><%- contact %></a>")({
|
||||
contact: _t('components.modals.publish.privacy.upgrade.contact'),
|
||||
mail: _t('components.modals.publish.privacy.upgrade.mail')
|
||||
});
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var html = template({
|
||||
title: _t('components.modals.publish.privacy.upgrade.title'),
|
||||
desc: _t('components.modals.publish.privacy.upgrade.desc', {
|
||||
contact: CONTACT_LINK_TEMPLATE
|
||||
})
|
||||
});
|
||||
this.$el.html(html);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="u-flex u-alignCenter">
|
||||
<div class="Upgrade-icon u-rSpace--xl">
|
||||
<svg width="16px" height="16px" viewBox="578 456 16 16">
|
||||
<g id="present" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(578.000000, 456.000000)">
|
||||
<path d="M10.0946667,4.4109223 C10.8886667,4.1368747 11.6846667,3.80147316 12.0073333,3.4715253 C12.7866667,2.67392407 12.7866667,1.37662907 12.0073333,0.579027844 C11.2513333,-0.193350099 9.934,-0.192668389 9.17866667,0.579027844 C8.74133333,1.0262299 8.29266667,2.36033726 8,3.37608583 C7.70666667,2.36033726 7.25866667,1.0262299 6.82133333,0.579027844 C6.066,-0.193350099 4.748,-0.192668389 3.99266667,0.579027844 C3.21266667,1.37662907 3.21266667,2.67392407 3.99266667,3.4715253 C4.316,3.80147316 5.11133333,4.1368747 5.90533333,4.4109223 L0.333333333,4.4109223 C0.149333333,4.4109223 0,4.56362544 0,4.75177753 L0,7.47861934 C0,7.66677142 0.149333333,7.81947456 0.333333333,7.81947456 L0.666666667,7.81947456 L0.666666667,15.6591448 C0.666666667,15.8472969 0.816,16 1,16 L15,16 C15.184,16 15.3333333,15.8472969 15.3333333,15.6591448 L15.3333333,7.81947456 L15.6666667,7.81947456 C15.8506667,7.81947456 16,7.66677142 16,7.47861934 L16,4.75177753 C16,4.56362544 15.8506667,4.4109223 15.6666667,4.4109223 L10.0946667,4.4109223 Z M6,6.95652174 L6,4.86956522 L10,4.86956522 L10,6.95652174 L6,6.95652174 L6,6.95652174 Z M10,7.65217391 L10,15.3043478 L6,15.3043478 L6,7.65217391 L10,7.65217391 L10,7.65217391 Z M9.80833133,1.08822938 C10.0510474,0.834996985 10.3727426,0.695652174 10.7169115,0.695652174 C11.0597961,0.695652174 11.3821334,0.834996985 11.6248495,1.08755946 C12.1250502,1.6101025 12.1250502,2.46023983 11.6248495,2.98278288 C11.2729754,3.34990286 9.79227867,3.85636766 8.66666667,4.17391304 C8.97102496,2.99953105 9.45581507,1.45534937 9.80833133,1.08822938 L9.80833133,1.08822938 Z M4.37515049,1.08822938 C4.6178666,0.834996985 4.94020387,0.695652174 5.28373064,0.695652174 C5.6266153,0.695652174 5.94895256,0.834996985 6.19166867,1.08755946 C6.54354282,1.45467944 7.02833293,2.99886112 7.33333333,4.17391304 C6.20707922,3.85569773 4.72638253,3.34990286 4.37515049,2.98278288 C3.87494984,2.46090976 3.87494984,1.61077243 4.37515049,1.08822938 L4.37515049,1.08822938 Z M0.666666667,4.86956522 L5.33333333,4.86956522 L5.33333333,6.95652174 L1,6.95652174 L0.666666667,6.95652174 L0.666666667,4.86956522 L0.666666667,4.86956522 Z M1.33333333,7.65217391 L5.33333333,7.65217391 L5.33333333,15.3043478 L1.33333333,15.3043478 L1.33333333,7.65217391 L1.33333333,7.65217391 Z M14.6666667,15.3043478 L10.6666667,15.3043478 L10.6666667,7.65217391 L14.6666667,7.65217391 L14.6666667,15.3043478 L14.6666667,15.3043478 Z M15.3333333,6.95652174 L15,6.95652174 L10.6666667,6.95652174 L10.6666667,4.86956522 L15.3333333,4.86956522 L15.3333333,6.95652174 L15.3333333,6.95652174 Z" id="Shape" fill="#9BC63B"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<!--
|
||||
<div class="Upgrade-body">
|
||||
<h2 class="CDB-Text CDB-Size-large"><%- title %></h2>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor "><%= desc %></p>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
Reference in New Issue
Block a user