refactor(comments): migrate to Vue 3

Signed-off-by: Edward Ly <contact@edward.ly>
This commit is contained in:
Edward Ly 2026-02-15 18:12:10 -08:00 committed by Ferdinand Thiessen
parent 3aa22804e1
commit c56ebcecb2
6 changed files with 61 additions and 77 deletions

View file

@ -34,7 +34,8 @@ class LoadSidebarScripts implements IEventListener {
$this->commentsManager->load();
$this->initialState->provideInitialState('activityEnabled', $this->appManager->isEnabledForUser('activity'));
// Add comments sidebar tab script
// Add comments sidebar tab script/style
Util::addStyle(Application::APP_ID, 'comments-tab');
Util::addScript(Application::APP_ID, 'comments-tab', 'files');
}
}

View file

@ -4,45 +4,40 @@
*/
import type { INode } from '@nextcloud/files'
import type { ComponentPublicInstance } from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue, { type ComponentPublicInstance } from 'vue'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import logger from './logger.ts'
import { getComments } from './services/GetComments.ts'
Vue.use(PiniaVuePlugin)
let ActivityTabPluginView
let ActivityTabPluginInstance
/**
* Register the comments plugins for the Activity sidebar
*/
export function registerCommentsPlugins() {
let app
window.OCA.Activity.registerSidebarAction({
mount: async (el: HTMLElement, { node, reload }: { node: INode, reload: () => void }) => {
const pinia = createPinia()
if (!ActivityTabPluginView) {
if (!app) {
const { default: ActivityCommentAction } = await import('./views/ActivityCommentAction.vue')
// @ts-expect-error Types are broken for Vue2
ActivityTabPluginView = Vue.extend(ActivityCommentAction)
app = createApp(
ActivityCommentAction,
{
reloadCallback: reload,
resourceId: node.fileid,
},
)
}
ActivityTabPluginInstance = new ActivityTabPluginView({
el,
pinia,
propsData: {
reloadCallback: reload,
resourceId: node.fileid,
},
})
app.use(pinia)
app.mount(el)
logger.info('Comments plugin mounted in Activity sidebar action', { node })
},
unmount: () => {
// destroy previous instance if available
if (ActivityTabPluginInstance) {
ActivityTabPluginInstance.$destroy()
}
app?.unmount()
},
})
@ -56,7 +51,6 @@ export function registerCommentsPlugins() {
)
logger.debug('Loaded comments', { node, comments })
const { default: CommentView } = await import('./views/ActivityCommentEntry.vue')
const CommentsViewObject = Vue.extend(CommentView)
return comments.map((comment) => ({
_CommentsViewInstance: undefined as ComponentPublicInstance | undefined,
@ -64,17 +58,18 @@ export function registerCommentsPlugins() {
timestamp: Date.parse(comment.props?.creationDateTime as string | undefined ?? ''),
mount(element: HTMLElement, { reload }) {
this._CommentsViewInstance = new CommentsViewObject({
el: element,
propsData: {
this._CommentsViewInstance = createApp(
CommentView,
{
comment,
resourceId: node.fileid,
reloadCallback: reload,
},
})
)
this._CommentsViewInstance.mount(el)
},
unmount() {
this._CommentsViewInstance?.$destroy()
this._CommentsViewInstance?.unmount()
},
}))
})

View file

@ -75,7 +75,7 @@
:model-value="localMessage"
:user-data="userData"
aria-describedby="tab-comments__editor-description"
@update:value="updateLocalMessage"
@update:model-value="updateLocalMessage"
@submit="onSubmit" />
<div class="comment__submit">
<NcButton
@ -104,7 +104,7 @@
:text="richContent.message"
:arguments="richContent.mentions"
use-markdown
@click.native="onExpand" />
@click="onExpand" />
</div>
</component>
</template>

View file

@ -4,17 +4,13 @@
*/
import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw'
import { getCSPNonce } from '@nextcloud/auth'
import { registerSidebarTab } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import wrap from '@vue/web-component-wrapper'
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
import { createPinia } from 'pinia'
import { defineCustomElement } from 'vue'
import { registerCommentsPlugins } from './comments-activity-tab.ts'
import { isUsingActivityIntegration } from './utils/activity.ts'
__webpack_nonce__ = getCSPNonce()
const tagName = 'comments_files-sidebar-tab'
if (isUsingActivityIntegration()) {
@ -32,17 +28,15 @@ if (isUsingActivityIntegration()) {
async onInit() {
const { default: FilesSidebarTab } = await import('./views/FilesSidebarTab.vue')
Vue.use(PiniaVuePlugin)
Vue.mixin({ pinia: createPinia() })
const webComponent = wrap(Vue, FilesSidebarTab)
// In Vue 2, wrap doesn't support disabling shadow. Disable with a hack
Object.defineProperty(webComponent.prototype, 'attachShadow', {
value() { return this },
const FilesSidebarTabElement = defineCustomElement(FilesSidebarTab, {
configureApp(app) {
const pinia = createPinia()
app.use(pinia)
},
shadowRoot: false,
})
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
get() { return this },
})
window.customElements.define(tagName, webComponent)
window.customElements.define(tagName, FilesSidebarTabElement)
},
})
}

View file

@ -3,51 +3,45 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCSPNonce } from '@nextcloud/auth'
import { n, t } from '@nextcloud/l10n'
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import CommentsApp from '../views/Comments.vue'
import logger from '../logger.ts'
Vue.use(PiniaVuePlugin)
__webpack_nonce__ = getCSPNonce()
// Add translates functions
Vue.mixin({
data() {
return {
logger,
}
},
methods: {
t,
n,
},
})
export default class CommentInstance {
/**
* Initialize a new Comments instance for the desired type
*
* @param {string} resourceType the comments endpoint type
* @param {object} options the vue options (propsData, parent, el...)
* @param {object} options the vue options (propsData, parent, el...)
*/
constructor(resourceType = 'files', options = {}) {
const pinia = createPinia()
// Merge options and set `resourceType` property
options = {
...options,
propsData: {
const app = createApp(
CommentsApp,
{
...(options.propsData ?? {}),
resourceType,
},
pinia,
}
// Init Comments component
const View = Vue.extend(CommentsApp)
return new View(options)
)
// Add translates functions
app.mixin({
data() {
return {
logger,
}
},
methods: {
t,
n,
},
})
app.use(pinia)
// app.mount(options.el)
return app
}
}

View file

@ -33,11 +33,11 @@
<Comment
v-for="comment in comments"
:key="comment.props.id"
v-model="comment.props.message"
tag="li"
v-bind="comment.props"
:auto-complete="autoComplete"
:resource-type="resourceType"
:message.sync="comment.props.message"
:resource-id="currentResourceId"
:user-data="genMentionsData(comment.props.mentions)"
class="comments__list"