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

Offical Documentation

Official Docs: