Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d786e9703e | |||
| 0d09f761dc |
@@ -6,6 +6,14 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- 💄(frontend) improve comments highlights #1961
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(backend) do not filter deleted documents for the owner in the tree #2121
|
||||
|
||||
## [v4.8.3] - 2026-03-23
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1209,7 +1209,20 @@ class DocumentViewSet(
|
||||
path__startswith=ancestor.path, depth=ancestor.depth + 1
|
||||
)
|
||||
|
||||
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
|
||||
# Include deleted documents owned by the user, if any.
|
||||
if request.user.is_authenticated:
|
||||
owned_document_ids = models.DocumentAccess.objects.filter(
|
||||
user=user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
document__deleted_at__isnull=False,
|
||||
).values("document_id")
|
||||
|
||||
children = self.queryset.filter(
|
||||
children_clause,
|
||||
db.Q(deleted_at__isnull=True) | db.Q(id__in=owned_document_ids),
|
||||
)
|
||||
else:
|
||||
children = self.queryset.filter(children_clause, deleted_at__isnull=True)
|
||||
|
||||
queryset = (
|
||||
ancestors.select_related("creator").filter(
|
||||
|
||||
@@ -1258,3 +1258,58 @@ def test_api_documents_tree_list_deleted_document_owner(django_assert_num_querie
|
||||
assert content["children"][0][
|
||||
"deleted_at"
|
||||
] == child.ancestors_deleted_at.isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
def test_api_documents_tree_deleted_child_visible_to_owner():
|
||||
"""
|
||||
A deleted child document should appear in the tree for users who are its direct owner,
|
||||
even when they are not the owner of the parent.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="public")
|
||||
owned_child = factories.DocumentFactory(parent=parent, users=[(user, "owner")])
|
||||
other_child = factories.DocumentFactory(parent=parent)
|
||||
|
||||
owned_child.soft_delete()
|
||||
owned_child.refresh_from_db()
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{parent.id!s}/tree/")
|
||||
assert response.status_code == 200
|
||||
|
||||
content = response.json()
|
||||
child_ids = [c["id"] for c in content["children"]]
|
||||
assert str(owned_child.id) in child_ids
|
||||
assert str(other_child.id) in child_ids
|
||||
|
||||
owned_child_data = next(
|
||||
c for c in content["children"] if c["id"] == str(owned_child.id)
|
||||
)
|
||||
assert owned_child_data["deleted_at"] == owned_child.deleted_at.isoformat().replace(
|
||||
"+00:00", "Z"
|
||||
)
|
||||
|
||||
|
||||
def test_api_documents_tree_deleted_child_hidden_from_non_owner():
|
||||
"""
|
||||
A deleted child document should not appear in the tree for users who are not its owner.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(link_reach="public")
|
||||
deleted_child = factories.DocumentFactory(parent=parent)
|
||||
visible_child = factories.DocumentFactory(parent=parent)
|
||||
|
||||
deleted_child.soft_delete()
|
||||
|
||||
response = client.get(f"/api/v1.0/documents/{parent.id!s}/tree/")
|
||||
assert response.status_code == 200
|
||||
|
||||
content = response.json()
|
||||
child_ids = [c["id"] for c in content["children"]]
|
||||
assert str(deleted_child.id) not in child_ids
|
||||
assert str(visible_child.id) in child_ids
|
||||
|
||||
@@ -131,12 +131,13 @@ test.describe('Doc Comments', () => {
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(thread.getByText('This is a comment').first()).toBeHidden();
|
||||
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
|
||||
|
||||
// Check background color changed
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor.first().click();
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
@@ -185,6 +186,7 @@ test.describe('Doc Comments', () => {
|
||||
await thread.getByText('This is an edited comment').first().hover();
|
||||
await thread.locator('[data-test="resolve"]').click();
|
||||
await expect(thread).toBeHidden();
|
||||
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
@@ -196,11 +198,13 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await thread.getByRole('paragraph').first().fill('This is a new comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
|
||||
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor.first().click();
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
@@ -208,6 +212,7 @@ test.describe('Doc Comments', () => {
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await getMenuItem(thread, 'Delete comment').click();
|
||||
|
||||
await expect(editor.getByText('Hello')).not.toHaveClass('bn-thread-mark');
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
@@ -263,7 +268,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
// We change the role of the second user to reader
|
||||
@@ -298,7 +303,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
await otherEditor.getByText('Hello').click();
|
||||
await expect(
|
||||
@@ -344,7 +349,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(editor1.getByText('Document One')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor1.getByText('Document One').click();
|
||||
|
||||
+29
-4
@@ -8,12 +8,37 @@ export const cssComments = (
|
||||
& .--docs--main-editor .ProseMirror {
|
||||
// Comments marks in the editor
|
||||
.bn-editor {
|
||||
.bn-thread-mark:not([data-orphan='true']),
|
||||
.bn-thread-mark-selected:not([data-orphan='true']) {
|
||||
background: ${canSeeComment ? '#EDB40066' : 'transparent'};
|
||||
color: var(--c--globals--colors--gray-700);
|
||||
// Resets blocknote comments styles
|
||||
.bn-thread-mark,
|
||||
.bn-thread-mark-selected {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
${canSeeComment &&
|
||||
css`
|
||||
.bn-thread-mark:not([data-orphan='true']) {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--c--contextuals--background--palette--yellow--tertiary) 40%,
|
||||
transparent
|
||||
);
|
||||
border-bottom: 2px solid
|
||||
var(--c--contextuals--background--palette--yellow--secondary);
|
||||
|
||||
mix-blend-mode: multiply;
|
||||
|
||||
transition:
|
||||
background-color var(--c--globals--transitions--duration),
|
||||
border-bottom-color var(--c--globals--transitions--duration);
|
||||
|
||||
&:has(.bn-thread-mark-selected) {
|
||||
background-color: var(
|
||||
--c--contextuals--background--palette--yellow--tertiary
|
||||
);
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
[data-show-selection] {
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user