Name
sn_docs.DocComponentAPI
Description
No description available
Script
var DocComponentAPI = Class.create();
DocComponentAPI.prototype = {
initialize: function() {
this.STATUS = {
ERROR: 'ERROR',
SUCCESS: 'SUCCESS',
PERMISSION_DENIED: 'PERMISSION_DENIED'
};
},
_checkDocAccess: function(docId) {
let doc = new GlideRecord('sn_doc');
if (doc.get(docId + '')) {
return doc.canRead();
}
},
_checkFieldAccess: function(sourceTable, sourceId, sourceField) {
const gr = new GlideRecord(sourceTable);
if (gr.get(sourceId)) {
if (sourceField)
return gr[sourceField].canRead();
return gr.canRead();
}
return false;
},
/**
* Get All pages with content for one page
* @param {string} docId Doc Sys ID
* @param {string} [selectedPageId] Selected Page Sys ID
* @return object{status: string, message: string, tree: array, doc: object}
*/
getAllPages: function(docId, selectedPageId) {
if (!docId) return this._getErrorObject(gs.getMessage('The document was not found.'));
//verify the docId
if (!this._checkDocAccess(docId)) {
return {
status: this.STATUS.PERMISSION_DENIED,
message: gs.getMessage("Permission Denied")
};
}
let docGr = new GlideRecordSecure('sn_doc');
if (!docGr.get(docId + "")) {
return this._getErrorObject(gs.getMessage('The document was not found.'));
}
//verify the selectedPageId
let _selectedPageId = this._validIdOrNull('sn_doc_page', selectedPageId + "");
const favouritePages = this._getUserPreferences();
const currentDocFavs = (favouritePages && favouritePages[docId]) || [];
const allPages = [];
const pageIndexMap = {};
let i = 0;
const gr = new GlideRecordSecure('sn_doc_page');
gr.addQuery('sn_doc_id', docId);
gr.orderBy('parent');
gr.orderBy('order');
gr.query();
while (gr.next()) {
if (global.JSUtil.nil(_selectedPageId))
_selectedPageId = gr.getValue('sys_id');
let page = {
sysId: gr.getValue('sys_id'),
title: gr.getValue('title'),
subTitle: gr.getValue('subtitle'),
icon: gr.getValue('icon') || 'document-outline',
parent: gr.getValue('parent'),
order: gr.getValue('order'),
watchList: this._buildUserList(gr, 'watch_list'),
contributors: this._buildUserList(gr, 'contributors'),
updatedOn: gr.getValue('sys_updated_on'),
children: [],
favourite: currentDocFavs.indexOf(gr.getValue('sys_id')) > -1 ? true : false
};
pageIndexMap[page.sysId] = i++;
if (gr.getValue('sys_id') == _selectedPageId) {
page.content = gr.getValue('content');
}
allPages.push(page);
}
//convert flat array to tree
const tree = [];
for (i = 0; i < allPages.length; i += 1) {
node = allPages[i];
if (node.parent) {
allPages[pageIndexMap[node.parent]]?.children?.push(node);
} else {
tree.push(node);
}
}
return {
status: this.STATUS.SUCCESS,
tree,
doc: {
title: docGr.getValue('title'),
sysId: docGr.getValue('sys_id'),
icon: docGr.getValue('icon'),
owner: {
sysId: docGr.getValue('owner'),
name: docGr.getDisplayValue('owner')
},
createdBy: docGr.getValue('sys_created_by'),
watchList: this._buildUserList(docGr, 'watch_list'),
},
selectedPageId: _selectedPageId
};
},
/**
* Returns the id passed if it belongs to valid record in the given table, else null
*/
_validIdOrNull: function(tableName, id) {
const gr = new GlideRecordSecure(tableName);
return gr.get(id) ? id : null;
},
/**
* Search All pages with content for one page
* @param {string} docId Doc Sys ID
* @param {string} searchTerm Search Text
* @return object{status: string, pageSysIDs: array<string>}
*/
searchAllPages: function(docId, searchTerm) {
if (!searchTerm) return this._getErrorObject(gs.getMessage('Search term can not be empty.'));
if (!docId) return this._getErrorObject(gs.getMessage('The document was not found.'));
//verify the docId
let docGr = new GlideRecordSecure('sn_doc');
if (!docGr.get(docId + "")) {
return this._getErrorObject(gs.getMessage('The document was not found.'));
}
let regex = /( |<([^>]+)>)/ig;
const pageSysIDs = [];
const gr = new GlideRecordSecure('sn_doc_page');
gr.addQuery('sn_doc_id', docId);
gr.addQuery('content', 'CONTAINS', searchTerm);
gr.orderBy('parent');
gr.orderBy('order');
gr.query();
while (gr.next()) {
var content = gr.getValue('content').replace(regex, " ");
//to be sure that its not the html tags
searchTerm = searchTerm.toString().toLowerCase();
content = content.toString().toLowerCase();
if (content.indexOf(searchTerm) > -1) {
pageSysIDs.push(gr.getValue('sys_id'));
}
}
return {
status: this.STATUS.SUCCESS,
pageSysIDs, //flat tree is enough for content tree search.
};
},
/**
* @param {array} categoryIds array of category sysIds
* @return object{status: string, templateTree: array<string>}
*/
getAllTemplates: function(categoryIds) {
categoryIds = categoryIds + "";
const categorySysIds = categoryIds.split(",");
var categories = new GlideRecordSecure('sn_doc_page_template_category');
if (categoryIds)
categories.addQuery("sys_id", "IN", categorySysIds);
categories.query();
var templateTree = [];
while (categories.next()) {
var category = categories.getUniqueValue();
var category_entry = {
sysId: category,
icon: 'catalog-outline',
title: categories.getValue("name"),
type: "category",
children: []
};
var templates = new GlideRecordSecure("sn_doc_page_template");
templates.addQuery("category", category);
templates.query();
while (templates.next()) {
var template = {
sysId: templates.getValue('sys_id'),
title: templates.getValue('name'),
description: templates.getValue('description'),
icon: templates.getValue('icon') || 'document-outline',
thumbnail: templates.getValue('thumbnail') || '',
parent: category,
visibility: templates.getValue('visibility'),
owner: templates.getValue('owner'),
type: "template",
updatedOn: templates.getValue('sys_updated_on'),
children: []
};
if (category)
category_entry.children.push(template);
else
templateTree.push(template);
}
if (category)
templateTree.push(category_entry);
}
return {
status: this.STATUS.SUCCESS,
templateTree: templateTree
};
},
/**
* Get Error result JSON Object
* @param {string} msg Error Message
* @return Object{status: string, message: string, tree: array, doc: object}
*/
_getErrorObject: function(msg) {
return {
status: this.STATUS.ERROR,
message: msg,
tree: [],
doc: {}
};
},
/**
* Build User List using GlideRecord
* @param {GlideRecord} docPageGr GlideRecord
* @param {string} field Field which has glide_list of users
* @return array<{displayName: string, avatar: string, sysId: string, email: string}>
*/
_buildUserList: function(docPageGr, field) {
const res = [];
if (docPageGr.getValue(field)) {
let userList = (docPageGr.getValue(field)).split(',');
for (let i = 0; i < userList.length; i++) {
const user = this._getUserInfo(userList[i]);
if (user)
res.push(user);
}
}
return res;
},
_getUserPreferences: function() {
let preference = gs.getUser().getPreference('sn_doc_component_starred_pages');
let allFavourites;
if (preference) {
allFavourites = JSON.parse(preference);
}
return allFavourites || {};
},
/**
* Build User info using Sys ID
* @param {string} userID User Sys ID
* @return object{displayName: string, avatar: string, sysId: string, email: string}
*/
_getUserInfo: function(userID) {
let userGr = new GlideRecord("sys_user");
if (userGr.get(userID)) {
return {
displayName: userGr.getValue("name"),
avatar: userGr.getValue("avatar") ? `${userGr.getValue("avatar")}.iix?t=small` : '',
sysId: userGr.getValue("sys_id"),
email: userGr.getValue("email")
};
}
return null;
},
/**
* Get all comments tree structure related to selectedPage of a doc
* @param {string} sourceTable
* @param {string} sourceId
* @param {string} sourceField Record Field
* @return object{[{id: string, created: string, text: string, user: object, reactionCount: integer, isReacted: boolean, parent: string, child: [object]}]}
*/
getComments: function(sourceTable, sourceId, sourceField) {
const allComments = [];
const commentIndexMap = {};
if (!sourceTable || !sourceId) return [];
const commentsGr = new GlideRecordSecure('sn_doc_comment');
commentsGr.addQuery('source_table', sourceTable);
commentsGr.addQuery('source_id', sourceId);
if (sourceField)
commentsGr.addQuery('source_field', sourceField);
commentsGr.query();
let commentsCount = 0;
while (commentsGr.next()) {
const comment = {
id: commentsGr.getValue('sys_id'),
created: commentsGr.getValue('sys_created_on'),
text: commentsGr.getValue('content'),
user: commentsGr.getValue('sys_created_by'),
reactionCount: 0,
isReacted: false,
parent: commentsGr.getValue('parent'),
children: []
};
const userGr = new GlideRecord('sys_user');
userGr.addQuery('user_name', comment.user);
userGr.query();
if (userGr.next()) {
comment.user = {
name: userGr.getValue('name'),
id: userGr.getValue('sys_id'),
avatar: `${userGr.getValue('avatar')}.iix?t=small`
};
} else {
comment.user = {
name: comment.user
};
}
const commentReactionGr = new GlideRecordSecure('sn_doc_reaction');
commentReactionGr.addQuery('source_table', 'sn_doc_comment');
commentReactionGr.addQuery('source_id', commentsGr.getValue('sys_id'));
commentReactionGr.query();
while (commentReactionGr.next()) {
const usersList = commentReactionGr.getValue('user_list');
if (usersList?.includes(gs.getUserID())) {
comment.isReacted = true;
}
comment.reactionCount += commentReactionGr.getValue('user_list')?.split(',').length || 0;
}
commentIndexMap[comment.id] = commentsCount++;
allComments.push(comment);
}
const commentTree = allComments.reduce((commentTree, comment) => {
if (comment.parent) {
allComments[commentIndexMap[comment.parent]]?.children?.push(comment);
} else {
commentTree.push(comment);
}
return commentTree;
}, []);
return commentTree;
},
/**
* Update user reaction for comment
* @param commentId {string} sysId of the comment
* @param reaction {boolean} true if user reacted else false
* @return {}
*/
updateReaction: function(commentId, reaction) {
const commentGr = new GlideRecordSecure('sn_doc_comment');
commentGr.get(commentId);
if (commentGr.isValidRecord()) {
const reactionGr = new GlideRecordSecure('sn_doc_reaction');
reactionGr.addQuery('source_table', 'sn_doc_comment');
reactionGr.addQuery('source_id', commentId);
// Update this once comments component support different types of reactions
reactionGr.addQuery('reaction_type', 'LIKE');
reactionGr.query();
let shouldUpdate = false;
if (reactionGr.next()) {
const existingUsersArray = reactionGr.getValue('user_list')?.split(',') || [];
const currentUser = gs.getUserID();
if (reaction) {
if (!existingUsersArray.includes(currentUser)) {
shouldUpdate = true;
existingUsersArray.push(currentUser);
}
} else {
if (existingUsersArray.includes(currentUser)) {
shouldUpdate = true;
existingUsersArray.splice(existingUsersArray.indexOf(currentUser), 1);
}
}
if (shouldUpdate) {
reactionGr.setValue('user_list', existingUsersArray.join(','));
reactionGr.update();
}
} else if (reaction) {
reactionGr.initialize();
reactionGr.setValue('source_table', 'sn_doc_comment');
reactionGr.setValue('source_id', commentId);
reactionGr.setValue('reaction_type', 'LIKE');
reactionGr.setValue('user_list', gs.getUserID());
reactionGr.insert();
}
return {
status: this.STATUS.SUCCESS
};
}
return {
status: this.STATUS.ERROR,
message: gs.getMessage('Invalid comment ID.')
};
},
/**
* Deletes a comment or marks it for deletion
* @param commentId {string} sysId of the comment to be deleted
* @return object{status: string, sysId: string, message: string}
*/
deleteComment: function(commentId) {
let commentGr = new GlideRecordSecure('sn_doc_comment');
commentGr.get(commentId);
if (commentGr.isValidRecord()) {
if (commentGr.sys_created_by != gs.getUserName()) {
return {
status: this.STATUS.ERROR,
message: gs.getMessage('You can\'t delete this comment.')
};
} else if (commentGr.deleted) {
return {
status: this.STATUS.ERROR,
message: gs.getMessage('The comment has been deleted already.')
};
}
commentGr.setValue('deleted', true);
commentGr.update();
let childCount = '0';
while (commentGr.isValidRecord() && commentGr.deleted) {
childCount = '0';
const childCommentsGa = new GlideAggregate('sn_doc_comment');
childCommentsGa.addQuery('parent', commentGr.sys_id);
childCommentsGa.addAggregate('COUNT');
childCommentsGa.query();
if (childCommentsGa.next()) {
childCount = childCommentsGa.getAggregate('COUNT');
}
if (childCount && childCount !== '0') {
return {
status: this.STATUS.SUCCESS,
sysId: commentId
};
}
let parent = commentGr.parent.getRefRecord();
commentGr.deleteRecord();
commentGr = parent;
}
return {
status: this.STATUS.SUCCESS,
sysId: commentId
};
} else {
return {
status: this.STATUS.ERROR,
message: gs.getMessage('The comment was not found.')
};
}
},
/**
* Get Document relationship for record field
* @param {string} sourceTable Doc Sys ID
* @param {string} sourceId Doc Sys ID
* @return object{status: string, docRelation: object}
*/
getRecordDoc: function(sourceTable, sourceId) {
const res = this.getRecordField(sourceTable, sourceId);
if (!res) {
return {
status: this.STATUS.ERROR,
message: gs.getMessage('The record was not found.')
};
}
if (res.status === this.STATUS.PERMISSION_DENIED) {
return res;
}
const gr = new GlideRecordSecure('sn_doc_m2m');
gr.addQuery('source_table', sourceTable);
gr.addQuery('source_id', sourceId);
gr.query();
let sys_created_by, sn_doc_id, page_id;
if (gr.next()) {
sys_created_by = gr.getValue('sys_created_by');
sn_doc_id = gr.getValue('sn_doc_id');
const pageGr = new GlideRecordSecure('sn_doc_page');
pageGr.addQuery('sn_doc_id', sn_doc_id);
pageGr.query();
if (pageGr.next())
page_id = pageGr.getValue('sys_id');
} else {
sn_doc_id = this._createDoc(res.record.displayValue, gs.getUserID());
page_id = this._createPage(sn_doc_id, res.record.displayValue);
sys_created_by = gs.getUserName();
gr.initialize();
gr.setValue('source_table', sourceTable);
gr.setValue('source_id', sourceId);
gr.setValue('sys_created_by', gs.getUserID());
gr.setValue('sn_doc_id', sn_doc_id);
gr.insert();
}
const docRelation = {
sys_created_by: sys_created_by,
sn_doc_id: sn_doc_id,
page_id: page_id
};
const result = this.getAllPages(sn_doc_id, page_id);
if (result.status === 'SUCCESS') {
return {
status: this.STATUS.SUCCESS,
docRelation: docRelation,
tree: result.tree,
doc: result.doc
};
}
return {
status: this.STATUS.ERROR,
message: gs.getMessage('The document was not found.')
};
},
_createDoc: function(title, owner) {
const gr = new GlideRecordSecure('sn_doc');
gr.initialize();
gr.setValue('title', title);
gr.setValue('owner', owner);
sysId = gr.insert();
return sysId;
},
_createPage: function(docId, title) {
const gr = new GlideRecordSecure('sn_doc_page');
gr.initialize();
gr.setValue('title', title);
gr.setValue('sn_doc_id', docId);
sysId = gr.insert();
return sysId;
},
/**
* Get Document relationship for record field
* @param {string} sourceTable Table Name
* @param {string} sourceId Record Sys ID
* * @param {string} sourceField Record Field
* @return object{status: string, record: object}
*/
getRecordField: function(sourceTable, sourceId, sourceField) {
if (sourceId && !this._checkFieldAccess(sourceTable, sourceId, sourceField)) {
return {
status: this.STATUS.PERMISSION_DENIED,
message: gs.getMessage("Permission Denied")
};
}
const gr = new GlideRecordSecure(sourceTable);
gr.addQuery('sys_id', sourceId);
gr.query();
if (gr.next()) {
const record = {
updatedOn: gr.getValue('sys_updated_on'),
displayValue: gr.getDisplayValue(),
fieldName: sourceField ? gr.getElement(sourceField).getLabel() : '',
fieldType: sourceField ? gr.getElement(sourceField).getED().getInternalType() : '',
content: sourceField ? gr.getValue(sourceField) : '',
canWrite: sourceField ? gr[sourceField].canWrite() : gr.canWrite()
};
return {
status: this.STATUS.SUCCESS,
record: record
};
}
return {
status: this.STATUS.ERROR,
message: gs.getMessage('The record was not found.')
};
},
type: 'DocComponentAPI'
};
Sys ID
5585e2e6c342e510ddecd0c57d40ddfb