Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cfa01d6
feat(board-notes): add, edit and delete of BoardNotes
zokkis Feb 3, 2024
79f5d58
feat(board-notes): styles for card-content and modals
zokkis Feb 3, 2024
c668d8e
feat(board-notes): move notes like issues
zokkis Feb 3, 2024
90d0b60
feat(board-notes): set boardID to 0 on when board gets deleted
zokkis Feb 3, 2024
a9924b6
feat(board-notes): add projectID to BoardNote
zokkis Feb 3, 2024
423404c
feat(board-notes): better styles for large cards
zokkis Feb 3, 2024
c369889
fix: update note content on frontend and backend
zokkis Feb 3, 2024
962ea45
enhance: don't reload page to remove deleted column and cards
zokkis Feb 3, 2024
ac79a31
fix: max-width for cards and attachments (fixes: #29029)
zokkis Feb 3, 2024
bff8dc6
enhance: can add notes to 'Uncategorized'-Column
zokkis Feb 3, 2024
2c63ee1
enhance: don't reload page after moving note
zokkis Feb 3, 2024
696a632
enhance: new sort-order -> newest BoardNote on top
zokkis Feb 4, 2024
a29422f
refactor: use form-fetch-action instead of own $.ajax
zokkis Feb 4, 2024
0f4b806
enhance: content of BoardNotes with MD support
zokkis Feb 4, 2024
12f8cda
fix: whole project-column has cursor grab
zokkis Feb 4, 2024
51c8e09
enhance: smaller cards and view-modal
zokkis Feb 4, 2024
4e17cf2
enhance: create issue from board-note
zokkis Feb 4, 2024
0d8f8a2
i18n: translations for board-notes
zokkis Feb 4, 2024
965f70d
fix: edit the column-title don't remove the count-div (fixes: #29031)
zokkis Feb 4, 2024
762f2ce
fix: v1.21 to main
zokkis Feb 4, 2024
76b90eb
chore: lint
zokkis Feb 4, 2024
4815e7a
fix: required changes from CI and Bot
zokkis Feb 4, 2024
410b286
enhance: show amount of tasks in board-notes card
zokkis Feb 4, 2024
5084134
enhance: board-notes can now be pinned
zokkis Feb 4, 2024
abb5152
chore: rename BoardNote to ProjectBoardNote
zokkis Feb 6, 2024
1c12d87
refactor: LoadAttributes function
zokkis Feb 6, 2024
7b28107
fix: missed translations for form
zokkis Feb 7, 2024
f3ce631
fix: projectBoardNote delete
zokkis Feb 7, 2024
99cf2d1
enhance: add tags to project-board-notes
zokkis Feb 7, 2024
1bd15a4
fix: init markdownEditor on pinned notes
zokkis Feb 7, 2024
dd7cb77
enhance: select labels while creating note
zokkis Feb 7, 2024
0c77014
fix: unique note_label
zokkis Feb 7, 2024
313016e
style: break note card title
zokkis Feb 7, 2024
c87b630
fix: issue content was set with old data
zokkis Feb 7, 2024
be88ded
enhance: link milestone to note
zokkis Feb 8, 2024
1f1c997
permissions: only admin and creator can edit notes
zokkis Feb 8, 2024
67e5a86
fix: cursor-grap only on columns and cards if column-id != 0 and use …
zokkis Feb 8, 2024
1c50683
fix: moving pinned cards need permissions
zokkis Feb 8, 2024
f1387ca
enhance: show pinned note in list
zokkis Feb 8, 2024
0aa0c5c
fix: show milestone in view-modal of note
zokkis Feb 8, 2024
50e8545
enhance: notes are now available in orgs
zokkis Feb 9, 2024
2f4631a
enhance: milestone and label selectors with search
zokkis Feb 9, 2024
6222c42
chore: add migration
zokkis Feb 9, 2024
98cb5c0
i18n: translations for milestone
zokkis Feb 9, 2024
0f74de6
chore: fmt and lint
zokkis Feb 9, 2024
afcd5d8
fix: prefill of issue form is now from pinned notes possible
zokkis Feb 9, 2024
7e470d9
Merge branch 'main' into feature/project-board-notes
6543 Feb 25, 2024
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
Prev Previous commit
Next Next commit
enhance: notes are now available in orgs
  • Loading branch information
zokkis committed Feb 9, 2024
commit 50e8545c9024cb0ddcde6b448e8b60a218e4c70c
15 changes: 7 additions & 8 deletions models/project/note.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ import (

// ProjectBoardNote is used to represent a note on a boards
type ProjectBoardNote struct {
ID int64 `xorm:"pk autoincr"`
Title string `xorm:"TEXT NOT NULL"`
Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"`
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
PinOrder int64 `xorm:"NOT NULL DEFAULT 0"`
LabelIDs []int64 `xorm:"-"` // can't be []*Label because of 'import cycle not allowed'
MilestoneID int64 `xorm:"INDEX"`
ID int64 `xorm:"pk autoincr"`
Title string `xorm:"TEXT NOT NULL"`
Content string `xorm:"LONGTEXT"`
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
PinOrder int64 `xorm:"NOT NULL DEFAULT 0"`
LabelIDs []int64 `xorm:"-"` // can't be []*Label because of 'import cycle not allowed'
MilestoneID int64 `xorm:"INDEX"`

ProjectID int64 `xorm:"INDEX NOT NULL"`
BoardID int64 `xorm:"INDEX NOT NULL"`
Expand Down
311 changes: 311 additions & 0 deletions routers/web/org/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ func Projects(ctx *context.Context) {

for _, project := range projects {
project.RenderedContent = project.Description

pinnedProjectBoardNotes, err := project_model.GetProjectBoardNotesByProjectID(ctx, project.ID, true)
if err != nil {
ctx.ServerError("GetProjectBoardNotesByProjectID", err)
return
}
if len(pinnedProjectBoardNotes) > 0 {
project.FirstPinnedProjectBoardNote = pinnedProjectBoardNotes[0]
}
}

err = shared_user.LoadHeaderCount(ctx)
Expand Down Expand Up @@ -362,6 +371,12 @@ func ViewProject(ctx *context.Context) {
return
}

notesMap, err := project.LoadProjectBoardNotesFromBoardList(ctx, boards)
if err != nil {
ctx.ServerError("LoadProjectBoardNotesOfBoards", err)
return
}

if project.CardType != project_model.CardTypeTextOnly {
issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment)
for _, issuesList := range issuesMap {
Expand Down Expand Up @@ -395,13 +410,21 @@ func ViewProject(ctx *context.Context) {
}
}

pinnedBoardNotes, err := project_model.GetPinnedProjectBoardNotes(ctx, project.ID)
if err != nil {
ctx.ServerError("GetPinnedProjectBoardNotes", err)
return
}

project.RenderedContent = project.Description
ctx.Data["LinkedPRs"] = linkedPrsMap
ctx.Data["PageIsViewProjects"] = true
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
ctx.Data["Project"] = project
ctx.Data["IssuesMap"] = issuesMap
ctx.Data["Columns"] = boards // TODO: rename boards to columns in backend
ctx.Data["PinnedProjectBoardNotes"] = pinnedBoardNotes
ctx.Data["ProjectBoardNotesMap"] = notesMap
shared_user.RenderUserHeader(ctx)

err = shared_user.LoadHeaderCount(ctx)
Expand Down Expand Up @@ -749,3 +772,291 @@ func MoveIssues(ctx *context.Context) {

ctx.JSONOK()
}

func checkProjectBoardNoteChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.ProjectBoardNote) {
if ctx.Doer == nil {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in users are allowed to perform this action.",
})
return nil, nil
}

project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotFound", err)
} else {
ctx.ServerError("GetProjectByID", err)
}
return nil, nil
}
if project.OwnerID != ctx.ContextUser.ID {
ctx.NotFound("InvalidRepoID", nil)
return nil, nil
}

projectBoardNote, err := project_model.GetProjectBoardNoteByID(ctx, ctx.ParamsInt64(":noteID"))
if err != nil {
if project_model.IsErrProjectBoardNoteNotExist(err) {
ctx.NotFound("ProjectBoardNoteNotFound", err)
} else {
ctx.ServerError("GetProjectBoardNoteById", err)
}
return nil, nil
}

if !ctx.Doer.IsAdmin && ctx.Doer.ID != projectBoardNote.CreatorID {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only the creator or an admin can perform this action.",
})
return nil, nil
}

if projectBoardNote.ProjectID != project.ID {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectBoardNote[%d] is not in Project[%d] as expected", projectBoardNote.ID, project.ID),
})
return nil, nil
}

return project, projectBoardNote
}

func AddProjectBoardNoteToBoard(ctx *context.Context) {
if ctx.Doer == nil {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in users are allowed to perform this action.",
})
return
}

form := web.GetForm(ctx).(*forms.ProjectBoardNoteForm)

// LabelIDs is send without parentheses - maybe because of multipart/form-data
labelIdsString := "[" + ctx.Req.FormValue("labelIds") + "]"
var labelIDs []int64
if err := json.Unmarshal([]byte(labelIdsString), &labelIDs); err != nil {
ctx.ServerError("Unmarshal", err)
}

// check that all LabelsIDs are valid
for _, labelID := range labelIDs {
_, err := issues_model.GetLabelByID(ctx, labelID)
if err != nil {
if issues_model.IsErrLabelNotExist(err) {
ctx.Error(http.StatusNotFound, "GetLabelByID")
} else {
ctx.ServerError("GetLabelByID", err)
}
return
}
}

projectBoardNote := project_model.ProjectBoardNote{
Title: form.Title,
Content: form.Content,

ProjectID: ctx.ParamsInt64(":id"),
BoardID: ctx.ParamsInt64(":boardID"),
CreatorID: ctx.Doer.ID,
}
err := project_model.NewProjectBoardNote(ctx, &projectBoardNote)
if err != nil {
ctx.ServerError("NewProjectBoardNote", err)
return
}

if len(labelIDs) > 0 {
for _, labelID := range labelIDs {
err := projectBoardNote.AddLabel(ctx, labelID)
if err != nil {
ctx.ServerError("AddLabel", err)
return
}
}
}

ctx.JSONOK()
}

func EditProjectBoardNote(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.ProjectBoardNoteForm)
_, projectBoardNote := checkProjectBoardNoteChangePermissions(ctx)
if ctx.Written() {
return
}

projectBoardNote.Title = form.Title
projectBoardNote.Content = form.Content

if err := project_model.UpdateProjectBoardNote(ctx, projectBoardNote); err != nil {
ctx.ServerError("UpdateProjectBoardNote", err)
return
}

ctx.JSONOK()
}

func DeleteProjectBoardNote(ctx *context.Context) {
_, projectBoardNote := checkProjectBoardNoteChangePermissions(ctx)
if ctx.Written() {
return
}

if err := project_model.DeleteProjectBoardNote(ctx, projectBoardNote); err != nil {
ctx.ServerError("DeleteProjectBoardNote", err)
return
}

ctx.JSONOK()
}

func MoveProjectBoardNote(ctx *context.Context) {
if ctx.Doer == nil {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in users are allowed to perform this action.",
})
return
}

project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotFound", err)
} else {
ctx.ServerError("GetProjectByID", err)
}
return
}

var board *project_model.Board

if ctx.ParamsInt64(":boardID") == 0 {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Tr("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil {
if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
}
return
}
if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil)
return
}
}

type MovedProjectBoardNotesForm struct {
ProjectBoardNotes []struct {
ProjectBoardNoteID int64 `json:"projectBoardNoteID"`
Sorting int64 `json:"sorting"`
} `json:"projectBoardNotes"`
}

form := &MovedProjectBoardNotesForm{}
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.ServerError("DecodeMovedProjectBoardNotesForm", err)
return
}

projectBoardNoteIDs := make([]int64, 0, len(form.ProjectBoardNotes))
sortedProjectBoardNoteIDs := make(map[int64]int64)
for _, boardNote := range form.ProjectBoardNotes {
projectBoardNoteIDs = append(projectBoardNoteIDs, boardNote.ProjectBoardNoteID)
sortedProjectBoardNoteIDs[boardNote.Sorting] = boardNote.ProjectBoardNoteID
}
movedProjectBoardNotes, err := project_model.GetProjectBoardNotesByIds(ctx, projectBoardNoteIDs)
if err != nil {
if project_model.IsErrProjectBoardNoteNotExist(err) {
ctx.NotFound("ProjectBoardNoteNotExisting", nil)
} else {
ctx.ServerError("GetProjectBoardNoteByIds", err)
}
return
}

if len(movedProjectBoardNotes) != len(form.ProjectBoardNotes) {
ctx.ServerError("some project-board-notes do not exist", errors.New("some project-board-notes do not exist"))
return
}

if err = project_model.MoveProjectBoardNoteOnProjectBoard(ctx, board, sortedProjectBoardNoteIDs); err != nil {
ctx.ServerError("MoveProjectBoardNoteOnProjectBoard", err)
return
}

ctx.JSONOK()
}

// PinProjectBoardNote pins the BoardNote
func PinProjectBoardNote(ctx *context.Context) {
projectBoardNote, err := project_model.GetProjectBoardNoteByID(ctx, ctx.ParamsInt64(":noteID"))
if err != nil {
ctx.ServerError("GetProjectBoardNoteByID", err)
return
}

err = projectBoardNote.Pin(ctx)
if err != nil {
ctx.ServerError("PinProjectBoardNote", err)
return
}

ctx.JSONOK()
}

// PinBoardNote unpins the BoardNote
func UnPinProjectBoardNote(ctx *context.Context) {
projectBoardNote, err := project_model.GetProjectBoardNoteByID(ctx, ctx.ParamsInt64(":noteID"))
if err != nil {
ctx.ServerError("GetBoardNoteByID", err)
return
}

err = projectBoardNote.Unpin(ctx)
if err != nil {
ctx.ServerError("UnpinProjectBoardNote", err)
return
}

ctx.JSONOK()
}

// PinBoardNote moves a pined the BoardNote
func PinMoveProjectBoardNote(ctx *context.Context) {
if ctx.Doer == nil {
ctx.JSON(http.StatusForbidden, "Only signed in users are allowed to perform this action.")
return
}

type MovePinProjectBoardNoteForm struct {
Position int64 `json:"position"`
}

form := &MovePinProjectBoardNoteForm{}
if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.ServerError("Decode MovePinProjectBoardNoteForm", err)
return
}

projectBoardNote, err := project_model.GetProjectBoardNoteByID(ctx, ctx.ParamsInt64(":noteID"))
if err != nil {
ctx.ServerError("GetProjectBoardNoteByID", err)
return
}

err = projectBoardNote.MovePin(ctx, form.Position)
if err != nil {
ctx.ServerError("MovePinProjectBoardNote", err)
return
}

ctx.JSONOK()
}
Loading