This is a simple Google Docs script that counts the number of comments in a Google Doc and displays the count in a popup dialogue.
Terms of Service
You can do whatever you want with this. Indie Art Creatives LLC shall not be liable for any damages arising from the use of this add-on.
Privacy Policy
Nothing is stored or used in any way. All this add-on does is run a function to count your comments and display it in a popup dialogue, communicating and storing nothing in the process.
Support
No support is provided for this application. Just use the menu that shows up.
Source Code
/**
* Comment Counter
*
* Google Workspace add-on for Google Docs.
* Uses per-file drive.file authorization to count comments
* without requesting access to the user's entire Google Drive.
*/
/**
* Runs when the add-on homepage opens in Google Docs.
*/
function onDocsHomepage(e) {
return [buildCommentCounterCard_(e)];
}
/**
* Runs after the user grants this add-on access
* to the active Google Doc.
*/
function onFileScopeGranted(e) {
return [buildCommentCounterCard_(e)];
}
/**
* Builds the Comment Counter card.
*/
function buildCommentCounterCard_(e) {
const card = CardService.newCardBuilder()
.setHeader(
CardService.newCardHeader()
.setTitle('Comment Counter')
.setSubtitle('Comments in this document')
);
const section = CardService.newCardSection();
const docs = e && e.docs ? e.docs : null;
/*
* FILE ACCESS NOT YET GRANTED
*
* Only show the authorization button when Google tells us
* that file-scope permission has not been granted.
*/
if (
!docs ||
!docs.addonHasFileScopePermission ||
!docs.id
) {
section.addWidget(
CardService.newTextParagraph()
.setText(
'Comment Counter needs permission to access this document before it can count comments.'
)
);
const permissionAction = CardService.newAction()
.setFunctionName('requestFilePermission');
section.addWidget(
CardService.newTextButton()
.setText('Allow Access to This Document')
.setTextButtonStyle(
CardService.TextButtonStyle.FILLED
)
.setOnClickAction(permissionAction)
);
card.addSection(section);
return card.build();
}
/*
* FILE ACCESS HAS BEEN GRANTED
*
* From this point forward, do not send the user back
* to the authorization prompt because of a Drive API error.
*/
try {
const counts = getCommentCounts_(docs.id);
section.addWidget(
CardService.newKeyValue()
.setTopLabel('Active comments')
.setContent(String(counts.active))
);
section.addWidget(
CardService.newKeyValue()
.setTopLabel('All comments')
.setContent(String(counts.total))
);
const refreshAction = CardService.newAction()
.setFunctionName('refreshCounts');
section.addWidget(
CardService.newTextButton()
.setText('Refresh')
.setTextButtonStyle(
CardService.TextButtonStyle.FILLED
)
.setOnClickAction(refreshAction)
);
} catch (error) {
console.error(error);
/*
* Permission was already confirmed by
* addonHasFileScopePermission, so an API error here
* should not cause another authorization prompt.
*/
section.addWidget(
CardService.newTextParagraph()
.setText(
'Comment counts could not be loaded. Please try again.'
)
);
const retryAction = CardService.newAction()
.setFunctionName('refreshCounts');
section.addWidget(
CardService.newTextButton()
.setText('Try Again')
.setOnClickAction(retryAction)
);
}
card.addSection(section);
return card.build();
}
/**
* Requests drive.file access for the active Google Doc.
*/
function requestFilePermission() {
return CardService
.newEditorFileScopeActionResponseBuilder()
.requestFileScopeForActiveDocument()
.build();
}
/**
* Refreshes the card and reloads the current counts.
*/
function refreshCounts(e) {
const card = buildCommentCounterCard_(e);
return CardService.newActionResponseBuilder()
.setNavigation(
CardService.newNavigation()
.updateCard(card)
)
.build();
}
/**
* Retrieves the active and total comment counts.
*/
function getCommentCounts_(fileId) {
let pageToken = null;
let active = 0;
let total = 0;
do {
const options = {
fields: 'comments(id,resolved),nextPageToken',
pageSize: 100
};
if (pageToken) {
options.pageToken = pageToken;
}
let response;
try {
response = Drive.Comments.list(fileId, options);
} catch (error) {
const message = String(error);
/*
* File-scope permission has already been confirmed
* before this function is called.
*
* A new/blank document may return "File not found"
* from comments.list(). In that situation there are
* effectively no comments to count.
*
* Do NOT send the user back through authorization.
*/
if (message.includes('File not found')) {
return {
active: 0,
total: 0
};
}
/*
* Any other Drive/API error is unexpected and should
* be passed upward to the card's normal error handler.
*/
throw error;
}
/*
* A document containing no comments may return no
* comments property. Treat that as an empty array.
*/
const comments = response.comments || [];
total += comments.length;
comments.forEach(function(comment) {
if (!comment.resolved) {
active++;
}
});
pageToken = response.nextPageToken || null;
} while (pageToken);
return {
active: active,
total: total
};
}