Js advanced commenting (#1131)

* Advanced comment box sample

* Typo error

Updated the SharePoint generator version info.

Co-authored-by: Mikael Svenson <miksvenson@gmail.com>
Co-authored-by: Laura Kokkarinen <41330990+LauraKokkarinen@users.noreply.github.com>
This commit is contained in:
Sudharsan 2020-02-10 17:37:50 +08:00 committed by GitHub
parent 4242412984
commit 8acf36979f
28 changed files with 22814 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"libraryName": "modern-page-comments",
"libraryId": "cfa8dbcf-b32d-4de0-a6ba-af486007c2f2",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,88 @@
# Advanced Comments Box
## Summary
> This component is developed for the advanced usage of commenting the page or article etc. **_Page Comments_** lists will be created to store the comments. Following are some of the features of this component.
* Can be used in the modern page with the existing comments disabled.
* Classification of comments by **_Popular_**, **_Newest_**, **_Oldest_** and **_Attachments_**
* Ability to refer files as a comment.
* **_Edit_**, **_Reply_** (nested comments), **_Like_** & **_Delete_** options are available based on the configuration.
* **_Hashtag_** & **_Ping Users_** are also available.
* **_Document Preview_** is also available for all office documents and videos based on the configuration.
* Display of **_New_** icon for the current day comments.
## Properties
1. **_DateTime_** format on when the comments were added or modified
2. **_Profile Picture_** style, whether it has to be rounded or square
3. Enable or Disable **_Navigation_** whether to display the comments classification
4. Enable or disable **_Attachments_**. Following properties are required when attachments are enabled.
* **_Library_** to store the files uploaded.
* Allowed **_File Formats_** in the comments box.
* Maximum **_File Size_** allowed.
5. **_Ping Users_** will allow to mention the users. The users are pulled from the **Site Users**.
6. **_Edit_** comments can be enabled or disabled to allow the users to edit the comments. Files added can be deleted not edited.
* **_Delete_** option can be enabled or disabled to allow the users to delete the comments. Comments with no-replies are allowed to delete. Delete is allowed only if Edit is allowed.
7. **_Upvoting_** of comments to like or dislike the comments.
8. **_Hashtags_**
9. **_Document Preview_** can be enabled or disabled for the office files and videos.
## Preview
![Advanced-Comments-Box](./assets/Advanced-Comments-Box.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-GA-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Prerequisites
> **@microsoft/generator-sharepoint - 1.9.1**
## Solution
Solution|Author(s)
--------|---------
SPFxPageComments | Sudharsan K.([@sudharsank](https://twitter.com/sudharsank), [Know More](http://windowssharepointserver.blogspot.com/))
## Version history
Version|Date|Comments
-------|----|--------
1.0.0.0|Feb 05 2020|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
- `npm install`
- `gulp bundle --ship && gulp package-solution --ship`
- Add the .sppkg file to the app catalog and add the **Page Comments** web part to the page.
## Features
- Used [SharePoint Framework Property Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/) to create the property pane controls(Text, ListPicker, Toggle) with callout.
- Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint.
- Used [jquery-comments](https://viima.github.io/jquery-comments/) for comments control with some customization.
- Used [Moment.js](https://momentjs.com/) for datetime formatting.
#### Local Mode
This solution doesn't work on local mode.
#### SharePoint Mode
If you want to try on a real environment, open:
[O365 Workbench](https://your-domain.sharepoint.com/_layouts/15/workbench.aspx)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 MiB

View File

@ -0,0 +1,31 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"page-comments-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/pageComments/PageCommentsWebPart.js",
"manifest": "./src/webparts/pageComments/PageCommentsWebPart.manifest.json"
}
]
}
},
"externals": {
"jquery": {
"path": "node_modules/jquery/dist/jquery.min.js",
"globalName": "jQuery"
},
"textcomplete": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.textcomplete/1.8.0/jquery.textcomplete.js",
"globalName": "jQuery",
"globalDependencies": [
"jquery"
]
}
},
"localizedResources": {
"PageCommentsWebPartStrings": "lib/webparts/pageComments/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "modern-page-comments",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "Advanced Comments Box",
"id": "cfa8dbcf-b32d-4de0-a6ba-af486007c2f2",
"version": "1.1.0.2",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/advanced-comments-box.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
{
"name": "modern-page-comments",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/sp": "^2.0.0",
"@pnp/spfx-property-controls": "1.16.0",
"@types/es6-promise": "0.0.33",
"@types/jquery": "^2.0.54",
"@types/webpack-env": "1.13.1",
"jquery": "^2.2.4",
"moment": "^2.24.0"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,33 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "7520fa3d-1f05-4d78-bf7f-91627d9b64d3",
"alias": "PageCommentsWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Advanced Comments" },
"description": { "default": "Comments with advanced features for modern pages" },
"officeFabricIconFontName": "FileComment",
"properties": {
"datetimeFormat":"DD/MM/YYYY",
"enableNavigation": true,
"enableReplying": true,
"enableEditing": false,
"enableUpvoting": true,
"enableDeleting": false,
"enableAttachments": false,
"enableHashtags": false,
"enablePinging": false,
"enableDocumentPreview": false,
"roundProfilePictures": true,
"attachmentFileFormats": "audio/*,image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx",
"attachmentFileSize": 2
}
}]
}

View File

@ -0,0 +1,44 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.pageComments {
.container {
// max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
}
.errorMessage {
margin: 10px;
border: 1px solid red;
padding: 10px;
background-color: #ead9d9;
font-weight: 600;
}

View File

@ -0,0 +1,374 @@
import * as React from 'react';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { SPComponentLoader } from '@microsoft/sp-loader';
import { CalloutTriggers } from '@pnp/spfx-property-controls/lib/PropertyFieldHeader';
import { PropertyFieldSliderWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldSliderWithCallout';
import { PropertyFieldToggleWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout';
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
import * as _ from "lodash";
import * as moment from 'moment';
import styles from './PageCommentsWebPart.module.scss';
import * as strings from 'PageCommentsWebPartStrings';
import * as $ from 'jquery';
require('textcomplete');
import { sp } from '@pnp/sp';
import SPHelper from './SPHelper';
require('./css/jquery-comments.css');
export interface IPageCommentsWebPartProps {
enableNavigation: boolean;
enableReplying: boolean;
enableAttachments: boolean;
enableEditing: boolean;
enableUpvoting: boolean;
enableDeleting: boolean;
enableDeletingCommentWithReplies: boolean;
enableHashtags: boolean;
enablePinging: boolean;
enableDocumentPreview: boolean;
roundProfilePictures: boolean;
datetimeFormat: string;
attachmentFileFormats: string;
attachmentFileSize: number;
docLib: string;
}
export default class PageCommentsWebPart extends BaseClientSideWebPart<IPageCommentsWebPartProps> {
private helper: SPHelper = null;
private currentUserInfo: any = null;
private siteUsers: any[] = [];
private pageurl: string = '';
private postAttachmentPath: string = '';
private pageFolderExists: boolean = false;
protected async onInit(): Promise<void> {
await super.onInit();
sp.setup(this.context);
}
public constructor() {
super();
SPComponentLoader.loadCss("https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css");
}
public render(): void {
if (this.properties.enableAttachments && (this.properties.docLib === null || undefined === this.properties.docLib ||
this.properties.docLib.toLocaleUpperCase() === "NO_LIST_SELECTED")) {
this.domElement.innerHTML = `
<div class="${styles.errorMessage}"><i class="fa fa-times-circle" aria-hidden="true"></i>&nbsp;${strings.NoAttachmentRepoMsg}</div>
`;
} else {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, strings.LoadingMsg, 0);
this.checkAndCreateList();
}
}
private async checkAndCreateList() {
if (this.properties.enableAttachments) {
this.helper = new SPHelper(this.properties.docLib);
} else {
this.helper = new SPHelper();
}
await this.helper.checkListExists();
this.initializeComments();
}
private initializeComments = async () => {
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.domElement.innerHTML = `
<div class="${ styles.pageComments}">
<div class="${ styles.container}">
<div class="${ styles.row}">
<div id="page-comments"></div>
</div>
</div>
</div>`;
var self = this;
if (this.properties.enableAttachments) {
await this.helper.getDocLibInfo();
this.postAttachmentPath = await this.helper.getPostAttachmentFilePath(this.pageurl);
this.pageFolderExists = await this.helper.checkForPageFolder(this.postAttachmentPath);
}
this.pageurl = this.context.pageContext.legacyPageContext.serverRequestPath;
this.currentUserInfo = await this.helper.getCurrentUserInfo();
this.siteUsers = await this.helper.getSiteUsers(self.currentUserInfo.ID);
require(['jquery', './js/jquery-comments.min'], (jQuery, comments) => {
jQuery('#page-comments').comments({
profilePictureURL: self.currentUserInfo.Picture,
currentUserId: self.currentUserInfo.ID,
maxRepliesVisible: 3,
textareaRows: 1,
textareaRowsOnFocus: 2,
textareaMaxRows: 5,
highlightColor: '#b5121b',
attachmentFileFormats: self.properties.attachmentFileFormats !== undefined ? self.properties.attachmentFileFormats : 'audio/*,image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx',
attachmentFileSize: self.properties.attachmentFileSize !== undefined ? self.properties.attachmentFileSize : 5,
siteURL: self.context.pageContext.legacyPageContext.webServerRelativeUrl,
enableNavigation: self.properties.enableNavigation !== undefined ? self.properties.enableNavigation : true,
enableReplying: self.properties.enableReplying !== undefined ? self.properties.enableReplying : true,
enableEditing: self.properties.enableEditing !== undefined ? self.properties.enableEditing : false,
enableUpvoting: self.properties.enableUpvoting !== undefined ? self.properties.enableUpvoting : true,
enableDeleting: self.properties.enableDeleting !== undefined ? self.properties.enableDeleting : false,
enableAttachments: self.properties.enableAttachments !== undefined ? self.properties.enableAttachments : false,
enableHashtags: self.properties.enableHashtags !== undefined ? self.properties.enableHashtags : false,
enablePinging: self.properties.enablePinging !== undefined ? self.properties.enablePinging : false,
enableDocumentPreview: self.properties.enableDocumentPreview !== undefined ? self.properties.enableDocumentPreview : false,
roundProfilePictures: self.properties.roundProfilePictures !== undefined ? self.properties.roundProfilePictures : true,
timeFormatter: (time) => {
try {
if (self.properties.datetimeFormat) {
return moment(time).format(self.properties.datetimeFormat);
} else return moment(time).format(self.properties.datetimeFormat);
} catch (err) {
return moment(time).format("DD/MM/YYYY hh:mm:ss A");
}
},
getComments: async (success, error) => {
let commentsArray = await self.helper.getPostComments(self.pageurl, self.currentUserInfo);
if (commentsArray.length > 0) {
var fil = _.filter(commentsArray, (o) => { return moment(o.created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY"); });
fil.map((comment) => {
_.set(comment, 'is_new', true);
});
fil = _.filter(commentsArray, (o) => { return o.userid == self.currentUserInfo.ID; });
fil.map((comment) => {
_.set(comment, 'created_by_current_user', true);
});
}
success(commentsArray);
},
postComment: async (commentJson, success, error) => {
commentJson.fullname = self.currentUserInfo.DisplayName;
commentJson.userid = self.currentUserInfo.ID;
commentJson = self.saveComment(commentJson);
await self.helper.postComment(self.pageurl, commentJson, self.currentUserInfo);
if (moment(commentJson.created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY")) _.set(commentJson, 'is_new', true);
_.set(commentJson, 'created_by_current_user', true);
success(commentJson);
},
searchUsers: async (term, success, error) => {
let res = [];
if (self.siteUsers.length <= 0) self.siteUsers = await self.helper.getSiteUsers(self.currentUserInfo.ID);
res = _.chain(self.siteUsers).filter((o) => { return o.fullname.toLowerCase().indexOf(term) >= 0 || o.email.toLowerCase().indexOf(term) >= 0; }).take(10).value();
success(res);
},
upvoteComment: async (commentJSON, success, error) => {
await self.helper.voteComment(self.pageurl, commentJSON, self.currentUserInfo);
success(commentJSON);
},
deleteComment: async (commentJSON, success, error) => {
await self.helper.deleteComment(self.pageurl, commentJSON);
success();
},
putComment: async (commentJSON, success, error) => {
commentJSON = self.saveComment(commentJSON);
await self.helper.editComments(self.pageurl, commentJSON);
success(commentJSON);
},
uploadAttachments: async (commentArray, success, error) => {
let res = await self.helper.postAttachments(commentArray, self.pageFolderExists, self.postAttachmentPath);
_.merge(res[0], { userid: self.currentUserInfo.ID, fullname: self.currentUserInfo.DisplayName });
await self.helper.postComment(self.pageurl, res[0], self.currentUserInfo);
if (moment(res[0].created).format("DD/MM/YYYY") === moment().format("DD/MM/YYYY")) _.set(res[0], 'is_new', true);
_.set(res[0], 'created_by_current_user', true);
success(res);
}
});
});
}
private saveComment = (data) => {
// Convert pings to human readable format
$(Object.keys(data.pings)).each((index, userId) => {
var fullname = data.pings[`${userId}`];
var pingText = '@' + fullname;
data.content = data.content.replace(new RegExp('@' + userId, 'g'), pingText);
});
return data;
}
private checkForDocumentLibrary = (value: string): string => {
if (value === null || value.trim().length === 0 || value.toLocaleUpperCase() === "NO_LIST_SELECTED") {
return strings.AttachmentRepoPropValMsg;
}
return '';
}
protected get disableReactivePropertyChanges(): boolean {
return true;
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('datetimeFormat', {
label: strings.DateTimeFormatLabel,
description: strings.DateTimeFormatDescription,
multiline: false,
resizable: false,
value: this.properties.datetimeFormat
}),
PropertyFieldToggleWithCallout('roundProfilePictures', {
calloutTrigger: CalloutTriggers.Hover,
key: 'roundProfilePicturesFieldId',
label: strings.RoundProfilePicLabel,
calloutContent: React.createElement('p', {}, strings.RoundProfilePicDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.roundProfilePictures !== undefined ? this.properties.roundProfilePictures : true
}),
PropertyFieldToggleWithCallout('enableNavigation', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableNavigationFieldId',
label: strings.NavigationLabel,
calloutContent: React.createElement('p', {}, strings.NavigationDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableNavigation !== undefined ? this.properties.enableNavigation : true
}),
PropertyFieldToggleWithCallout('enableAttachments', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableAttachmentsFieldId',
label: strings.AttachmentLabel,
calloutContent: React.createElement('p', {}, strings.AttachmentDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableAttachments !== undefined ? this.properties.enableAttachments : false
}),
PropertyFieldListPicker('docLib', {
label: strings.AttachmentRepoLabel,
selectedList: this.properties.docLib,
includeHidden: false,
orderBy: PropertyFieldListPickerOrderBy.Title,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: this.checkForDocumentLibrary.bind(this),
deferredValidationTime: 0,
key: 'docLibFieldId',
baseTemplate: 101,
disabled: !this.properties.enableAttachments
}),
PropertyPaneTextField('attachmentFileFormats', {
label: strings.AttachmentFileFormatLabel,
description: strings.AttachmentFileFormatDescription,
multiline: false,
resizable: false,
value: this.properties.attachmentFileFormats,
disabled: !this.properties.enableAttachments
}),
PropertyFieldSliderWithCallout('attachmentFileSize', {
calloutContent: React.createElement('div', {}, strings.AttachmentFileSizeDescription),
calloutTrigger: CalloutTriggers.Hover,
calloutWidth: 200,
key: 'attachmentFileSizeFieldId',
label: strings.AttachmentFileSizeLabel,
max: 10,
min: 1,
step: 1,
showValue: true,
value: this.properties.attachmentFileSize,
disabled: !this.properties.enableAttachments
}),
PropertyFieldToggleWithCallout('enablePinging', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enablePingingFieldId',
label: strings.PingLabel,
calloutContent: React.createElement('p', {}, strings.PingDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enablePinging !== undefined ? this.properties.enablePinging : false
}),
PropertyFieldToggleWithCallout('enableEditing', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableEditingFieldId',
label: strings.EditingLabel,
calloutContent: React.createElement('p', {}, strings.EditingDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableEditing !== undefined ? this.properties.enableEditing : false
}),
PropertyFieldToggleWithCallout('enableDeleting', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableDeletingFieldId',
label: strings.DeleteLabel,
calloutContent: React.createElement('p', {}, strings.DeleteDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableDeleting !== undefined ? this.properties.enableDeleting : false,
disabled: !this.properties.enableEditing
}),
// PropertyFieldToggleWithCallout('enableDeletingCommentWithReplies', {
// calloutTrigger: CalloutTriggers.Hover,
// key: 'enableDeletingCommentWithRepliesFieldId',
// label: strings.DeleteRepliesLabel,
// calloutContent: React.createElement('p', {}, strings.DeleteRepliesDescription),
// onText: 'Enable',
// offText: 'Disable',
// checked: this.properties.enableDeletingCommentWithReplies,
// disabled: !this.properties.enableEditing
// }),
PropertyFieldToggleWithCallout('enableUpvoting', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableUpvotingFieldId',
label: strings.UpVotingLabel,
calloutContent: React.createElement('p', {}, strings.UpVotingDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableUpvoting !== undefined ? this.properties.enableUpvoting : true
}),
PropertyFieldToggleWithCallout('enableReplying', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableReplyingFieldId',
label: strings.ReplyLabel,
calloutContent: React.createElement('p', {}, strings.ReplyDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableReplying !== undefined ? this.properties.enableReplying : true
}),
PropertyFieldToggleWithCallout('enableHashtags', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableHashtagsFieldId',
label: strings.HashtagsLabel,
calloutContent: React.createElement('p', {}, strings.HashtagsDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableHashtags !== undefined ? this.properties.enableHashtags : false
}),
PropertyFieldToggleWithCallout('enableDocumentPreview', {
calloutTrigger: CalloutTriggers.Hover,
key: 'enableDocumentPreviewFieldId',
label: strings.DocumentPreviewLabel,
calloutContent: React.createElement('p', {}, strings.DocumentPreviewDescription),
onText: 'Enable',
offText: 'Disable',
checked: this.properties.enableDocumentPreview !== undefined ? this.properties.enableDocumentPreview : false
}),
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,281 @@
import { sp } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists/web";
import "@pnp/sp/folders/web";
import "@pnp/sp/files/folder";
import "@pnp/sp/items/list";
import "@pnp/sp/fields/list";
import "@pnp/sp/views/list";
import "@pnp/sp/site-users/web";
import { IList } from "@pnp/sp/lists";
import * as _ from "lodash";
export default class SPHelper {
private lst_pageComments: string = '';
private lst_pageDocuments: string = '';
private lst_docListName: string = '';
private _list: IList = null;
private _doclist: IList = null;
private cqPostDocs: string = `<View>
<Query>
<Where>
<And>
<Eq>
<FieldRef Name='FSObjType' />
<Value Type='Text'>1</Value>
</Eq>
<Eq>
<FieldRef Name='FileRef' />
<Value Type='Text'>{{FilePath}}</Value>
</Eq>
</And>
</Where>
<ViewFields><FieldRef Name="ID" /></ViewFields>
</Query>
</View>`;
public constructor(lstDocLib?: string) {
this.lst_pageComments = "Page Comments";
this._list = sp.web.lists.getByTitle(this.lst_pageComments);
if (lstDocLib) {
this.lst_pageDocuments = lstDocLib;
}
}
public getDocLibInfo = async () => {
this._doclist = sp.web.lists.getById(this.lst_pageDocuments);
let listInfo: any = await this._doclist.select('Title').get();
this.lst_docListName = listInfo.Title;
}
public queryList = async (query, $list: IList) => {
return await $list.getItemsByCAMLQuery(query);
}
public getCurrentUserInfo = async () => {
let currentUserInfo = await sp.web.currentUser.get();
return ({
ID: currentUserInfo.Id,
Email: currentUserInfo.Email,
LoginName: currentUserInfo.LoginName,
DisplayName: currentUserInfo.Title,
Picture: '/_layouts/15/userphoto.aspx?size=S&username=' + currentUserInfo.UserPrincipalName,
});
}
public getSiteUsers = async (currentUserId: number) => {
let resusers = await sp.web.siteUsers.filter('IsHiddenInUI eq false and PrincipalType eq 1').get();
_.remove(resusers, (o) => { return o.Id == currentUserId || o.Email == ""; });
let userResults = [];
resusers.map((user) => {
userResults.push({
id: user.Id,
fullname: user.Title,
email: user.Email,
profile_picture_url: '/_layouts/15/userphoto.aspx?size=S&username=' + user.UserPrincipalName
});
});
return userResults;
}
public getPostAttachmentFilePath = async (pageUrl) => {
let pageName = pageUrl.split('/')[pageUrl.split('/').length - 1].split('.').slice(0, -1).join('.');
let res = await sp.web.select('ServerRelativeUrl').get();
let doclistName = (this.lst_docListName.toLowerCase() === 'documents') ? "Shared Documents" : this.lst_docListName;
return res.ServerRelativeUrl + "/" + doclistName + "/" + pageName;
}
public checkForPageFolder = async (postAttachmentPath) => {
let xml = this.cqPostDocs.replace('{{FilePath}}', postAttachmentPath);
let q = {
ViewXml: xml
};
let res = await this.queryList(q, this._doclist);
if (res.length > 0) return true; else return false;
}
public getPostComments = async (pageurl, currentUserInfo) => {
let pagecomments = await this._list.items.select('Comments', 'Likes', 'FieldValuesAsText/Comments', 'FieldValuesAsText/Likes')
.filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
if (pagecomments.length > 0) {
var tempComments = pagecomments[0].FieldValuesAsText.Comments;
var tempLikes = pagecomments[0].FieldValuesAsText.Likes;
if (tempLikes != undefined && tempLikes != null && tempLikes !== "") tempLikes = JSON.parse(tempLikes);
else tempLikes = [];
if (tempComments != undefined && tempComments != null && tempComments !== "") {
var jsonComments = JSON.parse(tempComments);
if (tempLikes.length > 0) {
tempLikes.map((liked) => {
var fil = _.find(jsonComments, (o) => { return o.id == liked.commentID; });
if (fil !== undefined && fil !== null) {
fil.upvote_count = liked.userVote.length;
var cufil = _.find(liked.userVote, (o) => { return o.userid == currentUserInfo.ID; });
if (cufil !== undefined && cufil !== null) fil.user_has_upvoted = true;
}
});
}
return jsonComments;
} else return [];
} else return [];
}
public getComment = async (pageurl) => {
let pagecomments = await this._list.items.select('Comments', 'FieldValuesAsText/Comments')
.filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
if (pagecomments.length > 0) return pagecomments[0].FieldValuesAsText.Comments;
else return null;
}
public addComment = async (pageUrl, comments) => {
let pageName = pageUrl.split('/')[pageUrl.split('/').length - 1];
let commentsToAdd = await sp.web.lists.getByTitle(this.lst_pageComments).items.add({
Title: pageName,
PageURL: pageUrl,
Comments: JSON.stringify(comments)
});
return commentsToAdd;
}
public updateComment = async (pageurl, comments) => {
let pageComment = await this._list.items.select('ID', 'PageURL').filter(`PageURL eq '${pageurl}'`).get();
if (comments.length > 0) {
if (pageComment.length > 0) {
let pageCommentsToUpdate = await this._list.items.getById(pageComment[0].ID).update({
Comments: JSON.stringify(comments)
});
return pageCommentsToUpdate;
}
} else {
return await this._list.items.getById(pageComment[0].ID).delete();
}
}
public postComment = async (pageurl, commentJson, currentUserInfo) => {
commentJson.created_by_current_user = false;
let comments = await this.getPostComments(pageurl, currentUserInfo);
if (comments.length > 0) {
comments.push(commentJson);
let updateComments = await this.updateComment(pageurl, comments);
return updateComments;
} else {
comments.push(commentJson);
let addComments = await this.addComment(pageurl, comments);
return addComments;
}
}
public addVoteForComment = async (pageurl, commentJson, currentUserInfo) => {
var tempLikes = [];
tempLikes.push({
commentID: commentJson.id,
userVote: [{ userid: currentUserInfo.ID, name: currentUserInfo.DisplayName }]
});
let pageComment = await this._list.items.select('ID').filter(`PageURL eq '${pageurl}'`).get();
if (pageComment.length > 0) {
return await this._list.items.getById(pageComment[0].ID).update({ Likes: JSON.stringify(tempLikes) });
}
}
public updateVoteForComment = async (pageurl, jsonLikes) => {
let pageComment = await this._list.items.select('ID').filter(`PageURL eq '${pageurl}'`).get();
if (pageComment.length > 0) {
return await this._list.items.getById(pageComment[0].ID).update({ Likes: JSON.stringify(jsonLikes) });
}
}
public voteComment = async (pageurl, commentJson, currentUserInfo) => {
let res = await this._list.items.select('Likes', 'FieldValuesAsText/Likes').filter(`PageURL eq '${pageurl}'`).expand('FieldValuesAsText').get();
if (res.length > 0) {
var tempLikes = res[0].FieldValuesAsText.Likes;
if (tempLikes != undefined && tempLikes != null && tempLikes !== "") {
// Likes already exits so update the item
var jsonLikes = JSON.parse(tempLikes);
var userAlreadyVoted = _.find(jsonLikes, (o) => { return o.commentID == commentJson.id && _.find(o.userVote, (oo) => { return oo.userid == currentUserInfo.ID; }); });
var userPresent = (userAlreadyVoted === undefined || userAlreadyVoted == null) ? false : true;
var fil = _.find(jsonLikes, (o) => { return o.commentID == commentJson.id; });
if (fil !== undefined && fil !== null) {
// Found likes for the comment id
if (commentJson.user_has_upvoted) {
if (!userPresent) fil.userVote = _.concat(fil.userVote, { userid: currentUserInfo.ID, name: currentUserInfo.DisplayName });
} else {
if (userPresent) {
if (fil !== undefined && fil !== null) _.remove(fil.userVote, (o) => { return o['userid'] == currentUserInfo.ID; });
}
}
} else {
// No likes found for the comment id
jsonLikes.push({ commentID: commentJson.id, userVote: [{ userid: currentUserInfo.ID, name: currentUserInfo.DisplayName }] });
}
return await this.updateVoteForComment(pageurl, jsonLikes);
} else {
// Likes doesn't exists so add new
if (commentJson.user_has_upvoted) return await this.addVoteForComment(pageurl, commentJson, currentUserInfo);
}
} else {
return commentJson;
}
}
public deleteComment = async (pageurl, commentJson) => {
let comments = await this.getComment(pageurl);
if (comments !== undefined && comments !== null) {
var jsonComments = JSON.parse(comments);
_.remove(jsonComments, (o) => { return o['id'] == commentJson.id; });
return await this.updateComment(pageurl, jsonComments);
}
}
public editComments = async (pageurl, commentJson) => {
let comment = await this.getComment(pageurl);
if (comment !== undefined && comment !== null) {
var jsonComments = JSON.parse(comment);
var match = _.find(jsonComments, (o) => { return o.id == commentJson.id; });
if (match) _.merge(match, { pings: commentJson.pings, content: commentJson.content, modified: commentJson.modified });
return await this.updateComment(pageurl, jsonComments);
}
}
public createFolder = async (folderPath) => {
return await sp.web.folders.add(folderPath);
}
public uploadFileToFolder = async (folderpath, fileinfo) => {
return await sp.web.getFolderByServerRelativeUrl(folderpath).files.add(fileinfo.name, fileinfo.content, true);
}
public postAttachments = async (commentArray: any[], pageFolderExists, postAttachmentPath): Promise<any> => {
var self = this;
return new Promise(async (resolve, reject) => {
if (!pageFolderExists) await this.createFolder(postAttachmentPath);
var reader = new FileReader();
reader.onload = async () => {
var contentBuffer = reader.result;
let uploadedFile = await self.uploadFileToFolder(postAttachmentPath, { name: commentArray[0].file.name, content: contentBuffer });
_.set(commentArray[0], 'file_id', uploadedFile.data.UniqueId);
_.set(commentArray[0], 'file_url', postAttachmentPath + "/" + commentArray[0].file.name);
resolve(commentArray);
};
await reader.readAsArrayBuffer(commentArray[0].file);
});
}
public checkListExists = async (): Promise<boolean> => {
return new Promise<boolean>(async (res, rej) => {
sp.web.lists.getByTitle(this.lst_pageComments).get().then((listExists) => {
res(true);
}).catch(async err => {
let listExists = await (await sp.web.lists.ensure(this.lst_pageComments)).list;
await listExists.fields.addText('PageURL', 255, { Required: true, Description: '' });
await listExists.fields.addMultilineText('Comments', 6, false, false, false, false, { Required: true, Description: '' });
await listExists.fields.addMultilineText('Likes', 6, false, false, false, false, { Required: false, Description: '' });
let allItemsView = await listExists.views.getByTitle('All Items');
await allItemsView.fields.add('PageURL');
await allItemsView.fields.add('Comments');
await allItemsView.fields.add('Likes');
res(true);
});
});
}
}

View File

@ -0,0 +1,789 @@
/*jquery-comments.js 1.4.0
(c) 2017 Joona Tykkyläinen, Viima Solutions Oy
jquery-comments may be freely distributed under the MIT license.
For all details and documentation:
http://viima.github.io/jquery-comments/*/
.jquery-comments * {
box-sizing: border-box;
text-shadow: none;
}
.jquery-comments a[href] {
color: #2793e6;
text-decoration: none;
font-size: 13px;
font-weight: normal;
}
.jquery-comments a[href]:hover {
text-decoration: none;
font-weight: bold;
}
.jquery-comments a[href]:visited {
text-decoration: none;
color: #2793e6;
}
.jquery-comments .textarea, .jquery-comments input, .jquery-comments button {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
vertical-align: top;
border-radius: 0;
margin: 0;
padding: 0;
border: 0;
outline: 0;
background: rgba(0, 0, 0, 0);
}
.jquery-comments button {
vertical-align: inherit;
}
.jquery-comments .tag {
color: inherit;
font-size: 0.9em;
line-height: 1.2em;
background: #ddd;
border: 1px solid #ccc;
padding: 0.1em 0.3em;
cursor: pointer;
font-weight: normal;
border-radius: 1em;
transition: all 0.2s linear;
}
.jquery-comments .tag:hover {
text-decoration: none;
background-color: #d8edf8;
border-color: #2793e6;
}
.jquery-comments [contentEditable=true]:empty:not(:focus):before{
content:attr(data-placeholder);
color: #CCC;
position: inherit;
}
.jquery-comments i.fa {
width: 1em;
height: 1em;
background-size: cover;
}
.jquery-comments i.fa.image:before {
content: "";
}
.jquery-comments .spinner {
font-size: 2em;
text-align: center;
padding: 0.5em;
color: #666;
}
.jquery-comments ul {
list-style: none;
padding: 0;
margin: 0;
}
.jquery-comments .profile-picture {
float: left;
width: 2.5rem;
height: 2.5rem;
max-width: 50px;
max-height: 50px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
.jquery-comments i.profile-picture {
font-size: 3.4em;
text-align: center;
}
.jquery-comments .profile-picture.round {
border-radius: 50%;
}
.jquery-comments .commenting-field.main{
margin-bottom: 0.75em;
}
.jquery-comments .commenting-field.main .profile-picture {
margin-bottom: 0.5rem;
}
.jquery-comments .textarea-wrapper {
overflow: hidden;
padding-left: 15px;
position: relative;
}
.jquery-comments .textarea-wrapper:before {
content: " ";
position: absolute;
border: 5px solid #D5D5D5;
left: 5px;
top: 0;
width: 10px;
height: 10px;
box-sizing: border-box;
border-bottom-color: rgba(0, 0, 0, 0);
border-left-color: rgba(0, 0, 0, 0);
}
.jquery-comments .textarea-wrapper:after {
content: " ";
position: absolute;
border: 7px solid #FFF;
left: 7px;
top: 1px;
width: 10px;
height: 10px;
box-sizing: border-box;
border-bottom-color: rgba(0, 0, 0, 0);
border-left-color: rgba(0, 0, 0, 0);
}
.jquery-comments .textarea-wrapper .inline-button {
cursor: pointer;
right: 0;
z-index: 10;
position: absolute;
border: .5em solid rgba(0,0,0,0);
box-sizing: content-box;
font-size: inherit;
overflow: hidden;
opacity: 0.5;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.jquery-comments .textarea-wrapper .inline-button:hover {
opacity: 1;
}
.jquery-comments:not(.mobile) .commenting-field-scrollable .textarea-wrapper .inline-button {
margin-right: 15px; /* Because of scrollbar */
}
.jquery-comments .textarea-wrapper .upload.inline-button i {
font-size: 1.3em;
}
.jquery-comments .textarea-wrapper .upload input {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
min-width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0;
}
.jquery-comments .textarea-wrapper .close {
width: 1em;
height: 1em;
}
.jquery-comments .textarea-wrapper .close span {
background: #999;
width: 25%;
left: 37.5%;
height: 100%;
position: absolute;
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.jquery-comments .textarea-wrapper .close .right {
-ms-transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.jquery-comments .textarea-wrapper .textarea {
margin: 0;
outline: 0;
overflow-y: auto;
overflow-x: hidden;
cursor: text;
border: 1px solid #CCC;;
background: #FFF;
font-size: 1em;
line-height: 1.45em;
padding: .25em .8em;
padding-right: 2em;
}
.jquery-comments:not(.mobile) .commenting-field-scrollable .textarea-wrapper .textarea {
padding-right: calc(2em + 15px); /* Because of scrollbar */
}
.jquery-comments .textarea-wrapper .control-row > span {
float: right;
color: #FFF;
padding: 0 1em;
font-size: 1em;
line-height: 1.6em;
margin-top: .4em;
border: 1px solid rgba(0, 0, 0, 0);
opacity: .5;
}
.jquery-comments .textarea-wrapper .control-row > span:not(:first-child) {
margin-right: .5em;
}
.jquery-comments .textarea-wrapper .control-row > span.enabled {
opacity: 1;
cursor: pointer;
}
.jquery-comments .textarea-wrapper .control-row > span:not(.enabled) {
pointer-events: none;
}
.jquery-comments .textarea-wrapper .control-row > span.enabled:hover {
opacity: .9;
}
.jquery-comments .textarea-wrapper .control-row > span.upload {
position: relative;
overflow: hidden;
background-color: #999;
}
.jquery-comments ul.navigation {
clear: both;
color: #CCC;
border-bottom: 2px solid #CCC;
line-height: 2em;
font-size: 1em;
margin-bottom: 0.5em;
}
.jquery-comments ul.navigation .navigation-wrapper {
position: relative;
}
.jquery-comments ul.navigation li {
display: inline-block;
position: relative;
padding: 0 1em;
cursor: pointer;
font-weight: bold;
text-align: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.jquery-comments ul.navigation li.active,
.jquery-comments ul.navigation li:hover {
color: #000;
}
.jquery-comments ul.navigation li.active:after {
content: " ";
display: block;
right: 0;
height: 2px;
background: #000;
position: absolute;
bottom: -2px;
left: 0;
}
.jquery-comments ul.navigation li[data-sort-key="attachments"] {
float: right;
}
.jquery-comments ul.navigation li[data-sort-key="attachments"] i {
margin-right: 0.25em;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive {
display: none;
}
@media screen and (max-width: 600px) {
.jquery-comments ul.navigation .navigation-wrapper {
display: none;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive {
display: inline;
}
}
.jquery-comments.responsive ul.navigation .navigation-wrapper {
display: none;
}
.jquery-comments.responsive ul.navigation .navigation-wrapper.responsive {
display: inline;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title {
padding: 0 1.5em;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title header:after {
display: inline-block;
content: "";
border-left: 0.3em solid rgba(0, 0, 0, 0) !important;
border-right: 0.3em solid rgba(0, 0, 0, 0) !important;
border-top: 0.4em solid #CCC;
margin-left: 0.5em;
position: relative;
top: -0.1em;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title.active header:after,
.jquery-comments ul.navigation .navigation-wrapper.responsive li.title:hover header:after {
border-top-color: #000;
}
.jquery-comments ul.dropdown {
display: none;
position: absolute;
background: #FFF;
z-index: 99;
line-height: 1.2em;
border: 1px solid #CCC;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
-moz-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
-ms-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}
.jquery-comments ul.dropdown.autocomplete {
margin-top: 0.25em;
}
.jquery-comments ul.dropdown li {
display: block;
white-space: nowrap;
clear: both;
padding: 0.6em;
font-weight: normal;
cursor: pointer;
}
.jquery-comments ul.dropdown li.active {
background: #EEE;
}
.jquery-comments ul.dropdown li a {
display: block;
text-decoration: none;
color: inherit;
}
.jquery-comments ul.dropdown li .profile-picture {
float: left;
width: 2.4em;
height: 2.4em;
margin-right: 0.5em;
}
.jquery-comments ul.dropdown li .details {
display: inline-block;
}
.jquery-comments ul.dropdown li .name {
font-weight: bold;
}
.jquery-comments ul.dropdown li .details.no-email {
line-height: 2.4em;
}
.jquery-comments ul.dropdown li .email {
color: #999;
font-size: 0.95em;
margin-top: 0.1em;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown {
left: 0;
width: 100%;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li {
color: #000;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li.active {
color: #FFF;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li:hover:not(.active) {
background: #F5F5F5;
}
.jquery-comments ul.navigation .navigation-wrapper.responsive ul.dropdown li:after {
display: none;
}
.jquery-comments .no-data {
display: none;
margin: 1em;
text-align: center;
font-size: 1.5em;
color: #CCC;
}
.jquery-comments ul.main:empty ~ .no-comments {
display: inherit;
}
.jquery-comments ul#attachment-list:empty ~ .no-attachments {
display: inherit;
}
.jquery-comments ul.main li.comment {
clear: both;
}
.jquery-comments ul.main li.comment .comment-wrapper,
.jquery-comments ul.main li.toggle-all,
.jquery-comments ul.main li.comment .commenting-field {
padding: .5em;
}
.jquery-comments ul.main li.comment .comment-wrapper {
border-top: 1px solid #DDD;
overflow: hidden;
}
.jquery-comments ul.main > li.comment:first-child > .comment-wrapper {
border-top: none;
}
.jquery-comments ul.main li.comment .comment-wrapper > .profile-picture {
margin-right: 1rem;
}
.jquery-comments ul.main li.comment time {
float: right;
line-height: 1.4em;
margin-left: .5em;
font-size: 0.8em;
color: #666;
}
.jquery-comments ul.main li.comment .name {
line-height: 1.4em;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-bottom: 5px;
}
.jquery-comments ul.main li.comment .name a {
color: inherit;
}
.jquery-comments ul.main li.comment .name .reply-to {
color: #999;
font-size: .8em;
font-weight: normal;
vertical-align: top;
}
.jquery-comments ul.main li.comment .name .reply-to i {
margin-left: .5em;
margin-right: .25em;
}
.jquery-comments ul.main li.comment .name .new {
margin-left: .5em;
background: #2793e6;
font-size: 0.8em;
padding: 0.2em 0.5em;
color: #fff;
font-weight: normal;
border-radius: 1em;
vertical-align: bottom;
}
.jquery-comments ul.main li.comment .wrapper{
line-height: 1.4em;
overflow: hidden;
}
.jquery-comments.mobile ul.main li.comment .child-comments li.comment .wrapper{
overflow: visible;
}
/* Content */
.jquery-comments ul.main li.comment .wrapper .content {
white-space: pre-line;
word-break: break-word;
}
.jquery-comments ul.main li.comment .wrapper .content a.attachment i {
margin-right: 0.5em;
}
.jquery-comments ul.main li.comment .wrapper .content a.attachment > * {
max-width: 100%;
max-height: 300px;
width: auto;
height: auto;
margin-top: .25em;
margin-bottom: .25em;
}
.jquery-comments ul.main li.comment .wrapper .content time.edited {
float: inherit;
margin: 0;
font-size: .9em;
font-style: italic;
color: #999;
}
.jquery-comments ul.main li.comment .wrapper .content time.edited:before {
content: " - ";
}
/* Actions */
.jquery-comments.mobile ul.main li.comment .actions {
font-size: 1em;
}
.jquery-comments ul.main li.comment .actions > * {
color: #999;
font-weight: bold;
}
.jquery-comments ul.main li.comment .actions .action {
display: inline-block;
cursor: pointer;
margin-left: 1em;
margin-right: 1em;
line-height: 1.5em;
font-size: 0.9em;
}
.jquery-comments ul.main li.comment .actions .action:first-child {
margin-left: 0;
}
.jquery-comments ul.main li.comment .actions .action.upvote {
cursor: inherit;
}
.jquery-comments ul.main li.comment .actions .action.upvote .upvote-count {
margin-right: .5em;
}
.jquery-comments ul.main li.comment .actions .action.upvote .upvote-count:empty {
display: none;
}
.jquery-comments ul.main li.comment .actions .action.upvote i {
cursor: pointer;
}
.jquery-comments ul.main li.comment .actions .action:not(.upvote):hover,
.jquery-comments ul.main li.comment .actions .action.upvote:not(.highlight-font) i:hover {
color: #666;
}
.jquery-comments ul.main li.comment .actions .action.delete {
opacity: 0.5;
pointer-events: none;
}
.jquery-comments ul.main li.comment .actions .action.delete.enabled {
opacity: 1;
pointer-events: auto;
}
.jquery-comments ul#attachment-list li.comment .actions .action:not(.delete) {
display: none;
}
.jquery-comments ul#attachment-list li.comment .actions .action.delete {
margin: 0;
}
.jquery-comments ul#attachment-list li.comment .actions .separator {
display: none;
}
/* Child comments */
.jquery-comments ul.main li.comment .child-comments > *:before { /* Margin for second level content */
content: "";
height: 1px;
float: left;
width: calc(3.6em + .5em); /* Profile picture width plus margin */
max-width: calc(50px + .5em); /* Profile picture max width plus margin */
}
.jquery-comments ul.main li.comment .child-comments .profile-picture {
width: 2.4rem;
height: 2.4rem;
}
.jquery-comments ul.main li.comment .child-comments i.profile-picture {
font-size: 2.4em;
}
.jquery-comments ul.main li.comment .child-comments li.toggle-all {
padding-top: 0;
}
.jquery-comments ul.main li.comment .child-comments li.toggle-all span:first-child {
vertical-align: middle;
}
.jquery-comments ul.main li.comment .child-comments li.toggle-all span:first-child:hover {
cursor: pointer;
text-decoration: underline;
}
.jquery-comments ul.main li.comment .child-comments li.toggle-all .caret {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: .5em;
border: .3em solid;
margin-top: .35em;
border-left-color: rgba(0, 0, 0, 0);
border-bottom-color: rgba(0, 0, 0, 0);
border-right-color: rgba(0, 0, 0, 0);
}
.jquery-comments ul.main li.comment .child-comments li.toggle-all .caret.up {
border-top-color: rgba(0, 0, 0, 0);
border-bottom-color: inherit;
margin-top: -.2em;
}
.jquery-comments ul.main li.comment .child-comments .togglable-reply {
display: none;
}
.jquery-comments ul.main li.comment .child-comments .visible {
display: inherit;
}
.jquery-comments ul.main li.comment.hidden {
display: none;
}
/* Editing comment */
.jquery-comments ul.main li.comment.edit > .comment-wrapper > *:not(.commenting-field) {
display: none;
}
.jquery-comments ul.main li.comment.edit > .comment-wrapper .commenting-field {
padding: 0 !important; /* Parent element has the padding */
}
/* Drag & drop attachments */
.jquery-comments.drag-ongoing {
overflow-y: hidden !important;
}
.jquery-comments .droppable-overlay {
display: table;
position: fixed;
z-index: 99;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.3)
}
.jquery-comments .droppable-overlay .droppable-container {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.jquery-comments .droppable-overlay .droppable-container .droppable {
background: #FFF;
color: #CCC;
padding: 6em;
}
.jquery-comments .droppable-overlay .droppable-container .droppable.drag-over {
color: #999;
}
.jquery-comments .droppable-overlay .droppable-container .droppable i {
margin-bottom: 5px;
}
/* Read-only mode */
.jquery-comments.read-only .commenting-field {
display: none;
}
.jquery-comments.read-only .actions {
display: none;
}
.displayctrl {
display: '' !important;
}
.hidectrl {
display: none !important;
}
.greenColor {
color: green;
}
.blueColor {
color: blue;
}
.redColor {
color: red;
}
.yellowColor {
color: yellow;
}
.jquery-comments .msgContainer {
margin-top: 5px;
display: inline-block;
}
.jquery-comments .errorMsg {
background: #f3c4c4;
}
.jquery-comments .msgAlert {
padding: 10px;
font-size: 12px;
font-weight: 500;
width: 100%;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
define([], function () {
return {
"PropertyPaneDescription": "",
"BasicGroupName": "Settings",
DateTimeFormatLabel: "Datetime format",
DateTimeFormatDescription: "Please use the moment datetime format option.",
RoundProfilePicLabel: "Profile Picture rounded",
RoundProfilePicDescription: "Enable to display the user profile picture as rounded.",
NavigationLabel: "Navigation",
NavigationDescription: "Tabs like Newest, Oldest, Popular & Attachments will be displayed.",
AttachmentLabel: "Attachments",
AttachmentDescription: "Allow the users to upload files.",
PingLabel: "Ping users",
PingDescription: "Allow users to ping other users in the comments.",
EditingLabel: "Edit Comments",
EditingDescription: "Allow the users to edit their own comments.",
UpVotingLabel: "Upvoting of comments",
UpVotingDescription: "Allow the users to upvote the comments.",
ReplyLabel: "Reply to comments",
ReplyDescription: "Allow the users to reply to the comments.",
DeleteLabel: "Delete own comments",
DeleteDescription: "Allow the users to delete their own comments.",
DeleteRepliesLabel: "Delete own comments with replies",
DeleteRepliesDescription: "Allow the users to delete their own comments with replies.",
HashtagsLabel: "Hashtags",
HashtagsDescription: "Allow the users to use hashtags.",
DocumentPreviewLabel: "Document Preview",
DocumentPreviewDescription: "Allow the users to view few of the document formats inline.",
AttachmentFileFormatLabel: "Allowed Files",
AttachmentFileFormatDescription: "Allowed files to attach. Please enter file extensions separated by ,",
AttachmentFileSizeLabel: "Max File Size",
AttachmentFileSizeDescription: "Maximum file size(MB) allowed to attach.",
AttachmentRepoLabel: "Select a libary",
AttachmentRepoPropValMsg: "Please choose the document library",
NoAttachmentRepoMsg: "Please select the document library to store the attachments.",
LoadingMsg: "Please wait..."
}
});

View File

@ -0,0 +1,42 @@
declare interface IPageCommentsWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DateTimeFormatLabel: string;
DateTimeFormatDescription: string;
RoundProfilePicLabel: string;
RoundProfilePicDescription: string;
NavigationLabel: string;
NavigationDescription: string;
AttachmentLabel: string;
AttachmentDescription: string;
PingLabel: string;
PingDescription: string;
EditingLabel: string;
EditingDescription: string;
UpVotingLabel: string;
UpVotingDescription: string;
ReplyLabel: string;
ReplyDescription: string;
DeleteLabel: string;
DeleteDescription: string;
DeleteRepliesLabel: string;
DeleteRepliesDescription: string;
HashtagsLabel: string;
HashtagsDescription: string;
DocumentPreviewLabel: string;
DocumentPreviewDescription: string;
AttachmentFileFormatLabel: string;
AttachmentFileFormatDescription: string;
AttachmentFileSizeLabel: string;
AttachmentFileSizeDescription: string;
AttachmentRepoLabel: string;
AttachmentRepoPropValMsg: string;
NoAttachmentRepoMsg: string;
LoadingMsg: string;
}
declare module 'PageCommentsWebPartStrings' {
const strings: IPageCommentsWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}