範例
在此套件中,我們也提供了一些自訂範例(可在guides_extension/src取得) 。 以下為各範本的簡短說明。
- 內容功能表:在此範例中,我們已自訂
file_options內容功能表,以移除Delete和Edit選項,並將Duplicate選項取代為Download選項。 下載內容功能表的程式碼範例。
enum VIEW_STATE {
APPEND = 'append',
PREPEND = 'prepend',
REPLACE = 'replace',
}
const loadDitaFile = (filePath, uuid) =>{
return $.ajax({
type: 'POST',
url: '/bin/referencelistener',
data: {
operation: 'getdita',
path: filePath,
type: uuid ? 'UUID' : 'PATH',
cache: false,
},
})
}
const fileOptions = {
id: "file_options",
contextMenuWidget: "repository_panel",
view: {
items: [
{
component: "div",
target: {
key: "displayName", value: "Delete",
viewState: VIEW_STATE.REPLACE
}
},
{
component: "div",
target: {
key: "displayName", value: "Edit",
viewState: VIEW_STATE.REPLACE
}
},
{
"displayName": "Download",
"data": {
"eventid": "downloadFile"
},
"icon": "downloadFromCloud",
"class": "menu-separator",
target: {
key: "displayName", value: "Duplicate",
viewState: VIEW_STATE.REPLACE
}
},
]
},
controller: {
downloadFile() {
const path = this.getValue('selectedItems')[0].path
loadDitaFile(path, true).then((file) => {
function download_file(name, contents) {
const mime_type = "text/plain";
const blob = new Blob([contents], { type: mime_type });
const dlink = document.createElement('a');
dlink.download = name;
dlink.href = window.URL.createObjectURL(blob);
dlink.onclick = function () {
// revokeObjectURL needs a delay to work properly
const that = this;
setTimeout(function () {
window.URL.revokeObjectURL(that.href);
}, 1500);
};
dlink.click();
dlink.remove();
}
download_file(path, file.xml)
});
}
}
}
export default fileOptions
- 左側面板:在此範例中,我們已自訂
left tab panel,讓另一個tab標題為「TEST EXTENSION」,以及對應的tab panel具有標籤:Test Tab Panel。
下載左側面板的程式碼範例。
const tabLeftPanel = {
"id": "left_panel_container",
"tabView": {
"id": "left_panel_container",
"tabs": [
{
"component": "tab",
"id": "new_tab_extension",
"extraclass": "collection-panel-tab",
"showClass": "@visibleTabs.collection_panel",
"on-click": "tabClick",
"icon": "collection",
"title": "TEST EXTENSION",
"label": "TEST EXTENSION",
"prevTabID": "condition_panel"
},
],
"tabPanels":
[
{
"component": "tabPanel",
"tabId": "new_tab_extension",
"showClass": "@visibleTabs.citation_panel",
"items": [
{
"id": "annotation_toolbox"
}
],
},
]
}
}
export default tabLeftPanel
- 右側面板:在此範例中,我們已自訂
right tab panel,讓另一個tab標題為「TEST EXTENSION」,以及對應的tab panel具有標籤:New Tab Panel。 下載右側面板的程式碼範例。
const rightPanel = {
"id": "right_panel_container",
"tabView": {
"id": "right_panel_container_tab",
"tabs": [
{
"component": "tab",
"id": "new_tab_extension",
"on-click": "tabClick",
"icon": "collection",
"title": "TEST EXTENSION",
},
],
"tabPanels":
[
{
"component": "tabPanel",
"tabId": "new_tab_extension",
"items": [
{
"component": "label",
"label": "New Tab Label",
}
],
},
]
}
}
export default rightPanel
- 存放庫面板:下載存放庫面板的程式碼範例。
export enum VIEW_STATE2 {
APPEND = 'append',
PREPEND = 'prepend',
REPLACE = 'replace',
}
export default {
class: "flex bg-red-100 bg-green-200 bg-green-300 mr-4",
id: 'repository_panel',
view: {
className: '',
items: [
{
target: {
key: "id",
value: 'respository-'
,
},
component: 'widget',
id: 'loading_shimmer',
viewState: VIEW_STATE2.REPLACE,
index: 2,
},
{
component: 'button',
label: 'Close',
'on-click': 'cancel',
variant: 'secondary',
quiet: true,
index: 20,
},
{
label: "@testLabel",
component: "label"
}
],
},
controller: {
init: function() {
console.log('subject: ', this.subject)
this.subscribe({
key: 'rename',
next: () => { console.log('rename using extension') }
})
console.log('Logging view config ', this.viewConfig)
this.next(this.viewConfig.items[1].searchModeChangedEvent, { searchMode: true })
this.subscribeAppEvent({
key: 'app.active_document_changed',
next: () => { console.log('active doc changed subs') }
})
this.subscribeAppModel('app.mode',
() => { console.log('app mode subs') }
)
this.subscribeParentEvent({
key: 'tabChange',
next: () => { console.log('tab change subs') }
})
this.parentEventHandlerNext('tabChange', {
data: 'map_panel'
})
this.appModelNext('app.mode', 'author')
this.appEventHandlerNext('app.active_document_changed', 'active doc changed')
},
cancel: function (args) {
this.setValue('testLabel', 'testlabel2')
},
},
model: {
deps: ['testLabel'],
},
}
- 工具列:在此範例中,我們已將
Insert Element、Insert Paragraph、Insert Numbered List、Insert Bulleted List按鈕取代為包含所有這些按鈕的單一More Insert Options按鈕。 下載工具列的程式碼範例。
import { VIEW_STATE } from "./review_app_examples/review_comment"
const topbarExtend = {
id: "toolbar",
view: {
items: [
{
component: "div",
target: {
key: "title", value: "Insert Element",
viewState: VIEW_STATE.REPLACE
}
},
{
component: "div",
target: {
key: "title", value: "Insert Paragraph",
viewState: VIEW_STATE.REPLACE
}
},
{
component: "div",
target: {
key: "title", value: "Insert Numbered List",
viewState: VIEW_STATE.REPLACE
}
},
{
component: "div",
target: {
key: "title", value: "Insert Bulleted List",
viewState: VIEW_STATE.REPLACE
}
},
{
"component": "button",
"extraclass": "insert-multimedia",
"icon": "more",
"variant": "action",
"quiet": true,
"holdAffordance": true,
"title": "More Insert Options",
"elementID": "toolbar_insert",
"on-click": {
"name": "APP_SHOW_OPTIONS_POPOVER",
"args": {
"target": "toolbar_insert",
"extraclass": "new_options_buttons",
"items": [
{
"component": "button",
"icon": "add",
"variant": "action",
"quiet": true,
"title": "Insert Element",
"on-click": "AUTHOR_SHOW_INSERT_ELEMENT_UI"
},
{
"component": "button",
"icon": "textParagraph",
"variant": "action",
"quiet": true,
"title": "Insert Paragraph",
"on-click": "INSERT_P"
},
{
"component": "button",
"icon": "textNumbered",
"variant": "action",
"quiet": true,
"title": "Insert Numbered List",
"on-click": "AUTHOR_INSERT_REMOVE_NUMBERED_LIST"
},
{
"component": "button",
"icon": "textBulleted",
"variant": "action",
"quiet": true,
"title": "Insert Bulleted List",
"on-click": "AUTHOR_INSERT_REMOVE_BULLETED_LIST"
},
{
"component": "button",
"icon": "table",
"variant": "action",
"quiet": true,
"title": "Insert Table",
"on-click": "AUTHOR_INSERT_ELEMENT",
}
]
},
},
target: {
key: "title", value: "Insert Table",
viewState: VIEW_STATE.REPLACE
}
},
]
},
controller: {
init() {
console.log(this.proxy.getValue("canUndo"))
this.proxy.subscribeAppEvent({
key: "editor.preview_rendered",
next: async function (e) {
console.log(this.proxy.getValue("canUndo"))
}.bind(this)
})
},
INSERT_P() {
this.next("AUTHOR_INSERT_ELEMENT", "p")
}
}
}
export default topbarExtend
- 中繼資料面板上的 管理 按鈕:在此範例中,我們已自訂 管理 按鈕(位於「報表」頁面上的「中繼資料」面板),以便在選取的檔案處於唯讀模式時停用該按鈕。 這有助於防止意外編輯不打算編輯的檔案的中繼資料。 下載中繼資料面板🔗上管理按鈕的程式碼範例。
const mapConsoleActionBar = {
id: "map_console_action_bar",
view: {
items: [
{
"key": "manageTags",
"component": "button",
"title": "Manage",
"on-click": "reports.manage_report",
"label": "Manage",
"icon": "s2AppGear",
"type": "secondary",
"variant": "action",
"quiet": true,
"show": "@showManageTags",
"disabled": "$extensionMap.overrideShowManageTags",
target: {
key: "title", value: "Manage",
viewState: 'replace'
}
},
{
"key": "selectAll",
"component": "button",
"title": "@selectAllTitle",
"on-click": "SELECT_ALL",
"label": "@selectAllTitle",
"variant": "action",
"extraclass": "select-all-button",
"quiet": true,
"show": "@showSelectAllButton",
"hide": "$extensionMap.overrideShowManageTags",
target: {
key: "key", value: "selectAll",
viewState: 'replace'
}
},
]
}
}
function getAutoCheckoutConfigFromAppModel() {
const config = tcx.model.getValue(tcx.model.KEYS.EDITOR_CHECKOUT_CONFIG)
return config === true || config === 'true'
}
const bulkMetadataEditorController = {
id: "bulkmetadata_report_view",
controller: {
rowSelectionChanged() {
const selectedItems = this.getValue('selectedItems');
let areReadOnlyFilesSelected = false;
let autoCheckoutConfig = getAutoCheckoutConfigFromAppModel();
for (let idx = 0; idx < selectedItems.length; idx++) {
const item = selectedItems[idx].obj;
const isLocked = Boolean(item.isCheckedOut);
if (autoCheckoutConfig && !isLocked) {
areReadOnlyFilesSelected = true;
break;
}
}
this.setExtensionAppState('overrideShowManageTags', areReadOnlyFilesSelected);
}
}
}
export {
mapConsoleActionBar,
bulkMetadataEditorController
}
檢閱應用程式範例
- 註解工具箱:在此範例中,我們已將另一個按鈕新增至註解工具箱,以開啟AEM中的目前稽核主題。 下載註解工具箱的程式碼範例。
import { VIEW_STATE } from './review_comment'
export default {
id: 'annotation_toolbox',
view: {
items: [
{
component: 'button',
icon: 'linkOut',
title: 'openTopicInAEM',
'on-click': 'openTopicInAEM',
target: {
key: 'value',
value: 'addcomment',
viewState: VIEW_STATE.APPEND
},
},
],
},
controller: {
openTopicInAEM: function (args) {
const topicIndex = tcx.model.getValue(tcx.model.KEYS.REVIEW_CURR_TOPIC)
const { allTopics = {} } = tcx.model.getValue(tcx.model.KEYS.REVIEW_DATA) || {}
tcx.appGet('util').openInAEM(allTopics[topicIndex])
},
},
}
- 檢閱評論:在此範例中,我們已新增以使用者資訊取代使用者名稱(包含評論者的全名和標題)、新增唯一的評論ID、mailTo圖示,以及新增輸入欄位以提及評論嚴重性和理由。
我們也在開啟對話方塊的XMLEditor側之註解上新增了accept with modification按鈕。 下載檢閱註解的程式碼範例。
export enum VIEW_STATE {
APPEND = 'append',
PREPEND = 'prepend',
REPLACE = 'replace',
}
const reviewComment = {
id: 'review_comment',
view: {
items: [
{
component: 'label',
label: '@extraProps.commentUniqId',
extraclass: 'commentUniqId',
target: {
key: 'extraclass',
value: 'user-image',
viewState: VIEW_STATE.PREPEND,
},
},
{
component: 'div',
extraclass: 'user-info',
items: [
{
component: 'label',
"label": "@extraProps.userInfo",
"extraclass": "reviewer-name",
},
{
component: 'button',
icon: 'email',
extraclass: 'mailto-icon',
"on-click": "openMailTo"
}
],
target: {
key: 'extraclass',
value: 'reviewer-name',
viewState: VIEW_STATE.REPLACE,
},
},
{
component: 'div',
extraclass: 'comment-details',
items:
[
{
component: 'div',
extraclass: 'comment-type-text',
items:
[
{
component: 'label',
label: 'Comment Type: ',
"extraclass": "severity-label",
},
{
component: 'label',
label: '@extraProps.severity'
}
],
},
{
component: 'div',
extraclass: 'comment-rationale',
items:
[
{
component: 'label',
label: 'Comment Rationale: ',
extraclass: 'comment-rationale-label'
},
{
component: 'label',
label: '@extraProps.commentRationale'
}
],
},
],
target: {
key: 'id',
value: 'attachment_tiles',
viewState: VIEW_STATE.PREPEND,
},
},
{
component: 'div',
items: [
{
component: 'div',
extraclass: 'edit-comment-type',
items: [
{
component: 'label',
"label": "Comment Type",
},
{
"component": "comboBox",
"data": "@extraProps.labels",
"extraclass": "severity-combobox",
"multiple": false,
"placeholder": "",
'value': "@extraProps.severity",
"on-change": "changeSeverity",
"on-keyup": { "name": "changeSeverity", "eventArgs": { "keys": ["ENTER"] } },
},
],
},
{
component: "div",
extraclass: 'edit-comment-rationale',
items: [
{
component: 'label',
label: 'Comment Rationale'
},
{
component: "textarea",
extraclass: "edit-textfield",
"id": "edit_comment_rationale",
"data": "@extraProps.commentRationale",
"on-keyup": {
"name": "submitEditComment",
"eventArgs": {
"keys": [
"ENTER"
]
}
},
"stopKeyPropagation": true
},
],
},
],
target: {
key: 'class',
value: 'comment-block',
viewState: VIEW_STATE.APPEND,
},
},
{
component: "button",
"icon": "MultipleAdd",
"variant": "action",
"quiet": true,
"extraclass": "hover-item",
"title": "Accept with Modifications",
"on-click": "acceptWithModification",
target: {
key: 'title',
value: 'Reject comment',
viewState: VIEW_STATE.APPEND,
},
}
],
},
controller: {
init: function () {
const reqComment = tcx.commentStore.getComment(this.getValue('commentId'))
this.setValue('extraProps', reqComment.extraProps)
this.setValue("labels", ['None', 'CRITICAL', 'MAJOR', 'SUBSTANTATIVE', 'ADMINISTRATIVE'])
},
sendAcceptWithModificationProps(args) {
this.next('updateExtraProps', args)
},
changeSeverity: function (args) {
this.setValue("severity", args.data)
this.next('updateExtraProps',
{ 'severity': this.getValue("severity") }
)
},
changeCommentRationale: function () {
this.next('updateExtraProps',
{ 'commentRationale': this.getValue("commentRationale") }
)
},
submitEditComment({ domEvent }: { domEvent?: KeyboardEvent } = {}) {
if (domEvent?.key === 'Enter') {
this.setValue('commentRationale', _.trim(this.getValue('commentRationale')))
}
if (this.getValue("originalCommentRationale") !== this.getValue("commentRationale")) {
this.setValue("originalCommentRationale", this.getValue("commentRationale"))
this.next('changeCommentRationale')
}
},
openMailTo() {
const mailToLink = `mailto:${this.getValue("userEmail")}`
tcx.util.openLink(mailToLink)
},
acceptWithModification() {
tcx.eventHandler.next(tcx.eventHandler.KEYS.APP_SHOW_DIALOG,
{
id: 'accept_with_modification_dialog',
args: {
onSuccess: (extraProps) => this.next('sendAcceptWithModificationProps', extraProps),
}
})
}
}
}
export default reviewComment
- 評論回覆:在此範例中,我們已新增以使用者資訊取代使用者名稱(包含評論者的完整名稱和標題),並在評論標題中新增mailTo圖示。 下載註解回覆的程式碼範例。
import { VIEW_STATE } from "./review_comment"
const commentReply = {
id: 'comment_reply',
view: {
items: [
{
component: 'div',
extraclass: 'user-info',
items: [
{
component: 'label',
label: "@extraProps.userInfo",
"extraclass": "user-name",
},
{
component: 'button',
icon: 'email',
extraclass: 'mailto-icon',
"on-click": "openMailTo"
}
],
target: {
key: 'extraclass',
value: 'user-name',
viewState: VIEW_STATE.REPLACE,
},
},
],
},
model: {
deps: [],
},
controller: {
init: function () {
const reqComment = tcx.commentStore.getComment(this.getValue('commentId'))
const reqReply = reqComment.findReply(this.getValue('replyId'))
this.setValue('extraProps', reqReply.extraProps)
},
openMailTo(){
const mailToLink = `mailto:${this.getValue("userEmail")}`
tcx.util.openLink(mailToLink)
}
}
}
export default commentReply
-
內嵌稽核面板:在此檔案中,我們會計算並指派在
Review Comment和Comment Reply範例中提及的唯一註解ID。-
setCommentId方法會根據註解計數,為每個註解設定唯一的註解ID。 -
setUserInfo會使用每個註解的完整名稱和標題來設定userInfo的值。 -
onNewCommentEvent會確保為每個新註解或回覆呼叫setUserInfo方法。 -
updatedProcessComments函式會針對每個新註解事件執行,並確保在收到新註解事件時呼叫setCommentId。
-
下載內嵌稽核面板的程式碼範例。
export const updatedProcessComments = function (data, topicIndex) {
const newCommentEvents = ["highlight", "strikethrough", "addcomment", "insertext"]
_.each(data, (event: any) => {
const identify = _.findIndex(newCommentEvents, eventType => eventType === event.eventType)
if (identify !== -1) {
this.next('setCommentId', { event, topicIndex })
}
})
}
const inline_extend = {
id: 'inline_review_panel',
model: {
deps: ['commentCount'],
},
controller: {
init: function () {
this.setValue("commentCount", {})
tcx.model.subscribeVal(tcx.model.KEYS.REVIEW_DATA, (reviewData) => {
for (let topicId of reviewData.topicsinReview) {
topicId = topicId.toString()
tcx.commentStore.onProcessEvent(topicId, (events) => updatedProcessComments.call(this, events, topicId))
}
})
},
onNewCommentEvent(args) {
const events = _.get(args, "events")
const currTopicIndex = tcx.model.getValue(tcx.model.KEYS.REVIEW_CURR_TOPIC) || this.getValue('currTopicIndex') || "0"
const event = _.get(_.get(events, currTopicIndex), '0')
const newComment = _.get(args, 'newComment')
const newReply = _.get(args, 'newReply')
if ((newComment || newReply) && event) {
this.next('setUserInfo', event)
}
},
setUserInfo(event) {
tcx.api?.getUserInfo(event.user).subscribe(userData => {
const extraProps = {
"userFirstName": userData?.givenName || '',
"userLastName": userData?.familyName || '',
"userTitle": userData?.title || '',
"userJobTitle": userData?.jobTitle || '',
'userEmail': userData?.email || '',
}
const name = `${extraProps.userFirstName} ${extraProps.userLastName}, ${extraProps.userJobTitle}`
if (_.trim(name) === ',') {
extraProps.userInfo = userData.displayName
}
else {
extraProps.userInfo = name
}
const data = { ...event, extraProps }
this.next(
'sendExtraProps',
data
)
})
},
setCommentId({ event, topicIndex }) {
const processingComments = this.getValue('processingComments')
const modelComment = _.find(processingComments, { commentId: event.commentId })
const reqComment = tcx.commentStore.getComment(event.commentId)
const commentCount = this.getValue('commentCount')
if (_.has(this.getValue('commentCount'), topicIndex)) {
commentCount[topicIndex] += 1
this.setValue("commentCount", commentCount)
}
else {
commentCount[topicIndex] = 1
}
if (reqComment) {
this.setValue("commentCount", commentCount)
const commentUniqId = `${Number(topicIndex) + 1}.${commentCount[topicIndex]}`
reqComment.extraProps.set("commentUniqId", commentUniqId)
modelComment?.extraProps?.set("commentUniqId", commentUniqId)
}
},
},
}
export default inline_extend
- 主題檢閱面板:此檔案擴充內嵌檢閱面板 (如前所述),讓新增的自訂功能也可在檢閱應用程式端運作。 下載主題評論面板的程式碼範例。
import inline_extend from './inline_review_panel';
import { updatedProcessComments } from './inline_review_panel';
const topic_reviews_extend = {
id: 'topic_reviews',
model: {
deps: [],
},
controller: {
...inline_extend.controller,
init: function () {
this.setValue("commentCount", {})
tcx.model.subscribeVal(tcx.model.KEYS.REVIEW_DATA, (reviewData) => {
for (let topicId of reviewData.topicsinReview) {
topicId = topicId.toString()
tcx.commentStore.onProcessEvent(topicId, (events) => updatedProcessComments.call(this, events, topicId))
}
})
},
},
}
export default topic_reviews_extend
- 透過修改對話方塊接受:此為將新Widget新增至應用程式的範例。 我們已在此建立新的對話方塊,其中包含兩個輸入文字欄位:
Revised Text和Adjudicator Comment Rationale。 下載Accept with Modification Dialog的程式碼範例。
const acceptWithModification = {
id: 'accept_with_modification_dialog',
view: {
component: "dialog",
"header": {
"items": [
{
component: 'label',
extraclass: "header",
label: 'Accept With Modifications',
}
]
},
content: {
items: [
{
component: 'div',
"extraclass": "revised-text",
items: [
{
component: 'label',
label: 'Revised Text (Required)',
extraclass: 'revised-text-label'
},
{
component: "textarea",
"extraclass": "revised-text-textarea",
"data": "@extraProps.revisedText",
"stopKeyPropagation": true,
}
]
},
{
component: 'div',
"extraclass": "adjudication-rationale",
items: [
{
component: 'label',
label: 'Adjudicator Comment Rationale (Required)',
extraclass: 'adjudication-rationale-label'
},
{
component: "textarea",
extraclass: "adjudication-rationale-textarea",
"data": "@extraProps.adjudicationRationale",
"on-keyup": {
"name": "",
"eventArgs": {
"keys": [
"ENTER"
]
}
},
"stopKeyPropagation": true
}
]
},
],
},
footer: {
"items": [
{
"component": "button",
"label": "Cancel",
"on-click": "handleClose",
"variant": "secondary"
},
{
"component": "button",
"label": "Submit",
"variant": "cta",
"on-click": "submitAcceptWithModification"
}
]
}
},
model: {
deps: [],
},
controller: {
init: function () {
},
submitAcceptWithModification: function () {
const extraProps = {
'revisedText': this.getValue("revisedText"),
'adjudicationRationale': this.getValue("adjudicationRationale"),
}
this.args.onSuccess(extraProps)
this.next('handleClose')
},
handleClose() {
tcx.eventHandler.next(tcx.eventHandler.KEYS.APP_HIDE_DIALOG, { id: 'accept_with_modification_dialog' })
}
}
}
export default acceptWithModification
- 儲存修訂專案:這是如何更新現有對話方塊的範例。 我們在此新增了發佈按鈕。 我們允許修改對話方塊的內容。 請在此參考其json:
save_revision。 下載儲存修訂版本的程式碼範例。
enum VIEW_STATE {
APPEND = 'append',
PREPEND = 'prepend',
REPLACE = 'replace',
}
const saveRevision = {
id: 'save_revision',
view: {
items: [
{
component: "button",
label: 'publish',
target: {
key: 'label',
value: 'Save',
viewState: VIEW_STATE.APPEND
}
}
]
}
}
export default saveRevision
以下是自訂前後的檢閱面板:
recommendation-more-help
experience-manager-guides-help-product-guide