refactor: migrate workflows feature to the new architecture
Bug: twpowertools:176
Change-Id: Ib0af4cd828577f2a399be93264d45fdfddbad9b0
diff --git a/src/features/workflows/core/common.js b/src/features/workflows/core/common.js
new file mode 100644
index 0000000..6d6ed03
--- /dev/null
+++ b/src/features/workflows/core/common.js
@@ -0,0 +1,16 @@
+// Source: https://stackoverflow.com/a/66046176
+export const arrayBufferToBase64 = async (data) => {
+ // Use a FileReader to generate a base64 data URI
+ const base64url = await new Promise((r) => {
+ const reader = new FileReader();
+ reader.onload = () => r(reader.result);
+ reader.readAsDataURL(new Blob([data]));
+ });
+
+ /*
+ The result looks like
+ "data:application/octet-stream;base64,<your base64 data>",
+ so we split off the beginning:
+ */
+ return base64url.split(',', 2)[1]
+}
diff --git a/src/features/workflows/core/communityConsole/actionRunners/attribute.js b/src/features/workflows/core/communityConsole/actionRunners/attribute.js
new file mode 100644
index 0000000..a542280
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/actionRunners/attribute.js
@@ -0,0 +1,20 @@
+import {CCApi} from '../../../../../common/api.js';
+import {getAuthUser} from '../../../../../common/communityConsoleUtils.js';
+
+export default class AttributeRunner {
+ async execute(attributeAction, thread) {
+ if (!attributeAction) {
+ throw new Error(
+ 'The workflow is malformed. The attribute action is missing.');
+ }
+ const action = attributeAction.getAttributeAction();
+
+ return await CCApi(
+ 'SetThreadAttribute', {
+ 1: thread.forumId,
+ 2: thread.threadId,
+ 3: action,
+ },
+ /* authenticated = */ true, getAuthUser());
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/actionRunners/readState.js b/src/features/workflows/core/communityConsole/actionRunners/readState.js
new file mode 100644
index 0000000..f24f7c6
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/actionRunners/readState.js
@@ -0,0 +1,21 @@
+import {CCApi} from '../../../../../common/api.js';
+import {getAuthUser} from '../../../../../common/communityConsoleUtils.js';
+
+export default class ReadStateRunner {
+ execute(readState, thread) {
+ // Although this should in theory be the last message ID, it seems like
+ // setting 0 marks the entire thread as read anyways.
+ const lastMessageId = readState ? '0' : '-1';
+
+ return CCApi(
+ 'SetUserReadStateBulk', {
+ // bulkItem:
+ 1: [{
+ 1: thread.forumId,
+ 2: thread.threadId,
+ 3: lastMessageId,
+ }],
+ },
+ /* authenticated = */ true, getAuthUser());
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/actionRunners/replyWithCR.js b/src/features/workflows/core/communityConsole/actionRunners/replyWithCR.js
new file mode 100644
index 0000000..347d5c4
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/actionRunners/replyWithCR.js
@@ -0,0 +1,80 @@
+import {CCApi} from '../../../../../common/api.js';
+import {getAuthUser} from '../../../../../common/communityConsoleUtils.js';
+
+const kPiiScanType_ScanNone = 0;
+const kType_Reply = 1;
+const kType_RecommendedAnswer = 3;
+const kPostMethodCommunityConsole = 4;
+
+const kVariablesRegex = /\$([A-Za-z_]+)/g;
+
+export default class CRRunner {
+ constructor() {
+ this._CRs = [];
+ this._haveCRsBeenLoaded = false;
+ }
+
+ loadCRs() {
+ return CCApi(
+ 'ListCannedResponses', {}, /* authenticated = */ true,
+ getAuthUser())
+ .then(res => {
+ this._CRs = res?.[1] ?? [];
+ this._haveCRsBeenLoaded = true;
+ });
+ }
+
+ _getCRPayload(id) {
+ let maybeLoadCRsPromise;
+ if (!this._haveCRsBeenLoaded)
+ maybeLoadCRsPromise = this.loadCRs();
+ else
+ maybeLoadCRsPromise = Promise.resolve();
+
+ return maybeLoadCRsPromise.then(() => {
+ let cr = this._CRs.find(cr => cr?.[1]?.[1] == id);
+ if (!cr) throw new Error(`Couldn't find CR with id ${id}.`);
+ return cr?.[3];
+ });
+ }
+
+ _templateSubstitute(payload, thread) {
+ if (!payload.match(kVariablesRegex)) return Promise.resolve(payload);
+
+ return thread.loadThreadDetails().then(() => {
+ return payload.replaceAll(kVariablesRegex, (_, p1) => {
+ return thread?.[p1] ?? '';
+ });
+ });
+ }
+
+ execute(action, thread) {
+ let crId = action?.getCannedResponseId?.();
+ if (!crId)
+ return Promise.reject(
+ new Error('The action doesn\'t contain a valid CR id.'));
+
+ return this._getCRPayload(crId)
+ .then(payload => this._templateSubstitute(payload, thread))
+ .then(payload => {
+ let subscribe = action?.getSubscribe?.() ?? false;
+ let markAsAnswer = action?.getMarkAsAnswer?.() ?? false;
+ return CCApi(
+ 'CreateMessage', {
+ 1: thread.forumId,
+ 2: thread.threadId,
+ // message
+ 3: {
+ 4: payload,
+ 6: {
+ 1: markAsAnswer ? kType_RecommendedAnswer : kType_Reply,
+ },
+ 11: kPostMethodCommunityConsole,
+ },
+ 4: subscribe,
+ 6: kPiiScanType_ScanNone,
+ },
+ /* authenticated = */ true, getAuthUser());
+ });
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/components/TwptCRImportButton.js b/src/features/workflows/core/communityConsole/components/TwptCRImportButton.js
new file mode 100644
index 0000000..0431de7
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/TwptCRImportButton.js
@@ -0,0 +1,41 @@
+import '@material/web/button/outlined-button.js';
+import '@material/web/icon/icon.js';
+
+import {html, LitElement} from 'lit';
+
+import {SHARED_MD3_STYLES} from '../../../../../common/styles/md3.js';
+
+export default class TwptCRImportButton extends LitElement {
+ static properties = {
+ cannedResponseId: {type: String},
+ selected: {type: Boolean},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ ];
+
+ render() {
+ const icon = this.selected ? 'done' : 'post_add';
+ const label = this.selected ? 'Selected' : 'Select';
+
+ return html`
+ <md-outlined-button
+ ?disabled=${this.selected}
+ @click=${this._importCR}>
+ <md-icon slot="icon">${icon}</md-icon>
+ ${label}
+ </md-outlined-button>
+ `;
+ }
+
+ _importCR() {
+ window.opener?.postMessage?.(
+ {
+ action: 'importCannedResponse',
+ cannedResponseId: this.cannedResponseId,
+ },
+ '*');
+ }
+}
+window.customElements.define('twpt-cr-import-button', TwptCRImportButton);
diff --git a/src/features/workflows/core/communityConsole/components/TwptConfirmDialog.js b/src/features/workflows/core/communityConsole/components/TwptConfirmDialog.js
new file mode 100644
index 0000000..bf80658
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/TwptConfirmDialog.js
@@ -0,0 +1,74 @@
+import '@material/web/dialog/dialog.js';
+import '@material/web/button/filled-button.js';
+import '@material/web/button/text-button.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../common/styles/md3.js';
+
+export default class TwptConfirmDialog extends LitElement {
+ static properties = {
+ open: {type: Boolean},
+ workflow: {type: Object},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ :host {
+ --mdc-dialog-content-ink-color: var(--mdc-theme-on-surface, #000);
+ --mdc-dialog-z-index: 200;
+ }
+
+ .workflow {
+ font-weight: 500;
+ }
+ `,
+ ];
+
+ constructor() {
+ super();
+ this.open = false;
+ }
+
+ render() {
+ return html`
+ <md-dialog
+ ?open=${this.open}
+ @open=${this._openingDialog}
+ @close=${this._closingDialog}>
+ <div slot="content">
+ Are you sure you want to run workflow
+ <span class="workflow">${this.workflow?.getName?.()}</span> for all
+ the selected threads?
+ </div>
+ <div slot="actions">
+ <md-text-button
+ @click=${() => this.open = false}>
+ Cancel
+ </md-text-button>
+ <md-filled-button
+ @click=${this._dispatchConfirmEvent}>
+ Run workflow
+ </md-filled-button>
+ </div>
+ </md-dialog>
+ `;
+ }
+
+ _openingDialog() {
+ this.open = true;
+ }
+
+ _closingDialog() {
+ this.open = false;
+ }
+
+ _dispatchConfirmEvent() {
+ const e = new Event('confirm');
+ this.dispatchEvent(e);
+ this.open = false;
+ }
+}
+window.customElements.define('twpt-confirm-dialog', TwptConfirmDialog);
diff --git a/src/features/workflows/core/communityConsole/components/TwptWorkflowDialog.js b/src/features/workflows/core/communityConsole/components/TwptWorkflowDialog.js
new file mode 100644
index 0000000..9e27edf
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/TwptWorkflowDialog.js
@@ -0,0 +1,85 @@
+import '@material/web/dialog/dialog.js';
+import '@material/web/button/text-button.js';
+import './TwptWorkflowProgress.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../common/styles/md3.js';
+import WorkflowRunner from '../runner.js';
+
+export default class TwptWorkflowDialog extends LitElement {
+ static properties = {
+ open: {type: Boolean},
+ workflow: {type: Object},
+ _runner: {type: Object, state: true},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ :host {
+ --mdc-dialog-content-ink-color: var(--mdc-theme-on-surface, #000);
+ --mdc-dialog-z-index: 200;
+ }
+
+ .workflow-name {
+ font-weight: 500;
+ }
+ `,
+ ];
+
+ progressRef = createRef();
+
+ constructor() {
+ super();
+ this.open = false;
+ }
+
+ render() {
+ return html`
+ <md-dialog
+ ?open=${this.open}
+ @open=${this._openingDialog}
+ @close=${this._closingDialog}>
+ <div slot="headline">
+ ${'Running ' + this.workflow?.getName?.() + '...'}
+ </div>
+ <div slot="content">
+ <twpt-workflow-progress ${ref(this.progressRef)}
+ .workflow=${this.workflow}
+ currentThread=${this._runner?.currentThreadIndex}
+ numThreads=${this._runner?.numThreads}
+ currentAction=${this._runner?.currentActionIndex}
+ status=${this._runner?.status}>
+ </twpt-workflow-progress>
+ </div>
+
+ <div slot="actions">
+ <md-text-button
+ ?disabled=${this._runner?.status !== 'finished'}
+ slot="primaryAction"
+ @click=${() => this.open = false}>
+ Close
+ </md-text-button>
+ </div>
+ </md-dialog>
+ `;
+ }
+
+ start() {
+ this._runner =
+ new WorkflowRunner(this.workflow, () => this.requestUpdate());
+ this._runner.start();
+ this.open = true;
+ }
+
+ _openingDialog() {
+ this.open = true;
+ }
+
+ _closingDialog() {
+ this.open = false;
+ }
+}
+window.customElements.define('twpt-workflow-dialog', TwptWorkflowDialog);
diff --git a/src/features/workflows/core/communityConsole/components/TwptWorkflowProgress.js b/src/features/workflows/core/communityConsole/components/TwptWorkflowProgress.js
new file mode 100644
index 0000000..587db8f
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/TwptWorkflowProgress.js
@@ -0,0 +1,75 @@
+import '@material/mwc-dialog/mwc-dialog.js';
+import '@material/web/button/filled-button.js';
+import '@material/web/button/text-button.js';
+
+import '../../manager/components/ActionEditor.js';
+
+import {css, html, LitElement} from 'lit';
+import {map} from 'lit/directives/map.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../common/styles/md3.js';
+
+export default class TwptWorkflowProgress extends LitElement {
+ static properties = {
+ workflow: {type: Object},
+ currentThread: {type: Number},
+ numThreads: {type: Number},
+ currentAction: {type: Number},
+ status: {type: String},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ .progressbar-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ `,
+ ];
+
+ renderThreadProgress() {
+ // @TODO: Improve this UI when the actions section is complete
+ return html`
+ <div class="progressbar-container">
+ <progress value=${this.currentThread + 1} max=${
+ this.numThreads}></progress>
+ <span>Thread ${this.currentThread + 1}/${this.numThreads}</span>
+ </div>
+ <p style="color: gray;">(Debug information) Status: ${this.status}</p>
+ `;
+ }
+
+ renderActions() {
+ const actions = this.workflow?.getActionsList?.() ?? [];
+ return map(actions, (action, i) => {
+ let status;
+ if (i > this.currentAction)
+ status = 'idle';
+ else if (i == this.currentAction && this.status == 'running')
+ status = 'running';
+ else if (i == this.currentAction && this.status == 'error')
+ status = 'error';
+ else
+ status = 'done';
+
+ return html`
+ <wf-action-editor
+ .action=${action}
+ readOnly
+ step=${i + 1}
+ status=${status}>
+ </wf-action-editor>
+ `;
+ });
+ }
+
+ render() {
+ return [
+ this.renderThreadProgress(),
+ this.renderActions(),
+ ];
+ }
+}
+window.customElements.define('twpt-workflow-progress', TwptWorkflowProgress);
diff --git a/src/features/workflows/core/communityConsole/components/TwptWorkflowsMenu.js b/src/features/workflows/core/communityConsole/components/TwptWorkflowsMenu.js
new file mode 100644
index 0000000..05fb216
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/TwptWorkflowsMenu.js
@@ -0,0 +1,134 @@
+import '@material/web/divider/divider.js';
+import '@material/web/icon/icon.js';
+import '@material/web/iconbutton/icon-button.js';
+import '@material/web/menu/menu.js';
+import '@material/web/menu/menu-item.js';
+
+import consoleCommonStyles from '!!raw-loader!../../../../../static/css/common/console.css';
+
+import {css, html, LitElement, nothing, unsafeCSS} from 'lit';
+import {map} from 'lit/directives/map.js';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../common/styles/md3.js';
+
+export default class TwptWorkflowsMenu extends LitElement {
+ static properties = {
+ workflows: {type: Object},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`${unsafeCSS(consoleCommonStyles)}`,
+ css`
+ .workflows-menu {
+ --md-menu-item-label-text-size: 14px;
+ }
+
+ .workflow-item {
+ --md-menu-item-one-line-container-height: 48px;
+
+ min-width: 250px;
+ }
+
+ /* Custom styles to override the common button with badge styles */
+ .TWPT-btn--with-badge {
+ padding-bottom: 0;
+ }
+
+ .TWPT-btn--with-badge .TWPT-badge {
+ bottom: 4px;
+ right: 2px;
+ }
+ `,
+ ];
+
+ menuRef = createRef();
+
+ renderWorkflowItems() {
+ if (!this.workflows) return nothing;
+ if (this.workflows?.length == 0)
+ return html`
+ <md-menu-item disabled>
+ <span class="workflow-item" slot="start">
+ No workflows
+ </span>
+ </md-menu-item>
+ `;
+ return map(this.workflows, w => html`
+ <md-menu-item
+ class="workflow-item"
+ @click="${() => this._dispatchSelectEvent(w.uuid)}">
+ <span slot="start">
+ ${w.proto.getName()}
+ </span>
+ </md-menu-item>
+ `);
+ }
+
+ renderMenuItems() {
+ return [
+ this.renderWorkflowItems(),
+ html`
+ <md-divider></md-divider>
+ <md-menu-item
+ class="workflow-item"
+ @click="${() => this._openWorkflowManager()}">
+ <span slot="start">
+ Manage workflows...
+ </span>
+ </md-menu-item>
+ `,
+ ];
+ }
+
+ // Based on createExtBadge() in ../../utils/common.js.
+ renderBadge() {
+ return html`
+ <div class="TWPT-badge">
+ <md-icon>repeat</md-icon>
+ </div>
+ `;
+ }
+
+ render() {
+ return html`
+ <span style="position: relative;">
+ <div
+ id="workflows-menu-anchor"
+ class="TWPT-btn--with-badge"
+ @click="${this._toggleMenu}">
+ <md-icon-button>
+ <md-icon>more_vert</md-icon>
+ </md-icon-button>
+ ${this.renderBadge()}
+ </div>
+ <md-menu ${ref(this.menuRef)}
+ class="workflows-menu"
+ anchor="workflows-menu-anchor">
+ ${this.renderMenuItems()}
+ </md-menu>
+ </span>
+ `;
+ }
+
+ _dispatchSelectEvent(uuid) {
+ const e = new CustomEvent('select', {
+ detail: {
+ selectedWorkflowUuid: uuid,
+ },
+ });
+ this.dispatchEvent(e);
+ this.menuRef.value.open = false;
+ }
+
+ _toggleMenu() {
+ this.menuRef.value.open = !this.menuRef.value.open;
+ }
+
+ _openWorkflowManager() {
+ const e = new Event('twpt-open-workflow-manager');
+ document.dispatchEvent(e);
+ }
+}
+window.customElements.define('twpt-workflows-menu', TwptWorkflowsMenu);
diff --git a/src/features/workflows/core/communityConsole/components/index.js b/src/features/workflows/core/communityConsole/components/index.js
new file mode 100644
index 0000000..31ac5cd
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/components/index.js
@@ -0,0 +1,72 @@
+import './TwptConfirmDialog.js';
+import './TwptCRImportButton.js';
+import './TwptWorkflowDialog.js';
+import './TwptWorkflowsMenu.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import WorkflowsStorage from '../../workflowsStorage.js';
+
+export default class TwptWorkflowsInject extends LitElement {
+ static properties = {
+ _workflows: {type: Object},
+ _selectedWorkflowUuid: {type: String},
+ };
+
+ confirmDialogRef = createRef();
+ workflowDialogRef = createRef();
+
+ constructor() {
+ super();
+ this._workflows = null;
+ this._selectedWorkflowUuid = null;
+ this.addEventListener('twpt-workflows-update', e => {
+ const workflows = e.detail?.workflows ?? [];
+ WorkflowsStorage.convertRawListToProtobuf(workflows);
+ this._workflows = workflows;
+ });
+ }
+
+ render() {
+ return html`
+ <twpt-workflows-menu
+ .workflows=${this._workflows}
+ @select=${this._workflowSelected}>
+ </twpt-workflows-menu>
+ <twpt-confirm-dialog ${ref(this.confirmDialogRef)}
+ .workflow=${this._selectedWorkflow}
+ @confirm=${this._startWorkflow}>
+ </twpt-confirm-dialog>
+ <twpt-workflow-dialog ${ref(this.workflowDialogRef)}>
+ </twpt-workflow-dialog>
+ `;
+ }
+
+ _workflowSelected(e) {
+ const uuid = e.detail?.selectedWorkflowUuid;
+ if (!uuid) {
+ console.error('Didn\'t get a correct uuid for the selected workflow.');
+ return;
+ }
+ this._selectedWorkflowUuid = uuid;
+ this.confirmDialogRef.value.open = true;
+ }
+
+ _startWorkflow() {
+ this.workflowDialogRef.value.workflow =
+ this._selectedWorkflow.cloneMessage();
+ this.workflowDialogRef.value.start();
+ }
+
+ get _selectedWorkflow() {
+ if (!this._workflows) return null;
+
+ for (const w of this._workflows) {
+ if (w.uuid == this._selectedWorkflowUuid) return w.proto;
+ }
+
+ return null;
+ }
+}
+window.customElements.define('twpt-workflows-inject', TwptWorkflowsInject);
diff --git a/src/features/workflows/core/communityConsole/import.js b/src/features/workflows/core/communityConsole/import.js
new file mode 100644
index 0000000..68eb5a7
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/import.js
@@ -0,0 +1,108 @@
+import {waitFor} from 'poll-until-promise';
+
+import {recursiveParentElement} from '../../../../common/commonUtils.js';
+import {injectStylesheet} from '../../../../common/contentScriptsUtils.js';
+import {isOptionEnabled} from '../../../../common/optionsUtils.js';
+
+const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse';
+
+const kImportParam = 'TWPTImportToWorkflow';
+const kSelectedIdParam = 'TWPTSelectedId';
+
+// Class which is used to inject a "select" button in the CRs list when loading
+// the canned response list for this purpose from the workflows manager.
+export default class WorkflowsImport {
+ constructor() {
+ // Only set this class up if the Community Console was opened with the
+ // purpose of importing CRs to the workflow manager.
+ const searchParams = new URLSearchParams(document.location.search);
+ if (!searchParams.has(kImportParam)) return;
+
+ this.selectedId = searchParams.get(kSelectedIdParam);
+
+ this.lastCRsList = {
+ body: {},
+ id: -1,
+ duplicateNames: new Set(),
+ };
+
+ this.setUpHandler();
+ this.addCustomStyles();
+ }
+
+ setUpHandler() {
+ window.addEventListener(kListCannedResponsesResponse, e => {
+ if (e.detail.id < this.lastCRsList.id) return;
+
+ // Look if there are duplicate names
+ const crs = e.detail.body?.['1'] ?? [];
+ const names = crs.map(cr => cr?.['7']).slice().sort();
+ let duplicateNames = new Set();
+ for (let i = 1; i < names.length; i++)
+ if (names[i - 1] == names[i]) duplicateNames.add(names[i]);
+
+ this.lastCRsList = {
+ body: e.detail.body,
+ id: e.detail.id,
+ duplicateNames,
+ };
+ });
+ }
+
+ addCustomStyles() {
+ injectStylesheet(chrome.runtime.getURL('css/workflow_import.css'));
+ }
+
+ addButton(tags) {
+ const cr = recursiveParentElement(tags, 'EC-CANNED-RESPONSE-ROW');
+ if (!cr) return;
+
+ const name = cr.querySelector('.text .name').textContent;
+ if (!name) return;
+
+ const toolbar = cr.querySelector('.action .toolbar');
+ if (!toolbar) return console.error(`Can't find toolbar.`);
+
+ // If it has already been injected, exit.
+ if (toolbar.querySelector('twpt-cr-import-button')) return;
+
+ waitFor(() => {
+ if (this.lastCRsList.id != -1) return Promise.resolve(this.lastCRsList);
+ return Promise.reject(new Error('Didn\'t receive canned responses list'));
+ }, {
+ interval: 500,
+ timeout: 15 * 1000,
+ }).then(crs => {
+ // If another CR has the same name, there's no easy way to distinguish
+ // them, so don't inject the button.
+ if (crs.duplicateNames.has(name)) {
+ console.warning(
+ 'CR "' + name +
+ '" is duplicate, so skipping the injection of the button.');
+ return;
+ }
+
+ for (const cr of (crs.body?.[1] ?? [])) {
+ if (cr[7] == name) {
+ const id = cr?.[1]?.[1];
+ if (!id) {
+ console.error('Can\'t find ID for canned response', cr);
+ break;
+ }
+
+ const button = document.createElement('twpt-cr-import-button');
+ button.setAttribute('cannedResponseId', id);
+ if (this.selectedId == id) button.setAttribute('selected', '');
+ toolbar.prepend(button);
+ break;
+ }
+ }
+ });
+ }
+
+ addButtonIfEnabled(tags) {
+ isOptionEnabled('workflows').then(isEnabled => {
+ if (isEnabled) this.addButton(tags);
+ });
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/models/thread.js b/src/features/workflows/core/communityConsole/models/thread.js
new file mode 100644
index 0000000..2618bcc
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/models/thread.js
@@ -0,0 +1,117 @@
+import {waitFor} from 'poll-until-promise';
+
+import {CCApi} from '../../../../../common/api.js';
+import {parseUrl} from '../../../../../common/commonUtils.js';
+import {getAuthUser} from '../../../../../common/communityConsoleUtils.js';
+
+export default class Thread {
+ constructor(forumId, threadId) {
+ this.forumId = forumId;
+ this.threadId = threadId;
+ this._details = null;
+ }
+
+ static fromUrl(url) {
+ const rawThread = parseUrl(url);
+ if (!rawThread) return null;
+
+ return new Thread(rawThread.forum, rawThread.thread);
+ }
+
+ loadThreadDetails() {
+ if (this._details) return Promise.resolve(true);
+
+ return waitFor(
+ () => {
+ return CCApi(
+ 'ViewForum', {
+ 1: '0', // forumID,
+ // options
+ 2: {
+ 3: false, // withMessages
+ 5: true, // withUserProfile
+ 6: false, // withUserReadState
+ 7: false, // withStickyThreads
+ 9: false, // withRequestorProfile
+ 10: false, // withPromotedMessages
+ 11: false, // withExpertResponder
+ 12: `forum:${this.forumId} thread:${
+ this.threadId}`, // forumViewFilters
+ 16: false, // withThreadNotes
+ 17: false, // withExpertReplyingIndicator
+ },
+ },
+ /* authenticated = */ true, getAuthUser())
+ .then(res => {
+ if (res?.['1']?.['2']?.length < 1)
+ throw new Error(
+ `Couldn't retrieve thread details (forum: ${
+ this.forumId}, thread: ${this.thread}).`);
+
+ return res?.['1']?.['2']?.[0];
+ });
+ },
+ {
+ interval: 500,
+ timeout: 2000,
+ })
+ .then(thread => {
+ this._details = thread;
+ return true;
+ });
+ }
+
+ get opName() {
+ return this._details?.['4']?.['1']?.['1'];
+ }
+
+ get opUserId() {
+ return this._details?.['4']?.['3'];
+ }
+
+ get forumTitle() {
+ return this._details?.['23'];
+ }
+
+ get isRead() {
+ return !!this._details?.['6'];
+ }
+
+ get isStarred() {
+ return !!this._details?.['7']?.['1'];
+ }
+
+ get numMessages() {
+ return this._details?.['8'];
+ }
+
+ get numAnswers() {
+ return this._details?.['15'];
+ }
+
+ get numSuggestedAnswers() {
+ return this._details?.['32'];
+ }
+
+ get title() {
+ return this._details?.['2']?.['9'];
+ }
+
+ get lastMessageId() {
+ return this._details?.['2']?.['10'];
+ }
+
+ get payload() {
+ return this._details?.['2']?.['13'];
+ }
+
+ // Accessors in the style of
+ // https://support.google.com/communities/answer/9147001.
+ get op_name() {
+ return this.opName;
+ }
+
+ get forum_name() {
+ return this.forumTitle;
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/runner.js b/src/features/workflows/core/communityConsole/runner.js
new file mode 100644
index 0000000..dfcb4d1
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/runner.js
@@ -0,0 +1,163 @@
+import {recursiveParentElement} from '../../../../common/commonUtils.js';
+import * as pb from '../proto/main_pb.js';
+
+import AttributeRunner from './actionRunners/attribute.js';
+import ReadStateRunner from './actionRunners/readState.js';
+import CRRunner from './actionRunners/replyWithCR.js';
+import Thread from './models/thread.js';
+
+export default class WorkflowRunner {
+ constructor(workflow, updateCallback) {
+ this.workflow = workflow;
+ this._threads = [];
+ this._currentThreadIndex = 0;
+ this._currentActionIndex = 0;
+ // Can be 'waiting', 'running', 'error', 'finished'
+ this._status = 'waiting';
+ this._updateCallback = updateCallback;
+
+ // Initialize action runners:
+ this._AttributeRunner = new AttributeRunner();
+ this._CRRunner = new CRRunner();
+ this._ReadStateRunner = new ReadStateRunner();
+ }
+
+ start() {
+ this._getSelectedThreads();
+ this.status = 'running';
+ this._runNextAction();
+ }
+
+ _getSelectedThreads() {
+ let threads = [];
+ const checkboxes = document.querySelectorAll(
+ '.thread-group material-checkbox[aria-checked="true"]');
+
+ for (const checkbox of checkboxes) {
+ const url = recursiveParentElement(checkbox, 'EC-THREAD-SUMMARY')
+ .querySelector('a.header-content')
+ .href;
+ const thread = Thread.fromUrl(url);
+ if (!thread) {
+ console.error('Couldn\'t parse URL ' + url);
+ continue;
+ }
+ threads.push(thread);
+ }
+
+ this.threads = threads;
+ }
+
+ _showError(err) {
+ console.warn(
+ `An error ocurred while executing action ${this.currentActionIndex}.`,
+ err);
+ this.status = 'error';
+ }
+
+ _runAction() {
+ switch (this._currentAction?.getActionCase?.()) {
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return this._AttributeRunner.execute(
+ this._currentAction?.getAttributeAction?.(), this._currentThread);
+
+ case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
+ return this._CRRunner.execute(
+ this._currentAction?.getReplyWithCrAction?.(), this._currentThread);
+
+ case pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION:
+ return this._ReadStateRunner.execute(true, this._currentThread);
+
+ case pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION:
+ return this._ReadStateRunner.execute(false, this._currentThread);
+
+ default:
+ return Promise.reject(new Error('This action isn\'t supported yet.'));
+ }
+ }
+
+ _runNextAction() {
+ if (this.status !== 'running')
+ return console.error(
+ 'Trying to run next action with status ' + this.status + '.');
+
+ this._runAction()
+ .then(() => {
+ if (this._nextActionIfAvailable())
+ this._runNextAction();
+ else
+ this._finish();
+ })
+ .catch(err => this._showError(err));
+ }
+
+ _nextActionIfAvailable() {
+ if (this.currentActionIndex === this._actions.length - 1) {
+ if (this.currentThreadIndex === this.numThreads - 1) return false;
+
+ this.currentThreadIndex++;
+ this.currentActionIndex = 0;
+ return true;
+ }
+
+ this.currentActionIndex++;
+ return true;
+ }
+
+ _finish() {
+ this.status = 'finished';
+ }
+
+ get numThreads() {
+ return this.threads.length ?? 0;
+ }
+
+ get _actions() {
+ return this.workflow?.getActionsList?.();
+ }
+
+ get _currentAction() {
+ return this._actions?.[this.currentActionIndex];
+ }
+
+ get _currentThread() {
+ return this._threads?.[this.currentThreadIndex];
+ }
+
+ // Setters/getters for properties, which will update the UI when changed.
+ get threads() {
+ return this._threads;
+ }
+
+ set threads(value) {
+ this._threads = value;
+ this._updateCallback();
+ }
+
+ get currentThreadIndex() {
+ return this._currentThreadIndex;
+ }
+
+ set currentThreadIndex(value) {
+ this._currentThreadIndex = value;
+ this._updateCallback();
+ }
+
+ get currentActionIndex() {
+ return this._currentActionIndex;
+ }
+
+ set currentActionIndex(value) {
+ this._currentActionIndex = value;
+ this._updateCallback();
+ }
+
+ get status() {
+ return this._status;
+ }
+
+ set status(value) {
+ this._status = value;
+ this._updateCallback();
+ }
+}
diff --git a/src/features/workflows/core/communityConsole/workflows.js b/src/features/workflows/core/communityConsole/workflows.js
new file mode 100644
index 0000000..652df29
--- /dev/null
+++ b/src/features/workflows/core/communityConsole/workflows.js
@@ -0,0 +1,50 @@
+import {isOptionEnabled} from '../../../../common/optionsUtils.js';
+import WorkflowsStorage from '../workflowsStorage.js';
+import {addElementToThreadListActions, shouldAddBtnToActionBar} from '../../../../contentScripts/communityConsole/utils/common.js';
+
+const wfDebugId = 'twpt-workflows';
+
+export default class Workflows {
+ constructor() {
+ this.menu = null;
+ this.workflows = null;
+
+ // Always keep the workflows list updated
+ WorkflowsStorage.watch(workflows => {
+ this.workflows = workflows;
+ this._emitWorkflowsUpdateEvent();
+ }, /* asProtobuf = */ false);
+
+ // Open the workflow manager when instructed to do so.
+ document.addEventListener('twpt-open-workflow-manager', () => {
+ chrome.runtime.sendMessage({
+ message: 'openWorkflowsManager',
+ });
+ });
+ }
+
+ _emitWorkflowsUpdateEvent() {
+ if (!this.menu) return;
+ const e = new CustomEvent('twpt-workflows-update', {
+ detail: {
+ workflows: this.workflows,
+ }
+ });
+ this.menu?.dispatchEvent?.(e);
+ }
+
+ addThreadListBtnIfEnabled(readToggle) {
+ isOptionEnabled('workflows').then(isEnabled => {
+ if (isEnabled) {
+ this.menu = document.createElement('twpt-workflows-inject');
+ this.menu.setAttribute('debugid', wfDebugId);
+ this._emitWorkflowsUpdateEvent();
+ addElementToThreadListActions(readToggle, this.menu);
+ }
+ });
+ }
+
+ shouldAddThreadListBtn(node) {
+ return shouldAddBtnToActionBar(wfDebugId, node);
+ }
+};
diff --git a/src/features/workflows/core/manager/components/ActionEditor.js b/src/features/workflows/core/manager/components/ActionEditor.js
new file mode 100644
index 0000000..cdcb85a
--- /dev/null
+++ b/src/features/workflows/core/manager/components/ActionEditor.js
@@ -0,0 +1,235 @@
+import './actions/Attribute.js';
+import './actions/ReplyWithCR.js';
+import '@material/mwc-circular-progress/mwc-circular-progress.js';
+
+import {html, LitElement, nothing} from 'lit';
+import {map} from 'lit/directives/map.js';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import * as pb from '../../proto/main_pb.js';
+import {kActionHeadings, kActionStyles, kSupportedActions} from '../shared/actions.js';
+
+const actionCases = Object.entries(pb.workflows.Action.ActionCase);
+
+export default class WFActionEditor extends LitElement {
+ static properties = {
+ action: {type: Object},
+ readOnly: {type: Boolean},
+ disableRemoveButton: {type: Boolean},
+ step: {type: Number},
+ status: {type: String},
+ };
+
+ static styles = kActionStyles;
+
+ selectRef = createRef();
+
+ constructor() {
+ super();
+ this.action = new pb.workflows.Action();
+ this.readOnly = false;
+ }
+
+ renderActionTitle() {
+ if (this.readOnly) return html`<h3 class="title">${this._stepTitle()}</h3>`;
+
+ let selectedActionCase = this._actionCase;
+
+ return html`
+ <select ${ref(this.selectRef)}
+ class="select"
+ @change=${this._actionCaseChanged}>
+ ${map(actionCases, ([actionName, num]) => {
+ if (!kSupportedActions.has(num)) return nothing;
+ return html`
+ <option value=${num} ?selected=${selectedActionCase == num}>
+ ${kActionHeadings[num] ?? actionName}
+ </option>
+ `;
+ })}
+ </select>
+ `;
+ }
+
+ renderSpecificActionEditor() {
+ switch (this._actionCase) {
+ case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
+ return html`
+ <wf-action-reply-with-cr
+ ?readOnly=${this.readOnly}
+ .action=${this.action.getReplyWithCrAction()}>
+ </wf-action-reply-with-cr>
+ `;
+
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return html`
+ <wf-action-attribute
+ ?readOnly=${this.readOnly}
+ .action=${this.action.getAttributeAction()}>
+ </wf-action-attribute>
+ `;
+
+ case pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION:
+ case pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION:
+ return nothing;
+
+ default:
+ return html`<p>This action has not yet been implemented.</p>`;
+ }
+ }
+
+ render() {
+ let actionClass = '';
+ if (this.readOnly && this.status) actionClass = 'action--' + this.status;
+ return html`
+ <div class="action ${actionClass}">
+ <div class="header">
+ <div class="step">
+ ${this.step}
+ ${
+ this.status == 'running' ?
+ html`<mwc-circular-progress indeterminate density="-1"></mwc-circular-progress>` :
+ ''}
+ </div>
+ ${this.renderActionTitle()}
+ ${
+ !this.readOnly ? html`
+ <button
+ ?disabled=${this.disableRemoveButton}
+ @click=${this._remove}>
+ Remove
+ </button>
+ ` :
+ nothing}
+ </div>
+ ${this.renderSpecificActionEditor()}
+ </div>
+ `;
+ }
+
+ checkValidity() {
+ if (this.readOnly || !kSupportedActions.has(this._actionCase)) return true;
+
+ const s = this._specificActionEditor();
+ if (!s) return true;
+
+ return this._specificActionEditor().checkValidity();
+ }
+
+ _actionCaseChanged() {
+ this._actionCaseString = this.selectRef.value.value;
+ }
+
+ _dispatchUpdateEvent() {
+ // Transmit to other components that the action has changed
+ const e = new Event('action-updated', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+
+ _remove() {
+ // Transmit to other components that the action has to be removed
+ const e = new Event('action-removed', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+
+ _stepTitle() {
+ return kActionHeadings[this._actionCase] ?? this._actionCase;
+ }
+
+ get _actionCase() {
+ return this.action.getActionCase();
+ }
+
+ set _actionCase(newCase) {
+ let value;
+ switch (newCase) {
+ case pb.workflows.Action.ActionCase.REPLY_ACTION:
+ value = new pb.workflows.Action.ReplyAction;
+ this.action.setReplyAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.MOVE_ACTION:
+ value = new pb.workflows.Action.MoveAction;
+ this.action.setMoveAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.MARK_DUPLICATE_ACTION:
+ value = new pb.workflows.Action.MarkDuplicateAction;
+ this.action.setMarkDuplicateAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.UNMARK_DUPLICATE_ACTION:
+ value = new pb.workflows.Action.UnmarkDuplicateAction;
+ this.action.setUnmarkDuplicateAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ value = new pb.workflows.Action.AttributeAction;
+ this.action.setAttributeAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
+ value = new pb.workflows.Action.ReplyWithCRAction;
+ this.action.setReplyWithCrAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.STAR_ACTION:
+ value = new pb.workflows.Action.StarAction;
+ this.action.setStarAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.SUBSCRIBE_ACTION:
+ value = new pb.workflows.Action.SubscribeAction;
+ this.action.setSubscribeAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.VOTE_ACTION:
+ value = new pb.workflows.Action.VoteAction;
+ this.action.setVoteAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.REPORT_ACTION:
+ value = new pb.workflows.Action.ReportAction;
+ this.action.setReportAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION:
+ value = new pb.workflows.Action.MarkAsReadAction;
+ this.action.setMarkAsReadAction(value);
+ break;
+ case pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION:
+ value = new pb.workflows.Action.MarkAsUnreadAction;
+ this.action.setMarkAsUnreadAction(value);
+ break;
+ default:
+ this.action.clearReplyAction();
+ this.action.clearMoveAction();
+ this.action.clearMarkDuplicateAction();
+ this.action.clearUnmarkDuplicateAction();
+ this.action.clearAttributeAction();
+ this.action.clearReplyWithCrAction();
+ this.action.clearStarAction();
+ this.action.clearSubscribeAction();
+ this.action.clearVoteAction();
+ this.action.clearReportAction();
+ this.action.clearMarkAsReadAction();
+ this.action.clearMarkAsUnreadAction();
+ }
+
+ this.requestUpdate();
+ this._dispatchUpdateEvent();
+ }
+
+ // The same as _actionCase, but represented as a String instead of a Number
+ get _actionCaseString() {
+ return this._actionCase.toString();
+ }
+
+ set _actionCaseString(newCase) {
+ this._actionCase = parseInt(newCase);
+ }
+
+ _specificActionEditor() {
+ switch (this._actionCase) {
+ case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
+ return this.renderRoot.querySelector('wf-action-reply-with-cr');
+
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return this.renderRoot.querySelector('wf-action-attribute');
+
+ default:
+ return null;
+ }
+ }
+}
+window.customElements.define('wf-action-editor', WFActionEditor);
diff --git a/src/features/workflows/core/manager/components/AddDialog.js b/src/features/workflows/core/manager/components/AddDialog.js
new file mode 100644
index 0000000..bcd582c
--- /dev/null
+++ b/src/features/workflows/core/manager/components/AddDialog.js
@@ -0,0 +1,93 @@
+import '@material/mwc-dialog/mwc-dialog.js';
+import '@material/web/button/text-button.js';
+import '@material/web/button/filled-button.js';
+import './WorkflowEditor.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import * as pb from '../../proto/main_pb.js';
+
+export default class WFAddDialog extends LitElement {
+ static properties = {
+ open: {type: Boolean},
+ };
+
+ static styles = css`
+ :host {
+ --mdc-dialog-content-ink-color: var(--mdc-theme-on-surface, #000);
+ }
+ `;
+
+ workflowEditorRef = createRef();
+
+ constructor() {
+ super();
+ this.open = false;
+ }
+
+ render() {
+ return html`
+ <mwc-dialog
+ ?open=${this.open}
+ @opening=${this._openingDialog}
+ @closing=${this._closingDialog}
+ @closed=${this._closedDialog}>
+ <wf-workflow-editor ${ref(this.workflowEditorRef)}>
+ </wf-workflow-editor>
+ <md-filled-button
+ slot="primaryAction"
+ @click=${this._save}>
+ Add
+ </md-filled-button>
+ <md-text-button
+ slot="secondaryAction"
+ dialogAction="cancel">
+ Cancel
+ </md-text-button>
+ </mwc-dialog>
+ `;
+ }
+
+ firstUpdated() {
+ this._resetWorkflow();
+ }
+
+ _resetWorkflow() {
+ this.workflowEditorRef.value.workflow = this._defaultWorkflow();
+ }
+
+ _getWorkflow() {
+ return this.workflowEditorRef.value.workflow;
+ }
+
+ _defaultWorkflow() {
+ let wf = new pb.workflows.Workflow();
+ let action = new pb.workflows.Action();
+ let rAction = new pb.workflows.Action.ReplyWithCRAction();
+ action.setReplyWithCrAction(rAction);
+ wf.addActions(action);
+ return wf;
+ }
+
+ _openingDialog() {
+ this.open = true;
+ }
+
+ _closingDialog() {
+ this.open = false;
+ }
+
+ _closedDialog(e) {
+ if (e.detail?.action === 'cancel') this._resetWorkflow();
+ }
+
+ _save() {
+ const success = this.workflowEditorRef.value.save();
+ if (success) {
+ this.open = false;
+ this._resetWorkflow();
+ }
+ }
+}
+window.customElements.define('wf-add-dialog', WFAddDialog);
diff --git a/src/features/workflows/core/manager/components/List.js b/src/features/workflows/core/manager/components/List.js
new file mode 100644
index 0000000..9f7ed5f
--- /dev/null
+++ b/src/features/workflows/core/manager/components/List.js
@@ -0,0 +1,97 @@
+import '@material/web/list/list.js';
+import '@material/web/list/list-item.js';
+import '@material/web/icon/icon.js';
+import '@material/web/iconbutton/icon-button.js';
+import './WorkflowDialog.js';
+
+import {css, html, LitElement, nothing} from 'lit';
+import {map} from 'lit/directives/map.js';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import WorkflowsStorage from '../../workflowsStorage.js';
+
+export default class WFList extends LitElement {
+ static properties = {
+ workflows: {type: Object},
+ };
+
+ static styles = css`
+ .noworkflows {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 32px 0;
+ }
+
+ .noworkflows--image {
+ margin-bottom: 16px;
+ max-width: 500px;
+ }
+
+ .noworkflows--helper {
+ color: #555;
+ }
+ `;
+
+ dialogRef = createRef();
+
+ renderListItems() {
+ return map(this.workflows, w => html`
+ <md-list-item
+ type="button"
+ @click=${() => this._show(w)}>
+ <div slot="headline">${w.proto?.getName?.()}</div>
+ <div slot="end" class="end">
+ <md-icon-button
+ @click=${e => this._showDelete(w.uuid, e)}>
+ <md-icon>delete</md-icon>
+ </md-icon-button>
+ </div>
+ </md-list-item>
+ `);
+ }
+
+ renderList() {
+ if (!this.workflows) return nothing;
+ if (this.workflows?.length === 0)
+ return html`
+ <div class="noworkflows">
+ <img class="noworkflows--image" src="/img/undraw_insert.svg">
+ <span class="noworkflows--helper">You haven't created any workflow yet! Create one by clicking the button in the bottom-right corner.</span>
+ </div>
+ `;
+
+ return html`
+ <md-list>
+ ${this.renderListItems()}
+ </md-list>
+ `;
+ }
+
+ renderDialog() {
+ return html`
+ <wf-workflow-dialog ${ref(this.dialogRef)}></wf-workflow-dialog>
+ `;
+ }
+
+ render() {
+ return [
+ this.renderList(),
+ this.renderDialog(),
+ ];
+ }
+
+ _show(fullWorkflow) {
+ this.dialogRef.value.uuid = fullWorkflow.uuid;
+ this.dialogRef.value.workflow = fullWorkflow.proto.cloneMessage();
+ this.dialogRef.value.open = true;
+ }
+
+ _showDelete(uuid, e) {
+ e.stopPropagation();
+ const proceed = window.confirm(
+ 'Do you really want to remove this workflow? This action is irreversible.');
+ if (proceed) WorkflowsStorage.remove(uuid);
+ }
+}
+window.customElements.define('wf-list', WFList);
diff --git a/src/features/workflows/core/manager/components/WorkflowDialog.js b/src/features/workflows/core/manager/components/WorkflowDialog.js
new file mode 100644
index 0000000..e990246
--- /dev/null
+++ b/src/features/workflows/core/manager/components/WorkflowDialog.js
@@ -0,0 +1,68 @@
+import '@material/mwc-dialog/mwc-dialog.js';
+import '@material/web/button/text-button.js';
+import '@material/web/button/filled-button.js';
+import './WorkflowEditor.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import * as pb from '../../proto/main_pb.js';
+
+export default class WFWorkflowDialog extends LitElement {
+ static properties = {
+ open: {type: Boolean},
+ uuid: {type: String},
+ workflow: {type: Object},
+ };
+
+ static styles = css`
+ :host {
+ --mdc-dialog-content-ink-color: var(--mdc-theme-on-surface, #000);
+ }
+ `;
+
+ workflowEditorRef = createRef();
+
+ constructor() {
+ super();
+ this.open = false;
+ this.workflow = new pb.workflows.Workflow();
+ }
+
+ render() {
+ return html`
+ <mwc-dialog
+ ?open=${this.open}
+ @opening=${this._openingDialog}
+ @closing=${this._closingDialog}>
+ <wf-workflow-editor ${ref(this.workflowEditorRef)}
+ .workflow=${this.workflow}>
+ </wf-workflow-editor>
+ <md-filled-button
+ slot="primaryAction"
+ @click=${this._save}>
+ Save
+ </md-filled-button>
+ <md-text-button
+ slot="secondaryAction"
+ dialogAction="cancel">
+ Cancel
+ </md-text-button>
+ </mwc-dialog>
+ `;
+ }
+
+ _openingDialog() {
+ this.open = true;
+ }
+
+ _closingDialog() {
+ this.open = false;
+ }
+
+ _save() {
+ const success = this.workflowEditorRef.value.save(this.uuid);
+ if (success) this.open = false;
+ }
+}
+window.customElements.define('wf-workflow-dialog', WFWorkflowDialog);
diff --git a/src/features/workflows/core/manager/components/WorkflowEditor.js b/src/features/workflows/core/manager/components/WorkflowEditor.js
new file mode 100644
index 0000000..1818ea7
--- /dev/null
+++ b/src/features/workflows/core/manager/components/WorkflowEditor.js
@@ -0,0 +1,144 @@
+import '@material/web/button/outlined-button.js';
+import '@material/web/icon/icon.js';
+import '@material/web/textfield/filled-text-field.js';
+import './ActionEditor.js';
+
+import {css, html, LitElement, nothing} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+import {repeat} from 'lit/directives/repeat.js';
+
+import * as pb from '../../proto/main_pb.js';
+import WorkflowsStorage from '../../workflowsStorage.js';
+
+export default class WFWorkflowEditor extends LitElement {
+ static properties = {
+ workflow: {type: Object},
+ readOnly: {type: Boolean},
+ };
+
+ static styles = css`
+ .name {
+ width: 100%;
+ margin-bottom: 20px;
+ }
+ `;
+
+ nameRef = createRef();
+
+ constructor() {
+ super();
+ this.workflow = new pb.workflows.Workflow();
+ this.readOnly = false;
+ }
+
+ renderName() {
+ return html`
+ <md-filled-text-field ${ref(this.nameRef)}
+ class="name"
+ placeholder="Untitled workflow"
+ value=${this.workflow.getName()}
+ required
+ @input=${this._nameChanged}>
+ </md-filled-text-field>
+ `;
+ }
+
+ renderActions() {
+ return repeat(this._actions(), (action, i) => html`
+ <wf-action-editor
+ .action=${action}
+ ?readOnly=${this.readOnly}
+ ?disableRemoveButton=${this._actions().length <= 1}
+ step=${i + 1}
+ @action-removed=${() => this._removeAction(i)}>
+ </wf-action-editor>
+ `);
+ }
+
+ renderAddActionBtn() {
+ if (this.readOnly) return nothing;
+ return html`
+ <md-outlined-button
+ @click=${this._addAction}>
+ <md-icon slot="icon">add</md-icon>
+ Add another action
+ </md-outlined-button>
+ `;
+ }
+
+ render() {
+ return [
+ this.renderName(),
+ this.renderActions(),
+ this.renderAddActionBtn(),
+ ];
+ }
+
+ checkValidity() {
+ let allValid = true;
+
+ // Check the workflow name is set
+ allValid &&= this.nameRef.value.reportValidity();
+
+ // Check all the actions are well-formed
+ const actionEditors = this.renderRoot.querySelectorAll('wf-action-editor');
+ for (const editor of actionEditors) allValid &&= editor.checkValidity();
+
+ return allValid;
+ }
+
+ save(uuid) {
+ const allValid = this.checkValidity();
+
+ // Save the workflow if the validation checks passed
+ if (allValid) {
+ if (!uuid)
+ WorkflowsStorage.add(this.workflow);
+ else
+ WorkflowsStorage.update(uuid, this.workflow);
+ }
+
+ return allValid;
+ }
+
+ _actions() {
+ return this.workflow.getActionsList();
+ }
+
+ _nameChanged() {
+ this.workflow.setName(this.nameRef.value.value);
+ this._dispatchUpdateEvent();
+ }
+
+ _addAction() {
+ let action = new pb.workflows.Action();
+ let rAction = new pb.workflows.Action.ReplyWithCRAction();
+ action.setReplyWithCrAction(rAction);
+ this.workflow.addActions(action);
+ this._dispatchUpdateEvent();
+ }
+
+ _removeAction(index) {
+ let actions = this.workflow.getActionsList();
+ actions.splice(index, 1);
+ this.workflow.setActionsList(actions);
+ this._dispatchUpdateEvent();
+ }
+
+ _updateAction(index, action) {
+ let actions = this.workflow.getActionsList();
+ actions[index] = action;
+ this.workflow.setActionsList(actions);
+ this._dispatchUpdateEvent();
+ }
+
+ _dispatchUpdateEvent() {
+ // Request an update for this component
+ this.requestUpdate();
+
+ // Transmit to other components that the workflow has changed
+ const e = new Event('workflow-updated', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+}
+window.customElements.define('wf-workflow-editor', WFWorkflowEditor);
diff --git a/src/features/workflows/core/manager/components/actions/Attribute.js b/src/features/workflows/core/manager/components/actions/Attribute.js
new file mode 100644
index 0000000..8076026
--- /dev/null
+++ b/src/features/workflows/core/manager/components/actions/Attribute.js
@@ -0,0 +1,87 @@
+import '@material/web/select/outlined-select.js';
+import '@material/web/select/select-option.js';
+
+import {html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../../common/styles/md3.js';
+import * as pb from '../../../proto/main_pb.js';
+
+import {FORM_STYLES} from './common.js';
+
+const kHiddenActions = [
+ pb.workflows.Action.AttributeAction.AttributeAction.AA_NONE,
+];
+
+export default class WFActionAttribute extends LitElement {
+ static properties = {
+ action: {type: Object},
+ readOnly: {type: Boolean},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ FORM_STYLES,
+ ];
+
+ attributeActionRef = createRef();
+
+ constructor() {
+ super();
+ this.action = new pb.workflows.Action.AttributeAction;
+ }
+
+ render() {
+ return html`
+ <div class="form-line">
+ <md-outlined-select ${ref(this.attributeActionRef)}
+ required
+ label="Action"
+ value=${this.action}
+ ?disabled=${this.readOnly}
+ @change=${this._attributeActionChanged}>
+ ${this.renderAttributeActions()}
+ </md-outlined-select>
+ </div>
+ `;
+ }
+
+ renderAttributeActions() {
+ const attributeActions =
+ Object.entries(pb.workflows.Action.AttributeAction.AttributeAction);
+ return attributeActions.filter(([, id]) => !kHiddenActions.includes(id))
+ .map(([actionCodename, id]) => html`
+ <md-select-option value=${id}>
+ <div slot="headline">${actionCodename}</div>
+ </md-select-option>
+ `);
+ }
+
+ checkValidity() {
+ return this.attributeActionRef.value.reportValidity();
+ }
+
+ _dispatchUpdateEvent() {
+ // Request an update for this component
+ this.requestUpdate();
+
+ // Transmit to other components that the action has changed
+ const e =
+ new Event('attribute-action-updated', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+
+ _attributeActionChanged() {
+ this.attributeAction = this.attributeActionRef.value.value;
+ }
+
+ get attributeAction() {
+ return this.action.getAttributeAction();
+ }
+
+ set attributeAction(value) {
+ this.action.setAttributeAction(value);
+ this._dispatchUpdateEvent();
+ }
+}
+window.customElements.define('wf-action-attribute', WFActionAttribute);
diff --git a/src/features/workflows/core/manager/components/actions/ReplyWithCR.js b/src/features/workflows/core/manager/components/actions/ReplyWithCR.js
new file mode 100644
index 0000000..65d04e2
--- /dev/null
+++ b/src/features/workflows/core/manager/components/actions/ReplyWithCR.js
@@ -0,0 +1,173 @@
+import '@material/web/icon/icon.js';
+import '@material/web/switch/switch.js';
+import '@material/web/textfield/outlined-text-field.js';
+import '../../../../../../common/components/FormField.js';
+
+import {css, html, LitElement, nothing} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../../../common/styles/md3.js';
+import * as pb from '../../../proto/main_pb.js';
+import { FORM_STYLES } from './common.js';
+
+export default class WFActionReplyWithCR extends LitElement {
+ static properties = {
+ action: {type: Object},
+ readOnly: {type: Boolean},
+ _importerWindow: {type: Object, state: true},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ FORM_STYLES,
+ css`
+ .select-cr-btn {
+ --md-outlined-button-icon-size: 24px;
+ }
+ `,
+ ];
+
+ cannedResponseRef = createRef();
+ subscribeRef = createRef();
+ markAsAnswerRef = createRef();
+
+ constructor() {
+ super();
+ this.action = new pb.workflows.Action.ReplyWithCRAction;
+ this._importerWindow = undefined;
+
+ window.addEventListener('message', e => {
+ if (e.source === this._importerWindow &&
+ e.data?.action === 'importCannedResponse') {
+ this._cannedResponseIdString = e.data?.cannedResponseId;
+ this._importerWindow?.close?.();
+ }
+ });
+ }
+
+ render() {
+ return html`
+ <div class="form-line">
+ <md-outlined-text-field ${ref(this.cannedResponseRef)}
+ type="number"
+ label="Canned response ID"
+ required
+ value=${this._cannedResponseIdString}
+ ?readonly=${this.readOnly}
+ @input=${this._cannedResponseIdChanged}>
+ </md-outlined-text-field>
+ ${this.readOnly ? nothing : html`
+ <md-outlined-button
+ class="select-cr-btn"
+ @click=${this._openCRImporter}>
+ <md-icon slot="icon" filled>more</md-icon>
+ Select CR
+ </md-outlined-button>
+ `}
+ </div>
+ <div class="form-line">
+ <twpt-form-field>
+ <md-switch ${ref(this.subscribeRef)}
+ ?selected=${this.subscribe}
+ ?disabled=${this.readOnly}
+ @change=${this._subscribeChanged}/>
+ </md-switch>
+ <span slot="label">
+ Subscribe to thread
+ </span>
+ </twpt-form-field>
+ </div>
+ <div class="form-line">
+ <twpt-form-field>
+ <md-switch ${ref(this.markAsAnswerRef)}
+ ?selected=${this.markAsAnswer}
+ ?disabled=${this.readOnly}
+ @change=${this._markAsAnswerChanged}/>
+ </md-switch>
+ <span slot="label">
+ Mark as answer
+ </span>
+ </twpt-form-field>
+ </div>
+ `;
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this._importerWindow?.close?.();
+ }
+
+ checkValidity() {
+ return this.cannedResponseRef.value.reportValidity();
+ }
+
+ _dispatchUpdateEvent() {
+ // Request an update for this component
+ this.requestUpdate();
+
+ // Transmit to other components that the action has changed
+ const e = new Event(
+ 'replywithcr-action-updated', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+
+ _cannedResponseIdChanged() {
+ this._cannedResponseIdString = this.cannedResponseRef.value.value;
+ }
+
+ _subscribeChanged() {
+ this.subscribe = this.subscribeRef.value.selected;
+ }
+
+ _markAsAnswerChanged() {
+ this.markAsAnswer = this.markAsAnswerRef.value.selected;
+ }
+
+ _openCRImporter() {
+ if (!(this._importerWindow?.closed ?? true))
+ this._importerWindow?.close?.();
+
+ this._importerWindow = window.open(
+ 'https://support.google.com/s/community/cannedresponses?TWPTImportToWorkflow&TWPTSelectedId=' +
+ encodeURIComponent(this._cannedResponseIdString),
+ '', 'popup,width=720,height=540');
+ }
+
+ get cannedResponseId() {
+ return this.action.getCannedResponseId() ?? 0;
+ }
+
+ set cannedResponseId(value) {
+ this.action.setCannedResponseId(value);
+ this._dispatchUpdateEvent();
+ }
+
+ get _cannedResponseIdString() {
+ let id = this.cannedResponseId;
+ if (id == 0) return '';
+ return id.toString();
+ }
+
+ set _cannedResponseIdString(value) {
+ this.cannedResponseId = parseInt(value);
+ }
+
+ get subscribe() {
+ return this.action.getSubscribe();
+ }
+
+ set subscribe(value) {
+ this.action.setSubscribe(value);
+ this._dispatchUpdateEvent();
+ }
+
+ get markAsAnswer() {
+ return this.action.getMarkAsAnswer();
+ }
+
+ set markAsAnswer(value) {
+ this.action.setMarkAsAnswer(value);
+ this._dispatchUpdateEvent();
+ }
+}
+window.customElements.define('wf-action-reply-with-cr', WFActionReplyWithCR);
diff --git a/src/features/workflows/core/manager/components/actions/common.js b/src/features/workflows/core/manager/components/actions/common.js
new file mode 100644
index 0000000..3d5be56
--- /dev/null
+++ b/src/features/workflows/core/manager/components/actions/common.js
@@ -0,0 +1,11 @@
+import {css} from 'lit';
+
+export const FORM_STYLES = css`
+ .form-line {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-block: 1em;
+ gap: .5rem;
+ }
+`;
diff --git a/src/features/workflows/core/manager/index.js b/src/features/workflows/core/manager/index.js
new file mode 100644
index 0000000..68bbf21
--- /dev/null
+++ b/src/features/workflows/core/manager/index.js
@@ -0,0 +1,66 @@
+import '@material/web/fab/fab.js';
+import '@material/web/icon/icon.js';
+import './components/List.js';
+import './components/AddDialog.js';
+import './components/WorkflowDialog.js';
+
+import {css, html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
+import {default as WorkflowsStorage, kWorkflowsDataKey} from '../workflowsStorage.js';
+
+export default class WFApp extends LitElement {
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ :host {
+ font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ Helvetica, Arial, sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol'!important;
+
+ display: block;
+ max-width: 1024px;
+ margin: auto;
+ position: relative;
+ }
+
+ md-fab {
+ position: fixed;
+ bottom: 2em;
+ right: 2em;
+ }
+ `,
+ ];
+
+ addFabRef = createRef();
+ addDialog = createRef();
+
+ constructor() {
+ super();
+ this._workflows = undefined;
+ WorkflowsStorage.watch(workflows => {
+ this._workflows = workflows;
+ this.requestUpdate();
+ }, /* asProtobuf = */ true);
+ }
+
+ render() {
+ return html`
+ <h1>Workflows</h1>
+ <p>Workflows allow you to run a customized list of actions on a thread easily.</p>
+ <wf-list .workflows=${this._workflows}></wf-list>
+ <md-fab ${ref(this.addFabRef)}
+ @click=${this._showAddDialog}>
+ <md-icon slot="icon">add</md-icon>
+ </md-fab>
+ <wf-add-dialog ${ref(this.addDialog)}>
+ </wf-add-dialog>
+ `;
+ }
+
+ _showAddDialog() {
+ this.addDialog.value.open = true;
+ }
+}
+window.customElements.define('wf-app', WFApp);
diff --git a/src/features/workflows/core/manager/shared/actions.js b/src/features/workflows/core/manager/shared/actions.js
new file mode 100644
index 0000000..ef23926
--- /dev/null
+++ b/src/features/workflows/core/manager/shared/actions.js
@@ -0,0 +1,85 @@
+import {css} from 'lit';
+
+import * as pb from '../../proto/main_pb.js';
+
+// TODO: remove this and substitute it with proper localization.
+export const kActionHeadings = {
+ 0: 'Unknown action',
+ 1: 'Reply',
+ 2: 'Move to a forum',
+ 3: 'Mark as duplicate of a thread',
+ 4: 'Unmark duplicate',
+ 5: 'Change thread attribute',
+ 6: 'Reply with canned response',
+ 16: 'Star/unstar thread',
+ 17: 'Subscribe/unsubscribe to thread',
+ 18: 'Vote thread',
+ 19: 'Report thread',
+ 20: 'Mark as read',
+ 21: 'Mark as unread',
+};
+
+export const kSupportedActions = new Set([
+ pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION,
+ pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION,
+ pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION,
+ pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION,
+]);
+
+export const kActionStyles = css`
+ .action {
+ margin-bottom: 20px;
+ }
+
+ .header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ .step {
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+
+ min-height: 30px;
+ min-width: 30px;
+ margin-inline-end: 8px;
+
+ font-weight: 500;
+ font-size: 18px;
+
+ border-radius: 50%;
+ color: white;
+ background-color: #018786;
+ }
+
+ :is(.action--idle, .action--running) .step {
+ color: black;
+ background-color: #d1d1d1;
+ }
+
+ .action--error .step {
+ background-color: #c30000;
+ }
+
+ .step mwc-circular-progress {
+ position: absolute;
+ --mdc-theme-primary: #018786;
+ }
+
+ .title {
+ font-weight: 500;
+ margin: 0;
+ flex-grow: 1;
+ }
+
+ .header .select {
+ flex-grow: 1;
+ width: 300px;
+ padding: 4px;
+ margin-inline-end: 8px;
+ font-size: 16px;
+ }
+`;
diff --git a/src/features/workflows/core/proto/main.proto b/src/features/workflows/core/proto/main.proto
new file mode 100644
index 0000000..dd10682
--- /dev/null
+++ b/src/features/workflows/core/proto/main.proto
@@ -0,0 +1,110 @@
+syntax = "proto3";
+
+package workflows;
+
+message Thread {
+ int64 forum_id = 1;
+ int64 thread_id = 2;
+}
+
+message Action {
+ message ReplyAction {
+ string payload = 1;
+ bool subscribe = 2;
+ bool mark_as_answer = 3;
+ }
+
+ message MoveAction {
+ int64 forum_id = 1;
+ string category = 2;
+ string language = 3;
+ map<string, string> property = 4;
+ }
+
+ message MarkDuplicateAction {
+ Thread destination = 1;
+ }
+
+ message UnmarkDuplicateAction {}
+
+ message ReplyWithCRAction {
+ int64 canned_response_id = 1;
+ bool subscribe = 2;
+ bool mark_as_answer = 3;
+ }
+
+ message StarAction {
+ bool star = 1; // true stars, and false unstars.
+ }
+
+ message SubscribeAction {
+ bool subscribe = 1; // true subscribes, false unsubscribes.
+ }
+
+ message VoteAction {
+ enum Vote {
+ NONE = 0;
+ UP = 1;
+ DOWN = -1;
+ }
+ Vote vote = 1;
+ }
+
+ message AttributeAction {
+ enum AttributeAction {
+ AA_NONE = 0;
+ AA_LOCK = 1;
+ AA_UNLOCK = 2;
+ AA_PIN = 3;
+ AA_UNPIN = 4;
+ AA_NON_ISSUE = 5;
+ AA_OBSOLETE = 6;
+ AA_REVERT = 7;
+ AA_SET_TRENDING = 8;
+ AA_UNSET_TRENDING = 9;
+ AA_SET_ISSUE_RESOLVED = 10;
+ AA_UNSET_ISSUE_RESOLVED = 11;
+ AA_SOFT_LOCK = 12;
+ AA_UNSOFT_LOCK = 13;
+ AA_EXCLUDE_FROM_GOLDEN = 14;
+ AA_UNEXCLUDE_FROM_GOLDEN = 15;
+ AA_INCLUDE_IN_GOLDEN = 16;
+ }
+ AttributeAction attribute_action = 1;
+ }
+
+ message ReportAction {
+ enum ReportType {
+ RT_UNKNOWN = 0;
+ RT_OFF_TOPIC = 1;
+ RT_ABUSE = 2;
+ }
+ ReportType report_type = 1;
+ }
+
+ message MarkAsReadAction {}
+
+ message MarkAsUnreadAction {}
+
+ oneof action {
+ ReplyAction reply_action = 1;
+ MoveAction move_action = 2;
+ MarkDuplicateAction mark_duplicate_action = 3;
+ UnmarkDuplicateAction unmark_duplicate_action = 4;
+ AttributeAction attribute_action = 5;
+ ReplyWithCRAction reply_with_cr_action = 6;
+ StarAction star_action = 16;
+ SubscribeAction subscribe_action = 17;
+ VoteAction vote_action = 18;
+ ReportAction report_action = 19;
+ MarkAsReadAction mark_as_read_action = 20;
+ MarkAsUnreadAction mark_as_unread_action = 21;
+ }
+}
+
+message Workflow {
+ string name = 1;
+ string description = 2;
+ int32 index = 3;
+ repeated Action actions = 4;
+}
diff --git a/src/features/workflows/core/proto/main_pb.js b/src/features/workflows/core/proto/main_pb.js
new file mode 100644
index 0000000..75ee13b
--- /dev/null
+++ b/src/features/workflows/core/proto/main_pb.js
@@ -0,0 +1,3259 @@
+// source: proto/main.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {missingRequire} reports error on implicit type usages.
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ * field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var proto = {};
+
+goog.exportSymbol('workflows.Action', null, proto);
+goog.exportSymbol('workflows.Action.ActionCase', null, proto);
+goog.exportSymbol('workflows.Action.AttributeAction', null, proto);
+goog.exportSymbol('workflows.Action.AttributeAction.AttributeAction', null, proto);
+goog.exportSymbol('workflows.Action.MarkAsReadAction', null, proto);
+goog.exportSymbol('workflows.Action.MarkAsUnreadAction', null, proto);
+goog.exportSymbol('workflows.Action.MarkDuplicateAction', null, proto);
+goog.exportSymbol('workflows.Action.MoveAction', null, proto);
+goog.exportSymbol('workflows.Action.ReplyAction', null, proto);
+goog.exportSymbol('workflows.Action.ReplyWithCRAction', null, proto);
+goog.exportSymbol('workflows.Action.ReportAction', null, proto);
+goog.exportSymbol('workflows.Action.ReportAction.ReportType', null, proto);
+goog.exportSymbol('workflows.Action.StarAction', null, proto);
+goog.exportSymbol('workflows.Action.SubscribeAction', null, proto);
+goog.exportSymbol('workflows.Action.UnmarkDuplicateAction', null, proto);
+goog.exportSymbol('workflows.Action.VoteAction', null, proto);
+goog.exportSymbol('workflows.Action.VoteAction.Vote', null, proto);
+goog.exportSymbol('workflows.Thread', null, proto);
+goog.exportSymbol('workflows.Workflow', null, proto);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Thread = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Thread, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Thread.displayName = 'proto.workflows.Thread';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, proto.workflows.Action.oneofGroups_);
+};
+goog.inherits(proto.workflows.Action, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.displayName = 'proto.workflows.Action';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.ReplyAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.ReplyAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.ReplyAction.displayName = 'proto.workflows.Action.ReplyAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.MoveAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.MoveAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.MoveAction.displayName = 'proto.workflows.Action.MoveAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.MarkDuplicateAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.MarkDuplicateAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.MarkDuplicateAction.displayName = 'proto.workflows.Action.MarkDuplicateAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.UnmarkDuplicateAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.UnmarkDuplicateAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.UnmarkDuplicateAction.displayName = 'proto.workflows.Action.UnmarkDuplicateAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.ReplyWithCRAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.ReplyWithCRAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.ReplyWithCRAction.displayName = 'proto.workflows.Action.ReplyWithCRAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.StarAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.StarAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.StarAction.displayName = 'proto.workflows.Action.StarAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.SubscribeAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.SubscribeAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.SubscribeAction.displayName = 'proto.workflows.Action.SubscribeAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.VoteAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.VoteAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.VoteAction.displayName = 'proto.workflows.Action.VoteAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.AttributeAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.AttributeAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.AttributeAction.displayName = 'proto.workflows.Action.AttributeAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.ReportAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.ReportAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.ReportAction.displayName = 'proto.workflows.Action.ReportAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.MarkAsReadAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.MarkAsReadAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.MarkAsReadAction.displayName = 'proto.workflows.Action.MarkAsReadAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Action.MarkAsUnreadAction = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.workflows.Action.MarkAsUnreadAction, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Action.MarkAsUnreadAction.displayName = 'proto.workflows.Action.MarkAsUnreadAction';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.workflows.Workflow = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, proto.workflows.Workflow.repeatedFields_, null);
+};
+goog.inherits(proto.workflows.Workflow, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.workflows.Workflow.displayName = 'proto.workflows.Workflow';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Thread.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Thread.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Thread} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Thread.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ forumId: jspb.Message.getFieldWithDefault(msg, 1, 0),
+ threadId: jspb.Message.getFieldWithDefault(msg, 2, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Thread}
+ */
+proto.workflows.Thread.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Thread;
+ return proto.workflows.Thread.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Thread} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Thread}
+ */
+proto.workflows.Thread.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setForumId(value);
+ break;
+ case 2:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setThreadId(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Thread.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Thread.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Thread} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Thread.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getForumId();
+ if (f !== 0) {
+ writer.writeInt64(
+ 1,
+ f
+ );
+ }
+ f = message.getThreadId();
+ if (f !== 0) {
+ writer.writeInt64(
+ 2,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional int64 forum_id = 1;
+ * @return {number}
+ */
+proto.workflows.Thread.prototype.getForumId = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.workflows.Thread} returns this
+ */
+proto.workflows.Thread.prototype.setForumId = function(value) {
+ return jspb.Message.setProto3IntField(this, 1, value);
+};
+
+
+/**
+ * optional int64 thread_id = 2;
+ * @return {number}
+ */
+proto.workflows.Thread.prototype.getThreadId = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.workflows.Thread} returns this
+ */
+proto.workflows.Thread.prototype.setThreadId = function(value) {
+ return jspb.Message.setProto3IntField(this, 2, value);
+};
+
+
+
+/**
+ * Oneof group definitions for this message. Each group defines the field
+ * numbers belonging to that group. When of these fields' value is set, all
+ * other fields in the group are cleared. During deserialization, if multiple
+ * fields are encountered for a group, only the last value seen will be kept.
+ * @private {!Array<!Array<number>>}
+ * @const
+ */
+proto.workflows.Action.oneofGroups_ = [[1,2,3,4,5,6,16,17,18,19,20,21]];
+
+/**
+ * @enum {number}
+ */
+proto.workflows.Action.ActionCase = {
+ ACTION_NOT_SET: 0,
+ REPLY_ACTION: 1,
+ MOVE_ACTION: 2,
+ MARK_DUPLICATE_ACTION: 3,
+ UNMARK_DUPLICATE_ACTION: 4,
+ ATTRIBUTE_ACTION: 5,
+ REPLY_WITH_CR_ACTION: 6,
+ STAR_ACTION: 16,
+ SUBSCRIBE_ACTION: 17,
+ VOTE_ACTION: 18,
+ REPORT_ACTION: 19,
+ MARK_AS_READ_ACTION: 20,
+ MARK_AS_UNREAD_ACTION: 21
+};
+
+/**
+ * @return {proto.workflows.Action.ActionCase}
+ */
+proto.workflows.Action.prototype.getActionCase = function() {
+ return /** @type {proto.workflows.Action.ActionCase} */(jspb.Message.computeOneofCase(this, proto.workflows.Action.oneofGroups_[0]));
+};
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ replyAction: (f = msg.getReplyAction()) && proto.workflows.Action.ReplyAction.toObject(includeInstance, f),
+ moveAction: (f = msg.getMoveAction()) && proto.workflows.Action.MoveAction.toObject(includeInstance, f),
+ markDuplicateAction: (f = msg.getMarkDuplicateAction()) && proto.workflows.Action.MarkDuplicateAction.toObject(includeInstance, f),
+ unmarkDuplicateAction: (f = msg.getUnmarkDuplicateAction()) && proto.workflows.Action.UnmarkDuplicateAction.toObject(includeInstance, f),
+ attributeAction: (f = msg.getAttributeAction()) && proto.workflows.Action.AttributeAction.toObject(includeInstance, f),
+ replyWithCrAction: (f = msg.getReplyWithCrAction()) && proto.workflows.Action.ReplyWithCRAction.toObject(includeInstance, f),
+ starAction: (f = msg.getStarAction()) && proto.workflows.Action.StarAction.toObject(includeInstance, f),
+ subscribeAction: (f = msg.getSubscribeAction()) && proto.workflows.Action.SubscribeAction.toObject(includeInstance, f),
+ voteAction: (f = msg.getVoteAction()) && proto.workflows.Action.VoteAction.toObject(includeInstance, f),
+ reportAction: (f = msg.getReportAction()) && proto.workflows.Action.ReportAction.toObject(includeInstance, f),
+ markAsReadAction: (f = msg.getMarkAsReadAction()) && proto.workflows.Action.MarkAsReadAction.toObject(includeInstance, f),
+ markAsUnreadAction: (f = msg.getMarkAsUnreadAction()) && proto.workflows.Action.MarkAsUnreadAction.toObject(includeInstance, f)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action}
+ */
+proto.workflows.Action.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action;
+ return proto.workflows.Action.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action}
+ */
+proto.workflows.Action.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = new proto.workflows.Action.ReplyAction;
+ reader.readMessage(value,proto.workflows.Action.ReplyAction.deserializeBinaryFromReader);
+ msg.setReplyAction(value);
+ break;
+ case 2:
+ var value = new proto.workflows.Action.MoveAction;
+ reader.readMessage(value,proto.workflows.Action.MoveAction.deserializeBinaryFromReader);
+ msg.setMoveAction(value);
+ break;
+ case 3:
+ var value = new proto.workflows.Action.MarkDuplicateAction;
+ reader.readMessage(value,proto.workflows.Action.MarkDuplicateAction.deserializeBinaryFromReader);
+ msg.setMarkDuplicateAction(value);
+ break;
+ case 4:
+ var value = new proto.workflows.Action.UnmarkDuplicateAction;
+ reader.readMessage(value,proto.workflows.Action.UnmarkDuplicateAction.deserializeBinaryFromReader);
+ msg.setUnmarkDuplicateAction(value);
+ break;
+ case 5:
+ var value = new proto.workflows.Action.AttributeAction;
+ reader.readMessage(value,proto.workflows.Action.AttributeAction.deserializeBinaryFromReader);
+ msg.setAttributeAction(value);
+ break;
+ case 6:
+ var value = new proto.workflows.Action.ReplyWithCRAction;
+ reader.readMessage(value,proto.workflows.Action.ReplyWithCRAction.deserializeBinaryFromReader);
+ msg.setReplyWithCrAction(value);
+ break;
+ case 16:
+ var value = new proto.workflows.Action.StarAction;
+ reader.readMessage(value,proto.workflows.Action.StarAction.deserializeBinaryFromReader);
+ msg.setStarAction(value);
+ break;
+ case 17:
+ var value = new proto.workflows.Action.SubscribeAction;
+ reader.readMessage(value,proto.workflows.Action.SubscribeAction.deserializeBinaryFromReader);
+ msg.setSubscribeAction(value);
+ break;
+ case 18:
+ var value = new proto.workflows.Action.VoteAction;
+ reader.readMessage(value,proto.workflows.Action.VoteAction.deserializeBinaryFromReader);
+ msg.setVoteAction(value);
+ break;
+ case 19:
+ var value = new proto.workflows.Action.ReportAction;
+ reader.readMessage(value,proto.workflows.Action.ReportAction.deserializeBinaryFromReader);
+ msg.setReportAction(value);
+ break;
+ case 20:
+ var value = new proto.workflows.Action.MarkAsReadAction;
+ reader.readMessage(value,proto.workflows.Action.MarkAsReadAction.deserializeBinaryFromReader);
+ msg.setMarkAsReadAction(value);
+ break;
+ case 21:
+ var value = new proto.workflows.Action.MarkAsUnreadAction;
+ reader.readMessage(value,proto.workflows.Action.MarkAsUnreadAction.deserializeBinaryFromReader);
+ msg.setMarkAsUnreadAction(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getReplyAction();
+ if (f != null) {
+ writer.writeMessage(
+ 1,
+ f,
+ proto.workflows.Action.ReplyAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getMoveAction();
+ if (f != null) {
+ writer.writeMessage(
+ 2,
+ f,
+ proto.workflows.Action.MoveAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getMarkDuplicateAction();
+ if (f != null) {
+ writer.writeMessage(
+ 3,
+ f,
+ proto.workflows.Action.MarkDuplicateAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getUnmarkDuplicateAction();
+ if (f != null) {
+ writer.writeMessage(
+ 4,
+ f,
+ proto.workflows.Action.UnmarkDuplicateAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getAttributeAction();
+ if (f != null) {
+ writer.writeMessage(
+ 5,
+ f,
+ proto.workflows.Action.AttributeAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getReplyWithCrAction();
+ if (f != null) {
+ writer.writeMessage(
+ 6,
+ f,
+ proto.workflows.Action.ReplyWithCRAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getStarAction();
+ if (f != null) {
+ writer.writeMessage(
+ 16,
+ f,
+ proto.workflows.Action.StarAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getSubscribeAction();
+ if (f != null) {
+ writer.writeMessage(
+ 17,
+ f,
+ proto.workflows.Action.SubscribeAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getVoteAction();
+ if (f != null) {
+ writer.writeMessage(
+ 18,
+ f,
+ proto.workflows.Action.VoteAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getReportAction();
+ if (f != null) {
+ writer.writeMessage(
+ 19,
+ f,
+ proto.workflows.Action.ReportAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getMarkAsReadAction();
+ if (f != null) {
+ writer.writeMessage(
+ 20,
+ f,
+ proto.workflows.Action.MarkAsReadAction.serializeBinaryToWriter
+ );
+ }
+ f = message.getMarkAsUnreadAction();
+ if (f != null) {
+ writer.writeMessage(
+ 21,
+ f,
+ proto.workflows.Action.MarkAsUnreadAction.serializeBinaryToWriter
+ );
+ }
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.ReplyAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.ReplyAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.ReplyAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReplyAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ payload: jspb.Message.getFieldWithDefault(msg, 1, ""),
+ subscribe: jspb.Message.getBooleanFieldWithDefault(msg, 2, false),
+ markAsAnswer: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.ReplyAction}
+ */
+proto.workflows.Action.ReplyAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.ReplyAction;
+ return proto.workflows.Action.ReplyAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.ReplyAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.ReplyAction}
+ */
+proto.workflows.Action.ReplyAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {string} */ (reader.readString());
+ msg.setPayload(value);
+ break;
+ case 2:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setSubscribe(value);
+ break;
+ case 3:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setMarkAsAnswer(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.ReplyAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.ReplyAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.ReplyAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReplyAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getPayload();
+ if (f.length > 0) {
+ writer.writeString(
+ 1,
+ f
+ );
+ }
+ f = message.getSubscribe();
+ if (f) {
+ writer.writeBool(
+ 2,
+ f
+ );
+ }
+ f = message.getMarkAsAnswer();
+ if (f) {
+ writer.writeBool(
+ 3,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional string payload = 1;
+ * @return {string}
+ */
+proto.workflows.Action.ReplyAction.prototype.getPayload = function() {
+ return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.workflows.Action.ReplyAction} returns this
+ */
+proto.workflows.Action.ReplyAction.prototype.setPayload = function(value) {
+ return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
+/**
+ * optional bool subscribe = 2;
+ * @return {boolean}
+ */
+proto.workflows.Action.ReplyAction.prototype.getSubscribe = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 2, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.ReplyAction} returns this
+ */
+proto.workflows.Action.ReplyAction.prototype.setSubscribe = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 2, value);
+};
+
+
+/**
+ * optional bool mark_as_answer = 3;
+ * @return {boolean}
+ */
+proto.workflows.Action.ReplyAction.prototype.getMarkAsAnswer = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.ReplyAction} returns this
+ */
+proto.workflows.Action.ReplyAction.prototype.setMarkAsAnswer = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 3, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.MoveAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.MoveAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.MoveAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MoveAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ forumId: jspb.Message.getFieldWithDefault(msg, 1, 0),
+ category: jspb.Message.getFieldWithDefault(msg, 2, ""),
+ language: jspb.Message.getFieldWithDefault(msg, 3, ""),
+ propertyMap: (f = msg.getPropertyMap()) ? f.toObject(includeInstance, undefined) : []
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.MoveAction}
+ */
+proto.workflows.Action.MoveAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.MoveAction;
+ return proto.workflows.Action.MoveAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.MoveAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.MoveAction}
+ */
+proto.workflows.Action.MoveAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setForumId(value);
+ break;
+ case 2:
+ var value = /** @type {string} */ (reader.readString());
+ msg.setCategory(value);
+ break;
+ case 3:
+ var value = /** @type {string} */ (reader.readString());
+ msg.setLanguage(value);
+ break;
+ case 4:
+ var value = msg.getPropertyMap();
+ reader.readMessage(value, function(message, reader) {
+ jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString, null, "", "");
+ });
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.MoveAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.MoveAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.MoveAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MoveAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getForumId();
+ if (f !== 0) {
+ writer.writeInt64(
+ 1,
+ f
+ );
+ }
+ f = message.getCategory();
+ if (f.length > 0) {
+ writer.writeString(
+ 2,
+ f
+ );
+ }
+ f = message.getLanguage();
+ if (f.length > 0) {
+ writer.writeString(
+ 3,
+ f
+ );
+ }
+ f = message.getPropertyMap(true);
+ if (f && f.getLength() > 0) {
+ f.serializeBinary(4, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString);
+ }
+};
+
+
+/**
+ * optional int64 forum_id = 1;
+ * @return {number}
+ */
+proto.workflows.Action.MoveAction.prototype.getForumId = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.workflows.Action.MoveAction} returns this
+ */
+proto.workflows.Action.MoveAction.prototype.setForumId = function(value) {
+ return jspb.Message.setProto3IntField(this, 1, value);
+};
+
+
+/**
+ * optional string category = 2;
+ * @return {string}
+ */
+proto.workflows.Action.MoveAction.prototype.getCategory = function() {
+ return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.workflows.Action.MoveAction} returns this
+ */
+proto.workflows.Action.MoveAction.prototype.setCategory = function(value) {
+ return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
+/**
+ * optional string language = 3;
+ * @return {string}
+ */
+proto.workflows.Action.MoveAction.prototype.getLanguage = function() {
+ return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.workflows.Action.MoveAction} returns this
+ */
+proto.workflows.Action.MoveAction.prototype.setLanguage = function(value) {
+ return jspb.Message.setProto3StringField(this, 3, value);
+};
+
+
+/**
+ * map<string, string> property = 4;
+ * @param {boolean=} opt_noLazyCreate Do not create the map if
+ * empty, instead returning `undefined`
+ * @return {!jspb.Map<string,string>}
+ */
+proto.workflows.Action.MoveAction.prototype.getPropertyMap = function(opt_noLazyCreate) {
+ return /** @type {!jspb.Map<string,string>} */ (
+ jspb.Message.getMapField(this, 4, opt_noLazyCreate,
+ null));
+};
+
+
+/**
+ * Clears values from the map. The map will be non-null.
+ * @return {!proto.workflows.Action.MoveAction} returns this
+ */
+proto.workflows.Action.MoveAction.prototype.clearPropertyMap = function() {
+ this.getPropertyMap().clear();
+ return this;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.MarkDuplicateAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.MarkDuplicateAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.MarkDuplicateAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkDuplicateAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ destination: (f = msg.getDestination()) && proto.workflows.Thread.toObject(includeInstance, f)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.MarkDuplicateAction}
+ */
+proto.workflows.Action.MarkDuplicateAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.MarkDuplicateAction;
+ return proto.workflows.Action.MarkDuplicateAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.MarkDuplicateAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.MarkDuplicateAction}
+ */
+proto.workflows.Action.MarkDuplicateAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = new proto.workflows.Thread;
+ reader.readMessage(value,proto.workflows.Thread.deserializeBinaryFromReader);
+ msg.setDestination(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.MarkDuplicateAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.MarkDuplicateAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.MarkDuplicateAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkDuplicateAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getDestination();
+ if (f != null) {
+ writer.writeMessage(
+ 1,
+ f,
+ proto.workflows.Thread.serializeBinaryToWriter
+ );
+ }
+};
+
+
+/**
+ * optional Thread destination = 1;
+ * @return {?proto.workflows.Thread}
+ */
+proto.workflows.Action.MarkDuplicateAction.prototype.getDestination = function() {
+ return /** @type{?proto.workflows.Thread} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Thread, 1));
+};
+
+
+/**
+ * @param {?proto.workflows.Thread|undefined} value
+ * @return {!proto.workflows.Action.MarkDuplicateAction} returns this
+*/
+proto.workflows.Action.MarkDuplicateAction.prototype.setDestination = function(value) {
+ return jspb.Message.setWrapperField(this, 1, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action.MarkDuplicateAction} returns this
+ */
+proto.workflows.Action.MarkDuplicateAction.prototype.clearDestination = function() {
+ return this.setDestination(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.MarkDuplicateAction.prototype.hasDestination = function() {
+ return jspb.Message.getField(this, 1) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.UnmarkDuplicateAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.UnmarkDuplicateAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.UnmarkDuplicateAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.UnmarkDuplicateAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.UnmarkDuplicateAction}
+ */
+proto.workflows.Action.UnmarkDuplicateAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.UnmarkDuplicateAction;
+ return proto.workflows.Action.UnmarkDuplicateAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.UnmarkDuplicateAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.UnmarkDuplicateAction}
+ */
+proto.workflows.Action.UnmarkDuplicateAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.UnmarkDuplicateAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.UnmarkDuplicateAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.UnmarkDuplicateAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.UnmarkDuplicateAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.ReplyWithCRAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.ReplyWithCRAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReplyWithCRAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ cannedResponseId: jspb.Message.getFieldWithDefault(msg, 1, 0),
+ subscribe: jspb.Message.getBooleanFieldWithDefault(msg, 2, false),
+ markAsAnswer: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.ReplyWithCRAction}
+ */
+proto.workflows.Action.ReplyWithCRAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.ReplyWithCRAction;
+ return proto.workflows.Action.ReplyWithCRAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.ReplyWithCRAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.ReplyWithCRAction}
+ */
+proto.workflows.Action.ReplyWithCRAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setCannedResponseId(value);
+ break;
+ case 2:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setSubscribe(value);
+ break;
+ case 3:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setMarkAsAnswer(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.ReplyWithCRAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.ReplyWithCRAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReplyWithCRAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getCannedResponseId();
+ if (f !== 0) {
+ writer.writeInt64(
+ 1,
+ f
+ );
+ }
+ f = message.getSubscribe();
+ if (f) {
+ writer.writeBool(
+ 2,
+ f
+ );
+ }
+ f = message.getMarkAsAnswer();
+ if (f) {
+ writer.writeBool(
+ 3,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional int64 canned_response_id = 1;
+ * @return {number}
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.getCannedResponseId = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.workflows.Action.ReplyWithCRAction} returns this
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.setCannedResponseId = function(value) {
+ return jspb.Message.setProto3IntField(this, 1, value);
+};
+
+
+/**
+ * optional bool subscribe = 2;
+ * @return {boolean}
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.getSubscribe = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 2, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.ReplyWithCRAction} returns this
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.setSubscribe = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 2, value);
+};
+
+
+/**
+ * optional bool mark_as_answer = 3;
+ * @return {boolean}
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.getMarkAsAnswer = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.ReplyWithCRAction} returns this
+ */
+proto.workflows.Action.ReplyWithCRAction.prototype.setMarkAsAnswer = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 3, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.StarAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.StarAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.StarAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.StarAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ star: jspb.Message.getBooleanFieldWithDefault(msg, 1, false)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.StarAction}
+ */
+proto.workflows.Action.StarAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.StarAction;
+ return proto.workflows.Action.StarAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.StarAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.StarAction}
+ */
+proto.workflows.Action.StarAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setStar(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.StarAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.StarAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.StarAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.StarAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getStar();
+ if (f) {
+ writer.writeBool(
+ 1,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional bool star = 1;
+ * @return {boolean}
+ */
+proto.workflows.Action.StarAction.prototype.getStar = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.StarAction} returns this
+ */
+proto.workflows.Action.StarAction.prototype.setStar = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 1, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.SubscribeAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.SubscribeAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.SubscribeAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.SubscribeAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ subscribe: jspb.Message.getBooleanFieldWithDefault(msg, 1, false)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.SubscribeAction}
+ */
+proto.workflows.Action.SubscribeAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.SubscribeAction;
+ return proto.workflows.Action.SubscribeAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.SubscribeAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.SubscribeAction}
+ */
+proto.workflows.Action.SubscribeAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {boolean} */ (reader.readBool());
+ msg.setSubscribe(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.SubscribeAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.SubscribeAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.SubscribeAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.SubscribeAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getSubscribe();
+ if (f) {
+ writer.writeBool(
+ 1,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional bool subscribe = 1;
+ * @return {boolean}
+ */
+proto.workflows.Action.SubscribeAction.prototype.getSubscribe = function() {
+ return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.workflows.Action.SubscribeAction} returns this
+ */
+proto.workflows.Action.SubscribeAction.prototype.setSubscribe = function(value) {
+ return jspb.Message.setProto3BooleanField(this, 1, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.VoteAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.VoteAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.VoteAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.VoteAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ vote: jspb.Message.getFieldWithDefault(msg, 1, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.VoteAction}
+ */
+proto.workflows.Action.VoteAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.VoteAction;
+ return proto.workflows.Action.VoteAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.VoteAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.VoteAction}
+ */
+proto.workflows.Action.VoteAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {!proto.workflows.Action.VoteAction.Vote} */ (reader.readEnum());
+ msg.setVote(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.VoteAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.VoteAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.VoteAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.VoteAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getVote();
+ if (f !== 0.0) {
+ writer.writeEnum(
+ 1,
+ f
+ );
+ }
+};
+
+
+/**
+ * @enum {number}
+ */
+proto.workflows.Action.VoteAction.Vote = {
+ NONE: 0,
+ UP: 1,
+ DOWN: -1
+};
+
+/**
+ * optional Vote vote = 1;
+ * @return {!proto.workflows.Action.VoteAction.Vote}
+ */
+proto.workflows.Action.VoteAction.prototype.getVote = function() {
+ return /** @type {!proto.workflows.Action.VoteAction.Vote} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {!proto.workflows.Action.VoteAction.Vote} value
+ * @return {!proto.workflows.Action.VoteAction} returns this
+ */
+proto.workflows.Action.VoteAction.prototype.setVote = function(value) {
+ return jspb.Message.setProto3EnumField(this, 1, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.AttributeAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.AttributeAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.AttributeAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.AttributeAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ attributeAction: jspb.Message.getFieldWithDefault(msg, 1, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.AttributeAction}
+ */
+proto.workflows.Action.AttributeAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.AttributeAction;
+ return proto.workflows.Action.AttributeAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.AttributeAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.AttributeAction}
+ */
+proto.workflows.Action.AttributeAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {!proto.workflows.Action.AttributeAction.AttributeAction} */ (reader.readEnum());
+ msg.setAttributeAction(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.AttributeAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.AttributeAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.AttributeAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.AttributeAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getAttributeAction();
+ if (f !== 0.0) {
+ writer.writeEnum(
+ 1,
+ f
+ );
+ }
+};
+
+
+/**
+ * @enum {number}
+ */
+proto.workflows.Action.AttributeAction.AttributeAction = {
+ AA_NONE: 0,
+ AA_LOCK: 1,
+ AA_UNLOCK: 2,
+ AA_PIN: 3,
+ AA_UNPIN: 4,
+ AA_NON_ISSUE: 5,
+ AA_OBSOLETE: 6,
+ AA_REVERT: 7,
+ AA_SET_TRENDING: 8,
+ AA_UNSET_TRENDING: 9,
+ AA_SET_ISSUE_RESOLVED: 10,
+ AA_UNSET_ISSUE_RESOLVED: 11,
+ AA_SOFT_LOCK: 12,
+ AA_UNSOFT_LOCK: 13,
+ AA_EXCLUDE_FROM_GOLDEN: 14,
+ AA_UNEXCLUDE_FROM_GOLDEN: 15,
+ AA_INCLUDE_IN_GOLDEN: 16
+};
+
+/**
+ * optional AttributeAction attribute_action = 1;
+ * @return {!proto.workflows.Action.AttributeAction.AttributeAction}
+ */
+proto.workflows.Action.AttributeAction.prototype.getAttributeAction = function() {
+ return /** @type {!proto.workflows.Action.AttributeAction.AttributeAction} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {!proto.workflows.Action.AttributeAction.AttributeAction} value
+ * @return {!proto.workflows.Action.AttributeAction} returns this
+ */
+proto.workflows.Action.AttributeAction.prototype.setAttributeAction = function(value) {
+ return jspb.Message.setProto3EnumField(this, 1, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.ReportAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.ReportAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.ReportAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReportAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ reportType: jspb.Message.getFieldWithDefault(msg, 1, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.ReportAction}
+ */
+proto.workflows.Action.ReportAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.ReportAction;
+ return proto.workflows.Action.ReportAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.ReportAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.ReportAction}
+ */
+proto.workflows.Action.ReportAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {!proto.workflows.Action.ReportAction.ReportType} */ (reader.readEnum());
+ msg.setReportType(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.ReportAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.ReportAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.ReportAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.ReportAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getReportType();
+ if (f !== 0.0) {
+ writer.writeEnum(
+ 1,
+ f
+ );
+ }
+};
+
+
+/**
+ * @enum {number}
+ */
+proto.workflows.Action.ReportAction.ReportType = {
+ RT_UNKNOWN: 0,
+ RT_OFF_TOPIC: 1,
+ RT_ABUSE: 2
+};
+
+/**
+ * optional ReportType report_type = 1;
+ * @return {!proto.workflows.Action.ReportAction.ReportType}
+ */
+proto.workflows.Action.ReportAction.prototype.getReportType = function() {
+ return /** @type {!proto.workflows.Action.ReportAction.ReportType} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {!proto.workflows.Action.ReportAction.ReportType} value
+ * @return {!proto.workflows.Action.ReportAction} returns this
+ */
+proto.workflows.Action.ReportAction.prototype.setReportType = function(value) {
+ return jspb.Message.setProto3EnumField(this, 1, value);
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.MarkAsReadAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.MarkAsReadAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.MarkAsReadAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkAsReadAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.MarkAsReadAction}
+ */
+proto.workflows.Action.MarkAsReadAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.MarkAsReadAction;
+ return proto.workflows.Action.MarkAsReadAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.MarkAsReadAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.MarkAsReadAction}
+ */
+proto.workflows.Action.MarkAsReadAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.MarkAsReadAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.MarkAsReadAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.MarkAsReadAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkAsReadAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Action.MarkAsUnreadAction.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Action.MarkAsUnreadAction.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Action.MarkAsUnreadAction} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkAsUnreadAction.toObject = function(includeInstance, msg) {
+ var f, obj = {
+
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Action.MarkAsUnreadAction}
+ */
+proto.workflows.Action.MarkAsUnreadAction.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Action.MarkAsUnreadAction;
+ return proto.workflows.Action.MarkAsUnreadAction.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Action.MarkAsUnreadAction} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Action.MarkAsUnreadAction}
+ */
+proto.workflows.Action.MarkAsUnreadAction.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Action.MarkAsUnreadAction.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Action.MarkAsUnreadAction.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Action.MarkAsUnreadAction} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Action.MarkAsUnreadAction.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+};
+
+
+/**
+ * optional ReplyAction reply_action = 1;
+ * @return {?proto.workflows.Action.ReplyAction}
+ */
+proto.workflows.Action.prototype.getReplyAction = function() {
+ return /** @type{?proto.workflows.Action.ReplyAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.ReplyAction, 1));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.ReplyAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setReplyAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 1, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearReplyAction = function() {
+ return this.setReplyAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasReplyAction = function() {
+ return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional MoveAction move_action = 2;
+ * @return {?proto.workflows.Action.MoveAction}
+ */
+proto.workflows.Action.prototype.getMoveAction = function() {
+ return /** @type{?proto.workflows.Action.MoveAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.MoveAction, 2));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.MoveAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setMoveAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 2, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearMoveAction = function() {
+ return this.setMoveAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasMoveAction = function() {
+ return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional MarkDuplicateAction mark_duplicate_action = 3;
+ * @return {?proto.workflows.Action.MarkDuplicateAction}
+ */
+proto.workflows.Action.prototype.getMarkDuplicateAction = function() {
+ return /** @type{?proto.workflows.Action.MarkDuplicateAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.MarkDuplicateAction, 3));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.MarkDuplicateAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setMarkDuplicateAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 3, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearMarkDuplicateAction = function() {
+ return this.setMarkDuplicateAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasMarkDuplicateAction = function() {
+ return jspb.Message.getField(this, 3) != null;
+};
+
+
+/**
+ * optional UnmarkDuplicateAction unmark_duplicate_action = 4;
+ * @return {?proto.workflows.Action.UnmarkDuplicateAction}
+ */
+proto.workflows.Action.prototype.getUnmarkDuplicateAction = function() {
+ return /** @type{?proto.workflows.Action.UnmarkDuplicateAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.UnmarkDuplicateAction, 4));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.UnmarkDuplicateAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setUnmarkDuplicateAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 4, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearUnmarkDuplicateAction = function() {
+ return this.setUnmarkDuplicateAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasUnmarkDuplicateAction = function() {
+ return jspb.Message.getField(this, 4) != null;
+};
+
+
+/**
+ * optional AttributeAction attribute_action = 5;
+ * @return {?proto.workflows.Action.AttributeAction}
+ */
+proto.workflows.Action.prototype.getAttributeAction = function() {
+ return /** @type{?proto.workflows.Action.AttributeAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.AttributeAction, 5));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.AttributeAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setAttributeAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 5, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearAttributeAction = function() {
+ return this.setAttributeAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasAttributeAction = function() {
+ return jspb.Message.getField(this, 5) != null;
+};
+
+
+/**
+ * optional ReplyWithCRAction reply_with_cr_action = 6;
+ * @return {?proto.workflows.Action.ReplyWithCRAction}
+ */
+proto.workflows.Action.prototype.getReplyWithCrAction = function() {
+ return /** @type{?proto.workflows.Action.ReplyWithCRAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.ReplyWithCRAction, 6));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.ReplyWithCRAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setReplyWithCrAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 6, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearReplyWithCrAction = function() {
+ return this.setReplyWithCrAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasReplyWithCrAction = function() {
+ return jspb.Message.getField(this, 6) != null;
+};
+
+
+/**
+ * optional StarAction star_action = 16;
+ * @return {?proto.workflows.Action.StarAction}
+ */
+proto.workflows.Action.prototype.getStarAction = function() {
+ return /** @type{?proto.workflows.Action.StarAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.StarAction, 16));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.StarAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setStarAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 16, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearStarAction = function() {
+ return this.setStarAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasStarAction = function() {
+ return jspb.Message.getField(this, 16) != null;
+};
+
+
+/**
+ * optional SubscribeAction subscribe_action = 17;
+ * @return {?proto.workflows.Action.SubscribeAction}
+ */
+proto.workflows.Action.prototype.getSubscribeAction = function() {
+ return /** @type{?proto.workflows.Action.SubscribeAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.SubscribeAction, 17));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.SubscribeAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setSubscribeAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 17, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearSubscribeAction = function() {
+ return this.setSubscribeAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasSubscribeAction = function() {
+ return jspb.Message.getField(this, 17) != null;
+};
+
+
+/**
+ * optional VoteAction vote_action = 18;
+ * @return {?proto.workflows.Action.VoteAction}
+ */
+proto.workflows.Action.prototype.getVoteAction = function() {
+ return /** @type{?proto.workflows.Action.VoteAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.VoteAction, 18));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.VoteAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setVoteAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 18, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearVoteAction = function() {
+ return this.setVoteAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasVoteAction = function() {
+ return jspb.Message.getField(this, 18) != null;
+};
+
+
+/**
+ * optional ReportAction report_action = 19;
+ * @return {?proto.workflows.Action.ReportAction}
+ */
+proto.workflows.Action.prototype.getReportAction = function() {
+ return /** @type{?proto.workflows.Action.ReportAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.ReportAction, 19));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.ReportAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setReportAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 19, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearReportAction = function() {
+ return this.setReportAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasReportAction = function() {
+ return jspb.Message.getField(this, 19) != null;
+};
+
+
+/**
+ * optional MarkAsReadAction mark_as_read_action = 20;
+ * @return {?proto.workflows.Action.MarkAsReadAction}
+ */
+proto.workflows.Action.prototype.getMarkAsReadAction = function() {
+ return /** @type{?proto.workflows.Action.MarkAsReadAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.MarkAsReadAction, 20));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.MarkAsReadAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setMarkAsReadAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 20, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearMarkAsReadAction = function() {
+ return this.setMarkAsReadAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasMarkAsReadAction = function() {
+ return jspb.Message.getField(this, 20) != null;
+};
+
+
+/**
+ * optional MarkAsUnreadAction mark_as_unread_action = 21;
+ * @return {?proto.workflows.Action.MarkAsUnreadAction}
+ */
+proto.workflows.Action.prototype.getMarkAsUnreadAction = function() {
+ return /** @type{?proto.workflows.Action.MarkAsUnreadAction} */ (
+ jspb.Message.getWrapperField(this, proto.workflows.Action.MarkAsUnreadAction, 21));
+};
+
+
+/**
+ * @param {?proto.workflows.Action.MarkAsUnreadAction|undefined} value
+ * @return {!proto.workflows.Action} returns this
+*/
+proto.workflows.Action.prototype.setMarkAsUnreadAction = function(value) {
+ return jspb.Message.setOneofWrapperField(this, 21, proto.workflows.Action.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.workflows.Action} returns this
+ */
+proto.workflows.Action.prototype.clearMarkAsUnreadAction = function() {
+ return this.setMarkAsUnreadAction(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.workflows.Action.prototype.hasMarkAsUnreadAction = function() {
+ return jspb.Message.getField(this, 21) != null;
+};
+
+
+
+/**
+ * List of repeated fields within this message type.
+ * @private {!Array<number>}
+ * @const
+ */
+proto.workflows.Workflow.repeatedFields_ = [4];
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.workflows.Workflow.prototype.toObject = function(opt_includeInstance) {
+ return proto.workflows.Workflow.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.workflows.Workflow} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Workflow.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ name: jspb.Message.getFieldWithDefault(msg, 1, ""),
+ description: jspb.Message.getFieldWithDefault(msg, 2, ""),
+ index: jspb.Message.getFieldWithDefault(msg, 3, 0),
+ actionsList: jspb.Message.toObjectList(msg.getActionsList(),
+ proto.workflows.Action.toObject, includeInstance)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.workflows.Workflow}
+ */
+proto.workflows.Workflow.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.workflows.Workflow;
+ return proto.workflows.Workflow.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.workflows.Workflow} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.workflows.Workflow}
+ */
+proto.workflows.Workflow.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {string} */ (reader.readString());
+ msg.setName(value);
+ break;
+ case 2:
+ var value = /** @type {string} */ (reader.readString());
+ msg.setDescription(value);
+ break;
+ case 3:
+ var value = /** @type {number} */ (reader.readInt32());
+ msg.setIndex(value);
+ break;
+ case 4:
+ var value = new proto.workflows.Action;
+ reader.readMessage(value,proto.workflows.Action.deserializeBinaryFromReader);
+ msg.addActions(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.workflows.Workflow.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.workflows.Workflow.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.workflows.Workflow} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.workflows.Workflow.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getName();
+ if (f.length > 0) {
+ writer.writeString(
+ 1,
+ f
+ );
+ }
+ f = message.getDescription();
+ if (f.length > 0) {
+ writer.writeString(
+ 2,
+ f
+ );
+ }
+ f = message.getIndex();
+ if (f !== 0) {
+ writer.writeInt32(
+ 3,
+ f
+ );
+ }
+ f = message.getActionsList();
+ if (f.length > 0) {
+ writer.writeRepeatedMessage(
+ 4,
+ f,
+ proto.workflows.Action.serializeBinaryToWriter
+ );
+ }
+};
+
+
+/**
+ * optional string name = 1;
+ * @return {string}
+ */
+proto.workflows.Workflow.prototype.getName = function() {
+ return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.workflows.Workflow} returns this
+ */
+proto.workflows.Workflow.prototype.setName = function(value) {
+ return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.workflows.Workflow.prototype.getDescription = function() {
+ return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.workflows.Workflow} returns this
+ */
+proto.workflows.Workflow.prototype.setDescription = function(value) {
+ return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
+/**
+ * optional int32 index = 3;
+ * @return {number}
+ */
+proto.workflows.Workflow.prototype.getIndex = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.workflows.Workflow} returns this
+ */
+proto.workflows.Workflow.prototype.setIndex = function(value) {
+ return jspb.Message.setProto3IntField(this, 3, value);
+};
+
+
+/**
+ * repeated Action actions = 4;
+ * @return {!Array<!proto.workflows.Action>}
+ */
+proto.workflows.Workflow.prototype.getActionsList = function() {
+ return /** @type{!Array<!proto.workflows.Action>} */ (
+ jspb.Message.getRepeatedWrapperField(this, proto.workflows.Action, 4));
+};
+
+
+/**
+ * @param {!Array<!proto.workflows.Action>} value
+ * @return {!proto.workflows.Workflow} returns this
+*/
+proto.workflows.Workflow.prototype.setActionsList = function(value) {
+ return jspb.Message.setRepeatedWrapperField(this, 4, value);
+};
+
+
+/**
+ * @param {!proto.workflows.Action=} opt_value
+ * @param {number=} opt_index
+ * @return {!proto.workflows.Action}
+ */
+proto.workflows.Workflow.prototype.addActions = function(opt_value, opt_index) {
+ return jspb.Message.addToRepeatedWrapperField(this, 4, opt_value, proto.workflows.Action, opt_index);
+};
+
+
+/**
+ * Clears the list making it empty but non-null.
+ * @return {!proto.workflows.Workflow} returns this
+ */
+proto.workflows.Workflow.prototype.clearActionsList = function() {
+ return this.setActionsList([]);
+};
+
+
+goog.object.extend(exports, proto);
diff --git a/src/features/workflows/core/workflowsStorage.js b/src/features/workflows/core/workflowsStorage.js
new file mode 100644
index 0000000..3fc6f95
--- /dev/null
+++ b/src/features/workflows/core/workflowsStorage.js
@@ -0,0 +1,104 @@
+import {arrayBufferToBase64} from './common.js';
+import * as pb from './proto/main_pb.js';
+
+export const kWorkflowsDataKey = 'workflowsData';
+
+export default class WorkflowsStorage {
+ static watch(callback, asProtobuf = false) {
+ // Function which will be called when the watcher is initialized and every
+ // time the workflows storage changes.
+ const callOnChanged = () => {
+ this.getAll(asProtobuf).then(workflows => callback(workflows));
+ };
+
+ chrome.storage.onChanged.addListener((changes, areaName) => {
+ if (areaName == 'local' && changes[kWorkflowsDataKey]) callOnChanged();
+ });
+
+ callOnChanged();
+ }
+
+ static convertRawListToProtobuf(workflows) {
+ workflows.forEach(w => {
+ w.proto = pb.workflows.Workflow.deserializeBinary(w?.data);
+ delete w.data;
+ });
+ }
+
+ static getAll(asProtobuf = false) {
+ return new Promise(res => {
+ chrome.storage.local.get(kWorkflowsDataKey, items => {
+ const workflows = items[kWorkflowsDataKey];
+ if (!Array.isArray(workflows)) return res([]);
+ if (!asProtobuf) return res(workflows);
+
+ this.convertRawListToProtobuf(workflows);
+ return res(workflows);
+ });
+ });
+ }
+
+ static get(uuid, asProtobuf = false) {
+ return this.getAll(asProtobuf).then(workflows => {
+ for (const w of workflows) {
+ if (w.uuid == uuid) return w;
+ }
+ return null;
+ });
+ }
+
+ static exists(uuid) {
+ return this.get(uuid).then(w => w !== null);
+ }
+
+ static addRaw(base64Workflow) {
+ const w = {
+ uuid: self.crypto.randomUUID(),
+ data: base64Workflow,
+ };
+ return this.getAll().then(workflows => {
+ workflows.push(w);
+ const items = {};
+ items[kWorkflowsDataKey] = workflows;
+ chrome.storage.local.set(items);
+ });
+ }
+
+ static add(workflow) {
+ return this._proto2Base64(workflow).then(data => {
+ return this.addRaw(data);
+ });
+ }
+
+ static updateRaw(uuid, base64Workflow) {
+ return this.getAll().then(workflows => {
+ workflows.map(w => {
+ if (w.uuid !== uuid) return w;
+ w.data = base64Workflow;
+ return w;
+ });
+ const items = {};
+ items[kWorkflowsDataKey] = workflows;
+ chrome.storage.local.set(items);
+ });
+ }
+
+ static update(uuid, workflow) {
+ return this._proto2Base64(workflow).then(data => {
+ return this.updateRaw(uuid, data);
+ });
+ }
+
+ static remove(uuid) {
+ return this.getAll().then(workflows => {
+ const items = {};
+ items[kWorkflowsDataKey] = workflows.filter(w => w.uuid != uuid);
+ chrome.storage.local.set(items);
+ });
+ }
+
+ static _proto2Base64(workflow) {
+ const binaryWorkflow = workflow.serializeBinary();
+ return arrayBufferToBase64(binaryWorkflow);
+ }
+}
diff --git a/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts b/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts
new file mode 100644
index 0000000..27c10d9
--- /dev/null
+++ b/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts
@@ -0,0 +1,14 @@
+import CssSelectorNodeWatcherScriptHandler from '../../../common/architecture/scripts/nodeWatcher/handlers/CssSelectorNodeWatcherScriptHandler';
+import { NodeMutation } from '../../../common/nodeWatcher/NodeWatcherHandler';
+import { WorkflowsNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
+
+/**
+ * Injects the button to import a canned response next to each CR.
+ */
+export default class WorkflowsImportCRTagsHandler extends CssSelectorNodeWatcherScriptHandler<WorkflowsNodeWatcherDependencies> {
+ cssSelector = 'ec-canned-response-row .tags';
+
+ onMutatedNode(mutation: NodeMutation) {
+ this.options.workflowsImport.addButtonIfEnabled(mutation.node);
+ }
+}
diff --git a/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts b/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts
new file mode 100644
index 0000000..ff62487
--- /dev/null
+++ b/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts
@@ -0,0 +1,20 @@
+import { NodeWatcherScriptHandler } from '../../../common/architecture/scripts/nodeWatcher/handlers/NodeWatcherScriptHandler';
+import { NodeMutation } from '../../../common/nodeWatcher/NodeWatcherHandler';
+import { WorkflowsNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
+
+/**
+ * Injects the workflows menu in the thread list.
+ */
+export default class WorkflowsThreadListActionBarHandler extends NodeWatcherScriptHandler<WorkflowsNodeWatcherDependencies> {
+ initialDiscoverySelector =
+ ':is(ec-bulk-actions material-button[debugid="mark-read-button"],' +
+ 'ec-bulk-actions material-button[debugid="mark-unread-button"])';
+
+ nodeFilter(mutation: NodeMutation) {
+ return this.options.workflows.shouldAddThreadListBtn(mutation.node);
+ }
+
+ onMutatedNode(mutation: NodeMutation) {
+ this.options.workflows.addThreadListBtnIfEnabled(mutation.node);
+ }
+}
diff --git a/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts b/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts
new file mode 100644
index 0000000..37ec85f
--- /dev/null
+++ b/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts
@@ -0,0 +1,17 @@
+import {
+ Dependency,
+ WorkflowsDependency,
+} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
+import {
+ ScriptEnvironment,
+ ScriptPage,
+ ScriptRunPhase,
+} from '../../../common/architecture/scripts/Script';
+import SetUpDependenciesScript from '../../../common/architecture/scripts/setUpDependencies/SetUpDependenciesScript';
+
+export default class WorkflowsDependenciesSetUpAtMainScript extends SetUpDependenciesScript {
+ public page = ScriptPage.CommunityConsole;
+ public environment = ScriptEnvironment.ContentScript;
+ public runPhase = ScriptRunPhase.Main;
+ public dependencies: Dependency[] = [WorkflowsDependency];
+}
diff --git a/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts b/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts
new file mode 100644
index 0000000..0b52082
--- /dev/null
+++ b/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts
@@ -0,0 +1,18 @@
+import {
+ Dependency,
+ WorkflowsImportDependency,
+} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
+import {
+ ScriptEnvironment,
+ ScriptPage,
+ ScriptRunPhase,
+} from '../../../common/architecture/scripts/Script';
+import SetUpDependenciesScript from '../../../common/architecture/scripts/setUpDependencies/SetUpDependenciesScript';
+
+export default class WorkflowsDependenciesSetUpAtStartScript extends SetUpDependenciesScript {
+ public priority = 102;
+ public page = ScriptPage.CommunityConsole;
+ public environment = ScriptEnvironment.ContentScript;
+ public runPhase = ScriptRunPhase.Start;
+ public dependencies: Dependency[] = [WorkflowsImportDependency];
+}
diff --git a/src/features/workflows/scripts/nodeWatcher.script.ts b/src/features/workflows/scripts/nodeWatcher.script.ts
new file mode 100644
index 0000000..8d26c86
--- /dev/null
+++ b/src/features/workflows/scripts/nodeWatcher.script.ts
@@ -0,0 +1,39 @@
+import DependenciesProviderSingleton, {
+ WorkflowsDependency,
+ WorkflowsImportDependency,
+} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
+import {
+ ScriptEnvironment,
+ ScriptPage,
+ ScriptRunPhase,
+} from '../../../common/architecture/scripts/Script';
+import NodeWatcherScript from '../../../common/architecture/scripts/nodeWatcher/NodeWatcherScript';
+import WorkflowsImport from '../core/communityConsole/import';
+import Workflows from '../core/communityConsole/workflows';
+import WorkflowsImportCRTagsHandler from '../nodeWatcherHandlers/crTags.handler';
+import WorkflowsThreadListActionBarHandler from '../nodeWatcherHandlers/threadListActionBar.handler';
+
+export interface WorkflowsNodeWatcherDependencies {
+ workflows: Workflows;
+ workflowsImport: WorkflowsImport;
+}
+
+export default class WorkflowsNodeWatcherScript extends NodeWatcherScript<WorkflowsNodeWatcherDependencies> {
+ public page = ScriptPage.CommunityConsole;
+ public environment = ScriptEnvironment.ContentScript;
+ public runPhase = ScriptRunPhase.Main;
+ public handlers = new Map([
+ ['workflowsImportCRTags', WorkflowsImportCRTagsHandler],
+ ['workflowsThreadListActionBar', WorkflowsThreadListActionBarHandler],
+ ]);
+
+ protected optionsFactory(): WorkflowsNodeWatcherDependencies {
+ const dependenciesProvider = DependenciesProviderSingleton.getInstance();
+ return {
+ workflows: dependenciesProvider.getDependency(WorkflowsDependency),
+ workflowsImport: dependenciesProvider.getDependency(
+ WorkflowsImportDependency,
+ ),
+ };
+ }
+}
diff --git a/src/features/workflows/workflows.feature.ts b/src/features/workflows/workflows.feature.ts
new file mode 100644
index 0000000..7a6976c
--- /dev/null
+++ b/src/features/workflows/workflows.feature.ts
@@ -0,0 +1,17 @@
+import Feature from '../../common/architecture/features/Feature';
+import { ConcreteScript } from '../../common/architecture/scripts/Script';
+import { OptionCodename } from '../../common/optionsPrototype';
+import WorkflowsDependenciesSetUpAtMainScript from './scripts/dependenciesSetUpAtMain.script';
+import WorkflowsDependenciesSetUpAtStartScript from './scripts/dependenciesSetUpAtStart.script';
+import WorkflowsNodeWatcherScript from './scripts/nodeWatcher.script';
+
+export default class WorkflowsFeature extends Feature {
+ public readonly scripts: ConcreteScript[] = [
+ WorkflowsDependenciesSetUpAtStartScript,
+ WorkflowsDependenciesSetUpAtMainScript,
+ WorkflowsNodeWatcherScript,
+ ];
+
+ readonly codename = 'workflows';
+ readonly relatedOptions: OptionCodename[] = ['workflows'];
+}