mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					77af0a23c4 | ||
| 
						 | 
					87bfe02b5b | ||
| 
						 | 
					9bac656b7d | ||
| 
						 | 
					ad68c9ccb2 | ||
| 
						 | 
					8d1cd4d252 | ||
| 
						 | 
					64eaa2a942 | ||
| 
						 | 
					489e9162fc | 
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,6 +4,17 @@ This changelog goes through all the changes that have been made in each release
 | 
			
		||||
without substantial changes to our git log; to see the highlights of what has
 | 
			
		||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
			
		||||
 | 
			
		||||
## [1.12.3](https://github.com/go-gitea/gitea/releases/tag/v1.12.3) - 2020-07-28
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
  * Don't change creation date when updating Release (#12343) (#12351)
 | 
			
		||||
  * Show 404 page when release not found (#12328) (#12332)
 | 
			
		||||
  * Fix emoji detection in certain cases (#12320) (#12327)
 | 
			
		||||
  * Reduce emoji size (#12317) (#12327)
 | 
			
		||||
  * Fix double-indirection bug in logging IDs (#12294) (#12308)
 | 
			
		||||
  * Link to pull list page on sidebar when view pr (#12256) (#12263)
 | 
			
		||||
  * Extend Notifications API and return pinned notifications by default (#12164) (#12232)
 | 
			
		||||
 | 
			
		||||
## [1.12.2](https://github.com/go-gitea/gitea/releases/tag/v1.12.2) - 2020-07-11
 | 
			
		||||
 | 
			
		||||
* BUGFIXES
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ func TestAPINotification(t *testing.T) {
 | 
			
		||||
	assert.EqualValues(t, false, apiNL[2].Pinned)
 | 
			
		||||
 | 
			
		||||
	// -- GET /repos/{owner}/{repo}/notifications --
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	DecodeJSON(t, resp, &apiNL)
 | 
			
		||||
 | 
			
		||||
@@ -92,7 +92,7 @@ func TestAPINotification(t *testing.T) {
 | 
			
		||||
	assert.True(t, new.New > 0)
 | 
			
		||||
 | 
			
		||||
	// -- mark notifications as read --
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	DecodeJSON(t, resp, &apiNL)
 | 
			
		||||
	assert.Len(t, apiNL, 2)
 | 
			
		||||
@@ -101,7 +101,7 @@ func TestAPINotification(t *testing.T) {
 | 
			
		||||
	req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusResetContent)
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	DecodeJSON(t, resp, &apiNL)
 | 
			
		||||
	assert.Len(t, apiNL, 1)
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ func TestEventSourceManagerRun(t *testing.T) {
 | 
			
		||||
	var apiNL []api.NotificationThread
 | 
			
		||||
 | 
			
		||||
	// -- mark notifications as read --
 | 
			
		||||
	req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
			
		||||
	req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	DecodeJSON(t, resp, &apiNL)
 | 
			
		||||
@@ -69,7 +69,7 @@ func TestEventSourceManagerRun(t *testing.T) {
 | 
			
		||||
	req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusResetContent)
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
			
		||||
	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token))
 | 
			
		||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	DecodeJSON(t, resp, &apiNL)
 | 
			
		||||
	assert.Len(t, apiNL, 1)
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ type FindNotificationOptions struct {
 | 
			
		||||
	UserID            int64
 | 
			
		||||
	RepoID            int64
 | 
			
		||||
	IssueID           int64
 | 
			
		||||
	Status            NotificationStatus
 | 
			
		||||
	Status            []NotificationStatus
 | 
			
		||||
	UpdatedAfterUnix  int64
 | 
			
		||||
	UpdatedBeforeUnix int64
 | 
			
		||||
}
 | 
			
		||||
@@ -89,8 +89,8 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
 | 
			
		||||
	if opts.IssueID != 0 {
 | 
			
		||||
		cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
 | 
			
		||||
	}
 | 
			
		||||
	if opts.Status != 0 {
 | 
			
		||||
		cond = cond.And(builder.Eq{"notification.status": opts.Status})
 | 
			
		||||
	if len(opts.Status) > 0 {
 | 
			
		||||
		cond = cond.And(builder.In("notification.status", opts.Status))
 | 
			
		||||
	}
 | 
			
		||||
	if opts.UpdatedAfterUnix != 0 {
 | 
			
		||||
		cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
 | 
			
		||||
 
 | 
			
		||||
@@ -130,6 +130,8 @@ func ReplaceAliases(s string) string {
 | 
			
		||||
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
 | 
			
		||||
func FindEmojiSubmatchIndex(s string) []int {
 | 
			
		||||
	loadMap()
 | 
			
		||||
	found := make(map[int]int)
 | 
			
		||||
	keys := make([]int, 0)
 | 
			
		||||
 | 
			
		||||
	//see if there are any emoji in string before looking for position of specific ones
 | 
			
		||||
	//no performance difference when there is a match but 10x faster when there are not
 | 
			
		||||
@@ -137,11 +139,26 @@ func FindEmojiSubmatchIndex(s string) []int {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get index of first emoji occurrence while also checking for longest combination
 | 
			
		||||
	for j := range GemojiData {
 | 
			
		||||
		i := strings.Index(s, GemojiData[j].Emoji)
 | 
			
		||||
		if i != -1 {
 | 
			
		||||
			return []int{i, i + len(GemojiData[j].Emoji)}
 | 
			
		||||
			if _, ok := found[i]; !ok {
 | 
			
		||||
				if len(keys) == 0 || i < keys[0] {
 | 
			
		||||
					found[i] = j
 | 
			
		||||
					keys = []int{i}
 | 
			
		||||
				}
 | 
			
		||||
				if i == 0 {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(keys) > 0 {
 | 
			
		||||
		index := keys[0]
 | 
			
		||||
		return []int{index, index + len(GemojiData[found[index]].Emoji)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -355,7 +355,7 @@ func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue {
 | 
			
		||||
// The Value will be colored with FgCyan
 | 
			
		||||
// If a ColoredValue is provided it is not changed
 | 
			
		||||
func NewColoredIDValue(value interface{}) *ColoredValue {
 | 
			
		||||
	return NewColoredValueBytes(&value, &fgCyanBytes)
 | 
			
		||||
	return NewColoredValueBytes(value, &fgCyanBytes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format will format the provided value and protect against ANSI color spoofing within the value
 | 
			
		||||
 
 | 
			
		||||
@@ -266,6 +266,10 @@ func TestRender_emoji(t *testing.T) {
 | 
			
		||||
	test(
 | 
			
		||||
		"Some text with 😄😄 2 emoji next to each other",
 | 
			
		||||
		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
 | 
			
		||||
	test(
 | 
			
		||||
		"😎🤪🔐🤑❓",
 | 
			
		||||
		`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
 | 
			
		||||
 | 
			
		||||
	// should match nothing
 | 
			
		||||
	test(
 | 
			
		||||
		"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,37 @@ import (
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func statusStringToNotificationStatus(status string) models.NotificationStatus {
 | 
			
		||||
	switch strings.ToLower(strings.TrimSpace(status)) {
 | 
			
		||||
	case "unread":
 | 
			
		||||
		return models.NotificationStatusUnread
 | 
			
		||||
	case "read":
 | 
			
		||||
		return models.NotificationStatusRead
 | 
			
		||||
	case "pinned":
 | 
			
		||||
		return models.NotificationStatusPinned
 | 
			
		||||
	default:
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func statusStringsToNotificationStatuses(statuses []string, defaultStatuses []string) []models.NotificationStatus {
 | 
			
		||||
	if len(statuses) == 0 {
 | 
			
		||||
		statuses = defaultStatuses
 | 
			
		||||
	}
 | 
			
		||||
	results := make([]models.NotificationStatus, 0, len(statuses))
 | 
			
		||||
	for _, status := range statuses {
 | 
			
		||||
		notificationStatus := statusStringToNotificationStatus(status)
 | 
			
		||||
		if notificationStatus > 0 {
 | 
			
		||||
			results = append(results, notificationStatus)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return results
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListRepoNotifications list users's notification threads on a specific repo
 | 
			
		||||
func ListRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
 | 
			
		||||
@@ -39,6 +67,14 @@ func ListRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
	//   description: If true, show notifications marked as read. Default value is false
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: status-types
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned"
 | 
			
		||||
	//   type: array
 | 
			
		||||
	//   collectionFormat: multi
 | 
			
		||||
	//   items:
 | 
			
		||||
	//     type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: since
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
 | 
			
		||||
@@ -75,9 +111,10 @@ func ListRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
		UpdatedBeforeUnix: before,
 | 
			
		||||
		UpdatedAfterUnix:  since,
 | 
			
		||||
	}
 | 
			
		||||
	qAll := strings.Trim(ctx.Query("all"), " ")
 | 
			
		||||
	if qAll != "true" {
 | 
			
		||||
		opts.Status = models.NotificationStatusUnread
 | 
			
		||||
 | 
			
		||||
	if !ctx.QueryBool("all") {
 | 
			
		||||
		statuses := ctx.QueryStrings("status-types")
 | 
			
		||||
		opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
 | 
			
		||||
	}
 | 
			
		||||
	nl, err := models.GetNotifications(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -97,7 +134,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
func ReadRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Mark notification threads as read on a specific repo
 | 
			
		||||
	// summary: Mark notification threads as read, pinned or unread on a specific repo
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
@@ -113,6 +150,24 @@ func ReadRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: all
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: If true, mark all notifications on this repo. Default value is false
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: status-types
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
 | 
			
		||||
	//   type: array
 | 
			
		||||
	//   collectionFormat: multi
 | 
			
		||||
	//   items:
 | 
			
		||||
	//     type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: to-status
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Status to mark notifications as. Defaults to read.
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: last_read_at
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
 | 
			
		||||
@@ -135,11 +190,17 @@ func ReadRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
			lastRead = tmpLastRead.Unix()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts := models.FindNotificationOptions{
 | 
			
		||||
		UserID:            ctx.User.ID,
 | 
			
		||||
		RepoID:            ctx.Repo.Repository.ID,
 | 
			
		||||
		UpdatedBeforeUnix: lastRead,
 | 
			
		||||
		Status:            models.NotificationStatusUnread,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.QueryBool("all") {
 | 
			
		||||
		statuses := ctx.QueryStrings("status-types")
 | 
			
		||||
		opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
 | 
			
		||||
		log.Error("%v", opts.Status)
 | 
			
		||||
	}
 | 
			
		||||
	nl, err := models.GetNotifications(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -147,8 +208,13 @@ func ReadRepoNotifications(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
 | 
			
		||||
	if targetStatus == 0 {
 | 
			
		||||
		targetStatus = models.NotificationStatusRead
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, n := range nl {
 | 
			
		||||
		err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
			
		||||
		err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.InternalServerError(err)
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,12 @@ func ReadThread(ctx *context.APIContext) {
 | 
			
		||||
	//   description: id of notification thread
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: to-status
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Status to mark notifications as
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   default: read
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "205":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
@@ -75,7 +81,12 @@ func ReadThread(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
			
		||||
	targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
 | 
			
		||||
	if targetStatus == 0 {
 | 
			
		||||
		targetStatus = models.NotificationStatusRead
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.InternalServerError(err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,14 @@ func ListNotifications(ctx *context.APIContext) {
 | 
			
		||||
	//   description: If true, show notifications marked as read. Default value is false
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: status-types
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned."
 | 
			
		||||
	//   type: array
 | 
			
		||||
	//   collectionFormat: multi
 | 
			
		||||
	//   items:
 | 
			
		||||
	//     type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: since
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
 | 
			
		||||
@@ -64,9 +72,9 @@ func ListNotifications(ctx *context.APIContext) {
 | 
			
		||||
		UpdatedBeforeUnix: before,
 | 
			
		||||
		UpdatedAfterUnix:  since,
 | 
			
		||||
	}
 | 
			
		||||
	qAll := strings.Trim(ctx.Query("all"), " ")
 | 
			
		||||
	if qAll != "true" {
 | 
			
		||||
		opts.Status = models.NotificationStatusUnread
 | 
			
		||||
	if !ctx.QueryBool("all") {
 | 
			
		||||
		statuses := ctx.QueryStrings("status-types")
 | 
			
		||||
		opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
 | 
			
		||||
	}
 | 
			
		||||
	nl, err := models.GetNotifications(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -82,11 +90,11 @@ func ListNotifications(ctx *context.APIContext) {
 | 
			
		||||
	ctx.JSON(http.StatusOK, nl.APIFormat())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadNotifications mark notification threads as read
 | 
			
		||||
// ReadNotifications mark notification threads as read, unread, or pinned
 | 
			
		||||
func ReadNotifications(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PUT /notifications notification notifyReadList
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Mark notification threads as read
 | 
			
		||||
	// summary: Mark notification threads as read, pinned or unread
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
@@ -98,6 +106,24 @@ func ReadNotifications(ctx *context.APIContext) {
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   format: date-time
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: all
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: If true, mark all notifications on this repo. Default value is false
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: status-types
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
 | 
			
		||||
	//   type: array
 | 
			
		||||
	//   collectionFormat: multi
 | 
			
		||||
	//   items:
 | 
			
		||||
	//     type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: to-status
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: Status to mark notifications as, Defaults to read.
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "205":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
@@ -117,7 +143,10 @@ func ReadNotifications(ctx *context.APIContext) {
 | 
			
		||||
	opts := models.FindNotificationOptions{
 | 
			
		||||
		UserID:            ctx.User.ID,
 | 
			
		||||
		UpdatedBeforeUnix: lastRead,
 | 
			
		||||
		Status:            models.NotificationStatusUnread,
 | 
			
		||||
	}
 | 
			
		||||
	if !ctx.QueryBool("all") {
 | 
			
		||||
		statuses := ctx.QueryStrings("status-types")
 | 
			
		||||
		opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
 | 
			
		||||
	}
 | 
			
		||||
	nl, err := models.GetNotifications(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -125,8 +154,13 @@ func ReadNotifications(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
 | 
			
		||||
	if targetStatus == 0 {
 | 
			
		||||
		targetStatus = models.NotificationStatusRead
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, n := range nl {
 | 
			
		||||
		err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
			
		||||
		err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.InternalServerError(err)
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -134,6 +134,10 @@ func SingleRelease(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrReleaseNotExist(err) {
 | 
			
		||||
			ctx.NotFound("GetRelease", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx.ServerError("GetReleasesByRepoID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
 | 
			
		||||
				rel.Publisher, rel.Repo, git.TagPrefix+rel.TagName,
 | 
			
		||||
				git.EmptySHA, commit.ID.String(), repository.NewPushCommits())
 | 
			
		||||
			notification.NotifyCreateRef(rel.Publisher, rel.Repo, "tag", git.TagPrefix+rel.TagName)
 | 
			
		||||
			rel.CreatedUnix = timeutil.TimeStampNow()
 | 
			
		||||
		}
 | 
			
		||||
		commit, err := gitRepo.GetTagCommit(rel.TagName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -53,7 +54,6 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		rel.Sha1 = commit.ID.String()
 | 
			
		||||
		rel.CreatedUnix = timeutil.TimeStampNow()
 | 
			
		||||
		rel.NumCommits, err = commit.CommitsCount()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("CommitsCount: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package release
 | 
			
		||||
import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
@@ -101,3 +102,153 @@ func TestRelease_Create(t *testing.T) {
 | 
			
		||||
		IsTag:        true,
 | 
			
		||||
	}, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRelease_Update(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, models.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
	repoPath := models.RepoPath(user.Name, repo.Name)
 | 
			
		||||
 | 
			
		||||
	gitRepo, err := git.OpenRepository(repoPath)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
 | 
			
		||||
	// Test a changed release
 | 
			
		||||
	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v1.1.1",
 | 
			
		||||
		Target:       "master",
 | 
			
		||||
		Title:        "v1.1.1 is released",
 | 
			
		||||
		Note:         "v1.1.1 is released",
 | 
			
		||||
		IsDraft:      false,
 | 
			
		||||
		IsPrerelease: false,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}, nil))
 | 
			
		||||
	release, err := models.GetRelease(repo.ID, "v1.1.1")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	releaseCreatedUnix := release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Note = "Changed note"
 | 
			
		||||
	assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
 | 
			
		||||
	release, err = models.GetReleaseByID(release.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
 | 
			
		||||
	// Test a changed draft
 | 
			
		||||
	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v1.2.1",
 | 
			
		||||
		Target:       "65f1bf2",
 | 
			
		||||
		Title:        "v1.2.1 is draft",
 | 
			
		||||
		Note:         "v1.2.1 is draft",
 | 
			
		||||
		IsDraft:      true,
 | 
			
		||||
		IsPrerelease: false,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}, nil))
 | 
			
		||||
	release, err = models.GetRelease(repo.ID, "v1.2.1")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	releaseCreatedUnix = release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Title = "Changed title"
 | 
			
		||||
	assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
 | 
			
		||||
	release, err = models.GetReleaseByID(release.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
 | 
			
		||||
	// Test a changed pre-release
 | 
			
		||||
	assert.NoError(t, CreateRelease(gitRepo, &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v1.3.1",
 | 
			
		||||
		Target:       "65f1bf2",
 | 
			
		||||
		Title:        "v1.3.1 is pre-released",
 | 
			
		||||
		Note:         "v1.3.1 is pre-released",
 | 
			
		||||
		IsDraft:      false,
 | 
			
		||||
		IsPrerelease: true,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}, nil))
 | 
			
		||||
	release, err = models.GetRelease(repo.ID, "v1.3.1")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	releaseCreatedUnix = release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Title = "Changed title"
 | 
			
		||||
	release.Note = "Changed note"
 | 
			
		||||
	assert.NoError(t, UpdateRelease(user, gitRepo, release, nil))
 | 
			
		||||
	release, err = models.GetReleaseByID(release.ID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRelease_createTag(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, models.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
 | 
			
		||||
	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
			
		||||
	repoPath := models.RepoPath(user.Name, repo.Name)
 | 
			
		||||
 | 
			
		||||
	gitRepo, err := git.OpenRepository(repoPath)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
 | 
			
		||||
	// Test a changed release
 | 
			
		||||
	release := &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v2.1.1",
 | 
			
		||||
		Target:       "master",
 | 
			
		||||
		Title:        "v2.1.1 is released",
 | 
			
		||||
		Note:         "v2.1.1 is released",
 | 
			
		||||
		IsDraft:      false,
 | 
			
		||||
		IsPrerelease: false,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	assert.NotEmpty(t, release.CreatedUnix)
 | 
			
		||||
	releaseCreatedUnix := release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Note = "Changed note"
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
 | 
			
		||||
	// Test a changed draft
 | 
			
		||||
	release = &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v2.2.1",
 | 
			
		||||
		Target:       "65f1bf2",
 | 
			
		||||
		Title:        "v2.2.1 is draft",
 | 
			
		||||
		Note:         "v2.2.1 is draft",
 | 
			
		||||
		IsDraft:      true,
 | 
			
		||||
		IsPrerelease: false,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	releaseCreatedUnix = release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Title = "Changed title"
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
 | 
			
		||||
	// Test a changed pre-release
 | 
			
		||||
	release = &models.Release{
 | 
			
		||||
		RepoID:       repo.ID,
 | 
			
		||||
		PublisherID:  user.ID,
 | 
			
		||||
		TagName:      "v2.3.1",
 | 
			
		||||
		Target:       "65f1bf2",
 | 
			
		||||
		Title:        "v2.3.1 is pre-released",
 | 
			
		||||
		Note:         "v2.3.1 is pre-released",
 | 
			
		||||
		IsDraft:      false,
 | 
			
		||||
		IsPrerelease: true,
 | 
			
		||||
		IsTag:        false,
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	releaseCreatedUnix = release.CreatedUnix
 | 
			
		||||
	time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
 | 
			
		||||
	release.Title = "Changed title"
 | 
			
		||||
	release.Note = "Changed note"
 | 
			
		||||
	assert.NoError(t, createTag(gitRepo, release))
 | 
			
		||||
	assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -128,12 +128,12 @@
 | 
			
		||||
			<span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span>
 | 
			
		||||
			{{range .Labels}}
 | 
			
		||||
				<div class="item">
 | 
			
		||||
					<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
 | 
			
		||||
					<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			{{range .OrgLabels}}
 | 
			
		||||
				<div class="item">
 | 
			
		||||
					<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
 | 
			
		||||
					<a class="ui label {{if not .IsChecked}}hide{{end}}" id="label_{{.ID}}" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
			{{end}}
 | 
			
		||||
@@ -238,7 +238,7 @@
 | 
			
		||||
			<div class="selected">
 | 
			
		||||
				{{range .Issue.Assignees}}
 | 
			
		||||
					<div class="item" style="margin-bottom: 10px;">
 | 
			
		||||
						<a href="{{$.RepoLink}}/issues?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}</a>
 | 
			
		||||
						<a href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}"><img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}}</a>
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -459,6 +459,16 @@
 | 
			
		||||
            "name": "all",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "array",
 | 
			
		||||
            "items": {
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "collectionFormat": "multi",
 | 
			
		||||
            "description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned.",
 | 
			
		||||
            "name": "status-types",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
@@ -502,7 +512,7 @@
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "notification"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Mark notification threads as read",
 | 
			
		||||
        "summary": "Mark notification threads as read, pinned or unread",
 | 
			
		||||
        "operationId": "notifyReadList",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
@@ -511,6 +521,28 @@
 | 
			
		||||
            "description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
 | 
			
		||||
            "name": "last_read_at",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "If true, mark all notifications on this repo. Default value is false",
 | 
			
		||||
            "name": "all",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "array",
 | 
			
		||||
            "items": {
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "collectionFormat": "multi",
 | 
			
		||||
            "description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
 | 
			
		||||
            "name": "status-types",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "Status to mark notifications as, Defaults to read.",
 | 
			
		||||
            "name": "to-status",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
@@ -587,6 +619,13 @@
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "default": "read",
 | 
			
		||||
            "description": "Status to mark notifications as",
 | 
			
		||||
            "name": "to-status",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
@@ -6290,6 +6329,16 @@
 | 
			
		||||
            "name": "all",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "array",
 | 
			
		||||
            "items": {
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "collectionFormat": "multi",
 | 
			
		||||
            "description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned",
 | 
			
		||||
            "name": "status-types",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
@@ -6333,7 +6382,7 @@
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "notification"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Mark notification threads as read on a specific repo",
 | 
			
		||||
        "summary": "Mark notification threads as read, pinned or unread on a specific repo",
 | 
			
		||||
        "operationId": "notifyReadRepoList",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
@@ -6350,6 +6399,28 @@
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "If true, mark all notifications on this repo. Default value is false",
 | 
			
		||||
            "name": "all",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "array",
 | 
			
		||||
            "items": {
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "collectionFormat": "multi",
 | 
			
		||||
            "description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
 | 
			
		||||
            "name": "status-types",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "Status to mark notifications as. Defaults to read.",
 | 
			
		||||
            "name": "to-status",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
 
 | 
			
		||||
@@ -1271,33 +1271,18 @@ i.icon.centerlock {
 | 
			
		||||
 | 
			
		||||
.emoji,
 | 
			
		||||
.reaction {
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
    line-height: 1.2;
 | 
			
		||||
    font-size: 1.25em;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    font-style: normal !important;
 | 
			
		||||
    font-weight: normal !important;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    vertical-align: -.075em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#issue-title > .emoji {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commit-summary > .emoji {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.label > .emoji {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown .emoji {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
.emoji img,
 | 
			
		||||
.reaction img {
 | 
			
		||||
    border-width: 0 !important;
 | 
			
		||||
    margin: 0 !important;
 | 
			
		||||
    width: 1em !important;
 | 
			
		||||
    height: 1em !important;
 | 
			
		||||
    vertical-align: middle !important;
 | 
			
		||||
    vertical-align: -.15em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2246,7 +2246,7 @@
 | 
			
		||||
        .select-reaction {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            padding: .5rem;
 | 
			
		||||
            padding: 0 .5rem;
 | 
			
		||||
 | 
			
		||||
            &:not(.active) a {
 | 
			
		||||
                display: none;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user