- Created by Robert Reiner, last modified on 16. Nov 2020
projectdoc Toolbox
Renders a menu with tools to inspect information from a projectdoc document, shown in the browser.
- Tags
- Identifier
de.smartics.userscripts.confluence.projectdoc-inspect-menu
- Type
- Repository
- Since
- 1.0
The userscript renders a menu on a Confluence page with tools to inspect the projectdoc document shown in the browser.
It implements the following actions:
- Display Document Properties
- Display Space Properties
- Display Transcluding Documents
Code
The code of the script for reference.
/* * Copyright 2019-2024 Kronseder & Reiner GmbH, smartics * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ "use strict"; AJS.toInit(function () { const logToConsole = USERSCRIPT4C.isVerboseLoggingRequestedFor('projectdoc-inspect-menu'); const $propertiesMarker = AJS.$(".projectdoc-document-element.properties"); if (!$propertiesMarker.length) { if (logToConsole) AJS.log("[projectdoc-inspect-menu] Not a projectdoc document. Quitting."); return; } const appendHead = function (htmlTitle, $html) { const $head = AJS.$('<head></head>').append(AJS.$('<title></title>', {'text': htmlTitle})) .append(AJS.$('<style></style>', { 'text': 'body {margin: 1rem !important;}' + ' .table-sm td, .table-sm th {padding: .1rem !important;}' + ' .table td, .table th { font-size: .8rem !important;}' })) .append(AJS.$('<link/>', { 'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous' })); $head.appendTo($html); return $head }; const createStandardTable = function ($rootElement) { return AJS.$('<table></table>').addClass('table table-sm table-bordered table-striped').appendTo($rootElement); }; const appendRow = function ($table, url, name, shortDescription) { const $tr = AJS.$('<tr></tr>').appendTo($table); AJS.$('<th></th>').append(AJS.$('<a></a>', { 'href': url, 'text': name })).appendTo($tr); AJS.$('<td></td>').text(shortDescription).appendTo($tr); }; const createHitCountIntroText = function (intro, hitCount) { return intro + (hitCount === 0 ? 'no document.' : (hitCount === 1 ? 'one document.' : hitCount + ' documents.')); }; const showDialog = function ($html) { const showDialog = window.open('', '', 'width=750,height=800,location=no,toolbar=0'); showDialog.document.body.innerHTML = $html.prop('outerHTML'); }; const showDocumentProperties = function () { if (logToConsole) AJS.log("[projectdoc-inspect-menu] Fetching document properties ..."); const pageId = AJS.Meta.get('page-id'); const locale = AJS.Meta.get('user-locale'); const baseURL = AJS.Meta.get('base-url'); const htmlTitle = "Page Properties"; const $html = AJS.$("<html></html>").attr("lang", locale); appendHead(htmlTitle, $html); const $body = AJS.$('<body></body>'); AJS.$('<h6></h6>').text(htmlTitle).appendTo($body); $body.appendTo($html); if (logToConsole) AJS.log("[projectdoc-inspect-menu] Making call ..."); AJS.$.ajax({ url: baseURL + "/rest/projectdoc/1/document/" + pageId + ".json?expand=property&resource-mode=html", async: true, contentType: 'application/json' }).success(function (data) { if (logToConsole) AJS.log("[projectdoc-inspect-menu] Document Properties Data: " + JSON.stringify(data)); const $table = createStandardTable($body); AJS.$.each(data["property"], function (index, obj) { const $tr = AJS.$('<tr></tr>').appendTo($table); AJS.$('<th></th>').text(obj.name).appendTo($tr); // Render the content as HTML fragment AJS.$('<td></td>').append(obj.value).appendTo($tr); } ); showDialog($html); }).error(function (jqXHR, textStatus) { AJS.log("[projectdoc-inspect-menu] Error fetching document properties: " + jqXHR.status + " (" + textStatus + ")"); alert("Failed to fetch document properties: " + jqXHR.status + " (" + textStatus + ")"); }); }; const showSpaceProperties = function () { const spaceKey = AJS.Meta.get('space-key'); const locale = AJS.Meta.get('user-locale'); const baseUrl = AJS.Meta.get('base-url'); const htmlTitle = "Space Properties for " + spaceKey; const $html = AJS.$("<html></html>").attr("lang", locale); appendHead(htmlTitle, $html); const $body = AJS.$('<body></body>'); AJS.$('<h6></h6>').text(htmlTitle).appendTo($body); $body.appendTo($html); if (logToConsole) AJS.log("[projectdoc-inspect-menu] Querying Properties Data for Space '" + spaceKey + "' ..."); AJS.$.ajax({ url: baseUrl + "/rest/projectdoc/1/space/" + spaceKey, async: true, dataType: 'json' //contentType: 'application/json' }).success(function (data) { if (logToConsole) AJS.log("[projectdoc-inspect-menu] Space Properties Data: " + JSON.stringify(data)); const $table = createStandardTable($body); AJS.$.each(data["property"], function (index, obj) { const $tr = AJS.$("<tr></tr>").appendTo($table); AJS.$("<td></td>").text(obj.source).appendTo($tr); AJS.$("<th></th>").text(obj.name).appendTo($tr); // Render the content as HTML fragment AJS.$("<td></td>").append(obj.value).appendTo($tr); } ); showDialog($html); }).error(function (jqXHR, textStatus) { AJS.log("[projectdoc-inspect-menu] Error fetching space properties: " + jqXHR.status + " (" + textStatus + ")"); alert("Failed to fetch space properties: " + jqXHR.status + " (" + textStatus + ")"); }); }; function renderDocumentTable(hitCount, $body, documents, i18n, tinyUrlNamePlain) { if (hitCount > 0) { const $tableTransclusions = createStandardTable($body); AJS.$.each(documents.document, function (_index, doc) { const current = {}; AJS.$.each(doc.property, function (i, property) { current[property.name] = property.value; }); const name = current[i18n.name]; const shortDescription = current[i18n.shortDescription]; const url = current[tinyUrlNamePlain]; appendRow($tableTransclusions, url, name, shortDescription); }); } } function renderTableWithAnchorLinks(currentDocument, $body, hitCount, documents, i18n, tinyUrlNamePlain) { const extractAnchorId = function (anchorLink, dynamicLinkPrefix, staticLinkPrefix) { const length = anchorLink.length; if (anchorLink.startsWith(dynamicLinkPrefix)) { const i = dynamicLinkPrefix.length + 1; if (length > i) { return anchorLink.substring(i); } } else if (anchorLink.startsWith(staticLinkPrefix)) { const i = staticLinkPrefix.length + 1; if (length > i) { return anchorLink.substring(i); } } return null; }; const anchorLinksName = i18n["projectdoc.doctype.common.metadata.anchorLinks"]; if (hitCount > 0) { const documentUrl = currentDocument[tinyUrlNamePlain]; const documentTitle = currentDocument[i18n.title]; const documentSpaceKey = currentDocument[i18n.spaceKey]; const dynamicLinkPrefix = "*." + documentTitle; const staticLinkPrefix = documentSpaceKey + "." + documentTitle; const $table = createStandardTable($body); AJS.$.each(documents.document, function (_index, doc) { const current = {}; AJS.$.each(doc.property, function (i, property) { current[property.name] = property.value; }); const name = current[i18n.name]; const anchorLinks = current[anchorLinksName]; AJS.log("Value for property '" + anchorLinksName + "' of document '" + name + "' is: " + anchorLinks); if (anchorLinks) { const shortDescription = current[i18n.shortDescription]; const url = current[tinyUrlNamePlain]; const $tr = AJS.$('<tr></tr>').appendTo($table); AJS.$('<th></th>').append(AJS.$('<a></a>', { 'href': url, 'text': name })).appendTo($tr); AJS.$('<td></td>').text(shortDescription).appendTo($tr); const $list = AJS.$('<ul style="margin-bottom: 0;"></ul>'); AJS.$.each(anchorLinks.split(/,\s*/), function (_index, anchorLink) { const anchorId = extractAnchorId(anchorLink, dynamicLinkPrefix, staticLinkPrefix); if (anchorId != null) { AJS.$('<li></li>').append(AJS.$('<a></a>', { 'href': documentUrl + "#" + encodeURIComponent(anchorId), 'text': anchorId })).appendTo($list); } }); AJS.$('<td></td>').append($list).appendTo($tr); } }); } } const listTranscludingDocument = function () { const pageId = AJS.Meta.get('page-id'); const locale = AJS.Meta.get('user-locale'); const baseUrl = AJS.Meta.get('base-url'); function createPage(i18n, currentDocument, transclusionDocuments, delegateDocuments, dynamicLinkTitlesDocuments, doctypeNameReferencesDocuments, anchorLinkDocuments) { const documentName = currentDocument[i18n.name].trim(); const htmlTitle = "Transcluding Documents for " + documentName; const $html = AJS.$("<html></html>").attr("lang", locale); appendHead(htmlTitle, $html); const transclusionHitCount = transclusionDocuments.document.length; const tinyUrlNamePlain = i18n["tinyUrl"] + '\u00a7'; const $body = AJS.$('<body></body>').appendTo($html); AJS.$('<h3></h3>') .text(htmlTitle).appendTo($body); AJS.$("<p></p>") .append(document.createTextNode('Content of document ')) .append(AJS.$('<a/>', { 'href': currentDocument[tinyUrlNamePlain], 'text': documentName })) .append(document.createTextNode(createHitCountIntroText(' is transcluded by ', transclusionHitCount))).appendTo($body); renderDocumentTable(transclusionHitCount, $body, transclusionDocuments, i18n, tinyUrlNamePlain); const delegateHitCount = delegateDocuments.document.length; AJS.$('<p></p>').text(createHitCountIntroText('The document is delegate of ', delegateHitCount)).appendTo($body); renderDocumentTable(delegateHitCount, $body, delegateDocuments, i18n, tinyUrlNamePlain); const dynamicLinksHitCount = dynamicLinkTitlesDocuments.document.length; AJS.$('<p></p>').text(createHitCountIntroText('The document is a possible target for dynamic links in ', dynamicLinksHitCount)).appendTo($body); renderDocumentTable(dynamicLinksHitCount, $body, dynamicLinkTitlesDocuments, i18n, tinyUrlNamePlain); const doctypeNameReferencesHitCount = doctypeNameReferencesDocuments.document.length; AJS.$('<p></p>').text(createHitCountIntroText('The document is a possible target for Doctype/Name reference in ', doctypeNameReferencesHitCount)).appendTo($body); renderDocumentTable(doctypeNameReferencesHitCount, $body, doctypeNameReferencesDocuments, i18n, tinyUrlNamePlain); const anchorLinkDocumentsHitCount = anchorLinkDocuments.document.length; AJS.$('<p></p>').text(createHitCountIntroText('The document is a possible target for anchor links in ', anchorLinkDocumentsHitCount)).appendTo($body); renderTableWithAnchorLinks(currentDocument, $body, anchorLinkDocumentsHitCount, anchorLinkDocuments, i18n, tinyUrlNamePlain); return $html; } if (PDBMLS) { const i18n = PDBMLS.fetchI18n(baseUrl, ["title", "spaceKey", "doctype", "name", "shortDescription", "tinyUrl", "projectdoc.doctype.common.delegateDocument.pageRef", "projectdoc.doctype.common.metadata.dynamicLinkTitles", "projectdoc.doctype.common.metadata.documentDoctypeNameReferences", "projectdoc.doctype.common.metadata.anchorLinkDocuments", "projectdoc.doctype.common.metadata.anchorLinks"]); const tinyUrlNamePlain = i18n["tinyUrl"] + '\u00a7'; const currentDocument = PDBMLS.fetchDocument(baseUrl, pageId, [i18n.spaceKey, i18n.title, i18n.doctype, i18n.name, tinyUrlNamePlain]); if (currentDocument) { const spaceKey = currentDocument[i18n.spaceKey]; const title = currentDocument[i18n.title]; const pageReference = spaceKey + "." + title; const whereTransclusion = "$<TranscludedDocumentTitles>=[" + pageReference + "]"; const tableDataPropertyNames = [i18n.name, i18n.shortDescription, tinyUrlNamePlain]; const transcludingDocuments = PDBMLS.fetchDocuments(baseUrl, tableDataPropertyNames, whereTransclusion); const delegatePageRefName = i18n["projectdoc.doctype.common.delegateDocument.pageRef"]; const whereDelegate = "$<" + delegatePageRefName + ">=[" + pageReference + "]"; const delegateDocuments = PDBMLS.fetchDocuments(baseUrl, tableDataPropertyNames, whereDelegate); const dynamicLinkTitles = i18n["projectdoc.doctype.common.metadata.dynamicLinkTitles"]; const whereDynamicLinkTitles = "$<" + dynamicLinkTitles + ">=[" + title + "]"; // AJS.log("Where (Dynamic Link Title): " + whereDynamicLinkTitles); const dynamicLinkTitlesDocuments = PDBMLS.fetchDocuments(baseUrl, tableDataPropertyNames, whereDynamicLinkTitles); let doctypeNameReferencesDocuments; const doctypeNameReferencesName = i18n["projectdoc.doctype.common.metadata.documentDoctypeNameReferences"]; if (doctypeNameReferencesName) { const doctype = currentDocument[i18n.doctype]; const name = currentDocument[i18n.name].trim(); const documentReference = doctype + ":" + name; const whereDoctypeNameReferences = "$<" + doctypeNameReferencesName + ">=[" + documentReference + "]"; // AJS.log("[projectdoc-inspect-menu] Where: " + whereDoctypeNameReferences); doctypeNameReferencesDocuments = PDBMLS.fetchDocuments(baseUrl, tableDataPropertyNames, whereDoctypeNameReferences); } const anchorLinkDocumentsName = i18n["projectdoc.doctype.common.metadata.anchorLinkDocuments"]; const whereAnchorLinkDocuments = "$<" + anchorLinkDocumentsName + ">~(" + spaceKey + "." + title + ", *." + title + "]"; const anchorLinksName = i18n["projectdoc.doctype.common.metadata.anchorLinks"]; const tableDataAnchorLinkPropertyNames = [i18n.name, i18n.shortDescription, tinyUrlNamePlain, anchorLinksName]; const anchorLinkDocuments = PDBMLS.fetchDocuments(baseUrl, tableDataAnchorLinkPropertyNames, whereAnchorLinkDocuments); const $html = createPage(i18n, currentDocument, transcludingDocuments, delegateDocuments, dynamicLinkTitlesDocuments, doctypeNameReferencesDocuments, anchorLinkDocuments); showDialog($html); } } else { AJS.log("[projectdoc-inspect-menu] Error transcluding documents. PDBMLS service of Bookmarklets Extension not found."); alert("Failed to transcluding documents: PDBMLS service of Bookmarklets Extension not found."); } }; const createMenu = function () { const menuId = "inspect"; const propertiesSectionId = "projectdoc-inspect-menu-properties"; const $mainMenu = USERSCRIPT4C_MENU.createMenu(menuId, "Inspect"); USERSCRIPT4C_MENU.registerMenu("view.menu", $mainMenu); // In case you need to append the menu to an element identified by a selector, use this: // USERSCRIPT4C_MENU.registerBySelector($mainMenu, "#my-id"); USERSCRIPT4C_MENU.addSection(menuId, { id: propertiesSectionId, label: "Properties", weight: 10 }); USERSCRIPT4C_MENU.addMenuItem(propertiesSectionId, { id: "projectdoc-menu-inspect-item-document-properties", label: "Show document properties", weight: "100" }, showDocumentProperties); USERSCRIPT4C_MENU.addMenuItem(propertiesSectionId, { id: "projectdoc-menu-inspect-item-space-properties", label: "Show space properties", weight: "200" }, showSpaceProperties); USERSCRIPT4C_MENU.addMenuItem(propertiesSectionId, { id: "projectdoc-menu-inspect-item-transclusions", label: "Show transclusions", weight: "300" }, listTranscludingDocument); return createMenu; } if (logToConsole) AJS.log("[projectdoc-inspect-menu] Adding menu ..."); createMenu(); });
Details
More information on using this userscript.
Rendering
The inspect menu is rendered next to the create button in the Confluence toolbar.
Requirements
The script requires the following apps to be installed on Confluence.
- The projectdoc Toolbox for Atlassian Confluence
- The projectdoc Toolbox supports agile teams in writing project documentation collaboratively. This is an introduction to use cases for and features of the projectdoc Toolbox.
- Web API Extension
- Add-on to extend projectdoc with an API to access on the web.
- Bookmarklets Extension
- Add-on to extend the Toolkit with Bookmarklets. Allows to execute tools via the browser.
Transcluding Documents Information
This version lists documents that
- transclude from the current document
- delegate to the current document
Only Static Transclusions
Please note that only static transclusions are listed in this report. A static transclusion is content from a referenced document. The Transclusion Macro uses static transclusion.
Dynamic transclusions are based on document queries. These transclusions are not listed in the report. The Transclude Documents Macro uses dynamic transclusion.
Related Scripts
Name | Short Description |
---|---|
Hide projectdoc Tools | Removes projectdoc tools (blueprints and macros) from the current page. |
projectdoc Search Tool | Provides an interface to specify and launch queries for projectdoc documents. |
Refactor projectdoc Document | Adds a refactor menu and checks the current document for property issues. |
Resources
More information on this topic is available by the following resources.
- Display Document Properties
- Displays the document properties of the projectdoc document currently shown in the browser.
- Display Space Properties
- Displays the space properties of the projectdoc document's space currently shown in the browser.
- List Transcluding Documents
- Shows the list of documents that transclude content from the current document.
- projectdoc Dynamic Link Titles
- Lists the titles of all pages targeted by dynamic links.