Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Server Pro] New Feature: Support the Review Panel in Overleaf #95

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,13 @@
{
"command": "overleaf-workshop.collaboration.copyLineRef",
"title": "%commands.collaboration.copyLineRef.title%",
"enablement": "editorTextFocus && resourceScheme == overleaf-workshop",
"enablement": "editorTextFocus && editorHasSelection && resourceScheme == overleaf-workshop",
"category": "%extension.displayName%"
},
{
"command": "overleaf-workshop.collaboration.insertLineRef",
"title": "%commands.collaboration.insertLineRef.title%",
"enablement": "editorTextFocus && resourceScheme == overleaf-workshop",
"enablement": "editorTextFocus && editorHasSelection && resourceScheme == overleaf-workshop",
"category": "%extension.displayName%"
},
{
Expand All @@ -349,6 +349,18 @@
"title": "%commands.collaboration.jumpToUser.title%",
"enablement": "resourceScheme == overleaf-workshop || overleaf-workshop.activate",
"category": "%extension.displayName%"
},
{
"command": "overleaf-workshop.collaboration.addComment",
"title": "%commands.collaboration.addComment.title%",
"enablement": "editorTextFocus && editorHasSelection && resourceScheme == overleaf-workshop",
"category": "%extension.displayName%"
},
{
"command": "overleaf-workshop.collaboration.toggleTrackChanges",
"title": "%commands.collaboration.toggleTrackChanges.title%",
"enablement": "resourceScheme == overleaf-workshop || overleaf-workshop.activate",
"category": "%extension.displayName%"
}
],
"keybindings": [
Expand Down Expand Up @@ -501,6 +513,10 @@
{
"command": "overleaf-workshop.collaboration.insertLineRef",
"when": "resourceScheme == overleaf-workshop || overleaf-workshop.activate"
},
{
"command": "overleaf-workshop.collaboration.addComment",
"when": "resourceScheme == overleaf-workshop || overleaf-workshop.activate"
}
],
"view/title": [
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"commands.collaboration.insertLineRef.title": "Overleaf: Insert Line Reference",
"commands.collaboration.revealChatView.title": "Focus on Overleaf Chat View",
"commands.collaboration.jumpToUser.title": "Jump to Collaborator ...",
"commands.collaboration.addComment.title": "Overleaf: Add Comment",
"commands.collaboration.toggleTrackChanges.title": "Overleaf: Toggle Track Changes",

"configuration.compileOnSave.enabled.markdownDescription": "Always update the compiled PDF when a file is saved.",
"configuration.compileOutputFolderName.markdownDescription": "The name of the folder where the compiled output files (e.g., `output.pdf`) is located. (Take effect after restarting VSCode)",
Expand Down
1 change: 1 addition & 0 deletions resources/icons/dark/gutter-comment-unresolved.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/dark/gutter-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/dark/gutter-pass.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/light/gutter-comment-unresolved.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/light/gutter-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/light/gutter-pass.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export interface MisspellingItemSchema {
suggestions: string[]
}

export interface UserInfoSchema {
id: string,
first_name: string,
last_name?: string,
email: string,
}

export interface MemberEntity {
_id: string,
first_name: string,
Expand Down Expand Up @@ -111,7 +118,7 @@ export interface ProjectLabelResponseSchema {
}

export interface ProjectUpdateMeta {
users: {id:string, first_name:string, last_name?:string, email:string}[],
users: UserInfoSchema[],
start_ts: number,
end_ts: number,
}
Expand Down Expand Up @@ -154,7 +161,7 @@ export interface ProjectMessageResponseSchema {
content: string,
timestamp: number,
user_id: string,
user: {id:string, first_name:string, last_name?:string, email:string},
user: UserInfoSchema,
clientId: string,
}

Expand Down
102 changes: 101 additions & 1 deletion src/api/extendedBase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { BaseAPI, Identity } from "./base";
import { BaseAPI, Identity, ResponseSchema, UserInfoSchema } from "./base";

export interface ProjectLinkedFileProvider {
provider: 'project_file',
Expand All @@ -12,6 +12,49 @@ export interface UrlLinkedFileProvider {
url: string,
}

export interface CommentThreadSchema {
doc_id?: string, // to be filled in via API call
messages: CommentThreadMessageSchema[],
resolved?: boolean,
resolved_at?: string, //ISO format date string
resolved_by_user_id?: string,
resolved_by_user?: UserInfoSchema,
}

export interface CommentThreadMessageSchema {
id: string,
content: string,
timestamp: number,
user_id: string,
room_id?: string,
edited_at?: number,
user: UserInfoSchema,
}

export interface DocumentReviewSchema {
id: string,
metadata: {user_id:string, ts:string}, //"ts" is ISO format date string
}

export interface DocumentReviewChangeSchema extends DocumentReviewSchema {
op: {p:number, i?:string, d?:string},
}

export interface DocumentReviewCommentSchema extends DocumentReviewSchema {
op: {p:number, c:string, t:string}, // "c" for quoted text, "t" for thread_id
thread?: CommentThreadSchema, // to be filled in via API call
}

export interface DocumentRangesSchema {
changes?: DocumentReviewChangeSchema[],
comments?: DocumentReviewCommentSchema[],
}

export interface ExtendedResponseSchema extends ResponseSchema {
threads: {[threadId:string]: CommentThreadSchema},
ranges: {[docId:string]: DocumentRangesSchema},
}

export class ExtendedBaseAPI extends BaseAPI {
async refreshLinkedFile(identity:Identity, project_id:string, file_id:string) {
this.setIdentity(identity);
Expand All @@ -28,4 +71,61 @@ export class ExtendedBaseAPI extends BaseAPI {
return {message};
}, {'X-Csrf-Token': identity.csrfToken});
}

async getAllCommentThreads(identity: Identity, project_id: string) {
this.setIdentity(identity);
return await this.request('GET', `project/${project_id}/threads`, undefined, (res) => {
const threads = JSON.parse(res!);
return {threads};
}) as ExtendedResponseSchema;
}

async getAllDocumentRanges(identity: Identity, project_id: string) {
this.setIdentity(identity);
return await this.request('GET', `project/${project_id}/ranges`, undefined, (res) => {
const rangeList = JSON.parse(res!) as {id:string, ranges:DocumentRangesSchema}[];
const ranges = Object.assign({}, ...rangeList.map((r) => ({[r.id]: r.ranges})));
return {ranges};
}) as ExtendedResponseSchema;
}

async resolveCommentThread(identity: Identity, project_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/resolve`);
}

async reopenResolvedCommentThread(identity: Identity, project_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/reopen`);
}

async deleteResolvedCommentThread(identity: Identity, project_id: string, doc_id: string, thread_id: string) {
this.setIdentity(identity);
return await this.request('DELETE', `project/${project_id}/doc/${doc_id}/thread/${thread_id}`);
}

async postCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, content: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/messages`, {content});
}

async deleteCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, message_id: string) {
this.setIdentity(identity);
return await this.request('DELETE', `project/${project_id}/thread/${thread_id}/messages/${message_id}`);
}

async editCommentThreadMessage(identity: Identity, project_id: string, thread_id: string, message_id: string, content: string) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/thread/${thread_id}/messages/${message_id}/edit`, {content});
}

async toggleTrackChanges(identity: Identity, project_id: string, on_for: boolean | {[userId:string]: boolean}) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/track_changes`, {on_for});
}

async acceptTrackChanges(identity: Identity, project_id: string, doc_id: string, change_ids: string[]) {
this.setIdentity(identity);
return await this.request('POST', `project/${project_id}/doc/${doc_id}/changes/accept`, {change_ids});
}
}
55 changes: 54 additions & 1 deletion src/api/socketio.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Identity, BaseAPI, ProjectMessageResponseSchema } from './base';
import { Identity, BaseAPI, ProjectMessageResponseSchema, UserInfoSchema } from './base';
import { FileEntity, DocumentEntity, FileRefEntity, FileType, FolderEntity, ProjectEntity } from '../core/remoteFileSystemProvider';
import { EventBus } from '../utils/eventBus';
import { SocketIOAlt } from './socketioAlt';
import { CommentThreadMessageSchema } from './extendedBase';

export interface UpdateUserSchema {
id: string,
Expand Down Expand Up @@ -38,6 +39,8 @@ export interface UpdateSchema {
i?: string, //insert
d?: string, //delete
u?: boolean, //isUndo
c?: string, //quoted text
t?: string, //thread id
}[],
v: number, //doc version number
lastV?: number, //last version number
Expand All @@ -46,6 +49,7 @@ export interface UpdateSchema {
source: string, //socketio client id
ts: number, //unix timestamp
user_id: string,
tc?: string, //track change id
}
}

Expand All @@ -66,6 +70,15 @@ export interface EventsHandler {
onSpellCheckLanguageUpdated?: (language:string) => void,
onCompilerUpdated?: (compiler:string) => void,
onRootDocUpdated?: (rootDocId:string) => void,
// [Server Pro] comment
onCommentThreadResolved?: (threadId:string, userInfo:UserInfoSchema) => void,
onCommentThreadReopen?: (threadId:string) => void,
onCommentThreadDeleted?: (threadId:string) => void,
onCommentThreadMessageCreated?: (threadId:string, message:CommentThreadMessageSchema) => void,
onCommentThreadMessageEdited?: (threadId:string, messageId:string, content:string) => void,
onCommentThreadMessageDeleted?: (threadId:string, messageId:string) => void,
onToggleTrackChanges?: (enabling:boolean | {[userId:string]: boolean}) => void,
onAcceptTrackChanges?: (docId:string, tcIds: string[]) => void,
}

type ConnectionScheme = 'Alt' | 'v1' | 'v2';
Expand Down Expand Up @@ -260,6 +273,46 @@ export class SocketIOAPI {
handler(rootDocId);
});
break;
case handlers.onCommentThreadResolved:
this.socket.on('resolve-thread', (threadId:string, userInfo:UserInfoSchema) => {
handler(threadId, userInfo);
});
break;
case handlers.onCommentThreadReopen:
this.socket.on('reopen-thread', (threadId:string) => {
handler(threadId);
});
break;
case handlers.onCommentThreadDeleted:
this.socket.on('delete-thread', (threadId:string) => {
handler(threadId);
});
break;
case handlers.onCommentThreadMessageCreated:
this.socket.on('new-comment', (threadId:string, message:CommentThreadMessageSchema) => {
handler(threadId, message);
});
break;
case handlers.onCommentThreadMessageEdited:
this.socket.on('edit-message', (threadId:string, messageId:string, content:string) => {
handler(threadId, messageId, content);
});
break;
case handlers.onCommentThreadMessageDeleted:
this.socket.on('delete-message', (threadId:string, messageId:string) => {
handler(threadId, messageId);
});
break;
case handlers.onToggleTrackChanges:
this.socket.on('toggle-track-changes', (enabling:boolean | {[userId:string]: boolean}) => {
handler(enabling);
});
break;
case handlers.onAcceptTrackChanges:
this.socket.on('accept-changes', (docId:string, tcIds: string[]) => {
handler(docId, tcIds);
});
break;
default:
break;
}
Expand Down
18 changes: 16 additions & 2 deletions src/collaboration/clientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SocketIOAPI, UpdateUserSchema } from '../api/socketio';
import { VirtualFileSystem } from '../core/remoteFileSystemProvider';
import { ChatViewProvider } from './chatViewProvider';
import { LocalReplicaSCMProvider } from '../scm/localReplicaSCM';
import { ReviewPanelProvider } from './reviewPanelProvider';

interface ExtendedUpdateUserSchema extends UpdateUserSchema {
selection?: {
Expand Down Expand Up @@ -59,6 +60,7 @@ export class ClientManager {
private readonly onlineUsers: {[K:string]:ExtendedUpdateUserSchema} = {};
private connectedFlag: boolean = true;
private readonly chatViewer: ChatViewProvider;
private readonly reviewPanel: ReviewPanelProvider;

constructor(
private readonly vfs: VirtualFileSystem,
Expand Down Expand Up @@ -101,6 +103,7 @@ export class ClientManager {
});

this.chatViewer = new ChatViewProvider(this.vfs, this.publicId, this.context.extensionUri, this.socket);
this.reviewPanel = new ReviewPanelProvider(this.vfs, this.context, this.socket);
this.status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
this.updateStatus();
}
Expand Down Expand Up @@ -253,12 +256,21 @@ export class ClientManager {
break;
case true:
let prefixText = '';
let tooltip = new vscode.MarkdownString();

// notify track changes state
if (this.vfs.trackChangesState) {
prefixText = prefixText.concat(`$(record-small) `);
tooltip.appendMarkdown(`<h5 align="center">${vscode.l10n.t('Track changes is on.')}</h5><hr>\n\n`);
}

// notify unread messages
if (this.chatViewer.hasUnread) {
prefixText = prefixText.concat(`$(bell-dot) ${this.chatViewer.hasUnread} `);
}
this.status.command = this.chatViewer.hasUnread? `${ROOT_NAME}.collaboration.revealChatView` : `${ROOT_NAME}.collaboration.settings`;
this.status.backgroundColor = this.chatViewer.hasUnread? new vscode.ThemeColor('statusBarItem.warningBackground') : undefined;

// notify unSynced changes
const unSynced = this.socket.unSyncFileChanges;
if (unSynced) {
Expand All @@ -271,12 +283,12 @@ export class ClientManager {
case 0:
this.status.color = undefined;
this.status.text = prefixText + `${onlineIcon} 0`;
this.status.tooltip = `${ELEGANT_NAME}: ${vscode.l10n.t('Online')}`;
tooltip.appendMarkdown(`${ELEGANT_NAME}: ${vscode.l10n.t('Online')}`);
this.status.tooltip = tooltip;
break;
default:
this.status.color = this.activeExists ? this.onlineUsers[this.activeExists].selection?.color : undefined;
this.status.text = prefixText + `${onlineIcon} ${count}`;
const tooltip = new vscode.MarkdownString();
tooltip.appendMarkdown(`${ELEGANT_NAME}: ${this.activeExists? vscode.l10n.t('Active'): vscode.l10n.t('Idle') }\n\n`);

Object.values(this.onlineUsers).forEach(user => {
Expand Down Expand Up @@ -376,6 +388,8 @@ export class ClientManager {
}),
// register chat view provider
...this.chatViewer.triggers,
// register review panel provider
...this.reviewPanel.triggers,
// update this client's position
vscode.window.onDidChangeTextEditorSelection(async e => {
if (e.kind===undefined) { return; }
Expand Down
Loading
Loading