/* ---------------------------------------------------------------------------------
file-start: js/jquery/jquery.lj.calendar.js
*/
/* ---------------------------------------------------------------------------------
file-start: js/jquery/jquery.lj.inlineCalendar.js
*/
/*!
* LiveJournal Inline calendar
*
* Copyright 2011, dmitry.petrov@sup.com
*
* http://docs.jquery.com/UI
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
*
* @overview Inline calendar widget.
*
* Widget can be attached to any existant markup.
*
* Date wildcards used:
* - %D - day ( 01 - 31 )
* - %M - month ( 01 - 02 )
* - %Y - year ( yyyy, e.g. 2002 )
* - %s - unix timestamp in ms
*
* Options:
* - dayRef: Format of the url that will be attached to each day in the calendar.
* - allRefs: Wether to attach links to days in the calendar.
* and override currentDate on success.
* Could be: true/false/Object {from: Date, to: Date} (all fields are not required)
* - activeFrom: Days before this will be inactive in calendar.
* - actoveUntil: Days after this willbe inactive incalendar.
* - startMonth: Widget will not allow to switch calendar pane to the month before this.
* - endMonth: Widget will not allow to switch calendar pane to the month after this.
* - startAtSunday: Wether to count sunday as the start of the week.
* - events: Object, containing events to show in the calendar. They will be rendered as links. Structure of the object:
* { "yyyy": { "mm1" : [ d1, d2, d3, d4 ], "mm2": [ d5, d6, d7 ] } }
*
* Events:
* - daySelected: Event is triggered when user selects a day in the calendar. The second parameter passed to the
* function is a Date object.
* - dateChange Event is triggered when user click on next or prev month/year button.
* - currentDateChange: Events is triggered when a new date is set in calendar as current.
*
* Consistent options ( setting these options is guaranteed to work correctly ):
* - currentDate, date - Set/get current date.
* - activeFrom, date - Set/get earliest active date.
* - activeUntil, date - Set/get last active date.
* - title, title - set calendar title.
* - events, obj - override current events object
*
* @TODO: move all service functions to the widget object and merge it with the view.
*
*/
(function( $, window ) {
var defaultOptions = {
dayRef: '/%Y/%M/%D',
monthRef: '', //the same, but for the months and year. Calendar will render link, if options are set
yearRef: '',
allRefs: false,
currentDate: new Date(),
//allow user to select dates in this range
activeUntil: null,
activeFrom: null,
//allow user to switch months between these dates
startMonth: new Date( 1900, 0, 1 ),
endMonth: new Date( 2050, 0, 1 ),
startAtSunday: !(LJ.ml('date.format.offset') !== '0') || false,
dateFormat: "%Y-%M-%D",
defaultTitle: "Calendar",
longMonth: false,
events: null, //object with events to show in the calendar
displayedMonth: null, //month displayed on the calendar. If not specified at
//startup currentDate is used instead.
dateChange: null,
selectors: {
table: 'table',
title: 'h5',
tbody: 'tbody',
month: '.cal-nav-month',
year: '.cal-nav-year',
monthSelect: '.cal-nav-month-select',
yearSelect: '.cal-nav-year-select',
prevMonth: '.cal-nav-month .cal-nav-prev',
nextMonth: '.cal-nav-month .cal-nav-next',
prevYear: '.cal-nav-year .cal-nav-prev',
nextYear: '.cal-nav-year .cal-nav-next',
monthLabel: '.cal-nav-month .cal-month',
yearLabel: '.cal-nav-year .cal-year'
},
classNames: {
container: '',
inactive : 'other',
future : 'other',
current : 'current',
weekend: 'weekend',
nextDisabled : 'cal-nav-next-dis',
prevDisabled : 'cal-nav-prev-dis',
cellHover : 'hover',
longMonth: 'sidebar-cal-longmonth'
},
//now, all lang variables are collected from Site.ml_text and should not be modified
mlPrefix: {
monthNamesShort: ['monthNames', 'date.month.{name}.short'],
monthNamesLong: ['monthNames', 'date.month.{name}.long'],
dayNamesShort: ['dayNames', 'date.day.{name}.short']
},
ml: {
monthNames: [ "january", "february", "march", "april", "may", "june", "july",
"august", "september", "october", "november", "december"],
dayNames: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'],
caption: "Calendar"
}
};
function getDateNumber( d, dropDays ) {
dropDays = dropDays || false;
var day = d.getDate().toString();
if( day.length === 1 ) { day = "0" + day; }
if( dropDays ) {
day = "";
}
var month = d.getMonth().toString();
if( month.length === 1 ) { month = "0" + month; }
return parseInt( d.getFullYear().toString() + month + day, 10);
}
function insideTimeRange( range, iDate ) {
return getDateNumber( iDate, true ) >= getDateNumber( range[0], true ) &&
getDateNumber( iDate, true ) <= getDateNumber( range[1], true );
}
function View(nodes, styles, o)
{
this.initialize = function (date) {
this.tbody = this.catchTableStructure(date);
};
this.modelChanged = function (monthDate, events, switcherStates)
{
var monthml = o.longMonth? o.ml.monthNamesLong : o.ml.monthNamesShort;
//we have a 30% speedup when we temporary remove tbody from dom
this.tbody.detach();
this.fillDates(monthDate, events);
for (var sws in switcherStates) {
nodes[sws][ (!switcherStates[sws]) ? 'addClass' : 'removeClass']( this.disabledStyle(sws) );
}
var monthText = o.monthRef
? $( '', {
href: LJ.Util.Date.format( monthDate, o.monthRef ),
text: monthml[ monthDate.getMonth() ] + (o.monthWithYear ? (' ' + monthDate.getFullYear()) : '')
} )
: monthml[ monthDate.getMonth() ];
var yearText = o.yearRef
? $( '', { href: LJ.Util.Date.format( monthDate, o.yearRef ), text: monthDate.getFullYear() } )
: monthDate.getFullYear();
nodes.monthLabel.empty().append( monthText );
nodes.yearLabel.empty().append( yearText );
this.tbody.appendTo( nodes.table );
};
this.catchTableStructure = function(date) {
var tbody = nodes.tbody[0];
nodes.daysCells = [];
nodes.daysSpans = [];
var row, rowsCount = tbody.rows.length, cell, cellsCount;
var toAdd = 6 - rowsCount;
var rowStr = '
*
* If you need direct access to template as a string, use $templateCache.
*
* Notice:
* By default required templates will be named in $templateCache as filename,
* e.g. `angular/widgets/social/twitter.ng.tmpl` will have name `twitter.ng.tmpl`
*
* If two files have same name, but located in different folders, e.g.
* `angular/widgets/social/v1/twitter.ng.tmpl` and `angular/widgets/social/v2/twitter.ng.tmpl`
* files will be named as a file and folder they are located in =>
* `v1/twitter.ng.tmpl` and `v2/twitter.ng.tmpl`
*
*/
angular.module('LJ.Templates', []).run(['$templateCache', function($templateCache) {
var templates = angular.copy( LJ.get('template') );
var temp = [];
function getDups(array, regexp) {
return array.filter(function (item) {
return item.match(regexp) && item;
});
}
function getContains(string1, string2) {
var index = 0;
for (var i = 0; i < string1.length; i++) {
if ( string2.indexOf( string1.substr(0, i + 1) ) !== -1 ) {
index = i + 1;
}
}
return string1.slice(0, index);
}
angular.forEach(templates, function (value, key) {
temp.push(key.split('').reverse().join(''));
});
// Convert templates to uniq names
temp.forEach(function (value) {
var match = value.match(/([\w\.]*)/),
name, regexp, dups, max, pathPattern;
if ( !(match && match[1]) ) {
return;
}
regexp = new RegExp( match[1].replace('.', '\\.') );
dups = getDups(temp, regexp);
max = value.length - 1;
dups.forEach(function (dup) {
var contains = getContains(value, dup);
var length = contains.length;
max = max > length ? length : max;
});
if (dups.length === 1) {
name = value.split('').reverse().join('');
templates[name.split('/').pop()] = templates[name];
delete templates[name];
return;
}
dups.push(value);
pathPattern = new RegExp('\\/?(\\w*.{' + max + '})$');
dups.forEach(function (item) {
var exec;
item = item.split('').reverse().join('');
exec = pathPattern.exec(item);
if (templates.hasOwnProperty(item) && (exec && exec[1]) ) {
templates[exec[1]] = templates[item];
if (exec[1] !== item) {
delete templates[item];
}
}
});
});
angular.forEach(templates, function (value, key) {
if ( key.indexOf('/') !== -1 ) {
console.warn('Template `%s` has been registered with path', key);
}
$templateCache.put(key, value);
});
}]);
/*
* Common directives
*/
angular.module('LJ.Directives', [])
/**
* Insert html and do not watch for changes
*/
.directive('ljHtml', ['$parse', function ($parse) {
return {
compile: function (element, attrs) {
var getter = $parse( attrs.ljHtml );
return function link(scope, element) {
element.html( getter(scope) || '' );
};
}
};
}])
/**
* Insert html and watch for changes
*/
.directive('ljHtmlLive', ['$parse', function ($parse) {
return {
scope: true,
restrict: 'A',
compile: function (element, attrs) {
var getValue = $parse(attrs.ljHtmlLive);
return function link(scope, element) {
scope.$watch(function () {
return getValue(scope);
}, function (value) {
element.html( value || '' );
});
};
}
};
}])
/**
* Replace ng-include root with it's content,
* if you have to just include template into the DOM.
* Notice: should be used in conjunction with ng-include
*
* @example
*
*
*/
.directive('includeReplace', function () {
return {
require: 'ngInclude',
link: function (scope, element) {
element.replaceWith( element.contents() );
}
};
})
/**
* Notice:
* lj-ml-compile could be only used on node without any other directives,
* otherwise their behavior will be duplicated (ng-click handlers for example)
*
* @example
*
*
*
* @example
*
*
*
* @example
*
*
*
* @example
*
*
*
* @example
*
* Html:
*
* Simple:
*
*
* Deferred:
*
*
*
* var mlDefer = $q.defer();
*
* // Notice:
* // Object with `promise` field is needed!
* // If promise is assigned directly, it will be resolved to `undefined`
* $scope.mlResolve = { promise: mlDefer.promise }
* mlDefer.resolve({ username: 'good' })
*/
.directive('ljMl', ['$parse', '$compile', function($parse, $compile) {
return {
link: function(scope, element, attrs) {
if ( attrs.hasOwnProperty('ljMlDynamic') ) {
attrs.$observe('ljMl', function () {
processMl(attrs.ljMl);
});
}
processMl(attrs.ljMl);
function processMl(mlVar) {
var resolve;
// ml has been processed yet
if ( attrs.mlParsed ) {
return;
}
// we don't need substitutions
if ( !attrs.ljMlResolve ) {
replaceMl(mlVar);
return;
}
resolve = $parse(attrs.ljMlResolve)(scope);
if ( !resolve.promise ) {
replaceMl(mlVar, resolve);
return;
}
// if object has promise key => wait for promise resolve
resolve.promise.then(function (data) {
replaceMl(mlVar, data);
});
}
// replace variable with value
function replaceMl(mlVar, dictionary) {
var ml = LJ.ml(objNotation( mlVar ), dictionary);
// attribute
if (attrs.ljMlAttr) {
element.attr( attrs.ljMlAttr, ml);
} else {
// html
element.html(ml);
}
if ( attrs.hasOwnProperty('ljMlCompile') ) {
// prevent cycled compilation of lj-ml directive
element.attr('ml-parsed', true);
$compile(element)(scope);
}
}
/**
* Allow object notation for ml
* @param {String} mlVar Variable value to check
* @return {String} Result ml variable value
*/
function objNotation(mlVar) {
if ( mlVar.indexOf('{') === -1 ) {
return mlVar;
}
var obj = scope.$eval(mlVar),
prop;
if ( obj && typeof obj === 'object' ) {
for (prop in obj) {
if ( obj.hasOwnProperty(prop) && obj[prop] ) {
return prop;
}
}
}
return mlVar;
}
}
};
}])
.directive('ljSwitchOff', ['$document', '$parse',
function ( $document , $parse ) {
/*
* When area outside of element is clicked or ESC is pressed
* 'showPopup' will be set to false and 'doSomething()' will be evaluated.
*
*
*
* Skip switching:
* Notice: now skipping of switch is working for all `lj-switch-off` directives (is not grouped)
* This element prevents switching via `lj-switch-off` directive
*
*/
return {
restrict: 'A',
link: function (scope, element, attrs) {
var state = $parse(attrs.ljSwitchOff),
_clicked = false;
element.on('click', function () {
_clicked = true;
});
$document
.on('keydown', keyPressed)
.on('click', '[lj-switch-off-skip]', preventSwitching)
.on('click', documentClick);
// cleanup
scope.$on('$destroy', function () {
$document
.off('keydown', keyPressed)
.off('click', '[lj-switch-off-skip]', preventSwitching)
.off('click', documentClick);
});
function close() {
if (attrs.ljSwitchOffAction) {
scope.$eval(attrs.ljSwitchOffAction);
}
state.assign(scope, false);
}
function preventSwitching() {
_clicked = true;
}
function documentClick(event) {
// right click in Firefox is closing the bubble
if (event.button === 2) {
return;
}
if ( !_clicked && state(scope, { $event: event }) ) {
scope.$apply(close);
}
_clicked = false;
}
function keyPressed(event) {
/** ESC */
if ( event.which === 27 && state(scope, { $event: event }) ) {
scope.$apply(close);
}
}
}
};
}]).
directive('ljDisabled', [function() {
/*
* Toggles disabled state for LiveJournal button
*
*
*
*
*/
return function (scope, element, attrs) {
scope.$watch(attrs.ljDisabled, function(value) {
element.prop('disabled', value);
element.parent().toggleClass('b-ljbutton-disabled', value);
});
};
}])
.directive('focusAndSelect', ['$timeout', function ($timeout) {
/*
* Focus and select an input or textarea when expression is true
*
*
*/
return {
restrict: 'A',
link: function (scope, element, attrs) {
var attr = attrs.focusAndSelect,
timeout = 50; // scroll can jump under 50ms
scope.$watch(attr, function (value, prev) {
if (value) {
$timeout(function () {
element[0].focus();
element[0].select();
}, timeout);
} else if (prev) {
// fix: blur for IE
element[0].blur();
}
});
}
};
}])
/**
* Debounced update of scope variable on changes
* [lj-debounce-delay]: option for set delay duration (in milliseconds). Default: 300
*
* @example
*
*
*
*
* js:
* angular.module('MyModule', ['LJ.Bubble'])
* .controller('MyCtrl', ['$scope', 'Bubble', function($scope, Bubble) {
*
* // Use Bubble.open to control bubble from JS.
* // You can pass jQuery node to reposition the bubble from the original placement.
* Bubble.open('myBubble', { 'some-stuff': 'optional' }, jQuery('.b-some-other-block'));
* }]);
*
* @todo
* Refactor detection of current bubble open
* Refactor disableClick option of directive
*
* @authors
* Artem Tyurin (artem.tyurin@sup.com)
* Valeriy Vasin (valeriy.vasin@sup.com)
*/
(function($) {
'use strict';
angular.module('LJ.Bubble', ['LJ.Templates', 'LJ.Directives', 'LJ.Ref'])
.factory('Bubble', ['$rootScope', '$compile', 'Ref', function ($rootScope, $compile, Ref) {
var factory = {},
// bubbles options
_options = {},
// body className flag
bubbleOpenClass = 'p-openpopup';
// currently opened bubble name
factory.current = null;
// we can change bubble node while opening bubble
factory.node = null;
/**
* Register bubble from service
* @example
* Bubble.register({ name: 'share', template: 'share.ng.tmpl' });
*/
factory.register = (function () {
// cache for registerd from service bubbles
var cache = {};
function register(opts, scope) {
if ( !opts || !opts.name || !opts.template ) {
throw new Error('Incorrect bubble options. You should provide name and template.', opts);
}
var name = opts.name;
// click is always disabled when register from factory
opts.disableClick = true;
// bubble has been registered before - increase counter
if ( cache[name] ) {
cache[name].count += 1;
return unregister.bind(null, name);
}
// create lj-bubble node with params
var bubbleNode = $('').attr('lj-bubble', JSON.stringify(opts));
// create new isolated scope for the bubble if we not provided it
var isScopeCreated = typeof scope === 'undefined';
if ( isScopeCreated ) {
scope = $rootScope.$new(true);
}
bubbleNode.appendTo('body');
$compile(bubbleNode)(scope);
cache[name] = {
count: 1,
node: bubbleNode,
scope: scope,
isScopeCreated: isScopeCreated
};
// unregistration function
return unregister.bind(null, name);
}
function unregister(name) {
var opts = cache[name];
if ( !opts ) {
// nothing to unregister
return;
}
opts.count -= 1;
// perform remove
if ( opts.count === 0 ) {
// do not destroy scope we have not created
if ( !opts.isScopeCreated ) {
opts.scope.$destroy();
}
opts.node.remove();
delete cache[name];
}
}
return register;
}());
/**
* Register bubble
* @param {String} name Bubble name
*/
factory._register = function (name, options) {
if ( _options.hasOwnProperty(name) ) {
console.warn('Warning: bubble with name `%s` has been registered before!', name);
return false;
}
var opts = angular.isDefined(options) ? angular.copy(options) : {};
// extend with default options
opts = angular.extend({
closeControl: true
}, opts);
_options[name] = opts;
};
factory._unregister = function (name) {
delete _options[name];
if (factory.current === name) {
factory.current = null;
}
};
/**
* Open bubble with provided name
* @param {String} name Bubble name
* @param {Object} [options] Options that will be available for bubble
* @param {jQuery|String} [node] jQuery: Node relative to which bubble will be positioned
* String: Registered ref id
*/
factory.open = function (name, options, node) {
if ( !_options.hasOwnProperty(name) ) {
throw new Error('Bubble `'+ name +'` can\'t be opened, it has not been registered yet.');
}
if ( options instanceof jQuery ) {
node = options;
options = {};
}
if ( typeof options === 'string' ) {
node = Ref.get(options);
options = {};
}
if (typeof options === 'object') {
factory.options(name, options);
}
if (node instanceof jQuery) {
factory.node = node;
}
if (typeof node === 'string') {
factory.node = Ref.get(node);
}
factory.current = name;
$rootScope.$broadcast('bubble:open' , name, options, node);
$rootScope.$broadcast('bubble:open:' + name, name, options, node);
angular.element('body').addClass(bubbleOpenClass);
};
/**
* Close all opened bubbles
* @param {String} [name] Close bubble
*/
factory.close = function () {
var name = factory.current,
options = _options[name],
option;
$rootScope.$broadcast('bubble:close' , name, options, factory.node);
$rootScope.$broadcast('bubble:close:' + name, name, options, factory.node);
// clear current bubble options
for (option in options) {
if ( options.hasOwnProperty(option) ) {
delete options[option];
}
}
factory.current = null;
factory.node = null;
angular.element('body').removeClass(bubbleOpenClass);
};
/**
* Set context for opened bubble
* @param {String} name Bubble name
* @param {Object} options Opening context (arguments)
* @return {Object} Bubble options
*/
factory.options = function (name, options) {
if (typeof options === 'undefined' || options === _options[name]) {
return _options[name];
}
angular.copy(options, _options[name]);
};
return factory;
}])
.directive('ljBubble', ['Bubble', '$parse', '$compile', '$timeout', '$templateCache',
function( Bubble, $parse, $compile, $timeout, $templateCache ) {
return {
scope: true,
link: function(scope, element, attrs) {
var options = $parse(attrs.ljBubble)(scope),
name = options.name,
bubble = $compile($templateCache.get('ljBubble.tmpl'))(scope),
arrow = bubble.find('.i-popup-arr'),
$window = angular.element(window);
scope.show = false;
Bubble._register(name, options);
scope.template = options.template || (name + '.html');
scope.bubble = {
name: name,
close: Bubble.close,
options: Bubble.options(name)
};
scope.arrow = {
vertical: 't', /* top (t) or bottom (b) */
horizontal: 'l' /* left (l) or right (r) */
};
scope.position = {
x: -9999,
y: -9999
};
scope.visibility = 'hidden';
scope.arrowClass = function () {
var opts = scope.bubble.options;
return opts.aside || options.aside ?
'i-popup-arr' + scope.arrow.horizontal + scope.arrow.vertical :
'i-popup-arr' + scope.arrow.vertical + scope.arrow.horizontal;
};
scope.$watch(function() {
return Bubble.current;
}, function(value) {
hideFromViewport();
$timeout(function() {
scope.show = value === name;
if (value && scope.show) {
$timeout(setPosition);
}
});
}, true);
function setPosition() {
// try to use top/left coords
var vertical = scope.arrow.vertical,
horizontal = scope.arrow.horizontal,
opts = scope.bubble.options;
scope.visibility = 'hidden';
// calculate positions initially
recalculatePositions();
// if there is not enough vertical space,
// bubble shows at the top of the target
if ( isOutOfViewportY() ) {
// we should change arrow position if possible
scope.arrow.vertical = vertical === 't' ?
(opts.alwaysTop ? 'b' : 't'):
(opts.alwaysBottom ? 't' : 'b');
}
// if there is not enough horizontal space,
// bubble shows at the right of the target
if ( isOutOfViewportX() ) {
// we should change arrow position
scope.arrow.horizontal = horizontal === 'r' ?
(opts.alwaysRight ? 'l' : 'r') :
(opts.alwaysLeft ? 'r' : 'l');
}
// top/left coords can't be used
if (
scope.arrow.horizontal !== horizontal ||
scope.arrow.vertical !== vertical
) {
// positions should be recalculated
// timeout is needed to apply arrow positions
$timeout(function () {
recalculatePositions();
scope.visibility = 'visible';
});
} else {
scope.visibility = 'visible';
}
}
function recalculatePositions() {
var el = Bubble.node || element,
centerX = el.offset().left + Math.floor(el.outerWidth() / 2),
forceX = scope.bubble.options.forceX || 0,
forceY = scope.bubble.options.forceY || 0;
if ( scope.bubble.options.aside || options.aside ) {
scope.position.x = scope.arrow.horizontal === 'r' ?
el.offset().left - bubble.outerWidth() - arrow.outerWidth() :
el.offset().left + el.outerWidth() + arrow.outerWidth();
scope.position.y = el.offset().top - arrow.position().top + ( el.outerHeight() - arrow.outerHeight() )/ 2 + forceY;
} else {
scope.position.x = centerX - arrow.position().left - Math.floor(arrow.outerWidth() / 2) - 2 + forceX;
scope.position.y = scope.arrow.vertical === 't' ?
el.offset().top + el.outerHeight() + arrow.outerHeight() :
el.offset().top - arrow.outerHeight() - bubble.outerHeight();
}
}
function hideFromViewport() {
scope.position.x = -9999;
}
function isOutOfViewportY() {
var scrollTop = $window.scrollTop();
if ( scope.position.y < scrollTop || scope.bubble.options.aside ) {
return true;
}
return scope.position.y + bubble.outerHeight() > scrollTop + $window.outerHeight();
}
function isOutOfViewportX() {
var scrollLeft = $window.scrollLeft();
if ( scope.position.x < scrollLeft || scope.bubble.options.aside ) {
return true;
}
return scope.position.x + bubble.outerWidth() > scrollLeft + $window.outerWidth();
}
/**
* Bubble target click handler
* @param {jQuery.Event} event Click event object
*/
function onTargetClick(event) {
event.preventDefault();
if ( Bubble.current === options.name ) {
return;
}
$timeout(function() {
Bubble.open(options.name);
});
}
function onResize() {
if (scope.show) {
$timeout(setPosition);
}
}
if ( !options.disableClick ) {
element.on('click', onTargetClick);
}
$window.on('resize', onResize);
if ( options.recalculateOnScroll ) {
$window.on('scroll', onResize);
}
$('body').append(bubble);
scope.$on('$destroy', function () {
element.off('click', onTargetClick);
$window.off('resize', onResize);
Bubble._unregister(name);
bubble.remove();
});
}
};
}]);
})(jQuery);
;
/* file-end: js/core/angular/bubble.js
----------------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------------
file-start: js/core/angular/ljUser.js
*/
/* ---------------------------------------------------------------------------------
file-start: js/core/angular/api.js
*/
/* ---------------------------------------------------------------------------------
file-start: js/core/angular/messages.js
*/
Site.page.template['angular/ljMessages.ng.tmpl'] = '
\n
\n \n
\n \n
\n \n \n ×\n \n
\n
\n';
LJ.injectStyle('/* System Message\n----------------------------------- */\n.b-msgsystem-wrapper {\n width: 100%;\n height: auto;\n }\n .b-msgsystem-wrapper.b-msgsystem-wrapper-fixed {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1001;\n overflow: hidden;\n padding: 0 0 10px;\n }\n .s-schemius .b-msgsystem-wrapper.b-msgsystem-wrapper-fixed {\n top: 60px;\n }\n\n .g-sensor .b-msgsystem-wrapper.b-msgsystem-wrapper-fixed,\n .mobile-msg .b-msgsystem-wrapper.b-msgsystem-wrapper-fixed {\n position: static;\n padding: 0;\n background-image: none;\n }\n\n.b-msgsystem {\n position: relative;\n min-height: 50px;\n padding: 10px 140px 10px 120px;\n font: 14px/1.357 Arial, Helvetica, sans-serif;\n }\n .b-msgsystem DD,\n .b-msgsystem DT,\n .b-msgsystem TH,\n .b-msgsystem TD,\n .b-msgsystem P,\n .b-msgsystem DIV,\n .b-msgsystem LI,\n .b-msgsystem PRE,\n .b-msgsystem CODE,\n .b-msgsystem KBD {\n font-size: 100%;\n }\n .b-msgsystem .i-ljuser-userhead {\n vertical-align: top !important;\n margin: 3px 0 0 0;\n }\n.b-msgsystem:after {\n position: absolute;\n bottom: -10px;\n left: 0;\n display: block;\n content: \'\';\n width: 100%;\n height: 10px;\n background-image: -webkit-linear-gradient(top, rgba(53, 99, 161, 0.3), rgba(53, 99, 161, 0));\n background-image: -moz-linear-gradient(top, rgba(53, 99, 161, 0.3), rgba(53, 99, 161, 0));\n background-image: -o-linear-gradient(top, rgba(53, 99, 161, 0.3), rgba(53, 99, 161, 0));\n background-image: linear-gradient(to bottom, rgba(53, 99, 161, 0.3), rgba(53, 99, 161, 0));\n -webkit-background-size: 10px 10px;\n -o-background-size: 10px 10px;\n background-size: 10px 10px;\n background-position: 0 100%;\n background-repeat: repeat-x;\n }\n .b-msgsystem-head {\n margin: 0 0 0.357em;\n padding: 0;\n font-size: 1.142em;\n font-weight: bold;\n }\n .b-msgsystem-wrap {\n line-height: 50px;\n min-height: 50px;\n }\n\n .b-msgsystem-head ~ .b-msgsystem-wrap {\n line-height: inherit;\n min-height: 0;\n }\n\n .b-msgsystem-body {\n display: inline-block;\n vertical-align: middle;\n line-height: 1.571;\n font-size: 1em;\n color: #787878;\n }\n\n.b-msgsystem .i-ljuser-username,\n.b-msgsystem .i-ljuser-username:link,\n.b-msgsystem .i-ljuser-username:visited {\n color: #0051B7 !important;\n }\n.b-msgsystem .i-ljuser-username:hover,\n.b-msgsystem .i-ljuser-username:active {\n color: #C00 !important;\n }\n\n.b-msgsystem-type-error {\n background: #FFEFEF no-repeat 2.7em 50% url(\'data:image/png;base64,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\');\n color: #CB1427;\n }\n .b-msgsystem-type-error + .b-msgsystem-type-error {\n border-top: 1px solid #FFDFDF;\n }\n.b-msgsystem-type-warning {\n background: #FCF8E3 no-repeat 2.7em 50% url(\'data:image/png;base64,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\');\n color: #C09853;\n }\n .b-msgsystem-type-warning + .b-msgsystem-type-warning {\n border-top: 1px solid #F9F1C7;\n }\n.b-msgsystem-type-success {\n background: #DFF0D8 no-repeat 2.7em 50% url(\'data:image/png;base64,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\');\n color: #468847;\n }\n .b-msgsystem-type-success + .b-msgsystem-type-success {\n border-top: 1px solid #BFE1B1;\n }\n.b-msgsystem-type-info {\n background: #D9EDF7 no-repeat 2.7em 50% url(\'data:image/png;base64,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\');\n color: #3A87AD;\n }\n .b-msgsystem-type-info + .b-msgsystem-type-info {\n border-top: 1px solid #B3DBEF;\n }\n\n.b-msgsystem-close {\n position: absolute;\n right: 0;\n top: 50%;\n width: 100px;\n margin: -0.714em 0 0;\n padding: 0 2em 0 0;\n text-align: right;\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n color: #000;\n }\n.b-msgsystem-close:hover {\n color: #C00;\n }\n .b-msgsystem-close-text {\n border-bottom: 1px dotted;\n }\n .b-msgsystem-close-ctrl {\n margin: 0 0 0 0.1em;\n vertical-align: middle;\n font-size: 1.3em;\n }\n\n\n/* xs-size screen (45em) ~ 630px */\n@media all and (max-width: 45em) {\n .b-msgsystem {\n padding: 10px 50px 10px 44px;\n background-position: 10px 10px;\n -webkit-background-size: 24px;\n -o-background-size: 24px;\n background-size: 24px;\n }\n\n .b-msgsystem-close {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n width: 50px;\n }\n .b-msgsystem-close-text {\n display: none;\n }\n .b-msgsystem-close-ctrl {\n position: absolute;\n top: 50%;\n left: 50%;\n margin: -0.6em 0 0 -0.2em;\n text-shadow: 0 -1px 0 rgba(255, 255, 255, 0.5);\n font-size: 1.7em;\n color: rgba(0, 0, 0, 0.5);\n }\n}\n\n/* Toggle */\n.b-msgsystem {\n top: 0;\n opacity: 1;\n -webkit-transition: 0.3s top, 0.3s opacity;\n -moz-transition: 0.3s top, 0.3s opacity;\n -ms-transition: 0.3s top, 0.3s opacity;\n -o-transition: 0.3s top, 0.3s opacity;\n transition: 0.3s top, 0.3s opacity;\n }\n.b-msgsystem-enter,\n.b-msgsystem-leave {\n top: -100px;\n opacity: 0;\n }\n\n');
//= require_ml component.messages.close
/*
* LiveJournal page messages
* https://conf.sup.com/pages/viewpage.action?pageId=6161047#id-1AddRemoveLinksandButtonsFRSCR-Warnings
*
* Available methods:
* - error
* - info
* - success
* - warning
*
* @example
* // show error message
* Message.error('Call the cops');
*
* // show error message with title
* Messages.error({ body: 'Call the cops!', title: 'Title is optional' });
*
* // General form of message showing (providing type directly)
* Messages.add({ type: 'error', body: 'Call the cops!', title: 'Title is optional' });
*
* @example
* Alternatively, you can use it from Perl controller:
*
* LJ::need_var(messages => [{
* title => 'hello',
* body => 'world',
* type => 'success'
* }]);
*/
(function() {
'use strict';
angular.module('LJ.Messages', ['LJ.Templates', 'LJ.Directives'])
.run(['$compile', '$rootScope', function($compile, $rootScope) {
$compile(
''
)($rootScope)
.appendTo('body');
}])
.factory('Messages', [function() {
var messages = [];
var factory = {
get: get,
add: add,
remove: remove,
clear: clear
};
var MESSAGE_TYPES = ['error', 'info', 'success', 'warning'];
// is needed for filtering of messages on the page
var _cache = {};
// show messages that come from server
if ( LJ.get('messages') ) {
LJ.get('messages').forEach(add);
}
/**
* Add message
* @param {Object} message Message object
* @param {String} message.type Message type: one of `MESSAGE_TYPES`
* @param {String} [message.title] Title of the message
* @param {String} message.body Body of the message
*/
function add(message) {
if ( MESSAGE_TYPES.indexOf(message.type) === -1 ) {
return;
}
// check if message with same body is currently on the page
if ( _cache[message.body] ) {
return;
}
_cache[message.body] = true;
messages.push(message);
return factory;
}
MESSAGE_TYPES.forEach(function (type) {
factory[type] = function (message) {
if ( typeof message === 'string' ) {
message = { body: message };
}
message.type = type;
add(message);
};
});
function clear() {
messages.length = 0;
_cache = {};
}
function remove(message) {
messages = messages.filter(function (_message) {
return _message !== message;
});
delete _cache[message.body];
return factory;
}
function get() {
return messages;
}
return factory;
}])
.directive('ljMessages', ['$timeout', 'Messages',
function( $timeout, Messages ) {
return {
templateUrl: 'ljMessages.ng.tmpl',
scope: true,
controllerAs: 'directive',
controller: ['$scope', function ($scope) {
var that = this;
this.messages = Messages.get();
this.close = Messages.remove;
$scope.$watchCollection(Messages.get, function (messages) {
that.messages = messages;
});
}]
};
}]);
})();
;
/* file-end: js/core/angular/messages.js
----------------------------------------------------------------------------------*/
/**
* @description Angular wrapper of LJ.Api
* @author Valeriy Vasin (valeriy.vasin@sup.com)
*
* Available options:
* - cache: turn on/off caching for the request
* - silent: turn on/off messages (error/info etc)
* - meta: result will be returned with meta information
* is needed to determine is response from cache or not at the moment
* If provided promise will be resolved with object that contains fields:
* - response: {*} - server response
* - fromCache: {Boolean} - is response from cache or not
*
* @example
* // fetch without caching
* Api.call('rpc.method', {param: 'value'});
*
* // fetch with caching
* Api.call('rpc.method', { param: 'value' }, { cache: true });
*
* // usage of meta information
* Api.call('ratings.journals_top', params, { cache: true, meta: true })
* .then(function (result) {
* var response = result.response;
*
* // cache users if result is from server
* if ( !result.fromCache ) {
*
* Users.Cache.add(
* response.journals.map( LJ.Function.get('user') )
* );
* }
* });
*
* // turn off messages for the request
* Api.call('rpc.method', {param: 'value'}, {silent: true});
*/
angular.module('LJ.Api', ['LJ.Messages'])
.factory('Api', ['$cacheFactory', '$rootScope', '$q', 'Messages',
function ( $cacheFactory, $rootScope, $q, Messages ) {
var factory = {},
cachePromises = $cacheFactory('LJApiPromises');
/**
* Call JSON Rpc API
*
* @param {String} method JSON Rpc method
* @param {Object} [params] JSON Rpc method params
* @param {Function} [callback] Callback to call after data recieved
* @param {Object} [options] Additional api options
* @param {Boolean} [options.cache] Cache options: turn on/off cache for request
* @param {Boolean} [options.silent] Turn on/off messages (error/info etc)
* @param {Boolean} [options.meta] If set to `true` - result will be returned with meta
* information, e.g. { response, fromCache }
*
* @return {Promise} Promise that will be resolved when data received
*/
factory.call = function (method, params, callback, options) {
var defer = $q.defer(),
defaults = { cache: false, silent: false, meta: false },
fromCache = false,
promise,
cacheKey;
// only `Object` and `null` are allowed, otherwise - empty object
if ( typeof params !== 'object' || params === null ) {
params = {};
}
if (typeof callback === 'object') {
options = callback;
callback = null;
}
options = angular.extend(defaults, options || {});
cacheKey = method + JSON.stringify(params);
if ( options.cache ) {
promise = cachePromises.get(cacheKey);
if (promise) {
fromCache = true;
}
}
if ( !fromCache ) {
promise = defer.promise;
LJ.Api.call(method, params, function (response) {
if (response.error) {
defer.reject(response.error);
} else {
defer.resolve(response);
}
$rootScope.$apply();
});
// save original promise
if ( options.cache ) {
cachePromises.put(cacheKey, promise);
}
}
// trigger events
LJ.Event.trigger('api:request:change', method, true);
promise.then(function() {
LJ.Event.trigger('api:request:change', method, false);
});
// show errors/messages
if ( !options.silent ) {
promise.then(function showMessage(response) {
var message = response.message;
if (typeof message === 'undefined') {
return;
}
message.body = message.content;
Messages.add(message);
}, function showErrorMessage(error) {
// no error message provided
if ( typeof error.message === 'undefined' ) {
return;
}
// Do not show internal api error
// See: lj.api.js#handleError
if ( error.code === 1 ) {
return;
}
Messages.error({ body: error.message });
});
}
// add meta information
if ( options.meta ) {
promise = promise.then(function (response) {
return {
response: response,
fromCache: fromCache
};
});
}
if (typeof callback === 'function') {
promise.then(callback);
}
return promise;
};
return factory;
}
]);
;
/* file-end: js/core/angular/api.js
----------------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------------
file-start: js/core/angular/users.js
*/
/* ---------------------------------------------------------------------------------
file-start: js/core/angular/options.js
*/
angular.module("LJ.Options",[]).factory("Options",[function(){return{create:function(a){function b(b,c){"undefined"==typeof c?angular.extend(a,b):a[b]=c}function c(b){return a[b]}function d(){return a}if("undefined"==typeof a&&(a={}),"object"!=typeof a)throw new TypeError("Options should be an object.");return{set:b,get:c,raw:d}}}}]);;
/* file-end: js/core/angular/options.js
----------------------------------------------------------------------------------*/
;(function () {
'use strict';
angular.module('Users', ['LJ.Api', 'LJ.Options'])
// Relations wrapper. Currently works through js/relations/relations.js
.factory('Relations', ['$q', '$timeout', 'UsersCache',
function ( $q, $timeout, UsersCache ) {
/**
* // add friend without options. State will be changed immediately
* Relations.toggleFriend('test', true);
*
* // unsubscribe, but don't change user props immediately
* Relations.toggleSubscription('test', false, { immediate: false });
*/
/**
* Helper that helps to interact with exiting relations
* @param {String} username Username of user we are changing relation
* @param {String} action Action to perform
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved after action is completed
*/
function _action(username, action, options) {
var defer = $q.defer(),
// immediate update user props
updateProps = {
addFriend: { is_invite_sent: true },
removeFriend: { is_friend: false },
subscribe: { is_subscribedon: true },
unsubscribe: { is_subscribedon: false },
join: { is_invite_sent: true },
leave: { is_member: false },
setBan: { is_banned: true },
setUnban: { is_banned: false }
},
// revert properties values if error happens
revertProps = {
addFriend: { is_invite_sent: false },
removeFriend: { is_friend: true },
subscribe: { is_subscribedon: false },
unsubscribe: { is_subscribedon: true },
join: { is_invite_sent: false },
leave: { is_member: true },
setBan: { is_banned: false },
setUnban: { is_banned: true }
},
// save current props
userProps = angular.copy( UsersCache.get(username) || {} );
if (typeof options === 'undefined') {
options = {};
}
if ( !options.wait ) {
UsersCache.update(username, updateProps[action] || {});
}
LJ.Event.trigger('relations.change', {
username: username,
action: action,
callback: function (data) {
$timeout(function () {
if (data.error) {
// rollback props
if ( !options.wait ) {
UsersCache.update( username, angular.extend(revertProps[action], userProps) );
}
defer.reject(data.error.message);
return;
}
var props = LJ.Object.pick(data,
'is_banned',
'is_friend',
'is_member',
'is_subscriber',
'is_subscribedon',
'is_friend_of',
'is_invite_sent'
);
// update user props
UsersCache.update(username, props);
defer.resolve(data);
});
}
});
return defer.promise;
}
/**
* Toggles subscription status of a user with provided username
*
* @param {String} username Username
* @param {Boolean} state State: subscribe (true) or unsubscribe (false)
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved with data after
* subscription status will be changed
*/
function toggleSubscription(username, state, options) {
var promise = _action(username, state ? 'subscribe' : 'unsubscribe', options);
// reset filter mask after unsubscribe
if ( !state ) {
promise.then(function () {
UsersCache.update(username, { filtermask: 0 });
});
}
return promise;
}
/**
* Toggles friend status of a user with provided username
*
* @param {String} username Username
* @param {Boolean} state State: add friend (true) or remove from friend (false)
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved with data after
* friend status will be changed
*/
function toggleFriend(username, state, options) {
return _action(username, state ? 'addFriend' : 'removeFriend', options);
}
/**
* Join/leave community
*
* @param {String} username Username
* @param {Boolean} state State: add friend (true) or remove from friend (false)
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved with data after action completeness
*/
function toggleMember(username, state, options) {
return _action(username, state ? 'join' : 'leave', options);
}
/**
* Ban/unban usern
*
* @param {String} username Username
* @param {Boolean} state State: ban (true) or unban user (false)
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved with data after action is completed
*/
function toggleBan(username, state, options) {
return _action(username, state ? 'setBan' : 'setUnban', options);
}
/**
* Ban/unban user everywhere
*
* @param {String} username Username
* @param {Boolean} state State: ban (true) or unban user (false) everywhere
* @param {Object} [options] Options
* @param {Boolean} [options.wait] Wait for response before updating user props
* @return {Promise} Promise that will be resolved with data after action is completed
*/
function toggleBanEverywhere(username, state, options) {
return _action(username, state ? 'banEverywhere' : 'unbanEverywhere', options);
}
return {
toggleFriend: toggleFriend,
toggleSubscription: toggleSubscription,
toggleMember: toggleMember,
toggleBan: toggleBan,
toggleBanEverywhere: toggleBanEverywhere
};
}])
// mask operations
.factory('Mask', function () {
var factory = {};
/**
* Notice: first bit of a mask is not used
*/
/**
* Notice:
* Our max filter id is equal 31: last bit (of 32) could be equal to 1
*
* JavaScript bitwise operators converts numbers to signed integers:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers
*
* If max bit is equal to 1, it will be converted to negative number.
*
* That is why we should manually interpret number as unsigned integer
*/
function unsigned(number) {
return number >>> 0;
}
/**
* Change mask accroding to provided operations
*
* @param {Number} mask Mask field
* @param {Object} actions Operations
* @param {Number|Array} [actions.add] Group id(s) to be added in
* @param {Number|Array} [actions.remove] Group id(s) to be removed from
*
* @return {Number} Updated mask
*/
factory.change = function (mask, actions) {
var add = actions.add,
remove = actions.remove;
// add
if (typeof add !== 'undefined') {
if ( !Array.isArray(add) ) {
add = [add];
}
mask = add.reduce(function (mask, id) {
return unsigned( mask | Math.pow(2, id) );
}, mask);
}
// remove
if (typeof remove !== 'undefined') {
if ( !Array.isArray(remove) ) {
remove = [remove];
}
mask = remove.reduce(function (mask, id) {
var filterMask = (Math.pow(2, 32) - 1) - Math.pow(2, id);
return unsigned( mask & filterMask );
}, mask);
}
return mask;
};
/**
* Check mask for inclusion in group
*
* @param {Number} mask Mask
* @param {Number} groupId Group id
* @return {Boolean} Result of the check
*/
factory.check = function (mask, groupId) {
var groupMask = Math.pow(2, groupId);
// convert to Boolean, because `&` returns number (group id)
return Boolean(mask & groupMask);
};
return factory;
})
/**
* Service is responsible for retrieving, caching and updating users
*
* // get by username
* UsersCache.get('test');
*
* // get all available users
* UsersCache.get();
*
* // get users that satisfy filtering function
* UsersCache.get( LJ.Function.get('is_community') );
*/
.factory('UsersCache', ['$q', '$cacheFactory', 'Options',
function ( $q, $cacheFactory, Options ) {
var _cache = $cacheFactory('users'),
options = Options.create({
journal: LJ.get('remoteUser')
}),
factory;
/**
* Add / update user(s) to cache
* @param {Array|Object} users Models to cache
* @return {Array|Object} Cached models
*/
function add(users) {
if (typeof users === 'undefined') {
return;
}
var hash = _cache.get( options.get('journal') ) || {},
result = users;
if ( !Array.isArray(users) ) {
users = [users];
}
users.forEach(function (user) {
if (!user || typeof user !== 'object' || typeof user.username === 'undefined') {
return;
}
var existed = hash[user.username];
hash[user.username] = existed ? angular.extend(existed, user) : user;
});
_cache.put(options.get('journal'), hash);
return result;
}
/**
* Check if user exists in cache
*
* @param {String} username Username
* @return {Boolean} Checking result
*/
function exists(username) {
var cachedUsers = _cache.get( options.get('journal') );
return Boolean( cachedUsers[username] );
}
/**
* User(s) getter
* @param {String|Function} [username] Get users by username if string passed,
* apply filtering function for all users if function passed,
* return all users, if nothing has been passed
* @return {Object|Array|NULL} Result
*
* @example
*
* // get by username
* get('test');
*
* // get all communities
* get( LJ.Function.get('is_community') );
*
* // get all users
* get();
*/
function get(username) {
var type = typeof username,
cache;
if (type === 'string') {
cache = _cache.get( options.get('journal') );
return cache && cache[username] ? cache[username] : null;
}
if (type === 'function') {
// get users with filter function
return _users(username);
}
if (type === 'undefined') {
// get all users
return _users();
}
throw new TypeError('Incorrect argument passed.');
}
/**
* Getter helper
* @param {Function} [filter] Filter function
* @return {Array} List of users
*/
function _users(filter) {
var cachedUsers = _cache.get( options.get('journal') ),
result = [],
username;
for (username in cachedUsers) {
if ( cachedUsers.hasOwnProperty(username) ) {
// skip users that not satisfy filter function
if (filter && !filter( cachedUsers[username] )) {
continue;
}
result.push(cachedUsers[username]);
}
}
return result;
}
/**
* Update user props
*
* @param {String} username Username of user to update
* @param {Object} props Props to update
* @return {Object} Updated user
*/
function update(username, props) {
var user = get(username) || { username: username };
angular.extend(user, props);
add(user);
return user;
}
factory = {
// cache users
add: add,
update: update,
// work with options
set: options.set,
// work with users
get: get,
exists: exists
};
return factory;
}])
.factory('Users', ['$q', '$timeout', 'Api', 'Mask', 'UsersCache', 'Options',
function ( $q, $timeout, Api, Mask, UsersCache, Options ) {
var rpc = {
friends: {
read: 'relations.list_friends',
readOne: 'relations.get_friend',
update: 'groups.update_users'
},
subscriptions: {
read: 'relations.list_subscriptions',
readOne: 'relations.get_subscription',
update: 'filters.update_users'
}
},
options = Options.create({
type: 'friends',
journal: LJ.get('remoteUser')
});
function _rpc(rpcType) {
return rpc[ options.get('type') ][rpcType];
}
/**
* Check if user should be extracted from cache according friend/subscription status
* @param {Object} user User model
* @return {Boolean} Result
*/
function _isUserCorrect(user) {
if (options.get('type') === 'subscriptions') {
return Boolean( user.is_subscribedon );
}
// we are able to add any user to group, that is why any know user is correct for groups
return true;
}
/**
* Mask getter
*
* @param {Object} user User object
* @param {Number} [value] Mask value to set
* @return {Number} Mask
*/
function mask(user, value) {
if (typeof value === 'undefined') {
return user[ maskField() ] || 1;
}
user[ maskField() ] = value;
}
function maskField() {
return options.get('type') === 'subscriptions' ? 'filtermask' : 'groupmask';
}
function fetchUser(username, fields, options) {
return Api.call('user.get', { target: username, fields: fields }, options)
.then(function (response) {
var user = response.user;
UsersCache.add(user);
return user;
});
}
/**
* Fetch friends of current user
* @param {Object} [fields] Fields to fetch,
* e.g. { groupmask: 1, is_personal: 1, is_identity: 1 }
* @param {Object} [apiOptions] Api options: {cache: true}
* @return {Promise} Promise that will be resolved with data
*/
function fetchFriends(fields, apiOptions) {
return Api.call('relations.list_friends', {
journal: options.get('journal'),
fields: fields
}, apiOptions).then( _setFlagAndCache('is_friend') );
}
function fetchGroupUsers(fields) {
return Api.call('groups.list_users', {
journal: options.get('journal'),
fields: fields
}).then(function (response) {
UsersCache.add(response.users);
return response;
});
}
function fetchSubscriptions(fields) {
return Api.call('relations.list_subscriptions', {
journal: options.get('journal'),
fields: fields
}).then( _setFlagAndCache('is_subscribedon') );
}
function fetchBanned(fields) {
return Api.call('relations.list_banned', {
journal: options.get('journal'),
fields: fields
}).then( _setFlagAndCache('is_banned') );
}
/**
* Fetch helper method: set flag for fetched users, cache and return them
* @return {Function} Function to use inside of .then()
*/
function _setFlagAndCache(flag) {
return function (response) {
var users = response.users;
users.forEach(
LJ.Function.set(flag, true)
);
UsersCache.add(users);
return users;
};
}
function fetchCount(type) {
return Api.call('relations.'+ type +'_count').then(function (response) {
return response.count;
});
}
/**
* Sync user(s) model(s) with server
*
* @param {Array|Object} users User model or collection of models
* @return {Promise} Promise that will be resolved after server response
*/
function sync(users) {
if ( !angular.isArray(users) ) {
users = [users];
}
if (users.length === 0) {
return $q.reject('You should provide users to sync.');
}
return Api.call(_rpc('update'), {
users: users,
journal: options.get('journal')
}).then(function (response) {
UsersCache.add(response.users);
return response;
});
}
/**
* Check is user listed in group/filter with `id`
*
* @method isUserInGroup
*
* @param {String} username Username
* @param {Number} id Filter/Group id (1..30)
* @return {Boolean} Result
*/
function isUserInGroup(username, id) {
var user = UsersCache.get(username);
return user ? Mask.check(mask(user), id) : false;
}
/**
* @todo Remove this method and replace it usage in controller
*
* Temp method for extracting only existing users by usernames
*
* @param {Array} usernames Username(s)
* @return {Array} Array of existing users
*/
function getExisting(usernames) {
return usernames.filter(UsersCache.exists)
.map(UsersCache.get)
.filter(_isUserCorrect);
}
/**
* Filter users that are in exact group
*
* @method fromGroup
*
* @param {Object} options Options for users filtering
* @param {Number} options.id Group id: (1..31)
* @param {Number} [options.limit] Limit of users to extract
* @param {String} [options.filter] Filter username string
* @return {Array} Array of users that are in group
*/
function fromGroup(options) {
var filter = (options.filter || '').toLowerCase(),
users = UsersCache.get(function (user) {
// filter friends/subscription
if ( !_isUserCorrect(user) ) {
return false;
}
if ( !Mask.check(mask(user), options.id) ) {
return false;
}
if ( filter && user.display_username.toLowerCase().indexOf(filter) === -1 ) {
return false;
}
return true;
});
if (options.limit) {
users = users.slice(0, options.limit);
}
return users;
}
/**
* Filter users that are out of exact group
*
* @method outOfGroup
*
* @param {Object} options Options for users filtering
* @param {Number} options.id Group id: (1..30)
* @param {Number} [options.limit] Limit of users to extract
* @param {String} [options.filter] Filter username string
* @return {Array} Array of users that are out of group
*/
function outOfGroup(options) {
var filter = (options.filter || '').toLowerCase(),
users = UsersCache.get(function (user) {
// filter friends/subscription
if ( !_isUserCorrect(user) ) {
return false;
}
if ( Mask.check(mask(user), options.id) ) {
return false;
}
if ( filter && user.display_username.toLowerCase().indexOf(filter) === -1 ) {
return false;
}
return true;
});
if (options.limit) {
users = users.slice(0, options.limit);
}
return users;
}
/**
* Add users to group
*
* @method addToGroup
*
* @param {Number} id Group id
* @param {String|Array} usernames Username(s) of users to add
* @return {Promise} Promise that will be resolved
* with synced users
*/
function addToGroup(id, usernames) {
if ( !Array.isArray(usernames) ) {
usernames = [usernames];
}
var users;
if (options.get('type') === 'subscriptions') {
// for subscriptions we should filter non-existed
users = getExisting(usernames);
} else {
users = usernames.map(function (username) {
return UsersCache.get(username) || { username: username };
});
}
users.forEach(function (user) {
mask(user, Mask.change(mask(user), { add: id }));
});
return sync(users);
}
/**
* Remove users from group
*
* @method removeFromGroup
*
* @param {Number} id Group id
* @param {String|Array} usernames Username(s) of users to remove
* @param {Object} [options] Options
* @param {Boolean} [options.silent] Remove users from group without sync
*
* @return {Promise|Undefined} Promise that will be resolved
* with synced users or Undefined, if options.silent provided
*/
function removeFromGroup(id, usernames, options) {
if ( !Array.isArray(usernames) ) {
usernames = [usernames];
}
var existing = getExisting(usernames);
// update mask
existing.forEach(function (user) {
mask(user, Mask.change(mask(user), { remove: id }));
});
// return without sync if silent
if (options && options.silent) {
return;
}
return sync(existing);
}
/**
* Set alias for user
*
* @param {String} username Username
* @param {String} value User alias
* @return {Promise} Promise that will be resolved with data when complete
*/
function alias(username, value) {
UsersCache.update(username, { alias: value });
return Api.call('user.alias_set', { target: username, alias: value });
}
/**
* Sort helper
*
* @param {String} prop Property. Available: username or display_username
* @return {Function} Comparator function
*
* @example
*
* users.sort( Users.comparator('username') );
*/
function comparator(prop) {
return function (a, b) {
return a[prop].toLowerCase().localeCompare(
b[prop].toLowerCase()
);
};
}
/**
* Patched version of options.set
*
* If we set journal for users it should also set journal for cache
*/
function set() {
var old = options.get('journal'),
journal;
options.set.apply(null, arguments);
journal = options.get('journal');
// update journal of cache if it changed
if ( journal !== old ) {
UsersCache.set('journal', journal);
}
}
return {
USERHEAD_FIELDS: {
alias: 1,
journal_url: 1,
profile_url: 1,
userhead_url: 1,
is_invisible: 1,
journaltype: 1
},
// options
set: set,
get: options.get,
Cache: UsersCache,
// work with server
fetchUser: fetchUser,
fetchBanned: fetchBanned,
fetchFriends: fetchFriends,
fetchGroupUsers: fetchGroupUsers,
fetchSubscriptions: fetchSubscriptions,
fetchCount: fetchCount,
sync: sync,
alias: alias,
isUserInGroup: isUserInGroup,
getExisting: getExisting,
fromGroup: fromGroup,
outOfGroup: outOfGroup,
addToGroup: addToGroup,
removeFromGroup: removeFromGroup,
comparator: comparator
};
}]);
}());
;
/* file-end: js/core/angular/users.js
----------------------------------------------------------------------------------*/
Site.page.template['angular/ljUser.ng.tmpl'] = '\n';
angular.module("LJ.User",["LJ.Api","LJ.Templates","Users"]).factory("ljUser",["$rootScope","Api","$q","$templateCache","$compile","$timeout","Users",function(a,b,c,d,e,f,g){function h(a){var b=c.defer(),d=g.Cache.get(a);return d&&d.userhead_url?(b.resolve(d),b.promise):g.fetchUser(a,g.USERHEAD_FIELDS,{cache:!0})}function i(b,d){var i=c.defer(),l=a.$new();return h(b).then(function(){var a;l.user=angular.extend({},g.Cache.get(b),d||{}),a=e(k)(l),f(function(){i.resolve(j.empty().append(a).html()),l.$destroy()})}),i.promise}var j=angular.element(""),k=d.get("ljUser.ng.tmpl");return{prepare:h,get:i}}]).directive("ljUserDynamic",["$parse","Users","ljUser",function(a,b,c){return{templateUrl:"ljUser.ng.tmpl",replace:!0,scope:!0,compile:function(d,e){var f=a(e.ljUserDynamic),g=a(e.ljUserDynamicOptions);return function(a){var d=f(a),e=g(a);a.user=angular.extend({username:d,display_username:d},e||{}),c.prepare(d).then(function(){a.$watch(function(){return b.Cache.get(d)},function(b){angular.extend(a.user,b)},!0)})}}}}]);;
/* file-end: js/core/angular/ljUser.js
----------------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------------
file-start: js/settings/services/filters/filters.js
*/
;(function () {
'use strict';
angular.module('GroupsAndFilters.Services.Filters', ['LJ.Api', 'LJ.Options', 'Users'])
// filter for output filters and groups
.filter('filtersOrder', function () {
return function filtersOrder(filters) {
// copy array to prevent digest loop during sort
var copy = filters.slice(0);
copy.sort(function (a, b) {
if ( a.id === 31 ) {
return -1;
}
if (b.id === 31) {
return 1;
}
var aName = a.name.toLowerCase(),
bName = b.name.toLowerCase();
return aName > bName ? 1 : -1;
});
// return sorted copy
return copy;
};
})
.factory('FilterGroupFactory', ['$q', '$timeout', 'Api', 'Options', 'Users',
function ( $q, $timeout, Api, Options, Users ) {
/**
* Filter/Group factory
*
* @class FilterGroupFactory
* @type {Angular.Service}
* @constructor
* @private
*/
function Factory() {
angular.extend(
this,
{
MAX_COUNT: 31,
// filters cache
filters: [],
// API key: filters/groups
key: 'filters',
// RPC endpoints: should be redefined
rpc: {}
},
Options.create({
journal: LJ.get('remoteUser')
})
);
}
/**
* Get filter JSON params, exclude not needed
*
* @method toJSON
* @param {Object} filter Filter object
* @return {Object} Filtered fields
*/
Factory.prototype.toJSON = function (filter) {
var json = angular.copy(filter);
delete json.checked;
delete json.users;
return json;
};
/**
* Generate id for new filter/group based on existed:
* next free id (that is not used by any existed filter)
*
* @method nextId
* @return {Number} Generated next id
*/
Factory.prototype.nextId = function () {
var id = null,
ids = this.filters.map( LJ.Function.get('id') ),
// id = 0 - is not used
i = 1,
max = this.MAX_COUNT;
while (i < max && id === null) {
if ( ids.indexOf(i) === -1) {
id = i;
}
i += 1;
}
return id;
};
/**
* Sync current state of filter(s) with server
*
* @method sync
* @param {Array|Object} filters Filter instance(s)
*/
Factory.prototype.sync = function (filters) {
var params = {};
// wrap single filter to array
if ( !angular.isArray(filters) ) {
filters = [filters];
}
params[this.key] = filters.map(this.toJSON);
params.journal = this.get('journal');
return Api.call(this.rpc.update, params);
};
/**
* Fetch filters from server
*
* @method fetch
*
* @param {Object} [options] Request option
* @param {Boolean} [options.cache=false] Cache request or not
*
* @return {Promise} Promise that will be resolved with filters data
*/
Factory.prototype.fetch = function (options) {
var that = this,
params = {
journal: this.get('journal')
};
options = angular.extend(options || {}, { cache: false });
return Api.call(this.rpc.read, params, options).then(function (response) {
// save fetched filters
that.filters = response[that.key];
return that.filters;
});
};
/**
* Check is filter/group name uniq
* @param {String} name Filter/group name to check
* @return {Boolean} Check result
*/
Factory.prototype._isNameUniq = function (name) {
var names = this.filters.map(function (filter) {
return filter.name.toLowerCase();
});
name = name.toLowerCase();
return names.indexOf(name) === -1;
};
/**
* Create filter
*
* @method create
* @param {String} name Filter name
* @return {Promise} Promise that will be resolved with created
* filter data
*/
Factory.prototype.create = function (name) {
var filter;
name = name.trim();
if ( name.length === 0 ) {
return $q.reject(
this.key === 'filters' ?
LJ.ml('api.error.filters.filter_name_not_specified') :
LJ.ml('api.error.groups.group_name_not_specified')
);
}
// check if filter name is uniq
if ( !this._isNameUniq(name) ) {
return $q.reject(
this.key === 'filters' ?
LJ.ml('api.error.filters.filter_already_exist', { name: name }) :
LJ.ml('api.error.groups.group_already_exist', { name: name })
);
}
filter = {
id: this.nextId(),
name: name,
users: [],
public: false,
journal: this.get('journal')
};
this.filters.push(filter);
return Api.call(this.rpc.create, filter);
};
/**
* Remove filter(s)
*
* @method remove
* @param {Object|Object[]} filters Filters to remove
* @return {Promise} Promise that will be resolved
* with removed filters data
*/
Factory.prototype.remove = function (filters) {
var that = this,
params = {};
filters = Array.isArray(filters) ? filters : [filters];
params[this.key] = filters.map( LJ.Function.get('id') );
params.journal = this.get('journal');
// splice filters
filters.forEach(function (filter) {
that.filters.splice( that.filters.indexOf(filter), 1 );
});
return Api.call(this.rpc.remove, params);
};
/**
* Get filter/group count
* @method getCount
* @return {Promise} Promise that will be resolved when server respond
*/
Factory.prototype.getCount = function () {
return Api.call(this.rpc.count, {
journal: this.get('journal')
}).then( LJ.Function.get('count') );
};
/**
* Get filters/groups for user
* @param {String} username Username of the user
* @return {Array} Array of filters
*/
Factory.prototype.by = function (username) {
var user = Users.Cache.get(username);
if ( !user ) {
return [];
}
return this.filters.filter(function (filter) {
return Users.isUserInGroup(username, filter.id);
});
};
return Factory;
}
])
.factory('Filter', ['FilterGroupFactory', function (Factory) {
/**
* @class Filter
* @extends FilterGroupFactory
* @constructor
*/
function Filter() {
/**
* RPC endpoints
* @property {Object} rpc
*
* @property {String} rpc.create Create filter endpoint
* @property {String} rpc.read Read filters endpoint
* @property {String} rpc.update Update filter(s) endpoint
* @property {String} rpc.remove Remove filter(s) endpoint
* @property {String} rpc.count Count filters endpoint
*/
this.rpc = {
create: 'filters.create',
read: 'filters.list',
update: 'filters.update',
remove: 'filters.remove',
count: 'filters.count'
};
/**
* API key: filters/groups
* @property {String} key
*/
this.key = 'filters';
}
Filter.prototype = new Factory();
/**
* Update privacy for filter(s)
*
* @method _setPrivacy
* @private
* @param {Objects[]|Object} filters Filter(s) object(s) to set privacy
* @param {String} privacy Privacy to be set: 'public' or 'private'
* @return {Promise} Promise that will be resolved when sync is done
*/
Filter.prototype._setPrivacy = function (filters, privacy) {
filters = Array.isArray(filters) ? filters : [filters];
filters.forEach( LJ.Function.set('public', privacy === 'public') );
return this.sync(filters);
};
/**
* Update filter(s) privacy to private
*
* @method private
* @param {Object|Object[]} filters Filter(s) to update
* @return {Promise} Promise that will be resolved
* with update filters data
*/
Filter.prototype.private = function (filters) {
return this._setPrivacy(filters, 'private');
};
/**
* Update filter(s) privacy to public
*
* @method public Set privacy to public
* @param {Object|Object[]} filters Filter(s) to update
* @return {Promise} Promise that will be resolved
* with update filters data
*/
Filter.prototype.public = function (filters) {
return this._setPrivacy(filters, 'public');
};
return new Filter();
}])
.factory('Group', ['FilterGroupFactory', function (Factory) {
/**
* @class Group
* @extends FilterGroupFactory
* @constructor
*/
function Group() {
/**
* RPC endpoints
* @property {Object} rpc
*
* @property {String} rpc.create Create group endpoint
* @property {String} rpc.read Read groups endpoint
* @property {String} rpc.update Update group(s) endpoint
* @property {String} rpc.remove Remove group(s) endpoint
* @property {String} rpc.count Count groups endpoint
*/
this.rpc = {
create: 'groups.create',
read: 'groups.list',
update: 'groups.update',
remove: 'groups.remove',
count: 'groups.count'
};
/**
* Key for group and filter distinction
*
* @property {String} key
* @type {String}
*/
this.key = 'groups';
}
Group.prototype = new Factory();
return new Group();
}]);
}());
;
/* file-end: js/settings/services/filters/filters.js
----------------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------------
file-start: js/settings/directives/filtersFor.js
*/
LJ.injectStyle('.b-filterset{max-width:290px;font:14px/1.4 Arial,sans-serif;color:#000}.b-filterset DD,.b-filterset DT,.b-filterset TH,.b-filterset TD,.b-filterset P,.b-filterset DIV,.b-filterset SPAN,.b-filterset EM,.b-filterset STRONG,.b-filterset B,.b-filterset I,.b-filterset LI,.b-filterset LABEL,.b-filterset PRE,.b-filterset CODE,.b-filterset KBD{font-size:100%}.b-filterset .i-ljuser-userhead{vertical-align:top!important;margin:1px 0 0!important;border:none!important}.b-filterset .i-ljuser-username:link,.b-filterset .i-ljuser-username:visited,.b-filterset .i-ljuser-username:hover,.b-filterset .i-ljuser-username:active{color:#0051B7!important;text-decoration:none!important;border:none!important}.b-filterset .b-filterset-title{font-weight:700!important}.b-filterset .b-filterset-title,.b-filterset .b-filterset-subtitle{margin:0 1.142em .5em 0!important;color:#222!important}.b-filterset-list{position:relative;z-index:1;overflow:auto;max-height:14em;min-width:220px;margin:0 0 .3em;padding:0;background-attachment:scroll;-moz-background-clip:border-box;background-clip:border-box;background-image:-webkit-radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.1),transparent),-webkit-radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.1),transparent);background-image:-o-radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.1),transparent),-o-radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.1),transparent);background-image:-moz-radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.1),transparent),-moz-radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.1),transparent);background-image:radial-gradient(farthest-side at 50% 0,rgba(0,0,0,.1),transparent),radial-gradient(farthest-side at 50% 100%,rgba(0,0,0,.1),transparent);-moz-background-origin:padding-box;background-origin:padding-box;background-position:0 0,0 100%;background-repeat:no-repeat;-moz-background-size:100% 7px;background-size:100% 7px}.b-filterset-list:before,.b-filterset-list:after{position:relative;z-index:-1;display:block;content:\"\";height:14px;margin:0 0 -14px;background:-webkit-linear-gradient(top,#F4F5F6,#F4F5F6 30%,rgba(244,245,246,0)) 0 0;background:-moz-linear-gradient(top,#F4F5F6,#F4F5F6 30%,rgba(244,245,246,0)) 0 0;background:-o-linear-gradient(top,#F4F5F6,#F4F5F6 30%,rgba(244,245,246,0)) 0 0;background:linear-gradient(to bottom,#F4F5F6,#F4F5F6 30%,rgba(244,245,246,0)) 0 0}.b-filterset-list:after{margin:-14px 0 0;background:-webkit-linear-gradient(top,rgba(228,229,233,0),#E4E5E9 70%,#E4E5E9) 0 0;background:-moz-linear-gradient(top,rgba(228,229,233,0),#E4E5E9 70%,#E4E5E9) 0 0;background:-o-linear-gradient(top,rgba(228,229,233,0),#E4E5E9 70%,#E4E5E9) 0 0;background:linear-gradient(to bottom,rgba(228,229,233,0),#E4E5E9 70%,#E4E5E9) 0 0}.b-filterset-list LI{margin:0;padding:0;list-style-type:none}.b-filterset-list LI.loading{background:url(/img/preloader/preloader-blue-gray.gif?v=16423) no-repeat 100% 50%}.b-filterset-list LABEL{display:block;white-space:nowrap;line-height:1.333;font-size:.857em}.b-filterset-list LABEL:hover{background:#7292BD;color:#FFF}.b-filterset-addnew-input{display:block;margin:0 0 .5em;white-space:nowrap;line-height:1.333;font-size:.857em}.b-filterset-addnew{margin:.5em 0 0}.b-filterset-pseudo{font-size:.857em;color:#0051B7}.b-filterset-submit{text-align:right}.b-filterset-loader:after,.b-filterset-loader:before{display:inline-block;visibility:hidden;content:\'\';width:21px;height:21px;margin:-2px 0 0 4px;background:url(/img/preloader/preloader-blue-gray.gif?v=16423) no-repeat 50% 50%;vertical-align:middle}.b-filterset-loader-before:after,.b-filterset-loader-after:before{display:none}.b-filterset-loading .b-filterset-loader-after:after,.b-filterset-loading .b-filterset-loader-before:before{visibility:visible}');
Site.page.template['angular/controlstrip/filters.ng.tmpl'] = '