mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 07:31:09 -05:00
Add comment and reply functionality with preview and notification templates
This commit is contained in:
@@ -124,6 +124,289 @@ class VersionControl {
|
||||
.then(response => response.json());
|
||||
}
|
||||
|
||||
// Comment operations
|
||||
async createComment(threadId, content, parentId = null) {
|
||||
try {
|
||||
const response = await fetch('/vcs/comments/create/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
thread_id: threadId,
|
||||
content: content,
|
||||
parent_id: parentId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create comment');
|
||||
}
|
||||
|
||||
const comment = await response.json();
|
||||
return comment;
|
||||
} catch (error) {
|
||||
this.showError('Error creating comment: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createCommentThread(changeId, anchor, initialComment) {
|
||||
try {
|
||||
const response = await fetch('/vcs/comments/threads/create/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
change_id: changeId,
|
||||
anchor: anchor,
|
||||
initial_comment: initialComment
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create comment thread');
|
||||
}
|
||||
|
||||
const thread = await response.json();
|
||||
return thread;
|
||||
} catch (error) {
|
||||
this.showError('Error creating comment thread: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async resolveThread(threadId) {
|
||||
try {
|
||||
const response = await fetch(`/vcs/comments/threads/${threadId}/resolve/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to resolve thread');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
this.showError('Error resolving thread: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async editComment(commentId, content) {
|
||||
try {
|
||||
const response = await fetch(`/vcs/comments/${commentId}/`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: content
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to edit comment');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
this.showError('Error editing comment: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getThreadComments(threadId) {
|
||||
try {
|
||||
const response = await fetch(`/vcs/comments/threads/${threadId}/`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch thread comments');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
this.showError('Error fetching comments: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
initializeCommentPanel(containerId, options = {}) {
|
||||
const panel = new InlineCommentPanel({
|
||||
...options,
|
||||
onReply: async (content, parentId) => {
|
||||
const comment = await this.createComment(
|
||||
options.threadId,
|
||||
content,
|
||||
parentId
|
||||
);
|
||||
const thread = await this.getThreadComments(options.threadId);
|
||||
panel.setThread(thread);
|
||||
},
|
||||
onResolve: async () => {
|
||||
await this.resolveThread(options.threadId);
|
||||
const thread = await this.getThreadComments(options.threadId);
|
||||
panel.setThread(thread);
|
||||
}
|
||||
});
|
||||
panel.initialize(containerId);
|
||||
return panel;
|
||||
}
|
||||
|
||||
// Branch locking operations
|
||||
async acquireLock(branchName, duration = 48, reason = "") {
|
||||
try {
|
||||
const response = await fetch('/vcs/branches/lock/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
branch: branchName,
|
||||
duration: duration,
|
||||
reason: reason
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to acquire lock');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
this.refreshLockStatus(branchName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
this.showError('Error acquiring lock: ' + error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async releaseLock(branchName, force = false) {
|
||||
try {
|
||||
const response = await fetch('/vcs/branches/unlock/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
branch: branchName,
|
||||
force: force
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to release lock');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
this.refreshLockStatus(branchName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
this.showError('Error releasing lock: ' + error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getLockStatus(branchName) {
|
||||
try {
|
||||
const response = await fetch(`/vcs/branches/${encodeURIComponent(branchName)}/lock-status/`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get lock status');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
this.showError('Error getting lock status: ' + error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getLockHistory(branchName, limit = 10) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/vcs/branches/${encodeURIComponent(branchName)}/lock-history/?limit=${limit}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get lock history');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
this.showError('Error getting lock history: ' + error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async refreshLockStatus(branchName) {
|
||||
const lockStatus = await this.getLockStatus(branchName);
|
||||
if (!lockStatus) return;
|
||||
|
||||
const statusElement = document.querySelector(`[data-branch="${branchName}"] .lock-status`);
|
||||
if (!statusElement) return;
|
||||
|
||||
if (lockStatus.locked) {
|
||||
const expiryDate = new Date(lockStatus.expires);
|
||||
statusElement.className = 'lock-status locked';
|
||||
statusElement.innerHTML = `
|
||||
<svg class="lock-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div class="lock-info">
|
||||
<span class="user">${lockStatus.user}</span>
|
||||
<span class="expiry">Expires: ${expiryDate.toLocaleString()}</span>
|
||||
${lockStatus.reason ? `<span class="reason">${lockStatus.reason}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
statusElement.className = 'lock-status unlocked';
|
||||
statusElement.innerHTML = `
|
||||
<svg class="lock-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"/>
|
||||
</svg>
|
||||
<span>Unlocked</span>
|
||||
`;
|
||||
}
|
||||
|
||||
// Update lock controls
|
||||
this.updateLockControls(branchName, lockStatus);
|
||||
}
|
||||
|
||||
async updateLockControls(branchName, lockStatus) {
|
||||
const controlsElement = document.querySelector(`[data-branch="${branchName}"] .lock-controls`);
|
||||
if (!controlsElement) return;
|
||||
|
||||
if (lockStatus.locked) {
|
||||
controlsElement.innerHTML = `
|
||||
<button class="lock-button unlock" onclick="window.versionControl.releaseLock('${branchName}')">
|
||||
<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"/>
|
||||
</svg>
|
||||
Unlock Branch
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
controlsElement.innerHTML = `
|
||||
<button class="lock-button lock" onclick="window.versionControl.acquireLock('${branchName}')">
|
||||
<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Lock Branch
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
getCsrfToken() {
|
||||
return document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
@@ -149,7 +432,105 @@ class VersionControl {
|
||||
}
|
||||
}
|
||||
|
||||
// Import DiffViewer component
|
||||
import DiffViewer from './components/diff-viewer.js';
|
||||
|
||||
// Initialize version control
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.versionControl = new VersionControl();
|
||||
});
|
||||
|
||||
// Initialize DiffViewer if diff container exists
|
||||
const diffContainer = document.getElementById('diff-container');
|
||||
if (diffContainer) {
|
||||
window.diffViewer = new DiffViewer({
|
||||
renderStrategy: 'side-by-side'
|
||||
});
|
||||
diffViewer.initialize('diff-container');
|
||||
}
|
||||
});
|
||||
|
||||
// Add to VersionControl class constructor
|
||||
class VersionControl {
|
||||
constructor() {
|
||||
this.setupEventListeners();
|
||||
this.diffViewer = null;
|
||||
if (document.getElementById('diff-container')) {
|
||||
this.diffViewer = new DiffViewer({
|
||||
renderStrategy: 'side-by-side'
|
||||
});
|
||||
this.diffViewer.initialize('diff-container');
|
||||
}
|
||||
}
|
||||
|
||||
// Add getDiff method to VersionControl class
|
||||
async getDiff(version1, version2) {
|
||||
const url = `/vcs/diff/?v1=${encodeURIComponent(version1)}&v2=${encodeURIComponent(version2)}`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch diff');
|
||||
}
|
||||
const diffData = await response.json();
|
||||
|
||||
if (this.diffViewer) {
|
||||
await this.diffViewer.render(diffData);
|
||||
}
|
||||
|
||||
return diffData;
|
||||
} catch (error) {
|
||||
this.showError('Error loading diff: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add viewChanges method to VersionControl class
|
||||
async viewChanges(changeId) {
|
||||
try {
|
||||
const response = await fetch(`/vcs/changes/${changeId}/`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch changes');
|
||||
}
|
||||
const changeData = await response.json();
|
||||
|
||||
if (this.diffViewer) {
|
||||
await this.diffViewer.render(changeData);
|
||||
} else {
|
||||
this.showError('Diff viewer not initialized');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error loading changes: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add addComment method to VersionControl class
|
||||
async addComment(changeId, anchor, content) {
|
||||
try {
|
||||
const response = await fetch('/vcs/comments/add/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
change_id: changeId,
|
||||
anchor: anchor,
|
||||
content: content
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add comment');
|
||||
}
|
||||
|
||||
const comment = await response.json();
|
||||
if (this.diffViewer) {
|
||||
this.diffViewer.addCommentThread(anchor, [comment]);
|
||||
}
|
||||
|
||||
return comment;
|
||||
} catch (error) {
|
||||
this.showError('Error adding comment: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user