Skip to content

Commit 45e5957

Browse files
authored
Merge branch 'main' into readeable-error
2 parents 70d58cb + a3efd04 commit 45e5957

File tree

8 files changed

+102
-31
lines changed

8 files changed

+102
-31
lines changed

docs/content/doc/developers/guidelines-frontend.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
4040
7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
4141

4242

43+
### Framework Usage
44+
45+
Mixing different frameworks together is highly discouraged. A JavaScript module should follow one major framework and follow the framework's best practice.
46+
47+
Recommended implementations:
48+
* Vue + Native
49+
* Fomantic-UI (jQuery)
50+
* Native only
51+
52+
Discouraged implementations:
53+
* Vue + jQuery
54+
* jQuery + Native
55+
4356
### `async` Functions
4457

4558
Only mark a function as `async` if and only if there are `await` calls
@@ -98,6 +111,19 @@ $('#el').on('click', async (e) => { // not recommended but acceptable
98111
});
99112
```
100113

114+
### HTML Attributes and `dataset`
115+
116+
We forbid `dataset` usage, its camel-casing behaviour makes it hard to grep for attributes. However there are still some special cases, so the current guideline is:
117+
118+
* For legacy code:
119+
* `$.data()` should be refactored to `$.attr()`.
120+
* `$.data()` can be used to bind some non-string data to elements in rare cases, but it is highly discouraged.
121+
122+
* For new code:
123+
* `node.dataset` should not be used, use `node.getAttribute` instead.
124+
* never bind any user data to a DOM node, use a suitable design pattern to describe the relation between node and data.
125+
126+
101127
### Vue2/Vue3 and JSX
102128

103129
Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.

models/issue.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -824,14 +824,25 @@ func (issue *Issue) UpdateAttachments(uuids []string) (err error) {
824824

825825
// ChangeContent changes issue content, as the given user.
826826
func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
827-
issue.Content = content
828-
829827
ctx, committer, err := db.TxContext()
830828
if err != nil {
831829
return err
832830
}
833831
defer committer.Close()
834832

833+
hasContentHistory, err := issues.HasIssueContentHistory(ctx, issue.ID, 0)
834+
if err != nil {
835+
return fmt.Errorf("HasIssueContentHistory: %v", err)
836+
}
837+
if !hasContentHistory {
838+
if err = issues.SaveIssueContentHistory(db.GetEngine(ctx), issue.PosterID, issue.ID, 0,
839+
issue.CreatedUnix, issue.Content, true); err != nil {
840+
return fmt.Errorf("SaveIssueContentHistory: %v", err)
841+
}
842+
}
843+
844+
issue.Content = content
845+
835846
if err = updateIssueCols(db.GetEngine(ctx), issue, "content"); err != nil {
836847
return fmt.Errorf("UpdateIssueCols: %v", err)
837848
}
@@ -1012,11 +1023,6 @@ func newIssue(ctx context.Context, doer *User, opts NewIssueOptions) (err error)
10121023
return err
10131024
}
10141025

1015-
if err = issues.SaveIssueContentHistory(e, doer.ID, opts.Issue.ID, 0,
1016-
timeutil.TimeStampNow(), opts.Issue.Content, true); err != nil {
1017-
return err
1018-
}
1019-
10201026
return opts.Issue.addCrossReferences(ctx, doer, false)
10211027
}
10221028

models/issues/content_history.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,16 @@ func keepLimitedContentHistory(e db.Engine, issueID, commentID int64, limit int)
7575
log.Error("can not query content history for deletion, err=%v", err)
7676
return
7777
}
78-
if len(res) <= 1 {
78+
if len(res) <= 2 {
7979
return
8080
}
8181

8282
outDatedCount := len(res) - limit
8383
for outDatedCount > 0 {
8484
var indexToDelete int
8585
minEditedInterval := -1
86-
// find a history revision with minimal edited interval to delete
87-
for i := 1; i < len(res); i++ {
86+
// find a history revision with minimal edited interval to delete, the first and the last should never be deleted
87+
for i := 1; i < len(res)-1; i++ {
8888
editedInterval := int(res[i].EditedUnix - res[i-1].EditedUnix)
8989
if minEditedInterval == -1 || editedInterval < minEditedInterval {
9090
minEditedInterval = editedInterval
@@ -167,7 +167,20 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID int64, commentI
167167
return res, nil
168168
}
169169

170-
//SoftDeleteIssueContentHistory soft delete
170+
// HasIssueContentHistory check if a ContentHistory entry exists
171+
func HasIssueContentHistory(dbCtx context.Context, issueID int64, commentID int64) (bool, error) {
172+
exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{
173+
IssueID: issueID,
174+
CommentID: commentID,
175+
})
176+
if err != nil {
177+
log.Error("can not fetch issue content history. err=%v", err)
178+
return false, err
179+
}
180+
return exists, err
181+
}
182+
183+
// SoftDeleteIssueContentHistory soft delete
171184
func SoftDeleteIssueContentHistory(dbCtx context.Context, historyID int64) error {
172185
if _, err := db.GetEngine(dbCtx).ID(historyID).Cols("is_deleted", "content_text").Update(&ContentHistory{
173186
IsDeleted: true,

models/issues/content_history_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ func TestContentHistory(t *testing.T) {
5353
list2, _ := FetchIssueContentHistoryList(dbCtx, 10, 100)
5454
assert.Len(t, list2, 5)
5555

56+
hasHistory1, _ := HasIssueContentHistory(dbCtx, 10, 0)
57+
assert.True(t, hasHistory1)
58+
hasHistory2, _ := HasIssueContentHistory(dbCtx, 10, 1)
59+
assert.False(t, hasHistory2)
60+
5661
h6, h6Prev, _ := GetIssueContentHistoryAndPrev(dbCtx, 6)
5762
assert.EqualValues(t, 6, h6.ID)
5863
assert.EqualValues(t, 5, h6Prev.ID)
@@ -63,13 +68,13 @@ func TestContentHistory(t *testing.T) {
6368
assert.EqualValues(t, 6, h6.ID)
6469
assert.EqualValues(t, 4, h6Prev.ID)
6570

66-
// only keep 3 history revisions for comment_id=100
71+
// only keep 3 history revisions for comment_id=100, the first and the last should never be deleted
6772
keepLimitedContentHistory(dbEngine, 10, 100, 3)
6873
list1, _ = FetchIssueContentHistoryList(dbCtx, 10, 0)
6974
assert.Len(t, list1, 3)
7075
list2, _ = FetchIssueContentHistoryList(dbCtx, 10, 100)
7176
assert.Len(t, list2, 3)
72-
assert.EqualValues(t, 7, list2[0].HistoryID)
73-
assert.EqualValues(t, 6, list2[1].HistoryID)
77+
assert.EqualValues(t, 8, list2[0].HistoryID)
78+
assert.EqualValues(t, 7, list2[1].HistoryID)
7479
assert.EqualValues(t, 4, list2[2].HistoryID)
7580
}

services/comments/comments.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ func CreateIssueComment(doer *models.User, repo *models.Repository, issue *model
2525
if err != nil {
2626
return nil, err
2727
}
28-
err = issues.SaveIssueContentHistory(db.GetEngine(db.DefaultContext), doer.ID, issue.ID, comment.ID, timeutil.TimeStampNow(), comment.Content, true)
29-
if err != nil {
30-
return nil, err
31-
}
3228

3329
mentions, err := issue.FindAndUpdateIssueMentions(db.DefaultContext, doer, comment.Content)
3430
if err != nil {
@@ -42,11 +38,26 @@ func CreateIssueComment(doer *models.User, repo *models.Repository, issue *model
4238

4339
// UpdateComment updates information of comment.
4440
func UpdateComment(c *models.Comment, doer *models.User, oldContent string) error {
41+
var needsContentHistory = c.Content != oldContent &&
42+
(c.Type == models.CommentTypeComment || c.Type == models.CommentTypeReview || c.Type == models.CommentTypeCode)
43+
if needsContentHistory {
44+
hasContentHistory, err := issues.HasIssueContentHistory(db.DefaultContext, c.IssueID, c.ID)
45+
if err != nil {
46+
return err
47+
}
48+
if !hasContentHistory {
49+
if err = issues.SaveIssueContentHistory(db.GetEngine(db.DefaultContext), c.PosterID, c.IssueID, c.ID,
50+
c.CreatedUnix, oldContent, true); err != nil {
51+
return err
52+
}
53+
}
54+
}
55+
4556
if err := models.UpdateComment(c, doer); err != nil {
4657
return err
4758
}
4859

49-
if c.Type == models.CommentTypeComment && c.Content != oldContent {
60+
if needsContentHistory {
5061
err := issues.SaveIssueContentHistory(db.GetEngine(db.DefaultContext), doer.ID, c.IssueID, c.ID, timeutil.TimeStampNow(), c.Content, false)
5162
if err != nil {
5263
return err

templates/repo/pulls/files.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{{template "base/head" .}}
2+
3+
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
4+
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
5+
26
<div class="page-content repository view issue pull files diff">
37
{{template "repo/header" .}}
48
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">

web_src/js/features/repo-issue-content.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,11 @@ function showContentHistoryMenu(issueBaseUrl, $item, commentId) {
106106

107107
export function initRepoIssueContentHistory() {
108108
const issueIndex = $('#issueIndex').val();
109-
const $itemIssue = $('.timeline-item.comment.first');
110-
if (!issueIndex || !$itemIssue.length) return;
109+
if (!issueIndex) return;
110+
111+
const $itemIssue = $('.repository.issue .timeline-item.comment.first'); // issue(PR) main content
112+
const $comments = $('.repository.issue .comment-list .comment'); // includes: issue(PR) comments, code rerview comments
113+
if (!$itemIssue.length && !$comments.length) return;
111114

112115
const repoLink = $('#repolink').val();
113116
const issueBaseUrl = `${appSubUrl}/${repoLink}/issues/${issueIndex}`;
@@ -123,7 +126,7 @@ export function initRepoIssueContentHistory() {
123126
i18nTextDeleteFromHistoryConfirm = resp.i18n.textDeleteFromHistoryConfirm;
124127
i18nTextOptions = resp.i18n.textOptions;
125128

126-
if (resp.editedHistoryCountMap[0]) {
129+
if (resp.editedHistoryCountMap[0] && $itemIssue.length) {
127130
showContentHistoryMenu(issueBaseUrl, $itemIssue, '0');
128131
}
129132
for (const [commentId, _editedCount] of Object.entries(resp.editedHistoryCountMap)) {

web_src/js/features/repo-projects.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
const {csrfToken} = window.config;
22

33
async function initRepoProjectSortable() {
4-
const els = document.getElementsByClassName('board');
4+
const els = document.querySelectorAll('#project-board > .board');
55
if (!els.length) return;
66

77
const {Sortable} = await import(/* webpackChunkName: "sortable" */'sortablejs');
8-
const boardColumns = document.getElementsByClassName('board-column');
98

10-
new Sortable(els[0], {
9+
// the HTML layout is: #project-board > .board > .board-column .board.cards > .board-card.card .content
10+
const mainBoard = els[0];
11+
let boardColumns = mainBoard.getElementsByClassName('board-column');
12+
new Sortable(mainBoard, {
1113
group: 'board-column',
1214
draggable: '.board-column',
15+
filter: '[data-id="0"]',
1316
animation: 150,
1417
ghostClass: 'card-ghost',
1518
onSort: () => {
16-
const board = document.getElementsByClassName('board')[0];
17-
const boardColumns = board.getElementsByClassName('board-column');
18-
19-
for (const [i, column] of boardColumns.entries()) {
19+
boardColumns = mainBoard.getElementsByClassName('board-column');
20+
for (let i = 0; i < boardColumns.length; i++) {
21+
const column = boardColumns[i];
2022
if (parseInt($(column).data('sorting')) !== i) {
2123
$.ajax({
2224
url: $(column).data('url'),
@@ -33,8 +35,9 @@ async function initRepoProjectSortable() {
3335
},
3436
});
3537

36-
for (const column of boardColumns) {
37-
new Sortable(column.getElementsByClassName('board')[0], {
38+
for (const boardColumn of boardColumns) {
39+
const boardCardList = boardColumn.getElementsByClassName('board')[0];
40+
new Sortable(boardCardList, {
3841
group: 'shared',
3942
animation: 150,
4043
ghostClass: 'card-ghost',

0 commit comments

Comments
 (0)