Your IP : 18.119.110.128


Current Path : /var/www/axolotl/data/www/axolotl.ru/www/bitrix/js/im/model/src/
Upload File :
Current File : /var/www/axolotl/data/www/axolotl.ru/www/bitrix/js/im/model/src/notifications.js

/**
 * Bitrix Messenger
 * Notifications model (Vuex Builder model)
 *
 * @package bitrix
 * @subpackage im
 * @copyright 2001-2021 Bitrix
 */

import { Vue } from 'ui.vue';
import { VuexBuilderModel } from 'ui.vue.vuex';
import { Utils } from 'im.lib.utils';
import { Type } from 'main.core';
import { NotificationTypesCodes } from 'im.const';

class NotificationsModel extends VuexBuilderModel
{
	getName()
	{
		return 'notifications';
	}

	getState()
	{
		return {
			collection: [],
			searchCollection: [],
			chat_id: 0,
			total: 0,
			host: this.getVariable('host', location.protocol+'//'+location.host),
			unreadCounter: 0,
			schema: {}
		}
	}

	getElementState()
	{
		return {
			id: 0,
			authorId: 0,
			date: new Date(),
			text: '',
			sectionCode: NotificationTypesCodes.simple,
			textConverted: '',
			title: '',
			unread: false,
			display: true,
			settingName: 'im|default',
		};
	}

	getGetters()
	{
		return {
			get: state => () =>
			{
				return state.collection;
			},
			getById: state => (notificationId) =>
			{
				if (Type.isString(notificationId))
				{
					notificationId = parseInt(notificationId);
				}

				const existingItem = this.findItemInArr(state.collection, notificationId);
				if (!existingItem.element)
				{
					return false;
				}

				return existingItem.element;
			},
			getSearchItemById: state => (notificationId) =>
			{
				if (Type.isString(notificationId))
				{
					notificationId = parseInt(notificationId);
				}

				const existingItem = this.findItemInArr(state.searchCollection, notificationId);
				if (!existingItem.element)
				{
					return false;
				}

				return existingItem.element;
			},
			getBlank: state => params =>
			{
				return this.getElementState();
			},
		}
	}

	getActions()
	{
		return {
			set: (store, payload) =>
			{
				const result = {
					notification: [],
				};

				if (payload.notification instanceof Array)
				{
					result.notification = payload.notification.map(notification => this.prepareNotification(notification, { host: store.state.host }));
				}

				if (Type.isNumber(payload.total) || Type.isString(payload.total))
				{
					result.total = parseInt(payload.total);
				}

				store.commit('set', result);
			},
			setSearchResults: (store, payload) =>
			{
				const result = {
					notification: [],
				};

				if (!(payload.notification instanceof Array))
				{
					return false;
				}

				// we don't need validation for the local results
				if (payload.type === 'local')
				{
					result.notification = payload.notification;
				}
				else
				{
					result.notification = payload.notification.map(notification => this.prepareNotification(notification, { host: store.state.host }));
				}

				store.commit('setSearchResults', {
					data: result
				});
			},
			deleteSearchResults: (store, payload) =>
			{
				store.commit('deleteSearchResults');
			},
			setCounter: (store, payload) =>
			{
				if (Type.isNumber(payload.unreadTotal) || Type.isString(payload.unreadTotal))
				{
					const unreadCounter = parseInt(payload.unreadTotal);
					store.commit('setCounter', unreadCounter);
				}
			},
			setTotal: (store, payload) =>
			{
				if (Type.isNumber(payload.total) || Type.isString(payload.total))
				{
					store.commit('setTotal', payload.total);
				}
			},
			add: (store, payload) =>
			{
				const addItem = this.prepareNotification(payload.data, { host: store.state.host });
				addItem.unread = true;

				const existingItem = this.findItemInArr(store.state.collection, addItem.id);
				if (!existingItem.element)
				{
					store.commit('add', {
						data: addItem,
					});

					store.commit('setTotal', store.state.total + 1);
				}
				else
				{
					store.commit('update', {
						index: existingItem.index,
						fields: Object.assign({}, payload.fields),
					});
				}
			},
			updatePlaceholders: (store, payload) =>
			{
				if (payload.items instanceof Array)
				{
					payload.items = payload.items.map(notification => this.prepareNotification(notification));
				}
				else
				{
					return false;
				}

				store.commit('updatePlaceholders', payload);

				return true;
			},
			clearPlaceholders: (store, payload) =>
			{
				store.commit('clearPlaceholders', payload);
			},
			update: (store, payload) =>
			{
				const existingItem = this.findItemInArr(store.state.collection, payload.id);
				if (existingItem.element)
				{
					store.commit('update', {
						index: existingItem.index,
						fields: Object.assign({}, payload.fields),
					});
				}

				if (payload.searchMode)
				{
					const existingItemInSearchCollection = this.findItemInArr(store.state.searchCollection, payload.id);
					if (existingItemInSearchCollection.element)
					{
						store.commit('update', {
							searchCollection: true,
							index: existingItemInSearchCollection.index,
							fields: Object.assign({}, payload.fields),
						});
					}
				}
			},
			read: (store, payload) =>
			{
				for (const notificationId of payload.ids)
				{
					const existingItem = this.findItemInArr(store.state.collection, notificationId);
					if (!existingItem.element)
					{
						return false;
					}

					store.commit('read', {
						index: existingItem.index,
						action: !payload.action,
					});
				}
			},
			readAll: (store, payload) =>
			{
				store.commit('readAll');
			},
			delete: (store, payload) =>
			{
				const existingItem = this.findItemInArr(store.state.collection, payload.id);
				if (existingItem.element)
				{
					store.commit('delete', {
						searchCollection: false,
						index: existingItem.index,
					});

					store.commit('setTotal', store.state.total - 1);
				}

				if (payload.searchMode)
				{
					const existingItemInSearchCollection = this.findItemInArr(store.state.searchCollection, payload.id);
					if (existingItemInSearchCollection.element)
					{
						store.commit('delete', {
							searchCollection: true,
							index: existingItemInSearchCollection.index,
						});
					}
				}
			},
			deleteAll: (store, payload) =>
			{
				store.commit('deleteAll');
			},
			setSchema: (store, payload) =>
			{
				store.commit('setSchema', {
					data: payload.data,
				});
			},
		}
	}

	getMutations()
	{
		return {
			set: (state, payload) =>
			{
				state.total = payload.hasOwnProperty('total') ? payload.total : state.total;

				if (!payload.hasOwnProperty('notification') || !Type.isArray(payload.notification))
				{
					return;
				}

				for (const element of payload.notification)
				{
					const existingItem = this.findItemInArr(state.collection, element.id);

					if (!existingItem.element)
					{
						state.collection.push(element);
					}
					else
					{
						// we trust unread status of existing item to prevent notifications blinking while init loading.
						if (element.unread !== state.collection[existingItem.index].unread)
						{
							element.unread = state.collection[existingItem.index].unread;
							state.unreadCounter = (element.unread === true ? state.unreadCounter + 1 : state.unreadCounter - 1);
						}

						state.collection[existingItem.index] = Object.assign(
							state.collection[existingItem.index],
							element
						);
					}
				}

				state.collection.sort(this.sortByType);
			},
			setSearchResults: (state, payload) =>
			{
				for (const element of payload.data.notification)
				{
					const existingItem = this.findItemInArr(state.searchCollection, element.id);

					if (!existingItem.element)
					{
						state.searchCollection.push(element);
					}
					else
					{
						state.searchCollection[existingItem.index] = Object.assign(
							state.searchCollection[existingItem.index],
							element
						);
					}
				}
			},
			deleteAll: (state, payload) =>
			{
				state.collection = [];
			},
			deleteSearchResults: (state, payload) =>
			{
				state.searchCollection = [];
			},
			add: (state, payload) =>
			{
				let firstNotificationIndex = null;
				if (payload.data.sectionCode === NotificationTypesCodes.confirm)
				{
					//new confirms should always add to the beginning of the collection
					state.collection.unshift(payload.data);
				}
				else //if (payload.data.sectionCode === NotificationTypesCodes.simple)
				{
					for (let index = 0; state.collection.length > index; index++)
					{
						if (state.collection[index].sectionCode === NotificationTypesCodes.simple)
						{
							firstNotificationIndex = index;
							break;
						}
					}

					//if we didn't find any simple notification and its index, then add new one to the end.
					if (firstNotificationIndex === null)
					{
						state.collection.push(payload.data);
					}
					else //otherwise, put it right before first simple notification.
					{
						state.collection.splice(firstNotificationIndex, 0, payload.data);
					}
				}

				state.collection.sort(this.sortByType);
			},
			update: (state, payload) =>
			{
				const collectionName = payload.searchCollection ? 'searchCollection' : 'collection';

				Vue.set(state[collectionName], payload.index, Object.assign(
					{},
					state[collectionName][payload.index],
					payload.fields
				));

			},
			delete: (state, payload) =>
			{
				const collectionName = payload.searchCollection ? 'searchCollection' : 'collection';
				state[collectionName].splice(payload.index, 1);
			},
			read: (state, payload) =>
			{
				state.collection[payload.index].unread = payload.action;
			},
			readAll: (state, payload) =>
			{
				for (let index = 0; state.collection.length > index; index++)
				{
					if (state.collection[index].sectionCode === NotificationTypesCodes.simple)
					{
						state.collection[index].unread = false;
					}
				}
			},
			updatePlaceholders: (state, payload) =>
			{
				const collectionName = payload.searchCollection ? 'searchCollection' : 'collection';

				payload.items.forEach((element, index) => {
					const placeholderId = `placeholder${payload.firstItem + index}`;
					const existingPlaceholderIndex = state[collectionName].findIndex(notification => {
						return notification.id === placeholderId;
					});

					const existingMessageIndex = state[collectionName].findIndex(notification => {
						return notification.id === element.id;
					});

					if (existingMessageIndex >= 0)
					{
						state[collectionName][existingMessageIndex] = Object.assign(
							state[collectionName][existingMessageIndex],
							element
						);
						state[collectionName].splice(existingPlaceholderIndex, 1);
					}
					else
					{
						state[collectionName].splice(
							existingPlaceholderIndex,
							1,
							Object.assign({}, element)
						);
					}
				});

				state[collectionName].sort(this.sortByType);
			},
			clearPlaceholders: (state, payload) =>
			{
				state.collection = state.collection.filter(element => {
					return !element.id.toString().startsWith('placeholder');
				});

				state.searchCollection = state.searchCollection.filter(element => {
					return !element.id.toString().startsWith('placeholder');
				});
			},
			setCounter: (state, payload) =>
			{
				state.unreadCounter = payload;
			},
			setTotal: (state, payload) =>
			{
				state.total = payload;
			},
			setSchema: (state, payload) =>
			{
				state.schema = payload.data;
			}
		}
	}

	/* region Validation */
	validate(fields, options)
	{
		const result = {};

		if (Type.isString(fields.id) || Type.isNumber(fields.id))
		{
			result.id = fields.id;
		}

		if (!Type.isNil(fields.date))
		{
			result.date = Utils.date.cast(fields.date);
		}

		// previous P&P format
		if (Type.isString(fields.textOriginal) || Type.isNumber(fields.textOriginal))
		{
			result.text = fields.textOriginal.toString();

			if (Type.isString(fields.text) || Type.isNumber(fields.text))
			{
				result.textConverted = this.convertToHtml({
					text: fields.text.toString(),
				});
			}
		}
		else // modern format
		{
			if (!Type.isNil(fields.text_converted))
			{
				fields.textConverted = fields.text_converted;
			}
			if (Type.isString(fields.textConverted) || Type.isNumber(fields.textConverted))
			{
				result.textConverted = fields.textConverted.toString();
			}
			if (Type.isString(fields.text) || Type.isNumber(fields.text))
			{
				result.text = fields.text.toString();

				let isConverted = !Type.isNil(result.textConverted);

				result.textConverted = this.convertToHtml({
					text: isConverted? result.textConverted: result.text,
				});
			}
		}

		if (Type.isNumber(fields.author_id))
		{
			if (fields.system === true || fields.system === 'Y')
			{
				result.authorId = 0;
			}
			else
			{
				result.authorId = fields.author_id;
			}
		}

		if (Type.isNumber(fields.userId))
		{
			result.authorId = fields.userId;
		}

		if (Type.isObjectLike(fields.params))
		{
			const params = this.validateParams(fields.params);
			if (params)
			{
				result.params = params;
			}
		}

		if (!Type.isNil(fields.notify_buttons))
		{
			result.notifyButtons = JSON.parse(fields.notify_buttons);
		}

		//p&p format
		if (!Type.isNil(fields.buttons))
		{
			result.notifyButtons = fields.buttons.map((button) => {
				return {
					COMMAND: 'notifyConfirm',
					COMMAND_PARAMS: `${result.id}|${button.VALUE}`,
					TEXT: `${button.TITLE}`,
					TYPE: 'BUTTON',
					DISPLAY: 'LINE',
					BG_COLOR: (button.VALUE === 'Y' ? '#8bc84b' : '#ef4b57'),
					TEXT_COLOR: '#fff',
				};
			});
		}
		if (fields.notify_type === NotificationTypesCodes.confirm || fields.type === NotificationTypesCodes.confirm)
		{
			result.sectionCode = NotificationTypesCodes.confirm;
		}
		else if (fields.type === NotificationTypesCodes.placeholder)
		{
			result.sectionCode = NotificationTypesCodes.placeholder;
		}

		if (!Type.isNil(fields.notify_read))
		{
			result.unread = fields.notify_read === 'N';
		}

		//p&p format
		if (!Type.isNil(fields.read))
		{
			result.unread = fields.read === 'N'; //?
		}

		if (Type.isString(fields.setting_name))
		{
			result.settingName = fields.setting_name;
		}

		// rest format
		if (Type.isString(fields.notify_title) && fields.notify_title.length > 0)
		{
			result.title = fields.notify_title;
		}

		// p&p format
		if (Type.isString(fields.title) && fields.title.length > 0)
		{
			result.title = fields.title;
		}

		return result;
	}

	validateParams(params)
	{
		const result = {};
		try
		{
			for (let field in params)
			{
				if (!params.hasOwnProperty(field))
				{
					continue;
				}

				if (field === 'COMPONENT_ID')
				{
					if (Type.isString(params[field]) && BX.Vue.isComponent(params[field]))
					{
						result[field] = params[field];
					}
				}
				else if (field === 'LIKE')
				{
					if (params[field] instanceof Array)
					{
						result['REACTION'] = {like: params[field].map(element => parseInt(element))};
					}
				}
				else if (field === 'CHAT_LAST_DATE')
				{
					result[field] = Utils.date.cast(params[field]);
				}
				else if (field === 'AVATAR')
				{
					if (params[field])
					{
						result[field] = params[field].startsWith('http') ? params[field] : options.host + params[field];
					}
				}
				else if (field === 'NAME')
				{
					if (params[field])
					{
						result[field] = params[field];
					}
				}
				else
				{
					result[field] = params[field];
				}
			}
		}
		catch (e) {}

		let hasResultElements = false;
		for (let field in result)
		{
			if (!result.hasOwnProperty(field))
			{
				continue;
			}

			hasResultElements = true;
			break
		}

		return hasResultElements? result: null;
	}
	/* endregion Validation */

	/* region Internal helpers */
	prepareNotification(notification, options = {})
	{
		let result = this.validate(Object.assign({}, notification));

		return Object.assign({}, this.getElementState(), result, options);
	}

	findItemInArr(arr, value, key = 'id')
	{
		const result = {};

		const elementIndex = arr.findIndex((element, index) => {
			return element[key] === value;
		});

		if (elementIndex !== -1)
		{
			result.index = elementIndex;
			result.element = arr[elementIndex];
		}

		return result;
	}

	sortByType(a, b)
	{
		if (a.sectionCode === NotificationTypesCodes.confirm && b.sectionCode !== NotificationTypesCodes.confirm)
		{
			return -1;
		}
		else if (a.sectionCode !== NotificationTypesCodes.confirm && b.sectionCode === NotificationTypesCodes.confirm)
		{
			return 1;
		}
		else
		{
			return b.id - a.id;
		}
	}
	/* endregion Internal helpers */

	/* region Text utils */
	convertToHtml(params = {})
	{
		let { text = '' } = params;

		text = text.trim();
		text = text.replace(/\n/gi, '<br />');
		text = text.replace(/\t/gi, '&nbsp;&nbsp;&nbsp;&nbsp;');

		text = NotificationsModel.decodeBbCode({ text });

		if (Utils.platform.isBitrixDesktop())
		{
			text = text.replace(/<a(.*?)>(.*?)<\/a>/ig, function(whole, anchor, text) {
				return '<a'+anchor.replace('target="_self"', 'target="_blank"')+' class="bx-im-notifications-item-link">'+text+'</a>';
			});
		}

		return text;
	};

	static decodeBbCode(params = {})
	{
		let { text } = params;

		text = text.replace(/\[url=([^\]]+)\](.*?)\[\/url\]/ig, function(whole, link, text)
		{
			let tag = document.createElement('a');
			tag.href = Utils.text.htmlspecialcharsback(link);
			tag.target = '_blank';
			tag.text = Utils.text.htmlspecialcharsback(text);

			const allowList = [
				'http:',
				'https:',
				'ftp:',
				'file:',
				'tel:',
				'callto:',
				'mailto:',
				'skype:',
				'viber:',
			];
			if (allowList.indexOf(tag.protocol) <= -1)
			{
				return whole;
			}

			return tag.outerHTML;
		});

		text = text.replace(/\[LIKE\]/ig, '<span class="bx-smile bx-im-smile-like"></span>');
		text = text.replace(/\[DISLIKE\]/ig, '<span class="bx-smile bx-im-smile-dislike"></span>');

		text = text.replace(/\[RATING\=([1-5]{1})\]/ig, (whole, rating) => {
			// todo: refactor legacy call
			return BX.MessengerCommon.linesVoteHeadNodes(0, rating, false).outerHTML;
		});

		text = text.replace(/\[BR\]/ig, '<br/>');
		text = text.replace(/\[([buis])\](.*?)\[(\/[buis])\]/ig, (whole, open, inner, close) => {
			return '<' + open + '>' + inner + '<' + close + '>';
		});

		text = text.replace(/\[CHAT=(imol\|)?([0-9]{1,})\](.*?)\[\/CHAT\]/ig, (whole, openlines, chatId, inner) => {
			chatId = parseInt(chatId);

			if (chatId <= 0)
			{
				return inner;
			}

			if (openlines)
			{
				return '<span class="bx-im-mention" data-type="OPENLINES" data-value="'+chatId+'">'+inner+'</span>';
			}
			else
			{
				return '<span class="bx-im-mention" data-type="CHAT" data-value="'+chatId+'">'+inner+'</span>';
			}
		});
		text = text.replace(/\[USER=([0-9]{1,})\](.*?)\[\/USER\]/ig, (whole, userId, text) =>
		{
			let html = '';

			userId = parseInt(userId);
			if (userId > 0 && typeof(BXIM) != 'undefined')
			{
				html = `<span class="bx-im-mention ${userId === +BXIM.userId ? 'bx-messenger-ajax-self' : ''}" data-type="USER" data-value="${userId}">${text}</span>`;
			}
			else
			{
				html = text;
			}

			return html;
		});

		text = text.replace(/\[PCH=([0-9]{1,})\](.*?)\[\/PCH\]/ig, (whole, historyId, text) => text);

		return text;
	}
	/* endregion Text utils */
}

export {NotificationsModel};