From ffa82556e0a8837b127b8b5385b1765bb4b72e4c Mon Sep 17 00:00:00 2001
From: waza-ari
Date: Sun, 10 Mar 2024 14:04:32 +0000
Subject: [PATCH 01/66] feat(teams): add public flags to teams to allow easier
sharing with other teams (#2179)
Resolves #2173
Co-authored-by: Daniel Herrmann
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2179
Reviewed-by: konrad
Co-authored-by: waza-ari
Co-committed-by: waza-ari
---
config.yml.sample | 3 +
docs/content/doc/setup/config.md | 11 +++
docs/content/doc/setup/openid.md | 17 ++++-
frontend/src/components/sharing/userTeam.vue | 13 +++-
frontend/src/i18n/lang/en.json | 4 +-
frontend/src/modelTypes/ITeam.ts | 1 +
frontend/src/models/team.ts | 1 +
frontend/src/stores/config.ts | 2 +
frontend/src/views/teams/EditTeam.vue | 24 +++++++
frontend/src/views/teams/NewTeam.vue | 25 +++++++
pkg/config/config.go | 2 +
pkg/db/fixtures/team_members.yml | 50 +++++--------
pkg/db/fixtures/teams.yml | 10 ++-
pkg/migration/20240309111148.go | 43 +++++++++++
pkg/models/teams.go | 25 ++++++-
pkg/models/teams_test.go | 75 ++++++++++++++++++++
pkg/modules/auth/openid/openid.go | 36 ++++++++--
pkg/modules/auth/openid/openid_test.go | 37 +++++++++-
pkg/routes/api/v1/info.go | 2 +
pkg/swagger/docs.go | 19 +++++
pkg/swagger/swagger.json | 19 +++++
pkg/swagger/swagger.yaml | 18 +++++
22 files changed, 392 insertions(+), 45 deletions(-)
create mode 100644 pkg/migration/20240309111148.go
diff --git a/config.yml.sample b/config.yml.sample
index acbb5a300..10f30b0d4 100644
--- a/config.yml.sample
+++ b/config.yml.sample
@@ -62,6 +62,9 @@ service:
allowiconchanges: true
# Allow using a custom logo via external URL.
customlogourl: ''
+ # Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
+ # discoverable when sharing a project, therefore not only showing teams the user is member of.
+ enablepublicteams: false
sentry:
# If set to true, enables anonymous error tracking of api errors via Sentry. This allows us to gather more
diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md
index a68bf22b5..c5210edec 100644
--- a/docs/content/doc/setup/config.md
+++ b/docs/content/doc/setup/config.md
@@ -346,6 +346,17 @@ Full path: `service.customlogourl`
Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
+### enablepublicteams
+
+discoverable when sharing a project, therefore not only showing teams the user is member of.
+
+Default: `false`
+
+Full path: `service.enablepublicteams`
+
+Environment path: `VIKUNJA_SERVICE_ENABLEPUBLICTEAMS`
+
+
---
## sentry
diff --git a/docs/content/doc/setup/openid.md b/docs/content/doc/setup/openid.md
index 320c2a725..b0e66e69c 100644
--- a/docs/content/doc/setup/openid.md
+++ b/docs/content/doc/setup/openid.md
@@ -99,7 +99,7 @@ It depends on the provider being used as well as the preferences of the administ
Typically you'd want to request an additional scope (e.g. `vikunja_scope`) which then triggers the identity provider to add the claim.
If the `vikunja_groups` is part of the **ID token**, Vikunja will start the procedure and import teams and team memberships.
-The claim structure expexted by Vikunja is as follows:
+The minimal claim structure expected by Vikunja is as follows:
```json
{
@@ -116,6 +116,21 @@ The claim structure expexted by Vikunja is as follows:
}
```
+It also also possible to pass the description and isPublic flag as optional parameter. If not present, the description will be empty and project visibility defaults to false.
+
+```json
+{
+ "vikunja_groups": [
+ {
+ "name": "team 3",
+ "oidcID": 33349,
+ "description": "My Team Description",
+ "isPublic": true
+ },
+ ]
+}
+```
+
For each team, you need to define a team `name` and an `oidcID`, where the `oidcID` can be any string with a length of less than 250 characters.
The `oidcID` is used to uniquely identify the team, so please make sure to keep this unique.
diff --git a/frontend/src/components/sharing/userTeam.vue b/frontend/src/components/sharing/userTeam.vue
index 3858c6ab5..535b8cdbb 100644
--- a/frontend/src/components/sharing/userTeam.vue
+++ b/frontend/src/components/sharing/userTeam.vue
@@ -172,6 +172,7 @@ import Multiselect from '@/components/input/multiselect.vue'
import Nothing from '@/components/misc/nothing.vue'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
+import {useConfigStore} from '@/stores/config'
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
@@ -210,8 +211,8 @@ const selectedRight = ref({})
const sharables = ref([])
const showDeleteModal = ref(false)
-
const authStore = useAuthStore()
+const configStore = useConfigStore()
const userInfo = computed(() => authStore.info)
function createShareTypeNameComputed(count: number) {
@@ -360,7 +361,15 @@ async function find(query: string) {
found.value = []
return
}
- const results = await searchService.getAll({}, {s: query})
+
+ // Include public teams here if we are sharing with teams and its enabled in the config
+ let results = []
+ if (props.shareType === 'team' && configStore.publicTeamsEnabled) {
+ results = await searchService.getAll({}, {s: query, includePublic: true})
+ } else {
+ results = await searchService.getAll({}, {s: query})
+ }
+
found.value = results
.filter(m => {
if(props.shareType === 'user' && m.id === currentUserId.value) {
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 652b43450..b7747ecb7 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -986,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/modelTypes/ITeam.ts b/frontend/src/modelTypes/ITeam.ts
index 3cdaae987..e9e7142cb 100644
--- a/frontend/src/modelTypes/ITeam.ts
+++ b/frontend/src/modelTypes/ITeam.ts
@@ -10,6 +10,7 @@ export interface ITeam extends IAbstract {
members: ITeamMember[]
right: Right
oidcId: string
+ isPublic: boolean
createdBy: IUser
created: Date
diff --git a/frontend/src/models/team.ts b/frontend/src/models/team.ts
index 1e75738bb..cc17849fa 100644
--- a/frontend/src/models/team.ts
+++ b/frontend/src/models/team.ts
@@ -14,6 +14,7 @@ export default class TeamModel extends AbstractModel implements ITeam {
members: ITeamMember[] = []
right: Right = RIGHTS.READ
oidcId = ''
+ isPublic: boolean = false
createdBy: IUser = {} // FIXME: seems wrong
created: Date = null
diff --git a/frontend/src/stores/config.ts b/frontend/src/stores/config.ts
index c5dbe6973..eb09372da 100644
--- a/frontend/src/stores/config.ts
+++ b/frontend/src/stores/config.ts
@@ -37,6 +37,7 @@ export interface ConfigState {
providers: IProvider[],
},
},
+ publicTeamsEnabled: boolean,
}
export const useConfigStore = defineStore('config', () => {
@@ -70,6 +71,7 @@ export const useConfigStore = defineStore('config', () => {
providers: [],
},
},
+ publicTeamsEnabled: false,
})
const migratorsEnabled = computed(() => state.availableMigrators?.length > 0)
diff --git a/frontend/src/views/teams/EditTeam.vue b/frontend/src/views/teams/EditTeam.vue
index 993f2e6af..f435d4df8 100644
--- a/frontend/src/views/teams/EditTeam.vue
+++ b/frontend/src/views/teams/EditTeam.vue
@@ -33,6 +33,27 @@
>
{{ $t('team.attributes.nameRequired') }}
+
+
{{ $t('team.attributes.isPublic') }}
+
+
+ {{ $t('team.attributes.isPublicDescription') }}
+
+
+
+
+
{{ $t('team.attributes.isPublic') }}
+
+
+ {{ $t('team.attributes.isPublicDescription') }}
+
+
+
t('team.create.title'))
useTitle(title)
@@ -60,6 +83,8 @@ const teamService = shallowReactive(new TeamService())
const team = reactive(new TeamModel())
const showError = ref(false)
+const configStore = useConfigStore()
+
async function newTeam() {
if (team.name === '') {
showError.value = true
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 0e82a6637..d8f904e52 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -64,6 +64,7 @@ const (
ServiceMaxAvatarSize Key = `service.maxavatarsize`
ServiceAllowIconChanges Key = `service.allowiconchanges`
ServiceCustomLogoURL Key = `service.customlogourl`
+ ServiceEnablePublicTeams Key = `service.enablepublicteams`
SentryEnabled Key = `sentry.enabled`
SentryDsn Key = `sentry.dsn`
@@ -312,6 +313,7 @@ func InitDefaultConfig() {
ServiceMaxAvatarSize.setDefault(1024)
ServiceDemoMode.setDefault(false)
ServiceAllowIconChanges.setDefault(true)
+ ServiceEnablePublicTeams.setDefault(false)
// Sentry
SentryDsn.setDefault("https://440eedc957d545a795c17bbaf477497c@o1047380.ingest.sentry.io/4504254983634944")
diff --git a/pkg/db/fixtures/team_members.yml b/pkg/db/fixtures/team_members.yml
index 889322b7b..2b260b329 100644
--- a/pkg/db/fixtures/team_members.yml
+++ b/pkg/db/fixtures/team_members.yml
@@ -1,61 +1,49 @@
--
- team_id: 1
+- team_id: 1
user_id: 1
admin: true
created: 2018-12-01 15:13:12
--
- team_id: 1
+- team_id: 1
user_id: 2
created: 2018-12-01 15:13:12
--
- team_id: 2
+- team_id: 2
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 3
+- team_id: 3
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 4
+- team_id: 4
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 5
+- team_id: 5
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 6
+- team_id: 6
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 7
+- team_id: 7
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 8
+- team_id: 8
user_id: 1
created: 2018-12-01 15:13:12
--
- team_id: 9
+- team_id: 9
user_id: 2
created: 2018-12-01 15:13:12
--
- team_id: 10
+- team_id: 10
user_id: 3
created: 2018-12-01 15:13:12
--
- team_id: 11
+- team_id: 11
user_id: 8
created: 2018-12-01 15:13:12
--
- team_id: 12
+- team_id: 12
user_id: 9
created: 2018-12-01 15:13:12
--
- team_id: 13
+- team_id: 13
user_id: 10
created: 2018-12-01 15:13:12
--
- team_id: 14
+- team_id: 14
user_id: 10
- created: 2018-12-01 15:13:12
\ No newline at end of file
+ created: 2018-12-01 15:13:12
+- team_id: 15
+ user_id: 10
+ created: 2018-12-01 15:13:12
diff --git a/pkg/db/fixtures/teams.yml b/pkg/db/fixtures/teams.yml
index aaba624a3..5c4f49ec7 100644
--- a/pkg/db/fixtures/teams.yml
+++ b/pkg/db/fixtures/teams.yml
@@ -29,8 +29,16 @@
- id: 13
name: testteam13
created_by_id: 7
+ is_public: true
- id: 14
name: testteam14
created_by_id: 7
oidc_id: 14
- issuer: "https://some.issuer"
\ No newline at end of file
+ issuer: "https://some.issuer"
+- id: 15
+ name: testteam15
+ created_by_id: 7
+ oidc_id: 15
+ issuer: "https://some.issuer"
+ is_public: true
+ description: "This is a public team"
diff --git a/pkg/migration/20240309111148.go b/pkg/migration/20240309111148.go
new file mode 100644
index 000000000..7b0aa4c83
--- /dev/null
+++ b/pkg/migration/20240309111148.go
@@ -0,0 +1,43 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package migration
+
+import (
+ "src.techknowlogick.com/xormigrate"
+ "xorm.io/xorm"
+)
+
+type teams20240309111148 struct {
+ IsPublic bool `xorm:"not null default false" json:"is_public"`
+}
+
+func (teams20240309111148) TableName() string {
+ return "teams"
+}
+
+func init() {
+ migrations = append(migrations, &xormigrate.Migration{
+ ID: "20240309111148",
+ Description: "Add IsPublic field to teams table to control discoverability of teams.",
+ Migrate: func(tx *xorm.Engine) error {
+ return tx.Sync2(teams20240309111148{})
+ },
+ Rollback: func(tx *xorm.Engine) error {
+ return nil
+ },
+ })
+}
diff --git a/pkg/models/teams.go b/pkg/models/teams.go
index 648cbe919..8cae52324 100644
--- a/pkg/models/teams.go
+++ b/pkg/models/teams.go
@@ -19,6 +19,7 @@ package models
import (
"time"
+ "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
@@ -53,6 +54,12 @@ type Team struct {
// A timestamp when this relation was last updated. You cannot change this value.
Updated time.Time `xorm:"updated" json:"updated"`
+ // Defines wether the team should be publicly discoverable when sharing a project
+ IsPublic bool `xorm:"not null default false" json:"is_public"`
+
+ // Query parameter controlling whether to include public projects or not
+ IncludePublic bool `xorm:"-" query:"include_public" json:"include_public"`
+
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`
}
@@ -100,6 +107,7 @@ type OIDCTeam struct {
Name string
OidcID string
Description string
+ IsPublic bool
}
// GetTeamByID gets a team by its ID
@@ -287,11 +295,24 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
limit, start := getLimitFromPageIndex(page, perPage)
all := []*Team{}
+
query := s.Select("teams.*").
Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id").
- Where("team_members.user_id = ?", a.GetID()).
Where(db.ILIKE("teams.name", search))
+
+ // If public teams are enabled, we want to include them in the result
+ if config.ServiceEnablePublicTeams.GetBool() && t.IncludePublic {
+ query = query.Where(
+ builder.Or(
+ builder.Eq{"teams.is_public": true},
+ builder.Eq{"team_members.user_id": a.GetID()},
+ ),
+ )
+ } else {
+ query = query.Where("team_members.user_id = ?", a.GetID())
+ }
+
if limit > 0 {
query = query.Limit(limit, start)
}
@@ -398,7 +419,7 @@ func (t *Team) Update(s *xorm.Session, _ web.Auth) (err error) {
return
}
- _, err = s.ID(t.ID).Update(t)
+ _, err = s.ID(t.ID).UseBool("is_public").Update(t)
if err != nil {
return
}
diff --git a/pkg/models/teams_test.go b/pkg/models/teams_test.go
index 627cbc249..4f0ccd2d2 100644
--- a/pkg/models/teams_test.go
+++ b/pkg/models/teams_test.go
@@ -20,6 +20,7 @@ import (
"reflect"
"testing"
+ "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/user"
@@ -49,6 +50,7 @@ func TestTeam_Create(t *testing.T) {
"id": team.ID,
"name": "Testteam293",
"description": "Lorem Ispum",
+ "is_public": false,
}, false)
})
t.Run("empty name", func(t *testing.T) {
@@ -61,6 +63,27 @@ func TestTeam_Create(t *testing.T) {
require.Error(t, err)
assert.True(t, IsErrTeamNameCannotBeEmpty(err))
})
+ t.Run("public", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ team := &Team{
+ Name: "Testteam293_Public",
+ Description: "Lorem Ispum",
+ IsPublic: true,
+ }
+ err := team.Create(s, doer)
+ require.NoError(t, err)
+ err = s.Commit()
+ require.NoError(t, err)
+ db.AssertExists(t, "teams", map[string]interface{}{
+ "id": team.ID,
+ "name": "Testteam293_Public",
+ "description": "Lorem Ispum",
+ "is_public": true,
+ }, false)
+ })
}
func TestTeam_ReadOne(t *testing.T) {
@@ -126,6 +149,58 @@ func TestTeam_ReadAll(t *testing.T) {
assert.Len(t, ts, 1)
assert.Equal(t, int64(2), ts[0].ID)
})
+ t.Run("public discovery disabled", func(t *testing.T) {
+
+ s := db.NewSession()
+ defer s.Close()
+
+ team := &Team{}
+
+ // Default setting is having ServiceEnablePublicTeams disabled
+ // In this default case, fetching teams with or without public flag should return the same result
+
+ // Fetch without public flag
+ teams, _, _, err := team.ReadAll(s, doer, "", 1, 50)
+ require.NoError(t, err)
+ assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind())
+ ts := teams.([]*Team)
+ assert.Len(t, ts, 5)
+
+ // Fetch with public flag
+ team.IncludePublic = true
+ teams, _, _, err = team.ReadAll(s, doer, "", 1, 50)
+ require.NoError(t, err)
+ assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind())
+ ts = teams.([]*Team)
+ assert.Len(t, ts, 5)
+ })
+
+ t.Run("public discovery enabled", func(t *testing.T) {
+
+ s := db.NewSession()
+ defer s.Close()
+
+ team := &Team{}
+
+ // Enable ServiceEnablePublicTeams feature
+ config.ServiceEnablePublicTeams.Set(true)
+
+ // Fetch without public flag should be the same as before
+ team.IncludePublic = false
+ teams, _, _, err := team.ReadAll(s, doer, "", 1, 50)
+ require.NoError(t, err)
+ assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind())
+ ts := teams.([]*Team)
+ assert.Len(t, ts, 5)
+
+ // Fetch with public flag should return more teams
+ team.IncludePublic = true
+ teams, _, _, err = team.ReadAll(s, doer, "", 1, 50)
+ require.NoError(t, err)
+ assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind())
+ ts = teams.([]*Team)
+ assert.Len(t, ts, 7)
+ })
}
func TestTeam_Update(t *testing.T) {
diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go
index 1da50c06b..ad9655315 100644
--- a/pkg/modules/auth/openid/openid.go
+++ b/pkg/modules/auth/openid/openid.go
@@ -298,14 +298,27 @@ func getTeamDataFromToken(groups []map[string]interface{}, provider *Provider) (
var name string
var description string
var oidcID string
+ var IsPublic bool
+
+ // Read name
_, exists := team["name"]
if exists {
name = team["name"].(string)
}
+
+ // Read description
_, exists = team["description"]
if exists {
description = team["description"].(string)
}
+
+ // Read isPublic flag
+ _, exists = team["isPublic"]
+ if exists {
+ IsPublic = team["isPublic"].(bool)
+ }
+
+ // Read oidcID
_, exists = team["oidcID"]
if exists {
switch t := team["oidcID"].(type) {
@@ -324,7 +337,7 @@ func getTeamDataFromToken(groups []map[string]interface{}, provider *Provider) (
errs = append(errs, &user.ErrOpenIDCustomScopeMalformed{})
continue
}
- teamData = append(teamData, &models.OIDCTeam{Name: name, OidcID: oidcID, Description: description})
+ teamData = append(teamData, &models.OIDCTeam{Name: name, OidcID: oidcID, Description: description, IsPublic: IsPublic})
}
return teamData, errs
}
@@ -339,6 +352,7 @@ func CreateOIDCTeam(s *xorm.Session, teamData *models.OIDCTeam, u *user.User, is
Description: teamData.Description,
OidcID: teamData.OidcID,
Issuer: issuer,
+ IsPublic: teamData.IsPublic,
}
err = team.CreateNewTeam(s, u, false)
return team, err
@@ -363,12 +377,24 @@ func GetOrCreateTeamsByOIDC(s *xorm.Session, teamData []*models.OIDCTeam, u *use
continue
}
+ // Compare the name and update if it changed
if team.Name != getOIDCTeamName(oidcTeam.Name) {
team.Name = getOIDCTeamName(oidcTeam.Name)
- err = team.Update(s, u)
- if err != nil {
- return nil, err
- }
+ }
+
+ // Compare the description and update if it changed
+ if team.Description != oidcTeam.Description {
+ team.Description = oidcTeam.Description
+ }
+
+ // Compare the isPublic flag and update if it changed
+ if team.IsPublic != oidcTeam.IsPublic {
+ team.IsPublic = oidcTeam.IsPublic
+ }
+
+ err = team.Update(s, u)
+ if err != nil {
+ return nil, err
}
log.Debugf("Team with oidc_id %v and name %v already exists.", team.OidcID, team.Name)
diff --git a/pkg/modules/auth/openid/openid_test.go b/pkg/modules/auth/openid/openid_test.go
index eb234600d..b1ebcdaa7 100644
--- a/pkg/modules/auth/openid/openid_test.go
+++ b/pkg/modules/auth/openid/openid_test.go
@@ -128,8 +128,41 @@ func TestGetOrCreateUser(t *testing.T) {
"email": cl.Email,
}, false)
db.AssertExists(t, "teams", map[string]interface{}{
- "id": oidcTeams,
- "name": team + " (OIDC)",
+ "id": oidcTeams,
+ "name": team + " (OIDC)",
+ "is_public": false,
+ }, false)
+ })
+
+ t.Run("Update IsPublic flag for existing team", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ team := "testteam15"
+ oidcID := "15"
+ cl := &claims{
+ Email: "other-email-address@some.service.com",
+ VikunjaGroups: []map[string]interface{}{
+ {"name": team, "oidcID": oidcID, "isPublic": true},
+ },
+ }
+
+ u, err := getOrCreateUser(s, cl, "https://some.service.com", "12345")
+ require.NoError(t, err)
+ teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
+ for _, err := range errs {
+ require.NoError(t, err)
+ }
+ oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
+ require.NoError(t, err)
+ err = s.Commit()
+ require.NoError(t, err)
+
+ db.AssertExists(t, "teams", map[string]interface{}{
+ "id": oidcTeams,
+ "name": team + " (OIDC)",
+ "is_public": true,
}, false)
})
diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go
index 59958b0dc..8bd16e76b 100644
--- a/pkg/routes/api/v1/info.go
+++ b/pkg/routes/api/v1/info.go
@@ -51,6 +51,7 @@ type vikunjaInfos struct {
TaskCommentsEnabled bool `json:"task_comments_enabled"`
DemoModeEnabled bool `json:"demo_mode_enabled"`
WebhooksEnabled bool `json:"webhooks_enabled"`
+ PublicTeamsEnabled bool `json:"public_teams_enabled"`
}
type authInfo struct {
@@ -95,6 +96,7 @@ func Info(c echo.Context) error {
TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(),
DemoModeEnabled: config.ServiceDemoMode.GetBool(),
WebhooksEnabled: config.WebhooksEnabled.GetBool(),
+ PublicTeamsEnabled: config.ServiceEnablePublicTeams.GetBool(),
AvailableMigrators: []string{
(&vikunja_file.FileMigrator{}).Name(),
(&ticktick.Migrator{}).Name(),
diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go
index 76a185175..b67243664 100644
--- a/pkg/swagger/docs.go
+++ b/pkg/swagger/docs.go
@@ -8229,6 +8229,14 @@ const docTemplate = `{
"description": "The unique, numeric id of this team.",
"type": "integer"
},
+ "include_public": {
+ "description": "Query parameter controlling whether to include public projects or not",
+ "type": "boolean"
+ },
+ "is_public": {
+ "description": "Defines wether the team should be publicly discoverable when sharing a project",
+ "type": "boolean"
+ },
"members": {
"description": "An array of all members in this team.",
"type": "array",
@@ -8364,6 +8372,14 @@ const docTemplate = `{
"description": "The unique, numeric id of this team.",
"type": "integer"
},
+ "include_public": {
+ "description": "Query parameter controlling whether to include public projects or not",
+ "type": "boolean"
+ },
+ "is_public": {
+ "description": "Defines wether the team should be publicly discoverable when sharing a project",
+ "type": "boolean"
+ },
"members": {
"description": "An array of all members in this team.",
"type": "array",
@@ -8886,6 +8902,9 @@ const docTemplate = `{
"motd": {
"type": "string"
},
+ "public_teams_enabled": {
+ "type": "boolean"
+ },
"registration_enabled": {
"type": "boolean"
},
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index 7edf42ae0..836a1baa4 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -8221,6 +8221,14 @@
"description": "The unique, numeric id of this team.",
"type": "integer"
},
+ "include_public": {
+ "description": "Query parameter controlling whether to include public projects or not",
+ "type": "boolean"
+ },
+ "is_public": {
+ "description": "Defines wether the team should be publicly discoverable when sharing a project",
+ "type": "boolean"
+ },
"members": {
"description": "An array of all members in this team.",
"type": "array",
@@ -8356,6 +8364,14 @@
"description": "The unique, numeric id of this team.",
"type": "integer"
},
+ "include_public": {
+ "description": "Query parameter controlling whether to include public projects or not",
+ "type": "boolean"
+ },
+ "is_public": {
+ "description": "Defines wether the team should be publicly discoverable when sharing a project",
+ "type": "boolean"
+ },
"members": {
"description": "An array of all members in this team.",
"type": "array",
@@ -8878,6 +8894,9 @@
"motd": {
"type": "string"
},
+ "public_teams_enabled": {
+ "type": "boolean"
+ },
"registration_enabled": {
"type": "boolean"
},
diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml
index 76a301bc3..1365bd24d 100644
--- a/pkg/swagger/swagger.yaml
+++ b/pkg/swagger/swagger.yaml
@@ -877,6 +877,14 @@ definitions:
id:
description: The unique, numeric id of this team.
type: integer
+ include_public:
+ description: Query parameter controlling whether to include public projects
+ or not
+ type: boolean
+ is_public:
+ description: Defines wether the team should be publicly discoverable when
+ sharing a project
+ type: boolean
members:
description: An array of all members in this team.
items:
@@ -984,6 +992,14 @@ definitions:
id:
description: The unique, numeric id of this team.
type: integer
+ include_public:
+ description: Query parameter controlling whether to include public projects
+ or not
+ type: boolean
+ is_public:
+ description: Defines wether the team should be publicly discoverable when
+ sharing a project
+ type: boolean
members:
description: An array of all members in this team.
items:
@@ -1369,6 +1385,8 @@ definitions:
type: string
motd:
type: string
+ public_teams_enabled:
+ type: boolean
registration_enabled:
type: boolean
task_attachments_enabled:
From 4b4a7f3c0afa2344e72e695918fdad20d97ddb2d Mon Sep 17 00:00:00 2001
From: Elscrux
Date: Sun, 10 Mar 2024 14:12:00 +0000
Subject: [PATCH 02/66] docs: fix broken link in migration docs (#2185)
Seems like one link was broken, this attempts to fix that.
Co-authored-by: Elscrux
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2185
Reviewed-by: konrad
Co-authored-by: Elscrux
Co-committed-by: Elscrux
---
docs/content/doc/setup/migration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/content/doc/setup/migration.md b/docs/content/doc/setup/migration.md
index 1e4bececf..c364d71d6 100644
--- a/docs/content/doc/setup/migration.md
+++ b/docs/content/doc/setup/migration.md
@@ -14,7 +14,7 @@ menu:
There are several importers available for third-party services like Trello, Microsoft To Do or Todoist.
All available migration options can be found [here](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample#L218).
-You can develop migrations for more services, see the [documentation]({{< ref "./development/migration.md">}}) for more info.
+You can develop migrations for more services, see the [documentation]({{< ref "../development/migration.md">}}) for more info.
{{< table_of_contents >}}
From 4bb1d5edfca38da086b51897e1bb0bf0ebd66668 Mon Sep 17 00:00:00 2001
From: waza-ari
Date: Sun, 10 Mar 2024 14:43:04 +0000
Subject: [PATCH 03/66] fix(docs): openid docs whitespace formatting (#2186)
Co-authored-by: Daniel Herrmann
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2186
Reviewed-by: konrad
Co-authored-by: waza-ari
Co-committed-by: waza-ari
---
docs/content/doc/setup/openid.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/content/doc/setup/openid.md b/docs/content/doc/setup/openid.md
index b0e66e69c..4b65b2c08 100644
--- a/docs/content/doc/setup/openid.md
+++ b/docs/content/doc/setup/openid.md
@@ -64,7 +64,7 @@ auth:
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
providers:
- name:
- authurl: <----- Used for OIDC Discovery, usually the issuer
+ authurl: <----- Used for OIDC Discovery, usually the issuer
clientid:
clientsecret:
scope: openid profile email
@@ -116,7 +116,7 @@ The minimal claim structure expected by Vikunja is as follows:
}
```
-It also also possible to pass the description and isPublic flag as optional parameter. If not present, the description will be empty and project visibility defaults to false.
+It is also possible to pass the `description` and the `isPublic` flag as optional parameters. If not present, the description will be empty and project visibility defaults to false.
```json
{
@@ -124,8 +124,8 @@ It also also possible to pass the description and isPublic flag as optional para
{
"name": "team 3",
"oidcID": 33349,
- "description": "My Team Description",
- "isPublic": true
+ "description": "My Team Description",
+ "isPublic": true
},
]
}
From ca0de680ad058e338d2081e2c2d77f864a0dc2fe Mon Sep 17 00:00:00 2001
From: kolaente
Date: Sun, 10 Mar 2024 16:30:01 +0100
Subject: [PATCH 04/66] fix(migration): import card covers when migrating from
Trello
---
go.mod | 2 ++
go.sum | 4 +--
.../migration/create_from_structure.go | 10 ++++++
pkg/modules/migration/trello/trello.go | 35 +++++++++++++++++--
pkg/modules/migration/trello/trello_test.go | 1 +
5 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/go.mod b/go.mod
index 33d34fa71..bcb56100a 100644
--- a/go.mod
+++ b/go.mod
@@ -190,3 +190,5 @@ replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20
go 1.21
toolchain go1.21.2
+
+replace github.com/adlio/trello => github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51
diff --git a/go.sum b/go.sum
index 9cf20eabf..5e6e8ac31 100644
--- a/go.sum
+++ b/go.sum
@@ -24,8 +24,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
-github.com/adlio/trello v1.10.0 h1:ia/rzoBwJJKr4IqnMlrU6n09CVqeyaahSkEVcV5/gPc=
-github.com/adlio/trello v1.10.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
@@ -301,6 +299,8 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
+github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51 h1:R8xiJ/zSWOndiUjG03GmkkIm1O8MDKt2av0SeaIZy/c=
+github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
diff --git a/pkg/modules/migration/create_from_structure.go b/pkg/modules/migration/create_from_structure.go
index 2fc965e9f..66bdeefa1 100644
--- a/pkg/modules/migration/create_from_structure.go
+++ b/pkg/modules/migration/create_from_structure.go
@@ -267,6 +267,8 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
for _, a := range t.Attachments {
// Check if we have a file to create
if len(a.File.FileContent) > 0 {
+ oldID := a.ID
+ a.ID = 0
a.TaskID = t.ID
fr := io.NopCloser(bytes.NewReader(a.File.FileContent))
err = a.NewAttachment(s, fr, a.File.Name, a.File.Size, user)
@@ -274,6 +276,14 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
return
}
log.Debugf("[creating structure] Created new attachment %d", a.ID)
+
+ if t.CoverImageAttachmentID == oldID {
+ t.CoverImageAttachmentID = a.ID
+ err = t.Update(s, user)
+ if err != nil {
+ return
+ }
+ }
}
}
diff --git a/pkg/modules/migration/trello/trello.go b/pkg/modules/migration/trello/trello.go
index 3c44000fc..f1ca9b0ea 100644
--- a/pkg/modules/migration/trello/trello.go
+++ b/pkg/modules/migration/trello/trello.go
@@ -319,18 +319,49 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
return nil, err
}
- task.Attachments = append(task.Attachments, &models.TaskAttachment{
+ vikunjaAttachment := &models.TaskAttachment{
File: &files.File{
Name: attachment.Name,
Mime: attachment.MimeType,
Size: uint64(buf.Len()),
FileContent: buf.Bytes(),
},
- })
+ }
+
+ if card.IDAttachmentCover != "" && card.IDAttachmentCover == attachment.ID {
+ vikunjaAttachment.ID = 42
+ task.CoverImageAttachmentID = 42
+ }
+
+ task.Attachments = append(task.Attachments, vikunjaAttachment)
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
}
+ // When the cover image was set manually, we need to add it as an attachment
+ if card.ManualCoverAttachment && len(card.Cover.Scaled) > 0 {
+
+ cover := card.Cover.Scaled[len(card.Cover.Scaled)-1]
+
+ buf, err := migration.DownloadFile(cover.URL)
+ if err != nil {
+ return nil, err
+ }
+
+ coverAttachment := &models.TaskAttachment{
+ ID: 43,
+ File: &files.File{
+ Name: cover.ID + ".jpg",
+ Mime: "image/jpg", // Seems to always return jpg
+ Size: uint64(buf.Len()),
+ FileContent: buf.Bytes(),
+ },
+ }
+
+ task.Attachments = append(task.Attachments, coverAttachment)
+ task.CoverImageAttachmentID = coverAttachment.ID
+ }
+
project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
}
diff --git a/pkg/modules/migration/trello/trello_test.go b/pkg/modules/migration/trello/trello_test.go
index cb1c59c10..cc7757ca3 100644
--- a/pkg/modules/migration/trello/trello_test.go
+++ b/pkg/modules/migration/trello/trello_test.go
@@ -69,6 +69,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
},
Attachments: []*trello.Attachment{
{
+ ID: "5cc71b16f0c7a57bed3c94e9",
Name: "Testimage.jpg",
IsUpload: true,
MimeType: "image/jpg",
From 22dcedcd7db2a9eaccf430acb4d184f9164b943e Mon Sep 17 00:00:00 2001
From: kolaente
Date: Sun, 10 Mar 2024 18:32:15 +0100
Subject: [PATCH 05/66] fix(filter): correctly replace project title in filter
query
Resolves https://community.vikunja.io/t/filter-option-to-exclude-a-tag-project-etc/1523/6
---
frontend/src/components/project/partials/FilterInput.vue | 6 +++---
frontend/src/components/project/partials/filters.vue | 2 +-
frontend/src/helpers/filters.ts | 1 +
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue
index e4221a1fc..cfb8bd92f 100644
--- a/frontend/src/components/project/partials/FilterInput.vue
+++ b/frontend/src/components/project/partials/FilterInput.vue
@@ -211,9 +211,9 @@ function handleFieldInput() {
function autocompleteSelect(value) {
filterQuery.value = filterQuery.value.substring(0, autocompleteMatchPosition.value + 1) +
- (autocompleteResultType.value === 'labels'
- ? value.title
- : value.username) +
+ (autocompleteResultType.value === 'assignees'
+ ? value.username
+ : value.title) +
filterQuery.value.substring(autocompleteMatchPosition.value + autocompleteMatchText.value.length + 1)
autocompleteResults.value = []
diff --git a/frontend/src/components/project/partials/filters.vue b/frontend/src/components/project/partials/filters.vue
index 7b5b87418..85da6412c 100644
--- a/frontend/src/components/project/partials/filters.vue
+++ b/frontend/src/components/project/partials/filters.vue
@@ -88,7 +88,7 @@ watchDebounced(
val.filter = transformFilterStringFromApi(
val?.filter || '',
labelId => labelStore.getLabelById(labelId)?.title,
- projectId => projectStore.projects.value[projectId]?.title || null,
+ projectId => projectStore.projects[projectId]?.title || null,
)
params.value = val
},
diff --git a/frontend/src/helpers/filters.ts b/frontend/src/helpers/filters.ts
index d8e4b2bd5..87d2b7263 100644
--- a/frontend/src/helpers/filters.ts
+++ b/frontend/src/helpers/filters.ts
@@ -33,6 +33,7 @@ export const AVAILABLE_FILTER_FIELDS = [
...DATE_FIELDS,
...ASSIGNEE_FIELDS,
...LABEL_FIELDS,
+ ...PROJECT_FIELDS,
]
export const FILTER_OPERATORS = [
From 0057ac5836afcbbd62fcba85d3781592d14246f6 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Sun, 10 Mar 2024 18:41:37 +0100
Subject: [PATCH 06/66] fix(migration): only download uploaded attachments
---
pkg/modules/migration/trello/trello.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/modules/migration/trello/trello.go b/pkg/modules/migration/trello/trello.go
index f1ca9b0ea..380ec539c 100644
--- a/pkg/modules/migration/trello/trello.go
+++ b/pkg/modules/migration/trello/trello.go
@@ -305,7 +305,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
log.Debugf("[Trello Migration] Downloading %d card attachments from card %s", len(card.Attachments), card.ID)
}
for _, attachment := range card.Attachments {
- if attachment.MimeType == "" { // Attachments can also be not downloadable - the mime type is empty in that case.
+ if !attachment.IsUpload { // There are other types of attachments which are not files. We can only handle files.
log.Debugf("[Trello Migration] Attachment %s does not have a mime type, not downloading", attachment.ID)
continue
}
From 6c98052176c52f390f878c80323185d8050ab293 Mon Sep 17 00:00:00 2001
From: waza-ari
Date: Sun, 10 Mar 2024 21:42:34 +0000
Subject: [PATCH 07/66] fix(teams): fix duplicate teams being shown when new
public team visibility feature is enabled (#2187)
Due to the `INNER JOIN` on the `team_members` table and the new `OR` conditions allowing teams with the `isPublic` flag set to `true`, teams are returned multiple times. As we're only after the teams, a simple distinct query should fix the issue.
Co-authored-by: Daniel Herrmann
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2187
Co-authored-by: waza-ari
Co-committed-by: waza-ari
---
pkg/models/teams.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/models/teams.go b/pkg/models/teams.go
index 8cae52324..2c911b42e 100644
--- a/pkg/models/teams.go
+++ b/pkg/models/teams.go
@@ -296,7 +296,7 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
limit, start := getLimitFromPageIndex(page, perPage)
all := []*Team{}
- query := s.Select("teams.*").
+ query := s.Distinct("teams.*").
Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id").
Where(db.ILIKE("teams.name", search))
From 12fbde8e8494dd2ed3a8a622d81111cce071d7ab Mon Sep 17 00:00:00 2001
From: renovate
Date: Mon, 11 Mar 2024 05:06:04 +0000
Subject: [PATCH 08/66] chore(deps): update dev-dependencies
---
frontend/package.json | 4 ++--
frontend/pnpm-lock.yaml | 26 +++++++++++++-------------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 38c5eefac..9f247aecc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -153,14 +153,14 @@
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.18",
"browserslist": "4.23.0",
- "caniuse-lite": "1.0.30001596",
+ "caniuse-lite": "1.0.30001597",
"css-has-pseudo": "6.0.2",
"csstype": "3.1.3",
"cypress": "13.6.6",
"esbuild": "0.20.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.22.0",
- "happy-dom": "13.7.1",
+ "happy-dom": "13.7.3",
"histoire": "0.17.9",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index eebce6ce2..31ddb804f 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -313,8 +313,8 @@ devDependencies:
specifier: 4.23.0
version: 4.23.0
caniuse-lite:
- specifier: 1.0.30001596
- version: 1.0.30001596
+ specifier: 1.0.30001597
+ version: 1.0.30001597
css-has-pseudo:
specifier: 6.0.2
version: 6.0.2(postcss@8.4.35)
@@ -334,8 +334,8 @@ devDependencies:
specifier: 9.22.0
version: 9.22.0(eslint@8.57.0)
happy-dom:
- specifier: 13.7.1
- version: 13.7.1
+ specifier: 13.7.3
+ version: 13.7.3
histoire:
specifier: 0.17.9
version: 0.17.9(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)(vite@5.1.5)
@@ -386,7 +386,7 @@ devDependencies:
version: 5.1.0(vue@3.4.21)
vitest:
specifier: 1.3.1
- version: 1.3.1(@types/node@20.11.25)(happy-dom@13.7.1)(sass@1.71.1)(terser@5.24.0)
+ version: 1.3.1(@types/node@20.11.25)(happy-dom@13.7.3)(sass@1.71.1)(terser@5.24.0)
vue-tsc:
specifier: 2.0.6
version: 2.0.6(typescript@5.4.2)
@@ -4548,7 +4548,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.23.0
- caniuse-lite: 1.0.30001596
+ caniuse-lite: 1.0.30001597
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.0
@@ -4704,7 +4704,7 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
- caniuse-lite: 1.0.30001596
+ caniuse-lite: 1.0.30001597
electron-to-chromium: 1.4.685
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.23.0)
@@ -4790,8 +4790,8 @@ packages:
engines: {node: '>=6'}
dev: true
- /caniuse-lite@1.0.30001596:
- resolution: {integrity: sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==}
+ /caniuse-lite@1.0.30001597:
+ resolution: {integrity: sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==}
dev: true
/capital-case@1.0.4:
@@ -6449,8 +6449,8 @@ packages:
strip-bom-string: 1.0.0
dev: true
- /happy-dom@13.7.1:
- resolution: {integrity: sha512-uQgxSTqQY4lMVIhV/W6GWYOT6h7Z6CNlsa+SyvAcOy311spU3zPDNAMzayJky9q4xqfEQf3cQj8yDZngiYUEDA==}
+ /happy-dom@13.7.3:
+ resolution: {integrity: sha512-xMwilTgO34BGEX0TAoM369wwwAy0fK/Jq6BGaRYhSjxLtfQ740nqxHfjFyBqPjCVmKiPUS4npBnMrGLii7eCOg==}
engines: {node: '>=16.0.0'}
dependencies:
entities: 4.5.0
@@ -10079,7 +10079,7 @@ packages:
fsevents: 2.3.3
dev: true
- /vitest@1.3.1(@types/node@20.11.25)(happy-dom@13.7.1)(sass@1.71.1)(terser@5.24.0):
+ /vitest@1.3.1(@types/node@20.11.25)(happy-dom@13.7.3)(sass@1.71.1)(terser@5.24.0):
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -10114,7 +10114,7 @@ packages:
chai: 4.3.10
debug: 4.3.4(supports-color@8.1.1)
execa: 8.0.1
- happy-dom: 13.7.1
+ happy-dom: 13.7.3
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.1
From 3b77fff4c999d2c95788da602b11e7b91189727d Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 14:21:42 +0100
Subject: [PATCH 09/66] fix(project): correctly show the number of tasks and
projects when deleting a project
---
frontend/src/i18n/lang/en.json | 1 +
.../src/views/project/settings/delete.vue | 30 ++++++++++++++-----
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index b7747ecb7..8516f6812 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
diff --git a/frontend/src/views/project/settings/delete.vue b/frontend/src/views/project/settings/delete.vue
index 2ee998672..059099bdb 100644
--- a/frontend/src/views/project/settings/delete.vue
+++ b/frontend/src/views/project/settings/delete.vue
@@ -16,9 +16,7 @@
v-if="totalTasks !== null"
class="has-text-weight-bold"
>
- {{
- totalTasks > 0 ? $t('project.delete.tasksToDelete', {count: totalTasks}) : $t('project.delete.noTasksToDelete')
- }}
+ {{ deleteNotice }}
(null)
const project = computed(() => projectStore.projects[route.params.projectId])
+const childProjectIds = ref([])
watchEffect(
() => {
@@ -58,15 +57,32 @@ watchEffect(
return
}
- const taskCollectionService = new TaskCollectionService()
- taskCollectionService.getAll({projectId: route.params.projectId}).then(() => {
- totalTasks.value = taskCollectionService.totalPages * taskCollectionService.resultCount
+ childProjectIds.value = projectStore.getChildProjects(parseInt(route.params.projectId)).map(p => p.id)
+ if (childProjectIds.value.length === 0) {
+ childProjectIds.value = [parseInt(route.params.projectId)]
+ }
+
+ const taskService = new TaskService()
+ taskService.getAll({}, {filter: `project in '${childProjectIds.value.join(',')}'`}).then(() => {
+ totalTasks.value = taskService.totalPages * taskService.resultCount
})
},
)
useTitle(() => t('project.delete.title', {project: project?.value?.title}))
+const deleteNotice = computed(() => {
+ if(totalTasks.value && totalTasks.value > 0 && childProjectIds.value.length <= 1) {
+ return t('project.delete.tasksToDelete', {count: totalTasks.value})
+ }
+
+ if(totalTasks.value && totalTasks.value > 0 && childProjectIds.value.length > 1) {
+ return t('project.delete.tasksAndChildProjectsToDelete', {tasks: totalTasks.value, projects: childProjectIds.value.length})
+ }
+
+ return t('project.delete.noTasksToDelete')
+})
+
async function deleteProject() {
if (!project.value) {
return
From 3896c680d35382be891f48b64ce0451411336f4e Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 14:36:59 +0100
Subject: [PATCH 10/66] fix(filters): do not require string for in comparator
---
frontend/src/views/project/settings/delete.vue | 2 +-
pkg/models/task_collection_filter.go | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/frontend/src/views/project/settings/delete.vue b/frontend/src/views/project/settings/delete.vue
index 059099bdb..86980a942 100644
--- a/frontend/src/views/project/settings/delete.vue
+++ b/frontend/src/views/project/settings/delete.vue
@@ -63,7 +63,7 @@ watchEffect(
}
const taskService = new TaskService()
- taskService.getAll({}, {filter: `project in '${childProjectIds.value.join(',')}'`}).then(() => {
+ taskService.getAll({}, {filter: `project in ${childProjectIds.value.join(',')}`}).then(() => {
totalTasks.value = taskService.totalPages * taskService.resultCount
})
},
diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go
index 0e69fb2f9..3195fa4df 100644
--- a/pkg/models/task_collection_filter.go
+++ b/pkg/models/task_collection_filter.go
@@ -19,6 +19,7 @@ package models
import (
"fmt"
"reflect"
+ "regexp"
"strconv"
"strings"
"time"
@@ -153,6 +154,18 @@ func getTaskFiltersFromFilterString(filter string) (filters []*taskFilter, err e
filter = strings.ReplaceAll(filter, " in ", " ?= ")
+ // Replaces all occurences with in with a string so that it passes the filter
+ pattern := `\?=\s+([^&|]+)`
+ re := regexp.MustCompile(pattern)
+
+ filter = re.ReplaceAllStringFunc(filter, func(match string) string {
+ value := strings.TrimSpace(strings.TrimPrefix(match, "?="))
+ value = strings.ReplaceAll(value, "'", `\'`)
+ enclosedValue := "'" + value + "'"
+
+ return "?= " + enclosedValue
+ })
+
parsedFilter, err := fexpr.Parse(filter)
if err != nil {
return nil, &ErrInvalidFilterExpression{
@@ -242,7 +255,7 @@ func getValueForField(field reflect.StructField, rawValue string) (value interfa
// In that case we don't really care about what the actual type is, we just cast the value to an
// int64 since we need the id - yes, this assumes we only ever have int64 IDs, but this is fine.
if field.Type.Elem().Kind() == reflect.Ptr {
- value, err = strconv.ParseInt(rawValue, 10, 64)
+ value, err = strconv.ParseInt(strings.TrimSpace(rawValue), 10, 64)
return
}
From 0529f30e770416e18ef575f66bd1be8aeeb18678 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 15:16:39 +0100
Subject: [PATCH 11/66] fix(filters): parse labels and projects correctly when
using `in` filter operator
---
frontend/src/helpers/filters.test.ts | 118 ++++++++++++++++-----------
frontend/src/helpers/filters.ts | 64 +++++++++++----
2 files changed, 118 insertions(+), 64 deletions(-)
diff --git a/frontend/src/helpers/filters.test.ts b/frontend/src/helpers/filters.test.ts
index a1910ff26..6f04f5757 100644
--- a/frontend/src/helpers/filters.test.ts
+++ b/frontend/src/helpers/filters.test.ts
@@ -17,7 +17,7 @@ describe('Filter Transformation', () => {
'assignees': 'assignees',
'labels': 'labels',
}
-
+
describe('For api', () => {
for (const c in fieldCases) {
it('should transform all filter params for ' + c + ' to snake_case', () => {
@@ -37,23 +37,35 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('labels = 1')
})
+ const multipleDummyResolver = (title: string) => {
+ switch (title) {
+ case 'lorem':
+ return 1
+ case 'ipsum':
+ return 2
+ default:
+ return null
+ }
+ }
+
it('should correctly resolve multiple labels', () => {
const transformed = transformFilterStringForApi(
'labels = lorem && dueDate = now && labels = ipsum',
- (title: string) => {
- switch (title) {
- case 'lorem':
- return 1
- case 'ipsum':
- return 2
- default:
- return null
- }
- },
+ multipleDummyResolver,
nullTitleToIdResolver,
)
- expect(transformed).toBe('labels = 1&& due_date = now && labels = 2')
+ expect(transformed).toBe('labels = 1 && due_date = now && labels = 2')
+ })
+
+ it('should correctly resolve multiple labels with an in clause', () => {
+ const transformed = transformFilterStringForApi(
+ 'labels in lorem, ipsum && dueDate = now',
+ multipleDummyResolver,
+ nullTitleToIdResolver,
+ )
+
+ expect(transformed).toBe('labels in 1, 2 && due_date = now')
})
it('should correctly resolve projects', () => {
@@ -70,19 +82,20 @@ describe('Filter Transformation', () => {
const transformed = transformFilterStringForApi(
'project = lorem && dueDate = now || project = ipsum',
nullTitleToIdResolver,
- (title: string) => {
- switch (title) {
- case 'lorem':
- return 1
- case 'ipsum':
- return 2
- default:
- return null
- }
- },
+ multipleDummyResolver,
)
- expect(transformed).toBe('project = 1&& due_date = now || project = 2')
+ expect(transformed).toBe('project = 1 && due_date = now || project = 2')
+ })
+
+ it('should correctly resolve multiple projects with in', () => {
+ const transformed = transformFilterStringForApi(
+ 'project in lorem, ipsum',
+ nullTitleToIdResolver,
+ multipleDummyResolver,
+ )
+
+ expect(transformed).toBe('project in 1, 2')
})
})
@@ -104,24 +117,36 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('labels = lorem')
})
-
+
+ const multipleIdToTitleResolver = (id: number) => {
+ switch (id) {
+ case 1:
+ return 'lorem'
+ case 2:
+ return 'ipsum'
+ default:
+ return null
+ }
+ }
+
it('should correctly resolve multiple labels', () => {
const transformed = transformFilterStringFromApi(
'labels = 1 && due_date = now && labels = 2',
- (id: number) => {
- switch (id) {
- case 1:
- return 'lorem'
- case 2:
- return 'ipsum'
- default:
- return null
- }
- },
+ multipleIdToTitleResolver,
nullIdToTitleResolver,
)
- expect(transformed).toBe('labels = lorem&& dueDate = now && labels = ipsum')
+ expect(transformed).toBe('labels = lorem && dueDate = now && labels = ipsum')
+ })
+
+ it('should correctly resolve multiple labels in', () => {
+ const transformed = transformFilterStringFromApi(
+ 'labels in 1, 2',
+ multipleIdToTitleResolver,
+ nullIdToTitleResolver,
+ )
+
+ expect(transformed).toBe('labels in lorem, ipsum')
})
it('should correctly resolve projects', () => {
@@ -136,21 +161,22 @@ describe('Filter Transformation', () => {
it('should correctly resolve multiple projects', () => {
const transformed = transformFilterStringFromApi(
- 'project = lorem && due_date = now || project = ipsum',
+ 'project = 1 && due_date = now || project = 2',
nullIdToTitleResolver,
- (id: number) => {
- switch (id) {
- case 1:
- return 'lorem'
- case 2:
- return 'ipsum'
- default:
- return null
- }
- },
+ multipleIdToTitleResolver,
)
expect(transformed).toBe('project = lorem && dueDate = now || project = ipsum')
})
+
+ it('should correctly resolve multiple projects in', () => {
+ const transformed = transformFilterStringFromApi(
+ 'project in 1, 2',
+ nullIdToTitleResolver,
+ multipleIdToTitleResolver,
+ )
+
+ expect(transformed).toBe('project in lorem, ipsum')
+ })
})
})
diff --git a/frontend/src/helpers/filters.ts b/frontend/src/helpers/filters.ts
index 87d2b7263..9f720e5d9 100644
--- a/frontend/src/helpers/filters.ts
+++ b/frontend/src/helpers/filters.ts
@@ -55,7 +55,7 @@ export const FILTER_JOIN_OPERATOR = [
')',
]
-export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=)'
+export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=|in)'
function getFieldPattern(field: string): RegExp {
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
@@ -66,11 +66,11 @@ export function transformFilterStringForApi(
labelResolver: (title: string) => number | null,
projectResolver: (title: string) => number | null,
): string {
-
+
if (filter.trim() === '') {
return ''
}
-
+
// Transform labels to ids
LABEL_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
@@ -80,10 +80,17 @@ export function transformFilterStringForApi(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
- const labelId = labelResolver(keyword.trim())
- if (labelId !== null) {
- filter = filter.replace(keyword, String(labelId))
+ let keywords = [keyword.trim()]
+ if (operator === 'in' || operator === '?=') {
+ keywords = keyword.trim().split(',').map(k => k.trim())
}
+
+ keywords.forEach(k => {
+ const labelId = labelResolver(k)
+ if (labelId !== null) {
+ filter = filter.replace(k, String(labelId))
+ }
+ })
}
}
})
@@ -96,10 +103,17 @@ export function transformFilterStringForApi(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
- const projectId = projectResolver(keyword.trim())
- if (projectId !== null) {
- filter = filter.replace(keyword, String(projectId))
+ let keywords = [keyword.trim()]
+ if (operator === 'in' || operator === '?=') {
+ keywords = keyword.trim().split(',').map(k => k.trim())
}
+
+ keywords.forEach(k => {
+ const projectId = projectResolver(k)
+ if (projectId !== null) {
+ filter = filter.replace(k, String(projectId))
+ }
+ })
}
}
})
@@ -117,16 +131,16 @@ export function transformFilterStringFromApi(
labelResolver: (id: number) => string | null,
projectResolver: (id: number) => string | null,
): string {
-
+
if (filter.trim() === '') {
return ''
}
-
+
// Transform all attributes from snake case
AVAILABLE_FILTER_FIELDS.forEach(f => {
filter = filter.replace(snakeCase(f), f)
})
-
+
// Transform labels to their titles
LABEL_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
@@ -136,10 +150,17 @@ export function transformFilterStringFromApi(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
- const labelTitle = labelResolver(Number(keyword.trim()))
- if (labelTitle !== null) {
- filter = filter.replace(keyword, labelTitle)
+ let keywords = [keyword.trim()]
+ if (operator === 'in' || operator === '?=') {
+ keywords = keyword.trim().split(',').map(k => k.trim())
}
+
+ keywords.forEach(k => {
+ const labelTitle = labelResolver(parseInt(k))
+ if (labelTitle !== null) {
+ filter = filter.replace(k, labelTitle)
+ }
+ })
}
}
})
@@ -153,10 +174,17 @@ export function transformFilterStringFromApi(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
- const project = projectResolver(Number(keyword.trim()))
- if (project !== null) {
- filter = filter.replace(keyword, project)
+ let keywords = [keyword.trim()]
+ if (operator === 'in' || operator === '?=') {
+ keywords = keyword.trim().split(',').map(k => k.trim())
}
+
+ keywords.forEach(k => {
+ const project = projectResolver(parseInt(k))
+ if (project !== null) {
+ filter = filter.replace(k, project)
+ }
+ })
}
}
})
From dbfe162cd233b8ddd16317ac065a54e6c393afb3 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 15:41:06 +0100
Subject: [PATCH 12/66] fix(filters): label highlighting and autocomplete
fields now work with in operator
Previously, when creating a filter query with the 'in' operator and multiple values, autocompletion and highlighting was not available. This change now implements a split for each value, seperated by a comma.
---
docs/content/doc/usage/filters.md | 2 +-
.../project/partials/FilterInput.vue | 35 +++++++++++++------
frontend/src/helpers/filters.ts | 10 +++---
frontend/src/i18n/lang/en.json | 2 +-
frontend/src/stores/labels.ts | 7 ++++
5 files changed, 39 insertions(+), 17 deletions(-)
diff --git a/docs/content/doc/usage/filters.md b/docs/content/doc/usage/filters.md
index b7885efc9..2a841469a 100644
--- a/docs/content/doc/usage/filters.md
+++ b/docs/content/doc/usage/filters.md
@@ -46,7 +46,7 @@ The available operators for filtering include:
* `<`: Less than
* `<=`: Less than or equal to
* `like`: Matches a pattern (using wildcard `%`)
-* `in`: Matches any value in a list
+* `in`: Matches any value in a comma-seperated list of values
To combine multiple conditions, you can use the following logical operators:
diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue
index cfb8bd92f..e02eb6838 100644
--- a/frontend/src/components/project/partials/FilterInput.vue
+++ b/frontend/src/components/project/partials/FilterInput.vue
@@ -16,7 +16,7 @@ import {
AVAILABLE_FILTER_FIELDS,
FILTER_JOIN_OPERATOR,
FILTER_OPERATORS,
- FILTER_OPERATORS_REGEX, LABEL_FIELDS,
+ FILTER_OPERATORS_REGEX, LABEL_FIELDS, getFilterFieldRegexPattern,
} from '@/helpers/filters'
const {
@@ -104,15 +104,25 @@ const highlightedFilterQuery = computed(() => {
})
LABEL_FIELDS
.forEach(f => {
- const pattern = new RegExp(f + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig')
- highlighted = highlighted.replaceAll(pattern, (match, token, start, value) => {
+ const pattern = getFilterFieldRegexPattern(f)
+ highlighted = highlighted.replaceAll(pattern, (match, prefix, operator, space, value) => {
+
if (typeof value === 'undefined') {
value = ''
}
+
+ let labelTitles = [value]
+ if(operator === 'in' || operator === '?=') {
+ labelTitles = value.split(',').map(v => v.trim())
+ }
- const label = labelStore.getLabelsByExactTitles([value])[0] || undefined
+ const labelsHtml: string[] = []
+ labelTitles.forEach(t => {
+ const label = labelStore.getLabelByExactTitle(t) || undefined
+ labelsHtml.push(`${label?.title ?? t} `)
+ })
- return `${f} ${token} ${label?.title ?? value}`
+ return `${f} ${operator} ${labelsHtml.join(', ')}`
})
})
FILTER_OPERATORS
@@ -184,26 +194,31 @@ function handleFieldInput() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
+ let search = keyword
+ if(operator === 'in' || operator === '?=') {
+ const keywords = keyword.split(',')
+ search = keywords[keywords.length - 1].trim()
+ }
if (matched.startsWith('label')) {
autocompleteResultType.value = 'labels'
- autocompleteResults.value = labelStore.filterLabelsByQuery([], keyword)
+ autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
}
if (matched.startsWith('assignee')) {
autocompleteResultType.value = 'assignees'
if (projectId) {
- projectUserService.getAll({projectId}, {s: keyword})
+ projectUserService.getAll({projectId}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
} else {
- userService.getAll({}, {s: keyword})
+ userService.getAll({}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
}
}
if (!projectId && matched.startsWith('project')) {
autocompleteResultType.value = 'projects'
- autocompleteResults.value = projectStore.searchProject(keyword)
+ autocompleteResults.value = projectStore.searchProject(search)
}
autocompleteMatchText.value = keyword
- autocompleteMatchPosition.value = prefix.length - 1
+ autocompleteMatchPosition.value = prefix.length - 1 + keyword.replace(search, '').length
}
}
})
diff --git a/frontend/src/helpers/filters.ts b/frontend/src/helpers/filters.ts
index 9f720e5d9..de10f57f6 100644
--- a/frontend/src/helpers/filters.ts
+++ b/frontend/src/helpers/filters.ts
@@ -57,7 +57,7 @@ export const FILTER_JOIN_OPERATOR = [
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=|in)'
-function getFieldPattern(field: string): RegExp {
+export function getFilterFieldRegexPattern(field: string): RegExp {
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
}
@@ -73,7 +73,7 @@ export function transformFilterStringForApi(
// Transform labels to ids
LABEL_FIELDS.forEach(field => {
- const pattern = getFieldPattern(field)
+ const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@@ -96,7 +96,7 @@ export function transformFilterStringForApi(
})
// Transform projects to ids
PROJECT_FIELDS.forEach(field => {
- const pattern = getFieldPattern(field)
+ const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@@ -143,7 +143,7 @@ export function transformFilterStringFromApi(
// Transform labels to their titles
LABEL_FIELDS.forEach(field => {
- const pattern = getFieldPattern(field)
+ const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@@ -167,7 +167,7 @@ export function transformFilterStringFromApi(
// Transform projects to ids
PROJECT_FIELDS.forEach(field => {
- const pattern = getFieldPattern(field)
+ const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 8516f6812..158ef586f 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -446,7 +446,7 @@
"lessThan": "Less than",
"lessThanOrEqual": "Less than or equal to",
"like": "Matches a pattern (using wildcard %)",
- "in": "Matches any value in a list"
+ "in": "Matches any value in a comma-seperated list of values"
},
"logicalOperators": {
"intro": "To combine multiple conditions, you can use the following logical operators:",
diff --git a/frontend/src/stores/labels.ts b/frontend/src/stores/labels.ts
index 5d8c48221..9c3d959b0 100644
--- a/frontend/src/stores/labels.ts
+++ b/frontend/src/stores/labels.ts
@@ -57,6 +57,12 @@ export const useLabelStore = defineStore('label', () => {
.values(labels.value)
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
})
+
+ const getLabelByExactTitle = computed(() => {
+ return (labelTitle: string) => Object
+ .values(labels.value)
+ .find(l => l.title.toLowerCase() === labelTitle.toLowerCase())
+ })
function setIsLoading(newIsLoading: boolean) {
@@ -145,6 +151,7 @@ export const useLabelStore = defineStore('label', () => {
getLabelById,
filterLabelsByQuery,
getLabelsByExactTitles,
+ getLabelByExactTitle,
setLabels,
setLabel,
From 6fc3d1e98fe28d7e561a4ebe1d00938f8346fae1 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 15:42:09 +0100
Subject: [PATCH 13/66] fix: lint
---
pkg/models/task_collection_filter.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go
index 3195fa4df..560094bf9 100644
--- a/pkg/models/task_collection_filter.go
+++ b/pkg/models/task_collection_filter.go
@@ -154,7 +154,7 @@ func getTaskFiltersFromFilterString(filter string) (filters []*taskFilter, err e
filter = strings.ReplaceAll(filter, " in ", " ?= ")
- // Replaces all occurences with in with a string so that it passes the filter
+ // Replaces all occurrences with in with a string so that it passes the filter
pattern := `\?=\s+([^&|]+)`
re := regexp.MustCompile(pattern)
From a66e26678ece858cc13515e160acbd5261371dbc Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 16:13:42 +0100
Subject: [PATCH 14/66] feat(filters): pass timezone down when filtering with
relative date math
Resolves https://community.vikunja.io/t/my-vikunja-instance-creates-tasks-with-due-date-time-of-9am-for-tasks-with-the-word-today-word-in-it/2105/8
---
frontend/src/composables/useTaskList.ts | 10 ++++--
frontend/src/services/taskCollection.ts | 2 ++
frontend/src/stores/kanban.ts | 11 +++++--
frontend/src/stores/tasks.ts | 8 +++--
.../views/project/helpers/useGanttTaskList.ts | 7 ++++
frontend/src/views/tasks/ShowTasks.vue | 2 ++
pkg/models/kanban.go | 3 +-
pkg/models/task_collection.go | 7 +++-
pkg/models/task_collection_filter.go | 33 +++++++++++++------
pkg/models/tasks.go | 1 +
10 files changed, 65 insertions(+), 19 deletions(-)
diff --git a/frontend/src/composables/useTaskList.ts b/frontend/src/composables/useTaskList.ts
index 51e874c4d..7b05375e8 100644
--- a/frontend/src/composables/useTaskList.ts
+++ b/frontend/src/composables/useTaskList.ts
@@ -6,6 +6,7 @@ import TaskCollectionService, {getDefaultTaskFilterParams} from '@/services/task
import type {ITask} from '@/modelTypes/ITask'
import {error} from '@/message'
import type {IProject} from '@/modelTypes/IProject'
+import {useAuthStore} from '@/stores/auth'
export type Order = 'asc' | 'desc' | 'none'
@@ -81,11 +82,16 @@ export function useTaskList(projectIdGetter: ComputedGetter, sor
page.value = 1
},
)
-
+
+ const authStore = useAuthStore()
+
const getAllTasksParams = computed(() => {
return [
{projectId: projectId.value},
- allParams.value,
+ {
+ ...allParams.value,
+ filter_timezone: authStore.settings.timezone,
+ },
page.value,
]
})
diff --git a/frontend/src/services/taskCollection.ts b/frontend/src/services/taskCollection.ts
index 0c8a13f06..965ad05b3 100644
--- a/frontend/src/services/taskCollection.ts
+++ b/frontend/src/services/taskCollection.ts
@@ -8,6 +8,7 @@ export interface TaskFilterParams {
order_by: ('asc' | 'desc')[],
filter: string,
filter_include_nulls: boolean,
+ filter_timezone: string,
s: string,
}
@@ -17,6 +18,7 @@ export function getDefaultTaskFilterParams(): TaskFilterParams {
order_by: ['asc', 'desc'],
filter: '',
filter_include_nulls: false,
+ filter_timezone: '',
s: '',
}
}
diff --git a/frontend/src/stores/kanban.ts b/frontend/src/stores/kanban.ts
index 94c626748..6c0e7b044 100644
--- a/frontend/src/stores/kanban.ts
+++ b/frontend/src/stores/kanban.ts
@@ -7,13 +7,14 @@ import {i18n} from '@/i18n'
import {success} from '@/message'
import BucketService from '@/services/bucket'
-import TaskCollectionService from '@/services/taskCollection'
+import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection'
import {setModuleLoading} from '@/stores/helper'
import type {ITask} from '@/modelTypes/ITask'
import type {IProject} from '@/modelTypes/IProject'
import type {IBucket} from '@/modelTypes/IBucket'
+import {useAuthStore} from '@/stores/auth'
const TASKS_PER_BUCKET = 25
@@ -44,6 +45,8 @@ const addTaskToBucketAndSort = (buckets: IBucket[], task: ITask) => {
* It should hold only the current buckets.
*/
export const useKanbanStore = defineStore('kanban', () => {
+ const authStore = useAuthStore()
+
const buckets = ref([])
const projectId = ref(0)
const bucketLoading = ref<{[id: IBucket['id']]: boolean}>({})
@@ -247,7 +250,7 @@ export const useKanbanStore = defineStore('kanban', () => {
async function loadNextTasksForBucket(
projectId: IProject['id'],
- ps,
+ ps: TaskFilterParams,
bucketId: IBucket['id'],
) {
const isLoading = bucketLoading.value[bucketId] ?? false
@@ -265,7 +268,7 @@ export const useKanbanStore = defineStore('kanban', () => {
const cancel = setModuleLoading(setIsLoading)
setBucketLoading({bucketId: bucketId, loading: true})
- const params = JSON.parse(JSON.stringify(ps))
+ const params: TaskFilterParams = JSON.parse(JSON.stringify(ps))
params.sort_by = 'kanban_position'
params.order_by = 'asc'
@@ -286,6 +289,8 @@ export const useKanbanStore = defineStore('kanban', () => {
params.filter_value = [...(params.filter_value ?? []), bucketId]
params.filter_comparator = [...(params.filter_comparator ?? []), 'equals']
}
+
+ params.filter_timezone = authStore.settings.timezone
params.per_page = TASKS_PER_BUCKET
diff --git a/frontend/src/stores/tasks.ts b/frontend/src/stores/tasks.ts
index b57213766..9cf3ab3e5 100644
--- a/frontend/src/stores/tasks.ts
+++ b/frontend/src/stores/tasks.ts
@@ -28,7 +28,7 @@ import {useKanbanStore} from '@/stores/kanban'
import {useBaseStore} from '@/stores/base'
import ProjectUserService from '@/services/projectUsers'
import {useAuthStore} from '@/stores/auth'
-import TaskCollectionService from '@/services/taskCollection'
+import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection'
import {getRandomColorHex} from '@/helpers/color/randomColor'
interface MatchedAssignee extends IUser {
@@ -124,7 +124,11 @@ export const useTaskStore = defineStore('task', () => {
})
}
- async function loadTasks(params, projectId: IProject['id'] | null = null) {
+ async function loadTasks(params: TaskFilterParams, projectId: IProject['id'] | null = null) {
+
+ if (params.filter_timezone === '') {
+ params.filter_timezone = authStore.settings.timezone
+ }
const cancel = setModuleLoading(setIsLoading)
try {
diff --git a/frontend/src/views/project/helpers/useGanttTaskList.ts b/frontend/src/views/project/helpers/useGanttTaskList.ts
index 7f094252c..f2a76c8d6 100644
--- a/frontend/src/views/project/helpers/useGanttTaskList.ts
+++ b/frontend/src/views/project/helpers/useGanttTaskList.ts
@@ -9,6 +9,7 @@ import TaskService from '@/services/task'
import TaskModel from '@/models/task'
import {error, success} from '@/message'
+import {useAuthStore} from '@/stores/auth'
// FIXME: unify with general `useTaskList`
export function useGanttTaskList(
@@ -21,12 +22,18 @@ export function useGanttTaskList(
}) {
const taskCollectionService = shallowReactive(new TaskCollectionService())
const taskService = shallowReactive(new TaskService())
+ const authStore = useAuthStore()
const isLoading = computed(() => taskCollectionService.loading)
const tasks = ref>(new Map())
async function fetchTasks(params: TaskFilterParams, page = 1): Promise {
+
+ if(params.filter_timezone === '') {
+ params.filter_timezone = authStore.settings.timezone
+ }
+
const tasks = await taskCollectionService.getAll({projectId: filters.value.projectId}, params, page) as ITask[]
if (options.loadAll && page < taskCollectionService.totalPages) {
const nextTasks = await fetchTasks(params, page + 1)
diff --git a/frontend/src/views/tasks/ShowTasks.vue b/frontend/src/views/tasks/ShowTasks.vue
index 0d2d747ea..899edd04c 100644
--- a/frontend/src/views/tasks/ShowTasks.vue
+++ b/frontend/src/views/tasks/ShowTasks.vue
@@ -85,6 +85,7 @@ import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
import {useProjectStore} from '@/stores/projects'
+import type {TaskFilterParams} from '@/services/taskCollection'
// Linting disabled because we explicitely enabled destructuring in vite's config, this will work.
// eslint-disable-next-line vue/no-setup-props-destructure
@@ -184,6 +185,7 @@ async function loadPendingTasks(from: string, to: string) {
const params = {
sortBy: ['due_date', 'id'],
orderBy: ['asc', 'desc'],
+ filterTimezone: authStore.settings.timezone,
filterBy: ['done'],
filterValue: ['false'],
filterComparator: ['equals'],
diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go
index 24a307c44..597420a97 100644
--- a/pkg/models/kanban.go
+++ b/pkg/models/kanban.go
@@ -109,6 +109,7 @@ func getDefaultBucketID(s *xorm.Session, project *Project) (bucketID int64, err
// @Param per_page query int false "The maximum number of tasks per bucket per page. This parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text."
// @Param filter query string false "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature."
+// @Param filter_timezone query string false "The time zone which should be used for date match (statements like "now" resolve to different actual times)"
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
// @Success 200 {array} models.Bucket "The buckets with their tasks"
// @Failure 500 {object} models.Message "Internal server error"
@@ -197,7 +198,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
} else {
filterString = "(" + originalFilter + ") && bucket_id = " + strconv.FormatInt(id, 10)
}
- opts.parsedFilters, err = getTaskFiltersFromFilterString(filterString)
+ opts.parsedFilters, err = getTaskFiltersFromFilterString(filterString, opts.filterTimezone)
if err != nil {
return
}
diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go
index bb844086e..4c8160b72 100644
--- a/pkg/models/task_collection.go
+++ b/pkg/models/task_collection.go
@@ -33,7 +33,10 @@ type TaskCollection struct {
OrderBy []string `query:"order_by" json:"order_by"`
OrderByArr []string `query:"order_by[]" json:"-"`
+ // The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature.
Filter string `query:"filter" json:"filter"`
+ // The time zone which should be used for date match (statements like "now" resolve to different actual times)
+ FilterTimezone string `query:"filter_timezone" json:"filter_timezone"`
// If set to true, the result will also include null values
FilterIncludeNulls bool `query:"filter_include_nulls" json:"filter_include_nulls"`
@@ -103,9 +106,10 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskSearchOption
sortby: sort,
filterIncludeNulls: tf.FilterIncludeNulls,
filter: tf.Filter,
+ filterTimezone: tf.FilterTimezone,
}
- opts.parsedFilters, err = getTaskFiltersFromFilterString(tf.Filter)
+ opts.parsedFilters, err = getTaskFiltersFromFilterString(tf.Filter, tf.FilterTimezone)
return opts, err
}
@@ -122,6 +126,7 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskSearchOption
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `project_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
// @Param filter query string false "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature."
+// @Param filter_timezone query string false "The time zone which should be used for date match (statements like "now" resolve to different actual times)"
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
// @Security JWTKeyAuth
// @Success 200 {array} models.Task "The tasks"
diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go
index 560094bf9..d0885ff4d 100644
--- a/pkg/models/task_collection_filter.go
+++ b/pkg/models/task_collection_filter.go
@@ -92,7 +92,7 @@ func parseTimeFromUserInput(timeString string) (value time.Time, err error) {
return value.In(config.GetTimeZone()), err
}
-func parseFilterFromExpression(f fexpr.ExprGroup) (filter *taskFilter, err error) {
+func parseFilterFromExpression(f fexpr.ExprGroup, loc *time.Location) (filter *taskFilter, err error) {
filter = &taskFilter{
join: filterConcatAnd,
}
@@ -112,7 +112,7 @@ func parseFilterFromExpression(f fexpr.ExprGroup) (filter *taskFilter, err error
case []fexpr.ExprGroup:
values := make([]*taskFilter, 0, len(v))
for _, expression := range v {
- subfilter, err := parseFilterFromExpression(expression)
+ subfilter, err := parseFilterFromExpression(expression, loc)
if err != nil {
return nil, err
}
@@ -132,7 +132,7 @@ func parseFilterFromExpression(f fexpr.ExprGroup) (filter *taskFilter, err error
if filter.field == "project" {
filter.field = "project_id"
}
- reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, value)
+ reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, value, loc)
if err != nil {
return nil, ErrInvalidTaskFilterValue{
Value: filter.field,
@@ -146,7 +146,7 @@ func parseFilterFromExpression(f fexpr.ExprGroup) (filter *taskFilter, err error
return filter, nil
}
-func getTaskFiltersFromFilterString(filter string) (filters []*taskFilter, err error) {
+func getTaskFiltersFromFilterString(filter string, filterTimezone string) (filters []*taskFilter, err error) {
if filter == "" {
return
@@ -174,9 +174,17 @@ func getTaskFiltersFromFilterString(filter string) (filters []*taskFilter, err e
}
}
+ var loc *time.Location
+ if filterTimezone != "" {
+ loc, err = time.LoadLocation(filterTimezone)
+ if err != nil {
+ return
+ }
+ }
+
filters = make([]*taskFilter, 0, len(parsedFilter))
for _, f := range parsedFilter {
- parsedFilter, err := parseFilterFromExpression(f)
+ parsedFilter, err := parseFilterFromExpression(f, loc)
if err != nil {
return nil, err
}
@@ -230,7 +238,12 @@ func getFilterComparatorFromOp(op fexpr.SignOp) (taskFilterComparator, error) {
}
}
-func getValueForField(field reflect.StructField, rawValue string) (value interface{}, err error) {
+func getValueForField(field reflect.StructField, rawValue string, loc *time.Location) (value interface{}, err error) {
+
+ if loc == nil {
+ loc = config.GetTimeZone()
+ }
+
switch field.Type.Kind() {
case reflect.Int64:
value, err = strconv.ParseInt(rawValue, 10, 64)
@@ -245,7 +258,7 @@ func getValueForField(field reflect.StructField, rawValue string) (value interfa
var t datemath.Expression
t, err = datemath.Parse(rawValue)
if err == nil {
- value = t.Time(datemath.WithLocation(config.GetTimeZone()))
+ value = t.Time(datemath.WithLocation(config.GetTimeZone())).In(loc)
} else {
value, err = parseTimeFromUserInput(rawValue)
}
@@ -273,7 +286,7 @@ func getValueForField(field reflect.StructField, rawValue string) (value interfa
return
}
-func getNativeValueForTaskField(fieldName string, comparator taskFilterComparator, value string) (reflectField *reflect.StructField, nativeValue interface{}, err error) {
+func getNativeValueForTaskField(fieldName string, comparator taskFilterComparator, value string, loc *time.Location) (reflectField *reflect.StructField, nativeValue interface{}, err error) {
realFieldName := strings.ReplaceAll(strcase.ToCamel(fieldName), "Id", "ID")
@@ -299,7 +312,7 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
vals := strings.Split(value, ",")
valueSlice := []interface{}{}
for _, val := range vals {
- v, err := getValueForField(field, val)
+ v, err := getValueForField(field, val, loc)
if err != nil {
return nil, nil, err
}
@@ -308,6 +321,6 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
return nil, valueSlice, nil
}
- val, err := getValueForField(field, value)
+ val, err := getValueForField(field, value, loc)
return &field, val, err
}
diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go
index c00ce0d00..2f331d482 100644
--- a/pkg/models/tasks.go
+++ b/pkg/models/tasks.go
@@ -174,6 +174,7 @@ type taskSearchOptions struct {
parsedFilters []*taskFilter
filterIncludeNulls bool
filter string
+ filterTimezone string
projectIDs []int64
}
From e09772181766f3340c228801d9e1ad253376fa82 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 16:39:27 +0100
Subject: [PATCH 15/66] fix(tasks): use correct filter query when filtering
---
.../quick-actions/quick-actions.vue | 43 ++--------
frontend/src/composables/useTaskList.ts | 4 +-
frontend/src/services/taskCollection.ts | 5 +-
frontend/src/stores/kanban.ts | 84 ++++++++-----------
frontend/src/stores/tasks.ts | 2 +-
frontend/src/views/project/ProjectTable.vue | 4 +-
frontend/src/views/tasks/ShowTasks.vue | 22 ++---
7 files changed, 52 insertions(+), 112 deletions(-)
diff --git a/frontend/src/components/quick-actions/quick-actions.vue b/frontend/src/components/quick-actions/quick-actions.vue
index 34f8ec947..46e47dd80 100644
--- a/frontend/src/components/quick-actions/quick-actions.vue
+++ b/frontend/src/components/quick-actions/quick-actions.vue
@@ -350,26 +350,6 @@ const isNewTaskCommand = computed(() => (
const taskSearchTimeout = ref | null>(null)
-type Filter = { by: string, value: string | number, comparator: string }
-
-function filtersToParams(filters: Filter[]) {
- const filter_by: Filter['by'][] = []
- const filter_value: Filter['value'][] = []
- const filter_comparator: Filter['comparator'][] = []
-
- filters.forEach(({by, value, comparator}) => {
- filter_by.push(by)
- filter_value.push(value)
- filter_comparator.push(comparator)
- })
-
- return {
- filter_by,
- filter_value,
- filter_comparator,
- }
-}
-
function searchTasks() {
if (
searchMode.value !== SEARCH_MODE.ALL &&
@@ -391,40 +371,27 @@ function searchTasks() {
const {text, project: projectName, labels} = parsedQuery.value
- const filters: Filter[] = []
-
- // FIXME: improve types
- function addFilter(
- by: Filter['by'],
- value: Filter['value'],
- comparator: Filter['comparator'],
- ) {
- filters.push({
- by,
- value,
- comparator,
- })
- }
-
+ let filter = ''
+
if (projectName !== null) {
const project = projectStore.findProjectByExactname(projectName)
console.log({project})
if (project !== null) {
- addFilter('project_id', project.id, 'equals')
+ filter += ' project = ' + project.id
}
}
if (labels.length > 0) {
const labelIds = labelStore.getLabelsByExactTitles(labels).map((l) => l.id)
if (labelIds.length > 0) {
- addFilter('labels', labelIds.join(), 'in')
+ filter += 'labels in ' + labelIds.join(', ')
}
}
const params = {
s: text,
sort_by: 'done',
- ...filtersToParams(filters),
+ filter,
}
taskSearchTimeout.value = setTimeout(async () => {
diff --git a/frontend/src/composables/useTaskList.ts b/frontend/src/composables/useTaskList.ts
index 7b05375e8..ef048dc7a 100644
--- a/frontend/src/composables/useTaskList.ts
+++ b/frontend/src/composables/useTaskList.ts
@@ -2,7 +2,7 @@ import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue'
import {useRoute} from 'vue-router'
import {useRouteQuery} from '@vueuse/router'
-import TaskCollectionService, {getDefaultTaskFilterParams} from '@/services/taskCollection'
+import TaskCollectionService, {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
import type {ITask} from '@/modelTypes/ITask'
import {error} from '@/message'
import type {IProject} from '@/modelTypes/IProject'
@@ -58,7 +58,7 @@ export function useTaskList(projectIdGetter: ComputedGetter, sor
const projectId = computed(() => projectIdGetter())
- const params = ref({...getDefaultTaskFilterParams()})
+ const params = ref({...getDefaultTaskFilterParams()})
const search = ref('')
const page = useRouteQuery('page', '1', { transform: Number })
diff --git a/frontend/src/services/taskCollection.ts b/frontend/src/services/taskCollection.ts
index 965ad05b3..2cc39773f 100644
--- a/frontend/src/services/taskCollection.ts
+++ b/frontend/src/services/taskCollection.ts
@@ -4,12 +4,13 @@ import TaskModel from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
export interface TaskFilterParams {
- sort_by: ('start_date' | 'done' | 'id' | 'position')[],
+ sort_by: ('start_date' | 'done' | 'id' | 'position' | 'kanban_position')[],
order_by: ('asc' | 'desc')[],
filter: string,
filter_include_nulls: boolean,
- filter_timezone: string,
+ filter_timezone?: string,
s: string,
+ per_page?: number,
}
export function getDefaultTaskFilterParams(): TaskFilterParams {
diff --git a/frontend/src/stores/kanban.ts b/frontend/src/stores/kanban.ts
index 6c0e7b044..997cdeac2 100644
--- a/frontend/src/stores/kanban.ts
+++ b/frontend/src/stores/kanban.ts
@@ -1,5 +1,5 @@
import {computed, readonly, ref} from 'vue'
-import {defineStore, acceptHMRUpdate} from 'pinia'
+import {acceptHMRUpdate, defineStore} from 'pinia'
import {klona} from 'klona/lite'
import {findById, findIndexById} from '@/helpers/utils'
@@ -20,7 +20,7 @@ const TASKS_PER_BUCKET = 25
function getTaskIndicesById(buckets: IBucket[], taskId: ITask['id']) {
let taskIndex
- const bucketIndex = buckets.findIndex(({ tasks }) => {
+ const bucketIndex = buckets.findIndex(({tasks}) => {
taskIndex = findIndexById(tasks, taskId)
return taskIndex !== -1
})
@@ -28,12 +28,12 @@ function getTaskIndicesById(buckets: IBucket[], taskId: ITask['id']) {
return {
bucketIndex: bucketIndex !== -1 ? bucketIndex : null,
taskIndex: taskIndex !== -1 ? taskIndex : null,
- }
+ }
}
const addTaskToBucketAndSort = (buckets: IBucket[], task: ITask) => {
const bucketIndex = findIndexById(buckets, task.bucketId)
- if(typeof buckets[bucketIndex] === 'undefined') {
+ if (typeof buckets[bucketIndex] === 'undefined') {
return
}
buckets[bucketIndex].tasks.push(task)
@@ -46,19 +46,19 @@ const addTaskToBucketAndSort = (buckets: IBucket[], task: ITask) => {
*/
export const useKanbanStore = defineStore('kanban', () => {
const authStore = useAuthStore()
-
+
const buckets = ref([])
const projectId = ref(0)
- const bucketLoading = ref<{[id: IBucket['id']]: boolean}>({})
- const taskPagesPerBucket = ref<{[id: IBucket['id']]: number}>({})
- const allTasksLoadedForBucket = ref<{[id: IBucket['id']]: boolean}>({})
+ const bucketLoading = ref<{ [id: IBucket['id']]: boolean }>({})
+ const taskPagesPerBucket = ref<{ [id: IBucket['id']]: number }>({})
+ const allTasksLoadedForBucket = ref<{ [id: IBucket['id']]: boolean }>({})
const isLoading = ref(false)
const getBucketById = computed(() => (bucketId: IBucket['id']): IBucket | undefined => findById(buckets.value, bucketId))
const getTaskById = computed(() => {
return (id: ITask['id']) => {
- const { bucketIndex, taskIndex } = getTaskIndicesById(buckets.value, id)
-
+ const {bucketIndex, taskIndex} = getTaskIndicesById(buckets.value, id)
+
return {
bucketIndex,
taskIndex,
@@ -98,9 +98,9 @@ export const useKanbanStore = defineStore('kanban', () => {
}
function setBucketByIndex({
- bucketIndex,
- bucket,
- } : {
+ bucketIndex,
+ bucket,
+ }: {
bucketIndex: number,
bucket: IBucket
}) {
@@ -108,10 +108,10 @@ export const useKanbanStore = defineStore('kanban', () => {
}
function setTaskInBucketByIndex({
- bucketIndex,
- taskIndex,
- task,
- } : {
+ bucketIndex,
+ taskIndex,
+ task,
+ }: {
bucketIndex: number,
taskIndex: number,
task: ITask
@@ -201,7 +201,7 @@ export const useKanbanStore = defineStore('kanban', () => {
return
}
- const { bucketIndex, taskIndex } = getTaskIndicesById(buckets.value, task.id)
+ const {bucketIndex, taskIndex} = getTaskIndicesById(buckets.value, task.id)
if (
bucketIndex === null ||
@@ -211,16 +211,16 @@ export const useKanbanStore = defineStore('kanban', () => {
) {
return
}
-
+
buckets.value[bucketIndex].tasks.splice(taskIndex, 1)
buckets.value[bucketIndex].count--
}
- function setBucketLoading({bucketId, loading}: {bucketId: IBucket['id'], loading: boolean}) {
+ function setBucketLoading({bucketId, loading}: { bucketId: IBucket['id'], loading: boolean }) {
bucketLoading.value[bucketId] = loading
}
- function setTasksLoadedForBucketPage({bucketId, page}: {bucketId: IBucket['id'], page: number}) {
+ function setTasksLoadedForBucketPage({bucketId, page}: { bucketId: IBucket['id'], page: number }) {
taskPagesPerBucket.value[bucketId] = page
}
@@ -228,7 +228,7 @@ export const useKanbanStore = defineStore('kanban', () => {
allTasksLoadedForBucket.value[bucketId] = true
}
- async function loadBucketsForProject({projectId, params}: {projectId: IProject['id'], params}) {
+ async function loadBucketsForProject({projectId, params}: { projectId: IProject['id'], params }) {
const cancel = setModuleLoading(setIsLoading)
// Clear everything to prevent having old buckets in the project if loading the buckets from this project takes a few moments
@@ -269,29 +269,11 @@ export const useKanbanStore = defineStore('kanban', () => {
setBucketLoading({bucketId: bucketId, loading: true})
const params: TaskFilterParams = JSON.parse(JSON.stringify(ps))
-
- params.sort_by = 'kanban_position'
- params.order_by = 'asc'
-
- let hasBucketFilter = false
- for (const f in params.filter_by) {
- if (params.filter_by[f] === 'bucket_id') {
- hasBucketFilter = true
- if (params.filter_value[f] !== bucketId) {
- params.filter_value[f] = bucketId
- }
- break
- }
- }
-
- if (!hasBucketFilter) {
- params.filter_by = [...(params.filter_by ?? []), 'bucket_id']
- params.filter_value = [...(params.filter_value ?? []), bucketId]
- params.filter_comparator = [...(params.filter_comparator ?? []), 'equals']
- }
+ params.sort_by = ['kanban_position']
+ params.order_by = ['asc']
+ params.filter = `${params.filter === '' ? '' : params.filter + ' && '}bucket_id = ${bucketId}`
params.filter_timezone = authStore.settings.timezone
-
params.per_page = TASKS_PER_BUCKET
const taskService = new TaskCollectionService()
@@ -322,7 +304,7 @@ export const useKanbanStore = defineStore('kanban', () => {
}
}
- async function deleteBucket({bucket, params}: {bucket: IBucket, params}) {
+ async function deleteBucket({bucket, params}: { bucket: IBucket, params }) {
const cancel = setModuleLoading(setIsLoading)
const bucketService = new BucketService()
@@ -349,13 +331,13 @@ export const useKanbanStore = defineStore('kanban', () => {
}
setBucketByIndex({bucketIndex, bucket: updatedBucket})
-
+
const bucketService = new BucketService()
try {
const returnedBucket = await bucketService.update(updatedBucket)
setBucketByIndex({bucketIndex, bucket: returnedBucket})
return returnedBucket
- } catch(e) {
+ } catch (e) {
// restore original state
setBucketByIndex({bucketIndex, bucket: oldBucket})
@@ -365,7 +347,7 @@ export const useKanbanStore = defineStore('kanban', () => {
}
}
- async function updateBucketTitle({ id, title }: { id: IBucket['id'], title: IBucket['title'] }) {
+ async function updateBucketTitle({id, title}: { id: IBucket['id'], title: IBucket['title'] }) {
const bucket = findById(buckets.value, id)
if (bucket?.title === title) {
@@ -373,14 +355,14 @@ export const useKanbanStore = defineStore('kanban', () => {
return
}
- await updateBucket({ id, title })
+ await updateBucket({id, title})
success({message: i18n.global.t('project.kanban.bucketTitleSavedSuccess')})
}
-
+
return {
buckets,
isLoading: readonly(isLoading),
-
+
getBucketById,
getTaskById,
@@ -401,5 +383,5 @@ export const useKanbanStore = defineStore('kanban', () => {
// support hot reloading
if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useKanbanStore, import.meta.hot))
+ import.meta.hot.accept(acceptHMRUpdate(useKanbanStore, import.meta.hot))
}
\ No newline at end of file
diff --git a/frontend/src/stores/tasks.ts b/frontend/src/stores/tasks.ts
index 9cf3ab3e5..332a4eba7 100644
--- a/frontend/src/stores/tasks.ts
+++ b/frontend/src/stores/tasks.ts
@@ -126,7 +126,7 @@ export const useTaskStore = defineStore('task', () => {
async function loadTasks(params: TaskFilterParams, projectId: IProject['id'] | null = null) {
- if (params.filter_timezone === '') {
+ if (!params.filter_timezone || params.filter_timezone === '') {
params.filter_timezone = authStore.settings.timezone
}
diff --git a/frontend/src/views/project/ProjectTable.vue b/frontend/src/views/project/ProjectTable.vue
index 8ed043f8a..48255f9c1 100644
--- a/frontend/src/views/project/ProjectTable.vue
+++ b/frontend/src/views/project/ProjectTable.vue
@@ -333,9 +333,7 @@ const {
const tasks: Ref = taskList.tasks
Object.assign(params.value, {
- filter_by: [],
- filter_value: [],
- filter_comparator: [],
+ filter: '',
})
// FIXME: by doing this we can have multiple sort orders
diff --git a/frontend/src/views/tasks/ShowTasks.vue b/frontend/src/views/tasks/ShowTasks.vue
index 899edd04c..f947696db 100644
--- a/frontend/src/views/tasks/ShowTasks.vue
+++ b/frontend/src/views/tasks/ShowTasks.vue
@@ -182,29 +182,21 @@ async function loadPendingTasks(from: string, to: string) {
return
}
- const params = {
- sortBy: ['due_date', 'id'],
- orderBy: ['asc', 'desc'],
- filterTimezone: authStore.settings.timezone,
- filterBy: ['done'],
- filterValue: ['false'],
- filterComparator: ['equals'],
- filterConcat: 'and',
- filterIncludeNulls: showNulls,
+ const params: TaskFilterParams = {
+ sort_by: ['due_date', 'id'],
+ order_by: ['asc', 'desc'],
+ filter: 'done = false',
+ filter_include_nulls: showNulls,
}
if (!showAll.value) {
- params.filterBy.push('due_date')
- params.filterValue.push(to)
- params.filterComparator.push('less')
+ params.filter += ` && due_date < '${to}'`
// NOTE: Ideally we could also show tasks with a start or end date in the specified range, but the api
// is not capable (yet) of combining multiple filters with 'and' and 'or'.
if (!showOverdue) {
- params.filterBy.push('due_date')
- params.filterValue.push(from)
- params.filterComparator.push('greater')
+ params.filter += ` && due_date > '${from}'`
}
}
From 09d51280507849e9d917e00ec74b893c3596366f Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 17:02:04 +0100
Subject: [PATCH 16/66] fix(filters): don't escape valid escaped in queries
---
pkg/models/task_collection.go | 10 +++++++++-
pkg/models/task_collection_filter.go | 2 +-
pkg/models/task_collection_test.go | 12 ++++++++++++
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go
index 4c8160b72..f32658fa4 100644
--- a/pkg/models/task_collection.go
+++ b/pkg/models/task_collection.go
@@ -36,7 +36,7 @@ type TaskCollection struct {
// The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature.
Filter string `query:"filter" json:"filter"`
// The time zone which should be used for date match (statements like "now" resolve to different actual times)
- FilterTimezone string `query:"filter_timezone" json:"filter_timezone"`
+ FilterTimezone string `query:"filter_timezone" json:"-"`
// If set to true, the result will also include null values
FilterIncludeNulls bool `query:"filter_include_nulls" json:"filter_include_nulls"`
@@ -158,6 +158,14 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
sf.Filters.OrderBy = orderby
sf.Filters.OrderByArr = nil
+ if sf.Filters.FilterTimezone == "" {
+ u, err := user.GetUserByID(s, a.GetID())
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ sf.Filters.FilterTimezone = u.Timezone
+ }
+
return sf.getTaskCollection().ReadAll(s, a, search, page, perPage)
}
diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go
index d0885ff4d..9ac4ad847 100644
--- a/pkg/models/task_collection_filter.go
+++ b/pkg/models/task_collection_filter.go
@@ -155,7 +155,7 @@ func getTaskFiltersFromFilterString(filter string, filterTimezone string) (filte
filter = strings.ReplaceAll(filter, " in ", " ?= ")
// Replaces all occurrences with in with a string so that it passes the filter
- pattern := `\?=\s+([^&|]+)`
+ pattern := `\?=\s+([^&|']+)`
re := regexp.MustCompile(pattern)
filter = re.ReplaceAllStringFunc(filter, func(match string) string {
diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go
index a8c2e852a..0745b586a 100644
--- a/pkg/models/task_collection_test.go
+++ b/pkg/models/task_collection_test.go
@@ -1044,6 +1044,18 @@ func TestTaskCollection_ReadAll(t *testing.T) {
},
wantErr: false,
},
+ {
+ name: "filter in keyword without quotes",
+ fields: fields{
+ Filter: "id in 1,2,34", // user does not have permission to access task 34
+ },
+ args: defaultArgs,
+ want: []*Task{
+ task1,
+ task2,
+ },
+ wantErr: false,
+ },
{
name: "filter in",
fields: fields{
From 0910d5d2f236d478cbf00e6fa6fc8ebd8ff98bd0 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 17:20:05 +0100
Subject: [PATCH 17/66] chore(auth): refactor removing empty openid teams to
cron job
---
pkg/initialize/init.go | 1 +
pkg/modules/auth/openid/cron.go | 68 ++++++++++++++++++++++++++
pkg/modules/auth/openid/openid.go | 22 +--------
pkg/modules/auth/openid/openid_test.go | 40 ---------------
4 files changed, 70 insertions(+), 61 deletions(-)
create mode 100644 pkg/modules/auth/openid/cron.go
diff --git a/pkg/initialize/init.go b/pkg/initialize/init.go
index 7d25bb882..0e72e659b 100644
--- a/pkg/initialize/init.go
+++ b/pkg/initialize/init.go
@@ -95,6 +95,7 @@ func FullInit() {
models.RegisterUserDeletionCron()
models.RegisterOldExportCleanupCron()
openid.CleanupSavedOpenIDProviders()
+ openid.RegisterEmptyOpenIDTeamCleanupCron()
// Start processing events
go func() {
diff --git a/pkg/modules/auth/openid/cron.go b/pkg/modules/auth/openid/cron.go
new file mode 100644
index 000000000..96ae5c175
--- /dev/null
+++ b/pkg/modules/auth/openid/cron.go
@@ -0,0 +1,68 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package openid
+
+import (
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
+ "xorm.io/builder"
+ "xorm.io/xorm"
+
+ "code.vikunja.io/api/pkg/cron"
+ "code.vikunja.io/api/pkg/db"
+)
+
+func RemoveEmptySSOTeams(s *xorm.Session) (err error) {
+ teams := []*models.Team{}
+ err = s.
+ Where(
+ builder.NotIn("id", builder.Expr("select team_members.team_id from team_members")),
+ builder.Or(builder.Neq{"oidc_id": ""}, builder.NotNull{"oidc_id"}),
+ ).
+ Find(&teams)
+ if err != nil {
+ return err
+ }
+
+ teamIDs := make([]int64, 0, len(teams))
+ for _, team := range teams {
+ teamIDs = append(teamIDs, team.ID)
+ }
+
+ log.Debugf("Deleting empty teams: %v", teamIDs)
+
+ _, err = s.In("id", teamIDs).Delete(&models.Team{})
+ return err
+}
+
+func RegisterEmptyOpenIDTeamCleanupCron() {
+ const logPrefix = "[Empty openid Team Cleanup Cron] "
+
+ err := cron.Schedule("* * * * *", func() {
+ s := db.NewSession()
+ defer s.Close()
+
+ err := RemoveEmptySSOTeams(s)
+ if err != nil {
+ log.Errorf(logPrefix+"Error removing empty openid team: %s", err)
+ return
+ }
+ })
+ if err != nil {
+ log.Fatalf("Could not empty openid teams cleanup cron: %s", err)
+ }
+}
diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go
index ad9655315..c2ee5dcf1 100644
--- a/pkg/modules/auth/openid/openid.go
+++ b/pkg/modules/auth/openid/openid.go
@@ -222,13 +222,7 @@ func HandleCallback(c echo.Context) error {
teamIDsToLeave := utils.NotIn(oldOidcTeams, oidcTeams)
err = RemoveUserFromTeamsByIDs(s, u, teamIDsToLeave)
if err != nil {
- log.Errorf("Found error while leaving teams %v", err)
- }
- errs := RemoveEmptySSOTeams(s, teamIDsToLeave)
- if len(errs) > 0 {
- for _, err := range errs {
- log.Errorf("Found error while removing empty teams %v", err)
- }
+ log.Errorf("Error while leaving teams %v", err)
}
}
err = s.Commit()
@@ -266,20 +260,6 @@ func AssignOrCreateUserToTeams(s *xorm.Session, u *user.User, teamData []*models
return oidcTeams, err
}
-func RemoveEmptySSOTeams(s *xorm.Session, teamIDs []int64) (errs []error) {
- for _, teamID := range teamIDs {
- count, err := s.Where("team_id = ?", teamID).Count(&models.TeamMember{})
- if count == 0 && err == nil {
- log.Debugf("SSO team with id %v has no members. It will be deleted", teamID)
- _, _err := s.Where("id = ?", teamID).Delete(&models.Team{})
- if _err != nil {
- errs = append(errs, _err)
- }
- }
- }
- return errs
-}
-
func RemoveUserFromTeamsByIDs(s *xorm.Session, u *user.User, teamIDs []int64) (err error) {
if len(teamIDs) < 1 {
diff --git a/pkg/modules/auth/openid/openid_test.go b/pkg/modules/auth/openid/openid_test.go
index b1ebcdaa7..e50f26d81 100644
--- a/pkg/modules/auth/openid/openid_test.go
+++ b/pkg/modules/auth/openid/openid_test.go
@@ -220,14 +220,6 @@ func TestGetOrCreateUser(t *testing.T) {
require.NoError(t, err)
err = RemoveUserFromTeamsByIDs(s, u, teamIDsToLeave)
require.NoError(t, err)
- errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
- for _, err = range errs {
- require.NoError(t, err)
- }
- errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
- for _, err = range errs {
- require.NoError(t, err)
- }
err = s.Commit()
require.NoError(t, err)
@@ -235,38 +227,6 @@ func TestGetOrCreateUser(t *testing.T) {
"team_id": oidcTeams,
"user_id": u.ID,
})
- })
- t.Run("existing user, remove from existing team and delete team", func(t *testing.T) {
- db.LoadAndAssertFixtures(t)
- s := db.NewSession()
- defer s.Close()
-
- cl := &claims{
- Email: "other-email-address@some.service.com",
- VikunjaGroups: []map[string]interface{}{},
- }
-
- u := &user.User{ID: 10}
- teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
- if len(errs) > 0 {
- for _, err := range errs {
- require.NoError(t, err)
- }
- }
- oldOidcTeams, err := models.FindAllOidcTeamIDsForUser(s, u.ID)
- require.NoError(t, err)
- oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
- require.NoError(t, err)
- teamIDsToLeave := utils.NotIn(oldOidcTeams, oidcTeams)
- require.NoError(t, err)
- err = RemoveUserFromTeamsByIDs(s, u, teamIDsToLeave)
- require.NoError(t, err)
- errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
- for _, err := range errs {
- require.NoError(t, err)
- }
- err = s.Commit()
- require.NoError(t, err)
db.AssertMissing(t, "teams", map[string]interface{}{
"id": oidcTeams,
})
From 49ab90fc19f9da7d1308c923d6dd99b8a6a355ef Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 17:24:40 +0100
Subject: [PATCH 18/66] fix: lint
---
frontend/src/stores/kanban.ts | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/frontend/src/stores/kanban.ts b/frontend/src/stores/kanban.ts
index 997cdeac2..67cbfe988 100644
--- a/frontend/src/stores/kanban.ts
+++ b/frontend/src/stores/kanban.ts
@@ -97,13 +97,10 @@ export const useKanbanStore = defineStore('kanban', () => {
buckets.value[bucketIndex] = newBucket
}
- function setBucketByIndex({
- bucketIndex,
- bucket,
- }: {
+ function setBucketByIndex(
bucketIndex: number,
- bucket: IBucket
- }) {
+ bucket: IBucket,
+ ) {
buckets.value[bucketIndex] = bucket
}
@@ -269,7 +266,7 @@ export const useKanbanStore = defineStore('kanban', () => {
setBucketLoading({bucketId: bucketId, loading: true})
const params: TaskFilterParams = JSON.parse(JSON.stringify(ps))
-
+
params.sort_by = ['kanban_position']
params.order_by = ['asc']
params.filter = `${params.filter === '' ? '' : params.filter + ' && '}bucket_id = ${bucketId}`
@@ -330,16 +327,16 @@ export const useKanbanStore = defineStore('kanban', () => {
...updatedBucketData,
}
- setBucketByIndex({bucketIndex, bucket: updatedBucket})
+ setBucketByIndex(bucketIndex, updatedBucket)
const bucketService = new BucketService()
try {
const returnedBucket = await bucketService.update(updatedBucket)
- setBucketByIndex({bucketIndex, bucket: returnedBucket})
+ setBucketByIndex(bucketIndex, returnedBucket)
return returnedBucket
} catch (e) {
// restore original state
- setBucketByIndex({bucketIndex, bucket: oldBucket})
+ setBucketByIndex(bucketIndex, oldBucket)
throw e
} finally {
From 659de54db1ffcdc0a6722dfdf65a245e24846ed9 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 17:29:28 +0100
Subject: [PATCH 19/66] feat(kanban): do not remove focus from the input after
creating a new bucket
---
frontend/src/views/project/ProjectKanban.vue | 1 -
1 file changed, 1 deletion(-)
diff --git a/frontend/src/views/project/ProjectKanban.vue b/frontend/src/views/project/ProjectKanban.vue
index ab46a5cb1..9793235bb 100644
--- a/frontend/src/views/project/ProjectKanban.vue
+++ b/frontend/src/views/project/ProjectKanban.vue
@@ -554,7 +554,6 @@ async function createNewBucket() {
projectId: project.value.id,
}))
newBucketTitle.value = ''
- showNewBucketInput.value = false
}
function deleteBucketModal(bucketId: IBucket['id']) {
From 3f380e0d61e78b829310f9cfd8c6a176a21976fd Mon Sep 17 00:00:00 2001
From: "Frederick [Bot]"
Date: Mon, 11 Mar 2024 16:41:16 +0000
Subject: [PATCH 20/66] [skip ci] Updated swagger docs
---
pkg/swagger/docs.go | 13 +++++++++++++
pkg/swagger/swagger.json | 13 +++++++++++++
pkg/swagger/swagger.yaml | 12 ++++++++++++
3 files changed, 38 insertions(+)
diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go
index b67243664..0fa109f4d 100644
--- a/pkg/swagger/docs.go
+++ b/pkg/swagger/docs.go
@@ -1941,6 +1941,12 @@ const docTemplate = `{
"name": "filter",
"in": "query"
},
+ {
+ "type": "string",
+ "description": "The time zone which should be used for date match (statements like ",
+ "name": "filter_timezone",
+ "in": "query"
+ },
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to ` + "`" + `null` + "`" + `. Available values are ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `. Defaults to ` + "`" + `false` + "`" + `.",
@@ -2155,6 +2161,12 @@ const docTemplate = `{
"name": "filter",
"in": "query"
},
+ {
+ "type": "string",
+ "description": "The time zone which should be used for date match (statements like ",
+ "name": "filter_timezone",
+ "in": "query"
+ },
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to ` + "`" + `null` + "`" + `. Available values are ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `. Defaults to ` + "`" + `false` + "`" + `.",
@@ -8097,6 +8109,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"filter": {
+ "description": "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature.",
"type": "string"
},
"filter_include_nulls": {
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index 836a1baa4..0bc6e5b2e 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -1933,6 +1933,12 @@
"name": "filter",
"in": "query"
},
+ {
+ "type": "string",
+ "description": "The time zone which should be used for date match (statements like ",
+ "name": "filter_timezone",
+ "in": "query"
+ },
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`.",
@@ -2147,6 +2153,12 @@
"name": "filter",
"in": "query"
},
+ {
+ "type": "string",
+ "description": "The time zone which should be used for date match (statements like ",
+ "name": "filter_timezone",
+ "in": "query"
+ },
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`.",
@@ -8089,6 +8101,7 @@
"type": "object",
"properties": {
"filter": {
+ "description": "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature.",
"type": "string"
},
"filter_include_nulls": {
diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml
index 1365bd24d..5f6722b42 100644
--- a/pkg/swagger/swagger.yaml
+++ b/pkg/swagger/swagger.yaml
@@ -784,6 +784,8 @@ definitions:
models.TaskCollection:
properties:
filter:
+ description: The filter query to match tasks by. Check out https://vikunja.io/docs/filters
+ for a full explanation of the feature.
type: string
filter_include_nulls:
description: If set to true, the result will also include null values
@@ -2724,6 +2726,11 @@ paths:
in: query
name: filter
type: string
+ - description: 'The time zone which should be used for date match (statements
+ like '
+ in: query
+ name: filter_timezone
+ type: string
- description: If set to true the result will include filtered fields whose
value is set to `null`. Available values are `true` or `false`. Defaults
to `false`.
@@ -2874,6 +2881,11 @@ paths:
in: query
name: filter
type: string
+ - description: 'The time zone which should be used for date match (statements
+ like '
+ in: query
+ name: filter_timezone
+ type: string
- description: If set to true the result will include filtered fields whose
value is set to `null`. Available values are `true` or `false`. Defaults
to `false`.
From 85fb8e3443d19e36edd0033bef52ea00f13b720e Mon Sep 17 00:00:00 2001
From: kolaente
Date: Mon, 11 Mar 2024 23:28:35 +0100
Subject: [PATCH 21/66] fix(filters): invalid filter range when converting
dates to strings
Resolves https://community.vikunja.io/t/my-vikunja-instance-creates-tasks-with-due-date-time-of-9am-for-tasks-with-the-word-today-word-in-it/2105/10
---
frontend/src/services/taskCollection.ts | 2 +-
frontend/src/views/tasks/ShowTasks.vue | 8 +++++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/frontend/src/services/taskCollection.ts b/frontend/src/services/taskCollection.ts
index 2cc39773f..f83dac2e2 100644
--- a/frontend/src/services/taskCollection.ts
+++ b/frontend/src/services/taskCollection.ts
@@ -4,7 +4,7 @@ import TaskModel from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
export interface TaskFilterParams {
- sort_by: ('start_date' | 'done' | 'id' | 'position' | 'kanban_position')[],
+ sort_by: ('start_date' | 'end_date' | 'due_date' | 'done' | 'id' | 'position' | 'kanban_position')[],
order_by: ('asc' | 'desc')[],
filter: string,
filter_include_nulls: boolean,
diff --git a/frontend/src/views/tasks/ShowTasks.vue b/frontend/src/views/tasks/ShowTasks.vue
index f947696db..0a5318051 100644
--- a/frontend/src/views/tasks/ShowTasks.vue
+++ b/frontend/src/views/tasks/ShowTasks.vue
@@ -173,7 +173,7 @@ function setShowNulls(show: boolean) {
})
}
-async function loadPendingTasks(from: string, to: string) {
+async function loadPendingTasks(from: Date|string, to: Date|string) {
// FIXME: HACK! This should never happen.
// Since this route is authentication only, users would get an error message if they access the page unauthenticated.
// Since this component is mounted as the home page before unauthenticated users get redirected
@@ -187,16 +187,18 @@ async function loadPendingTasks(from: string, to: string) {
order_by: ['asc', 'desc'],
filter: 'done = false',
filter_include_nulls: showNulls,
+ s: '',
}
if (!showAll.value) {
- params.filter += ` && due_date < '${to}'`
+
+ params.filter += ` && due_date < '${to instanceof Date ? to.toISOString() : to}'`
// NOTE: Ideally we could also show tasks with a start or end date in the specified range, but the api
// is not capable (yet) of combining multiple filters with 'and' and 'or'.
if (!showOverdue) {
- params.filter += ` && due_date > '${from}'`
+ params.filter += ` && due_date > '${from instanceof Date ? from.toISOString() : from}'`
}
}
From da53c8e7ef0a2a88a6f1bdad82a12394582d8e03 Mon Sep 17 00:00:00 2001
From: renovate
Date: Tue, 12 Mar 2024 06:07:20 +0000
Subject: [PATCH 22/66] chore(deps): update dev-dependencies
---
frontend/package.json | 14 +-
frontend/pnpm-lock.yaml | 332 ++++++++++++++++++++--------------------
2 files changed, 173 insertions(+), 173 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 9f247aecc..f000a5356 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -141,11 +141,11 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
- "@types/node": "20.11.25",
+ "@types/node": "20.11.26",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.8",
- "@typescript-eslint/eslint-plugin": "7.1.1",
- "@typescript-eslint/parser": "7.1.1",
+ "@typescript-eslint/eslint-plugin": "7.2.0",
+ "@typescript-eslint/parser": "7.2.0",
"@vitejs/plugin-legacy": "5.3.2",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
@@ -159,20 +159,20 @@
"cypress": "13.6.6",
"esbuild": "0.20.1",
"eslint": "8.57.0",
- "eslint-plugin-vue": "9.22.0",
- "happy-dom": "13.7.3",
+ "eslint-plugin-vue": "9.23.0",
+ "happy-dom": "13.7.8",
"histoire": "0.17.9",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.5.0",
- "rollup": "4.12.1",
+ "rollup": "4.13.0",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.71.1",
"start-server-and-test": "2.0.3",
"typescript": "5.4.2",
- "vite": "5.1.5",
+ "vite": "5.1.6",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.19.2",
"vite-plugin-sentry": "1.4.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 31ddb804f..8806e06dc 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -30,7 +30,7 @@ dependencies:
version: 2.2.0(dayjs@1.11.10)(vue@3.4.21)
'@intlify/unplugin-vue-i18n':
specifier: 3.0.1
- version: 3.0.1(rollup@4.12.1)(vue-i18n@9.10.1)
+ version: 3.0.1(rollup@4.13.0)(vue-i18n@9.10.1)
'@kyvg/vue3-notification':
specifier: 3.2.0
version: 3.2.0(vue@3.4.21)
@@ -254,7 +254,7 @@ devDependencies:
version: 0.17.8(histoire@0.17.9)
'@histoire/plugin-vue':
specifier: 0.17.12
- version: 0.17.12(histoire@0.17.9)(vite@5.1.5)(vue@3.4.21)
+ version: 0.17.12(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21)
'@rushstack/eslint-patch':
specifier: 1.7.2
version: 1.7.2
@@ -277,8 +277,8 @@ devDependencies:
specifier: 5.0.2
version: 5.0.2
'@types/node':
- specifier: 20.11.25
- version: 20.11.25
+ specifier: 20.11.26
+ version: 20.11.26
'@types/postcss-preset-env':
specifier: 7.7.0
version: 7.7.0
@@ -286,20 +286,20 @@ devDependencies:
specifier: 1.15.8
version: 1.15.8
'@typescript-eslint/eslint-plugin':
- specifier: 7.1.1
- version: 7.1.1(@typescript-eslint/parser@7.1.1)(eslint@8.57.0)(typescript@5.4.2)
+ specifier: 7.2.0
+ version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/parser':
- specifier: 7.1.1
- version: 7.1.1(eslint@8.57.0)(typescript@5.4.2)
+ specifier: 7.2.0
+ version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
'@vitejs/plugin-legacy':
specifier: 5.3.2
- version: 5.3.2(terser@5.24.0)(vite@5.1.5)
+ version: 5.3.2(terser@5.24.0)(vite@5.1.6)
'@vitejs/plugin-vue':
specifier: 5.0.4
- version: 5.0.4(vite@5.1.5)(vue@3.4.21)
+ version: 5.0.4(vite@5.1.6)(vue@3.4.21)
'@vue/eslint-config-typescript':
specifier: 13.0.0
- version: 13.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.4.2)
+ version: 13.0.0(eslint-plugin-vue@9.23.0)(eslint@8.57.0)(typescript@5.4.2)
'@vue/test-utils':
specifier: 2.4.4
version: 2.4.4(vue@3.4.21)
@@ -331,14 +331,14 @@ devDependencies:
specifier: 8.57.0
version: 8.57.0
eslint-plugin-vue:
- specifier: 9.22.0
- version: 9.22.0(eslint@8.57.0)
+ specifier: 9.23.0
+ version: 9.23.0(eslint@8.57.0)
happy-dom:
- specifier: 13.7.3
- version: 13.7.3
+ specifier: 13.7.8
+ version: 13.7.8
histoire:
specifier: 0.17.9
- version: 0.17.9(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)(vite@5.1.5)
+ version: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
postcss:
specifier: 8.4.35
version: 8.4.35
@@ -355,11 +355,11 @@ devDependencies:
specifier: 9.5.0
version: 9.5.0(postcss@8.4.35)
rollup:
- specifier: 4.12.1
- version: 4.12.1
+ specifier: 4.13.0
+ version: 4.13.0
rollup-plugin-visualizer:
specifier: 5.12.0
- version: 5.12.0(rollup@4.12.1)
+ version: 5.12.0(rollup@4.13.0)
sass:
specifier: 1.71.1
version: 1.71.1
@@ -370,23 +370,23 @@ devDependencies:
specifier: 5.4.2
version: 5.4.2
vite:
- specifier: 5.1.5
- version: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ specifier: 5.1.6
+ version: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
vite-plugin-inject-preload:
specifier: 1.3.3
- version: 1.3.3(vite@5.1.5)
+ version: 1.3.3(vite@5.1.6)
vite-plugin-pwa:
specifier: 0.19.2
- version: 0.19.2(vite@5.1.5)(workbox-build@7.0.0)(workbox-window@7.0.0)
+ version: 0.19.2(vite@5.1.6)(workbox-build@7.0.0)(workbox-window@7.0.0)
vite-plugin-sentry:
specifier: 1.4.0
- version: 1.4.0(vite@5.1.5)
+ version: 1.4.0(vite@5.1.6)
vite-svg-loader:
specifier: 5.1.0
version: 5.1.0(vue@3.4.21)
vitest:
specifier: 1.3.1
- version: 1.3.1(@types/node@20.11.25)(happy-dom@13.7.3)(sass@1.71.1)(terser@5.24.0)
+ version: 1.3.1(@types/node@20.11.26)(happy-dom@13.7.8)(sass@1.71.1)(terser@5.24.0)
vue-tsc:
specifier: 2.0.6
version: 2.0.6(typescript@5.4.2)
@@ -2701,11 +2701,11 @@ packages:
'@hapi/hoek': 9.2.1
dev: true
- /@histoire/app@0.17.9(vite@5.1.5):
+ /@histoire/app@0.17.9(vite@5.1.6):
resolution: {integrity: sha512-JoSGbsoo1/JY5TtTiMBUSPllIEJLvC6jHIGruvwPG/cJ3niqa3EyEMOsOWtcu+xjtx1uETgL9Yj5RJMJjC+OBA==}
dependencies:
- '@histoire/controls': 0.17.9(vite@5.1.5)
- '@histoire/shared': 0.17.9(vite@5.1.5)
+ '@histoire/controls': 0.17.9(vite@5.1.6)
+ '@histoire/shared': 0.17.9(vite@5.1.6)
'@histoire/vendors': 0.17.8
'@types/flexsearch': 0.7.6
flexsearch: 0.7.21
@@ -2714,7 +2714,7 @@ packages:
- vite
dev: true
- /@histoire/controls@0.17.9(vite@5.1.5):
+ /@histoire/controls@0.17.9(vite@5.1.6):
resolution: {integrity: sha512-1f1cE1NZ2emzGMRnGfAb/gCKDtBT3bUZzj3aAcDmhm3MA2Vy5tGYSb9j+KuTTj7+exhOrKefmedr9a0q1/5g2w==}
dependencies:
'@codemirror/commands': 6.3.2
@@ -2724,7 +2724,7 @@ packages:
'@codemirror/state': 6.3.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.22.1
- '@histoire/shared': 0.17.9(vite@5.1.5)
+ '@histoire/shared': 0.17.9(vite@5.1.6)
'@histoire/vendors': 0.17.8
transitivePeerDependencies:
- vite
@@ -2738,7 +2738,7 @@ packages:
capture-website: 2.4.1
defu: 6.1.3
fs-extra: 10.1.0
- histoire: 0.17.9(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)(vite@5.1.5)
+ histoire: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
pathe: 1.1.1
transitivePeerDependencies:
- bufferutil
@@ -2747,18 +2747,18 @@ packages:
- utf-8-validate
dev: true
- /@histoire/plugin-vue@0.17.12(histoire@0.17.9)(vite@5.1.5)(vue@3.4.21):
+ /@histoire/plugin-vue@0.17.12(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21):
resolution: {integrity: sha512-mpx2uwHq/qemnX+ARQtDR3M9kIt1y4kBCmzBkOquhJTp61mtHMu4hZKSzzQpQWA2QxEyuuwpaNiU7Mlms13EaQ==}
peerDependencies:
histoire: ^0.17.9
vue: ^3.2.47
dependencies:
- '@histoire/controls': 0.17.9(vite@5.1.5)
- '@histoire/shared': 0.17.10(vite@5.1.5)
+ '@histoire/controls': 0.17.9(vite@5.1.6)
+ '@histoire/shared': 0.17.10(vite@5.1.6)
'@histoire/vendors': 0.17.8
change-case: 4.1.2
globby: 13.2.2
- histoire: 0.17.9(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)(vite@5.1.5)
+ histoire: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
launch-editor: 2.6.1
pathe: 1.1.1
vue: 3.4.21(typescript@5.4.2)
@@ -2766,7 +2766,7 @@ packages:
- vite
dev: true
- /@histoire/shared@0.17.10(vite@5.1.5):
+ /@histoire/shared@0.17.10(vite@5.1.6):
resolution: {integrity: sha512-8hzk/WKASrYfaJ+UtR6Mv7aZlP8IZvQ5POoHAi+JvHMJTtzCXZeuL0qdQAXg0zdk3vWIH20oSl6N8hZE1AP7yA==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
@@ -2777,10 +2777,10 @@ packages:
chokidar: 3.5.3
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
dev: true
- /@histoire/shared@0.17.9(vite@5.1.5):
+ /@histoire/shared@0.17.9(vite@5.1.6):
resolution: {integrity: sha512-E/l4EzYc69/bOImUnvfi7h4/DHGl1rc96lkuMYulL5hjRjuNhSy5AlN5bG0nkVOG4RVIAnLGevMaMi207wtvLw==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
@@ -2791,7 +2791,7 @@ packages:
chokidar: 3.5.3
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
dev: true
/@histoire/vendors@0.17.8:
@@ -2877,7 +2877,7 @@ packages:
engines: {node: '>= 16'}
dev: false
- /@intlify/unplugin-vue-i18n@3.0.1(rollup@4.12.1)(vue-i18n@9.10.1):
+ /@intlify/unplugin-vue-i18n@3.0.1(rollup@4.13.0)(vue-i18n@9.10.1):
resolution: {integrity: sha512-q1zJhA/WpoLBzAAuKA5/AEp0e+bMOM10ll/HxT4g1VAw/9JhC4TTobP9KobKH90JMZ4U2daLFlYQfKNd29lpqw==}
engines: {node: '>= 14.16'}
peerDependencies:
@@ -2894,7 +2894,7 @@ packages:
dependencies:
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.10.1)
'@intlify/shared': 9.10.1
- '@rollup/pluginutils': 5.1.0(rollup@4.12.1)
+ '@rollup/pluginutils': 5.1.0(rollup@4.13.0)
'@vue/compiler-sfc': 3.4.21
debug: 4.3.4(supports-color@8.1.1)
fast-glob: 3.3.2
@@ -3133,7 +3133,7 @@ packages:
rollup: 2.79.1
dev: true
- /@rollup/pluginutils@5.1.0(rollup@4.12.1):
+ /@rollup/pluginutils@5.1.0(rollup@4.13.0):
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
peerDependencies:
@@ -3145,95 +3145,95 @@ packages:
'@types/estree': 1.0.5
estree-walker: 2.0.2
picomatch: 2.3.1
- rollup: 4.12.1
+ rollup: 4.13.0
dev: false
- /@rollup/rollup-android-arm-eabi@4.12.1:
- resolution: {integrity: sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==}
+ /@rollup/rollup-android-arm-eabi@4.13.0:
+ resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==}
cpu: [arm]
os: [android]
requiresBuild: true
optional: true
- /@rollup/rollup-android-arm64@4.12.1:
- resolution: {integrity: sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==}
+ /@rollup/rollup-android-arm64@4.13.0:
+ resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==}
cpu: [arm64]
os: [android]
requiresBuild: true
optional: true
- /@rollup/rollup-darwin-arm64@4.12.1:
- resolution: {integrity: sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==}
+ /@rollup/rollup-darwin-arm64@4.13.0:
+ resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
optional: true
- /@rollup/rollup-darwin-x64@4.12.1:
- resolution: {integrity: sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==}
+ /@rollup/rollup-darwin-x64@4.13.0:
+ resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==}
cpu: [x64]
os: [darwin]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-arm-gnueabihf@4.12.1:
- resolution: {integrity: sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==}
+ /@rollup/rollup-linux-arm-gnueabihf@4.13.0:
+ resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-arm64-gnu@4.12.1:
- resolution: {integrity: sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==}
+ /@rollup/rollup-linux-arm64-gnu@4.13.0:
+ resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==}
cpu: [arm64]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-arm64-musl@4.12.1:
- resolution: {integrity: sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==}
+ /@rollup/rollup-linux-arm64-musl@4.13.0:
+ resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==}
cpu: [arm64]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-riscv64-gnu@4.12.1:
- resolution: {integrity: sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==}
+ /@rollup/rollup-linux-riscv64-gnu@4.13.0:
+ resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==}
cpu: [riscv64]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-x64-gnu@4.12.1:
- resolution: {integrity: sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==}
+ /@rollup/rollup-linux-x64-gnu@4.13.0:
+ resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==}
cpu: [x64]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-linux-x64-musl@4.12.1:
- resolution: {integrity: sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==}
+ /@rollup/rollup-linux-x64-musl@4.13.0:
+ resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==}
cpu: [x64]
os: [linux]
requiresBuild: true
optional: true
- /@rollup/rollup-win32-arm64-msvc@4.12.1:
- resolution: {integrity: sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==}
+ /@rollup/rollup-win32-arm64-msvc@4.13.0:
+ resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
optional: true
- /@rollup/rollup-win32-ia32-msvc@4.12.1:
- resolution: {integrity: sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==}
+ /@rollup/rollup-win32-ia32-msvc@4.13.0:
+ resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==}
cpu: [ia32]
os: [win32]
requiresBuild: true
optional: true
- /@rollup/rollup-win32-x64-msvc@4.12.1:
- resolution: {integrity: sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==}
+ /@rollup/rollup-win32-x64-msvc@4.13.0:
+ resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==}
cpu: [x64]
os: [win32]
requiresBuild: true
@@ -3794,7 +3794,7 @@ packages:
/@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
dev: true
/@types/har-format@1.2.10:
@@ -3818,7 +3818,7 @@ packages:
/@types/keyv@3.1.3:
resolution: {integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==}
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
dev: true
/@types/linkify-it@3.0.2:
@@ -3859,8 +3859,8 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
- /@types/node@20.11.25:
- resolution: {integrity: sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==}
+ /@types/node@20.11.26:
+ resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -3887,13 +3887,13 @@ packages:
/@types/resolve@1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
dev: true
/@types/responselike@1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
dev: true
/@types/semver@7.5.0:
@@ -3942,12 +3942,12 @@ packages:
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
requiresBuild: true
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
dev: true
optional: true
- /@typescript-eslint/eslint-plugin@7.1.1(@typescript-eslint/parser@7.1.1)(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-zioDz623d0RHNhvx0eesUmGfIjzrk18nSBC8xewepKXbBvN/7c1qImV7Hg8TI1URTxKax7/zxfxj3Uph8Chcuw==}
+ /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
@@ -3958,11 +3958,11 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.6.2
- '@typescript-eslint/parser': 7.1.1(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/scope-manager': 7.1.1
- '@typescript-eslint/type-utils': 7.1.1(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/utils': 7.1.1(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/visitor-keys': 7.1.1
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.57.0
graphemer: 1.4.0
@@ -3975,8 +3975,8 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-ZWUFyL0z04R1nAEgr9e79YtV5LbafdOtN7yapNbn1ansMyaegl2D4bL7vHoJ4HPSc4CaLwuCVas8CVuneKzplQ==}
+ /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@@ -3985,10 +3985,10 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/scope-manager': 7.1.1
- '@typescript-eslint/types': 7.1.1
- '@typescript-eslint/typescript-estree': 7.1.1(typescript@5.4.2)
- '@typescript-eslint/visitor-keys': 7.1.1
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.57.0
typescript: 5.4.2
@@ -3996,16 +3996,16 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/scope-manager@7.1.1:
- resolution: {integrity: sha512-cirZpA8bJMRb4WZ+rO6+mnOJrGFDd38WoXCEI57+CYBqta8Yc8aJym2i7vyqLL1vVYljgw0X27axkUXz32T8TA==}
+ /@typescript-eslint/scope-manager@7.2.0:
+ resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
- '@typescript-eslint/types': 7.1.1
- '@typescript-eslint/visitor-keys': 7.1.1
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
dev: true
- /@typescript-eslint/type-utils@7.1.1(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-5r4RKze6XHEEhlZnJtR3GYeCh1IueUHdbrukV2KSlLXaTjuSfeVF8mZUVPLovidCuZfbVjfhi4c0DNSa/Rdg5g==}
+ /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@@ -4014,8 +4014,8 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/typescript-estree': 7.1.1(typescript@5.4.2)
- '@typescript-eslint/utils': 7.1.1(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
debug: 4.3.4(supports-color@8.1.1)
eslint: 8.57.0
ts-api-utils: 1.0.1(typescript@5.4.2)
@@ -4024,13 +4024,13 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/types@7.1.1:
- resolution: {integrity: sha512-KhewzrlRMrgeKm1U9bh2z5aoL4s7K3tK5DwHDn8MHv0yQfWFz/0ZR6trrIHHa5CsF83j/GgHqzdbzCXJ3crx0Q==}
+ /@typescript-eslint/types@7.2.0:
+ resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
- /@typescript-eslint/typescript-estree@7.1.1(typescript@5.4.2):
- resolution: {integrity: sha512-9ZOncVSfr+sMXVxxca2OJOPagRwT0u/UHikM2Rd6L/aB+kL/QAuTnsv6MeXtjzCJYb8PzrXarypSGIPx3Jemxw==}
+ /@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2):
+ resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
typescript: '*'
@@ -4038,8 +4038,8 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/types': 7.1.1
- '@typescript-eslint/visitor-keys': 7.1.1
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
debug: 4.3.4(supports-color@8.1.1)
globby: 11.1.0
is-glob: 4.0.3
@@ -4051,8 +4051,8 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/utils@7.1.1(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==}
+ /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^8.56.0
@@ -4060,9 +4060,9 @@ packages:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.12
'@types/semver': 7.5.0
- '@typescript-eslint/scope-manager': 7.1.1
- '@typescript-eslint/types': 7.1.1
- '@typescript-eslint/typescript-estree': 7.1.1(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
@@ -4070,11 +4070,11 @@ packages:
- typescript
dev: true
- /@typescript-eslint/visitor-keys@7.1.1:
- resolution: {integrity: sha512-yTdHDQxY7cSoCcAtiBzVzxleJhkGB9NncSIyMYe2+OGON1ZsP9zOPws/Pqgopa65jvknOjlk/w7ulPlZ78PiLQ==}
+ /@typescript-eslint/visitor-keys@7.2.0:
+ resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
- '@typescript-eslint/types': 7.1.1
+ '@typescript-eslint/types': 7.2.0
eslint-visitor-keys: 3.4.3
dev: true
@@ -4082,7 +4082,7 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
- /@vitejs/plugin-legacy@5.3.2(terser@5.24.0)(vite@5.1.5):
+ /@vitejs/plugin-legacy@5.3.2(terser@5.24.0)(vite@5.1.6):
resolution: {integrity: sha512-8moCOrIMaZ/Rjln0Q6GsH6s8fAt1JOI3k8nmfX4tXUxE5KAExVctSyOBk+A25GClsdSWqIk2yaUthH3KJ2X4tg==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
@@ -4098,19 +4098,19 @@ packages:
regenerator-runtime: 0.14.1
systemjs: 6.14.3
terser: 5.24.0
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
transitivePeerDependencies:
- supports-color
dev: true
- /@vitejs/plugin-vue@5.0.4(vite@5.1.5)(vue@3.4.21):
+ /@vitejs/plugin-vue@5.0.4(vite@5.1.6)(vue@3.4.21):
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vue: ^3.2.25
dependencies:
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
vue: 3.4.21(typescript@5.4.2)
dev: true
@@ -4214,7 +4214,7 @@ packages:
resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
dev: false
- /@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.4.2):
+ /@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.23.0)(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -4225,10 +4225,10 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/eslint-plugin': 7.1.1(@typescript-eslint/parser@7.1.1)(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/parser': 7.1.1(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
eslint: 8.57.0
- eslint-plugin-vue: 9.22.0(eslint@8.57.0)
+ eslint-plugin-vue: 9.23.0(eslint@8.57.0)
typescript: 5.4.2
vue-eslint-parser: 9.4.2(eslint@8.57.0)
transitivePeerDependencies:
@@ -5782,8 +5782,8 @@ packages:
optionalDependencies:
source-map: 0.6.1
- /eslint-plugin-vue@9.22.0(eslint@8.57.0):
- resolution: {integrity: sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==}
+ /eslint-plugin-vue@9.23.0(eslint@8.57.0):
+ resolution: {integrity: sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
@@ -6449,8 +6449,8 @@ packages:
strip-bom-string: 1.0.0
dev: true
- /happy-dom@13.7.3:
- resolution: {integrity: sha512-xMwilTgO34BGEX0TAoM369wwwAy0fK/Jq6BGaRYhSjxLtfQ740nqxHfjFyBqPjCVmKiPUS4npBnMrGLii7eCOg==}
+ /happy-dom@13.7.8:
+ resolution: {integrity: sha512-dnvgCiPPfXXts+AW1DVAoDa9nPmI48YPHUv34L6pmjv2lwNZte8OwsK9SajEXENfibS8uo1zG7xJwlW/NXlDxQ==}
engines: {node: '>=16.0.0'}
dependencies:
entities: 4.5.0
@@ -6517,16 +6517,16 @@ packages:
engines: {node: '>=12.0.0'}
dev: false
- /histoire@0.17.9(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)(vite@5.1.5):
+ /histoire@0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6):
resolution: {integrity: sha512-z5Jb9QwbOw0TKvpkU0v7+CxJG6hIljIKMhWXzOfteteRZGDFElpTEwbr5/8EdPI6VTdF/k76fqZ07nmS9YdUvA==}
hasBin: true
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies:
'@akryum/tinypool': 0.3.1
- '@histoire/app': 0.17.9(vite@5.1.5)
- '@histoire/controls': 0.17.9(vite@5.1.5)
- '@histoire/shared': 0.17.9(vite@5.1.5)
+ '@histoire/app': 0.17.9(vite@5.1.6)
+ '@histoire/controls': 0.17.9(vite@5.1.6)
+ '@histoire/shared': 0.17.9(vite@5.1.6)
'@histoire/vendors': 0.17.8
'@types/flexsearch': 0.7.6
'@types/markdown-it': 12.2.3
@@ -6553,8 +6553,8 @@ packages:
sade: 1.8.1
shiki-es: 0.2.0
sirv: 2.0.3
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
- vite-node: 0.34.6(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite-node: 0.34.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- bufferutil
@@ -7000,7 +7000,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@@ -8967,7 +8967,7 @@ packages:
- acorn
dev: true
- /rollup-plugin-visualizer@5.12.0(rollup@4.12.1):
+ /rollup-plugin-visualizer@5.12.0(rollup@4.13.0):
resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==}
engines: {node: '>=14'}
hasBin: true
@@ -8979,7 +8979,7 @@ packages:
dependencies:
open: 8.4.0
picomatch: 2.3.1
- rollup: 4.12.1
+ rollup: 4.13.0
source-map: 0.7.4
yargs: 17.6.0
dev: true
@@ -8992,26 +8992,26 @@ packages:
fsevents: 2.3.3
dev: true
- /rollup@4.12.1:
- resolution: {integrity: sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==}
+ /rollup@4.13.0:
+ resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.12.1
- '@rollup/rollup-android-arm64': 4.12.1
- '@rollup/rollup-darwin-arm64': 4.12.1
- '@rollup/rollup-darwin-x64': 4.12.1
- '@rollup/rollup-linux-arm-gnueabihf': 4.12.1
- '@rollup/rollup-linux-arm64-gnu': 4.12.1
- '@rollup/rollup-linux-arm64-musl': 4.12.1
- '@rollup/rollup-linux-riscv64-gnu': 4.12.1
- '@rollup/rollup-linux-x64-gnu': 4.12.1
- '@rollup/rollup-linux-x64-musl': 4.12.1
- '@rollup/rollup-win32-arm64-msvc': 4.12.1
- '@rollup/rollup-win32-ia32-msvc': 4.12.1
- '@rollup/rollup-win32-x64-msvc': 4.12.1
+ '@rollup/rollup-android-arm-eabi': 4.13.0
+ '@rollup/rollup-android-arm64': 4.13.0
+ '@rollup/rollup-darwin-arm64': 4.13.0
+ '@rollup/rollup-darwin-x64': 4.13.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.13.0
+ '@rollup/rollup-linux-arm64-gnu': 4.13.0
+ '@rollup/rollup-linux-arm64-musl': 4.13.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.13.0
+ '@rollup/rollup-linux-x64-gnu': 4.13.0
+ '@rollup/rollup-linux-x64-musl': 4.13.0
+ '@rollup/rollup-win32-arm64-msvc': 4.13.0
+ '@rollup/rollup-win32-ia32-msvc': 4.13.0
+ '@rollup/rollup-win32-x64-msvc': 4.13.0
fsevents: 2.3.3
/rope-sequence@1.3.4:
@@ -9944,7 +9944,7 @@ packages:
extsprintf: 1.3.0
dev: true
- /vite-node@0.34.6(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0):
+ /vite-node@0.34.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -9954,7 +9954,7 @@ packages:
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -9966,7 +9966,7 @@ packages:
- terser
dev: true
- /vite-node@1.3.1(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0):
+ /vite-node@1.3.1(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -9975,7 +9975,7 @@ packages:
debug: 4.3.4(supports-color@8.1.1)
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -9987,17 +9987,17 @@ packages:
- terser
dev: true
- /vite-plugin-inject-preload@1.3.3(vite@5.1.5):
+ /vite-plugin-inject-preload@1.3.3(vite@5.1.6):
resolution: {integrity: sha512-nh5+6BZdR/iFZj6pfDR8NHxQgRELkcmM5f9ufj9X6BWXgh3x6SWNp24TfiYvhwQyOV/vrVXpo0DqNBSgppmeOQ==}
engines: {node: '>=14.18.0'}
peerDependencies:
vite: ^3.0.0 || ^4.0.0
dependencies:
mime-types: 2.1.35
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
dev: true
- /vite-plugin-pwa@0.19.2(vite@5.1.5)(workbox-build@7.0.0)(workbox-window@7.0.0):
+ /vite-plugin-pwa@0.19.2(vite@5.1.6)(workbox-build@7.0.0)(workbox-window@7.0.0):
resolution: {integrity: sha512-LSQJFPxCAQYbRuSyc9EbRLRqLpaBA9onIZuQFomfUYjWSgHuQLonahetDlPSC9zsxmkSEhQH8dXZN8yL978h3w==}
engines: {node: '>=16.0.0'}
peerDependencies:
@@ -10012,21 +10012,21 @@ packages:
debug: 4.3.4(supports-color@8.1.1)
fast-glob: 3.3.2
pretty-bytes: 6.1.1
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
workbox-build: 7.0.0(acorn@8.11.2)
workbox-window: 7.0.0
transitivePeerDependencies:
- supports-color
dev: true
- /vite-plugin-sentry@1.4.0(vite@5.1.5):
+ /vite-plugin-sentry@1.4.0(vite@5.1.6):
resolution: {integrity: sha512-Jt9AeDnh9XLjEA1pAfU0NW0jCyJE8lAXMJWZKc+SoIxZRKSY64fitDOg9Ta1G98LhPaiDKL6dhVeROUmjY/aUQ==}
engines: {node: '>= 14'}
peerDependencies:
vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies:
'@sentry/cli': 2.19.1
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
transitivePeerDependencies:
- encoding
- supports-color
@@ -10041,8 +10041,8 @@ packages:
vue: 3.4.21(typescript@5.4.2)
dev: true
- /vite@5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0):
- resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
+ /vite@5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
+ resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -10069,17 +10069,17 @@ packages:
terser:
optional: true
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
esbuild: 0.19.12
postcss: 8.4.35
- rollup: 4.12.1
+ rollup: 4.13.0
sass: 1.71.1
terser: 5.24.0
optionalDependencies:
fsevents: 2.3.3
dev: true
- /vitest@1.3.1(@types/node@20.11.25)(happy-dom@13.7.3)(sass@1.71.1)(terser@5.24.0):
+ /vitest@1.3.1(@types/node@20.11.26)(happy-dom@13.7.8)(sass@1.71.1)(terser@5.24.0):
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -10104,7 +10104,7 @@ packages:
jsdom:
optional: true
dependencies:
- '@types/node': 20.11.25
+ '@types/node': 20.11.26
'@vitest/expect': 1.3.1
'@vitest/runner': 1.3.1
'@vitest/snapshot': 1.3.1
@@ -10114,7 +10114,7 @@ packages:
chai: 4.3.10
debug: 4.3.4(supports-color@8.1.1)
execa: 8.0.1
- happy-dom: 13.7.3
+ happy-dom: 13.7.8
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.1
@@ -10123,8 +10123,8 @@ packages:
strip-literal: 2.0.0
tinybench: 2.5.1
tinypool: 0.8.2
- vite: 5.1.5(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
- vite-node: 1.3.1(@types/node@20.11.25)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite-node: 1.3.1(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
From 40bdecfe0df52bc4a057e8408f0fb59a46bce35f Mon Sep 17 00:00:00 2001
From: renovate
Date: Mon, 11 Mar 2024 12:06:37 +0000
Subject: [PATCH 23/66] fix(deps): update dependency date-fns to v3.4.0
---
frontend/package.json | 2 +-
frontend/pnpm-lock.yaml | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index f000a5356..2e13ebb2d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -101,7 +101,7 @@
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
- "date-fns": "3.3.1",
+ "date-fns": "3.4.0",
"dayjs": "1.11.10",
"dompurify": "3.0.9",
"fast-deep-equal": "3.1.3",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 8806e06dc..67670afed 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -164,8 +164,8 @@ dependencies:
specifier: 4.1.2
version: 4.1.2
date-fns:
- specifier: 3.3.1
- version: 3.3.1
+ specifier: 3.4.0
+ version: 3.4.0
dayjs:
specifier: 1.11.10
version: 1.11.10
@@ -5335,8 +5335,8 @@ packages:
whatwg-url: 11.0.0
dev: true
- /date-fns@3.3.1:
- resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==}
+ /date-fns@3.4.0:
+ resolution: {integrity: sha512-Akz4R8J9MXBsOgF1QeWeCsbv6pntT5KCPjU0Q9prBxVmWJYPLhwAIsNg3b0QAdr0ttiozYLD3L/af7Ra0jqYXw==}
dev: false
/dayjs@1.11.10:
From b9c513f681a88253391061ab30c6b8ee59e03b15 Mon Sep 17 00:00:00 2001
From: renovate
Date: Tue, 12 Mar 2024 09:07:14 +0000
Subject: [PATCH 24/66] fix(deps): update sentry-javascript monorepo to
v7.106.1
---
frontend/package.json | 4 +-
frontend/pnpm-lock.yaml | 106 ++++++++++++++++++++--------------------
2 files changed, 55 insertions(+), 55 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 2e13ebb2d..b3e7ee027 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -58,8 +58,8 @@
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "3.0.1",
"@kyvg/vue3-notification": "3.2.0",
- "@sentry/tracing": "7.106.0",
- "@sentry/vue": "7.106.0",
+ "@sentry/tracing": "7.106.1",
+ "@sentry/vue": "7.106.1",
"@tiptap/core": "2.2.4",
"@tiptap/extension-blockquote": "2.2.4",
"@tiptap/extension-bold": "2.2.4",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 67670afed..fbb8ddca6 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -35,11 +35,11 @@ dependencies:
specifier: 3.2.0
version: 3.2.0(vue@3.4.21)
'@sentry/tracing':
- specifier: 7.106.0
- version: 7.106.0
+ specifier: 7.106.1
+ version: 7.106.1
'@sentry/vue':
- specifier: 7.106.0
- version: 7.106.0(vue@3.4.21)
+ specifier: 7.106.1
+ version: 7.106.1(vue@3.4.21)
'@tiptap/core':
specifier: 2.2.4
version: 2.2.4(@tiptap/pm@2.2.4)
@@ -3243,45 +3243,45 @@ packages:
resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
dev: true
- /@sentry-internal/feedback@7.106.0:
- resolution: {integrity: sha512-Uz6pv3SN8XORTMme5xPxP/kuho7CAA6E/pMlpMjsojjBbnwLIICu10JaEZNsF/AtEya1RcNVTyPCrtF1F3sBYA==}
+ /@sentry-internal/feedback@7.106.1:
+ resolution: {integrity: sha512-udYR7rQnnQJ0q4PP3R7lTFx7cUz3SB4ghm8T/fJzdItrk+Puv6y8VqI19SFfDgvwgStInEzE5yys6SUQcXLBtA==}
engines: {node: '>=12'}
dependencies:
- '@sentry/core': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry/core': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
- /@sentry-internal/replay-canvas@7.106.0:
- resolution: {integrity: sha512-59qmT6XqbwpQuK1nVmv+XFxgd80gpYNH3aqgF5BEKux23kRB02/ARR5MwYyIHgVO0JhwdGIuiTfiLVNDu+nwTQ==}
+ /@sentry-internal/replay-canvas@7.106.1:
+ resolution: {integrity: sha512-r+nhLrQuTQih93gZ08F6MLdmaoBy/bQFcVt/2ZVqe1SkDY+MxRlXxq8ydo3FfgEjMRHdody3yT1dj6E174h23w==}
engines: {node: '>=12'}
dependencies:
- '@sentry/core': 7.106.0
- '@sentry/replay': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry/core': 7.106.1
+ '@sentry/replay': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
- /@sentry-internal/tracing@7.106.0:
- resolution: {integrity: sha512-O8Es6Sa/tP80nfl+8soNfWzeRNFcT484SvjLR8BS3pHM9KDAlwNXyoQhFr2BKNYL1irbq6UF6eku4xCnUKVmqA==}
+ /@sentry-internal/tracing@7.106.1:
+ resolution: {integrity: sha512-Ui9zSmW88jTdmNnNBLYYpNoAi31esX5/auysC3v7+SpwxIsC3AGLFvXs4EPziyz8d0F62Ji0fNQZ96ui4fO6BQ==}
engines: {node: '>=8'}
dependencies:
- '@sentry/core': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry/core': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
- /@sentry/browser@7.106.0:
- resolution: {integrity: sha512-OrHdw44giTtMa1DmlIUMBN4ypj1xTES9DLjq16ufK+bLqW3rWzwCuTy0sb9ZmSxc7fL2pdBlsL+sECiS+U2TEw==}
+ /@sentry/browser@7.106.1:
+ resolution: {integrity: sha512-+Yp7OUx78ZwFFYfIvOKZGjMPW7Ds3zZSO8dsMxvDRzkA9NyyAmYMZ/dNTcsGb+PssgkCasF2XA07f6WgkNW92A==}
engines: {node: '>=8'}
dependencies:
- '@sentry-internal/feedback': 7.106.0
- '@sentry-internal/replay-canvas': 7.106.0
- '@sentry-internal/tracing': 7.106.0
- '@sentry/core': 7.106.0
- '@sentry/replay': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry-internal/feedback': 7.106.1
+ '@sentry-internal/replay-canvas': 7.106.1
+ '@sentry-internal/tracing': 7.106.1
+ '@sentry/core': 7.106.1
+ '@sentry/replay': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
/@sentry/cli@2.19.1:
@@ -3300,53 +3300,53 @@ packages:
- supports-color
dev: true
- /@sentry/core@7.106.0:
- resolution: {integrity: sha512-Dc13XtnyFaXup2E4vCbzuG0QKAVjrJBk4qfGwvSJaTuopEaEWBs2MpK6hRzFhsz9S3T0La7c1F/62NptvTUWsQ==}
+ /@sentry/core@7.106.1:
+ resolution: {integrity: sha512-cwCd66wkbutXCI8j14JLkyod9RHtqSNfzGpx/ieBE+N786jX+Yj1DiaZJ6ZYjKQpnToipFnacEakCd9Vc9oePA==}
engines: {node: '>=8'}
dependencies:
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
- /@sentry/replay@7.106.0:
- resolution: {integrity: sha512-buaAOvOI+3pFm+76vwtxSxciBATHyR78aDjStghJZcIpFDNF31K8ZV0uP9+EUPbXHohtkTwZ86cn/P9cyY6NgA==}
+ /@sentry/replay@7.106.1:
+ resolution: {integrity: sha512-UnuY6bj7v7CVv3T1sbLHjLutSG4hzcQQj6CjEB2NUpM+QAIguFrwAcYG4U42iNg4Qeg5q4kHi1rPpdpvh6unSA==}
engines: {node: '>=12'}
dependencies:
- '@sentry-internal/tracing': 7.106.0
- '@sentry/core': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry-internal/tracing': 7.106.1
+ '@sentry/core': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
dev: false
- /@sentry/tracing@7.106.0:
- resolution: {integrity: sha512-qHlRnNLcQpj7d/tSXL9uk49fjtfgKhd1VdqOcb66m180PF1vwIxzr3yPQ7wFZa91sHqf7Xto7faax8DnaDmpoQ==}
+ /@sentry/tracing@7.106.1:
+ resolution: {integrity: sha512-oVAxUhXR41SGpAXjfp/c1cmoUQZI0NSzn6iW95fGHN0o5FBXEU0Pxet/BAG0ik1E1dH6QHnWpKef5TytE1Nqsg==}
engines: {node: '>=8'}
dependencies:
- '@sentry-internal/tracing': 7.106.0
+ '@sentry-internal/tracing': 7.106.1
dev: false
- /@sentry/types@7.106.0:
- resolution: {integrity: sha512-oKTkDaL6P9xJC5/zHLRemHTWboUqRYjkJNaZCN63j4kJqGy56wee4vDtDese/NWWn4U4C1QV1h+Mifm2HmDcQg==}
+ /@sentry/types@7.106.1:
+ resolution: {integrity: sha512-g3OcyAHGugBwkQP4fZYCCZqF2ng9K7yQc9FVngKq/y7PwHm84epXdYYGDGgfQOIC1d5/GMaPxmzI5IIrZexzkg==}
engines: {node: '>=8'}
dev: false
- /@sentry/utils@7.106.0:
- resolution: {integrity: sha512-bVsePsXLpFu/1sH4rpJrPcnVxW2fXXfGfGxKs6Bm+dkOMbuVTlk/KAzIbdjCDIpVlrMDJmMNEv5xgTFjgWDkjw==}
+ /@sentry/utils@7.106.1:
+ resolution: {integrity: sha512-NIeuvB9MeDwrObbi6W5xRrNTcQj8klVvwWWYQB0zotY/LDjyl+c+cZzUshFOxBTp9ljVnYzWqZ7J8x/i4baj7w==}
engines: {node: '>=8'}
dependencies:
- '@sentry/types': 7.106.0
+ '@sentry/types': 7.106.1
dev: false
- /@sentry/vue@7.106.0(vue@3.4.21):
- resolution: {integrity: sha512-lKXLWtH1lArkURYkWdqpxGPvHgVfxnRSvdxUsWmCZGJP40Yvui2O3cfH/QIIyf+O7XjqJFI6z6/9Ou3av2A8MQ==}
+ /@sentry/vue@7.106.1(vue@3.4.21):
+ resolution: {integrity: sha512-XHrTtepinms5ZaKnOm8efLNxMSXMQL9j3ERy8pqrCrQCCTLrj9f9MaF/OyJkEZDgRYBxAnxPFiUyP4JNN9YCLw==}
engines: {node: '>=8'}
peerDependencies:
vue: 2.x || 3.x
dependencies:
- '@sentry/browser': 7.106.0
- '@sentry/core': 7.106.0
- '@sentry/types': 7.106.0
- '@sentry/utils': 7.106.0
+ '@sentry/browser': 7.106.1
+ '@sentry/core': 7.106.1
+ '@sentry/types': 7.106.1
+ '@sentry/utils': 7.106.1
vue: 3.4.21(typescript@5.4.2)
dev: false
From a5c51d4b1ebf0a6bde33c0004c00eca5e0321038 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 19:25:58 +0000
Subject: [PATCH 25/66] feat: emoji reactions for tasks and comments (#2196)
This PR adds reactions for tasks and comments, similar to what you can do on Gitea, GitHub, Slack and plenty of other tools.
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2196
Co-authored-by: kolaente
Co-committed-by: kolaente
---
docs/content/doc/usage/errors.md | 1 +
frontend/package.json | 4 +-
frontend/patches/@github__hotkey@3.1.0.patch | 28 +++
frontend/pnpm-lock.yaml | 31 ++-
frontend/public/emojis.json | 1 +
frontend/src/components/input/Reactions.vue | 193 +++++++++++++++
.../src/components/input/editor/TipTap.vue | 4 +
frontend/src/components/misc/Icon.ts | 3 +-
.../components/tasks/partials/comments.vue | 7 +
frontend/src/i18n/lang/en.json | 6 +
frontend/src/modelTypes/IReaction.ts | 14 ++
frontend/src/modelTypes/ITask.ts | 3 +
frontend/src/modelTypes/ITaskComment.ts | 3 +
frontend/src/models/reaction.ts | 14 ++
frontend/src/models/task.ts | 8 +
frontend/src/models/taskComment.ts | 8 +
frontend/src/services/abstractService.ts | 29 ++-
frontend/src/services/reactions.ts | 32 +++
frontend/src/services/task.ts | 17 +-
frontend/src/services/taskComment.ts | 19 ++
frontend/src/stores/tasks.ts | 1 +
frontend/src/views/tasks/TaskDetailView.vue | 9 +
pkg/db/db.go | 11 +
pkg/db/fixtures/reactions.yml | 6 +
pkg/db/fixtures/task_comments.yml | 6 +
pkg/integrations/task_collection_test.go | 30 +--
pkg/migration/20240311173251.go | 50 ++++
pkg/models/error.go | 27 +++
pkg/models/models.go | 1 +
pkg/models/project.go | 6 +-
pkg/models/reaction.go | 191 +++++++++++++++
pkg/models/reaction_rights.go | 81 +++++++
pkg/models/reaction_test.go | 217 +++++++++++++++++
pkg/models/task_collection_test.go | 3 +
pkg/models/task_comments.go | 15 +-
pkg/models/tasks.go | 13 ++
pkg/models/unit_tests.go | 1 +
pkg/modules/migration/trello/trello.go | 1 +
pkg/routes/routes.go | 9 +
pkg/swagger/docs.go | 219 +++++++++++++++++-
pkg/swagger/swagger.json | 214 +++++++++++++++++
pkg/swagger/swagger.yaml | 143 ++++++++++++
pkg/user/user.go | 11 +-
43 files changed, 1653 insertions(+), 37 deletions(-)
create mode 100644 frontend/patches/@github__hotkey@3.1.0.patch
create mode 100644 frontend/public/emojis.json
create mode 100644 frontend/src/components/input/Reactions.vue
create mode 100644 frontend/src/modelTypes/IReaction.ts
create mode 100644 frontend/src/models/reaction.ts
create mode 100644 frontend/src/services/reactions.ts
create mode 100644 pkg/db/fixtures/reactions.yml
create mode 100644 pkg/migration/20240311173251.go
create mode 100644 pkg/models/reaction.go
create mode 100644 pkg/models/reaction_rights.go
create mode 100644 pkg/models/reaction_test.go
diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md
index fc364ee7e..4aff46443 100644
--- a/docs/content/doc/usage/errors.md
+++ b/docs/content/doc/usage/errors.md
@@ -97,6 +97,7 @@ This document describes the different errors Vikunja can return.
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
| 4023 | 409 | Tried to create a task relation which would create a cycle. |
| 4024 | 400 | The provided filter expression is invalid. |
+| 4025 | 400 | The reaction kind is invalid. |
## Team
diff --git a/frontend/package.json b/frontend/package.json
index b3e7ee027..424c68f46 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -123,6 +123,7 @@
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.10.1",
"vue-router": "4.3.0",
+ "vuemoji-picker": "^0.2.1",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"
},
@@ -184,7 +185,8 @@
},
"pnpm": {
"patchedDependencies": {
- "flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch"
+ "flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch",
+ "@github/hotkey@3.1.0": "patches/@github__hotkey@3.1.0.patch"
}
}
}
diff --git a/frontend/patches/@github__hotkey@3.1.0.patch b/frontend/patches/@github__hotkey@3.1.0.patch
new file mode 100644
index 000000000..da4b16547
--- /dev/null
+++ b/frontend/patches/@github__hotkey@3.1.0.patch
@@ -0,0 +1,28 @@
+diff --git a/dist/index.js b/dist/index.js
+index b6e6e0a6864cb00bc085b8d4503a705cb3bc8404..0466ef46406b0df41c8d0bb9a5bac9eabf4a50de 100644
+--- a/dist/index.js
++++ b/dist/index.js
+@@ -368,10 +368,12 @@ const sequenceTracker = new SequenceTracker({
+ function keyDownHandler(event) {
+ if (event.defaultPrevented)
+ return;
+- if (!(event.target instanceof Node))
++ const target = event.explicitOriginalTarget || event.target;
++ if (target.shadowRoot)
+ return;
+- if (isFormField(event.target)) {
+- const target = event.target;
++ if (!(target instanceof Node))
++ return;
++ if (isFormField(target)) {
+ if (!target.id)
+ return;
+ if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${target.id}"]`))
+@@ -385,7 +387,6 @@ function keyDownHandler(event) {
+ sequenceTracker.registerKeypress(event);
+ currentTriePosition = newTriePosition;
+ if (newTriePosition instanceof Leaf) {
+- const target = event.target;
+ let shouldFire = false;
+ let elementToFire;
+ const formField = isFormField(target);
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index fbb8ddca6..ff01839a5 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
patchedDependencies:
+ '@github/hotkey@3.1.0':
+ hash: c67tdk7qpd5grxd2zj6lsxfbou
+ path: patches/@github__hotkey@3.1.0.patch
flexsearch@0.7.31:
hash: bfn3sngfuhktmdj7jgl3ejl35y
path: patches/flexsearch@0.7.31.patch
@@ -24,7 +27,7 @@ dependencies:
version: 3.0.6(@fortawesome/fontawesome-svg-core@6.5.1)(vue@3.4.21)
'@github/hotkey':
specifier: 3.1.0
- version: 3.1.0
+ version: 3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou)
'@infectoone/vue-ganttastic':
specifier: 2.2.0
version: 2.2.0(dayjs@1.11.10)(vue@3.4.21)
@@ -229,6 +232,9 @@ dependencies:
vue-router:
specifier: 4.3.0
version: 4.3.0(vue@3.4.21)
+ vuemoji-picker:
+ specifier: ^0.2.1
+ version: 0.2.1(vue@3.4.21)
workbox-precaching:
specifier: 7.0.0
version: 7.0.0
@@ -2687,9 +2693,10 @@ packages:
vue: 3.4.21(typescript@5.4.2)
dev: false
- /@github/hotkey@3.1.0:
+ /@github/hotkey@3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou):
resolution: {integrity: sha512-Lj9QjYa+b+Nk5U1nZtlXLdx3HI8/EeM6ZNwBjpYcGVYqpwHdM2ScRH0p7+5zh28JG6SPbTM9+Rb1dFd742qMTw==}
dev: false
+ patched: true
/@hapi/hoek@9.2.1:
resolution: {integrity: sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==}
@@ -4358,7 +4365,7 @@ packages:
/@vueuse/shared@9.13.0(vue@3.4.21):
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
dependencies:
- vue-demi: 0.14.6(vue@3.4.21)
+ vue-demi: 0.14.7(vue@3.4.21)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
@@ -5610,6 +5617,10 @@ packages:
resolution: {integrity: sha512-yDYeobbTEe4TNooEzOQO6xFqg9XnAkVy2Lod1C1B2it8u47JNLYvl9nLDWBamqUakWB8Jc1hhS1uHUNYTNQdfw==}
dev: true
+ /emoji-picker-element@1.21.1:
+ resolution: {integrity: sha512-XO3buLicIjIb59dy3R2PVzpyxUEye7DSmHApbxFJxK8gCFPlGKP/Pld8ccWNYvny9t6vYhnKP1FNYgqqMy1XHA==}
+ dev: false
+
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
@@ -10273,6 +10284,20 @@ packages:
'@vue/shared': 3.4.21
typescript: 5.4.2
+ /vuemoji-picker@0.2.1(vue@3.4.21):
+ resolution: {integrity: sha512-wKRZBZclTdnQIT4jPzmkJ5Ci9ObzMFPjkuYb+/+/9h+mAZIUwdcPqYbEJCohbxJPoOvkuPVDeuOdTKR8hqqVLA==}
+ peerDependencies:
+ '@vue/composition-api': ^1.7.0
+ vue: ^2.6.14 || ^3.2.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+ dependencies:
+ emoji-picker-element: 1.21.1
+ vue: 3.4.21(typescript@5.4.2)
+ vue-demi: 0.14.7(vue@3.4.21)
+ dev: false
+
/w3c-keyname@2.2.6:
resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==}
diff --git a/frontend/public/emojis.json b/frontend/public/emojis.json
new file mode 100644
index 000000000..aee19111a
--- /dev/null
+++ b/frontend/public/emojis.json
@@ -0,0 +1 @@
+[{"shortcodes":["grinning","grinning_face"],"annotation":"grinning face","tags":["face","grin"],"emoji":"😀","order":1,"group":0,"version":1},{"shortcodes":["grinning_face_with_big_eyes","smiley"],"annotation":"grinning face with big eyes","tags":["face","mouth","open","smile"],"emoji":"😃","order":2,"group":0,"version":0.6},{"shortcodes":["grinning_face_with_closed_eyes","smile"],"annotation":"grinning face with smiling eyes","tags":["eye","face","mouth","open","smile"],"emoji":"😄","order":3,"group":0,"version":0.6,"emoticon":":D"},{"shortcodes":["beaming_face","grin"],"annotation":"beaming face with smiling eyes","tags":["eye","face","grin","smile"],"emoji":"😁","order":4,"group":0,"version":0.6},{"shortcodes":["laughing","lol","satisfied","squinting_face"],"annotation":"grinning squinting face","tags":["face","laugh","mouth","satisfied","smile"],"emoji":"😆","order":5,"group":0,"version":0.6,"emoticon":"XD"},{"shortcodes":["grinning_face_with_sweat","sweat_smile"],"annotation":"grinning face with sweat","tags":["cold","face","open","smile","sweat"],"emoji":"😅","order":6,"group":0,"version":0.6},{"shortcodes":["rofl"],"annotation":"rolling on the floor laughing","tags":["face","floor","laugh","rofl","rolling","rotfl"],"emoji":"🤣","order":7,"group":0,"version":3,"emoticon":":'D"},{"shortcodes":["joy","lmao","tears_of_joy"],"annotation":"face with tears of joy","tags":["face","joy","laugh","tear"],"emoji":"😂","order":8,"group":0,"version":0.6,"emoticon":":')"},{"shortcodes":["slightly_smiling_face"],"annotation":"slightly smiling face","tags":["face","smile"],"emoji":"🙂","order":9,"group":0,"version":1,"emoticon":":)"},{"shortcodes":["upside_down_face"],"annotation":"upside-down face","tags":["face","upside-down"],"emoji":"🙃","order":10,"group":0,"version":1},{"shortcodes":["melt","melting_face"],"annotation":"melting face","tags":["disappear","dissolve","liquid","melt"],"emoji":"🫠","order":11,"group":0,"version":14},{"shortcodes":["wink","winking_face"],"annotation":"winking face","tags":["face","wink"],"emoji":"😉","order":12,"group":0,"version":0.6,"emoticon":";)"},{"shortcodes":["blush","smiling_face_with_closed_eyes"],"annotation":"smiling face with smiling eyes","tags":["blush","eye","face","smile"],"emoji":"😊","order":13,"group":0,"version":0.6,"emoticon":":>"},{"shortcodes":["halo","innocent"],"annotation":"smiling face with halo","tags":["angel","face","fantasy","halo","innocent"],"emoji":"😇","order":14,"group":0,"version":1,"emoticon":"O:)"},{"shortcodes":["smiling_face_with_3_hearts"],"annotation":"smiling face with hearts","tags":["adore","crush","hearts","in love"],"emoji":"🥰","order":15,"group":0,"version":11},{"shortcodes":["heart_eyes","smiling_face_with_heart_eyes"],"annotation":"smiling face with heart-eyes","tags":["eye","face","love","smile"],"emoji":"😍","order":16,"group":0,"version":0.6},{"shortcodes":["star_struck"],"annotation":"star-struck","tags":["eyes","face","grinning","star"],"emoji":"🤩","order":17,"group":0,"version":5},{"shortcodes":["blowing_a_kiss","kissing_heart"],"annotation":"face blowing a kiss","tags":["face","kiss"],"emoji":"😘","order":18,"group":0,"version":0.6,"emoticon":":X"},{"shortcodes":["kissing","kissing_face"],"annotation":"kissing face","tags":["face","kiss"],"emoji":"😗","order":19,"group":0,"version":1},{"shortcodes":["relaxed","smiling_face"],"annotation":"smiling face","tags":["face","outlined","relaxed","smile"],"emoji":"☺️","order":21,"group":0,"version":0.6},{"shortcodes":["kissing_closed_eyes","kissing_face_with_closed_eyes"],"annotation":"kissing face with closed eyes","tags":["closed","eye","face","kiss"],"emoji":"😚","order":22,"group":0,"version":0.6,"emoticon":":*"},{"shortcodes":["kissing_face_with_smiling_eyes","kissing_smiling_eyes"],"annotation":"kissing face with smiling eyes","tags":["eye","face","kiss","smile"],"emoji":"😙","order":23,"group":0,"version":1},{"shortcodes":["smiling_face_with_tear"],"annotation":"smiling face with tear","tags":["grateful","proud","relieved","smiling","tear","touched"],"emoji":"🥲","order":24,"group":0,"version":13},{"shortcodes":["savoring_food","yum"],"annotation":"face savoring food","tags":["delicious","face","savouring","smile","yum"],"emoji":"😋","order":25,"group":0,"version":0.6},{"shortcodes":["face_with_tongue","stuck_out_tongue"],"annotation":"face with tongue","tags":["face","tongue"],"emoji":"😛","order":26,"group":0,"version":1,"emoticon":":P"},{"shortcodes":["stuck_out_tongue_winking_eye"],"annotation":"winking face with tongue","tags":["eye","face","joke","tongue","wink"],"emoji":"😜","order":27,"group":0,"version":0.6,"emoticon":";P"},{"shortcodes":["zany","zany_face"],"annotation":"zany face","tags":["eye","goofy","large","small"],"emoji":"🤪","order":28,"group":0,"version":5},{"shortcodes":["stuck_out_tongue_closed_eyes"],"annotation":"squinting face with tongue","tags":["eye","face","horrible","taste","tongue"],"emoji":"😝","order":29,"group":0,"version":0.6,"emoticon":"XP"},{"shortcodes":["money_mouth_face"],"annotation":"money-mouth face","tags":["face","money","mouth"],"emoji":"🤑","order":30,"group":0,"version":1},{"shortcodes":["hug","hugging","hugging_face"],"annotation":"smiling face with open hands","tags":["face","hug","hugging","open hands","smiling face"],"emoji":"🤗","order":31,"group":0,"version":1},{"shortcodes":["face_with_hand_over_mouth","hand_over_mouth"],"annotation":"face with hand over mouth","tags":["whoops"],"emoji":"🤭","order":32,"group":0,"version":5},{"shortcodes":["face_with_open_eyes_hand_over_mouth","gasp"],"annotation":"face with open eyes and hand over mouth","tags":["amazement","awe","disbelief","embarrass","scared","surprise"],"emoji":"🫢","order":33,"group":0,"version":14},{"shortcodes":["face_with_peeking_eye","peek"],"annotation":"face with peeking eye","tags":["captivated","peep","stare"],"emoji":"🫣","order":34,"group":0,"version":14},{"shortcodes":["shush","shushing_face"],"annotation":"shushing face","tags":["quiet","shush"],"emoji":"🤫","order":35,"group":0,"version":5},{"shortcodes":["thinking","thinking_face","wtf"],"annotation":"thinking face","tags":["face","thinking"],"emoji":"🤔","order":36,"group":0,"version":1,"emoticon":":L"},{"shortcodes":["salute","saluting_face"],"annotation":"saluting face","tags":["ok","salute","sunny","troops","yes"],"emoji":"🫡","order":37,"group":0,"version":14},{"shortcodes":["zipper_mouth","zipper_mouth_face"],"annotation":"zipper-mouth face","tags":["face","mouth","zip","zipper"],"emoji":"🤐","order":38,"group":0,"version":1,"emoticon":":Z"},{"shortcodes":["face_with_raised_eyebrow","raised_eyebrow"],"annotation":"face with raised eyebrow","tags":["distrust","skeptic"],"emoji":"🤨","order":39,"group":0,"version":5},{"shortcodes":["neutral","neutral_face"],"annotation":"neutral face","tags":["deadpan","face","meh","neutral"],"emoji":"😐️","order":40,"group":0,"version":0.7,"emoticon":":|"},{"shortcodes":["expressionless","expressionless_face"],"annotation":"expressionless face","tags":["expressionless","face","inexpressive","meh","unexpressive"],"emoji":"😑","order":41,"group":0,"version":1},{"shortcodes":["no_mouth"],"annotation":"face without mouth","tags":["face","mouth","quiet","silent"],"emoji":"😶","order":42,"group":0,"version":1,"emoticon":":#"},{"shortcodes":["dotted_line_face"],"annotation":"dotted line face","tags":["depressed","disappear","hide","introvert","invisible"],"emoji":"🫥","order":43,"group":0,"version":14},{"shortcodes":["in_clouds"],"annotation":"face in clouds","tags":["absentminded","face in the fog","head in clouds"],"emoji":"😶🌫️","order":44,"group":0,"version":13.1},{"shortcodes":["smirk","smirking","smirking_face"],"annotation":"smirking face","tags":["face","smirk"],"emoji":"😏","order":46,"group":0,"version":0.6,"emoticon":":j"},{"shortcodes":["unamused","unamused_face"],"annotation":"unamused face","tags":["face","unamused","unhappy"],"emoji":"😒","order":47,"group":0,"version":0.6,"emoticon":":?"},{"shortcodes":["rolling_eyes"],"annotation":"face with rolling eyes","tags":["eyeroll","eyes","face","rolling"],"emoji":"🙄","order":48,"group":0,"version":1},{"shortcodes":["grimacing","grimacing_face"],"annotation":"grimacing face","tags":["face","grimace"],"emoji":"😬","order":49,"group":0,"version":1,"emoticon":"8D"},{"shortcodes":["exhale","exhaling"],"annotation":"face exhaling","tags":["exhale","gasp","groan","relief","whisper","whistle"],"emoji":"😮💨","order":50,"group":0,"version":13.1},{"shortcodes":["lying","lying_face"],"annotation":"lying face","tags":["face","lie","pinocchio"],"emoji":"🤥","order":51,"group":0,"version":3},{"shortcodes":["shaking","shaking_face"],"annotation":"shaking face","tags":["earthquake","face","shaking","shock","vibrate"],"emoji":"🫨","order":52,"group":0,"version":15},{"shortcodes":["head_shaking_horizontally"],"annotation":"head shaking horizontally","tags":["no","shake"],"emoji":"🙂↔️","order":53,"group":0,"version":15.1},{"shortcodes":["head_shaking_vertically"],"annotation":"head shaking vertically","tags":["nod","yes"],"emoji":"🙂↕️","order":55,"group":0,"version":15.1},{"shortcodes":["relieved","relieved_face"],"annotation":"relieved face","tags":["face","relieved"],"emoji":"😌","order":57,"group":0,"version":0.6},{"shortcodes":["pensive","pensive_face"],"annotation":"pensive face","tags":["dejected","face","pensive"],"emoji":"😔","order":58,"group":0,"version":0.6},{"shortcodes":["sleepy","sleepy_face"],"annotation":"sleepy face","tags":["face","good night","sleep"],"emoji":"😪","order":59,"group":0,"version":0.6},{"shortcodes":["drooling","drooling_face"],"annotation":"drooling face","tags":["drooling","face"],"emoji":"🤤","order":60,"group":0,"version":3},{"shortcodes":["sleeping","sleeping_face"],"annotation":"sleeping face","tags":["face","good night","sleep","zzz"],"emoji":"😴","order":61,"group":0,"version":1},{"shortcodes":["mask","medical_mask"],"annotation":"face with medical mask","tags":["cold","doctor","face","mask","sick"],"emoji":"😷","order":62,"group":0,"version":0.6},{"shortcodes":["face_with_thermometer"],"annotation":"face with thermometer","tags":["face","ill","sick","thermometer"],"emoji":"🤒","order":63,"group":0,"version":1},{"shortcodes":["face_with_head_bandage"],"annotation":"face with head-bandage","tags":["bandage","face","hurt","injury"],"emoji":"🤕","order":64,"group":0,"version":1},{"shortcodes":["nauseated","nauseated_face"],"annotation":"nauseated face","tags":["face","nauseated","vomit"],"emoji":"🤢","order":65,"group":0,"version":3,"emoticon":"%("},{"shortcodes":["face_vomiting","vomiting"],"annotation":"face vomiting","tags":["puke","sick","vomit"],"emoji":"🤮","order":66,"group":0,"version":5},{"shortcodes":["sneezing","sneezing_face"],"annotation":"sneezing face","tags":["face","gesundheit","sneeze"],"emoji":"🤧","order":67,"group":0,"version":3},{"shortcodes":["hot","hot_face"],"annotation":"hot face","tags":["feverish","heat stroke","hot","red-faced","sweating"],"emoji":"🥵","order":68,"group":0,"version":11},{"shortcodes":["cold","cold_face"],"annotation":"cold face","tags":["blue-faced","cold","freezing","frostbite","icicles"],"emoji":"🥶","order":69,"group":0,"version":11},{"shortcodes":["woozy","woozy_face"],"annotation":"woozy face","tags":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth"],"emoji":"🥴","order":70,"group":0,"version":11,"emoticon":":&"},{"shortcodes":["dizzy_face","knocked_out"],"annotation":"face with crossed-out eyes","tags":["crossed-out eyes","dead","face","knocked out"],"emoji":"😵","order":71,"group":0,"version":0.6,"emoticon":"XO"},{"shortcodes":["dizzy_eyes"],"annotation":"face with spiral eyes","tags":["dizzy","hypnotized","spiral","trouble","whoa"],"emoji":"😵💫","order":72,"group":0,"version":13.1},{"shortcodes":["exploding_head"],"annotation":"exploding head","tags":["mind blown","shocked"],"emoji":"🤯","order":73,"group":0,"version":5},{"shortcodes":["cowboy","cowboy_face"],"annotation":"cowboy hat face","tags":["cowboy","cowgirl","face","hat"],"emoji":"🤠","order":74,"group":0,"version":3},{"shortcodes":["hooray","partying","partying_face"],"annotation":"partying face","tags":["celebration","hat","horn","party"],"emoji":"🥳","order":75,"group":0,"version":11},{"shortcodes":["disguised","disguised_face"],"annotation":"disguised face","tags":["disguise","face","glasses","incognito","nose"],"emoji":"🥸","order":76,"group":0,"version":13},{"shortcodes":["smiling_face_with_sunglasses","sunglasses_cool","too_cool"],"annotation":"smiling face with sunglasses","tags":["bright","cool","face","sun","sunglasses"],"emoji":"😎","order":77,"group":0,"version":1,"emoticon":"8)"},{"shortcodes":["nerd","nerd_face"],"annotation":"nerd face","tags":["face","geek","nerd"],"emoji":"🤓","order":78,"group":0,"version":1,"emoticon":":B"},{"shortcodes":["face_with_monocle"],"annotation":"face with monocle","tags":["face","monocle","stuffy"],"emoji":"🧐","order":79,"group":0,"version":5},{"shortcodes":["confused","confused_face"],"annotation":"confused face","tags":["confused","face","meh"],"emoji":"😕","order":80,"group":0,"version":1,"emoticon":":/"},{"shortcodes":["face_with_diagonal_mouth"],"annotation":"face with diagonal mouth","tags":["disappointed","meh","skeptical","unsure"],"emoji":"🫤","order":81,"group":0,"version":14},{"shortcodes":["worried","worried_face"],"annotation":"worried face","tags":["face","worried"],"emoji":"😟","order":82,"group":0,"version":1},{"shortcodes":["slightly_frowning_face"],"annotation":"slightly frowning face","tags":["face","frown"],"emoji":"🙁","order":83,"group":0,"version":1},{"shortcodes":["white_frowning_face"],"annotation":"frowning face","tags":["face","frown"],"emoji":"☹️","order":85,"group":0,"version":0.7,"emoticon":":("},{"shortcodes":["face_with_open_mouth","open_mouth"],"annotation":"face with open mouth","tags":["face","mouth","open","sympathy"],"emoji":"😮","order":86,"group":0,"version":1},{"shortcodes":["hushed","hushed_face"],"annotation":"hushed face","tags":["face","hushed","stunned","surprised"],"emoji":"😯","order":87,"group":0,"version":1},{"shortcodes":["astonished","astonished_face"],"annotation":"astonished face","tags":["astonished","face","shocked","totally"],"emoji":"😲","order":88,"group":0,"version":0.6,"emoticon":":O"},{"shortcodes":["flushed","flushed_face"],"annotation":"flushed face","tags":["dazed","face","flushed"],"emoji":"😳","order":89,"group":0,"version":0.6,"emoticon":":$"},{"shortcodes":["pleading","pleading_face"],"annotation":"pleading face","tags":["begging","mercy","puppy eyes"],"emoji":"🥺","order":90,"group":0,"version":11},{"shortcodes":["face_holding_back_tears","watery_eyes"],"annotation":"face holding back tears","tags":["angry","cry","proud","resist","sad"],"emoji":"🥹","order":91,"group":0,"version":14},{"shortcodes":["frowning","frowning_face"],"annotation":"frowning face with open mouth","tags":["face","frown","mouth","open"],"emoji":"😦","order":92,"group":0,"version":1},{"shortcodes":["anguished","anguished_face"],"annotation":"anguished face","tags":["anguished","face"],"emoji":"😧","order":93,"group":0,"version":1,"emoticon":":S"},{"shortcodes":["fearful","fearful_face"],"annotation":"fearful face","tags":["face","fear","fearful","scared"],"emoji":"😨","order":94,"group":0,"version":0.6},{"shortcodes":["anxious","anxious_face","cold_sweat"],"annotation":"anxious face with sweat","tags":["blue","cold","face","rushed","sweat"],"emoji":"😰","order":95,"group":0,"version":0.6},{"shortcodes":["disappointed_relieved","sad_relieved_face"],"annotation":"sad but relieved face","tags":["disappointed","face","relieved","whew"],"emoji":"😥","order":96,"group":0,"version":0.6},{"shortcodes":["cry","crying_face"],"annotation":"crying face","tags":["cry","face","sad","tear"],"emoji":"😢","order":97,"group":0,"version":0.6,"emoticon":":'("},{"shortcodes":["loudly_crying_face","sob"],"annotation":"loudly crying face","tags":["cry","face","sad","sob","tear"],"emoji":"😭","order":98,"group":0,"version":0.6,"emoticon":":'o"},{"shortcodes":["scream","screaming_in_fear"],"annotation":"face screaming in fear","tags":["face","fear","munch","scared","scream"],"emoji":"😱","order":99,"group":0,"version":0.6,"emoticon":"Dx"},{"shortcodes":["confounded","confounded_face"],"annotation":"confounded face","tags":["confounded","face"],"emoji":"😖","order":100,"group":0,"version":0.6,"emoticon":"X("},{"shortcodes":["persevere","persevering_face"],"annotation":"persevering face","tags":["face","persevere"],"emoji":"😣","order":101,"group":0,"version":0.6},{"shortcodes":["disappointed","disappointed_face"],"annotation":"disappointed face","tags":["disappointed","face"],"emoji":"😞","order":102,"group":0,"version":0.6},{"shortcodes":["downcast_face","sweat"],"annotation":"downcast face with sweat","tags":["cold","face","sweat"],"emoji":"😓","order":103,"group":0,"version":0.6,"emoticon":":<"},{"shortcodes":["weary","weary_face"],"annotation":"weary face","tags":["face","tired","weary"],"emoji":"😩","order":104,"group":0,"version":0.6,"emoticon":"D:"},{"shortcodes":["tired","tired_face"],"annotation":"tired face","tags":["face","tired"],"emoji":"😫","order":105,"group":0,"version":0.6,"emoticon":":C"},{"shortcodes":["yawn","yawning","yawning_face"],"annotation":"yawning face","tags":["bored","tired","yawn"],"emoji":"🥱","order":106,"group":0,"version":12},{"shortcodes":["nose_steam","triumph"],"annotation":"face with steam from nose","tags":["face","triumph","won"],"emoji":"😤","order":107,"group":0,"version":0.6},{"shortcodes":["pout","pouting_face","rage"],"annotation":"enraged face","tags":["angry","enraged","face","mad","pouting","rage","red"],"emoji":"😡","order":108,"group":0,"version":0.6,"emoticon":">:/"},{"shortcodes":["angry","angry_face"],"annotation":"angry face","tags":["anger","angry","face","mad"],"emoji":"😠","order":109,"group":0,"version":0.6},{"shortcodes":["censored","face_with_symbols_on_mouth"],"annotation":"face with symbols on mouth","tags":["swearing"],"emoji":"🤬","order":110,"group":0,"version":5,"emoticon":":@"},{"shortcodes":["smiling_imp"],"annotation":"smiling face with horns","tags":["face","fairy tale","fantasy","horns","smile"],"emoji":"😈","order":111,"group":0,"version":1,"emoticon":">:)"},{"shortcodes":["angry_imp","imp"],"annotation":"angry face with horns","tags":["demon","devil","face","fantasy","imp"],"emoji":"👿","order":112,"group":0,"version":0.6,"emoticon":">:("},{"shortcodes":["skull"],"annotation":"skull","tags":["death","face","fairy tale","monster"],"emoji":"💀","order":113,"group":0,"version":0.6},{"shortcodes":["skull_and_crossbones"],"annotation":"skull and crossbones","tags":["crossbones","death","face","monster","skull"],"emoji":"☠️","order":115,"group":0,"version":1},{"shortcodes":["poop","shit"],"annotation":"pile of poo","tags":["dung","face","monster","poo","poop"],"emoji":"💩","order":116,"group":0,"version":0.6},{"shortcodes":["clown","clown_face"],"annotation":"clown face","tags":["clown","face"],"emoji":"🤡","order":117,"group":0,"version":3},{"shortcodes":["japanese_ogre","ogre"],"annotation":"ogre","tags":["creature","face","fairy tale","fantasy","monster"],"emoji":"👹","order":118,"group":0,"version":0.6,"emoticon":">0)"},{"shortcodes":["goblin","japanese_goblin"],"annotation":"goblin","tags":["creature","face","fairy tale","fantasy","monster"],"emoji":"👺","order":119,"group":0,"version":0.6},{"shortcodes":["ghost"],"annotation":"ghost","tags":["creature","face","fairy tale","fantasy","monster"],"emoji":"👻","order":120,"group":0,"version":0.6},{"shortcodes":["alien"],"annotation":"alien","tags":["creature","extraterrestrial","face","fantasy","ufo"],"emoji":"👽️","order":121,"group":0,"version":0.6},{"shortcodes":["alien_monster","space_invader"],"annotation":"alien monster","tags":["alien","creature","extraterrestrial","face","monster","ufo"],"emoji":"👾","order":122,"group":0,"version":0.6},{"shortcodes":["robot","robot_face"],"annotation":"robot","tags":["face","monster"],"emoji":"🤖","order":123,"group":0,"version":1},{"shortcodes":["grinning_cat","smiley_cat"],"annotation":"grinning cat","tags":["cat","face","grinning","mouth","open","smile"],"emoji":"😺","order":124,"group":0,"version":0.6},{"shortcodes":["grinning_cat_with_closed_eyes","smile_cat"],"annotation":"grinning cat with smiling eyes","tags":["cat","eye","face","grin","smile"],"emoji":"😸","order":125,"group":0,"version":0.6},{"shortcodes":["joy_cat","tears_of_joy_cat"],"annotation":"cat with tears of joy","tags":["cat","face","joy","tear"],"emoji":"😹","order":126,"group":0,"version":0.6},{"shortcodes":["heart_eyes_cat","smiling_cat_with_heart_eyes"],"annotation":"smiling cat with heart-eyes","tags":["cat","eye","face","heart","love","smile"],"emoji":"😻","order":127,"group":0,"version":0.6},{"shortcodes":["smirk_cat","wry_smile_cat"],"annotation":"cat with wry smile","tags":["cat","face","ironic","smile","wry"],"emoji":"😼","order":128,"group":0,"version":0.6},{"shortcodes":["kissing_cat"],"annotation":"kissing cat","tags":["cat","eye","face","kiss"],"emoji":"😽","order":129,"group":0,"version":0.6,"emoticon":":3"},{"shortcodes":["scream_cat","weary_cat"],"annotation":"weary cat","tags":["cat","face","oh","surprised","weary"],"emoji":"🙀","order":130,"group":0,"version":0.6},{"shortcodes":["crying_cat"],"annotation":"crying cat","tags":["cat","cry","face","sad","tear"],"emoji":"😿","order":131,"group":0,"version":0.6},{"shortcodes":["pouting_cat"],"annotation":"pouting cat","tags":["cat","face","pouting"],"emoji":"😾","order":132,"group":0,"version":0.6},{"shortcodes":["see_no_evil"],"annotation":"see-no-evil monkey","tags":["evil","face","forbidden","monkey","see"],"emoji":"🙈","order":133,"group":0,"version":0.6},{"shortcodes":["hear_no_evil"],"annotation":"hear-no-evil monkey","tags":["evil","face","forbidden","hear","monkey"],"emoji":"🙉","order":134,"group":0,"version":0.6},{"shortcodes":["speak_no_evil"],"annotation":"speak-no-evil monkey","tags":["evil","face","forbidden","monkey","speak"],"emoji":"🙊","order":135,"group":0,"version":0.6},{"shortcodes":["love_letter"],"annotation":"love letter","tags":["heart","letter","love","mail"],"emoji":"💌","order":136,"group":0,"version":0.6},{"shortcodes":["cupid","heart_with_arrow"],"annotation":"heart with arrow","tags":["arrow","cupid"],"emoji":"💘","order":137,"group":0,"version":0.6},{"shortcodes":["gift_heart","heart_with_ribbon"],"annotation":"heart with ribbon","tags":["ribbon","valentine"],"emoji":"💝","order":138,"group":0,"version":0.6},{"shortcodes":["sparkling_heart"],"annotation":"sparkling heart","tags":["excited","sparkle"],"emoji":"💖","order":139,"group":0,"version":0.6},{"shortcodes":["growing_heart","heartpulse"],"annotation":"growing heart","tags":["excited","growing","nervous","pulse"],"emoji":"💗","order":140,"group":0,"version":0.6},{"shortcodes":["beating_heart","heartbeat"],"annotation":"beating heart","tags":["beating","heartbeat","pulsating"],"emoji":"💓","order":141,"group":0,"version":0.6},{"shortcodes":["revolving_hearts"],"annotation":"revolving hearts","tags":["revolving"],"emoji":"💞","order":142,"group":0,"version":0.6},{"shortcodes":["two_hearts"],"annotation":"two hearts","tags":["love"],"emoji":"💕","order":143,"group":0,"version":0.6},{"shortcodes":["heart_decoration"],"annotation":"heart decoration","tags":["heart"],"emoji":"💟","order":144,"group":0,"version":0.6},{"shortcodes":["heart_exclamation"],"annotation":"heart exclamation","tags":["exclamation","mark","punctuation"],"emoji":"❣️","order":146,"group":0,"version":1},{"shortcodes":["broken_heart"],"annotation":"broken heart","tags":["break","broken"],"emoji":"💔","order":147,"group":0,"version":0.6,"emoticon":"3"},{"shortcodes":["heart_on_fire"],"annotation":"heart on fire","tags":["burn","heart","love","lust","sacred heart"],"emoji":"❤️🔥","order":148,"group":0,"version":13.1},{"shortcodes":["mending_heart"],"annotation":"mending heart","tags":["healthier","improving","mending","recovering","recuperating","well"],"emoji":"❤️🩹","order":150,"group":0,"version":13.1},{"shortcodes":["heart","red_heart"],"annotation":"red heart","tags":["heart"],"emoji":"❤️","order":153,"group":0,"version":0.6,"emoticon":"<3"},{"shortcodes":["pink_heart"],"annotation":"pink heart","tags":["cute","heart","like","love","pink"],"emoji":"🩷","order":154,"group":0,"version":15},{"shortcodes":["orange_heart"],"annotation":"orange heart","tags":["orange"],"emoji":"🧡","order":155,"group":0,"version":5},{"shortcodes":["yellow_heart"],"annotation":"yellow heart","tags":["yellow"],"emoji":"💛","order":156,"group":0,"version":0.6},{"shortcodes":["green_heart"],"annotation":"green heart","tags":["green"],"emoji":"💚","order":157,"group":0,"version":0.6},{"shortcodes":["blue_heart"],"annotation":"blue heart","tags":["blue"],"emoji":"💙","order":158,"group":0,"version":0.6},{"shortcodes":["light_blue_heart"],"annotation":"light blue heart","tags":["cyan","heart","light blue","teal"],"emoji":"🩵","order":159,"group":0,"version":15},{"shortcodes":["purple_heart"],"annotation":"purple heart","tags":["purple"],"emoji":"💜","order":160,"group":0,"version":0.6},{"shortcodes":["brown_heart"],"annotation":"brown heart","tags":["brown","heart"],"emoji":"🤎","order":161,"group":0,"version":12},{"shortcodes":["black_heart"],"annotation":"black heart","tags":["black","evil","wicked"],"emoji":"🖤","order":162,"group":0,"version":3},{"shortcodes":["gray_heart","grey_heart"],"annotation":"grey heart","tags":["gray","heart","silver","slate"],"emoji":"🩶","order":163,"group":0,"version":15},{"shortcodes":["white_heart"],"annotation":"white heart","tags":["heart","white"],"emoji":"🤍","order":164,"group":0,"version":12},{"shortcodes":["kiss"],"annotation":"kiss mark","tags":["kiss","lips"],"emoji":"💋","order":165,"group":0,"version":0.6},{"shortcodes":["100"],"annotation":"hundred points","tags":["100","full","hundred","score"],"emoji":"💯","order":166,"group":0,"version":0.6},{"shortcodes":["anger"],"annotation":"anger symbol","tags":["angry","comic","mad"],"emoji":"💢","order":167,"group":0,"version":0.6},{"shortcodes":["boom","collision"],"annotation":"collision","tags":["boom","comic"],"emoji":"💥","order":168,"group":0,"version":0.6},{"shortcodes":["dizzy"],"annotation":"dizzy","tags":["comic","star"],"emoji":"💫","order":169,"group":0,"version":0.6},{"shortcodes":["sweat_drops"],"annotation":"sweat droplets","tags":["comic","splashing","sweat"],"emoji":"💦","order":170,"group":0,"version":0.6},{"shortcodes":["dash","dashing_away"],"annotation":"dashing away","tags":["comic","dash","running"],"emoji":"💨","order":171,"group":0,"version":0.6},{"shortcodes":["hole"],"annotation":"hole","tags":["hole"],"emoji":"🕳️","order":173,"group":0,"version":0.7},{"shortcodes":["speech_balloon"],"annotation":"speech balloon","tags":["balloon","bubble","comic","dialog","speech"],"emoji":"💬","order":174,"group":0,"version":0.6},{"shortcodes":["eye_in_speech_bubble"],"annotation":"eye in speech bubble","tags":["balloon","bubble","eye","speech","witness"],"emoji":"👁️🗨️","order":175,"group":0,"version":2},{"shortcodes":["left_speech_bubble"],"annotation":"left speech bubble","tags":["balloon","bubble","dialog","speech"],"emoji":"🗨️","order":180,"group":0,"version":2},{"shortcodes":["right_anger_bubble"],"annotation":"right anger bubble","tags":["angry","balloon","bubble","mad"],"emoji":"🗯️","order":182,"group":0,"version":0.7},{"shortcodes":["thought_balloon"],"annotation":"thought balloon","tags":["balloon","bubble","comic","thought"],"emoji":"💭","order":183,"group":0,"version":1},{"shortcodes":["zzz"],"annotation":"ZZZ","tags":["comic","good night","sleep","zzz"],"emoji":"💤","order":184,"group":0,"version":0.6},{"shortcodes":["wave","waving_hand"],"annotation":"waving hand","tags":["hand","wave","waving"],"emoji":"👋","order":185,"group":1,"version":0.6,"skins":[{"emoji":"👋🏻","version":1,"tone":1},{"emoji":"👋🏼","version":1,"tone":2},{"emoji":"👋🏽","version":1,"tone":3},{"emoji":"👋🏾","version":1,"tone":4},{"emoji":"👋🏿","version":1,"tone":5}]},{"shortcodes":["raised_back_of_hand"],"annotation":"raised back of hand","tags":["backhand","raised"],"emoji":"🤚","order":191,"group":1,"version":3,"skins":[{"emoji":"🤚🏻","version":3,"tone":1},{"emoji":"🤚🏼","version":3,"tone":2},{"emoji":"🤚🏽","version":3,"tone":3},{"emoji":"🤚🏾","version":3,"tone":4},{"emoji":"🤚🏿","version":3,"tone":5}]},{"shortcodes":["raised_hand_with_fingers_splayed"],"annotation":"hand with fingers splayed","tags":["finger","hand","splayed"],"emoji":"🖐️","order":198,"group":1,"version":0.7,"skins":[{"emoji":"🖐🏻","version":1,"tone":1},{"emoji":"🖐🏼","version":1,"tone":2},{"emoji":"🖐🏽","version":1,"tone":3},{"emoji":"🖐🏾","version":1,"tone":4},{"emoji":"🖐🏿","version":1,"tone":5}]},{"shortcodes":["high_five","raised_hand"],"annotation":"raised hand","tags":["hand","high 5","high five"],"emoji":"✋️","order":204,"group":1,"version":0.6,"skins":[{"emoji":"✋🏻","version":1,"tone":1},{"emoji":"✋🏼","version":1,"tone":2},{"emoji":"✋🏽","version":1,"tone":3},{"emoji":"✋🏾","version":1,"tone":4},{"emoji":"✋🏿","version":1,"tone":5}]},{"shortcodes":["vulcan"],"annotation":"vulcan salute","tags":["finger","hand","spock","vulcan"],"emoji":"🖖","order":210,"group":1,"version":1,"skins":[{"emoji":"🖖🏻","version":1,"tone":1},{"emoji":"🖖🏼","version":1,"tone":2},{"emoji":"🖖🏽","version":1,"tone":3},{"emoji":"🖖🏾","version":1,"tone":4},{"emoji":"🖖🏿","version":1,"tone":5}]},{"shortcodes":["rightwards_hand"],"annotation":"rightwards hand","tags":["hand","right","rightward"],"emoji":"🫱","order":216,"group":1,"version":14,"skins":[{"emoji":"🫱🏻","version":14,"tone":1},{"emoji":"🫱🏼","version":14,"tone":2},{"emoji":"🫱🏽","version":14,"tone":3},{"emoji":"🫱🏾","version":14,"tone":4},{"emoji":"🫱🏿","version":14,"tone":5}]},{"shortcodes":["leftwards_hand"],"annotation":"leftwards hand","tags":["hand","left","leftward"],"emoji":"🫲","order":222,"group":1,"version":14,"skins":[{"emoji":"🫲🏻","version":14,"tone":1},{"emoji":"🫲🏼","version":14,"tone":2},{"emoji":"🫲🏽","version":14,"tone":3},{"emoji":"🫲🏾","version":14,"tone":4},{"emoji":"🫲🏿","version":14,"tone":5}]},{"shortcodes":["palm_down"],"annotation":"palm down hand","tags":["dismiss","drop","shoo"],"emoji":"🫳","order":228,"group":1,"version":14,"skins":[{"emoji":"🫳🏻","version":14,"tone":1},{"emoji":"🫳🏼","version":14,"tone":2},{"emoji":"🫳🏽","version":14,"tone":3},{"emoji":"🫳🏾","version":14,"tone":4},{"emoji":"🫳🏿","version":14,"tone":5}]},{"shortcodes":["palm_up"],"annotation":"palm up hand","tags":["beckon","catch","come","offer"],"emoji":"🫴","order":234,"group":1,"version":14,"skins":[{"emoji":"🫴🏻","version":14,"tone":1},{"emoji":"🫴🏼","version":14,"tone":2},{"emoji":"🫴🏽","version":14,"tone":3},{"emoji":"🫴🏾","version":14,"tone":4},{"emoji":"🫴🏿","version":14,"tone":5}]},{"shortcodes":["leftwards_pushing_hand"],"annotation":"leftwards pushing hand","tags":["high five","leftward","push","refuse","stop","wait"],"emoji":"🫷","order":240,"group":1,"version":15,"skins":[{"emoji":"🫷🏻","version":15,"tone":1},{"emoji":"🫷🏼","version":15,"tone":2},{"emoji":"🫷🏽","version":15,"tone":3},{"emoji":"🫷🏾","version":15,"tone":4},{"emoji":"🫷🏿","version":15,"tone":5}]},{"shortcodes":["rightwards_pushing_hand"],"annotation":"rightwards pushing hand","tags":["high five","push","refuse","rightward","stop","wait"],"emoji":"🫸","order":246,"group":1,"version":15,"skins":[{"emoji":"🫸🏻","version":15,"tone":1},{"emoji":"🫸🏼","version":15,"tone":2},{"emoji":"🫸🏽","version":15,"tone":3},{"emoji":"🫸🏾","version":15,"tone":4},{"emoji":"🫸🏿","version":15,"tone":5}]},{"shortcodes":["ok_hand"],"annotation":"OK hand","tags":["hand","ok"],"emoji":"👌","order":252,"group":1,"version":0.6,"skins":[{"emoji":"👌🏻","version":1,"tone":1},{"emoji":"👌🏼","version":1,"tone":2},{"emoji":"👌🏽","version":1,"tone":3},{"emoji":"👌🏾","version":1,"tone":4},{"emoji":"👌🏿","version":1,"tone":5}]},{"shortcodes":["pinch","pinched_fingers"],"annotation":"pinched fingers","tags":["fingers","hand gesture","interrogation","pinched","sarcastic"],"emoji":"🤌","order":258,"group":1,"version":13,"skins":[{"emoji":"🤌🏻","version":13,"tone":1},{"emoji":"🤌🏼","version":13,"tone":2},{"emoji":"🤌🏽","version":13,"tone":3},{"emoji":"🤌🏾","version":13,"tone":4},{"emoji":"🤌🏿","version":13,"tone":5}]},{"shortcodes":["pinching_hand"],"annotation":"pinching hand","tags":["small amount"],"emoji":"🤏","order":264,"group":1,"version":12,"skins":[{"emoji":"🤏🏻","version":12,"tone":1},{"emoji":"🤏🏼","version":12,"tone":2},{"emoji":"🤏🏽","version":12,"tone":3},{"emoji":"🤏🏾","version":12,"tone":4},{"emoji":"🤏🏿","version":12,"tone":5}]},{"shortcodes":["v","victory"],"annotation":"victory hand","tags":["hand","v","victory"],"emoji":"✌️","order":271,"group":1,"version":0.6,"skins":[{"emoji":"✌🏻","version":1,"tone":1},{"emoji":"✌🏼","version":1,"tone":2},{"emoji":"✌🏽","version":1,"tone":3},{"emoji":"✌🏾","version":1,"tone":4},{"emoji":"✌🏿","version":1,"tone":5}]},{"shortcodes":["fingers_crossed"],"annotation":"crossed fingers","tags":["cross","finger","hand","luck"],"emoji":"🤞","order":277,"group":1,"version":3,"skins":[{"emoji":"🤞🏻","version":3,"tone":1},{"emoji":"🤞🏼","version":3,"tone":2},{"emoji":"🤞🏽","version":3,"tone":3},{"emoji":"🤞🏾","version":3,"tone":4},{"emoji":"🤞🏿","version":3,"tone":5}]},{"shortcodes":["hand_with_index_finger_and_thumb_crossed"],"annotation":"hand with index finger and thumb crossed","tags":["expensive","heart","love","money","snap"],"emoji":"🫰","order":283,"group":1,"version":14,"skins":[{"emoji":"🫰🏻","version":14,"tone":1},{"emoji":"🫰🏼","version":14,"tone":2},{"emoji":"🫰🏽","version":14,"tone":3},{"emoji":"🫰🏾","version":14,"tone":4},{"emoji":"🫰🏿","version":14,"tone":5}]},{"shortcodes":["love_you_gesture"],"annotation":"love-you gesture","tags":["hand","ily"],"emoji":"🤟","order":289,"group":1,"version":5,"skins":[{"emoji":"🤟🏻","version":5,"tone":1},{"emoji":"🤟🏼","version":5,"tone":2},{"emoji":"🤟🏽","version":5,"tone":3},{"emoji":"🤟🏾","version":5,"tone":4},{"emoji":"🤟🏿","version":5,"tone":5}]},{"shortcodes":["metal","sign_of_the_horns"],"annotation":"sign of the horns","tags":["finger","hand","horns","rock-on"],"emoji":"🤘","order":295,"group":1,"version":1,"emoticon":"\\M/","skins":[{"emoji":"🤘🏻","version":1,"tone":1},{"emoji":"🤘🏼","version":1,"tone":2},{"emoji":"🤘🏽","version":1,"tone":3},{"emoji":"🤘🏾","version":1,"tone":4},{"emoji":"🤘🏿","version":1,"tone":5}]},{"shortcodes":["call_me_hand"],"annotation":"call me hand","tags":["call","hand","hang loose","shaka"],"emoji":"🤙","order":301,"group":1,"version":3,"skins":[{"emoji":"🤙🏻","version":3,"tone":1},{"emoji":"🤙🏼","version":3,"tone":2},{"emoji":"🤙🏽","version":3,"tone":3},{"emoji":"🤙🏾","version":3,"tone":4},{"emoji":"🤙🏿","version":3,"tone":5}]},{"shortcodes":["point_left"],"annotation":"backhand index pointing left","tags":["backhand","finger","hand","index","point"],"emoji":"👈️","order":307,"group":1,"version":0.6,"skins":[{"emoji":"👈🏻","version":1,"tone":1},{"emoji":"👈🏼","version":1,"tone":2},{"emoji":"👈🏽","version":1,"tone":3},{"emoji":"👈🏾","version":1,"tone":4},{"emoji":"👈🏿","version":1,"tone":5}]},{"shortcodes":["point_right"],"annotation":"backhand index pointing right","tags":["backhand","finger","hand","index","point"],"emoji":"👉️","order":313,"group":1,"version":0.6,"skins":[{"emoji":"👉🏻","version":1,"tone":1},{"emoji":"👉🏼","version":1,"tone":2},{"emoji":"👉🏽","version":1,"tone":3},{"emoji":"👉🏾","version":1,"tone":4},{"emoji":"👉🏿","version":1,"tone":5}]},{"shortcodes":["point_up"],"annotation":"backhand index pointing up","tags":["backhand","finger","hand","point","up"],"emoji":"👆️","order":319,"group":1,"version":0.6,"skins":[{"emoji":"👆🏻","version":1,"tone":1},{"emoji":"👆🏼","version":1,"tone":2},{"emoji":"👆🏽","version":1,"tone":3},{"emoji":"👆🏾","version":1,"tone":4},{"emoji":"👆🏿","version":1,"tone":5}]},{"shortcodes":["middle_finger"],"annotation":"middle finger","tags":["finger","hand"],"emoji":"🖕","order":325,"group":1,"version":1,"skins":[{"emoji":"🖕🏻","version":1,"tone":1},{"emoji":"🖕🏼","version":1,"tone":2},{"emoji":"🖕🏽","version":1,"tone":3},{"emoji":"🖕🏾","version":1,"tone":4},{"emoji":"🖕🏿","version":1,"tone":5}]},{"shortcodes":["point_down"],"annotation":"backhand index pointing down","tags":["backhand","down","finger","hand","point"],"emoji":"👇️","order":331,"group":1,"version":0.6,"skins":[{"emoji":"👇🏻","version":1,"tone":1},{"emoji":"👇🏼","version":1,"tone":2},{"emoji":"👇🏽","version":1,"tone":3},{"emoji":"👇🏾","version":1,"tone":4},{"emoji":"👇🏿","version":1,"tone":5}]},{"shortcodes":["point_up_2"],"annotation":"index pointing up","tags":["finger","hand","index","point","up"],"emoji":"☝️","order":338,"group":1,"version":0.6,"skins":[{"emoji":"☝🏻","version":1,"tone":1},{"emoji":"☝🏼","version":1,"tone":2},{"emoji":"☝🏽","version":1,"tone":3},{"emoji":"☝🏾","version":1,"tone":4},{"emoji":"☝🏿","version":1,"tone":5}]},{"shortcodes":["point_forward"],"annotation":"index pointing at the viewer","tags":["point","you"],"emoji":"🫵","order":344,"group":1,"version":14,"skins":[{"emoji":"🫵🏻","version":14,"tone":1},{"emoji":"🫵🏼","version":14,"tone":2},{"emoji":"🫵🏽","version":14,"tone":3},{"emoji":"🫵🏾","version":14,"tone":4},{"emoji":"🫵🏿","version":14,"tone":5}]},{"shortcodes":["+1","thumbsup","yes"],"annotation":"thumbs up","tags":["+1","hand","thumb","up"],"emoji":"👍️","order":350,"group":1,"version":0.6,"skins":[{"emoji":"👍🏻","version":1,"tone":1},{"emoji":"👍🏼","version":1,"tone":2},{"emoji":"👍🏽","version":1,"tone":3},{"emoji":"👍🏾","version":1,"tone":4},{"emoji":"👍🏿","version":1,"tone":5}]},{"shortcodes":["-1","no","thumbsdown"],"annotation":"thumbs down","tags":["-1","down","hand","thumb"],"emoji":"👎️","order":356,"group":1,"version":0.6,"skins":[{"emoji":"👎🏻","version":1,"tone":1},{"emoji":"👎🏼","version":1,"tone":2},{"emoji":"👎🏽","version":1,"tone":3},{"emoji":"👎🏾","version":1,"tone":4},{"emoji":"👎🏿","version":1,"tone":5}]},{"shortcodes":["fist"],"annotation":"raised fist","tags":["clenched","fist","hand","punch"],"emoji":"✊️","order":362,"group":1,"version":0.6,"skins":[{"emoji":"✊🏻","version":1,"tone":1},{"emoji":"✊🏼","version":1,"tone":2},{"emoji":"✊🏽","version":1,"tone":3},{"emoji":"✊🏾","version":1,"tone":4},{"emoji":"✊🏿","version":1,"tone":5}]},{"shortcodes":["punch"],"annotation":"oncoming fist","tags":["clenched","fist","hand","punch"],"emoji":"👊","order":368,"group":1,"version":0.6,"skins":[{"emoji":"👊🏻","version":1,"tone":1},{"emoji":"👊🏼","version":1,"tone":2},{"emoji":"👊🏽","version":1,"tone":3},{"emoji":"👊🏾","version":1,"tone":4},{"emoji":"👊🏿","version":1,"tone":5}]},{"shortcodes":["left_facing_fist"],"annotation":"left-facing fist","tags":["fist","leftwards"],"emoji":"🤛","order":374,"group":1,"version":3,"skins":[{"emoji":"🤛🏻","version":3,"tone":1},{"emoji":"🤛🏼","version":3,"tone":2},{"emoji":"🤛🏽","version":3,"tone":3},{"emoji":"🤛🏾","version":3,"tone":4},{"emoji":"🤛🏿","version":3,"tone":5}]},{"shortcodes":["right_facing_fist"],"annotation":"right-facing fist","tags":["fist","rightwards"],"emoji":"🤜","order":380,"group":1,"version":3,"skins":[{"emoji":"🤜🏻","version":3,"tone":1},{"emoji":"🤜🏼","version":3,"tone":2},{"emoji":"🤜🏽","version":3,"tone":3},{"emoji":"🤜🏾","version":3,"tone":4},{"emoji":"🤜🏿","version":3,"tone":5}]},{"shortcodes":["clap","clapping_hands"],"annotation":"clapping hands","tags":["clap","hand"],"emoji":"👏","order":386,"group":1,"version":0.6,"skins":[{"emoji":"👏🏻","version":1,"tone":1},{"emoji":"👏🏼","version":1,"tone":2},{"emoji":"👏🏽","version":1,"tone":3},{"emoji":"👏🏾","version":1,"tone":4},{"emoji":"👏🏿","version":1,"tone":5}]},{"shortcodes":["raised_hands"],"annotation":"raising hands","tags":["celebration","gesture","hand","hooray","raised"],"emoji":"🙌","order":392,"group":1,"version":0.6,"skins":[{"emoji":"🙌🏻","version":1,"tone":1},{"emoji":"🙌🏼","version":1,"tone":2},{"emoji":"🙌🏽","version":1,"tone":3},{"emoji":"🙌🏾","version":1,"tone":4},{"emoji":"🙌🏿","version":1,"tone":5}]},{"shortcodes":["heart_hands"],"annotation":"heart hands","tags":["love"],"emoji":"🫶","order":398,"group":1,"version":14,"skins":[{"emoji":"🫶🏻","version":14,"tone":1},{"emoji":"🫶🏼","version":14,"tone":2},{"emoji":"🫶🏽","version":14,"tone":3},{"emoji":"🫶🏾","version":14,"tone":4},{"emoji":"🫶🏿","version":14,"tone":5}]},{"shortcodes":["open_hands"],"annotation":"open hands","tags":["hand","open"],"emoji":"👐","order":404,"group":1,"version":0.6,"skins":[{"emoji":"👐🏻","version":1,"tone":1},{"emoji":"👐🏼","version":1,"tone":2},{"emoji":"👐🏽","version":1,"tone":3},{"emoji":"👐🏾","version":1,"tone":4},{"emoji":"👐🏿","version":1,"tone":5}]},{"shortcodes":["palms_up_together"],"annotation":"palms up together","tags":["prayer"],"emoji":"🤲","order":410,"group":1,"version":5,"skins":[{"emoji":"🤲🏻","version":5,"tone":1},{"emoji":"🤲🏼","version":5,"tone":2},{"emoji":"🤲🏽","version":5,"tone":3},{"emoji":"🤲🏾","version":5,"tone":4},{"emoji":"🤲🏿","version":5,"tone":5}]},{"shortcodes":["handshake"],"annotation":"handshake","tags":["agreement","hand","meeting","shake"],"emoji":"🤝","order":416,"group":1,"version":3,"skins":[{"emoji":"🤝🏻","version":14,"tone":1},{"emoji":"🤝🏼","version":14,"tone":2},{"emoji":"🤝🏽","version":14,"tone":3},{"emoji":"🤝🏾","version":14,"tone":4},{"emoji":"🤝🏿","version":14,"tone":5},{"emoji":"🫱🏻🫲🏼","version":14,"tone":[1,2]},{"emoji":"🫱🏻🫲🏽","version":14,"tone":[1,3]},{"emoji":"🫱🏻🫲🏾","version":14,"tone":[1,4]},{"emoji":"🫱🏻🫲🏿","version":14,"tone":[1,5]},{"emoji":"🫱🏼🫲🏻","version":14,"tone":[2,1]},{"emoji":"🫱🏼🫲🏽","version":14,"tone":[2,3]},{"emoji":"🫱🏼🫲🏾","version":14,"tone":[2,4]},{"emoji":"🫱🏼🫲🏿","version":14,"tone":[2,5]},{"emoji":"🫱🏽🫲🏻","version":14,"tone":[3,1]},{"emoji":"🫱🏽🫲🏼","version":14,"tone":[3,2]},{"emoji":"🫱🏽🫲🏾","version":14,"tone":[3,4]},{"emoji":"🫱🏽🫲🏿","version":14,"tone":[3,5]},{"emoji":"🫱🏾🫲🏻","version":14,"tone":[4,1]},{"emoji":"🫱🏾🫲🏼","version":14,"tone":[4,2]},{"emoji":"🫱🏾🫲🏽","version":14,"tone":[4,3]},{"emoji":"🫱🏾🫲🏿","version":14,"tone":[4,5]},{"emoji":"🫱🏿🫲🏻","version":14,"tone":[5,1]},{"emoji":"🫱🏿🫲🏼","version":14,"tone":[5,2]},{"emoji":"🫱🏿🫲🏽","version":14,"tone":[5,3]},{"emoji":"🫱🏿🫲🏾","version":14,"tone":[5,4]}]},{"shortcodes":["folded_hands","pray"],"annotation":"folded hands","tags":["ask","hand","high 5","high five","please","pray","thanks"],"emoji":"🙏","order":442,"group":1,"version":0.6,"skins":[{"emoji":"🙏🏻","version":1,"tone":1},{"emoji":"🙏🏼","version":1,"tone":2},{"emoji":"🙏🏽","version":1,"tone":3},{"emoji":"🙏🏾","version":1,"tone":4},{"emoji":"🙏🏿","version":1,"tone":5}]},{"shortcodes":["writing_hand"],"annotation":"writing hand","tags":["hand","write"],"emoji":"✍️","order":449,"group":1,"version":0.7,"skins":[{"emoji":"✍🏻","version":1,"tone":1},{"emoji":"✍🏼","version":1,"tone":2},{"emoji":"✍🏽","version":1,"tone":3},{"emoji":"✍🏾","version":1,"tone":4},{"emoji":"✍🏿","version":1,"tone":5}]},{"shortcodes":["nail_care","nail_polish"],"annotation":"nail polish","tags":["care","cosmetics","manicure","nail","polish"],"emoji":"💅","order":455,"group":1,"version":0.6,"skins":[{"emoji":"💅🏻","version":1,"tone":1},{"emoji":"💅🏼","version":1,"tone":2},{"emoji":"💅🏽","version":1,"tone":3},{"emoji":"💅🏾","version":1,"tone":4},{"emoji":"💅🏿","version":1,"tone":5}]},{"shortcodes":["selfie"],"annotation":"selfie","tags":["camera","phone"],"emoji":"🤳","order":461,"group":1,"version":3,"skins":[{"emoji":"🤳🏻","version":3,"tone":1},{"emoji":"🤳🏼","version":3,"tone":2},{"emoji":"🤳🏽","version":3,"tone":3},{"emoji":"🤳🏾","version":3,"tone":4},{"emoji":"🤳🏿","version":3,"tone":5}]},{"shortcodes":["muscle","right_bicep"],"annotation":"flexed biceps","tags":["biceps","comic","flex","muscle"],"emoji":"💪","order":467,"group":1,"version":0.6,"skins":[{"emoji":"💪🏻","version":1,"tone":1},{"emoji":"💪🏼","version":1,"tone":2},{"emoji":"💪🏽","version":1,"tone":3},{"emoji":"💪🏾","version":1,"tone":4},{"emoji":"💪🏿","version":1,"tone":5}]},{"shortcodes":["mechanical_arm"],"annotation":"mechanical arm","tags":["accessibility","prosthetic"],"emoji":"🦾","order":473,"group":1,"version":12},{"shortcodes":["mechanical_leg"],"annotation":"mechanical leg","tags":["accessibility","prosthetic"],"emoji":"🦿","order":474,"group":1,"version":12},{"shortcodes":["leg"],"annotation":"leg","tags":["kick","limb"],"emoji":"🦵","order":475,"group":1,"version":11,"skins":[{"emoji":"🦵🏻","version":11,"tone":1},{"emoji":"🦵🏼","version":11,"tone":2},{"emoji":"🦵🏽","version":11,"tone":3},{"emoji":"🦵🏾","version":11,"tone":4},{"emoji":"🦵🏿","version":11,"tone":5}]},{"shortcodes":["foot"],"annotation":"foot","tags":["kick","stomp"],"emoji":"🦶","order":481,"group":1,"version":11,"skins":[{"emoji":"🦶🏻","version":11,"tone":1},{"emoji":"🦶🏼","version":11,"tone":2},{"emoji":"🦶🏽","version":11,"tone":3},{"emoji":"🦶🏾","version":11,"tone":4},{"emoji":"🦶🏿","version":11,"tone":5}]},{"shortcodes":["ear"],"annotation":"ear","tags":["body"],"emoji":"👂️","order":487,"group":1,"version":0.6,"skins":[{"emoji":"👂🏻","version":1,"tone":1},{"emoji":"👂🏼","version":1,"tone":2},{"emoji":"👂🏽","version":1,"tone":3},{"emoji":"👂🏾","version":1,"tone":4},{"emoji":"👂🏿","version":1,"tone":5}]},{"shortcodes":["ear_with_hearing_aid","hearing_aid"],"annotation":"ear with hearing aid","tags":["accessibility","hard of hearing"],"emoji":"🦻","order":493,"group":1,"version":12,"skins":[{"emoji":"🦻🏻","version":12,"tone":1},{"emoji":"🦻🏼","version":12,"tone":2},{"emoji":"🦻🏽","version":12,"tone":3},{"emoji":"🦻🏾","version":12,"tone":4},{"emoji":"🦻🏿","version":12,"tone":5}]},{"shortcodes":["nose"],"annotation":"nose","tags":["body"],"emoji":"👃","order":499,"group":1,"version":0.6,"skins":[{"emoji":"👃🏻","version":1,"tone":1},{"emoji":"👃🏼","version":1,"tone":2},{"emoji":"👃🏽","version":1,"tone":3},{"emoji":"👃🏾","version":1,"tone":4},{"emoji":"👃🏿","version":1,"tone":5}]},{"shortcodes":["brain"],"annotation":"brain","tags":["intelligent"],"emoji":"🧠","order":505,"group":1,"version":5},{"shortcodes":["anatomical_heart"],"annotation":"anatomical heart","tags":["anatomical","cardiology","heart","organ","pulse"],"emoji":"🫀","order":506,"group":1,"version":13},{"shortcodes":["lungs"],"annotation":"lungs","tags":["breath","exhalation","inhalation","organ","respiration"],"emoji":"🫁","order":507,"group":1,"version":13},{"shortcodes":["tooth"],"annotation":"tooth","tags":["dentist"],"emoji":"🦷","order":508,"group":1,"version":11},{"shortcodes":["bone"],"annotation":"bone","tags":["skeleton"],"emoji":"🦴","order":509,"group":1,"version":11},{"shortcodes":["eyes"],"annotation":"eyes","tags":["eye","face"],"emoji":"👀","order":510,"group":1,"version":0.6},{"shortcodes":["eye"],"annotation":"eye","tags":["body"],"emoji":"👁️","order":512,"group":1,"version":0.7},{"shortcodes":["tongue"],"annotation":"tongue","tags":["body"],"emoji":"👅","order":513,"group":1,"version":0.6},{"shortcodes":["lips","mouth"],"annotation":"mouth","tags":["lips"],"emoji":"👄","order":514,"group":1,"version":0.6},{"shortcodes":["biting_lip"],"annotation":"biting lip","tags":["anxious","fear","flirting","nervous","uncomfortable","worried"],"emoji":"🫦","order":515,"group":1,"version":14},{"shortcodes":["baby"],"annotation":"baby","tags":["young"],"emoji":"👶","order":516,"group":1,"version":0.6,"skins":[{"emoji":"👶🏻","version":1,"tone":1},{"emoji":"👶🏼","version":1,"tone":2},{"emoji":"👶🏽","version":1,"tone":3},{"emoji":"👶🏾","version":1,"tone":4},{"emoji":"👶🏿","version":1,"tone":5}]},{"shortcodes":["child"],"annotation":"child","tags":["gender-neutral","unspecified gender","young"],"emoji":"🧒","order":522,"group":1,"version":5,"skins":[{"emoji":"🧒🏻","version":5,"tone":1},{"emoji":"🧒🏼","version":5,"tone":2},{"emoji":"🧒🏽","version":5,"tone":3},{"emoji":"🧒🏾","version":5,"tone":4},{"emoji":"🧒🏿","version":5,"tone":5}]},{"shortcodes":["boy"],"annotation":"boy","tags":["young"],"emoji":"👦","order":528,"group":1,"version":0.6,"skins":[{"emoji":"👦🏻","version":1,"tone":1},{"emoji":"👦🏼","version":1,"tone":2},{"emoji":"👦🏽","version":1,"tone":3},{"emoji":"👦🏾","version":1,"tone":4},{"emoji":"👦🏿","version":1,"tone":5}]},{"shortcodes":["girl"],"annotation":"girl","tags":["virgo","young","zodiac"],"emoji":"👧","order":534,"group":1,"version":0.6,"skins":[{"emoji":"👧🏻","version":1,"tone":1},{"emoji":"👧🏼","version":1,"tone":2},{"emoji":"👧🏽","version":1,"tone":3},{"emoji":"👧🏾","version":1,"tone":4},{"emoji":"👧🏿","version":1,"tone":5}]},{"shortcodes":["adult"],"annotation":"person","tags":["adult","gender-neutral","unspecified gender"],"emoji":"🧑","order":540,"group":1,"version":5,"skins":[{"emoji":"🧑🏻","version":5,"tone":1},{"emoji":"🧑🏼","version":5,"tone":2},{"emoji":"🧑🏽","version":5,"tone":3},{"emoji":"🧑🏾","version":5,"tone":4},{"emoji":"🧑🏿","version":5,"tone":5}]},{"shortcodes":["blond_haired"],"annotation":"person: blond hair","tags":["blond","blond-haired person","hair"],"emoji":"👱","order":546,"group":1,"version":0.6,"skins":[{"emoji":"👱🏻","version":1,"tone":1},{"emoji":"👱🏼","version":1,"tone":2},{"emoji":"👱🏽","version":1,"tone":3},{"emoji":"👱🏾","version":1,"tone":4},{"emoji":"👱🏿","version":1,"tone":5}]},{"shortcodes":["man"],"annotation":"man","tags":["adult"],"emoji":"👨","order":552,"group":1,"version":0.6,"skins":[{"emoji":"👨🏻","version":1,"tone":1},{"emoji":"👨🏼","version":1,"tone":2},{"emoji":"👨🏽","version":1,"tone":3},{"emoji":"👨🏾","version":1,"tone":4},{"emoji":"👨🏿","version":1,"tone":5}]},{"shortcodes":["person_bearded"],"annotation":"person: beard","tags":["beard","person"],"emoji":"🧔","order":558,"group":1,"version":5,"skins":[{"emoji":"🧔🏻","version":5,"tone":1},{"emoji":"🧔🏼","version":5,"tone":2},{"emoji":"🧔🏽","version":5,"tone":3},{"emoji":"🧔🏾","version":5,"tone":4},{"emoji":"🧔🏿","version":5,"tone":5}]},{"shortcodes":["man_bearded"],"annotation":"man: beard","tags":["beard","man"],"emoji":"🧔♂️","order":564,"group":1,"version":13.1,"skins":[{"emoji":"🧔🏻♂️","version":13.1,"tone":1},{"emoji":"🧔🏼♂️","version":13.1,"tone":2},{"emoji":"🧔🏽♂️","version":13.1,"tone":3},{"emoji":"🧔🏾♂️","version":13.1,"tone":4},{"emoji":"🧔🏿♂️","version":13.1,"tone":5}]},{"shortcodes":["woman_bearded"],"annotation":"woman: beard","tags":["beard","woman"],"emoji":"🧔♀️","order":576,"group":1,"version":13.1,"skins":[{"emoji":"🧔🏻♀️","version":13.1,"tone":1},{"emoji":"🧔🏼♀️","version":13.1,"tone":2},{"emoji":"🧔🏽♀️","version":13.1,"tone":3},{"emoji":"🧔🏾♀️","version":13.1,"tone":4},{"emoji":"🧔🏿♀️","version":13.1,"tone":5}]},{"shortcodes":["man_red_haired"],"annotation":"man: red hair","tags":["adult","man","red hair"],"emoji":"👨🦰","order":588,"group":1,"version":11,"skins":[{"emoji":"👨🏻🦰","version":11,"tone":1},{"emoji":"👨🏼🦰","version":11,"tone":2},{"emoji":"👨🏽🦰","version":11,"tone":3},{"emoji":"👨🏾🦰","version":11,"tone":4},{"emoji":"👨🏿🦰","version":11,"tone":5}]},{"shortcodes":["man_curly_haired"],"annotation":"man: curly hair","tags":["adult","curly hair","man"],"emoji":"👨🦱","order":594,"group":1,"version":11,"skins":[{"emoji":"👨🏻🦱","version":11,"tone":1},{"emoji":"👨🏼🦱","version":11,"tone":2},{"emoji":"👨🏽🦱","version":11,"tone":3},{"emoji":"👨🏾🦱","version":11,"tone":4},{"emoji":"👨🏿🦱","version":11,"tone":5}]},{"shortcodes":["man_white_haired"],"annotation":"man: white hair","tags":["adult","man","white hair"],"emoji":"👨🦳","order":600,"group":1,"version":11,"skins":[{"emoji":"👨🏻🦳","version":11,"tone":1},{"emoji":"👨🏼🦳","version":11,"tone":2},{"emoji":"👨🏽🦳","version":11,"tone":3},{"emoji":"👨🏾🦳","version":11,"tone":4},{"emoji":"👨🏿🦳","version":11,"tone":5}]},{"shortcodes":["man_bald"],"annotation":"man: bald","tags":["adult","bald","man"],"emoji":"👨🦲","order":606,"group":1,"version":11,"skins":[{"emoji":"👨🏻🦲","version":11,"tone":1},{"emoji":"👨🏼🦲","version":11,"tone":2},{"emoji":"👨🏽🦲","version":11,"tone":3},{"emoji":"👨🏾🦲","version":11,"tone":4},{"emoji":"👨🏿🦲","version":11,"tone":5}]},{"shortcodes":["woman"],"annotation":"woman","tags":["adult"],"emoji":"👩","order":612,"group":1,"version":0.6,"skins":[{"emoji":"👩🏻","version":1,"tone":1},{"emoji":"👩🏼","version":1,"tone":2},{"emoji":"👩🏽","version":1,"tone":3},{"emoji":"👩🏾","version":1,"tone":4},{"emoji":"👩🏿","version":1,"tone":5}]},{"shortcodes":["woman_red_haired"],"annotation":"woman: red hair","tags":["adult","red hair","woman"],"emoji":"👩🦰","order":618,"group":1,"version":11,"skins":[{"emoji":"👩🏻🦰","version":11,"tone":1},{"emoji":"👩🏼🦰","version":11,"tone":2},{"emoji":"👩🏽🦰","version":11,"tone":3},{"emoji":"👩🏾🦰","version":11,"tone":4},{"emoji":"👩🏿🦰","version":11,"tone":5}]},{"shortcodes":["red_haired"],"annotation":"person: red hair","tags":["adult","gender-neutral","person","red hair","unspecified gender"],"emoji":"🧑🦰","order":624,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦰","version":12.1,"tone":1},{"emoji":"🧑🏼🦰","version":12.1,"tone":2},{"emoji":"🧑🏽🦰","version":12.1,"tone":3},{"emoji":"🧑🏾🦰","version":12.1,"tone":4},{"emoji":"🧑🏿🦰","version":12.1,"tone":5}]},{"shortcodes":["woman_curly_haired"],"annotation":"woman: curly hair","tags":["adult","curly hair","woman"],"emoji":"👩🦱","order":630,"group":1,"version":11,"skins":[{"emoji":"👩🏻🦱","version":11,"tone":1},{"emoji":"👩🏼🦱","version":11,"tone":2},{"emoji":"👩🏽🦱","version":11,"tone":3},{"emoji":"👩🏾🦱","version":11,"tone":4},{"emoji":"👩🏿🦱","version":11,"tone":5}]},{"shortcodes":["curly_haired"],"annotation":"person: curly hair","tags":["adult","curly hair","gender-neutral","person","unspecified gender"],"emoji":"🧑🦱","order":636,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦱","version":12.1,"tone":1},{"emoji":"🧑🏼🦱","version":12.1,"tone":2},{"emoji":"🧑🏽🦱","version":12.1,"tone":3},{"emoji":"🧑🏾🦱","version":12.1,"tone":4},{"emoji":"🧑🏿🦱","version":12.1,"tone":5}]},{"shortcodes":["woman_white_haired"],"annotation":"woman: white hair","tags":["adult","white hair","woman"],"emoji":"👩🦳","order":642,"group":1,"version":11,"skins":[{"emoji":"👩🏻🦳","version":11,"tone":1},{"emoji":"👩🏼🦳","version":11,"tone":2},{"emoji":"👩🏽🦳","version":11,"tone":3},{"emoji":"👩🏾🦳","version":11,"tone":4},{"emoji":"👩🏿🦳","version":11,"tone":5}]},{"shortcodes":["white_haired"],"annotation":"person: white hair","tags":["adult","gender-neutral","person","unspecified gender","white hair"],"emoji":"🧑🦳","order":648,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦳","version":12.1,"tone":1},{"emoji":"🧑🏼🦳","version":12.1,"tone":2},{"emoji":"🧑🏽🦳","version":12.1,"tone":3},{"emoji":"🧑🏾🦳","version":12.1,"tone":4},{"emoji":"🧑🏿🦳","version":12.1,"tone":5}]},{"shortcodes":["woman_bald"],"annotation":"woman: bald","tags":["adult","bald","woman"],"emoji":"👩🦲","order":654,"group":1,"version":11,"skins":[{"emoji":"👩🏻🦲","version":11,"tone":1},{"emoji":"👩🏼🦲","version":11,"tone":2},{"emoji":"👩🏽🦲","version":11,"tone":3},{"emoji":"👩🏾🦲","version":11,"tone":4},{"emoji":"👩🏿🦲","version":11,"tone":5}]},{"shortcodes":["bald"],"annotation":"person: bald","tags":["adult","bald","gender-neutral","person","unspecified gender"],"emoji":"🧑🦲","order":660,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦲","version":12.1,"tone":1},{"emoji":"🧑🏼🦲","version":12.1,"tone":2},{"emoji":"🧑🏽🦲","version":12.1,"tone":3},{"emoji":"🧑🏾🦲","version":12.1,"tone":4},{"emoji":"🧑🏿🦲","version":12.1,"tone":5}]},{"shortcodes":["woman_blond_haired"],"annotation":"woman: blond hair","tags":["blond-haired woman","blonde","hair","woman"],"emoji":"👱♀️","order":666,"group":1,"version":4,"skins":[{"emoji":"👱🏻♀️","version":4,"tone":1},{"emoji":"👱🏼♀️","version":4,"tone":2},{"emoji":"👱🏽♀️","version":4,"tone":3},{"emoji":"👱🏾♀️","version":4,"tone":4},{"emoji":"👱🏿♀️","version":4,"tone":5}]},{"shortcodes":["man_blond_haired"],"annotation":"man: blond hair","tags":["blond","blond-haired man","hair","man"],"emoji":"👱♂️","order":678,"group":1,"version":4,"skins":[{"emoji":"👱🏻♂️","version":4,"tone":1},{"emoji":"👱🏼♂️","version":4,"tone":2},{"emoji":"👱🏽♂️","version":4,"tone":3},{"emoji":"👱🏾♂️","version":4,"tone":4},{"emoji":"👱🏿♂️","version":4,"tone":5}]},{"shortcodes":["older_adult"],"annotation":"older person","tags":["adult","gender-neutral","old","unspecified gender"],"emoji":"🧓","order":690,"group":1,"version":5,"skins":[{"emoji":"🧓🏻","version":5,"tone":1},{"emoji":"🧓🏼","version":5,"tone":2},{"emoji":"🧓🏽","version":5,"tone":3},{"emoji":"🧓🏾","version":5,"tone":4},{"emoji":"🧓🏿","version":5,"tone":5}]},{"shortcodes":["older_man"],"annotation":"old man","tags":["adult","man","old"],"emoji":"👴","order":696,"group":1,"version":0.6,"skins":[{"emoji":"👴🏻","version":1,"tone":1},{"emoji":"👴🏼","version":1,"tone":2},{"emoji":"👴🏽","version":1,"tone":3},{"emoji":"👴🏾","version":1,"tone":4},{"emoji":"👴🏿","version":1,"tone":5}]},{"shortcodes":["older_woman"],"annotation":"old woman","tags":["adult","old","woman"],"emoji":"👵","order":702,"group":1,"version":0.6,"skins":[{"emoji":"👵🏻","version":1,"tone":1},{"emoji":"👵🏼","version":1,"tone":2},{"emoji":"👵🏽","version":1,"tone":3},{"emoji":"👵🏾","version":1,"tone":4},{"emoji":"👵🏿","version":1,"tone":5}]},{"shortcodes":["person_frowning"],"annotation":"person frowning","tags":["frown","gesture"],"emoji":"🙍","order":708,"group":1,"version":0.6,"skins":[{"emoji":"🙍🏻","version":1,"tone":1},{"emoji":"🙍🏼","version":1,"tone":2},{"emoji":"🙍🏽","version":1,"tone":3},{"emoji":"🙍🏾","version":1,"tone":4},{"emoji":"🙍🏿","version":1,"tone":5}]},{"shortcodes":["man_frowning"],"annotation":"man frowning","tags":["frowning","gesture","man"],"emoji":"🙍♂️","order":714,"group":1,"version":4,"skins":[{"emoji":"🙍🏻♂️","version":4,"tone":1},{"emoji":"🙍🏼♂️","version":4,"tone":2},{"emoji":"🙍🏽♂️","version":4,"tone":3},{"emoji":"🙍🏾♂️","version":4,"tone":4},{"emoji":"🙍🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_frowning"],"annotation":"woman frowning","tags":["frowning","gesture","woman"],"emoji":"🙍♀️","order":726,"group":1,"version":4,"skins":[{"emoji":"🙍🏻♀️","version":4,"tone":1},{"emoji":"🙍🏼♀️","version":4,"tone":2},{"emoji":"🙍🏽♀️","version":4,"tone":3},{"emoji":"🙍🏾♀️","version":4,"tone":4},{"emoji":"🙍🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_pouting","pouting"],"annotation":"person pouting","tags":["gesture","pouting"],"emoji":"🙎","order":738,"group":1,"version":0.6,"skins":[{"emoji":"🙎🏻","version":1,"tone":1},{"emoji":"🙎🏼","version":1,"tone":2},{"emoji":"🙎🏽","version":1,"tone":3},{"emoji":"🙎🏾","version":1,"tone":4},{"emoji":"🙎🏿","version":1,"tone":5}]},{"shortcodes":["man_pouting"],"annotation":"man pouting","tags":["gesture","man","pouting"],"emoji":"🙎♂️","order":744,"group":1,"version":4,"skins":[{"emoji":"🙎🏻♂️","version":4,"tone":1},{"emoji":"🙎🏼♂️","version":4,"tone":2},{"emoji":"🙎🏽♂️","version":4,"tone":3},{"emoji":"🙎🏾♂️","version":4,"tone":4},{"emoji":"🙎🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_pouting"],"annotation":"woman pouting","tags":["gesture","pouting","woman"],"emoji":"🙎♀️","order":756,"group":1,"version":4,"skins":[{"emoji":"🙎🏻♀️","version":4,"tone":1},{"emoji":"🙎🏼♀️","version":4,"tone":2},{"emoji":"🙎🏽♀️","version":4,"tone":3},{"emoji":"🙎🏾♀️","version":4,"tone":4},{"emoji":"🙎🏿♀️","version":4,"tone":5}]},{"shortcodes":["no_good","person_gesturing_no"],"annotation":"person gesturing NO","tags":["forbidden","gesture","hand","person gesturing no","prohibited"],"emoji":"🙅","order":768,"group":1,"version":0.6,"skins":[{"emoji":"🙅🏻","version":1,"tone":1},{"emoji":"🙅🏼","version":1,"tone":2},{"emoji":"🙅🏽","version":1,"tone":3},{"emoji":"🙅🏾","version":1,"tone":4},{"emoji":"🙅🏿","version":1,"tone":5}]},{"shortcodes":["man_gesturing_no"],"annotation":"man gesturing NO","tags":["forbidden","gesture","hand","man","man gesturing no","prohibited"],"emoji":"🙅♂️","order":774,"group":1,"version":4,"skins":[{"emoji":"🙅🏻♂️","version":4,"tone":1},{"emoji":"🙅🏼♂️","version":4,"tone":2},{"emoji":"🙅🏽♂️","version":4,"tone":3},{"emoji":"🙅🏾♂️","version":4,"tone":4},{"emoji":"🙅🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_gesturing_no"],"annotation":"woman gesturing NO","tags":["forbidden","gesture","hand","prohibited","woman","woman gesturing no"],"emoji":"🙅♀️","order":786,"group":1,"version":4,"skins":[{"emoji":"🙅🏻♀️","version":4,"tone":1},{"emoji":"🙅🏼♀️","version":4,"tone":2},{"emoji":"🙅🏽♀️","version":4,"tone":3},{"emoji":"🙅🏾♀️","version":4,"tone":4},{"emoji":"🙅🏿♀️","version":4,"tone":5}]},{"shortcodes":["all_good","person_gesturing_ok"],"annotation":"person gesturing OK","tags":["gesture","hand","ok","person gesturing ok"],"emoji":"🙆","order":798,"group":1,"version":0.6,"skins":[{"emoji":"🙆🏻","version":1,"tone":1},{"emoji":"🙆🏼","version":1,"tone":2},{"emoji":"🙆🏽","version":1,"tone":3},{"emoji":"🙆🏾","version":1,"tone":4},{"emoji":"🙆🏿","version":1,"tone":5}]},{"shortcodes":["man_gesturing_ok"],"annotation":"man gesturing OK","tags":["gesture","hand","man","man gesturing ok","ok"],"emoji":"🙆♂️","order":804,"group":1,"version":4,"skins":[{"emoji":"🙆🏻♂️","version":4,"tone":1},{"emoji":"🙆🏼♂️","version":4,"tone":2},{"emoji":"🙆🏽♂️","version":4,"tone":3},{"emoji":"🙆🏾♂️","version":4,"tone":4},{"emoji":"🙆🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_gesturing_ok"],"annotation":"woman gesturing OK","tags":["gesture","hand","ok","woman","woman gesturing ok"],"emoji":"🙆♀️","order":816,"group":1,"version":4,"skins":[{"emoji":"🙆🏻♀️","version":4,"tone":1},{"emoji":"🙆🏼♀️","version":4,"tone":2},{"emoji":"🙆🏽♀️","version":4,"tone":3},{"emoji":"🙆🏾♀️","version":4,"tone":4},{"emoji":"🙆🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_tipping_hand"],"annotation":"person tipping hand","tags":["hand","help","information","sassy","tipping"],"emoji":"💁","order":828,"group":1,"version":0.6,"skins":[{"emoji":"💁🏻","version":1,"tone":1},{"emoji":"💁🏼","version":1,"tone":2},{"emoji":"💁🏽","version":1,"tone":3},{"emoji":"💁🏾","version":1,"tone":4},{"emoji":"💁🏿","version":1,"tone":5}]},{"shortcodes":["man_tipping_hand"],"annotation":"man tipping hand","tags":["man","sassy","tipping hand"],"emoji":"💁♂️","order":834,"group":1,"version":4,"skins":[{"emoji":"💁🏻♂️","version":4,"tone":1},{"emoji":"💁🏼♂️","version":4,"tone":2},{"emoji":"💁🏽♂️","version":4,"tone":3},{"emoji":"💁🏾♂️","version":4,"tone":4},{"emoji":"💁🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_tipping_hand"],"annotation":"woman tipping hand","tags":["sassy","tipping hand","woman"],"emoji":"💁♀️","order":846,"group":1,"version":4,"skins":[{"emoji":"💁🏻♀️","version":4,"tone":1},{"emoji":"💁🏼♀️","version":4,"tone":2},{"emoji":"💁🏽♀️","version":4,"tone":3},{"emoji":"💁🏾♀️","version":4,"tone":4},{"emoji":"💁🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_raising_hand"],"annotation":"person raising hand","tags":["gesture","hand","happy","raised"],"emoji":"🙋","order":858,"group":1,"version":0.6,"skins":[{"emoji":"🙋🏻","version":1,"tone":1},{"emoji":"🙋🏼","version":1,"tone":2},{"emoji":"🙋🏽","version":1,"tone":3},{"emoji":"🙋🏾","version":1,"tone":4},{"emoji":"🙋🏿","version":1,"tone":5}]},{"shortcodes":["man_raising_hand"],"annotation":"man raising hand","tags":["gesture","man","raising hand"],"emoji":"🙋♂️","order":864,"group":1,"version":4,"skins":[{"emoji":"🙋🏻♂️","version":4,"tone":1},{"emoji":"🙋🏼♂️","version":4,"tone":2},{"emoji":"🙋🏽♂️","version":4,"tone":3},{"emoji":"🙋🏾♂️","version":4,"tone":4},{"emoji":"🙋🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_raising_hand"],"annotation":"woman raising hand","tags":["gesture","raising hand","woman"],"emoji":"🙋♀️","order":876,"group":1,"version":4,"skins":[{"emoji":"🙋🏻♀️","version":4,"tone":1},{"emoji":"🙋🏼♀️","version":4,"tone":2},{"emoji":"🙋🏽♀️","version":4,"tone":3},{"emoji":"🙋🏾♀️","version":4,"tone":4},{"emoji":"🙋🏿♀️","version":4,"tone":5}]},{"shortcodes":["deaf_person"],"annotation":"deaf person","tags":["accessibility","deaf","ear","hear"],"emoji":"🧏","order":888,"group":1,"version":12,"skins":[{"emoji":"🧏🏻","version":12,"tone":1},{"emoji":"🧏🏼","version":12,"tone":2},{"emoji":"🧏🏽","version":12,"tone":3},{"emoji":"🧏🏾","version":12,"tone":4},{"emoji":"🧏🏿","version":12,"tone":5}]},{"shortcodes":["deaf_man"],"annotation":"deaf man","tags":["deaf","man"],"emoji":"🧏♂️","order":894,"group":1,"version":12,"skins":[{"emoji":"🧏🏻♂️","version":12,"tone":1},{"emoji":"🧏🏼♂️","version":12,"tone":2},{"emoji":"🧏🏽♂️","version":12,"tone":3},{"emoji":"🧏🏾♂️","version":12,"tone":4},{"emoji":"🧏🏿♂️","version":12,"tone":5}]},{"shortcodes":["deaf_woman"],"annotation":"deaf woman","tags":["deaf","woman"],"emoji":"🧏♀️","order":906,"group":1,"version":12,"skins":[{"emoji":"🧏🏻♀️","version":12,"tone":1},{"emoji":"🧏🏼♀️","version":12,"tone":2},{"emoji":"🧏🏽♀️","version":12,"tone":3},{"emoji":"🧏🏾♀️","version":12,"tone":4},{"emoji":"🧏🏿♀️","version":12,"tone":5}]},{"shortcodes":["bow","person_bowing"],"annotation":"person bowing","tags":["apology","bow","gesture","sorry"],"emoji":"🙇","order":918,"group":1,"version":0.6,"skins":[{"emoji":"🙇🏻","version":1,"tone":1},{"emoji":"🙇🏼","version":1,"tone":2},{"emoji":"🙇🏽","version":1,"tone":3},{"emoji":"🙇🏾","version":1,"tone":4},{"emoji":"🙇🏿","version":1,"tone":5}]},{"shortcodes":["man_bowing"],"annotation":"man bowing","tags":["apology","bowing","favor","gesture","man","sorry"],"emoji":"🙇♂️","order":924,"group":1,"version":4,"skins":[{"emoji":"🙇🏻♂️","version":4,"tone":1},{"emoji":"🙇🏼♂️","version":4,"tone":2},{"emoji":"🙇🏽♂️","version":4,"tone":3},{"emoji":"🙇🏾♂️","version":4,"tone":4},{"emoji":"🙇🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_bowing"],"annotation":"woman bowing","tags":["apology","bowing","favor","gesture","sorry","woman"],"emoji":"🙇♀️","order":936,"group":1,"version":4,"skins":[{"emoji":"🙇🏻♀️","version":4,"tone":1},{"emoji":"🙇🏼♀️","version":4,"tone":2},{"emoji":"🙇🏽♀️","version":4,"tone":3},{"emoji":"🙇🏾♀️","version":4,"tone":4},{"emoji":"🙇🏿♀️","version":4,"tone":5}]},{"shortcodes":["facepalm","person_facepalming"],"annotation":"person facepalming","tags":["disbelief","exasperation","face","palm"],"emoji":"🤦","order":948,"group":1,"version":3,"skins":[{"emoji":"🤦🏻","version":3,"tone":1},{"emoji":"🤦🏼","version":3,"tone":2},{"emoji":"🤦🏽","version":3,"tone":3},{"emoji":"🤦🏾","version":3,"tone":4},{"emoji":"🤦🏿","version":3,"tone":5}]},{"shortcodes":["man_facepalming"],"annotation":"man facepalming","tags":["disbelief","exasperation","facepalm","man"],"emoji":"🤦♂️","order":954,"group":1,"version":4,"skins":[{"emoji":"🤦🏻♂️","version":4,"tone":1},{"emoji":"🤦🏼♂️","version":4,"tone":2},{"emoji":"🤦🏽♂️","version":4,"tone":3},{"emoji":"🤦🏾♂️","version":4,"tone":4},{"emoji":"🤦🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_facepalming"],"annotation":"woman facepalming","tags":["disbelief","exasperation","facepalm","woman"],"emoji":"🤦♀️","order":966,"group":1,"version":4,"skins":[{"emoji":"🤦🏻♀️","version":4,"tone":1},{"emoji":"🤦🏼♀️","version":4,"tone":2},{"emoji":"🤦🏽♀️","version":4,"tone":3},{"emoji":"🤦🏾♀️","version":4,"tone":4},{"emoji":"🤦🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_shrugging","shrug"],"annotation":"person shrugging","tags":["doubt","ignorance","indifference","shrug"],"emoji":"🤷","order":978,"group":1,"version":3,"skins":[{"emoji":"🤷🏻","version":3,"tone":1},{"emoji":"🤷🏼","version":3,"tone":2},{"emoji":"🤷🏽","version":3,"tone":3},{"emoji":"🤷🏾","version":3,"tone":4},{"emoji":"🤷🏿","version":3,"tone":5}]},{"shortcodes":["man_shrugging"],"annotation":"man shrugging","tags":["doubt","ignorance","indifference","man","shrug"],"emoji":"🤷♂️","order":984,"group":1,"version":4,"skins":[{"emoji":"🤷🏻♂️","version":4,"tone":1},{"emoji":"🤷🏼♂️","version":4,"tone":2},{"emoji":"🤷🏽♂️","version":4,"tone":3},{"emoji":"🤷🏾♂️","version":4,"tone":4},{"emoji":"🤷🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_shrugging"],"annotation":"woman shrugging","tags":["doubt","ignorance","indifference","shrug","woman"],"emoji":"🤷♀️","order":996,"group":1,"version":4,"skins":[{"emoji":"🤷🏻♀️","version":4,"tone":1},{"emoji":"🤷🏼♀️","version":4,"tone":2},{"emoji":"🤷🏽♀️","version":4,"tone":3},{"emoji":"🤷🏾♀️","version":4,"tone":4},{"emoji":"🤷🏿♀️","version":4,"tone":5}]},{"shortcodes":["health_worker"],"annotation":"health worker","tags":["doctor","healthcare","nurse","therapist"],"emoji":"🧑⚕️","order":1008,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻⚕️","version":12.1,"tone":1},{"emoji":"🧑🏼⚕️","version":12.1,"tone":2},{"emoji":"🧑🏽⚕️","version":12.1,"tone":3},{"emoji":"🧑🏾⚕️","version":12.1,"tone":4},{"emoji":"🧑🏿⚕️","version":12.1,"tone":5}]},{"shortcodes":["man_health_worker"],"annotation":"man health worker","tags":["doctor","healthcare","man","nurse","therapist"],"emoji":"👨⚕️","order":1020,"group":1,"version":4,"skins":[{"emoji":"👨🏻⚕️","version":4,"tone":1},{"emoji":"👨🏼⚕️","version":4,"tone":2},{"emoji":"👨🏽⚕️","version":4,"tone":3},{"emoji":"👨🏾⚕️","version":4,"tone":4},{"emoji":"👨🏿⚕️","version":4,"tone":5}]},{"shortcodes":["woman_health_worker"],"annotation":"woman health worker","tags":["doctor","healthcare","nurse","therapist","woman"],"emoji":"👩⚕️","order":1032,"group":1,"version":4,"skins":[{"emoji":"👩🏻⚕️","version":4,"tone":1},{"emoji":"👩🏼⚕️","version":4,"tone":2},{"emoji":"👩🏽⚕️","version":4,"tone":3},{"emoji":"👩🏾⚕️","version":4,"tone":4},{"emoji":"👩🏿⚕️","version":4,"tone":5}]},{"shortcodes":["student"],"annotation":"student","tags":["graduate"],"emoji":"🧑🎓","order":1044,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🎓","version":12.1,"tone":1},{"emoji":"🧑🏼🎓","version":12.1,"tone":2},{"emoji":"🧑🏽🎓","version":12.1,"tone":3},{"emoji":"🧑🏾🎓","version":12.1,"tone":4},{"emoji":"🧑🏿🎓","version":12.1,"tone":5}]},{"shortcodes":["man_student"],"annotation":"man student","tags":["graduate","man","student"],"emoji":"👨🎓","order":1050,"group":1,"version":4,"skins":[{"emoji":"👨🏻🎓","version":4,"tone":1},{"emoji":"👨🏼🎓","version":4,"tone":2},{"emoji":"👨🏽🎓","version":4,"tone":3},{"emoji":"👨🏾🎓","version":4,"tone":4},{"emoji":"👨🏿🎓","version":4,"tone":5}]},{"shortcodes":["woman_student"],"annotation":"woman student","tags":["graduate","student","woman"],"emoji":"👩🎓","order":1056,"group":1,"version":4,"skins":[{"emoji":"👩🏻🎓","version":4,"tone":1},{"emoji":"👩🏼🎓","version":4,"tone":2},{"emoji":"👩🏽🎓","version":4,"tone":3},{"emoji":"👩🏾🎓","version":4,"tone":4},{"emoji":"👩🏿🎓","version":4,"tone":5}]},{"shortcodes":["teacher"],"annotation":"teacher","tags":["instructor","lecturer","professor"],"emoji":"🧑🏫","order":1062,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🏫","version":12.1,"tone":1},{"emoji":"🧑🏼🏫","version":12.1,"tone":2},{"emoji":"🧑🏽🏫","version":12.1,"tone":3},{"emoji":"🧑🏾🏫","version":12.1,"tone":4},{"emoji":"🧑🏿🏫","version":12.1,"tone":5}]},{"shortcodes":["man_teacher"],"annotation":"man teacher","tags":["instructor","lecturer","man","professor","teacher"],"emoji":"👨🏫","order":1068,"group":1,"version":4,"skins":[{"emoji":"👨🏻🏫","version":4,"tone":1},{"emoji":"👨🏼🏫","version":4,"tone":2},{"emoji":"👨🏽🏫","version":4,"tone":3},{"emoji":"👨🏾🏫","version":4,"tone":4},{"emoji":"👨🏿🏫","version":4,"tone":5}]},{"shortcodes":["woman_teacher"],"annotation":"woman teacher","tags":["instructor","lecturer","professor","teacher","woman"],"emoji":"👩🏫","order":1074,"group":1,"version":4,"skins":[{"emoji":"👩🏻🏫","version":4,"tone":1},{"emoji":"👩🏼🏫","version":4,"tone":2},{"emoji":"👩🏽🏫","version":4,"tone":3},{"emoji":"👩🏾🏫","version":4,"tone":4},{"emoji":"👩🏿🏫","version":4,"tone":5}]},{"shortcodes":["judge"],"annotation":"judge","tags":["justice","law","scales"],"emoji":"🧑⚖️","order":1080,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻⚖️","version":12.1,"tone":1},{"emoji":"🧑🏼⚖️","version":12.1,"tone":2},{"emoji":"🧑🏽⚖️","version":12.1,"tone":3},{"emoji":"🧑🏾⚖️","version":12.1,"tone":4},{"emoji":"🧑🏿⚖️","version":12.1,"tone":5}]},{"shortcodes":["man_judge"],"annotation":"man judge","tags":["judge","justice","law","man","scales"],"emoji":"👨⚖️","order":1092,"group":1,"version":4,"skins":[{"emoji":"👨🏻⚖️","version":4,"tone":1},{"emoji":"👨🏼⚖️","version":4,"tone":2},{"emoji":"👨🏽⚖️","version":4,"tone":3},{"emoji":"👨🏾⚖️","version":4,"tone":4},{"emoji":"👨🏿⚖️","version":4,"tone":5}]},{"shortcodes":["woman_judge"],"annotation":"woman judge","tags":["judge","justice","law","scales","woman"],"emoji":"👩⚖️","order":1104,"group":1,"version":4,"skins":[{"emoji":"👩🏻⚖️","version":4,"tone":1},{"emoji":"👩🏼⚖️","version":4,"tone":2},{"emoji":"👩🏽⚖️","version":4,"tone":3},{"emoji":"👩🏾⚖️","version":4,"tone":4},{"emoji":"👩🏿⚖️","version":4,"tone":5}]},{"shortcodes":["farmer"],"annotation":"farmer","tags":["gardener","rancher"],"emoji":"🧑🌾","order":1116,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🌾","version":12.1,"tone":1},{"emoji":"🧑🏼🌾","version":12.1,"tone":2},{"emoji":"🧑🏽🌾","version":12.1,"tone":3},{"emoji":"🧑🏾🌾","version":12.1,"tone":4},{"emoji":"🧑🏿🌾","version":12.1,"tone":5}]},{"shortcodes":["man_farmer"],"annotation":"man farmer","tags":["farmer","gardener","man","rancher"],"emoji":"👨🌾","order":1122,"group":1,"version":4,"skins":[{"emoji":"👨🏻🌾","version":4,"tone":1},{"emoji":"👨🏼🌾","version":4,"tone":2},{"emoji":"👨🏽🌾","version":4,"tone":3},{"emoji":"👨🏾🌾","version":4,"tone":4},{"emoji":"👨🏿🌾","version":4,"tone":5}]},{"shortcodes":["woman_farmer"],"annotation":"woman farmer","tags":["farmer","gardener","rancher","woman"],"emoji":"👩🌾","order":1128,"group":1,"version":4,"skins":[{"emoji":"👩🏻🌾","version":4,"tone":1},{"emoji":"👩🏼🌾","version":4,"tone":2},{"emoji":"👩🏽🌾","version":4,"tone":3},{"emoji":"👩🏾🌾","version":4,"tone":4},{"emoji":"👩🏿🌾","version":4,"tone":5}]},{"shortcodes":["cook"],"annotation":"cook","tags":["chef"],"emoji":"🧑🍳","order":1134,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🍳","version":12.1,"tone":1},{"emoji":"🧑🏼🍳","version":12.1,"tone":2},{"emoji":"🧑🏽🍳","version":12.1,"tone":3},{"emoji":"🧑🏾🍳","version":12.1,"tone":4},{"emoji":"🧑🏿🍳","version":12.1,"tone":5}]},{"shortcodes":["man_cook"],"annotation":"man cook","tags":["chef","cook","man"],"emoji":"👨🍳","order":1140,"group":1,"version":4,"skins":[{"emoji":"👨🏻🍳","version":4,"tone":1},{"emoji":"👨🏼🍳","version":4,"tone":2},{"emoji":"👨🏽🍳","version":4,"tone":3},{"emoji":"👨🏾🍳","version":4,"tone":4},{"emoji":"👨🏿🍳","version":4,"tone":5}]},{"shortcodes":["woman_cook"],"annotation":"woman cook","tags":["chef","cook","woman"],"emoji":"👩🍳","order":1146,"group":1,"version":4,"skins":[{"emoji":"👩🏻🍳","version":4,"tone":1},{"emoji":"👩🏼🍳","version":4,"tone":2},{"emoji":"👩🏽🍳","version":4,"tone":3},{"emoji":"👩🏾🍳","version":4,"tone":4},{"emoji":"👩🏿🍳","version":4,"tone":5}]},{"shortcodes":["mechanic"],"annotation":"mechanic","tags":["electrician","plumber","tradesperson"],"emoji":"🧑🔧","order":1152,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🔧","version":12.1,"tone":1},{"emoji":"🧑🏼🔧","version":12.1,"tone":2},{"emoji":"🧑🏽🔧","version":12.1,"tone":3},{"emoji":"🧑🏾🔧","version":12.1,"tone":4},{"emoji":"🧑🏿🔧","version":12.1,"tone":5}]},{"shortcodes":["man_mechanic"],"annotation":"man mechanic","tags":["electrician","man","mechanic","plumber","tradesperson"],"emoji":"👨🔧","order":1158,"group":1,"version":4,"skins":[{"emoji":"👨🏻🔧","version":4,"tone":1},{"emoji":"👨🏼🔧","version":4,"tone":2},{"emoji":"👨🏽🔧","version":4,"tone":3},{"emoji":"👨🏾🔧","version":4,"tone":4},{"emoji":"👨🏿🔧","version":4,"tone":5}]},{"shortcodes":["woman_mechanic"],"annotation":"woman mechanic","tags":["electrician","mechanic","plumber","tradesperson","woman"],"emoji":"👩🔧","order":1164,"group":1,"version":4,"skins":[{"emoji":"👩🏻🔧","version":4,"tone":1},{"emoji":"👩🏼🔧","version":4,"tone":2},{"emoji":"👩🏽🔧","version":4,"tone":3},{"emoji":"👩🏾🔧","version":4,"tone":4},{"emoji":"👩🏿🔧","version":4,"tone":5}]},{"shortcodes":["factory_worker"],"annotation":"factory worker","tags":["assembly","factory","industrial","worker"],"emoji":"🧑🏭","order":1170,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🏭","version":12.1,"tone":1},{"emoji":"🧑🏼🏭","version":12.1,"tone":2},{"emoji":"🧑🏽🏭","version":12.1,"tone":3},{"emoji":"🧑🏾🏭","version":12.1,"tone":4},{"emoji":"🧑🏿🏭","version":12.1,"tone":5}]},{"shortcodes":["man_factory_worker"],"annotation":"man factory worker","tags":["assembly","factory","industrial","man","worker"],"emoji":"👨🏭","order":1176,"group":1,"version":4,"skins":[{"emoji":"👨🏻🏭","version":4,"tone":1},{"emoji":"👨🏼🏭","version":4,"tone":2},{"emoji":"👨🏽🏭","version":4,"tone":3},{"emoji":"👨🏾🏭","version":4,"tone":4},{"emoji":"👨🏿🏭","version":4,"tone":5}]},{"shortcodes":["woman_factory_worker"],"annotation":"woman factory worker","tags":["assembly","factory","industrial","woman","worker"],"emoji":"👩🏭","order":1182,"group":1,"version":4,"skins":[{"emoji":"👩🏻🏭","version":4,"tone":1},{"emoji":"👩🏼🏭","version":4,"tone":2},{"emoji":"👩🏽🏭","version":4,"tone":3},{"emoji":"👩🏾🏭","version":4,"tone":4},{"emoji":"👩🏿🏭","version":4,"tone":5}]},{"shortcodes":["office_worker"],"annotation":"office worker","tags":["architect","business","manager","white-collar"],"emoji":"🧑💼","order":1188,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻💼","version":12.1,"tone":1},{"emoji":"🧑🏼💼","version":12.1,"tone":2},{"emoji":"🧑🏽💼","version":12.1,"tone":3},{"emoji":"🧑🏾💼","version":12.1,"tone":4},{"emoji":"🧑🏿💼","version":12.1,"tone":5}]},{"shortcodes":["man_office_worker"],"annotation":"man office worker","tags":["architect","business","man","manager","white-collar"],"emoji":"👨💼","order":1194,"group":1,"version":4,"skins":[{"emoji":"👨🏻💼","version":4,"tone":1},{"emoji":"👨🏼💼","version":4,"tone":2},{"emoji":"👨🏽💼","version":4,"tone":3},{"emoji":"👨🏾💼","version":4,"tone":4},{"emoji":"👨🏿💼","version":4,"tone":5}]},{"shortcodes":["woman_office_worker"],"annotation":"woman office worker","tags":["architect","business","manager","white-collar","woman"],"emoji":"👩💼","order":1200,"group":1,"version":4,"skins":[{"emoji":"👩🏻💼","version":4,"tone":1},{"emoji":"👩🏼💼","version":4,"tone":2},{"emoji":"👩🏽💼","version":4,"tone":3},{"emoji":"👩🏾💼","version":4,"tone":4},{"emoji":"👩🏿💼","version":4,"tone":5}]},{"shortcodes":["scientist"],"annotation":"scientist","tags":["biologist","chemist","engineer","physicist"],"emoji":"🧑🔬","order":1206,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🔬","version":12.1,"tone":1},{"emoji":"🧑🏼🔬","version":12.1,"tone":2},{"emoji":"🧑🏽🔬","version":12.1,"tone":3},{"emoji":"🧑🏾🔬","version":12.1,"tone":4},{"emoji":"🧑🏿🔬","version":12.1,"tone":5}]},{"shortcodes":["man_scientist"],"annotation":"man scientist","tags":["biologist","chemist","engineer","man","physicist","scientist"],"emoji":"👨🔬","order":1212,"group":1,"version":4,"skins":[{"emoji":"👨🏻🔬","version":4,"tone":1},{"emoji":"👨🏼🔬","version":4,"tone":2},{"emoji":"👨🏽🔬","version":4,"tone":3},{"emoji":"👨🏾🔬","version":4,"tone":4},{"emoji":"👨🏿🔬","version":4,"tone":5}]},{"shortcodes":["woman_scientist"],"annotation":"woman scientist","tags":["biologist","chemist","engineer","physicist","scientist","woman"],"emoji":"👩🔬","order":1218,"group":1,"version":4,"skins":[{"emoji":"👩🏻🔬","version":4,"tone":1},{"emoji":"👩🏼🔬","version":4,"tone":2},{"emoji":"👩🏽🔬","version":4,"tone":3},{"emoji":"👩🏾🔬","version":4,"tone":4},{"emoji":"👩🏿🔬","version":4,"tone":5}]},{"shortcodes":["technologist"],"annotation":"technologist","tags":["coder","developer","inventor","software"],"emoji":"🧑💻","order":1224,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻💻","version":12.1,"tone":1},{"emoji":"🧑🏼💻","version":12.1,"tone":2},{"emoji":"🧑🏽💻","version":12.1,"tone":3},{"emoji":"🧑🏾💻","version":12.1,"tone":4},{"emoji":"🧑🏿💻","version":12.1,"tone":5}]},{"shortcodes":["man_technologist"],"annotation":"man technologist","tags":["coder","developer","inventor","man","software","technologist"],"emoji":"👨💻","order":1230,"group":1,"version":4,"skins":[{"emoji":"👨🏻💻","version":4,"tone":1},{"emoji":"👨🏼💻","version":4,"tone":2},{"emoji":"👨🏽💻","version":4,"tone":3},{"emoji":"👨🏾💻","version":4,"tone":4},{"emoji":"👨🏿💻","version":4,"tone":5}]},{"shortcodes":["woman_technologist"],"annotation":"woman technologist","tags":["coder","developer","inventor","software","technologist","woman"],"emoji":"👩💻","order":1236,"group":1,"version":4,"skins":[{"emoji":"👩🏻💻","version":4,"tone":1},{"emoji":"👩🏼💻","version":4,"tone":2},{"emoji":"👩🏽💻","version":4,"tone":3},{"emoji":"👩🏾💻","version":4,"tone":4},{"emoji":"👩🏿💻","version":4,"tone":5}]},{"shortcodes":["singer"],"annotation":"singer","tags":["actor","entertainer","rock","star"],"emoji":"🧑🎤","order":1242,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🎤","version":12.1,"tone":1},{"emoji":"🧑🏼🎤","version":12.1,"tone":2},{"emoji":"🧑🏽🎤","version":12.1,"tone":3},{"emoji":"🧑🏾🎤","version":12.1,"tone":4},{"emoji":"🧑🏿🎤","version":12.1,"tone":5}]},{"shortcodes":["man_singer"],"annotation":"man singer","tags":["actor","entertainer","man","rock","singer","star"],"emoji":"👨🎤","order":1248,"group":1,"version":4,"skins":[{"emoji":"👨🏻🎤","version":4,"tone":1},{"emoji":"👨🏼🎤","version":4,"tone":2},{"emoji":"👨🏽🎤","version":4,"tone":3},{"emoji":"👨🏾🎤","version":4,"tone":4},{"emoji":"👨🏿🎤","version":4,"tone":5}]},{"shortcodes":["woman_singer"],"annotation":"woman singer","tags":["actor","entertainer","rock","singer","star","woman"],"emoji":"👩🎤","order":1254,"group":1,"version":4,"skins":[{"emoji":"👩🏻🎤","version":4,"tone":1},{"emoji":"👩🏼🎤","version":4,"tone":2},{"emoji":"👩🏽🎤","version":4,"tone":3},{"emoji":"👩🏾🎤","version":4,"tone":4},{"emoji":"👩🏿🎤","version":4,"tone":5}]},{"shortcodes":["artist"],"annotation":"artist","tags":["palette"],"emoji":"🧑🎨","order":1260,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🎨","version":12.1,"tone":1},{"emoji":"🧑🏼🎨","version":12.1,"tone":2},{"emoji":"🧑🏽🎨","version":12.1,"tone":3},{"emoji":"🧑🏾🎨","version":12.1,"tone":4},{"emoji":"🧑🏿🎨","version":12.1,"tone":5}]},{"shortcodes":["man_artist"],"annotation":"man artist","tags":["artist","man","palette"],"emoji":"👨🎨","order":1266,"group":1,"version":4,"skins":[{"emoji":"👨🏻🎨","version":4,"tone":1},{"emoji":"👨🏼🎨","version":4,"tone":2},{"emoji":"👨🏽🎨","version":4,"tone":3},{"emoji":"👨🏾🎨","version":4,"tone":4},{"emoji":"👨🏿🎨","version":4,"tone":5}]},{"shortcodes":["woman_artist"],"annotation":"woman artist","tags":["artist","palette","woman"],"emoji":"👩🎨","order":1272,"group":1,"version":4,"skins":[{"emoji":"👩🏻🎨","version":4,"tone":1},{"emoji":"👩🏼🎨","version":4,"tone":2},{"emoji":"👩🏽🎨","version":4,"tone":3},{"emoji":"👩🏾🎨","version":4,"tone":4},{"emoji":"👩🏿🎨","version":4,"tone":5}]},{"shortcodes":["pilot"],"annotation":"pilot","tags":["plane"],"emoji":"🧑✈️","order":1278,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻✈️","version":12.1,"tone":1},{"emoji":"🧑🏼✈️","version":12.1,"tone":2},{"emoji":"🧑🏽✈️","version":12.1,"tone":3},{"emoji":"🧑🏾✈️","version":12.1,"tone":4},{"emoji":"🧑🏿✈️","version":12.1,"tone":5}]},{"shortcodes":["man_pilot"],"annotation":"man pilot","tags":["man","pilot","plane"],"emoji":"👨✈️","order":1290,"group":1,"version":4,"skins":[{"emoji":"👨🏻✈️","version":4,"tone":1},{"emoji":"👨🏼✈️","version":4,"tone":2},{"emoji":"👨🏽✈️","version":4,"tone":3},{"emoji":"👨🏾✈️","version":4,"tone":4},{"emoji":"👨🏿✈️","version":4,"tone":5}]},{"shortcodes":["woman_pilot"],"annotation":"woman pilot","tags":["pilot","plane","woman"],"emoji":"👩✈️","order":1302,"group":1,"version":4,"skins":[{"emoji":"👩🏻✈️","version":4,"tone":1},{"emoji":"👩🏼✈️","version":4,"tone":2},{"emoji":"👩🏽✈️","version":4,"tone":3},{"emoji":"👩🏾✈️","version":4,"tone":4},{"emoji":"👩🏿✈️","version":4,"tone":5}]},{"shortcodes":["astronaut"],"annotation":"astronaut","tags":["rocket"],"emoji":"🧑🚀","order":1314,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🚀","version":12.1,"tone":1},{"emoji":"🧑🏼🚀","version":12.1,"tone":2},{"emoji":"🧑🏽🚀","version":12.1,"tone":3},{"emoji":"🧑🏾🚀","version":12.1,"tone":4},{"emoji":"🧑🏿🚀","version":12.1,"tone":5}]},{"shortcodes":["man_astronaut"],"annotation":"man astronaut","tags":["astronaut","man","rocket"],"emoji":"👨🚀","order":1320,"group":1,"version":4,"skins":[{"emoji":"👨🏻🚀","version":4,"tone":1},{"emoji":"👨🏼🚀","version":4,"tone":2},{"emoji":"👨🏽🚀","version":4,"tone":3},{"emoji":"👨🏾🚀","version":4,"tone":4},{"emoji":"👨🏿🚀","version":4,"tone":5}]},{"shortcodes":["woman_astronaut"],"annotation":"woman astronaut","tags":["astronaut","rocket","woman"],"emoji":"👩🚀","order":1326,"group":1,"version":4,"skins":[{"emoji":"👩🏻🚀","version":4,"tone":1},{"emoji":"👩🏼🚀","version":4,"tone":2},{"emoji":"👩🏽🚀","version":4,"tone":3},{"emoji":"👩🏾🚀","version":4,"tone":4},{"emoji":"👩🏿🚀","version":4,"tone":5}]},{"shortcodes":["firefighter"],"annotation":"firefighter","tags":["fire","firetruck"],"emoji":"🧑🚒","order":1332,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🚒","version":12.1,"tone":1},{"emoji":"🧑🏼🚒","version":12.1,"tone":2},{"emoji":"🧑🏽🚒","version":12.1,"tone":3},{"emoji":"🧑🏾🚒","version":12.1,"tone":4},{"emoji":"🧑🏿🚒","version":12.1,"tone":5}]},{"shortcodes":["man_firefighter"],"annotation":"man firefighter","tags":["firefighter","firetruck","man"],"emoji":"👨🚒","order":1338,"group":1,"version":4,"skins":[{"emoji":"👨🏻🚒","version":4,"tone":1},{"emoji":"👨🏼🚒","version":4,"tone":2},{"emoji":"👨🏽🚒","version":4,"tone":3},{"emoji":"👨🏾🚒","version":4,"tone":4},{"emoji":"👨🏿🚒","version":4,"tone":5}]},{"shortcodes":["woman_firefighter"],"annotation":"woman firefighter","tags":["firefighter","firetruck","woman"],"emoji":"👩🚒","order":1344,"group":1,"version":4,"skins":[{"emoji":"👩🏻🚒","version":4,"tone":1},{"emoji":"👩🏼🚒","version":4,"tone":2},{"emoji":"👩🏽🚒","version":4,"tone":3},{"emoji":"👩🏾🚒","version":4,"tone":4},{"emoji":"👩🏿🚒","version":4,"tone":5}]},{"shortcodes":["cop","police_officer"],"annotation":"police officer","tags":["cop","officer","police"],"emoji":"👮","order":1350,"group":1,"version":0.6,"skins":[{"emoji":"👮🏻","version":1,"tone":1},{"emoji":"👮🏼","version":1,"tone":2},{"emoji":"👮🏽","version":1,"tone":3},{"emoji":"👮🏾","version":1,"tone":4},{"emoji":"👮🏿","version":1,"tone":5}]},{"shortcodes":["man_police_officer"],"annotation":"man police officer","tags":["cop","man","officer","police"],"emoji":"👮♂️","order":1356,"group":1,"version":4,"skins":[{"emoji":"👮🏻♂️","version":4,"tone":1},{"emoji":"👮🏼♂️","version":4,"tone":2},{"emoji":"👮🏽♂️","version":4,"tone":3},{"emoji":"👮🏾♂️","version":4,"tone":4},{"emoji":"👮🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_police_officer"],"annotation":"woman police officer","tags":["cop","officer","police","woman"],"emoji":"👮♀️","order":1368,"group":1,"version":4,"skins":[{"emoji":"👮🏻♀️","version":4,"tone":1},{"emoji":"👮🏼♀️","version":4,"tone":2},{"emoji":"👮🏽♀️","version":4,"tone":3},{"emoji":"👮🏾♀️","version":4,"tone":4},{"emoji":"👮🏿♀️","version":4,"tone":5}]},{"shortcodes":["detective"],"annotation":"detective","tags":["sleuth","spy"],"emoji":"🕵️","order":1381,"group":1,"version":0.7,"skins":[{"emoji":"🕵🏻","version":2,"tone":1},{"emoji":"🕵🏼","version":2,"tone":2},{"emoji":"🕵🏽","version":2,"tone":3},{"emoji":"🕵🏾","version":2,"tone":4},{"emoji":"🕵🏿","version":2,"tone":5}]},{"shortcodes":["man_detective"],"annotation":"man detective","tags":["detective","man","sleuth","spy"],"emoji":"🕵️♂️","order":1387,"group":1,"version":4,"skins":[{"emoji":"🕵🏻♂️","version":4,"tone":1},{"emoji":"🕵🏼♂️","version":4,"tone":2},{"emoji":"🕵🏽♂️","version":4,"tone":3},{"emoji":"🕵🏾♂️","version":4,"tone":4},{"emoji":"🕵🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_detective"],"annotation":"woman detective","tags":["detective","sleuth","spy","woman"],"emoji":"🕵️♀️","order":1401,"group":1,"version":4,"skins":[{"emoji":"🕵🏻♀️","version":4,"tone":1},{"emoji":"🕵🏼♀️","version":4,"tone":2},{"emoji":"🕵🏽♀️","version":4,"tone":3},{"emoji":"🕵🏾♀️","version":4,"tone":4},{"emoji":"🕵🏿♀️","version":4,"tone":5}]},{"shortcodes":["guard"],"annotation":"guard","tags":["guard"],"emoji":"💂","order":1415,"group":1,"version":0.6,"skins":[{"emoji":"💂🏻","version":1,"tone":1},{"emoji":"💂🏼","version":1,"tone":2},{"emoji":"💂🏽","version":1,"tone":3},{"emoji":"💂🏾","version":1,"tone":4},{"emoji":"💂🏿","version":1,"tone":5}]},{"shortcodes":["man_guard"],"annotation":"man guard","tags":["guard","man"],"emoji":"💂♂️","order":1421,"group":1,"version":4,"skins":[{"emoji":"💂🏻♂️","version":4,"tone":1},{"emoji":"💂🏼♂️","version":4,"tone":2},{"emoji":"💂🏽♂️","version":4,"tone":3},{"emoji":"💂🏾♂️","version":4,"tone":4},{"emoji":"💂🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_guard"],"annotation":"woman guard","tags":["guard","woman"],"emoji":"💂♀️","order":1433,"group":1,"version":4,"skins":[{"emoji":"💂🏻♀️","version":4,"tone":1},{"emoji":"💂🏼♀️","version":4,"tone":2},{"emoji":"💂🏽♀️","version":4,"tone":3},{"emoji":"💂🏾♀️","version":4,"tone":4},{"emoji":"💂🏿♀️","version":4,"tone":5}]},{"shortcodes":["ninja"],"annotation":"ninja","tags":["fighter","hidden","stealth"],"emoji":"🥷","order":1445,"group":1,"version":13,"skins":[{"emoji":"🥷🏻","version":13,"tone":1},{"emoji":"🥷🏼","version":13,"tone":2},{"emoji":"🥷🏽","version":13,"tone":3},{"emoji":"🥷🏾","version":13,"tone":4},{"emoji":"🥷🏿","version":13,"tone":5}]},{"shortcodes":["construction_worker"],"annotation":"construction worker","tags":["construction","hat","worker"],"emoji":"👷","order":1451,"group":1,"version":0.6,"skins":[{"emoji":"👷🏻","version":1,"tone":1},{"emoji":"👷🏼","version":1,"tone":2},{"emoji":"👷🏽","version":1,"tone":3},{"emoji":"👷🏾","version":1,"tone":4},{"emoji":"👷🏿","version":1,"tone":5}]},{"shortcodes":["man_construction_worker"],"annotation":"man construction worker","tags":["construction","man","worker"],"emoji":"👷♂️","order":1457,"group":1,"version":4,"skins":[{"emoji":"👷🏻♂️","version":4,"tone":1},{"emoji":"👷🏼♂️","version":4,"tone":2},{"emoji":"👷🏽♂️","version":4,"tone":3},{"emoji":"👷🏾♂️","version":4,"tone":4},{"emoji":"👷🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_construction_worker"],"annotation":"woman construction worker","tags":["construction","woman","worker"],"emoji":"👷♀️","order":1469,"group":1,"version":4,"skins":[{"emoji":"👷🏻♀️","version":4,"tone":1},{"emoji":"👷🏼♀️","version":4,"tone":2},{"emoji":"👷🏽♀️","version":4,"tone":3},{"emoji":"👷🏾♀️","version":4,"tone":4},{"emoji":"👷🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_with_crown","royalty"],"annotation":"person with crown","tags":["monarch","noble","regal","royalty"],"emoji":"🫅","order":1481,"group":1,"version":14,"skins":[{"emoji":"🫅🏻","version":14,"tone":1},{"emoji":"🫅🏼","version":14,"tone":2},{"emoji":"🫅🏽","version":14,"tone":3},{"emoji":"🫅🏾","version":14,"tone":4},{"emoji":"🫅🏿","version":14,"tone":5}]},{"shortcodes":["prince"],"annotation":"prince","tags":["prince"],"emoji":"🤴","order":1487,"group":1,"version":3,"skins":[{"emoji":"🤴🏻","version":3,"tone":1},{"emoji":"🤴🏼","version":3,"tone":2},{"emoji":"🤴🏽","version":3,"tone":3},{"emoji":"🤴🏾","version":3,"tone":4},{"emoji":"🤴🏿","version":3,"tone":5}]},{"shortcodes":["princess"],"annotation":"princess","tags":["fairy tale","fantasy"],"emoji":"👸","order":1493,"group":1,"version":0.6,"skins":[{"emoji":"👸🏻","version":1,"tone":1},{"emoji":"👸🏼","version":1,"tone":2},{"emoji":"👸🏽","version":1,"tone":3},{"emoji":"👸🏾","version":1,"tone":4},{"emoji":"👸🏿","version":1,"tone":5}]},{"shortcodes":["person_wearing_turban"],"annotation":"person wearing turban","tags":["turban"],"emoji":"👳","order":1499,"group":1,"version":0.6,"skins":[{"emoji":"👳🏻","version":1,"tone":1},{"emoji":"👳🏼","version":1,"tone":2},{"emoji":"👳🏽","version":1,"tone":3},{"emoji":"👳🏾","version":1,"tone":4},{"emoji":"👳🏿","version":1,"tone":5}]},{"shortcodes":["man_wearing_turban"],"annotation":"man wearing turban","tags":["man","turban"],"emoji":"👳♂️","order":1505,"group":1,"version":4,"skins":[{"emoji":"👳🏻♂️","version":4,"tone":1},{"emoji":"👳🏼♂️","version":4,"tone":2},{"emoji":"👳🏽♂️","version":4,"tone":3},{"emoji":"👳🏾♂️","version":4,"tone":4},{"emoji":"👳🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_wearing_turban"],"annotation":"woman wearing turban","tags":["turban","woman"],"emoji":"👳♀️","order":1517,"group":1,"version":4,"skins":[{"emoji":"👳🏻♀️","version":4,"tone":1},{"emoji":"👳🏼♀️","version":4,"tone":2},{"emoji":"👳🏽♀️","version":4,"tone":3},{"emoji":"👳🏾♀️","version":4,"tone":4},{"emoji":"👳🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_with_skullcap"],"annotation":"person with skullcap","tags":["cap","gua pi mao","hat","person","skullcap"],"emoji":"👲","order":1529,"group":1,"version":0.6,"skins":[{"emoji":"👲🏻","version":1,"tone":1},{"emoji":"👲🏼","version":1,"tone":2},{"emoji":"👲🏽","version":1,"tone":3},{"emoji":"👲🏾","version":1,"tone":4},{"emoji":"👲🏿","version":1,"tone":5}]},{"shortcodes":["woman_with_headscarf"],"annotation":"woman with headscarf","tags":["headscarf","hijab","mantilla","tichel"],"emoji":"🧕","order":1535,"group":1,"version":5,"skins":[{"emoji":"🧕🏻","version":5,"tone":1},{"emoji":"🧕🏼","version":5,"tone":2},{"emoji":"🧕🏽","version":5,"tone":3},{"emoji":"🧕🏾","version":5,"tone":4},{"emoji":"🧕🏿","version":5,"tone":5}]},{"shortcodes":["person_in_tuxedo"],"annotation":"person in tuxedo","tags":["groom","person","tuxedo"],"emoji":"🤵","order":1541,"group":1,"version":3,"skins":[{"emoji":"🤵🏻","version":3,"tone":1},{"emoji":"🤵🏼","version":3,"tone":2},{"emoji":"🤵🏽","version":3,"tone":3},{"emoji":"🤵🏾","version":3,"tone":4},{"emoji":"🤵🏿","version":3,"tone":5}]},{"shortcodes":["man_in_tuxedo"],"annotation":"man in tuxedo","tags":["man","tuxedo"],"emoji":"🤵♂️","order":1547,"group":1,"version":13,"skins":[{"emoji":"🤵🏻♂️","version":13,"tone":1},{"emoji":"🤵🏼♂️","version":13,"tone":2},{"emoji":"🤵🏽♂️","version":13,"tone":3},{"emoji":"🤵🏾♂️","version":13,"tone":4},{"emoji":"🤵🏿♂️","version":13,"tone":5}]},{"shortcodes":["woman_in_tuxedo"],"annotation":"woman in tuxedo","tags":["tuxedo","woman"],"emoji":"🤵♀️","order":1559,"group":1,"version":13,"skins":[{"emoji":"🤵🏻♀️","version":13,"tone":1},{"emoji":"🤵🏼♀️","version":13,"tone":2},{"emoji":"🤵🏽♀️","version":13,"tone":3},{"emoji":"🤵🏾♀️","version":13,"tone":4},{"emoji":"🤵🏿♀️","version":13,"tone":5}]},{"shortcodes":["person_with_veil"],"annotation":"person with veil","tags":["bride","person","veil","wedding"],"emoji":"👰","order":1571,"group":1,"version":0.6,"skins":[{"emoji":"👰🏻","version":1,"tone":1},{"emoji":"👰🏼","version":1,"tone":2},{"emoji":"👰🏽","version":1,"tone":3},{"emoji":"👰🏾","version":1,"tone":4},{"emoji":"👰🏿","version":1,"tone":5}]},{"shortcodes":["man_with_veil"],"annotation":"man with veil","tags":["man","veil"],"emoji":"👰♂️","order":1577,"group":1,"version":13,"skins":[{"emoji":"👰🏻♂️","version":13,"tone":1},{"emoji":"👰🏼♂️","version":13,"tone":2},{"emoji":"👰🏽♂️","version":13,"tone":3},{"emoji":"👰🏾♂️","version":13,"tone":4},{"emoji":"👰🏿♂️","version":13,"tone":5}]},{"shortcodes":["woman_with_veil"],"annotation":"woman with veil","tags":["veil","woman"],"emoji":"👰♀️","order":1589,"group":1,"version":13,"skins":[{"emoji":"👰🏻♀️","version":13,"tone":1},{"emoji":"👰🏼♀️","version":13,"tone":2},{"emoji":"👰🏽♀️","version":13,"tone":3},{"emoji":"👰🏾♀️","version":13,"tone":4},{"emoji":"👰🏿♀️","version":13,"tone":5}]},{"shortcodes":["pregnant_woman"],"annotation":"pregnant woman","tags":["pregnant","woman"],"emoji":"🤰","order":1601,"group":1,"version":3,"skins":[{"emoji":"🤰🏻","version":3,"tone":1},{"emoji":"🤰🏼","version":3,"tone":2},{"emoji":"🤰🏽","version":3,"tone":3},{"emoji":"🤰🏾","version":3,"tone":4},{"emoji":"🤰🏿","version":3,"tone":5}]},{"shortcodes":["pregnant_man"],"annotation":"pregnant man","tags":["belly","bloated","full","pregnant"],"emoji":"🫃","order":1607,"group":1,"version":14,"skins":[{"emoji":"🫃🏻","version":14,"tone":1},{"emoji":"🫃🏼","version":14,"tone":2},{"emoji":"🫃🏽","version":14,"tone":3},{"emoji":"🫃🏾","version":14,"tone":4},{"emoji":"🫃🏿","version":14,"tone":5}]},{"shortcodes":["pregnant_person"],"annotation":"pregnant person","tags":["belly","bloated","full","pregnant"],"emoji":"🫄","order":1613,"group":1,"version":14,"skins":[{"emoji":"🫄🏻","version":14,"tone":1},{"emoji":"🫄🏼","version":14,"tone":2},{"emoji":"🫄🏽","version":14,"tone":3},{"emoji":"🫄🏾","version":14,"tone":4},{"emoji":"🫄🏿","version":14,"tone":5}]},{"shortcodes":["breast_feeding"],"annotation":"breast-feeding","tags":["baby","breast","nursing"],"emoji":"🤱","order":1619,"group":1,"version":5,"skins":[{"emoji":"🤱🏻","version":5,"tone":1},{"emoji":"🤱🏼","version":5,"tone":2},{"emoji":"🤱🏽","version":5,"tone":3},{"emoji":"🤱🏾","version":5,"tone":4},{"emoji":"🤱🏿","version":5,"tone":5}]},{"shortcodes":["woman_feeding_baby"],"annotation":"woman feeding baby","tags":["baby","feeding","nursing","woman"],"emoji":"👩🍼","order":1625,"group":1,"version":13,"skins":[{"emoji":"👩🏻🍼","version":13,"tone":1},{"emoji":"👩🏼🍼","version":13,"tone":2},{"emoji":"👩🏽🍼","version":13,"tone":3},{"emoji":"👩🏾🍼","version":13,"tone":4},{"emoji":"👩🏿🍼","version":13,"tone":5}]},{"shortcodes":["man_feeding_baby"],"annotation":"man feeding baby","tags":["baby","feeding","man","nursing"],"emoji":"👨🍼","order":1631,"group":1,"version":13,"skins":[{"emoji":"👨🏻🍼","version":13,"tone":1},{"emoji":"👨🏼🍼","version":13,"tone":2},{"emoji":"👨🏽🍼","version":13,"tone":3},{"emoji":"👨🏾🍼","version":13,"tone":4},{"emoji":"👨🏿🍼","version":13,"tone":5}]},{"shortcodes":["person_feeding_baby"],"annotation":"person feeding baby","tags":["baby","feeding","nursing","person"],"emoji":"🧑🍼","order":1637,"group":1,"version":13,"skins":[{"emoji":"🧑🏻🍼","version":13,"tone":1},{"emoji":"🧑🏼🍼","version":13,"tone":2},{"emoji":"🧑🏽🍼","version":13,"tone":3},{"emoji":"🧑🏾🍼","version":13,"tone":4},{"emoji":"🧑🏿🍼","version":13,"tone":5}]},{"shortcodes":["angel"],"annotation":"baby angel","tags":["angel","baby","face","fairy tale","fantasy"],"emoji":"👼","order":1643,"group":1,"version":0.6,"skins":[{"emoji":"👼🏻","version":1,"tone":1},{"emoji":"👼🏼","version":1,"tone":2},{"emoji":"👼🏽","version":1,"tone":3},{"emoji":"👼🏾","version":1,"tone":4},{"emoji":"👼🏿","version":1,"tone":5}]},{"shortcodes":["santa"],"annotation":"Santa Claus","tags":["celebration","christmas","claus","father","santa"],"emoji":"🎅","order":1649,"group":1,"version":0.6,"skins":[{"emoji":"🎅🏻","version":1,"tone":1},{"emoji":"🎅🏼","version":1,"tone":2},{"emoji":"🎅🏽","version":1,"tone":3},{"emoji":"🎅🏾","version":1,"tone":4},{"emoji":"🎅🏿","version":1,"tone":5}]},{"shortcodes":["mrs_claus"],"annotation":"Mrs. Claus","tags":["celebration","christmas","claus","mother","mrs."],"emoji":"🤶","order":1655,"group":1,"version":3,"skins":[{"emoji":"🤶🏻","version":3,"tone":1},{"emoji":"🤶🏼","version":3,"tone":2},{"emoji":"🤶🏽","version":3,"tone":3},{"emoji":"🤶🏾","version":3,"tone":4},{"emoji":"🤶🏿","version":3,"tone":5}]},{"shortcodes":["mx_claus"],"annotation":"mx claus","tags":["christmas","claus"],"emoji":"🧑🎄","order":1661,"group":1,"version":13,"skins":[{"emoji":"🧑🏻🎄","version":13,"tone":1},{"emoji":"🧑🏼🎄","version":13,"tone":2},{"emoji":"🧑🏽🎄","version":13,"tone":3},{"emoji":"🧑🏾🎄","version":13,"tone":4},{"emoji":"🧑🏿🎄","version":13,"tone":5}]},{"shortcodes":["superhero"],"annotation":"superhero","tags":["good","hero","heroine","superpower"],"emoji":"🦸","order":1667,"group":1,"version":11,"skins":[{"emoji":"🦸🏻","version":11,"tone":1},{"emoji":"🦸🏼","version":11,"tone":2},{"emoji":"🦸🏽","version":11,"tone":3},{"emoji":"🦸🏾","version":11,"tone":4},{"emoji":"🦸🏿","version":11,"tone":5}]},{"shortcodes":["man_superhero"],"annotation":"man superhero","tags":["good","hero","man","superpower"],"emoji":"🦸♂️","order":1673,"group":1,"version":11,"skins":[{"emoji":"🦸🏻♂️","version":11,"tone":1},{"emoji":"🦸🏼♂️","version":11,"tone":2},{"emoji":"🦸🏽♂️","version":11,"tone":3},{"emoji":"🦸🏾♂️","version":11,"tone":4},{"emoji":"🦸🏿♂️","version":11,"tone":5}]},{"shortcodes":["woman_superhero"],"annotation":"woman superhero","tags":["good","hero","heroine","superpower","woman"],"emoji":"🦸♀️","order":1685,"group":1,"version":11,"skins":[{"emoji":"🦸🏻♀️","version":11,"tone":1},{"emoji":"🦸🏼♀️","version":11,"tone":2},{"emoji":"🦸🏽♀️","version":11,"tone":3},{"emoji":"🦸🏾♀️","version":11,"tone":4},{"emoji":"🦸🏿♀️","version":11,"tone":5}]},{"shortcodes":["supervillain"],"annotation":"supervillain","tags":["criminal","evil","superpower","villain"],"emoji":"🦹","order":1697,"group":1,"version":11,"skins":[{"emoji":"🦹🏻","version":11,"tone":1},{"emoji":"🦹🏼","version":11,"tone":2},{"emoji":"🦹🏽","version":11,"tone":3},{"emoji":"🦹🏾","version":11,"tone":4},{"emoji":"🦹🏿","version":11,"tone":5}]},{"shortcodes":["man_supervillain"],"annotation":"man supervillain","tags":["criminal","evil","man","superpower","villain"],"emoji":"🦹♂️","order":1703,"group":1,"version":11,"skins":[{"emoji":"🦹🏻♂️","version":11,"tone":1},{"emoji":"🦹🏼♂️","version":11,"tone":2},{"emoji":"🦹🏽♂️","version":11,"tone":3},{"emoji":"🦹🏾♂️","version":11,"tone":4},{"emoji":"🦹🏿♂️","version":11,"tone":5}]},{"shortcodes":["woman_supervillain"],"annotation":"woman supervillain","tags":["criminal","evil","superpower","villain","woman"],"emoji":"🦹♀️","order":1715,"group":1,"version":11,"skins":[{"emoji":"🦹🏻♀️","version":11,"tone":1},{"emoji":"🦹🏼♀️","version":11,"tone":2},{"emoji":"🦹🏽♀️","version":11,"tone":3},{"emoji":"🦹🏾♀️","version":11,"tone":4},{"emoji":"🦹🏿♀️","version":11,"tone":5}]},{"shortcodes":["mage"],"annotation":"mage","tags":["sorcerer","sorceress","witch","wizard"],"emoji":"🧙","order":1727,"group":1,"version":5,"skins":[{"emoji":"🧙🏻","version":5,"tone":1},{"emoji":"🧙🏼","version":5,"tone":2},{"emoji":"🧙🏽","version":5,"tone":3},{"emoji":"🧙🏾","version":5,"tone":4},{"emoji":"🧙🏿","version":5,"tone":5}]},{"shortcodes":["man_mage"],"annotation":"man mage","tags":["sorcerer","wizard"],"emoji":"🧙♂️","order":1733,"group":1,"version":5,"emoticon":":{>","skins":[{"emoji":"🧙🏻♂️","version":5,"tone":1},{"emoji":"🧙🏼♂️","version":5,"tone":2},{"emoji":"🧙🏽♂️","version":5,"tone":3},{"emoji":"🧙🏾♂️","version":5,"tone":4},{"emoji":"🧙🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_mage"],"annotation":"woman mage","tags":["sorceress","witch"],"emoji":"🧙♀️","order":1745,"group":1,"version":5,"skins":[{"emoji":"🧙🏻♀️","version":5,"tone":1},{"emoji":"🧙🏼♀️","version":5,"tone":2},{"emoji":"🧙🏽♀️","version":5,"tone":3},{"emoji":"🧙🏾♀️","version":5,"tone":4},{"emoji":"🧙🏿♀️","version":5,"tone":5}]},{"shortcodes":["fairy"],"annotation":"fairy","tags":["oberon","puck","titania"],"emoji":"🧚","order":1757,"group":1,"version":5,"skins":[{"emoji":"🧚🏻","version":5,"tone":1},{"emoji":"🧚🏼","version":5,"tone":2},{"emoji":"🧚🏽","version":5,"tone":3},{"emoji":"🧚🏾","version":5,"tone":4},{"emoji":"🧚🏿","version":5,"tone":5}]},{"shortcodes":["man_fairy"],"annotation":"man fairy","tags":["oberon","puck"],"emoji":"🧚♂️","order":1763,"group":1,"version":5,"skins":[{"emoji":"🧚🏻♂️","version":5,"tone":1},{"emoji":"🧚🏼♂️","version":5,"tone":2},{"emoji":"🧚🏽♂️","version":5,"tone":3},{"emoji":"🧚🏾♂️","version":5,"tone":4},{"emoji":"🧚🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_fairy"],"annotation":"woman fairy","tags":["titania"],"emoji":"🧚♀️","order":1775,"group":1,"version":5,"skins":[{"emoji":"🧚🏻♀️","version":5,"tone":1},{"emoji":"🧚🏼♀️","version":5,"tone":2},{"emoji":"🧚🏽♀️","version":5,"tone":3},{"emoji":"🧚🏾♀️","version":5,"tone":4},{"emoji":"🧚🏿♀️","version":5,"tone":5}]},{"shortcodes":["vampire"],"annotation":"vampire","tags":["dracula","undead"],"emoji":"🧛","order":1787,"group":1,"version":5,"emoticon":":E","skins":[{"emoji":"🧛🏻","version":5,"tone":1},{"emoji":"🧛🏼","version":5,"tone":2},{"emoji":"🧛🏽","version":5,"tone":3},{"emoji":"🧛🏾","version":5,"tone":4},{"emoji":"🧛🏿","version":5,"tone":5}]},{"shortcodes":["man_vampire"],"annotation":"man vampire","tags":["dracula","undead"],"emoji":"🧛♂️","order":1793,"group":1,"version":5,"skins":[{"emoji":"🧛🏻♂️","version":5,"tone":1},{"emoji":"🧛🏼♂️","version":5,"tone":2},{"emoji":"🧛🏽♂️","version":5,"tone":3},{"emoji":"🧛🏾♂️","version":5,"tone":4},{"emoji":"🧛🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_vampire"],"annotation":"woman vampire","tags":["undead"],"emoji":"🧛♀️","order":1805,"group":1,"version":5,"skins":[{"emoji":"🧛🏻♀️","version":5,"tone":1},{"emoji":"🧛🏼♀️","version":5,"tone":2},{"emoji":"🧛🏽♀️","version":5,"tone":3},{"emoji":"🧛🏾♀️","version":5,"tone":4},{"emoji":"🧛🏿♀️","version":5,"tone":5}]},{"shortcodes":["merperson"],"annotation":"merperson","tags":["mermaid","merman","merwoman"],"emoji":"🧜","order":1817,"group":1,"version":5,"skins":[{"emoji":"🧜🏻","version":5,"tone":1},{"emoji":"🧜🏼","version":5,"tone":2},{"emoji":"🧜🏽","version":5,"tone":3},{"emoji":"🧜🏾","version":5,"tone":4},{"emoji":"🧜🏿","version":5,"tone":5}]},{"shortcodes":["merman"],"annotation":"merman","tags":["triton"],"emoji":"🧜♂️","order":1823,"group":1,"version":5,"skins":[{"emoji":"🧜🏻♂️","version":5,"tone":1},{"emoji":"🧜🏼♂️","version":5,"tone":2},{"emoji":"🧜🏽♂️","version":5,"tone":3},{"emoji":"🧜🏾♂️","version":5,"tone":4},{"emoji":"🧜🏿♂️","version":5,"tone":5}]},{"shortcodes":["mermaid"],"annotation":"mermaid","tags":["merwoman"],"emoji":"🧜♀️","order":1835,"group":1,"version":5,"skins":[{"emoji":"🧜🏻♀️","version":5,"tone":1},{"emoji":"🧜🏼♀️","version":5,"tone":2},{"emoji":"🧜🏽♀️","version":5,"tone":3},{"emoji":"🧜🏾♀️","version":5,"tone":4},{"emoji":"🧜🏿♀️","version":5,"tone":5}]},{"shortcodes":["elf"],"annotation":"elf","tags":["magical"],"emoji":"🧝","order":1847,"group":1,"version":5,"skins":[{"emoji":"🧝🏻","version":5,"tone":1},{"emoji":"🧝🏼","version":5,"tone":2},{"emoji":"🧝🏽","version":5,"tone":3},{"emoji":"🧝🏾","version":5,"tone":4},{"emoji":"🧝🏿","version":5,"tone":5}]},{"shortcodes":["man_elf"],"annotation":"man elf","tags":["magical"],"emoji":"🧝♂️","order":1853,"group":1,"version":5,"skins":[{"emoji":"🧝🏻♂️","version":5,"tone":1},{"emoji":"🧝🏼♂️","version":5,"tone":2},{"emoji":"🧝🏽♂️","version":5,"tone":3},{"emoji":"🧝🏾♂️","version":5,"tone":4},{"emoji":"🧝🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_elf"],"annotation":"woman elf","tags":["magical"],"emoji":"🧝♀️","order":1865,"group":1,"version":5,"skins":[{"emoji":"🧝🏻♀️","version":5,"tone":1},{"emoji":"🧝🏼♀️","version":5,"tone":2},{"emoji":"🧝🏽♀️","version":5,"tone":3},{"emoji":"🧝🏾♀️","version":5,"tone":4},{"emoji":"🧝🏿♀️","version":5,"tone":5}]},{"shortcodes":["genie"],"annotation":"genie","tags":["djinn"],"emoji":"🧞","order":1877,"group":1,"version":5},{"shortcodes":["man_genie"],"annotation":"man genie","tags":["djinn"],"emoji":"🧞♂️","order":1878,"group":1,"version":5},{"shortcodes":["woman_genie"],"annotation":"woman genie","tags":["djinn"],"emoji":"🧞♀️","order":1880,"group":1,"version":5},{"shortcodes":["zombie"],"annotation":"zombie","tags":["undead","walking dead"],"emoji":"🧟","order":1882,"group":1,"version":5,"emoticon":"8#"},{"shortcodes":["man_zombie"],"annotation":"man zombie","tags":["undead","walking dead"],"emoji":"🧟♂️","order":1883,"group":1,"version":5},{"shortcodes":["woman_zombie"],"annotation":"woman zombie","tags":["undead","walking dead"],"emoji":"🧟♀️","order":1885,"group":1,"version":5},{"shortcodes":["troll"],"annotation":"troll","tags":["fairy tale","fantasy","monster"],"emoji":"🧌","order":1887,"group":1,"version":14},{"shortcodes":["massage","person_getting_massage"],"annotation":"person getting massage","tags":["face","massage","salon"],"emoji":"💆","order":1888,"group":1,"version":0.6,"skins":[{"emoji":"💆🏻","version":1,"tone":1},{"emoji":"💆🏼","version":1,"tone":2},{"emoji":"💆🏽","version":1,"tone":3},{"emoji":"💆🏾","version":1,"tone":4},{"emoji":"💆🏿","version":1,"tone":5}]},{"shortcodes":["man_getting_massage"],"annotation":"man getting massage","tags":["face","man","massage"],"emoji":"💆♂️","order":1894,"group":1,"version":4,"skins":[{"emoji":"💆🏻♂️","version":4,"tone":1},{"emoji":"💆🏼♂️","version":4,"tone":2},{"emoji":"💆🏽♂️","version":4,"tone":3},{"emoji":"💆🏾♂️","version":4,"tone":4},{"emoji":"💆🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_getting_massage"],"annotation":"woman getting massage","tags":["face","massage","woman"],"emoji":"💆♀️","order":1906,"group":1,"version":4,"skins":[{"emoji":"💆🏻♀️","version":4,"tone":1},{"emoji":"💆🏼♀️","version":4,"tone":2},{"emoji":"💆🏽♀️","version":4,"tone":3},{"emoji":"💆🏾♀️","version":4,"tone":4},{"emoji":"💆🏿♀️","version":4,"tone":5}]},{"shortcodes":["haircut","person_getting_haircut"],"annotation":"person getting haircut","tags":["barber","beauty","haircut","parlor"],"emoji":"💇","order":1918,"group":1,"version":0.6,"skins":[{"emoji":"💇🏻","version":1,"tone":1},{"emoji":"💇🏼","version":1,"tone":2},{"emoji":"💇🏽","version":1,"tone":3},{"emoji":"💇🏾","version":1,"tone":4},{"emoji":"💇🏿","version":1,"tone":5}]},{"shortcodes":["man_getting_haircut"],"annotation":"man getting haircut","tags":["haircut","man"],"emoji":"💇♂️","order":1924,"group":1,"version":4,"skins":[{"emoji":"💇🏻♂️","version":4,"tone":1},{"emoji":"💇🏼♂️","version":4,"tone":2},{"emoji":"💇🏽♂️","version":4,"tone":3},{"emoji":"💇🏾♂️","version":4,"tone":4},{"emoji":"💇🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_getting_haircut"],"annotation":"woman getting haircut","tags":["haircut","woman"],"emoji":"💇♀️","order":1936,"group":1,"version":4,"skins":[{"emoji":"💇🏻♀️","version":4,"tone":1},{"emoji":"💇🏼♀️","version":4,"tone":2},{"emoji":"💇🏽♀️","version":4,"tone":3},{"emoji":"💇🏾♀️","version":4,"tone":4},{"emoji":"💇🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_walking","walking"],"annotation":"person walking","tags":["hike","walk","walking"],"emoji":"🚶","order":1948,"group":1,"version":0.6,"skins":[{"emoji":"🚶🏻","version":1,"tone":1},{"emoji":"🚶🏼","version":1,"tone":2},{"emoji":"🚶🏽","version":1,"tone":3},{"emoji":"🚶🏾","version":1,"tone":4},{"emoji":"🚶🏿","version":1,"tone":5}]},{"shortcodes":["man_walking"],"annotation":"man walking","tags":["hike","man","walk"],"emoji":"🚶♂️","order":1954,"group":1,"version":4,"skins":[{"emoji":"🚶🏻♂️","version":4,"tone":1},{"emoji":"🚶🏼♂️","version":4,"tone":2},{"emoji":"🚶🏽♂️","version":4,"tone":3},{"emoji":"🚶🏾♂️","version":4,"tone":4},{"emoji":"🚶🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_walking"],"annotation":"woman walking","tags":["hike","walk","woman"],"emoji":"🚶♀️","order":1966,"group":1,"version":4,"skins":[{"emoji":"🚶🏻♀️","version":4,"tone":1},{"emoji":"🚶🏼♀️","version":4,"tone":2},{"emoji":"🚶🏽♀️","version":4,"tone":3},{"emoji":"🚶🏾♀️","version":4,"tone":4},{"emoji":"🚶🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_walking_right"],"annotation":"person walking facing right","tags":["arrow","cardinal","direction","east","hike","person walking","right arrow","walk","walking"],"emoji":"🚶➡️","order":1978,"group":1,"version":15.1,"skins":[{"emoji":"🚶🏻➡️","version":15.1,"tone":1},{"emoji":"🚶🏼➡️","version":15.1,"tone":2},{"emoji":"🚶🏽➡️","version":15.1,"tone":3},{"emoji":"🚶🏾➡️","version":15.1,"tone":4},{"emoji":"🚶🏿➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_walking_right"],"annotation":"woman walking facing right","tags":["arrow","cardinal","direction","east","female sign","hike","person walking","right arrow","walk","walking","woman"],"emoji":"🚶♀️➡️","order":1990,"group":1,"version":15.1,"skins":[{"emoji":"🚶🏻♀️➡️","version":15.1,"tone":1},{"emoji":"🚶🏼♀️➡️","version":15.1,"tone":2},{"emoji":"🚶🏽♀️➡️","version":15.1,"tone":3},{"emoji":"🚶🏾♀️➡️","version":15.1,"tone":4},{"emoji":"🚶🏿♀️➡️","version":15.1,"tone":5}]},{"shortcodes":["man_walking_right"],"annotation":"man walking facing right","tags":["arrow","cardinal","direction","east","hike","male sign","man","person walking","right arrow","walk","walking"],"emoji":"🚶♂️➡️","order":2014,"group":1,"version":15.1,"skins":[{"emoji":"🚶🏻♂️➡️","version":15.1,"tone":1},{"emoji":"🚶🏼♂️➡️","version":15.1,"tone":2},{"emoji":"🚶🏽♂️➡️","version":15.1,"tone":3},{"emoji":"🚶🏾♂️➡️","version":15.1,"tone":4},{"emoji":"🚶🏿♂️➡️","version":15.1,"tone":5}]},{"shortcodes":["person_standing","standing"],"annotation":"person standing","tags":["stand","standing"],"emoji":"🧍","order":2038,"group":1,"version":12,"skins":[{"emoji":"🧍🏻","version":12,"tone":1},{"emoji":"🧍🏼","version":12,"tone":2},{"emoji":"🧍🏽","version":12,"tone":3},{"emoji":"🧍🏾","version":12,"tone":4},{"emoji":"🧍🏿","version":12,"tone":5}]},{"shortcodes":["man_standing"],"annotation":"man standing","tags":["man","standing"],"emoji":"🧍♂️","order":2044,"group":1,"version":12,"skins":[{"emoji":"🧍🏻♂️","version":12,"tone":1},{"emoji":"🧍🏼♂️","version":12,"tone":2},{"emoji":"🧍🏽♂️","version":12,"tone":3},{"emoji":"🧍🏾♂️","version":12,"tone":4},{"emoji":"🧍🏿♂️","version":12,"tone":5}]},{"shortcodes":["woman_standing"],"annotation":"woman standing","tags":["standing","woman"],"emoji":"🧍♀️","order":2056,"group":1,"version":12,"skins":[{"emoji":"🧍🏻♀️","version":12,"tone":1},{"emoji":"🧍🏼♀️","version":12,"tone":2},{"emoji":"🧍🏽♀️","version":12,"tone":3},{"emoji":"🧍🏾♀️","version":12,"tone":4},{"emoji":"🧍🏿♀️","version":12,"tone":5}]},{"shortcodes":["kneeling","person_kneeling"],"annotation":"person kneeling","tags":["kneel","kneeling"],"emoji":"🧎","order":2068,"group":1,"version":12,"skins":[{"emoji":"🧎🏻","version":12,"tone":1},{"emoji":"🧎🏼","version":12,"tone":2},{"emoji":"🧎🏽","version":12,"tone":3},{"emoji":"🧎🏾","version":12,"tone":4},{"emoji":"🧎🏿","version":12,"tone":5}]},{"shortcodes":["man_kneeling"],"annotation":"man kneeling","tags":["kneeling","man"],"emoji":"🧎♂️","order":2074,"group":1,"version":12,"skins":[{"emoji":"🧎🏻♂️","version":12,"tone":1},{"emoji":"🧎🏼♂️","version":12,"tone":2},{"emoji":"🧎🏽♂️","version":12,"tone":3},{"emoji":"🧎🏾♂️","version":12,"tone":4},{"emoji":"🧎🏿♂️","version":12,"tone":5}]},{"shortcodes":["woman_kneeling"],"annotation":"woman kneeling","tags":["kneeling","woman"],"emoji":"🧎♀️","order":2086,"group":1,"version":12,"skins":[{"emoji":"🧎🏻♀️","version":12,"tone":1},{"emoji":"🧎🏼♀️","version":12,"tone":2},{"emoji":"🧎🏽♀️","version":12,"tone":3},{"emoji":"🧎🏾♀️","version":12,"tone":4},{"emoji":"🧎🏿♀️","version":12,"tone":5}]},{"shortcodes":["person_kneeling_right"],"annotation":"person kneeling facing right","tags":["arrow","cardinal","direction","east","kneel","kneeling","person kneeling","right arrow"],"emoji":"🧎➡️","order":2098,"group":1,"version":15.1,"skins":[{"emoji":"🧎🏻➡️","version":15.1,"tone":1},{"emoji":"🧎🏼➡️","version":15.1,"tone":2},{"emoji":"🧎🏽➡️","version":15.1,"tone":3},{"emoji":"🧎🏾➡️","version":15.1,"tone":4},{"emoji":"🧎🏿➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_kneeling_right"],"annotation":"woman kneeling facing right","tags":["arrow","cardinal","direction","east","female sign","kneel","kneeling","person kneeling","right arrow","woman"],"emoji":"🧎♀️➡️","order":2110,"group":1,"version":15.1,"skins":[{"emoji":"🧎🏻♀️➡️","version":15.1,"tone":1},{"emoji":"🧎🏼♀️➡️","version":15.1,"tone":2},{"emoji":"🧎🏽♀️➡️","version":15.1,"tone":3},{"emoji":"🧎🏾♀️➡️","version":15.1,"tone":4},{"emoji":"🧎🏿♀️➡️","version":15.1,"tone":5}]},{"shortcodes":["man_kneeling_right"],"annotation":"man kneeling facing right","tags":["arrow","cardinal","direction","east","kneel","kneeling","male sign","man","person kneeling","right arrow"],"emoji":"🧎♂️➡️","order":2134,"group":1,"version":15.1,"skins":[{"emoji":"🧎🏻♂️➡️","version":15.1,"tone":1},{"emoji":"🧎🏼♂️➡️","version":15.1,"tone":2},{"emoji":"🧎🏽♂️➡️","version":15.1,"tone":3},{"emoji":"🧎🏾♂️➡️","version":15.1,"tone":4},{"emoji":"🧎🏿♂️➡️","version":15.1,"tone":5}]},{"shortcodes":["person_with_probing_cane","person_with_white_cane"],"annotation":"person with white cane","tags":["accessibility","blind"],"emoji":"🧑🦯","order":2158,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦯","version":12.1,"tone":1},{"emoji":"🧑🏼🦯","version":12.1,"tone":2},{"emoji":"🧑🏽🦯","version":12.1,"tone":3},{"emoji":"🧑🏾🦯","version":12.1,"tone":4},{"emoji":"🧑🏿🦯","version":12.1,"tone":5}]},{"shortcodes":["person_with_white_cane_right"],"annotation":"person with white cane facing right","tags":["accessibility","adult","arrow","blind","cardinal","direction","east","gender-neutral","person","right arrow","unspecified gender","white cane"],"emoji":"🧑🦯➡️","order":2164,"group":1,"version":15.1,"skins":[{"emoji":"🧑🏻🦯➡️","version":15.1,"tone":1},{"emoji":"🧑🏼🦯➡️","version":15.1,"tone":2},{"emoji":"🧑🏽🦯➡️","version":15.1,"tone":3},{"emoji":"🧑🏾🦯➡️","version":15.1,"tone":4},{"emoji":"🧑🏿🦯➡️","version":15.1,"tone":5}]},{"shortcodes":["man_with_probing_cane","man_with_white_cane"],"annotation":"man with white cane","tags":["accessibility","blind","man"],"emoji":"👨🦯","order":2176,"group":1,"version":12,"skins":[{"emoji":"👨🏻🦯","version":12,"tone":1},{"emoji":"👨🏼🦯","version":12,"tone":2},{"emoji":"👨🏽🦯","version":12,"tone":3},{"emoji":"👨🏾🦯","version":12,"tone":4},{"emoji":"👨🏿🦯","version":12,"tone":5}]},{"shortcodes":["man_with_white_cane_right"],"annotation":"man with white cane facing right","tags":["accessibility","adult","arrow","blind","cardinal","direction","east","man","right arrow","white cane"],"emoji":"👨🦯➡️","order":2182,"group":1,"version":15.1,"skins":[{"emoji":"👨🏻🦯➡️","version":15.1,"tone":1},{"emoji":"👨🏼🦯➡️","version":15.1,"tone":2},{"emoji":"👨🏽🦯➡️","version":15.1,"tone":3},{"emoji":"👨🏾🦯➡️","version":15.1,"tone":4},{"emoji":"👨🏿🦯➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_with_probing_cane","woman_with_white_cane"],"annotation":"woman with white cane","tags":["accessibility","blind","woman"],"emoji":"👩🦯","order":2194,"group":1,"version":12,"skins":[{"emoji":"👩🏻🦯","version":12,"tone":1},{"emoji":"👩🏼🦯","version":12,"tone":2},{"emoji":"👩🏽🦯","version":12,"tone":3},{"emoji":"👩🏾🦯","version":12,"tone":4},{"emoji":"👩🏿🦯","version":12,"tone":5}]},{"shortcodes":["woman_with_white_cane_right"],"annotation":"woman with white cane facing right","tags":["accessibility","adult","arrow","blind","cardinal","direction","east","right arrow","white cane","woman"],"emoji":"👩🦯➡️","order":2200,"group":1,"version":15.1,"skins":[{"emoji":"👩🏻🦯➡️","version":15.1,"tone":1},{"emoji":"👩🏼🦯➡️","version":15.1,"tone":2},{"emoji":"👩🏽🦯➡️","version":15.1,"tone":3},{"emoji":"👩🏾🦯➡️","version":15.1,"tone":4},{"emoji":"👩🏿🦯➡️","version":15.1,"tone":5}]},{"shortcodes":["person_in_motorized_wheelchair"],"annotation":"person in motorized wheelchair","tags":["accessibility","wheelchair"],"emoji":"🧑🦼","order":2212,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦼","version":12.1,"tone":1},{"emoji":"🧑🏼🦼","version":12.1,"tone":2},{"emoji":"🧑🏽🦼","version":12.1,"tone":3},{"emoji":"🧑🏾🦼","version":12.1,"tone":4},{"emoji":"🧑🏿🦼","version":12.1,"tone":5}]},{"shortcodes":["person_in_motorized_wheelchair_right"],"annotation":"person in motorized wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","gender-neutral","motorized wheelchair","person","right arrow","unspecified gender"],"emoji":"🧑🦼➡️","order":2218,"group":1,"version":15.1,"skins":[{"emoji":"🧑🏻🦼➡️","version":15.1,"tone":1},{"emoji":"🧑🏼🦼➡️","version":15.1,"tone":2},{"emoji":"🧑🏽🦼➡️","version":15.1,"tone":3},{"emoji":"🧑🏾🦼➡️","version":15.1,"tone":4},{"emoji":"🧑🏿🦼➡️","version":15.1,"tone":5}]},{"shortcodes":["man_in_motorized_wheelchair"],"annotation":"man in motorized wheelchair","tags":["accessibility","man","wheelchair"],"emoji":"👨🦼","order":2230,"group":1,"version":12,"skins":[{"emoji":"👨🏻🦼","version":12,"tone":1},{"emoji":"👨🏼🦼","version":12,"tone":2},{"emoji":"👨🏽🦼","version":12,"tone":3},{"emoji":"👨🏾🦼","version":12,"tone":4},{"emoji":"👨🏿🦼","version":12,"tone":5}]},{"shortcodes":["man_in_motorized_wheelchair_right"],"annotation":"man in motorized wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","man","motorized wheelchair","right arrow"],"emoji":"👨🦼➡️","order":2236,"group":1,"version":15.1,"skins":[{"emoji":"👨🏻🦼➡️","version":15.1,"tone":1},{"emoji":"👨🏼🦼➡️","version":15.1,"tone":2},{"emoji":"👨🏽🦼➡️","version":15.1,"tone":3},{"emoji":"👨🏾🦼➡️","version":15.1,"tone":4},{"emoji":"👨🏿🦼➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_in_motorized_wheelchair"],"annotation":"woman in motorized wheelchair","tags":["accessibility","wheelchair","woman"],"emoji":"👩🦼","order":2248,"group":1,"version":12,"skins":[{"emoji":"👩🏻🦼","version":12,"tone":1},{"emoji":"👩🏼🦼","version":12,"tone":2},{"emoji":"👩🏽🦼","version":12,"tone":3},{"emoji":"👩🏾🦼","version":12,"tone":4},{"emoji":"👩🏿🦼","version":12,"tone":5}]},{"shortcodes":["woman_in_motorized_wheelchair_right"],"annotation":"woman in motorized wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","motorized wheelchair","right arrow","woman"],"emoji":"👩🦼➡️","order":2254,"group":1,"version":15.1,"skins":[{"emoji":"👩🏻🦼➡️","version":15.1,"tone":1},{"emoji":"👩🏼🦼➡️","version":15.1,"tone":2},{"emoji":"👩🏽🦼➡️","version":15.1,"tone":3},{"emoji":"👩🏾🦼➡️","version":15.1,"tone":4},{"emoji":"👩🏿🦼➡️","version":15.1,"tone":5}]},{"shortcodes":["person_in_manual_wheelchair"],"annotation":"person in manual wheelchair","tags":["accessibility","wheelchair"],"emoji":"🧑🦽","order":2266,"group":1,"version":12.1,"skins":[{"emoji":"🧑🏻🦽","version":12.1,"tone":1},{"emoji":"🧑🏼🦽","version":12.1,"tone":2},{"emoji":"🧑🏽🦽","version":12.1,"tone":3},{"emoji":"🧑🏾🦽","version":12.1,"tone":4},{"emoji":"🧑🏿🦽","version":12.1,"tone":5}]},{"shortcodes":["person_in_manual_wheelchair_right"],"annotation":"person in manual wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","gender-neutral","manual wheelchair","person","right arrow","unspecified gender"],"emoji":"🧑🦽➡️","order":2272,"group":1,"version":15.1,"skins":[{"emoji":"🧑🏻🦽➡️","version":15.1,"tone":1},{"emoji":"🧑🏼🦽➡️","version":15.1,"tone":2},{"emoji":"🧑🏽🦽➡️","version":15.1,"tone":3},{"emoji":"🧑🏾🦽➡️","version":15.1,"tone":4},{"emoji":"🧑🏿🦽➡️","version":15.1,"tone":5}]},{"shortcodes":["man_in_manual_wheelchair"],"annotation":"man in manual wheelchair","tags":["accessibility","man","wheelchair"],"emoji":"👨🦽","order":2284,"group":1,"version":12,"skins":[{"emoji":"👨🏻🦽","version":12,"tone":1},{"emoji":"👨🏼🦽","version":12,"tone":2},{"emoji":"👨🏽🦽","version":12,"tone":3},{"emoji":"👨🏾🦽","version":12,"tone":4},{"emoji":"👨🏿🦽","version":12,"tone":5}]},{"shortcodes":["man_in_manual_wheelchair_right"],"annotation":"man in manual wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","man","manual wheelchair","right arrow"],"emoji":"👨🦽➡️","order":2290,"group":1,"version":15.1,"skins":[{"emoji":"👨🏻🦽➡️","version":15.1,"tone":1},{"emoji":"👨🏼🦽➡️","version":15.1,"tone":2},{"emoji":"👨🏽🦽➡️","version":15.1,"tone":3},{"emoji":"👨🏾🦽➡️","version":15.1,"tone":4},{"emoji":"👨🏿🦽➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_in_manual_wheelchair"],"annotation":"woman in manual wheelchair","tags":["accessibility","wheelchair","woman"],"emoji":"👩🦽","order":2302,"group":1,"version":12,"skins":[{"emoji":"👩🏻🦽","version":12,"tone":1},{"emoji":"👩🏼🦽","version":12,"tone":2},{"emoji":"👩🏽🦽","version":12,"tone":3},{"emoji":"👩🏾🦽","version":12,"tone":4},{"emoji":"👩🏿🦽","version":12,"tone":5}]},{"shortcodes":["woman_in_manual_wheelchair_right"],"annotation":"woman in manual wheelchair facing right","tags":["accessibility","adult","arrow","cardinal","direction","east","manual wheelchair","right arrow","woman"],"emoji":"👩🦽➡️","order":2308,"group":1,"version":15.1,"skins":[{"emoji":"👩🏻🦽➡️","version":15.1,"tone":1},{"emoji":"👩🏼🦽➡️","version":15.1,"tone":2},{"emoji":"👩🏽🦽➡️","version":15.1,"tone":3},{"emoji":"👩🏾🦽➡️","version":15.1,"tone":4},{"emoji":"👩🏿🦽➡️","version":15.1,"tone":5}]},{"shortcodes":["person_running","running"],"annotation":"person running","tags":["marathon","running"],"emoji":"🏃","order":2320,"group":1,"version":0.6,"skins":[{"emoji":"🏃🏻","version":1,"tone":1},{"emoji":"🏃🏼","version":1,"tone":2},{"emoji":"🏃🏽","version":1,"tone":3},{"emoji":"🏃🏾","version":1,"tone":4},{"emoji":"🏃🏿","version":1,"tone":5}]},{"shortcodes":["man_running"],"annotation":"man running","tags":["man","marathon","racing","running"],"emoji":"🏃♂️","order":2326,"group":1,"version":4,"skins":[{"emoji":"🏃🏻♂️","version":4,"tone":1},{"emoji":"🏃🏼♂️","version":4,"tone":2},{"emoji":"🏃🏽♂️","version":4,"tone":3},{"emoji":"🏃🏾♂️","version":4,"tone":4},{"emoji":"🏃🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_running"],"annotation":"woman running","tags":["marathon","racing","running","woman"],"emoji":"🏃♀️","order":2338,"group":1,"version":4,"skins":[{"emoji":"🏃🏻♀️","version":4,"tone":1},{"emoji":"🏃🏼♀️","version":4,"tone":2},{"emoji":"🏃🏽♀️","version":4,"tone":3},{"emoji":"🏃🏾♀️","version":4,"tone":4},{"emoji":"🏃🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_running_right"],"annotation":"person running facing right","tags":["arrow","cardinal","direction","east","marathon","person running","right arrow","running"],"emoji":"🏃➡️","order":2350,"group":1,"version":15.1,"skins":[{"emoji":"🏃🏻➡️","version":15.1,"tone":1},{"emoji":"🏃🏼➡️","version":15.1,"tone":2},{"emoji":"🏃🏽➡️","version":15.1,"tone":3},{"emoji":"🏃🏾➡️","version":15.1,"tone":4},{"emoji":"🏃🏿➡️","version":15.1,"tone":5}]},{"shortcodes":["woman_running_right"],"annotation":"woman running facing right","tags":["arrow","cardinal","direction","east","female sign","marathon","person running","right arrow","running","woman"],"emoji":"🏃♀️➡️","order":2362,"group":1,"version":15.1,"skins":[{"emoji":"🏃🏻♀️➡️","version":15.1,"tone":1},{"emoji":"🏃🏼♀️➡️","version":15.1,"tone":2},{"emoji":"🏃🏽♀️➡️","version":15.1,"tone":3},{"emoji":"🏃🏾♀️➡️","version":15.1,"tone":4},{"emoji":"🏃🏿♀️➡️","version":15.1,"tone":5}]},{"shortcodes":["man_running_right"],"annotation":"man running facing right","tags":["arrow","cardinal","direction","east","male sign","man","marathon","person running","right arrow","running"],"emoji":"🏃♂️➡️","order":2386,"group":1,"version":15.1,"skins":[{"emoji":"🏃🏻♂️➡️","version":15.1,"tone":1},{"emoji":"🏃🏼♂️➡️","version":15.1,"tone":2},{"emoji":"🏃🏽♂️➡️","version":15.1,"tone":3},{"emoji":"🏃🏾♂️➡️","version":15.1,"tone":4},{"emoji":"🏃🏿♂️➡️","version":15.1,"tone":5}]},{"shortcodes":["dancer","woman_dancing"],"annotation":"woman dancing","tags":["dance","dancing","woman"],"emoji":"💃","order":2410,"group":1,"version":0.6,"skins":[{"emoji":"💃🏻","version":1,"tone":1},{"emoji":"💃🏼","version":1,"tone":2},{"emoji":"💃🏽","version":1,"tone":3},{"emoji":"💃🏾","version":1,"tone":4},{"emoji":"💃🏿","version":1,"tone":5}]},{"shortcodes":["man_dancing"],"annotation":"man dancing","tags":["dance","dancing","man"],"emoji":"🕺","order":2416,"group":1,"version":3,"skins":[{"emoji":"🕺🏻","version":3,"tone":1},{"emoji":"🕺🏼","version":3,"tone":2},{"emoji":"🕺🏽","version":3,"tone":3},{"emoji":"🕺🏾","version":3,"tone":4},{"emoji":"🕺🏿","version":3,"tone":5}]},{"shortcodes":["levitate","levitating","person_in_suit_levitating"],"annotation":"person in suit levitating","tags":["business","person","suit"],"emoji":"🕴️","order":2423,"group":1,"version":0.7,"skins":[{"emoji":"🕴🏻","version":4,"tone":1},{"emoji":"🕴🏼","version":4,"tone":2},{"emoji":"🕴🏽","version":4,"tone":3},{"emoji":"🕴🏾","version":4,"tone":4},{"emoji":"🕴🏿","version":4,"tone":5}]},{"shortcodes":["dancers","people_with_bunny_ears_partying"],"annotation":"people with bunny ears","tags":["bunny ear","dancer","partying"],"emoji":"👯","order":2429,"group":1,"version":0.6},{"shortcodes":["men_with_bunny_ears_partying"],"annotation":"men with bunny ears","tags":["bunny ear","dancer","men","partying"],"emoji":"👯♂️","order":2430,"group":1,"version":4},{"shortcodes":["women_with_bunny_ears_partying"],"annotation":"women with bunny ears","tags":["bunny ear","dancer","partying","women"],"emoji":"👯♀️","order":2432,"group":1,"version":4},{"shortcodes":["person_in_steamy_room"],"annotation":"person in steamy room","tags":["sauna","steam room"],"emoji":"🧖","order":2434,"group":1,"version":5,"skins":[{"emoji":"🧖🏻","version":5,"tone":1},{"emoji":"🧖🏼","version":5,"tone":2},{"emoji":"🧖🏽","version":5,"tone":3},{"emoji":"🧖🏾","version":5,"tone":4},{"emoji":"🧖🏿","version":5,"tone":5}]},{"shortcodes":["man_in_steamy_room"],"annotation":"man in steamy room","tags":["sauna","steam room"],"emoji":"🧖♂️","order":2440,"group":1,"version":5,"skins":[{"emoji":"🧖🏻♂️","version":5,"tone":1},{"emoji":"🧖🏼♂️","version":5,"tone":2},{"emoji":"🧖🏽♂️","version":5,"tone":3},{"emoji":"🧖🏾♂️","version":5,"tone":4},{"emoji":"🧖🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_in_steamy_room"],"annotation":"woman in steamy room","tags":["sauna","steam room"],"emoji":"🧖♀️","order":2452,"group":1,"version":5,"skins":[{"emoji":"🧖🏻♀️","version":5,"tone":1},{"emoji":"🧖🏼♀️","version":5,"tone":2},{"emoji":"🧖🏽♀️","version":5,"tone":3},{"emoji":"🧖🏾♀️","version":5,"tone":4},{"emoji":"🧖🏿♀️","version":5,"tone":5}]},{"shortcodes":["climbing","person_climbing"],"annotation":"person climbing","tags":["climber"],"emoji":"🧗","order":2464,"group":1,"version":5,"skins":[{"emoji":"🧗🏻","version":5,"tone":1},{"emoji":"🧗🏼","version":5,"tone":2},{"emoji":"🧗🏽","version":5,"tone":3},{"emoji":"🧗🏾","version":5,"tone":4},{"emoji":"🧗🏿","version":5,"tone":5}]},{"shortcodes":["man_climbing"],"annotation":"man climbing","tags":["climber"],"emoji":"🧗♂️","order":2470,"group":1,"version":5,"skins":[{"emoji":"🧗🏻♂️","version":5,"tone":1},{"emoji":"🧗🏼♂️","version":5,"tone":2},{"emoji":"🧗🏽♂️","version":5,"tone":3},{"emoji":"🧗🏾♂️","version":5,"tone":4},{"emoji":"🧗🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_climbing"],"annotation":"woman climbing","tags":["climber"],"emoji":"🧗♀️","order":2482,"group":1,"version":5,"skins":[{"emoji":"🧗🏻♀️","version":5,"tone":1},{"emoji":"🧗🏼♀️","version":5,"tone":2},{"emoji":"🧗🏽♀️","version":5,"tone":3},{"emoji":"🧗🏾♀️","version":5,"tone":4},{"emoji":"🧗🏿♀️","version":5,"tone":5}]},{"shortcodes":["fencer","fencing","person_fencing"],"annotation":"person fencing","tags":["fencer","fencing","sword"],"emoji":"🤺","order":2494,"group":1,"version":3},{"shortcodes":["horse_racing"],"annotation":"horse racing","tags":["horse","jockey","racehorse","racing"],"emoji":"🏇","order":2495,"group":1,"version":1,"skins":[{"emoji":"🏇🏻","version":1,"tone":1},{"emoji":"🏇🏼","version":1,"tone":2},{"emoji":"🏇🏽","version":1,"tone":3},{"emoji":"🏇🏾","version":1,"tone":4},{"emoji":"🏇🏿","version":1,"tone":5}]},{"shortcodes":["person_skiing","skier","skiing"],"annotation":"skier","tags":["ski","snow"],"emoji":"⛷️","order":2502,"group":1,"version":0.7},{"shortcodes":["person_snowboarding","snowboarder","snowboarding"],"annotation":"snowboarder","tags":["ski","snow","snowboard"],"emoji":"🏂️","order":2503,"group":1,"version":0.6,"skins":[{"emoji":"🏂🏻","version":1,"tone":1},{"emoji":"🏂🏼","version":1,"tone":2},{"emoji":"🏂🏽","version":1,"tone":3},{"emoji":"🏂🏾","version":1,"tone":4},{"emoji":"🏂🏿","version":1,"tone":5}]},{"shortcodes":["golfer","golfing","person_golfing"],"annotation":"person golfing","tags":["ball","golf"],"emoji":"🏌️","order":2510,"group":1,"version":0.7,"skins":[{"emoji":"🏌🏻","version":4,"tone":1},{"emoji":"🏌🏼","version":4,"tone":2},{"emoji":"🏌🏽","version":4,"tone":3},{"emoji":"🏌🏾","version":4,"tone":4},{"emoji":"🏌🏿","version":4,"tone":5}]},{"shortcodes":["man_golfing"],"annotation":"man golfing","tags":["golf","man"],"emoji":"🏌️♂️","order":2516,"group":1,"version":4,"skins":[{"emoji":"🏌🏻♂️","version":4,"tone":1},{"emoji":"🏌🏼♂️","version":4,"tone":2},{"emoji":"🏌🏽♂️","version":4,"tone":3},{"emoji":"🏌🏾♂️","version":4,"tone":4},{"emoji":"🏌🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_golfing"],"annotation":"woman golfing","tags":["golf","woman"],"emoji":"🏌️♀️","order":2530,"group":1,"version":4,"skins":[{"emoji":"🏌🏻♀️","version":4,"tone":1},{"emoji":"🏌🏼♀️","version":4,"tone":2},{"emoji":"🏌🏽♀️","version":4,"tone":3},{"emoji":"🏌🏾♀️","version":4,"tone":4},{"emoji":"🏌🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_surfing","surfer","surfing"],"annotation":"person surfing","tags":["surfing"],"emoji":"🏄️","order":2544,"group":1,"version":0.6,"skins":[{"emoji":"🏄🏻","version":1,"tone":1},{"emoji":"🏄🏼","version":1,"tone":2},{"emoji":"🏄🏽","version":1,"tone":3},{"emoji":"🏄🏾","version":1,"tone":4},{"emoji":"🏄🏿","version":1,"tone":5}]},{"shortcodes":["man_surfing"],"annotation":"man surfing","tags":["man","surfing"],"emoji":"🏄♂️","order":2550,"group":1,"version":4,"skins":[{"emoji":"🏄🏻♂️","version":4,"tone":1},{"emoji":"🏄🏼♂️","version":4,"tone":2},{"emoji":"🏄🏽♂️","version":4,"tone":3},{"emoji":"🏄🏾♂️","version":4,"tone":4},{"emoji":"🏄🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_surfing"],"annotation":"woman surfing","tags":["surfing","woman"],"emoji":"🏄♀️","order":2562,"group":1,"version":4,"skins":[{"emoji":"🏄🏻♀️","version":4,"tone":1},{"emoji":"🏄🏼♀️","version":4,"tone":2},{"emoji":"🏄🏽♀️","version":4,"tone":3},{"emoji":"🏄🏾♀️","version":4,"tone":4},{"emoji":"🏄🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_rowing_boat","rowboat"],"annotation":"person rowing boat","tags":["boat","rowboat"],"emoji":"🚣","order":2574,"group":1,"version":1,"skins":[{"emoji":"🚣🏻","version":1,"tone":1},{"emoji":"🚣🏼","version":1,"tone":2},{"emoji":"🚣🏽","version":1,"tone":3},{"emoji":"🚣🏾","version":1,"tone":4},{"emoji":"🚣🏿","version":1,"tone":5}]},{"shortcodes":["man_rowing_boat"],"annotation":"man rowing boat","tags":["boat","man","rowboat"],"emoji":"🚣♂️","order":2580,"group":1,"version":4,"skins":[{"emoji":"🚣🏻♂️","version":4,"tone":1},{"emoji":"🚣🏼♂️","version":4,"tone":2},{"emoji":"🚣🏽♂️","version":4,"tone":3},{"emoji":"🚣🏾♂️","version":4,"tone":4},{"emoji":"🚣🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_rowing_boat"],"annotation":"woman rowing boat","tags":["boat","rowboat","woman"],"emoji":"🚣♀️","order":2592,"group":1,"version":4,"skins":[{"emoji":"🚣🏻♀️","version":4,"tone":1},{"emoji":"🚣🏼♀️","version":4,"tone":2},{"emoji":"🚣🏽♀️","version":4,"tone":3},{"emoji":"🚣🏾♀️","version":4,"tone":4},{"emoji":"🚣🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_swimming","swimmer","swimming"],"annotation":"person swimming","tags":["swim"],"emoji":"🏊️","order":2604,"group":1,"version":0.6,"skins":[{"emoji":"🏊🏻","version":1,"tone":1},{"emoji":"🏊🏼","version":1,"tone":2},{"emoji":"🏊🏽","version":1,"tone":3},{"emoji":"🏊🏾","version":1,"tone":4},{"emoji":"🏊🏿","version":1,"tone":5}]},{"shortcodes":["man_swimming"],"annotation":"man swimming","tags":["man","swim"],"emoji":"🏊♂️","order":2610,"group":1,"version":4,"skins":[{"emoji":"🏊🏻♂️","version":4,"tone":1},{"emoji":"🏊🏼♂️","version":4,"tone":2},{"emoji":"🏊🏽♂️","version":4,"tone":3},{"emoji":"🏊🏾♂️","version":4,"tone":4},{"emoji":"🏊🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_swimming"],"annotation":"woman swimming","tags":["swim","woman"],"emoji":"🏊♀️","order":2622,"group":1,"version":4,"skins":[{"emoji":"🏊🏻♀️","version":4,"tone":1},{"emoji":"🏊🏼♀️","version":4,"tone":2},{"emoji":"🏊🏽♀️","version":4,"tone":3},{"emoji":"🏊🏾♀️","version":4,"tone":4},{"emoji":"🏊🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_bouncing_ball"],"annotation":"person bouncing ball","tags":["ball"],"emoji":"⛹️","order":2635,"group":1,"version":0.7,"skins":[{"emoji":"⛹🏻","version":2,"tone":1},{"emoji":"⛹🏼","version":2,"tone":2},{"emoji":"⛹🏽","version":2,"tone":3},{"emoji":"⛹🏾","version":2,"tone":4},{"emoji":"⛹🏿","version":2,"tone":5}]},{"shortcodes":["man_bouncing_ball"],"annotation":"man bouncing ball","tags":["ball","man"],"emoji":"⛹️♂️","order":2641,"group":1,"version":4,"skins":[{"emoji":"⛹🏻♂️","version":4,"tone":1},{"emoji":"⛹🏼♂️","version":4,"tone":2},{"emoji":"⛹🏽♂️","version":4,"tone":3},{"emoji":"⛹🏾♂️","version":4,"tone":4},{"emoji":"⛹🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_bouncing_ball"],"annotation":"woman bouncing ball","tags":["ball","woman"],"emoji":"⛹️♀️","order":2655,"group":1,"version":4,"skins":[{"emoji":"⛹🏻♀️","version":4,"tone":1},{"emoji":"⛹🏼♀️","version":4,"tone":2},{"emoji":"⛹🏽♀️","version":4,"tone":3},{"emoji":"⛹🏾♀️","version":4,"tone":4},{"emoji":"⛹🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_lifting_weights","weight_lifter","weight_lifting"],"annotation":"person lifting weights","tags":["lifter","weight"],"emoji":"🏋️","order":2670,"group":1,"version":0.7,"skins":[{"emoji":"🏋🏻","version":2,"tone":1},{"emoji":"🏋🏼","version":2,"tone":2},{"emoji":"🏋🏽","version":2,"tone":3},{"emoji":"🏋🏾","version":2,"tone":4},{"emoji":"🏋🏿","version":2,"tone":5}]},{"shortcodes":["man_lifting_weights"],"annotation":"man lifting weights","tags":["man","weight lifter"],"emoji":"🏋️♂️","order":2676,"group":1,"version":4,"skins":[{"emoji":"🏋🏻♂️","version":4,"tone":1},{"emoji":"🏋🏼♂️","version":4,"tone":2},{"emoji":"🏋🏽♂️","version":4,"tone":3},{"emoji":"🏋🏾♂️","version":4,"tone":4},{"emoji":"🏋🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_lifting_weights"],"annotation":"woman lifting weights","tags":["weight lifter","woman"],"emoji":"🏋️♀️","order":2690,"group":1,"version":4,"skins":[{"emoji":"🏋🏻♀️","version":4,"tone":1},{"emoji":"🏋🏼♀️","version":4,"tone":2},{"emoji":"🏋🏽♀️","version":4,"tone":3},{"emoji":"🏋🏾♀️","version":4,"tone":4},{"emoji":"🏋🏿♀️","version":4,"tone":5}]},{"shortcodes":["bicyclist","biking","person_biking"],"annotation":"person biking","tags":["bicycle","biking","cyclist"],"emoji":"🚴","order":2704,"group":1,"version":1,"skins":[{"emoji":"🚴🏻","version":1,"tone":1},{"emoji":"🚴🏼","version":1,"tone":2},{"emoji":"🚴🏽","version":1,"tone":3},{"emoji":"🚴🏾","version":1,"tone":4},{"emoji":"🚴🏿","version":1,"tone":5}]},{"shortcodes":["man_biking"],"annotation":"man biking","tags":["bicycle","biking","cyclist","man"],"emoji":"🚴♂️","order":2710,"group":1,"version":4,"skins":[{"emoji":"🚴🏻♂️","version":4,"tone":1},{"emoji":"🚴🏼♂️","version":4,"tone":2},{"emoji":"🚴🏽♂️","version":4,"tone":3},{"emoji":"🚴🏾♂️","version":4,"tone":4},{"emoji":"🚴🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_biking"],"annotation":"woman biking","tags":["bicycle","biking","cyclist","woman"],"emoji":"🚴♀️","order":2722,"group":1,"version":4,"skins":[{"emoji":"🚴🏻♀️","version":4,"tone":1},{"emoji":"🚴🏼♀️","version":4,"tone":2},{"emoji":"🚴🏽♀️","version":4,"tone":3},{"emoji":"🚴🏾♀️","version":4,"tone":4},{"emoji":"🚴🏿♀️","version":4,"tone":5}]},{"shortcodes":["mountain_bicyclist","mountain_biking","person_mountain_biking"],"annotation":"person mountain biking","tags":["bicycle","bicyclist","bike","cyclist","mountain"],"emoji":"🚵","order":2734,"group":1,"version":1,"skins":[{"emoji":"🚵🏻","version":1,"tone":1},{"emoji":"🚵🏼","version":1,"tone":2},{"emoji":"🚵🏽","version":1,"tone":3},{"emoji":"🚵🏾","version":1,"tone":4},{"emoji":"🚵🏿","version":1,"tone":5}]},{"shortcodes":["man_mountain_biking"],"annotation":"man mountain biking","tags":["bicycle","bike","cyclist","man","mountain"],"emoji":"🚵♂️","order":2740,"group":1,"version":4,"skins":[{"emoji":"🚵🏻♂️","version":4,"tone":1},{"emoji":"🚵🏼♂️","version":4,"tone":2},{"emoji":"🚵🏽♂️","version":4,"tone":3},{"emoji":"🚵🏾♂️","version":4,"tone":4},{"emoji":"🚵🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_mountain_biking"],"annotation":"woman mountain biking","tags":["bicycle","bike","biking","cyclist","mountain","woman"],"emoji":"🚵♀️","order":2752,"group":1,"version":4,"skins":[{"emoji":"🚵🏻♀️","version":4,"tone":1},{"emoji":"🚵🏼♀️","version":4,"tone":2},{"emoji":"🚵🏽♀️","version":4,"tone":3},{"emoji":"🚵🏾♀️","version":4,"tone":4},{"emoji":"🚵🏿♀️","version":4,"tone":5}]},{"shortcodes":["cartwheeling","person_cartwheel"],"annotation":"person cartwheeling","tags":["cartwheel","gymnastics"],"emoji":"🤸","order":2764,"group":1,"version":3,"skins":[{"emoji":"🤸🏻","version":3,"tone":1},{"emoji":"🤸🏼","version":3,"tone":2},{"emoji":"🤸🏽","version":3,"tone":3},{"emoji":"🤸🏾","version":3,"tone":4},{"emoji":"🤸🏿","version":3,"tone":5}]},{"shortcodes":["man_cartwheeling"],"annotation":"man cartwheeling","tags":["cartwheel","gymnastics","man"],"emoji":"🤸♂️","order":2770,"group":1,"version":4,"skins":[{"emoji":"🤸🏻♂️","version":4,"tone":1},{"emoji":"🤸🏼♂️","version":4,"tone":2},{"emoji":"🤸🏽♂️","version":4,"tone":3},{"emoji":"🤸🏾♂️","version":4,"tone":4},{"emoji":"🤸🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_cartwheeling"],"annotation":"woman cartwheeling","tags":["cartwheel","gymnastics","woman"],"emoji":"🤸♀️","order":2782,"group":1,"version":4,"skins":[{"emoji":"🤸🏻♀️","version":4,"tone":1},{"emoji":"🤸🏼♀️","version":4,"tone":2},{"emoji":"🤸🏽♀️","version":4,"tone":3},{"emoji":"🤸🏾♀️","version":4,"tone":4},{"emoji":"🤸🏿♀️","version":4,"tone":5}]},{"shortcodes":["people_wrestling","wrestlers","wrestling"],"annotation":"people wrestling","tags":["wrestle","wrestler"],"emoji":"🤼","order":2794,"group":1,"version":3},{"shortcodes":["men_wrestling"],"annotation":"men wrestling","tags":["men","wrestle"],"emoji":"🤼♂️","order":2795,"group":1,"version":4},{"shortcodes":["women_wrestling"],"annotation":"women wrestling","tags":["women","wrestle"],"emoji":"🤼♀️","order":2797,"group":1,"version":4},{"shortcodes":["person_playing_water_polo","water_polo"],"annotation":"person playing water polo","tags":["polo","water"],"emoji":"🤽","order":2799,"group":1,"version":3,"skins":[{"emoji":"🤽🏻","version":3,"tone":1},{"emoji":"🤽🏼","version":3,"tone":2},{"emoji":"🤽🏽","version":3,"tone":3},{"emoji":"🤽🏾","version":3,"tone":4},{"emoji":"🤽🏿","version":3,"tone":5}]},{"shortcodes":["man_playing_water_polo"],"annotation":"man playing water polo","tags":["man","water polo"],"emoji":"🤽♂️","order":2805,"group":1,"version":4,"skins":[{"emoji":"🤽🏻♂️","version":4,"tone":1},{"emoji":"🤽🏼♂️","version":4,"tone":2},{"emoji":"🤽🏽♂️","version":4,"tone":3},{"emoji":"🤽🏾♂️","version":4,"tone":4},{"emoji":"🤽🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_playing_water_polo"],"annotation":"woman playing water polo","tags":["water polo","woman"],"emoji":"🤽♀️","order":2817,"group":1,"version":4,"skins":[{"emoji":"🤽🏻♀️","version":4,"tone":1},{"emoji":"🤽🏼♀️","version":4,"tone":2},{"emoji":"🤽🏽♀️","version":4,"tone":3},{"emoji":"🤽🏾♀️","version":4,"tone":4},{"emoji":"🤽🏿♀️","version":4,"tone":5}]},{"shortcodes":["handball","person_playing_handball"],"annotation":"person playing handball","tags":["ball","handball"],"emoji":"🤾","order":2829,"group":1,"version":3,"skins":[{"emoji":"🤾🏻","version":3,"tone":1},{"emoji":"🤾🏼","version":3,"tone":2},{"emoji":"🤾🏽","version":3,"tone":3},{"emoji":"🤾🏾","version":3,"tone":4},{"emoji":"🤾🏿","version":3,"tone":5}]},{"shortcodes":["man_playing_handball"],"annotation":"man playing handball","tags":["handball","man"],"emoji":"🤾♂️","order":2835,"group":1,"version":4,"skins":[{"emoji":"🤾🏻♂️","version":4,"tone":1},{"emoji":"🤾🏼♂️","version":4,"tone":2},{"emoji":"🤾🏽♂️","version":4,"tone":3},{"emoji":"🤾🏾♂️","version":4,"tone":4},{"emoji":"🤾🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_playing_handball"],"annotation":"woman playing handball","tags":["handball","woman"],"emoji":"🤾♀️","order":2847,"group":1,"version":4,"skins":[{"emoji":"🤾🏻♀️","version":4,"tone":1},{"emoji":"🤾🏼♀️","version":4,"tone":2},{"emoji":"🤾🏽♀️","version":4,"tone":3},{"emoji":"🤾🏾♀️","version":4,"tone":4},{"emoji":"🤾🏿♀️","version":4,"tone":5}]},{"shortcodes":["juggler","juggling","person_juggling"],"annotation":"person juggling","tags":["balance","juggle","multitask","skill"],"emoji":"🤹","order":2859,"group":1,"version":3,"skins":[{"emoji":"🤹🏻","version":3,"tone":1},{"emoji":"🤹🏼","version":3,"tone":2},{"emoji":"🤹🏽","version":3,"tone":3},{"emoji":"🤹🏾","version":3,"tone":4},{"emoji":"🤹🏿","version":3,"tone":5}]},{"shortcodes":["man_juggling"],"annotation":"man juggling","tags":["juggling","man","multitask"],"emoji":"🤹♂️","order":2865,"group":1,"version":4,"skins":[{"emoji":"🤹🏻♂️","version":4,"tone":1},{"emoji":"🤹🏼♂️","version":4,"tone":2},{"emoji":"🤹🏽♂️","version":4,"tone":3},{"emoji":"🤹🏾♂️","version":4,"tone":4},{"emoji":"🤹🏿♂️","version":4,"tone":5}]},{"shortcodes":["woman_juggling"],"annotation":"woman juggling","tags":["juggling","multitask","woman"],"emoji":"🤹♀️","order":2877,"group":1,"version":4,"skins":[{"emoji":"🤹🏻♀️","version":4,"tone":1},{"emoji":"🤹🏼♀️","version":4,"tone":2},{"emoji":"🤹🏽♀️","version":4,"tone":3},{"emoji":"🤹🏾♀️","version":4,"tone":4},{"emoji":"🤹🏿♀️","version":4,"tone":5}]},{"shortcodes":["person_in_lotus_position"],"annotation":"person in lotus position","tags":["meditation","yoga"],"emoji":"🧘","order":2889,"group":1,"version":5,"skins":[{"emoji":"🧘🏻","version":5,"tone":1},{"emoji":"🧘🏼","version":5,"tone":2},{"emoji":"🧘🏽","version":5,"tone":3},{"emoji":"🧘🏾","version":5,"tone":4},{"emoji":"🧘🏿","version":5,"tone":5}]},{"shortcodes":["man_in_lotus_position"],"annotation":"man in lotus position","tags":["meditation","yoga"],"emoji":"🧘♂️","order":2895,"group":1,"version":5,"skins":[{"emoji":"🧘🏻♂️","version":5,"tone":1},{"emoji":"🧘🏼♂️","version":5,"tone":2},{"emoji":"🧘🏽♂️","version":5,"tone":3},{"emoji":"🧘🏾♂️","version":5,"tone":4},{"emoji":"🧘🏿♂️","version":5,"tone":5}]},{"shortcodes":["woman_in_lotus_position"],"annotation":"woman in lotus position","tags":["meditation","yoga"],"emoji":"🧘♀️","order":2907,"group":1,"version":5,"skins":[{"emoji":"🧘🏻♀️","version":5,"tone":1},{"emoji":"🧘🏼♀️","version":5,"tone":2},{"emoji":"🧘🏽♀️","version":5,"tone":3},{"emoji":"🧘🏾♀️","version":5,"tone":4},{"emoji":"🧘🏿♀️","version":5,"tone":5}]},{"shortcodes":["bath","person_taking_bath"],"annotation":"person taking bath","tags":["bath","bathtub"],"emoji":"🛀","order":2919,"group":1,"version":0.6,"skins":[{"emoji":"🛀🏻","version":1,"tone":1},{"emoji":"🛀🏼","version":1,"tone":2},{"emoji":"🛀🏽","version":1,"tone":3},{"emoji":"🛀🏾","version":1,"tone":4},{"emoji":"🛀🏿","version":1,"tone":5}]},{"shortcodes":["person_in_bed","sleeping_accommodation"],"annotation":"person in bed","tags":["good night","hotel","sleep"],"emoji":"🛌","order":2925,"group":1,"version":1,"skins":[{"emoji":"🛌🏻","version":4,"tone":1},{"emoji":"🛌🏼","version":4,"tone":2},{"emoji":"🛌🏽","version":4,"tone":3},{"emoji":"🛌🏾","version":4,"tone":4},{"emoji":"🛌🏿","version":4,"tone":5}]},{"shortcodes":["people_holding_hands"],"annotation":"people holding hands","tags":["couple","hand","hold","holding hands","person"],"emoji":"🧑🤝🧑","order":2931,"group":1,"version":12,"skins":[{"emoji":"🧑🏻🤝🧑🏻","version":12,"tone":1},{"emoji":"🧑🏻🤝🧑🏼","version":12.1,"tone":[1,2]},{"emoji":"🧑🏻🤝🧑🏽","version":12.1,"tone":[1,3]},{"emoji":"🧑🏻🤝🧑🏾","version":12.1,"tone":[1,4]},{"emoji":"🧑🏻🤝🧑🏿","version":12.1,"tone":[1,5]},{"emoji":"🧑🏼🤝🧑🏻","version":12,"tone":[2,1]},{"emoji":"🧑🏼🤝🧑🏼","version":12,"tone":2},{"emoji":"🧑🏼🤝🧑🏽","version":12.1,"tone":[2,3]},{"emoji":"🧑🏼🤝🧑🏾","version":12.1,"tone":[2,4]},{"emoji":"🧑🏼🤝🧑🏿","version":12.1,"tone":[2,5]},{"emoji":"🧑🏽🤝🧑🏻","version":12,"tone":[3,1]},{"emoji":"🧑🏽🤝🧑🏼","version":12,"tone":[3,2]},{"emoji":"🧑🏽🤝🧑🏽","version":12,"tone":3},{"emoji":"🧑🏽🤝🧑🏾","version":12.1,"tone":[3,4]},{"emoji":"🧑🏽🤝🧑🏿","version":12.1,"tone":[3,5]},{"emoji":"🧑🏾🤝🧑🏻","version":12,"tone":[4,1]},{"emoji":"🧑🏾🤝🧑🏼","version":12,"tone":[4,2]},{"emoji":"🧑🏾🤝🧑🏽","version":12,"tone":[4,3]},{"emoji":"🧑🏾🤝🧑🏾","version":12,"tone":4},{"emoji":"🧑🏾🤝🧑🏿","version":12.1,"tone":[4,5]},{"emoji":"🧑🏿🤝🧑🏻","version":12,"tone":[5,1]},{"emoji":"🧑🏿🤝🧑🏼","version":12,"tone":[5,2]},{"emoji":"🧑🏿🤝🧑🏽","version":12,"tone":[5,3]},{"emoji":"🧑🏿🤝🧑🏾","version":12,"tone":[5,4]},{"emoji":"🧑🏿🤝🧑🏿","version":12,"tone":5}]},{"shortcodes":["two_women_holding_hands"],"annotation":"women holding hands","tags":["couple","hand","holding hands","women"],"emoji":"👭","order":2957,"group":1,"version":1,"skins":[{"emoji":"👭🏻","version":12,"tone":1},{"emoji":"👭🏼","version":12,"tone":2},{"emoji":"👭🏽","version":12,"tone":3},{"emoji":"👭🏾","version":12,"tone":4},{"emoji":"👭🏿","version":12,"tone":5},{"emoji":"👩🏻🤝👩🏼","version":12.1,"tone":[1,2]},{"emoji":"👩🏻🤝👩🏽","version":12.1,"tone":[1,3]},{"emoji":"👩🏻🤝👩🏾","version":12.1,"tone":[1,4]},{"emoji":"👩🏻🤝👩🏿","version":12.1,"tone":[1,5]},{"emoji":"👩🏼🤝👩🏻","version":12,"tone":[2,1]},{"emoji":"👩🏼🤝👩🏽","version":12.1,"tone":[2,3]},{"emoji":"👩🏼🤝👩🏾","version":12.1,"tone":[2,4]},{"emoji":"👩🏼🤝👩🏿","version":12.1,"tone":[2,5]},{"emoji":"👩🏽🤝👩🏻","version":12,"tone":[3,1]},{"emoji":"👩🏽🤝👩🏼","version":12,"tone":[3,2]},{"emoji":"👩🏽🤝👩🏾","version":12.1,"tone":[3,4]},{"emoji":"👩🏽🤝👩🏿","version":12.1,"tone":[3,5]},{"emoji":"👩🏾🤝👩🏻","version":12,"tone":[4,1]},{"emoji":"👩🏾🤝👩🏼","version":12,"tone":[4,2]},{"emoji":"👩🏾🤝👩🏽","version":12,"tone":[4,3]},{"emoji":"👩🏾🤝👩🏿","version":12.1,"tone":[4,5]},{"emoji":"👩🏿🤝👩🏻","version":12,"tone":[5,1]},{"emoji":"👩🏿🤝👩🏼","version":12,"tone":[5,2]},{"emoji":"👩🏿🤝👩🏽","version":12,"tone":[5,3]},{"emoji":"👩🏿🤝👩🏾","version":12,"tone":[5,4]}]},{"shortcodes":["couple"],"annotation":"woman and man holding hands","tags":["couple","hand","hold","holding hands","man","woman"],"emoji":"👫","order":2983,"group":1,"version":0.6,"skins":[{"emoji":"👫🏻","version":12,"tone":1},{"emoji":"👫🏼","version":12,"tone":2},{"emoji":"👫🏽","version":12,"tone":3},{"emoji":"👫🏾","version":12,"tone":4},{"emoji":"👫🏿","version":12,"tone":5},{"emoji":"👩🏻🤝👨🏼","version":12,"tone":[1,2]},{"emoji":"👩🏻🤝👨🏽","version":12,"tone":[1,3]},{"emoji":"👩🏻🤝👨🏾","version":12,"tone":[1,4]},{"emoji":"👩🏻🤝👨🏿","version":12,"tone":[1,5]},{"emoji":"👩🏼🤝👨🏻","version":12,"tone":[2,1]},{"emoji":"👩🏼🤝👨🏽","version":12,"tone":[2,3]},{"emoji":"👩🏼🤝👨🏾","version":12,"tone":[2,4]},{"emoji":"👩🏼🤝👨🏿","version":12,"tone":[2,5]},{"emoji":"👩🏽🤝👨🏻","version":12,"tone":[3,1]},{"emoji":"👩🏽🤝👨🏼","version":12,"tone":[3,2]},{"emoji":"👩🏽🤝👨🏾","version":12,"tone":[3,4]},{"emoji":"👩🏽🤝👨🏿","version":12,"tone":[3,5]},{"emoji":"👩🏾🤝👨🏻","version":12,"tone":[4,1]},{"emoji":"👩🏾🤝👨🏼","version":12,"tone":[4,2]},{"emoji":"👩🏾🤝👨🏽","version":12,"tone":[4,3]},{"emoji":"👩🏾🤝👨🏿","version":12,"tone":[4,5]},{"emoji":"👩🏿🤝👨🏻","version":12,"tone":[5,1]},{"emoji":"👩🏿🤝👨🏼","version":12,"tone":[5,2]},{"emoji":"👩🏿🤝👨🏽","version":12,"tone":[5,3]},{"emoji":"👩🏿🤝👨🏾","version":12,"tone":[5,4]}]},{"shortcodes":["two_men_holding_hands"],"annotation":"men holding hands","tags":["couple","gemini","holding hands","man","men","twins","zodiac"],"emoji":"👬","order":3009,"group":1,"version":1,"skins":[{"emoji":"👬🏻","version":12,"tone":1},{"emoji":"👬🏼","version":12,"tone":2},{"emoji":"👬🏽","version":12,"tone":3},{"emoji":"👬🏾","version":12,"tone":4},{"emoji":"👬🏿","version":12,"tone":5},{"emoji":"👨🏻🤝👨🏼","version":12.1,"tone":[1,2]},{"emoji":"👨🏻🤝👨🏽","version":12.1,"tone":[1,3]},{"emoji":"👨🏻🤝👨🏾","version":12.1,"tone":[1,4]},{"emoji":"👨🏻🤝👨🏿","version":12.1,"tone":[1,5]},{"emoji":"👨🏼🤝👨🏻","version":12,"tone":[2,1]},{"emoji":"👨🏼🤝👨🏽","version":12.1,"tone":[2,3]},{"emoji":"👨🏼🤝👨🏾","version":12.1,"tone":[2,4]},{"emoji":"👨🏼🤝👨🏿","version":12.1,"tone":[2,5]},{"emoji":"👨🏽🤝👨🏻","version":12,"tone":[3,1]},{"emoji":"👨🏽🤝👨🏼","version":12,"tone":[3,2]},{"emoji":"👨🏽🤝👨🏾","version":12.1,"tone":[3,4]},{"emoji":"👨🏽🤝👨🏿","version":12.1,"tone":[3,5]},{"emoji":"👨🏾🤝👨🏻","version":12,"tone":[4,1]},{"emoji":"👨🏾🤝👨🏼","version":12,"tone":[4,2]},{"emoji":"👨🏾🤝👨🏽","version":12,"tone":[4,3]},{"emoji":"👨🏾🤝👨🏿","version":12.1,"tone":[4,5]},{"emoji":"👨🏿🤝👨🏻","version":12,"tone":[5,1]},{"emoji":"👨🏿🤝👨🏼","version":12,"tone":[5,2]},{"emoji":"👨🏿🤝👨🏽","version":12,"tone":[5,3]},{"emoji":"👨🏿🤝👨🏾","version":12,"tone":[5,4]}]},{"shortcodes":["couple_kiss","couplekiss"],"annotation":"kiss","tags":["couple"],"emoji":"💏","order":3035,"group":1,"version":0.6,"skins":[{"emoji":"💏🏻","version":13.1,"tone":1},{"emoji":"💏🏼","version":13.1,"tone":2},{"emoji":"💏🏽","version":13.1,"tone":3},{"emoji":"💏🏾","version":13.1,"tone":4},{"emoji":"💏🏿","version":13.1,"tone":5},{"emoji":"🧑🏻❤️💋🧑🏼","version":13.1,"tone":[1,2]},{"emoji":"🧑🏻❤️💋🧑🏽","version":13.1,"tone":[1,3]},{"emoji":"🧑🏻❤️💋🧑🏾","version":13.1,"tone":[1,4]},{"emoji":"🧑🏻❤️💋🧑🏿","version":13.1,"tone":[1,5]},{"emoji":"🧑🏼❤️💋🧑🏻","version":13.1,"tone":[2,1]},{"emoji":"🧑🏼❤️💋🧑🏽","version":13.1,"tone":[2,3]},{"emoji":"🧑🏼❤️💋🧑🏾","version":13.1,"tone":[2,4]},{"emoji":"🧑🏼❤️💋🧑🏿","version":13.1,"tone":[2,5]},{"emoji":"🧑🏽❤️💋🧑🏻","version":13.1,"tone":[3,1]},{"emoji":"🧑🏽❤️💋🧑🏼","version":13.1,"tone":[3,2]},{"emoji":"🧑🏽❤️💋🧑🏾","version":13.1,"tone":[3,4]},{"emoji":"🧑🏽❤️💋🧑🏿","version":13.1,"tone":[3,5]},{"emoji":"🧑🏾❤️💋🧑🏻","version":13.1,"tone":[4,1]},{"emoji":"🧑🏾❤️💋🧑🏼","version":13.1,"tone":[4,2]},{"emoji":"🧑🏾❤️💋🧑🏽","version":13.1,"tone":[4,3]},{"emoji":"🧑🏾❤️💋🧑🏿","version":13.1,"tone":[4,5]},{"emoji":"🧑🏿❤️💋🧑🏻","version":13.1,"tone":[5,1]},{"emoji":"🧑🏿❤️💋🧑🏼","version":13.1,"tone":[5,2]},{"emoji":"🧑🏿❤️💋🧑🏽","version":13.1,"tone":[5,3]},{"emoji":"🧑🏿❤️💋🧑🏾","version":13.1,"tone":[5,4]}]},{"shortcodes":["kiss_mw","kiss_wm"],"annotation":"kiss: woman, man","tags":["couple","kiss","man","woman"],"emoji":"👩❤️💋👨","order":3081,"group":1,"version":2,"skins":[{"emoji":"👩🏻❤️💋👨🏻","version":13.1,"tone":1},{"emoji":"👩🏻❤️💋👨🏼","version":13.1,"tone":[1,2]},{"emoji":"👩🏻❤️💋👨🏽","version":13.1,"tone":[1,3]},{"emoji":"👩🏻❤️💋👨🏾","version":13.1,"tone":[1,4]},{"emoji":"👩🏻❤️💋👨🏿","version":13.1,"tone":[1,5]},{"emoji":"👩🏼❤️💋👨🏻","version":13.1,"tone":[2,1]},{"emoji":"👩🏼❤️💋👨🏼","version":13.1,"tone":2},{"emoji":"👩🏼❤️💋👨🏽","version":13.1,"tone":[2,3]},{"emoji":"👩🏼❤️💋👨🏾","version":13.1,"tone":[2,4]},{"emoji":"👩🏼❤️💋👨🏿","version":13.1,"tone":[2,5]},{"emoji":"👩🏽❤️💋👨🏻","version":13.1,"tone":[3,1]},{"emoji":"👩🏽❤️💋👨🏼","version":13.1,"tone":[3,2]},{"emoji":"👩🏽❤️💋👨🏽","version":13.1,"tone":3},{"emoji":"👩🏽❤️💋👨🏾","version":13.1,"tone":[3,4]},{"emoji":"👩🏽❤️💋👨🏿","version":13.1,"tone":[3,5]},{"emoji":"👩🏾❤️💋👨🏻","version":13.1,"tone":[4,1]},{"emoji":"👩🏾❤️💋👨🏼","version":13.1,"tone":[4,2]},{"emoji":"👩🏾❤️💋👨🏽","version":13.1,"tone":[4,3]},{"emoji":"👩🏾❤️💋👨🏾","version":13.1,"tone":4},{"emoji":"👩🏾❤️💋👨🏿","version":13.1,"tone":[4,5]},{"emoji":"👩🏿❤️💋👨🏻","version":13.1,"tone":[5,1]},{"emoji":"👩🏿❤️💋👨🏼","version":13.1,"tone":[5,2]},{"emoji":"👩🏿❤️💋👨🏽","version":13.1,"tone":[5,3]},{"emoji":"👩🏿❤️💋👨🏾","version":13.1,"tone":[5,4]},{"emoji":"👩🏿❤️💋👨🏿","version":13.1,"tone":5}]},{"shortcodes":["kiss_mm"],"annotation":"kiss: man, man","tags":["couple","kiss","man"],"emoji":"👨❤️💋👨","order":3133,"group":1,"version":2,"skins":[{"emoji":"👨🏻❤️💋👨🏻","version":13.1,"tone":1},{"emoji":"👨🏻❤️💋👨🏼","version":13.1,"tone":[1,2]},{"emoji":"👨🏻❤️💋👨🏽","version":13.1,"tone":[1,3]},{"emoji":"👨🏻❤️💋👨🏾","version":13.1,"tone":[1,4]},{"emoji":"👨🏻❤️💋👨🏿","version":13.1,"tone":[1,5]},{"emoji":"👨🏼❤️💋👨🏻","version":13.1,"tone":[2,1]},{"emoji":"👨🏼❤️💋👨🏼","version":13.1,"tone":2},{"emoji":"👨🏼❤️💋👨🏽","version":13.1,"tone":[2,3]},{"emoji":"👨🏼❤️💋👨🏾","version":13.1,"tone":[2,4]},{"emoji":"👨🏼❤️💋👨🏿","version":13.1,"tone":[2,5]},{"emoji":"👨🏽❤️💋👨🏻","version":13.1,"tone":[3,1]},{"emoji":"👨🏽❤️💋👨🏼","version":13.1,"tone":[3,2]},{"emoji":"👨🏽❤️💋👨🏽","version":13.1,"tone":3},{"emoji":"👨🏽❤️💋👨🏾","version":13.1,"tone":[3,4]},{"emoji":"👨🏽❤️💋👨🏿","version":13.1,"tone":[3,5]},{"emoji":"👨🏾❤️💋👨🏻","version":13.1,"tone":[4,1]},{"emoji":"👨🏾❤️💋👨🏼","version":13.1,"tone":[4,2]},{"emoji":"👨🏾❤️💋👨🏽","version":13.1,"tone":[4,3]},{"emoji":"👨🏾❤️💋👨🏾","version":13.1,"tone":4},{"emoji":"👨🏾❤️💋👨🏿","version":13.1,"tone":[4,5]},{"emoji":"👨🏿❤️💋👨🏻","version":13.1,"tone":[5,1]},{"emoji":"👨🏿❤️💋👨🏼","version":13.1,"tone":[5,2]},{"emoji":"👨🏿❤️💋👨🏽","version":13.1,"tone":[5,3]},{"emoji":"👨🏿❤️💋👨🏾","version":13.1,"tone":[5,4]},{"emoji":"👨🏿❤️💋👨🏿","version":13.1,"tone":5}]},{"shortcodes":["kiss_ww"],"annotation":"kiss: woman, woman","tags":["couple","kiss","woman"],"emoji":"👩❤️💋👩","order":3185,"group":1,"version":2,"skins":[{"emoji":"👩🏻❤️💋👩🏻","version":13.1,"tone":1},{"emoji":"👩🏻❤️💋👩🏼","version":13.1,"tone":[1,2]},{"emoji":"👩🏻❤️💋👩🏽","version":13.1,"tone":[1,3]},{"emoji":"👩🏻❤️💋👩🏾","version":13.1,"tone":[1,4]},{"emoji":"👩🏻❤️💋👩🏿","version":13.1,"tone":[1,5]},{"emoji":"👩🏼❤️💋👩🏻","version":13.1,"tone":[2,1]},{"emoji":"👩🏼❤️💋👩🏼","version":13.1,"tone":2},{"emoji":"👩🏼❤️💋👩🏽","version":13.1,"tone":[2,3]},{"emoji":"👩🏼❤️💋👩🏾","version":13.1,"tone":[2,4]},{"emoji":"👩🏼❤️💋👩🏿","version":13.1,"tone":[2,5]},{"emoji":"👩🏽❤️💋👩🏻","version":13.1,"tone":[3,1]},{"emoji":"👩🏽❤️💋👩🏼","version":13.1,"tone":[3,2]},{"emoji":"👩🏽❤️💋👩🏽","version":13.1,"tone":3},{"emoji":"👩🏽❤️💋👩🏾","version":13.1,"tone":[3,4]},{"emoji":"👩🏽❤️💋👩🏿","version":13.1,"tone":[3,5]},{"emoji":"👩🏾❤️💋👩🏻","version":13.1,"tone":[4,1]},{"emoji":"👩🏾❤️💋👩🏼","version":13.1,"tone":[4,2]},{"emoji":"👩🏾❤️💋👩🏽","version":13.1,"tone":[4,3]},{"emoji":"👩🏾❤️💋👩🏾","version":13.1,"tone":4},{"emoji":"👩🏾❤️💋👩🏿","version":13.1,"tone":[4,5]},{"emoji":"👩🏿❤️💋👩🏻","version":13.1,"tone":[5,1]},{"emoji":"👩🏿❤️💋👩🏼","version":13.1,"tone":[5,2]},{"emoji":"👩🏿❤️💋👩🏽","version":13.1,"tone":[5,3]},{"emoji":"👩🏿❤️💋👩🏾","version":13.1,"tone":[5,4]},{"emoji":"👩🏿❤️💋👩🏿","version":13.1,"tone":5}]},{"shortcodes":["couple_with_heart"],"annotation":"couple with heart","tags":["couple","love"],"emoji":"💑","order":3237,"group":1,"version":0.6,"skins":[{"emoji":"💑🏻","version":13.1,"tone":1},{"emoji":"💑🏼","version":13.1,"tone":2},{"emoji":"💑🏽","version":13.1,"tone":3},{"emoji":"💑🏾","version":13.1,"tone":4},{"emoji":"💑🏿","version":13.1,"tone":5},{"emoji":"🧑🏻❤️🧑🏼","version":13.1,"tone":[1,2]},{"emoji":"🧑🏻❤️🧑🏽","version":13.1,"tone":[1,3]},{"emoji":"🧑🏻❤️🧑🏾","version":13.1,"tone":[1,4]},{"emoji":"🧑🏻❤️🧑🏿","version":13.1,"tone":[1,5]},{"emoji":"🧑🏼❤️🧑🏻","version":13.1,"tone":[2,1]},{"emoji":"🧑🏼❤️🧑🏽","version":13.1,"tone":[2,3]},{"emoji":"🧑🏼❤️🧑🏾","version":13.1,"tone":[2,4]},{"emoji":"🧑🏼❤️🧑🏿","version":13.1,"tone":[2,5]},{"emoji":"🧑🏽❤️🧑🏻","version":13.1,"tone":[3,1]},{"emoji":"🧑🏽❤️🧑🏼","version":13.1,"tone":[3,2]},{"emoji":"🧑🏽❤️🧑🏾","version":13.1,"tone":[3,4]},{"emoji":"🧑🏽❤️🧑🏿","version":13.1,"tone":[3,5]},{"emoji":"🧑🏾❤️🧑🏻","version":13.1,"tone":[4,1]},{"emoji":"🧑🏾❤️🧑🏼","version":13.1,"tone":[4,2]},{"emoji":"🧑🏾❤️🧑🏽","version":13.1,"tone":[4,3]},{"emoji":"🧑🏾❤️🧑🏿","version":13.1,"tone":[4,5]},{"emoji":"🧑🏿❤️🧑🏻","version":13.1,"tone":[5,1]},{"emoji":"🧑🏿❤️🧑🏼","version":13.1,"tone":[5,2]},{"emoji":"🧑🏿❤️🧑🏽","version":13.1,"tone":[5,3]},{"emoji":"🧑🏿❤️🧑🏾","version":13.1,"tone":[5,4]}]},{"shortcodes":["couple_with_heart_mw","couple_with_heart_wm"],"annotation":"couple with heart: woman, man","tags":["couple","couple with heart","love","man","woman"],"emoji":"👩❤️👨","order":3283,"group":1,"version":2,"skins":[{"emoji":"👩🏻❤️👨🏻","version":13.1,"tone":1},{"emoji":"👩🏻❤️👨🏼","version":13.1,"tone":[1,2]},{"emoji":"👩🏻❤️👨🏽","version":13.1,"tone":[1,3]},{"emoji":"👩🏻❤️👨🏾","version":13.1,"tone":[1,4]},{"emoji":"👩🏻❤️👨🏿","version":13.1,"tone":[1,5]},{"emoji":"👩🏼❤️👨🏻","version":13.1,"tone":[2,1]},{"emoji":"👩🏼❤️👨🏼","version":13.1,"tone":2},{"emoji":"👩🏼❤️👨🏽","version":13.1,"tone":[2,3]},{"emoji":"👩🏼❤️👨🏾","version":13.1,"tone":[2,4]},{"emoji":"👩🏼❤️👨🏿","version":13.1,"tone":[2,5]},{"emoji":"👩🏽❤️👨🏻","version":13.1,"tone":[3,1]},{"emoji":"👩🏽❤️👨🏼","version":13.1,"tone":[3,2]},{"emoji":"👩🏽❤️👨🏽","version":13.1,"tone":3},{"emoji":"👩🏽❤️👨🏾","version":13.1,"tone":[3,4]},{"emoji":"👩🏽❤️👨🏿","version":13.1,"tone":[3,5]},{"emoji":"👩🏾❤️👨🏻","version":13.1,"tone":[4,1]},{"emoji":"👩🏾❤️👨🏼","version":13.1,"tone":[4,2]},{"emoji":"👩🏾❤️👨🏽","version":13.1,"tone":[4,3]},{"emoji":"👩🏾❤️👨🏾","version":13.1,"tone":4},{"emoji":"👩🏾❤️👨🏿","version":13.1,"tone":[4,5]},{"emoji":"👩🏿❤️👨🏻","version":13.1,"tone":[5,1]},{"emoji":"👩🏿❤️👨🏼","version":13.1,"tone":[5,2]},{"emoji":"👩🏿❤️👨🏽","version":13.1,"tone":[5,3]},{"emoji":"👩🏿❤️👨🏾","version":13.1,"tone":[5,4]},{"emoji":"👩🏿❤️👨🏿","version":13.1,"tone":5}]},{"shortcodes":["couple_with_heart_mm"],"annotation":"couple with heart: man, man","tags":["couple","couple with heart","love","man"],"emoji":"👨❤️👨","order":3335,"group":1,"version":2,"skins":[{"emoji":"👨🏻❤️👨🏻","version":13.1,"tone":1},{"emoji":"👨🏻❤️👨🏼","version":13.1,"tone":[1,2]},{"emoji":"👨🏻❤️👨🏽","version":13.1,"tone":[1,3]},{"emoji":"👨🏻❤️👨🏾","version":13.1,"tone":[1,4]},{"emoji":"👨🏻❤️👨🏿","version":13.1,"tone":[1,5]},{"emoji":"👨🏼❤️👨🏻","version":13.1,"tone":[2,1]},{"emoji":"👨🏼❤️👨🏼","version":13.1,"tone":2},{"emoji":"👨🏼❤️👨🏽","version":13.1,"tone":[2,3]},{"emoji":"👨🏼❤️👨🏾","version":13.1,"tone":[2,4]},{"emoji":"👨🏼❤️👨🏿","version":13.1,"tone":[2,5]},{"emoji":"👨🏽❤️👨🏻","version":13.1,"tone":[3,1]},{"emoji":"👨🏽❤️👨🏼","version":13.1,"tone":[3,2]},{"emoji":"👨🏽❤️👨🏽","version":13.1,"tone":3},{"emoji":"👨🏽❤️👨🏾","version":13.1,"tone":[3,4]},{"emoji":"👨🏽❤️👨🏿","version":13.1,"tone":[3,5]},{"emoji":"👨🏾❤️👨🏻","version":13.1,"tone":[4,1]},{"emoji":"👨🏾❤️👨🏼","version":13.1,"tone":[4,2]},{"emoji":"👨🏾❤️👨🏽","version":13.1,"tone":[4,3]},{"emoji":"👨🏾❤️👨🏾","version":13.1,"tone":4},{"emoji":"👨🏾❤️👨🏿","version":13.1,"tone":[4,5]},{"emoji":"👨🏿❤️👨🏻","version":13.1,"tone":[5,1]},{"emoji":"👨🏿❤️👨🏼","version":13.1,"tone":[5,2]},{"emoji":"👨🏿❤️👨🏽","version":13.1,"tone":[5,3]},{"emoji":"👨🏿❤️👨🏾","version":13.1,"tone":[5,4]},{"emoji":"👨🏿❤️👨🏿","version":13.1,"tone":5}]},{"shortcodes":["couple_with_heart_ww"],"annotation":"couple with heart: woman, woman","tags":["couple","couple with heart","love","woman"],"emoji":"👩❤️👩","order":3387,"group":1,"version":2,"skins":[{"emoji":"👩🏻❤️👩🏻","version":13.1,"tone":1},{"emoji":"👩🏻❤️👩🏼","version":13.1,"tone":[1,2]},{"emoji":"👩🏻❤️👩🏽","version":13.1,"tone":[1,3]},{"emoji":"👩🏻❤️👩🏾","version":13.1,"tone":[1,4]},{"emoji":"👩🏻❤️👩🏿","version":13.1,"tone":[1,5]},{"emoji":"👩🏼❤️👩🏻","version":13.1,"tone":[2,1]},{"emoji":"👩🏼❤️👩🏼","version":13.1,"tone":2},{"emoji":"👩🏼❤️👩🏽","version":13.1,"tone":[2,3]},{"emoji":"👩🏼❤️👩🏾","version":13.1,"tone":[2,4]},{"emoji":"👩🏼❤️👩🏿","version":13.1,"tone":[2,5]},{"emoji":"👩🏽❤️👩🏻","version":13.1,"tone":[3,1]},{"emoji":"👩🏽❤️👩🏼","version":13.1,"tone":[3,2]},{"emoji":"👩🏽❤️👩🏽","version":13.1,"tone":3},{"emoji":"👩🏽❤️👩🏾","version":13.1,"tone":[3,4]},{"emoji":"👩🏽❤️👩🏿","version":13.1,"tone":[3,5]},{"emoji":"👩🏾❤️👩🏻","version":13.1,"tone":[4,1]},{"emoji":"👩🏾❤️👩🏼","version":13.1,"tone":[4,2]},{"emoji":"👩🏾❤️👩🏽","version":13.1,"tone":[4,3]},{"emoji":"👩🏾❤️👩🏾","version":13.1,"tone":4},{"emoji":"👩🏾❤️👩🏿","version":13.1,"tone":[4,5]},{"emoji":"👩🏿❤️👩🏻","version":13.1,"tone":[5,1]},{"emoji":"👩🏿❤️👩🏼","version":13.1,"tone":[5,2]},{"emoji":"👩🏿❤️👩🏽","version":13.1,"tone":[5,3]},{"emoji":"👩🏿❤️👩🏾","version":13.1,"tone":[5,4]},{"emoji":"👩🏿❤️👩🏿","version":13.1,"tone":5}]},{"shortcodes":["family_mwb"],"annotation":"family: man, woman, boy","tags":["boy","family","man","woman"],"emoji":"👨👩👦","order":3439,"group":1,"version":2},{"shortcodes":["family_mwg"],"annotation":"family: man, woman, girl","tags":["family","girl","man","woman"],"emoji":"👨👩👧","order":3440,"group":1,"version":2},{"shortcodes":["family_mwgb"],"annotation":"family: man, woman, girl, boy","tags":["boy","family","girl","man","woman"],"emoji":"👨👩👧👦","order":3441,"group":1,"version":2},{"shortcodes":["family_mwbb"],"annotation":"family: man, woman, boy, boy","tags":["boy","family","man","woman"],"emoji":"👨👩👦👦","order":3442,"group":1,"version":2},{"shortcodes":["family_mwgg"],"annotation":"family: man, woman, girl, girl","tags":["family","girl","man","woman"],"emoji":"👨👩👧👧","order":3443,"group":1,"version":2},{"shortcodes":["family_mmb"],"annotation":"family: man, man, boy","tags":["boy","family","man"],"emoji":"👨👨👦","order":3444,"group":1,"version":2},{"shortcodes":["family_mmg"],"annotation":"family: man, man, girl","tags":["family","girl","man"],"emoji":"👨👨👧","order":3445,"group":1,"version":2},{"shortcodes":["family_mmgb"],"annotation":"family: man, man, girl, boy","tags":["boy","family","girl","man"],"emoji":"👨👨👧👦","order":3446,"group":1,"version":2},{"shortcodes":["family_mmbb"],"annotation":"family: man, man, boy, boy","tags":["boy","family","man"],"emoji":"👨👨👦👦","order":3447,"group":1,"version":2},{"shortcodes":["family_mmgg"],"annotation":"family: man, man, girl, girl","tags":["family","girl","man"],"emoji":"👨👨👧👧","order":3448,"group":1,"version":2},{"shortcodes":["family_wwb"],"annotation":"family: woman, woman, boy","tags":["boy","family","woman"],"emoji":"👩👩👦","order":3449,"group":1,"version":2},{"shortcodes":["family_wwg"],"annotation":"family: woman, woman, girl","tags":["family","girl","woman"],"emoji":"👩👩👧","order":3450,"group":1,"version":2},{"shortcodes":["family_wwgb"],"annotation":"family: woman, woman, girl, boy","tags":["boy","family","girl","woman"],"emoji":"👩👩👧👦","order":3451,"group":1,"version":2},{"shortcodes":["family_wwbb"],"annotation":"family: woman, woman, boy, boy","tags":["boy","family","woman"],"emoji":"👩👩👦👦","order":3452,"group":1,"version":2},{"shortcodes":["family_wwgg"],"annotation":"family: woman, woman, girl, girl","tags":["family","girl","woman"],"emoji":"👩👩👧👧","order":3453,"group":1,"version":2},{"shortcodes":["family_mb"],"annotation":"family: man, boy","tags":["boy","family","man"],"emoji":"👨👦","order":3454,"group":1,"version":4},{"shortcodes":["family_mbb"],"annotation":"family: man, boy, boy","tags":["boy","family","man"],"emoji":"👨👦👦","order":3455,"group":1,"version":4},{"shortcodes":["family_mg"],"annotation":"family: man, girl","tags":["family","girl","man"],"emoji":"👨👧","order":3456,"group":1,"version":4},{"shortcodes":["family_mgb"],"annotation":"family: man, girl, boy","tags":["boy","family","girl","man"],"emoji":"👨👧👦","order":3457,"group":1,"version":4},{"shortcodes":["family_mgg"],"annotation":"family: man, girl, girl","tags":["family","girl","man"],"emoji":"👨👧👧","order":3458,"group":1,"version":4},{"shortcodes":["family_wb"],"annotation":"family: woman, boy","tags":["boy","family","woman"],"emoji":"👩👦","order":3459,"group":1,"version":4},{"shortcodes":["family_wbb"],"annotation":"family: woman, boy, boy","tags":["boy","family","woman"],"emoji":"👩👦👦","order":3460,"group":1,"version":4},{"shortcodes":["family_wg"],"annotation":"family: woman, girl","tags":["family","girl","woman"],"emoji":"👩👧","order":3461,"group":1,"version":4},{"shortcodes":["family_wgb"],"annotation":"family: woman, girl, boy","tags":["boy","family","girl","woman"],"emoji":"👩👧👦","order":3462,"group":1,"version":4},{"shortcodes":["family_wgg"],"annotation":"family: woman, girl, girl","tags":["family","girl","woman"],"emoji":"👩👧👧","order":3463,"group":1,"version":4},{"shortcodes":["speaking_head"],"annotation":"speaking head","tags":["face","head","silhouette","speak","speaking"],"emoji":"🗣️","order":3465,"group":1,"version":0.7},{"shortcodes":["bust_in_silhouette"],"annotation":"bust in silhouette","tags":["bust","silhouette"],"emoji":"👤","order":3466,"group":1,"version":0.6},{"shortcodes":["busts_in_silhouette"],"annotation":"busts in silhouette","tags":["bust","silhouette"],"emoji":"👥","order":3467,"group":1,"version":1},{"shortcodes":["people_hugging"],"annotation":"people hugging","tags":["goodbye","hello","hug","thanks"],"emoji":"🫂","order":3468,"group":1,"version":13},{"shortcodes":["family"],"annotation":"family","tags":["family"],"emoji":"👪️","order":3469,"group":1,"version":0.6},{"shortcodes":["family_aac"],"annotation":"family: adult, adult, child","tags":["family: adult, adult, child"],"emoji":"🧑🧑🧒","order":3470,"group":1,"version":15.1},{"shortcodes":["family_aacc"],"annotation":"family: adult, adult, child, child","tags":["family: adult, adult, child, child"],"emoji":"🧑🧑🧒🧒","order":3471,"group":1,"version":15.1},{"shortcodes":["family_ac"],"annotation":"family: adult, child","tags":["family: adult, child"],"emoji":"🧑🧒","order":3472,"group":1,"version":15.1},{"shortcodes":["family_acc"],"annotation":"family: adult, child, child","tags":["family: adult, child, child"],"emoji":"🧑🧒🧒","order":3473,"group":1,"version":15.1},{"shortcodes":["footprints"],"annotation":"footprints","tags":["clothing","footprint","print"],"emoji":"👣","order":3474,"group":1,"version":0.6},{"shortcodes":["tone1","tone_light"],"annotation":"light skin tone","tags":["skin tone","type 1–2"],"emoji":"🏻","order":3475,"group":2,"version":1},{"shortcodes":["tone2","tone_medium_light"],"annotation":"medium-light skin tone","tags":["skin tone","type 3"],"emoji":"🏼","order":3476,"group":2,"version":1},{"shortcodes":["tone3","tone_medium"],"annotation":"medium skin tone","tags":["skin tone","type 4"],"emoji":"🏽","order":3477,"group":2,"version":1},{"shortcodes":["tone4","tone_medium_dark"],"annotation":"medium-dark skin tone","tags":["skin tone","type 5"],"emoji":"🏾","order":3478,"group":2,"version":1},{"shortcodes":["tone5","tone_dark"],"annotation":"dark skin tone","tags":["skin tone","type 6"],"emoji":"🏿","order":3479,"group":2,"version":1},{"shortcodes":["red_hair"],"annotation":"red hair","tags":["ginger","redhead"],"emoji":"🦰","order":3480,"group":2,"version":11},{"shortcodes":["curly_hair"],"annotation":"curly hair","tags":["afro","curly","ringlets"],"emoji":"🦱","order":3481,"group":2,"version":11},{"shortcodes":["white_hair"],"annotation":"white hair","tags":["gray","hair","old","white"],"emoji":"🦳","order":3482,"group":2,"version":11},{"shortcodes":["no_hair"],"annotation":"bald","tags":["chemotherapy","hairless","no hair","shaven"],"emoji":"🦲","order":3483,"group":2,"version":11},{"shortcodes":["monkey_face"],"annotation":"monkey face","tags":["face","monkey"],"emoji":"🐵","order":3484,"group":3,"version":0.6},{"shortcodes":["monkey"],"annotation":"monkey","tags":["monkey"],"emoji":"🐒","order":3485,"group":3,"version":0.6},{"shortcodes":["gorilla"],"annotation":"gorilla","tags":["gorilla"],"emoji":"🦍","order":3486,"group":3,"version":3},{"shortcodes":["orangutan"],"annotation":"orangutan","tags":["ape"],"emoji":"🦧","order":3487,"group":3,"version":12},{"shortcodes":["dog_face"],"annotation":"dog face","tags":["dog","face","pet"],"emoji":"🐶","order":3488,"group":3,"version":0.6},{"shortcodes":["dog"],"annotation":"dog","tags":["pet"],"emoji":"🐕️","order":3489,"group":3,"version":0.7},{"shortcodes":["guide_dog"],"annotation":"guide dog","tags":["accessibility","blind","guide"],"emoji":"🦮","order":3490,"group":3,"version":12},{"shortcodes":["service_dog"],"annotation":"service dog","tags":["accessibility","assistance","dog","service"],"emoji":"🐕🦺","order":3491,"group":3,"version":12},{"shortcodes":["poodle"],"annotation":"poodle","tags":["dog"],"emoji":"🐩","order":3492,"group":3,"version":0.6},{"shortcodes":["wolf","wolf_face"],"annotation":"wolf","tags":["face"],"emoji":"🐺","order":3493,"group":3,"version":0.6},{"shortcodes":["fox","fox_face"],"annotation":"fox","tags":["face"],"emoji":"🦊","order":3494,"group":3,"version":3},{"shortcodes":["raccoon"],"annotation":"raccoon","tags":["curious","sly"],"emoji":"🦝","order":3495,"group":3,"version":11},{"shortcodes":["cat_face"],"annotation":"cat face","tags":["cat","face","pet"],"emoji":"🐱","order":3496,"group":3,"version":0.6},{"shortcodes":["cat"],"annotation":"cat","tags":["pet"],"emoji":"🐈️","order":3497,"group":3,"version":0.7},{"shortcodes":["black_cat"],"annotation":"black cat","tags":["black","cat","unlucky"],"emoji":"🐈⬛","order":3498,"group":3,"version":13},{"shortcodes":["lion","lion_face"],"annotation":"lion","tags":["face","leo","zodiac"],"emoji":"🦁","order":3499,"group":3,"version":1},{"shortcodes":["tiger_face"],"annotation":"tiger face","tags":["face","tiger"],"emoji":"🐯","order":3500,"group":3,"version":0.6},{"shortcodes":["tiger"],"annotation":"tiger","tags":["tiger"],"emoji":"🐅","order":3501,"group":3,"version":1},{"shortcodes":["leopard"],"annotation":"leopard","tags":["leopard"],"emoji":"🐆","order":3502,"group":3,"version":1},{"shortcodes":["horse_face"],"annotation":"horse face","tags":["face","horse"],"emoji":"🐴","order":3503,"group":3,"version":0.6},{"shortcodes":["moose"],"annotation":"moose","tags":["animal","antlers","elk","mammal"],"emoji":"🫎","order":3504,"group":3,"version":15},{"shortcodes":["donkey"],"annotation":"donkey","tags":["animal","ass","burro","mammal","mule","stubborn"],"emoji":"🫏","order":3505,"group":3,"version":15},{"shortcodes":["horse","racehorse"],"annotation":"horse","tags":["equestrian","racehorse","racing"],"emoji":"🐎","order":3506,"group":3,"version":0.6},{"shortcodes":["unicorn","unicorn_face"],"annotation":"unicorn","tags":["face"],"emoji":"🦄","order":3507,"group":3,"version":1},{"shortcodes":["zebra"],"annotation":"zebra","tags":["stripe"],"emoji":"🦓","order":3508,"group":3,"version":5},{"shortcodes":["deer"],"annotation":"deer","tags":["deer"],"emoji":"🦌","order":3509,"group":3,"version":3},{"shortcodes":["bison"],"annotation":"bison","tags":["buffalo","herd","wisent"],"emoji":"🦬","order":3510,"group":3,"version":13},{"shortcodes":["cow_face"],"annotation":"cow face","tags":["cow","face"],"emoji":"🐮","order":3511,"group":3,"version":0.6},{"shortcodes":["ox"],"annotation":"ox","tags":["bull","taurus","zodiac"],"emoji":"🐂","order":3512,"group":3,"version":1},{"shortcodes":["water_buffalo"],"annotation":"water buffalo","tags":["buffalo","water"],"emoji":"🐃","order":3513,"group":3,"version":1},{"shortcodes":["cow"],"annotation":"cow","tags":["cow"],"emoji":"🐄","order":3514,"group":3,"version":1},{"shortcodes":["pig_face"],"annotation":"pig face","tags":["face","pig"],"emoji":"🐷","order":3515,"group":3,"version":0.6},{"shortcodes":["pig"],"annotation":"pig","tags":["sow"],"emoji":"🐖","order":3516,"group":3,"version":1},{"shortcodes":["boar"],"annotation":"boar","tags":["pig"],"emoji":"🐗","order":3517,"group":3,"version":0.6},{"shortcodes":["pig_nose"],"annotation":"pig nose","tags":["face","nose","pig"],"emoji":"🐽","order":3518,"group":3,"version":0.6},{"shortcodes":["ram"],"annotation":"ram","tags":["aries","male","sheep","zodiac"],"emoji":"🐏","order":3519,"group":3,"version":1},{"shortcodes":["ewe","sheep"],"annotation":"ewe","tags":["female","sheep"],"emoji":"🐑","order":3520,"group":3,"version":0.6},{"shortcodes":["goat"],"annotation":"goat","tags":["capricorn","zodiac"],"emoji":"🐐","order":3521,"group":3,"version":1},{"shortcodes":["dromedary_camel"],"annotation":"camel","tags":["dromedary","hump"],"emoji":"🐪","order":3522,"group":3,"version":1},{"shortcodes":["camel"],"annotation":"two-hump camel","tags":["bactrian","camel","hump"],"emoji":"🐫","order":3523,"group":3,"version":0.6},{"shortcodes":["llama"],"annotation":"llama","tags":["alpaca","guanaco","vicuña","wool"],"emoji":"🦙","order":3524,"group":3,"version":11},{"shortcodes":["giraffe"],"annotation":"giraffe","tags":["spots"],"emoji":"🦒","order":3525,"group":3,"version":5},{"shortcodes":["elephant"],"annotation":"elephant","tags":["elephant"],"emoji":"🐘","order":3526,"group":3,"version":0.6},{"shortcodes":["mammoth"],"annotation":"mammoth","tags":["extinction","large","tusk","woolly"],"emoji":"🦣","order":3527,"group":3,"version":13},{"shortcodes":["rhino","rhinoceros"],"annotation":"rhinoceros","tags":["rhinoceros"],"emoji":"🦏","order":3528,"group":3,"version":3},{"shortcodes":["hippo"],"annotation":"hippopotamus","tags":["hippo"],"emoji":"🦛","order":3529,"group":3,"version":11},{"shortcodes":["mouse_face"],"annotation":"mouse face","tags":["face","mouse"],"emoji":"🐭","order":3530,"group":3,"version":0.6},{"shortcodes":["mouse"],"annotation":"mouse","tags":["mouse"],"emoji":"🐁","order":3531,"group":3,"version":1},{"shortcodes":["rat"],"annotation":"rat","tags":["rat"],"emoji":"🐀","order":3532,"group":3,"version":1},{"shortcodes":["hamster","hamster_face"],"annotation":"hamster","tags":["face","pet"],"emoji":"🐹","order":3533,"group":3,"version":0.6},{"shortcodes":["rabbit_face"],"annotation":"rabbit face","tags":["bunny","face","pet","rabbit"],"emoji":"🐰","order":3534,"group":3,"version":0.6},{"shortcodes":["rabbit"],"annotation":"rabbit","tags":["bunny","pet"],"emoji":"🐇","order":3535,"group":3,"version":1},{"shortcodes":["chipmunk"],"annotation":"chipmunk","tags":["squirrel"],"emoji":"🐿️","order":3537,"group":3,"version":0.7},{"shortcodes":["beaver"],"annotation":"beaver","tags":["dam"],"emoji":"🦫","order":3538,"group":3,"version":13},{"shortcodes":["hedgehog"],"annotation":"hedgehog","tags":["spiny"],"emoji":"🦔","order":3539,"group":3,"version":5},{"shortcodes":["bat"],"annotation":"bat","tags":["vampire"],"emoji":"🦇","order":3540,"group":3,"version":3},{"shortcodes":["bear","bear_face"],"annotation":"bear","tags":["face"],"emoji":"🐻","order":3541,"group":3,"version":0.6},{"shortcodes":["polar_bear","polar_bear_face"],"annotation":"polar bear","tags":["arctic","bear","white"],"emoji":"🐻❄️","order":3542,"group":3,"version":13},{"shortcodes":["koala","koala_face"],"annotation":"koala","tags":["face","marsupial"],"emoji":"🐨","order":3544,"group":3,"version":0.6},{"shortcodes":["panda","panda_face"],"annotation":"panda","tags":["face"],"emoji":"🐼","order":3545,"group":3,"version":0.6},{"shortcodes":["sloth"],"annotation":"sloth","tags":["lazy","slow"],"emoji":"🦥","order":3546,"group":3,"version":12},{"shortcodes":["otter"],"annotation":"otter","tags":["fishing","playful"],"emoji":"🦦","order":3547,"group":3,"version":12},{"shortcodes":["skunk"],"annotation":"skunk","tags":["stink"],"emoji":"🦨","order":3548,"group":3,"version":12},{"shortcodes":["kangaroo"],"annotation":"kangaroo","tags":["joey","jump","marsupial"],"emoji":"🦘","order":3549,"group":3,"version":11},{"shortcodes":["badger"],"annotation":"badger","tags":["honey badger","pester"],"emoji":"🦡","order":3550,"group":3,"version":11},{"shortcodes":["paw_prints"],"annotation":"paw prints","tags":["feet","paw","print"],"emoji":"🐾","order":3551,"group":3,"version":0.6},{"shortcodes":["turkey"],"annotation":"turkey","tags":["bird"],"emoji":"🦃","order":3552,"group":3,"version":1},{"shortcodes":["chicken","chicken_face"],"annotation":"chicken","tags":["bird"],"emoji":"🐔","order":3553,"group":3,"version":0.6},{"shortcodes":["rooster"],"annotation":"rooster","tags":["bird"],"emoji":"🐓","order":3554,"group":3,"version":1},{"shortcodes":["hatching_chick"],"annotation":"hatching chick","tags":["baby","bird","chick","hatching"],"emoji":"🐣","order":3555,"group":3,"version":0.6},{"shortcodes":["baby_chick"],"annotation":"baby chick","tags":["baby","bird","chick"],"emoji":"🐤","order":3556,"group":3,"version":0.6},{"shortcodes":["hatched_chick"],"annotation":"front-facing baby chick","tags":["baby","bird","chick"],"emoji":"🐥","order":3557,"group":3,"version":0.6},{"shortcodes":["bird","bird_face"],"annotation":"bird","tags":["bird"],"emoji":"🐦️","order":3558,"group":3,"version":0.6},{"shortcodes":["penguin","penguin_face"],"annotation":"penguin","tags":["bird"],"emoji":"🐧","order":3559,"group":3,"version":0.6},{"shortcodes":["dove"],"annotation":"dove","tags":["bird","fly","peace"],"emoji":"🕊️","order":3561,"group":3,"version":0.7},{"shortcodes":["eagle"],"annotation":"eagle","tags":["bird"],"emoji":"🦅","order":3562,"group":3,"version":3},{"shortcodes":["duck"],"annotation":"duck","tags":["bird"],"emoji":"🦆","order":3563,"group":3,"version":3},{"shortcodes":["swan"],"annotation":"swan","tags":["bird","cygnet","ugly duckling"],"emoji":"🦢","order":3564,"group":3,"version":11},{"shortcodes":["owl"],"annotation":"owl","tags":["bird","wise"],"emoji":"🦉","order":3565,"group":3,"version":3},{"shortcodes":["dodo"],"annotation":"dodo","tags":["extinction","large","mauritius"],"emoji":"🦤","order":3566,"group":3,"version":13},{"shortcodes":["feather"],"annotation":"feather","tags":["bird","flight","light","plumage"],"emoji":"🪶","order":3567,"group":3,"version":13},{"shortcodes":["flamingo"],"annotation":"flamingo","tags":["flamboyant","tropical"],"emoji":"🦩","order":3568,"group":3,"version":12},{"shortcodes":["peacock"],"annotation":"peacock","tags":["bird","ostentatious","peahen","proud"],"emoji":"🦚","order":3569,"group":3,"version":11},{"shortcodes":["parrot"],"annotation":"parrot","tags":["bird","pirate","talk"],"emoji":"🦜","order":3570,"group":3,"version":11},{"shortcodes":["wing"],"annotation":"wing","tags":["angelic","aviation","bird","flying","mythology"],"emoji":"🪽","order":3571,"group":3,"version":15},{"shortcodes":["black_bird"],"annotation":"black bird","tags":["bird","black","crow","raven","rook"],"emoji":"🐦⬛","order":3572,"group":3,"version":15},{"shortcodes":["goose"],"annotation":"goose","tags":["bird","fowl","honk","silly"],"emoji":"🪿","order":3573,"group":3,"version":15},{"shortcodes":["phoenix"],"annotation":"phoenix","tags":["fantasy","firebird","rebirth","reincarnation"],"emoji":"🐦🔥","order":3574,"group":3,"version":15.1},{"shortcodes":["frog","frog_face"],"annotation":"frog","tags":["face"],"emoji":"🐸","order":3575,"group":3,"version":0.6},{"shortcodes":["crocodile"],"annotation":"crocodile","tags":["crocodile"],"emoji":"🐊","order":3576,"group":3,"version":1},{"shortcodes":["turtle"],"annotation":"turtle","tags":["terrapin","tortoise"],"emoji":"🐢","order":3577,"group":3,"version":0.6},{"shortcodes":["lizard"],"annotation":"lizard","tags":["reptile"],"emoji":"🦎","order":3578,"group":3,"version":3},{"shortcodes":["snake"],"annotation":"snake","tags":["bearer","ophiuchus","serpent","zodiac"],"emoji":"🐍","order":3579,"group":3,"version":0.6},{"shortcodes":["dragon_face"],"annotation":"dragon face","tags":["dragon","face","fairy tale"],"emoji":"🐲","order":3580,"group":3,"version":0.6},{"shortcodes":["dragon"],"annotation":"dragon","tags":["fairy tale"],"emoji":"🐉","order":3581,"group":3,"version":1},{"shortcodes":["sauropod"],"annotation":"sauropod","tags":["brachiosaurus","brontosaurus","diplodocus"],"emoji":"🦕","order":3582,"group":3,"version":5},{"shortcodes":["t-rex","trex"],"annotation":"T-Rex","tags":["t-rex","tyrannosaurus rex"],"emoji":"🦖","order":3583,"group":3,"version":5},{"shortcodes":["spouting_whale"],"annotation":"spouting whale","tags":["face","spouting","whale"],"emoji":"🐳","order":3584,"group":3,"version":0.6},{"shortcodes":["whale"],"annotation":"whale","tags":["whale"],"emoji":"🐋","order":3585,"group":3,"version":1},{"shortcodes":["dolphin"],"annotation":"dolphin","tags":["flipper"],"emoji":"🐬","order":3586,"group":3,"version":0.6},{"shortcodes":["seal"],"annotation":"seal","tags":["sea lion"],"emoji":"🦭","order":3587,"group":3,"version":13},{"shortcodes":["fish"],"annotation":"fish","tags":["pisces","zodiac"],"emoji":"🐟️","order":3588,"group":3,"version":0.6},{"shortcodes":["tropical_fish"],"annotation":"tropical fish","tags":["fish","tropical"],"emoji":"🐠","order":3589,"group":3,"version":0.6},{"shortcodes":["blowfish"],"annotation":"blowfish","tags":["fish"],"emoji":"🐡","order":3590,"group":3,"version":0.6},{"shortcodes":["shark"],"annotation":"shark","tags":["fish"],"emoji":"🦈","order":3591,"group":3,"version":3},{"shortcodes":["octopus"],"annotation":"octopus","tags":["octopus"],"emoji":"🐙","order":3592,"group":3,"version":0.6},{"shortcodes":["shell"],"annotation":"spiral shell","tags":["shell","spiral"],"emoji":"🐚","order":3593,"group":3,"version":0.6},{"shortcodes":["coral"],"annotation":"coral","tags":["ocean","reef"],"emoji":"🪸","order":3594,"group":3,"version":14},{"shortcodes":["jellyfish"],"annotation":"jellyfish","tags":["burn","invertebrate","jelly","marine","ouch","stinger"],"emoji":"🪼","order":3595,"group":3,"version":15},{"shortcodes":["snail"],"annotation":"snail","tags":["snail"],"emoji":"🐌","order":3596,"group":3,"version":0.6},{"shortcodes":["butterfly"],"annotation":"butterfly","tags":["insect","pretty"],"emoji":"🦋","order":3597,"group":3,"version":3},{"shortcodes":["bug"],"annotation":"bug","tags":["insect"],"emoji":"🐛","order":3598,"group":3,"version":0.6},{"shortcodes":["ant"],"annotation":"ant","tags":["insect"],"emoji":"🐜","order":3599,"group":3,"version":0.6},{"shortcodes":["bee"],"annotation":"honeybee","tags":["bee","insect"],"emoji":"🐝","order":3600,"group":3,"version":0.6},{"shortcodes":["beetle"],"annotation":"beetle","tags":["bug","insect"],"emoji":"🪲","order":3601,"group":3,"version":13},{"shortcodes":["lady_beetle"],"annotation":"lady beetle","tags":["beetle","insect","ladybird","ladybug"],"emoji":"🐞","order":3602,"group":3,"version":0.6},{"shortcodes":["cricket"],"annotation":"cricket","tags":["grasshopper"],"emoji":"🦗","order":3603,"group":3,"version":5},{"shortcodes":["cockroach"],"annotation":"cockroach","tags":["insect","pest","roach"],"emoji":"🪳","order":3604,"group":3,"version":13},{"shortcodes":["spider"],"annotation":"spider","tags":["insect"],"emoji":"🕷️","order":3606,"group":3,"version":0.7},{"shortcodes":["spider_web"],"annotation":"spider web","tags":["spider","web"],"emoji":"🕸️","order":3608,"group":3,"version":0.7},{"shortcodes":["scorpion"],"annotation":"scorpion","tags":["scorpio","zodiac"],"emoji":"🦂","order":3609,"group":3,"version":1},{"shortcodes":["mosquito"],"annotation":"mosquito","tags":["disease","fever","malaria","pest","virus"],"emoji":"🦟","order":3610,"group":3,"version":11},{"shortcodes":["fly"],"annotation":"fly","tags":["disease","maggot","pest","rotting"],"emoji":"🪰","order":3611,"group":3,"version":13},{"shortcodes":["worm"],"annotation":"worm","tags":["annelid","earthworm","parasite"],"emoji":"🪱","order":3612,"group":3,"version":13},{"shortcodes":["microbe"],"annotation":"microbe","tags":["amoeba","bacteria","virus"],"emoji":"🦠","order":3613,"group":3,"version":11},{"shortcodes":["bouquet"],"annotation":"bouquet","tags":["flower"],"emoji":"💐","order":3614,"group":3,"version":0.6},{"shortcodes":["cherry_blossom"],"annotation":"cherry blossom","tags":["blossom","cherry","flower"],"emoji":"🌸","order":3615,"group":3,"version":0.6},{"shortcodes":["white_flower"],"annotation":"white flower","tags":["flower"],"emoji":"💮","order":3616,"group":3,"version":0.6},{"shortcodes":["lotus"],"annotation":"lotus","tags":["buddhism","flower","hinduism","purity"],"emoji":"🪷","order":3617,"group":3,"version":14},{"shortcodes":["rosette"],"annotation":"rosette","tags":["plant"],"emoji":"🏵️","order":3619,"group":3,"version":0.7},{"shortcodes":["rose"],"annotation":"rose","tags":["flower"],"emoji":"🌹","order":3620,"group":3,"version":0.6},{"shortcodes":["wilted_flower"],"annotation":"wilted flower","tags":["flower","wilted"],"emoji":"🥀","order":3621,"group":3,"version":3},{"shortcodes":["hibiscus"],"annotation":"hibiscus","tags":["flower"],"emoji":"🌺","order":3622,"group":3,"version":0.6},{"shortcodes":["sunflower"],"annotation":"sunflower","tags":["flower","sun"],"emoji":"🌻","order":3623,"group":3,"version":0.6},{"shortcodes":["blossom"],"annotation":"blossom","tags":["flower"],"emoji":"🌼","order":3624,"group":3,"version":0.6},{"shortcodes":["tulip"],"annotation":"tulip","tags":["flower"],"emoji":"🌷","order":3625,"group":3,"version":0.6},{"shortcodes":["hyacinth"],"annotation":"hyacinth","tags":["bluebonnet","flower","lavender","lupine","snapdragon"],"emoji":"🪻","order":3626,"group":3,"version":15},{"shortcodes":["seedling"],"annotation":"seedling","tags":["young"],"emoji":"🌱","order":3627,"group":3,"version":0.6},{"shortcodes":["potted_plant"],"annotation":"potted plant","tags":["boring","grow","house","nurturing","plant","useless"],"emoji":"🪴","order":3628,"group":3,"version":13},{"shortcodes":["evergreen_tree"],"annotation":"evergreen tree","tags":["tree"],"emoji":"🌲","order":3629,"group":3,"version":1},{"shortcodes":["deciduous_tree"],"annotation":"deciduous tree","tags":["deciduous","shedding","tree"],"emoji":"🌳","order":3630,"group":3,"version":1},{"shortcodes":["palm_tree"],"annotation":"palm tree","tags":["palm","tree"],"emoji":"🌴","order":3631,"group":3,"version":0.6},{"shortcodes":["cactus"],"annotation":"cactus","tags":["plant"],"emoji":"🌵","order":3632,"group":3,"version":0.6},{"shortcodes":["ear_of_rice","sheaf_of_rice"],"annotation":"sheaf of rice","tags":["ear","grain","rice"],"emoji":"🌾","order":3633,"group":3,"version":0.6},{"shortcodes":["herb"],"annotation":"herb","tags":["leaf"],"emoji":"🌿","order":3634,"group":3,"version":0.6},{"shortcodes":["shamrock"],"annotation":"shamrock","tags":["plant"],"emoji":"☘️","order":3636,"group":3,"version":1},{"shortcodes":["four_leaf_clover"],"annotation":"four leaf clover","tags":["4","clover","four","four-leaf clover","leaf"],"emoji":"🍀","order":3637,"group":3,"version":0.6},{"shortcodes":["maple_leaf"],"annotation":"maple leaf","tags":["falling","leaf","maple"],"emoji":"🍁","order":3638,"group":3,"version":0.6},{"shortcodes":["fallen_leaf"],"annotation":"fallen leaf","tags":["falling","leaf"],"emoji":"🍂","order":3639,"group":3,"version":0.6},{"shortcodes":["leaves"],"annotation":"leaf fluttering in wind","tags":["blow","flutter","leaf","wind"],"emoji":"🍃","order":3640,"group":3,"version":0.6},{"shortcodes":["empty_nest","nest"],"annotation":"empty nest","tags":["nesting"],"emoji":"🪹","order":3641,"group":3,"version":14},{"shortcodes":["nest_with_eggs"],"annotation":"nest with eggs","tags":["nesting"],"emoji":"🪺","order":3642,"group":3,"version":14},{"shortcodes":["mushroom"],"annotation":"mushroom","tags":["toadstool"],"emoji":"🍄","order":3643,"group":3,"version":0.6},{"shortcodes":["grapes"],"annotation":"grapes","tags":["fruit","grape"],"emoji":"🍇","order":3644,"group":4,"version":0.6},{"shortcodes":["melon"],"annotation":"melon","tags":["fruit"],"emoji":"🍈","order":3645,"group":4,"version":0.6},{"shortcodes":["watermelon"],"annotation":"watermelon","tags":["fruit"],"emoji":"🍉","order":3646,"group":4,"version":0.6},{"shortcodes":["orange","tangerine"],"annotation":"tangerine","tags":["fruit","orange"],"emoji":"🍊","order":3647,"group":4,"version":0.6},{"shortcodes":["lemon"],"annotation":"lemon","tags":["citrus","fruit"],"emoji":"🍋","order":3648,"group":4,"version":1},{"shortcodes":["lime"],"annotation":"lime","tags":["citrus","fruit","tropical"],"emoji":"🍋🟩","order":3649,"group":4,"version":15.1},{"shortcodes":["banana"],"annotation":"banana","tags":["fruit"],"emoji":"🍌","order":3650,"group":4,"version":0.6},{"shortcodes":["pineapple"],"annotation":"pineapple","tags":["fruit"],"emoji":"🍍","order":3651,"group":4,"version":0.6},{"shortcodes":["mango"],"annotation":"mango","tags":["fruit","tropical"],"emoji":"🥭","order":3652,"group":4,"version":11},{"shortcodes":["apple","red_apple"],"annotation":"red apple","tags":["apple","fruit","red"],"emoji":"🍎","order":3653,"group":4,"version":0.6},{"shortcodes":["green_apple"],"annotation":"green apple","tags":["apple","fruit","green"],"emoji":"🍏","order":3654,"group":4,"version":0.6},{"shortcodes":["pear"],"annotation":"pear","tags":["fruit"],"emoji":"🍐","order":3655,"group":4,"version":1},{"shortcodes":["peach"],"annotation":"peach","tags":["fruit"],"emoji":"🍑","order":3656,"group":4,"version":0.6},{"shortcodes":["cherries"],"annotation":"cherries","tags":["berries","cherry","fruit","red"],"emoji":"🍒","order":3657,"group":4,"version":0.6},{"shortcodes":["strawberry"],"annotation":"strawberry","tags":["berry","fruit"],"emoji":"🍓","order":3658,"group":4,"version":0.6},{"shortcodes":["blueberries"],"annotation":"blueberries","tags":["berry","bilberry","blue","blueberry"],"emoji":"🫐","order":3659,"group":4,"version":13},{"shortcodes":["kiwi"],"annotation":"kiwi fruit","tags":["food","fruit","kiwi"],"emoji":"🥝","order":3660,"group":4,"version":3},{"shortcodes":["tomato"],"annotation":"tomato","tags":["fruit","vegetable"],"emoji":"🍅","order":3661,"group":4,"version":0.6},{"shortcodes":["olive"],"annotation":"olive","tags":["food"],"emoji":"🫒","order":3662,"group":4,"version":13},{"shortcodes":["coconut"],"annotation":"coconut","tags":["palm","piña colada"],"emoji":"🥥","order":3663,"group":4,"version":5},{"shortcodes":["avocado"],"annotation":"avocado","tags":["food","fruit"],"emoji":"🥑","order":3664,"group":4,"version":3},{"shortcodes":["eggplant"],"annotation":"eggplant","tags":["aubergine","vegetable"],"emoji":"🍆","order":3665,"group":4,"version":0.6},{"shortcodes":["potato"],"annotation":"potato","tags":["food","vegetable"],"emoji":"🥔","order":3666,"group":4,"version":3},{"shortcodes":["carrot"],"annotation":"carrot","tags":["food","vegetable"],"emoji":"🥕","order":3667,"group":4,"version":3},{"shortcodes":["corn","ear_of_corn"],"annotation":"ear of corn","tags":["corn","ear","maize","maze"],"emoji":"🌽","order":3668,"group":4,"version":0.6},{"shortcodes":["hot_pepper"],"annotation":"hot pepper","tags":["hot","pepper"],"emoji":"🌶️","order":3670,"group":4,"version":0.7},{"shortcodes":["bell_pepper"],"annotation":"bell pepper","tags":["capsicum","pepper","vegetable"],"emoji":"🫑","order":3671,"group":4,"version":13},{"shortcodes":["cucumber"],"annotation":"cucumber","tags":["food","pickle","vegetable"],"emoji":"🥒","order":3672,"group":4,"version":3},{"shortcodes":["leafy_green"],"annotation":"leafy green","tags":["bok choy","cabbage","kale","lettuce"],"emoji":"🥬","order":3673,"group":4,"version":11},{"shortcodes":["broccoli"],"annotation":"broccoli","tags":["wild cabbage"],"emoji":"🥦","order":3674,"group":4,"version":5},{"shortcodes":["garlic"],"annotation":"garlic","tags":["flavoring"],"emoji":"🧄","order":3675,"group":4,"version":12},{"shortcodes":["onion"],"annotation":"onion","tags":["flavoring"],"emoji":"🧅","order":3676,"group":4,"version":12},{"shortcodes":["peanuts"],"annotation":"peanuts","tags":["food","nut","peanut","vegetable"],"emoji":"🥜","order":3677,"group":4,"version":3},{"shortcodes":["beans"],"annotation":"beans","tags":["food","kidney","legume"],"emoji":"🫘","order":3678,"group":4,"version":14},{"shortcodes":["chestnut"],"annotation":"chestnut","tags":["plant"],"emoji":"🌰","order":3679,"group":4,"version":0.6},{"shortcodes":["ginger"],"annotation":"ginger root","tags":["beer","root","spice"],"emoji":"🫚","order":3680,"group":4,"version":15},{"shortcodes":["pea"],"annotation":"pea pod","tags":["beans","edamame","legume","pea","pod","vegetable"],"emoji":"🫛","order":3681,"group":4,"version":15},{"shortcodes":["brown_mushroom"],"annotation":"brown mushroom","tags":["food","fungus","nature","vegetable"],"emoji":"🍄🟫","order":3682,"group":4,"version":15.1},{"shortcodes":["bread"],"annotation":"bread","tags":["loaf"],"emoji":"🍞","order":3683,"group":4,"version":0.6},{"shortcodes":["croissant"],"annotation":"croissant","tags":["bread","breakfast","food","french","roll"],"emoji":"🥐","order":3684,"group":4,"version":3},{"shortcodes":["baguette_bread"],"annotation":"baguette bread","tags":["baguette","bread","food","french"],"emoji":"🥖","order":3685,"group":4,"version":3},{"shortcodes":["flatbread"],"annotation":"flatbread","tags":["arepa","lavash","naan","pita"],"emoji":"🫓","order":3686,"group":4,"version":13},{"shortcodes":["pretzel"],"annotation":"pretzel","tags":["twisted"],"emoji":"🥨","order":3687,"group":4,"version":5},{"shortcodes":["bagel"],"annotation":"bagel","tags":["bakery","breakfast","schmear"],"emoji":"🥯","order":3688,"group":4,"version":11},{"shortcodes":["pancakes"],"annotation":"pancakes","tags":["breakfast","crêpe","food","hotcake","pancake"],"emoji":"🥞","order":3689,"group":4,"version":3},{"shortcodes":["waffle"],"annotation":"waffle","tags":["breakfast","indecisive","iron"],"emoji":"🧇","order":3690,"group":4,"version":12},{"shortcodes":["cheese"],"annotation":"cheese wedge","tags":["cheese"],"emoji":"🧀","order":3691,"group":4,"version":1},{"shortcodes":["meat_on_bone"],"annotation":"meat on bone","tags":["bone","meat"],"emoji":"🍖","order":3692,"group":4,"version":0.6},{"shortcodes":["poultry_leg"],"annotation":"poultry leg","tags":["bone","chicken","drumstick","leg","poultry"],"emoji":"🍗","order":3693,"group":4,"version":0.6},{"shortcodes":["cut_of_meat"],"annotation":"cut of meat","tags":["chop","lambchop","porkchop","steak"],"emoji":"🥩","order":3694,"group":4,"version":5},{"shortcodes":["bacon"],"annotation":"bacon","tags":["breakfast","food","meat"],"emoji":"🥓","order":3695,"group":4,"version":3},{"shortcodes":["hamburger"],"annotation":"hamburger","tags":["burger"],"emoji":"🍔","order":3696,"group":4,"version":0.6},{"shortcodes":["french_fries","fries"],"annotation":"french fries","tags":["french","fries"],"emoji":"🍟","order":3697,"group":4,"version":0.6},{"shortcodes":["pizza"],"annotation":"pizza","tags":["cheese","slice"],"emoji":"🍕","order":3698,"group":4,"version":0.6},{"shortcodes":["hotdog"],"annotation":"hot dog","tags":["frankfurter","hotdog","sausage"],"emoji":"🌭","order":3699,"group":4,"version":1},{"shortcodes":["sandwich"],"annotation":"sandwich","tags":["bread"],"emoji":"🥪","order":3700,"group":4,"version":5},{"shortcodes":["taco"],"annotation":"taco","tags":["mexican"],"emoji":"🌮","order":3701,"group":4,"version":1},{"shortcodes":["burrito"],"annotation":"burrito","tags":["mexican","wrap"],"emoji":"🌯","order":3702,"group":4,"version":1},{"shortcodes":["tamale"],"annotation":"tamale","tags":["mexican","wrapped"],"emoji":"🫔","order":3703,"group":4,"version":13},{"shortcodes":["stuffed_flatbread"],"annotation":"stuffed flatbread","tags":["falafel","flatbread","food","gyro","kebab","stuffed"],"emoji":"🥙","order":3704,"group":4,"version":3},{"shortcodes":["falafel"],"annotation":"falafel","tags":["chickpea","meatball"],"emoji":"🧆","order":3705,"group":4,"version":12},{"shortcodes":["egg"],"annotation":"egg","tags":["breakfast","food"],"emoji":"🥚","order":3706,"group":4,"version":3},{"shortcodes":["cooking","fried_egg"],"annotation":"cooking","tags":["breakfast","egg","frying","pan"],"emoji":"🍳","order":3707,"group":4,"version":0.6},{"shortcodes":["shallow_pan_of_food"],"annotation":"shallow pan of food","tags":["casserole","food","paella","pan","shallow"],"emoji":"🥘","order":3708,"group":4,"version":3},{"shortcodes":["pot_of_food","stew"],"annotation":"pot of food","tags":["pot","stew"],"emoji":"🍲","order":3709,"group":4,"version":0.6},{"shortcodes":["fondue"],"annotation":"fondue","tags":["cheese","chocolate","melted","pot","swiss"],"emoji":"🫕","order":3710,"group":4,"version":13},{"shortcodes":["bowl_with_spoon"],"annotation":"bowl with spoon","tags":["breakfast","cereal","congee"],"emoji":"🥣","order":3711,"group":4,"version":5},{"shortcodes":["green_salad","salad"],"annotation":"green salad","tags":["food","green","salad"],"emoji":"🥗","order":3712,"group":4,"version":3},{"shortcodes":["popcorn"],"annotation":"popcorn","tags":["popcorn"],"emoji":"🍿","order":3713,"group":4,"version":1},{"shortcodes":["butter"],"annotation":"butter","tags":["dairy"],"emoji":"🧈","order":3714,"group":4,"version":12},{"shortcodes":["salt"],"annotation":"salt","tags":["condiment","shaker"],"emoji":"🧂","order":3715,"group":4,"version":11},{"shortcodes":["canned_food"],"annotation":"canned food","tags":["can"],"emoji":"🥫","order":3716,"group":4,"version":5},{"shortcodes":["bento","bento_box"],"annotation":"bento box","tags":["bento","box"],"emoji":"🍱","order":3717,"group":4,"version":0.6},{"shortcodes":["rice_cracker"],"annotation":"rice cracker","tags":["cracker","rice"],"emoji":"🍘","order":3718,"group":4,"version":0.6},{"shortcodes":["rice_ball"],"annotation":"rice ball","tags":["ball","japanese","rice"],"emoji":"🍙","order":3719,"group":4,"version":0.6},{"shortcodes":["cooked_rice","rice"],"annotation":"cooked rice","tags":["cooked","rice"],"emoji":"🍚","order":3720,"group":4,"version":0.6},{"shortcodes":["curry","curry_rice"],"annotation":"curry rice","tags":["curry","rice"],"emoji":"🍛","order":3721,"group":4,"version":0.6},{"shortcodes":["ramen","steaming_bowl"],"annotation":"steaming bowl","tags":["bowl","noodle","ramen","steaming"],"emoji":"🍜","order":3722,"group":4,"version":0.6},{"shortcodes":["spaghetti"],"annotation":"spaghetti","tags":["pasta"],"emoji":"🍝","order":3723,"group":4,"version":0.6},{"shortcodes":["sweet_potato"],"annotation":"roasted sweet potato","tags":["potato","roasted","sweet"],"emoji":"🍠","order":3724,"group":4,"version":0.6},{"shortcodes":["oden"],"annotation":"oden","tags":["kebab","seafood","skewer","stick"],"emoji":"🍢","order":3725,"group":4,"version":0.6},{"shortcodes":["sushi"],"annotation":"sushi","tags":["sushi"],"emoji":"🍣","order":3726,"group":4,"version":0.6},{"shortcodes":["fried_shrimp"],"annotation":"fried shrimp","tags":["fried","prawn","shrimp","tempura"],"emoji":"🍤","order":3727,"group":4,"version":0.6},{"shortcodes":["fish_cake"],"annotation":"fish cake with swirl","tags":["cake","fish","pastry","swirl"],"emoji":"🍥","order":3728,"group":4,"version":0.6},{"shortcodes":["moon_cake"],"annotation":"moon cake","tags":["autumn","festival","yuèbǐng"],"emoji":"🥮","order":3729,"group":4,"version":11},{"shortcodes":["dango"],"annotation":"dango","tags":["dessert","japanese","skewer","stick","sweet"],"emoji":"🍡","order":3730,"group":4,"version":0.6},{"shortcodes":["dumpling"],"annotation":"dumpling","tags":["empanada","gyōza","jiaozi","pierogi","potsticker"],"emoji":"🥟","order":3731,"group":4,"version":5},{"shortcodes":["fortune_cookie"],"annotation":"fortune cookie","tags":["prophecy"],"emoji":"🥠","order":3732,"group":4,"version":5},{"shortcodes":["takeout_box"],"annotation":"takeout box","tags":["oyster pail"],"emoji":"🥡","order":3733,"group":4,"version":5},{"shortcodes":["crab"],"annotation":"crab","tags":["cancer","zodiac"],"emoji":"🦀","order":3734,"group":4,"version":1},{"shortcodes":["lobster"],"annotation":"lobster","tags":["bisque","claws","seafood"],"emoji":"🦞","order":3735,"group":4,"version":11},{"shortcodes":["shrimp"],"annotation":"shrimp","tags":["food","shellfish","small"],"emoji":"🦐","order":3736,"group":4,"version":3},{"shortcodes":["squid"],"annotation":"squid","tags":["food","molusc"],"emoji":"🦑","order":3737,"group":4,"version":3},{"shortcodes":["oyster"],"annotation":"oyster","tags":["diving","pearl"],"emoji":"🦪","order":3738,"group":4,"version":12},{"shortcodes":["icecream","soft_serve"],"annotation":"soft ice cream","tags":["cream","dessert","ice","icecream","soft","sweet"],"emoji":"🍦","order":3739,"group":4,"version":0.6},{"shortcodes":["shaved_ice"],"annotation":"shaved ice","tags":["dessert","ice","shaved","sweet"],"emoji":"🍧","order":3740,"group":4,"version":0.6},{"shortcodes":["ice_cream"],"annotation":"ice cream","tags":["cream","dessert","ice","sweet"],"emoji":"🍨","order":3741,"group":4,"version":0.6},{"shortcodes":["doughnut"],"annotation":"doughnut","tags":["breakfast","dessert","donut","sweet"],"emoji":"🍩","order":3742,"group":4,"version":0.6},{"shortcodes":["cookie"],"annotation":"cookie","tags":["dessert","sweet"],"emoji":"🍪","order":3743,"group":4,"version":0.6},{"shortcodes":["birthday","birthday_cake"],"annotation":"birthday cake","tags":["birthday","cake","celebration","dessert","pastry","sweet"],"emoji":"🎂","order":3744,"group":4,"version":0.6},{"shortcodes":["cake","shortcake"],"annotation":"shortcake","tags":["cake","dessert","pastry","slice","sweet"],"emoji":"🍰","order":3745,"group":4,"version":0.6},{"shortcodes":["cupcake"],"annotation":"cupcake","tags":["bakery","sweet"],"emoji":"🧁","order":3746,"group":4,"version":11},{"shortcodes":["pie"],"annotation":"pie","tags":["filling","pastry"],"emoji":"🥧","order":3747,"group":4,"version":5},{"shortcodes":["chocolate_bar"],"annotation":"chocolate bar","tags":["bar","chocolate","dessert","sweet"],"emoji":"🍫","order":3748,"group":4,"version":0.6},{"shortcodes":["candy"],"annotation":"candy","tags":["dessert","sweet"],"emoji":"🍬","order":3749,"group":4,"version":0.6},{"shortcodes":["lollipop"],"annotation":"lollipop","tags":["candy","dessert","sweet"],"emoji":"🍭","order":3750,"group":4,"version":0.6},{"shortcodes":["custard"],"annotation":"custard","tags":["dessert","pudding","sweet"],"emoji":"🍮","order":3751,"group":4,"version":0.6},{"shortcodes":["honey_pot"],"annotation":"honey pot","tags":["honey","honeypot","pot","sweet"],"emoji":"🍯","order":3752,"group":4,"version":0.6},{"shortcodes":["baby_bottle"],"annotation":"baby bottle","tags":["baby","bottle","drink","milk"],"emoji":"🍼","order":3753,"group":4,"version":1},{"shortcodes":["glass_of_milk","milk"],"annotation":"glass of milk","tags":["drink","glass","milk"],"emoji":"🥛","order":3754,"group":4,"version":3},{"shortcodes":["coffee"],"annotation":"hot beverage","tags":["beverage","coffee","drink","hot","steaming","tea"],"emoji":"☕️","order":3755,"group":4,"version":0.6},{"shortcodes":["teapot"],"annotation":"teapot","tags":["drink","pot","tea"],"emoji":"🫖","order":3756,"group":4,"version":13},{"shortcodes":["tea"],"annotation":"teacup without handle","tags":["beverage","cup","drink","tea","teacup"],"emoji":"🍵","order":3757,"group":4,"version":0.6},{"shortcodes":["sake"],"annotation":"sake","tags":["bar","beverage","bottle","cup","drink"],"emoji":"🍶","order":3758,"group":4,"version":0.6},{"shortcodes":["champagne"],"annotation":"bottle with popping cork","tags":["bar","bottle","cork","drink","popping"],"emoji":"🍾","order":3759,"group":4,"version":1},{"shortcodes":["wine_glass"],"annotation":"wine glass","tags":["bar","beverage","drink","glass","wine"],"emoji":"🍷","order":3760,"group":4,"version":0.6},{"shortcodes":["cocktail"],"annotation":"cocktail glass","tags":["bar","cocktail","drink","glass"],"emoji":"🍸️","order":3761,"group":4,"version":0.6},{"shortcodes":["tropical_drink"],"annotation":"tropical drink","tags":["bar","drink","tropical"],"emoji":"🍹","order":3762,"group":4,"version":0.6},{"shortcodes":["beer"],"annotation":"beer mug","tags":["bar","beer","drink","mug"],"emoji":"🍺","order":3763,"group":4,"version":0.6},{"shortcodes":["beers"],"annotation":"clinking beer mugs","tags":["bar","beer","clink","drink","mug"],"emoji":"🍻","order":3764,"group":4,"version":0.6},{"shortcodes":["clinking_glasses"],"annotation":"clinking glasses","tags":["celebrate","clink","drink","glass"],"emoji":"🥂","order":3765,"group":4,"version":3},{"shortcodes":["tumbler_glass","whisky"],"annotation":"tumbler glass","tags":["glass","liquor","shot","tumbler","whisky"],"emoji":"🥃","order":3766,"group":4,"version":3},{"shortcodes":["pour","pouring_liquid"],"annotation":"pouring liquid","tags":["drink","empty","glass","spill"],"emoji":"🫗","order":3767,"group":4,"version":14},{"shortcodes":["cup_with_straw"],"annotation":"cup with straw","tags":["juice","soda"],"emoji":"🥤","order":3768,"group":4,"version":5},{"shortcodes":["boba_drink","bubble_tea"],"annotation":"bubble tea","tags":["bubble","milk","pearl","tea"],"emoji":"🧋","order":3769,"group":4,"version":13},{"shortcodes":["beverage_box","juice_box"],"annotation":"beverage box","tags":["beverage","box","juice","straw","sweet"],"emoji":"🧃","order":3770,"group":4,"version":12},{"shortcodes":["mate"],"annotation":"mate","tags":["drink"],"emoji":"🧉","order":3771,"group":4,"version":12},{"shortcodes":["ice","ice_cube"],"annotation":"ice","tags":["cold","ice cube","iceberg"],"emoji":"🧊","order":3772,"group":4,"version":12},{"shortcodes":["chopsticks"],"annotation":"chopsticks","tags":["hashi"],"emoji":"🥢","order":3773,"group":4,"version":5},{"shortcodes":["fork_knife_plate"],"annotation":"fork and knife with plate","tags":["cooking","fork","knife","plate"],"emoji":"🍽️","order":3775,"group":4,"version":0.7},{"shortcodes":["fork_and_knife"],"annotation":"fork and knife","tags":["cooking","cutlery","fork","knife"],"emoji":"🍴","order":3776,"group":4,"version":0.6},{"shortcodes":["spoon"],"annotation":"spoon","tags":["tableware"],"emoji":"🥄","order":3777,"group":4,"version":3},{"shortcodes":["knife"],"annotation":"kitchen knife","tags":["cooking","hocho","knife","tool","weapon"],"emoji":"🔪","order":3778,"group":4,"version":0.6},{"shortcodes":["jar"],"annotation":"jar","tags":["condiment","container","empty","sauce","store"],"emoji":"🫙","order":3779,"group":4,"version":14},{"shortcodes":["amphora"],"annotation":"amphora","tags":["aquarius","cooking","drink","jug","zodiac"],"emoji":"🏺","order":3780,"group":4,"version":1},{"shortcodes":["earth_africa","earth_europe"],"annotation":"globe showing Europe-Africa","tags":["africa","earth","europe","globe","globe showing europe-africa","world"],"emoji":"🌍️","order":3781,"group":5,"version":0.7},{"shortcodes":["earth_americas"],"annotation":"globe showing Americas","tags":["americas","earth","globe","globe showing americas","world"],"emoji":"🌎️","order":3782,"group":5,"version":0.7},{"shortcodes":["earth_asia"],"annotation":"globe showing Asia-Australia","tags":["asia","australia","earth","globe","globe showing asia-australia","world"],"emoji":"🌏️","order":3783,"group":5,"version":0.6},{"shortcodes":["globe_with_meridians"],"annotation":"globe with meridians","tags":["earth","globe","meridians","world"],"emoji":"🌐","order":3784,"group":5,"version":1},{"shortcodes":["world_map"],"annotation":"world map","tags":["map","world"],"emoji":"🗺️","order":3786,"group":5,"version":0.7},{"shortcodes":["japan_map"],"annotation":"map of Japan","tags":["japan","map","map of japan"],"emoji":"🗾","order":3787,"group":5,"version":0.6},{"shortcodes":["compass"],"annotation":"compass","tags":["magnetic","navigation","orienteering"],"emoji":"🧭","order":3788,"group":5,"version":11},{"shortcodes":["mountain_snow"],"annotation":"snow-capped mountain","tags":["cold","mountain","snow"],"emoji":"🏔️","order":3790,"group":5,"version":0.7},{"shortcodes":["mountain"],"annotation":"mountain","tags":["mountain"],"emoji":"⛰️","order":3792,"group":5,"version":0.7},{"shortcodes":["volcano"],"annotation":"volcano","tags":["eruption","mountain"],"emoji":"🌋","order":3793,"group":5,"version":0.6},{"shortcodes":["mount_fuji"],"annotation":"mount fuji","tags":["fuji","mountain"],"emoji":"🗻","order":3794,"group":5,"version":0.6},{"shortcodes":["camping"],"annotation":"camping","tags":["camping"],"emoji":"🏕️","order":3796,"group":5,"version":0.7},{"shortcodes":["beach","beach_with_umbrella"],"annotation":"beach with umbrella","tags":["beach","umbrella"],"emoji":"🏖️","order":3798,"group":5,"version":0.7},{"shortcodes":["desert"],"annotation":"desert","tags":["desert"],"emoji":"🏜️","order":3800,"group":5,"version":0.7},{"shortcodes":["desert_island","island"],"annotation":"desert island","tags":["desert","island"],"emoji":"🏝️","order":3802,"group":5,"version":0.7},{"shortcodes":["national_park"],"annotation":"national park","tags":["park"],"emoji":"🏞️","order":3804,"group":5,"version":0.7},{"shortcodes":["stadium"],"annotation":"stadium","tags":["stadium"],"emoji":"🏟️","order":3806,"group":5,"version":0.7},{"shortcodes":["classical_building"],"annotation":"classical building","tags":["classical"],"emoji":"🏛️","order":3808,"group":5,"version":0.7},{"shortcodes":["building_construction","construction_site"],"annotation":"building construction","tags":["construction"],"emoji":"🏗️","order":3810,"group":5,"version":0.7},{"shortcodes":["bricks"],"annotation":"brick","tags":["bricks","clay","mortar","wall"],"emoji":"🧱","order":3811,"group":5,"version":11},{"shortcodes":["rock"],"annotation":"rock","tags":["boulder","heavy","solid","stone"],"emoji":"🪨","order":3812,"group":5,"version":13},{"shortcodes":["wood"],"annotation":"wood","tags":["log","lumber","timber"],"emoji":"🪵","order":3813,"group":5,"version":13},{"shortcodes":["hut"],"annotation":"hut","tags":["house","roundhouse","yurt"],"emoji":"🛖","order":3814,"group":5,"version":13},{"shortcodes":["homes","houses"],"annotation":"houses","tags":["houses"],"emoji":"🏘️","order":3816,"group":5,"version":0.7},{"shortcodes":["derelict_house","house_abandoned"],"annotation":"derelict house","tags":["derelict","house"],"emoji":"🏚️","order":3818,"group":5,"version":0.7},{"shortcodes":["house"],"annotation":"house","tags":["home"],"emoji":"🏠️","order":3819,"group":5,"version":0.6},{"shortcodes":["house_with_garden"],"annotation":"house with garden","tags":["garden","home","house"],"emoji":"🏡","order":3820,"group":5,"version":0.6},{"shortcodes":["office"],"annotation":"office building","tags":["building"],"emoji":"🏢","order":3821,"group":5,"version":0.6},{"shortcodes":["post_office"],"annotation":"Japanese post office","tags":["japanese","japanese post office","post"],"emoji":"🏣","order":3822,"group":5,"version":0.6},{"shortcodes":["european_post_office"],"annotation":"post office","tags":["european","post"],"emoji":"🏤","order":3823,"group":5,"version":1},{"shortcodes":["hospital"],"annotation":"hospital","tags":["doctor","medicine"],"emoji":"🏥","order":3824,"group":5,"version":0.6},{"shortcodes":["bank"],"annotation":"bank","tags":["building"],"emoji":"🏦","order":3825,"group":5,"version":0.6},{"shortcodes":["hotel"],"annotation":"hotel","tags":["building"],"emoji":"🏨","order":3826,"group":5,"version":0.6},{"shortcodes":["love_hotel"],"annotation":"love hotel","tags":["hotel","love"],"emoji":"🏩","order":3827,"group":5,"version":0.6},{"shortcodes":["convenience_store"],"annotation":"convenience store","tags":["convenience","store"],"emoji":"🏪","order":3828,"group":5,"version":0.6},{"shortcodes":["school"],"annotation":"school","tags":["building"],"emoji":"🏫","order":3829,"group":5,"version":0.6},{"shortcodes":["department_store"],"annotation":"department store","tags":["department","store"],"emoji":"🏬","order":3830,"group":5,"version":0.6},{"shortcodes":["factory"],"annotation":"factory","tags":["building"],"emoji":"🏭️","order":3831,"group":5,"version":0.6},{"shortcodes":["japanese_castle"],"annotation":"Japanese castle","tags":["castle","japanese"],"emoji":"🏯","order":3832,"group":5,"version":0.6},{"shortcodes":["castle","european_castle"],"annotation":"castle","tags":["european"],"emoji":"🏰","order":3833,"group":5,"version":0.6},{"shortcodes":["wedding"],"annotation":"wedding","tags":["chapel","romance"],"emoji":"💒","order":3834,"group":5,"version":0.6},{"shortcodes":["tokyo_tower"],"annotation":"Tokyo tower","tags":["tokyo","tower"],"emoji":"🗼","order":3835,"group":5,"version":0.6},{"shortcodes":["statue_of_liberty"],"annotation":"Statue of Liberty","tags":["liberty","statue","statue of liberty"],"emoji":"🗽","order":3836,"group":5,"version":0.6},{"shortcodes":["church"],"annotation":"church","tags":["christian","cross","religion"],"emoji":"⛪️","order":3837,"group":5,"version":0.6},{"shortcodes":["mosque"],"annotation":"mosque","tags":["islam","muslim","religion"],"emoji":"🕌","order":3838,"group":5,"version":1},{"shortcodes":["hindu_temple"],"annotation":"hindu temple","tags":["hindu","temple"],"emoji":"🛕","order":3839,"group":5,"version":12},{"shortcodes":["synagogue"],"annotation":"synagogue","tags":["jew","jewish","religion","temple"],"emoji":"🕍","order":3840,"group":5,"version":1},{"shortcodes":["shinto_shrine"],"annotation":"shinto shrine","tags":["religion","shinto","shrine"],"emoji":"⛩️","order":3842,"group":5,"version":0.7},{"shortcodes":["kaaba"],"annotation":"kaaba","tags":["islam","muslim","religion"],"emoji":"🕋","order":3843,"group":5,"version":1},{"shortcodes":["fountain"],"annotation":"fountain","tags":["fountain"],"emoji":"⛲️","order":3844,"group":5,"version":0.6},{"shortcodes":["tent"],"annotation":"tent","tags":["camping"],"emoji":"⛺️","order":3845,"group":5,"version":0.6},{"shortcodes":["foggy"],"annotation":"foggy","tags":["fog"],"emoji":"🌁","order":3846,"group":5,"version":0.6},{"shortcodes":["night_with_stars"],"annotation":"night with stars","tags":["night","star"],"emoji":"🌃","order":3847,"group":5,"version":0.6},{"shortcodes":["cityscape"],"annotation":"cityscape","tags":["city"],"emoji":"🏙️","order":3849,"group":5,"version":0.7},{"shortcodes":["sunrise_over_mountains"],"annotation":"sunrise over mountains","tags":["morning","mountain","sun","sunrise"],"emoji":"🌄","order":3850,"group":5,"version":0.6},{"shortcodes":["sunrise"],"annotation":"sunrise","tags":["morning","sun"],"emoji":"🌅","order":3851,"group":5,"version":0.6},{"shortcodes":["city_dusk"],"annotation":"cityscape at dusk","tags":["city","dusk","evening","landscape","sunset"],"emoji":"🌆","order":3852,"group":5,"version":0.6},{"shortcodes":["city_sunrise","city_sunset"],"annotation":"sunset","tags":["dusk","sun"],"emoji":"🌇","order":3853,"group":5,"version":0.6},{"shortcodes":["bridge_at_night"],"annotation":"bridge at night","tags":["bridge","night"],"emoji":"🌉","order":3854,"group":5,"version":0.6},{"shortcodes":["hotsprings"],"annotation":"hot springs","tags":["hot","hotsprings","springs","steaming"],"emoji":"♨️","order":3856,"group":5,"version":0.6},{"shortcodes":["carousel_horse"],"annotation":"carousel horse","tags":["carousel","horse"],"emoji":"🎠","order":3857,"group":5,"version":0.6},{"shortcodes":["playground_slide","slide"],"annotation":"playground slide","tags":["amusement park","play","theme park"],"emoji":"🛝","order":3858,"group":5,"version":14},{"shortcodes":["ferris_wheel"],"annotation":"ferris wheel","tags":["amusement park","ferris","theme park","wheel"],"emoji":"🎡","order":3859,"group":5,"version":0.6},{"shortcodes":["roller_coaster"],"annotation":"roller coaster","tags":["amusement park","coaster","roller","theme park"],"emoji":"🎢","order":3860,"group":5,"version":0.6},{"shortcodes":["barber","barber_pole"],"annotation":"barber pole","tags":["barber","haircut","pole"],"emoji":"💈","order":3861,"group":5,"version":0.6},{"shortcodes":["circus_tent"],"annotation":"circus tent","tags":["circus","tent"],"emoji":"🎪","order":3862,"group":5,"version":0.6},{"shortcodes":["steam_locomotive"],"annotation":"locomotive","tags":["engine","railway","steam","train"],"emoji":"🚂","order":3863,"group":5,"version":1},{"shortcodes":["railway_car"],"annotation":"railway car","tags":["car","electric","railway","train","tram","trolleybus"],"emoji":"🚃","order":3864,"group":5,"version":0.6},{"shortcodes":["bullettrain_side"],"annotation":"high-speed train","tags":["railway","shinkansen","speed","train"],"emoji":"🚄","order":3865,"group":5,"version":0.6},{"shortcodes":["bullettrain_front"],"annotation":"bullet train","tags":["bullet","railway","shinkansen","speed","train"],"emoji":"🚅","order":3866,"group":5,"version":0.6},{"shortcodes":["train"],"annotation":"train","tags":["railway"],"emoji":"🚆","order":3867,"group":5,"version":1},{"shortcodes":["metro"],"annotation":"metro","tags":["subway"],"emoji":"🚇️","order":3868,"group":5,"version":0.6},{"shortcodes":["light_rail"],"annotation":"light rail","tags":["railway"],"emoji":"🚈","order":3869,"group":5,"version":1},{"shortcodes":["station"],"annotation":"station","tags":["railway","train"],"emoji":"🚉","order":3870,"group":5,"version":0.6},{"shortcodes":["tram"],"annotation":"tram","tags":["trolleybus"],"emoji":"🚊","order":3871,"group":5,"version":1},{"shortcodes":["monorail"],"annotation":"monorail","tags":["vehicle"],"emoji":"🚝","order":3872,"group":5,"version":1},{"shortcodes":["mountain_railway"],"annotation":"mountain railway","tags":["car","mountain","railway"],"emoji":"🚞","order":3873,"group":5,"version":1},{"shortcodes":["tram_car"],"annotation":"tram car","tags":["car","tram","trolleybus"],"emoji":"🚋","order":3874,"group":5,"version":1},{"shortcodes":["bus"],"annotation":"bus","tags":["vehicle"],"emoji":"🚌","order":3875,"group":5,"version":0.6},{"shortcodes":["oncoming_bus"],"annotation":"oncoming bus","tags":["bus","oncoming"],"emoji":"🚍️","order":3876,"group":5,"version":0.7},{"shortcodes":["trolleybus"],"annotation":"trolleybus","tags":["bus","tram","trolley"],"emoji":"🚎","order":3877,"group":5,"version":1},{"shortcodes":["minibus"],"annotation":"minibus","tags":["bus"],"emoji":"🚐","order":3878,"group":5,"version":1},{"shortcodes":["ambulance"],"annotation":"ambulance","tags":["vehicle"],"emoji":"🚑️","order":3879,"group":5,"version":0.6},{"shortcodes":["fire_engine"],"annotation":"fire engine","tags":["engine","fire","truck"],"emoji":"🚒","order":3880,"group":5,"version":0.6},{"shortcodes":["police_car"],"annotation":"police car","tags":["car","patrol","police"],"emoji":"🚓","order":3881,"group":5,"version":0.6},{"shortcodes":["oncoming_police_car"],"annotation":"oncoming police car","tags":["car","oncoming","police"],"emoji":"🚔️","order":3882,"group":5,"version":0.7},{"shortcodes":["taxi"],"annotation":"taxi","tags":["vehicle"],"emoji":"🚕","order":3883,"group":5,"version":0.6},{"shortcodes":["oncoming_taxi"],"annotation":"oncoming taxi","tags":["oncoming","taxi"],"emoji":"🚖","order":3884,"group":5,"version":1},{"shortcodes":["car","red_car"],"annotation":"automobile","tags":["car"],"emoji":"🚗","order":3885,"group":5,"version":0.6},{"shortcodes":["oncoming_automobile"],"annotation":"oncoming automobile","tags":["automobile","car","oncoming"],"emoji":"🚘️","order":3886,"group":5,"version":0.7},{"shortcodes":["blue_car","suv"],"annotation":"sport utility vehicle","tags":["recreational","sport utility"],"emoji":"🚙","order":3887,"group":5,"version":0.6},{"shortcodes":["pickup_truck"],"annotation":"pickup truck","tags":["pick-up","pickup","truck"],"emoji":"🛻","order":3888,"group":5,"version":13},{"shortcodes":["delivery_truck","truck"],"annotation":"delivery truck","tags":["delivery","truck"],"emoji":"🚚","order":3889,"group":5,"version":0.6},{"shortcodes":["articulated_lorry"],"annotation":"articulated lorry","tags":["lorry","semi","truck"],"emoji":"🚛","order":3890,"group":5,"version":1},{"shortcodes":["tractor"],"annotation":"tractor","tags":["vehicle"],"emoji":"🚜","order":3891,"group":5,"version":1},{"shortcodes":["racing_car"],"annotation":"racing car","tags":["car","racing"],"emoji":"🏎️","order":3893,"group":5,"version":0.7},{"shortcodes":["motorcycle"],"annotation":"motorcycle","tags":["racing"],"emoji":"🏍️","order":3895,"group":5,"version":0.7},{"shortcodes":["motor_scooter"],"annotation":"motor scooter","tags":["motor","scooter"],"emoji":"🛵","order":3896,"group":5,"version":3},{"shortcodes":["manual_wheelchair"],"annotation":"manual wheelchair","tags":["accessibility"],"emoji":"🦽","order":3897,"group":5,"version":12},{"shortcodes":["motorized_wheelchair"],"annotation":"motorized wheelchair","tags":["accessibility"],"emoji":"🦼","order":3898,"group":5,"version":12},{"shortcodes":["auto_rickshaw"],"annotation":"auto rickshaw","tags":["tuk tuk"],"emoji":"🛺","order":3899,"group":5,"version":12},{"shortcodes":["bicycle","bike"],"annotation":"bicycle","tags":["bike"],"emoji":"🚲️","order":3900,"group":5,"version":0.6},{"shortcodes":["scooter"],"annotation":"kick scooter","tags":["kick","scooter"],"emoji":"🛴","order":3901,"group":5,"version":3},{"shortcodes":["skateboard"],"annotation":"skateboard","tags":["board"],"emoji":"🛹","order":3902,"group":5,"version":11},{"shortcodes":["roller_skate"],"annotation":"roller skate","tags":["roller","skate"],"emoji":"🛼","order":3903,"group":5,"version":13},{"shortcodes":["busstop"],"annotation":"bus stop","tags":["bus","stop"],"emoji":"🚏","order":3904,"group":5,"version":0.6},{"shortcodes":["motorway"],"annotation":"motorway","tags":["highway","road"],"emoji":"🛣️","order":3906,"group":5,"version":0.7},{"shortcodes":["railway_track"],"annotation":"railway track","tags":["railway","train"],"emoji":"🛤️","order":3908,"group":5,"version":0.7},{"shortcodes":["oil_drum"],"annotation":"oil drum","tags":["drum","oil"],"emoji":"🛢️","order":3910,"group":5,"version":0.7},{"shortcodes":["fuelpump"],"annotation":"fuel pump","tags":["diesel","fuel","fuelpump","gas","pump","station"],"emoji":"⛽️","order":3911,"group":5,"version":0.6},{"shortcodes":["wheel"],"annotation":"wheel","tags":["circle","tire","turn"],"emoji":"🛞","order":3912,"group":5,"version":14},{"shortcodes":["rotating_light"],"annotation":"police car light","tags":["beacon","car","light","police","revolving"],"emoji":"🚨","order":3913,"group":5,"version":0.6},{"shortcodes":["traffic_light"],"annotation":"horizontal traffic light","tags":["light","signal","traffic"],"emoji":"🚥","order":3914,"group":5,"version":0.6},{"shortcodes":["vertical_traffic_light"],"annotation":"vertical traffic light","tags":["light","signal","traffic"],"emoji":"🚦","order":3915,"group":5,"version":1},{"shortcodes":["octagonal_sign","stop_sign"],"annotation":"stop sign","tags":["octagonal","sign","stop"],"emoji":"🛑","order":3916,"group":5,"version":3},{"shortcodes":["construction"],"annotation":"construction","tags":["barrier"],"emoji":"🚧","order":3917,"group":5,"version":0.6},{"shortcodes":["anchor"],"annotation":"anchor","tags":["ship","tool"],"emoji":"⚓️","order":3918,"group":5,"version":0.6},{"shortcodes":["lifebuoy","ring_buoy"],"annotation":"ring buoy","tags":["float","life preserver","life saver","rescue","safety"],"emoji":"🛟","order":3919,"group":5,"version":14},{"shortcodes":["sailboat"],"annotation":"sailboat","tags":["boat","resort","sea","yacht"],"emoji":"⛵️","order":3920,"group":5,"version":0.6},{"shortcodes":["canoe"],"annotation":"canoe","tags":["boat"],"emoji":"🛶","order":3921,"group":5,"version":3},{"shortcodes":["speedboat"],"annotation":"speedboat","tags":["boat"],"emoji":"🚤","order":3922,"group":5,"version":0.6},{"shortcodes":["cruise_ship","passenger_ship"],"annotation":"passenger ship","tags":["passenger","ship"],"emoji":"🛳️","order":3924,"group":5,"version":0.7},{"shortcodes":["ferry"],"annotation":"ferry","tags":["boat","passenger"],"emoji":"⛴️","order":3926,"group":5,"version":0.7},{"shortcodes":["motorboat"],"annotation":"motor boat","tags":["boat","motorboat"],"emoji":"🛥️","order":3928,"group":5,"version":0.7},{"shortcodes":["ship"],"annotation":"ship","tags":["boat","passenger"],"emoji":"🚢","order":3929,"group":5,"version":0.6},{"shortcodes":["airplane"],"annotation":"airplane","tags":["aeroplane"],"emoji":"✈️","order":3931,"group":5,"version":0.6},{"shortcodes":["small_airplane"],"annotation":"small airplane","tags":["aeroplane","airplane"],"emoji":"🛩️","order":3933,"group":5,"version":0.7},{"shortcodes":["airplane_departure"],"annotation":"airplane departure","tags":["aeroplane","airplane","check-in","departure","departures"],"emoji":"🛫","order":3934,"group":5,"version":1},{"shortcodes":["airplane_arriving"],"annotation":"airplane arrival","tags":["aeroplane","airplane","arrivals","arriving","landing"],"emoji":"🛬","order":3935,"group":5,"version":1},{"shortcodes":["parachute"],"annotation":"parachute","tags":["hang-glide","parasail","skydive"],"emoji":"🪂","order":3936,"group":5,"version":12},{"shortcodes":["seat"],"annotation":"seat","tags":["chair"],"emoji":"💺","order":3937,"group":5,"version":0.6},{"shortcodes":["helicopter"],"annotation":"helicopter","tags":["vehicle"],"emoji":"🚁","order":3938,"group":5,"version":1},{"shortcodes":["suspension_railway"],"annotation":"suspension railway","tags":["railway","suspension"],"emoji":"🚟","order":3939,"group":5,"version":1},{"shortcodes":["mountain_cableway"],"annotation":"mountain cableway","tags":["cable","gondola","mountain"],"emoji":"🚠","order":3940,"group":5,"version":1},{"shortcodes":["aerial_tramway"],"annotation":"aerial tramway","tags":["aerial","cable","car","gondola","tramway"],"emoji":"🚡","order":3941,"group":5,"version":1},{"shortcodes":["satellite"],"annotation":"satellite","tags":["space"],"emoji":"🛰️","order":3943,"group":5,"version":0.7},{"shortcodes":["rocket"],"annotation":"rocket","tags":["space"],"emoji":"🚀","order":3944,"group":5,"version":0.6},{"shortcodes":["flying_saucer"],"annotation":"flying saucer","tags":["ufo"],"emoji":"🛸","order":3945,"group":5,"version":5},{"shortcodes":["bellhop"],"annotation":"bellhop bell","tags":["bell","bellhop","hotel"],"emoji":"🛎️","order":3947,"group":5,"version":0.7},{"shortcodes":["luggage"],"annotation":"luggage","tags":["packing","travel"],"emoji":"🧳","order":3948,"group":5,"version":11},{"shortcodes":["hourglass"],"annotation":"hourglass done","tags":["sand","timer"],"emoji":"⌛️","order":3949,"group":5,"version":0.6},{"shortcodes":["hourglass_flowing_sand"],"annotation":"hourglass not done","tags":["hourglass","sand","timer"],"emoji":"⏳️","order":3950,"group":5,"version":0.6},{"shortcodes":["watch"],"annotation":"watch","tags":["clock"],"emoji":"⌚️","order":3951,"group":5,"version":0.6},{"shortcodes":["alarm_clock"],"annotation":"alarm clock","tags":["alarm","clock"],"emoji":"⏰️","order":3952,"group":5,"version":0.6},{"shortcodes":["stopwatch"],"annotation":"stopwatch","tags":["clock"],"emoji":"⏱️","order":3954,"group":5,"version":1},{"shortcodes":["timer_clock"],"annotation":"timer clock","tags":["clock","timer"],"emoji":"⏲️","order":3956,"group":5,"version":1},{"shortcodes":["clock"],"annotation":"mantelpiece clock","tags":["clock"],"emoji":"🕰️","order":3958,"group":5,"version":0.7},{"shortcodes":["clock12"],"annotation":"twelve o’clock","tags":["00","12","12:00","clock","o’clock","twelve"],"emoji":"🕛️","order":3959,"group":5,"version":0.6},{"shortcodes":["clock1230"],"annotation":"twelve-thirty","tags":["12","12:30","clock","thirty","twelve"],"emoji":"🕧️","order":3960,"group":5,"version":0.7},{"shortcodes":["clock1"],"annotation":"one o’clock","tags":["00","1","1:00","clock","one","o’clock"],"emoji":"🕐️","order":3961,"group":5,"version":0.6},{"shortcodes":["clock130"],"annotation":"one-thirty","tags":["1","1:30","clock","one","thirty"],"emoji":"🕜️","order":3962,"group":5,"version":0.7},{"shortcodes":["clock2"],"annotation":"two o’clock","tags":["00","2","2:00","clock","o’clock","two"],"emoji":"🕑️","order":3963,"group":5,"version":0.6},{"shortcodes":["clock230"],"annotation":"two-thirty","tags":["2","2:30","clock","thirty","two"],"emoji":"🕝️","order":3964,"group":5,"version":0.7},{"shortcodes":["clock3"],"annotation":"three o’clock","tags":["00","3","3:00","clock","o’clock","three"],"emoji":"🕒️","order":3965,"group":5,"version":0.6},{"shortcodes":["clock330"],"annotation":"three-thirty","tags":["3","3:30","clock","thirty","three"],"emoji":"🕞️","order":3966,"group":5,"version":0.7},{"shortcodes":["clock4"],"annotation":"four o’clock","tags":["00","4","4:00","clock","four","o’clock"],"emoji":"🕓️","order":3967,"group":5,"version":0.6},{"shortcodes":["clock430"],"annotation":"four-thirty","tags":["4","4:30","clock","four","thirty"],"emoji":"🕟️","order":3968,"group":5,"version":0.7},{"shortcodes":["clock5"],"annotation":"five o’clock","tags":["00","5","5:00","clock","five","o’clock"],"emoji":"🕔️","order":3969,"group":5,"version":0.6},{"shortcodes":["clock530"],"annotation":"five-thirty","tags":["5","5:30","clock","five","thirty"],"emoji":"🕠️","order":3970,"group":5,"version":0.7},{"shortcodes":["clock6"],"annotation":"six o’clock","tags":["00","6","6:00","clock","o’clock","six"],"emoji":"🕕️","order":3971,"group":5,"version":0.6},{"shortcodes":["clock630"],"annotation":"six-thirty","tags":["6","6:30","clock","six","thirty"],"emoji":"🕡️","order":3972,"group":5,"version":0.7},{"shortcodes":["clock7"],"annotation":"seven o’clock","tags":["00","7","7:00","clock","o’clock","seven"],"emoji":"🕖️","order":3973,"group":5,"version":0.6},{"shortcodes":["clock730"],"annotation":"seven-thirty","tags":["7","7:30","clock","seven","thirty"],"emoji":"🕢️","order":3974,"group":5,"version":0.7},{"shortcodes":["clock8"],"annotation":"eight o’clock","tags":["00","8","8:00","clock","eight","o’clock"],"emoji":"🕗️","order":3975,"group":5,"version":0.6},{"shortcodes":["clock830"],"annotation":"eight-thirty","tags":["8","8:30","clock","eight","thirty"],"emoji":"🕣️","order":3976,"group":5,"version":0.7},{"shortcodes":["clock9"],"annotation":"nine o’clock","tags":["00","9","9:00","clock","nine","o’clock"],"emoji":"🕘️","order":3977,"group":5,"version":0.6},{"shortcodes":["clock930"],"annotation":"nine-thirty","tags":["9","9:30","clock","nine","thirty"],"emoji":"🕤️","order":3978,"group":5,"version":0.7},{"shortcodes":["clock10"],"annotation":"ten o’clock","tags":["00","10","10:00","clock","o’clock","ten"],"emoji":"🕙️","order":3979,"group":5,"version":0.6},{"shortcodes":["clock1030"],"annotation":"ten-thirty","tags":["10","10:30","clock","ten","thirty"],"emoji":"🕥️","order":3980,"group":5,"version":0.7},{"shortcodes":["clock11"],"annotation":"eleven o’clock","tags":["00","11","11:00","clock","eleven","o’clock"],"emoji":"🕚️","order":3981,"group":5,"version":0.6},{"shortcodes":["clock1130"],"annotation":"eleven-thirty","tags":["11","11:30","clock","eleven","thirty"],"emoji":"🕦️","order":3982,"group":5,"version":0.7},{"shortcodes":["new_moon"],"annotation":"new moon","tags":["dark","moon"],"emoji":"🌑","order":3983,"group":5,"version":0.6},{"shortcodes":["waxing_crescent_moon"],"annotation":"waxing crescent moon","tags":["crescent","moon","waxing"],"emoji":"🌒","order":3984,"group":5,"version":1},{"shortcodes":["first_quarter_moon"],"annotation":"first quarter moon","tags":["moon","quarter"],"emoji":"🌓","order":3985,"group":5,"version":0.6},{"shortcodes":["waxing_gibbous_moon"],"annotation":"waxing gibbous moon","tags":["gibbous","moon","waxing"],"emoji":"🌔","order":3986,"group":5,"version":0.6},{"shortcodes":["full_moon"],"annotation":"full moon","tags":["full","moon"],"emoji":"🌕️","order":3987,"group":5,"version":0.6},{"shortcodes":["waning_gibbous_moon"],"annotation":"waning gibbous moon","tags":["gibbous","moon","waning"],"emoji":"🌖","order":3988,"group":5,"version":1},{"shortcodes":["last_quarter_moon"],"annotation":"last quarter moon","tags":["moon","quarter"],"emoji":"🌗","order":3989,"group":5,"version":1},{"shortcodes":["waning_crescent_moon"],"annotation":"waning crescent moon","tags":["crescent","moon","waning"],"emoji":"🌘","order":3990,"group":5,"version":1},{"shortcodes":["crescent_moon"],"annotation":"crescent moon","tags":["crescent","moon"],"emoji":"🌙","order":3991,"group":5,"version":0.6},{"shortcodes":["new_moon_with_face"],"annotation":"new moon face","tags":["face","moon"],"emoji":"🌚","order":3992,"group":5,"version":1},{"shortcodes":["first_quarter_moon_with_face"],"annotation":"first quarter moon face","tags":["face","moon","quarter"],"emoji":"🌛","order":3993,"group":5,"version":0.6},{"shortcodes":["last_quarter_moon_with_face"],"annotation":"last quarter moon face","tags":["face","moon","quarter"],"emoji":"🌜️","order":3994,"group":5,"version":0.7},{"shortcodes":["thermometer"],"annotation":"thermometer","tags":["weather"],"emoji":"🌡️","order":3996,"group":5,"version":0.7},{"shortcodes":["sun"],"annotation":"sun","tags":["bright","rays","sunny"],"emoji":"☀️","order":3998,"group":5,"version":0.6},{"shortcodes":["full_moon_with_face"],"annotation":"full moon face","tags":["bright","face","full","moon"],"emoji":"🌝","order":3999,"group":5,"version":1},{"shortcodes":["sun_with_face"],"annotation":"sun with face","tags":["bright","face","sun"],"emoji":"🌞","order":4000,"group":5,"version":1},{"shortcodes":["ringed_planet","saturn"],"annotation":"ringed planet","tags":["saturn","saturnine"],"emoji":"🪐","order":4001,"group":5,"version":12},{"shortcodes":["star"],"annotation":"star","tags":["star"],"emoji":"⭐️","order":4002,"group":5,"version":0.6},{"shortcodes":["glowing_star","star2"],"annotation":"glowing star","tags":["glittery","glow","shining","sparkle","star"],"emoji":"🌟","order":4003,"group":5,"version":0.6},{"shortcodes":["shooting_star","stars"],"annotation":"shooting star","tags":["falling","shooting","star"],"emoji":"🌠","order":4004,"group":5,"version":0.6},{"shortcodes":["milky_way"],"annotation":"milky way","tags":["space"],"emoji":"🌌","order":4005,"group":5,"version":0.6},{"shortcodes":["cloud"],"annotation":"cloud","tags":["weather"],"emoji":"☁️","order":4007,"group":5,"version":0.6},{"shortcodes":["partly_sunny","sun_behind_cloud"],"annotation":"sun behind cloud","tags":["cloud","sun"],"emoji":"⛅️","order":4008,"group":5,"version":0.6},{"shortcodes":["stormy","thunder_cloud_and_rain"],"annotation":"cloud with lightning and rain","tags":["cloud","rain","thunder"],"emoji":"⛈️","order":4010,"group":5,"version":0.7},{"shortcodes":["sun_behind_small_cloud","sunny"],"annotation":"sun behind small cloud","tags":["cloud","sun"],"emoji":"🌤️","order":4012,"group":5,"version":0.7},{"shortcodes":["cloudy","sun_behind_large_cloud"],"annotation":"sun behind large cloud","tags":["cloud","sun"],"emoji":"🌥️","order":4014,"group":5,"version":0.7},{"shortcodes":["sun_and_rain","sun_behind_rain_cloud"],"annotation":"sun behind rain cloud","tags":["cloud","rain","sun"],"emoji":"🌦️","order":4016,"group":5,"version":0.7},{"shortcodes":["cloud_with_rain","rainy"],"annotation":"cloud with rain","tags":["cloud","rain"],"emoji":"🌧️","order":4018,"group":5,"version":0.7},{"shortcodes":["cloud_with_snow","snowy"],"annotation":"cloud with snow","tags":["cloud","cold","snow"],"emoji":"🌨️","order":4020,"group":5,"version":0.7},{"shortcodes":["cloud_with_lightning","lightning"],"annotation":"cloud with lightning","tags":["cloud","lightning"],"emoji":"🌩️","order":4022,"group":5,"version":0.7},{"shortcodes":["tornado"],"annotation":"tornado","tags":["cloud","whirlwind"],"emoji":"🌪️","order":4024,"group":5,"version":0.7},{"shortcodes":["fog"],"annotation":"fog","tags":["cloud"],"emoji":"🌫️","order":4026,"group":5,"version":0.7},{"shortcodes":["wind_blowing_face"],"annotation":"wind face","tags":["blow","cloud","face","wind"],"emoji":"🌬️","order":4028,"group":5,"version":0.7},{"shortcodes":["cyclone"],"annotation":"cyclone","tags":["dizzy","hurricane","twister","typhoon"],"emoji":"🌀","order":4029,"group":5,"version":0.6},{"shortcodes":["rainbow"],"annotation":"rainbow","tags":["rain"],"emoji":"🌈","order":4030,"group":5,"version":0.6},{"shortcodes":["closed_umbrella"],"annotation":"closed umbrella","tags":["clothing","rain","umbrella"],"emoji":"🌂","order":4031,"group":5,"version":0.6},{"shortcodes":["umbrella"],"annotation":"umbrella","tags":["clothing","rain"],"emoji":"☂️","order":4033,"group":5,"version":0.7},{"shortcodes":["umbrella_with_rain"],"annotation":"umbrella with rain drops","tags":["clothing","drop","rain","umbrella"],"emoji":"☔️","order":4034,"group":5,"version":0.6},{"shortcodes":["beach_umbrella","umbrella_on_ground"],"annotation":"umbrella on ground","tags":["rain","sun","umbrella"],"emoji":"⛱️","order":4036,"group":5,"version":0.7},{"shortcodes":["high_voltage","zap"],"annotation":"high voltage","tags":["danger","electric","lightning","voltage","zap"],"emoji":"⚡️","order":4037,"group":5,"version":0.6},{"shortcodes":["snowflake"],"annotation":"snowflake","tags":["cold","snow"],"emoji":"❄️","order":4039,"group":5,"version":0.6},{"shortcodes":["snowman2"],"annotation":"snowman","tags":["cold","snow"],"emoji":"☃️","order":4041,"group":5,"version":0.7},{"shortcodes":["snowman"],"annotation":"snowman without snow","tags":["cold","snow","snowman"],"emoji":"⛄️","order":4042,"group":5,"version":0.6},{"shortcodes":["comet"],"annotation":"comet","tags":["space"],"emoji":"☄️","order":4044,"group":5,"version":1},{"shortcodes":["fire"],"annotation":"fire","tags":["flame","tool"],"emoji":"🔥","order":4045,"group":5,"version":0.6},{"shortcodes":["droplet"],"annotation":"droplet","tags":["cold","comic","drop","sweat"],"emoji":"💧","order":4046,"group":5,"version":0.6},{"shortcodes":["ocean","water_wave"],"annotation":"water wave","tags":["ocean","water","wave"],"emoji":"🌊","order":4047,"group":5,"version":0.6},{"shortcodes":["jack_o_lantern"],"annotation":"jack-o-lantern","tags":["celebration","halloween","jack","lantern"],"emoji":"🎃","order":4048,"group":6,"version":0.6},{"shortcodes":["christmas_tree"],"annotation":"Christmas tree","tags":["celebration","christmas","tree"],"emoji":"🎄","order":4049,"group":6,"version":0.6},{"shortcodes":["fireworks"],"annotation":"fireworks","tags":["celebration"],"emoji":"🎆","order":4050,"group":6,"version":0.6},{"shortcodes":["sparkler"],"annotation":"sparkler","tags":["celebration","fireworks","sparkle"],"emoji":"🎇","order":4051,"group":6,"version":0.6},{"shortcodes":["firecracker"],"annotation":"firecracker","tags":["dynamite","explosive","fireworks"],"emoji":"🧨","order":4052,"group":6,"version":11},{"shortcodes":["sparkles"],"annotation":"sparkles","tags":["*","sparkle","star"],"emoji":"✨️","order":4053,"group":6,"version":0.6},{"shortcodes":["balloon"],"annotation":"balloon","tags":["celebration"],"emoji":"🎈","order":4054,"group":6,"version":0.6},{"shortcodes":["party","party_popper","tada"],"annotation":"party popper","tags":["celebration","party","popper","tada"],"emoji":"🎉","order":4055,"group":6,"version":0.6},{"shortcodes":["confetti_ball"],"annotation":"confetti ball","tags":["ball","celebration","confetti"],"emoji":"🎊","order":4056,"group":6,"version":0.6},{"shortcodes":["tanabata_tree"],"annotation":"tanabata tree","tags":["banner","celebration","japanese","tree"],"emoji":"🎋","order":4057,"group":6,"version":0.6},{"shortcodes":["bamboo"],"annotation":"pine decoration","tags":["bamboo","celebration","japanese","pine"],"emoji":"🎍","order":4058,"group":6,"version":0.6},{"shortcodes":["dolls"],"annotation":"Japanese dolls","tags":["celebration","doll","festival","japanese","japanese dolls"],"emoji":"🎎","order":4059,"group":6,"version":0.6},{"shortcodes":["carp_streamer","flags"],"annotation":"carp streamer","tags":["carp","celebration","streamer"],"emoji":"🎏","order":4060,"group":6,"version":0.6},{"shortcodes":["wind_chime"],"annotation":"wind chime","tags":["bell","celebration","chime","wind"],"emoji":"🎐","order":4061,"group":6,"version":0.6},{"shortcodes":["moon_ceremony","rice_scene"],"annotation":"moon viewing ceremony","tags":["celebration","ceremony","moon"],"emoji":"🎑","order":4062,"group":6,"version":0.6},{"shortcodes":["red_envelope"],"annotation":"red envelope","tags":["gift","good luck","hóngbāo","lai see","money"],"emoji":"🧧","order":4063,"group":6,"version":11},{"shortcodes":["ribbon"],"annotation":"ribbon","tags":["celebration"],"emoji":"🎀","order":4064,"group":6,"version":0.6},{"shortcodes":["gift"],"annotation":"wrapped gift","tags":["box","celebration","gift","present","wrapped"],"emoji":"🎁","order":4065,"group":6,"version":0.6},{"shortcodes":["reminder_ribbon"],"annotation":"reminder ribbon","tags":["celebration","reminder","ribbon"],"emoji":"🎗️","order":4067,"group":6,"version":0.7},{"shortcodes":["admission_tickets","tickets"],"annotation":"admission tickets","tags":["admission","ticket"],"emoji":"🎟️","order":4069,"group":6,"version":0.7},{"shortcodes":["ticket"],"annotation":"ticket","tags":["admission"],"emoji":"🎫","order":4070,"group":6,"version":0.6},{"shortcodes":["military_medal"],"annotation":"military medal","tags":["celebration","medal","military"],"emoji":"🎖️","order":4072,"group":6,"version":0.7},{"shortcodes":["trophy"],"annotation":"trophy","tags":["prize"],"emoji":"🏆️","order":4073,"group":6,"version":0.6},{"shortcodes":["sports_medal"],"annotation":"sports medal","tags":["medal"],"emoji":"🏅","order":4074,"group":6,"version":1},{"shortcodes":["1st","first_place_medal"],"annotation":"1st place medal","tags":["first","gold","medal"],"emoji":"🥇","order":4075,"group":6,"version":3},{"shortcodes":["2nd","second_place_medal"],"annotation":"2nd place medal","tags":["medal","second","silver"],"emoji":"🥈","order":4076,"group":6,"version":3},{"shortcodes":["3rd","third_place_medal"],"annotation":"3rd place medal","tags":["bronze","medal","third"],"emoji":"🥉","order":4077,"group":6,"version":3},{"shortcodes":["soccer"],"annotation":"soccer ball","tags":["ball","football","soccer"],"emoji":"⚽️","order":4078,"group":6,"version":0.6},{"shortcodes":["baseball"],"annotation":"baseball","tags":["ball"],"emoji":"⚾️","order":4079,"group":6,"version":0.6},{"shortcodes":["softball"],"annotation":"softball","tags":["ball","glove","underarm"],"emoji":"🥎","order":4080,"group":6,"version":11},{"shortcodes":["basketball"],"annotation":"basketball","tags":["ball","hoop"],"emoji":"🏀","order":4081,"group":6,"version":0.6},{"shortcodes":["volleyball"],"annotation":"volleyball","tags":["ball","game"],"emoji":"🏐","order":4082,"group":6,"version":1},{"shortcodes":["football"],"annotation":"american football","tags":["american","ball","football"],"emoji":"🏈","order":4083,"group":6,"version":0.6},{"shortcodes":["rugby_football"],"annotation":"rugby football","tags":["ball","football","rugby"],"emoji":"🏉","order":4084,"group":6,"version":1},{"shortcodes":["tennis"],"annotation":"tennis","tags":["ball","racquet"],"emoji":"🎾","order":4085,"group":6,"version":0.6},{"shortcodes":["flying_disc"],"annotation":"flying disc","tags":["ultimate"],"emoji":"🥏","order":4086,"group":6,"version":11},{"shortcodes":["bowling"],"annotation":"bowling","tags":["ball","game"],"emoji":"🎳","order":4087,"group":6,"version":0.6},{"shortcodes":["cricket_game"],"annotation":"cricket game","tags":["ball","bat","game"],"emoji":"🏏","order":4088,"group":6,"version":1},{"shortcodes":["field_hockey"],"annotation":"field hockey","tags":["ball","field","game","hockey","stick"],"emoji":"🏑","order":4089,"group":6,"version":1},{"shortcodes":["hockey"],"annotation":"ice hockey","tags":["game","hockey","ice","puck","stick"],"emoji":"🏒","order":4090,"group":6,"version":1},{"shortcodes":["lacrosse"],"annotation":"lacrosse","tags":["ball","goal","stick"],"emoji":"🥍","order":4091,"group":6,"version":11},{"shortcodes":["ping_pong"],"annotation":"ping pong","tags":["ball","bat","game","paddle","table tennis"],"emoji":"🏓","order":4092,"group":6,"version":1},{"shortcodes":["badminton"],"annotation":"badminton","tags":["birdie","game","racquet","shuttlecock"],"emoji":"🏸","order":4093,"group":6,"version":1},{"shortcodes":["boxing_glove"],"annotation":"boxing glove","tags":["boxing","glove"],"emoji":"🥊","order":4094,"group":6,"version":3},{"shortcodes":["martial_arts_uniform"],"annotation":"martial arts uniform","tags":["judo","karate","martial arts","taekwondo","uniform"],"emoji":"🥋","order":4095,"group":6,"version":3},{"shortcodes":["goal_net"],"annotation":"goal net","tags":["goal","net"],"emoji":"🥅","order":4096,"group":6,"version":3},{"shortcodes":["golf"],"annotation":"flag in hole","tags":["golf","hole"],"emoji":"⛳️","order":4097,"group":6,"version":0.6},{"shortcodes":["ice_skate"],"annotation":"ice skate","tags":["ice","skate"],"emoji":"⛸️","order":4099,"group":6,"version":0.7},{"shortcodes":["fishing_pole","fishing_pole_and_fish"],"annotation":"fishing pole","tags":["fish","pole"],"emoji":"🎣","order":4100,"group":6,"version":0.6},{"shortcodes":["diving_mask"],"annotation":"diving mask","tags":["diving","scuba","snorkeling"],"emoji":"🤿","order":4101,"group":6,"version":12},{"shortcodes":["running_shirt","running_shirt_with_sash"],"annotation":"running shirt","tags":["athletics","running","sash","shirt"],"emoji":"🎽","order":4102,"group":6,"version":0.6},{"shortcodes":["ski"],"annotation":"skis","tags":["ski","snow"],"emoji":"🎿","order":4103,"group":6,"version":0.6},{"shortcodes":["sled"],"annotation":"sled","tags":["sledge","sleigh"],"emoji":"🛷","order":4104,"group":6,"version":5},{"shortcodes":["curling_stone"],"annotation":"curling stone","tags":["game","rock"],"emoji":"🥌","order":4105,"group":6,"version":5},{"shortcodes":["bullseye","dart","direct_hit"],"annotation":"bullseye","tags":["dart","direct hit","game","hit","target"],"emoji":"🎯","order":4106,"group":6,"version":0.6},{"shortcodes":["yo_yo"],"annotation":"yo-yo","tags":["fluctuate","toy"],"emoji":"🪀","order":4107,"group":6,"version":12},{"shortcodes":["kite"],"annotation":"kite","tags":["fly","soar"],"emoji":"🪁","order":4108,"group":6,"version":12},{"shortcodes":["gun","pistol"],"annotation":"water pistol","tags":["gun","handgun","pistol","revolver","tool","water","weapon"],"emoji":"🔫","order":4109,"group":6,"version":0.6},{"shortcodes":["8ball","billiards"],"annotation":"pool 8 ball","tags":["8","ball","billiard","eight","game"],"emoji":"🎱","order":4110,"group":6,"version":0.6},{"shortcodes":["crystal_ball"],"annotation":"crystal ball","tags":["ball","crystal","fairy tale","fantasy","fortune","tool"],"emoji":"🔮","order":4111,"group":6,"version":0.6},{"shortcodes":["magic_wand"],"annotation":"magic wand","tags":["magic","witch","wizard"],"emoji":"🪄","order":4112,"group":6,"version":13},{"shortcodes":["controller","video_game"],"annotation":"video game","tags":["controller","game"],"emoji":"🎮️","order":4113,"group":6,"version":0.6},{"shortcodes":["joystick"],"annotation":"joystick","tags":["game","video game"],"emoji":"🕹️","order":4115,"group":6,"version":0.7},{"shortcodes":["slot_machine"],"annotation":"slot machine","tags":["game","slot"],"emoji":"🎰","order":4116,"group":6,"version":0.6},{"shortcodes":["game_die"],"annotation":"game die","tags":["dice","die","game"],"emoji":"🎲","order":4117,"group":6,"version":0.6},{"shortcodes":["jigsaw","puzzle_piece"],"annotation":"puzzle piece","tags":["clue","interlocking","jigsaw","piece","puzzle"],"emoji":"🧩","order":4118,"group":6,"version":11},{"shortcodes":["teddy_bear"],"annotation":"teddy bear","tags":["plaything","plush","stuffed","toy"],"emoji":"🧸","order":4119,"group":6,"version":11},{"shortcodes":["pinata"],"annotation":"piñata","tags":["celebration","party"],"emoji":"🪅","order":4120,"group":6,"version":13},{"shortcodes":["disco","disco_ball","mirror_ball"],"annotation":"mirror ball","tags":["dance","disco","glitter","party"],"emoji":"🪩","order":4121,"group":6,"version":14},{"shortcodes":["nesting_dolls"],"annotation":"nesting dolls","tags":["doll","nesting","russia"],"emoji":"🪆","order":4122,"group":6,"version":13},{"shortcodes":["spades"],"annotation":"spade suit","tags":["card","game"],"emoji":"♠️","order":4124,"group":6,"version":0.6},{"shortcodes":["hearts"],"annotation":"heart suit","tags":["card","game"],"emoji":"♥️","order":4126,"group":6,"version":0.6},{"shortcodes":["diamonds"],"annotation":"diamond suit","tags":["card","game"],"emoji":"♦️","order":4128,"group":6,"version":0.6},{"shortcodes":["clubs"],"annotation":"club suit","tags":["card","game"],"emoji":"♣️","order":4130,"group":6,"version":0.6},{"shortcodes":["chess_pawn"],"annotation":"chess pawn","tags":["chess","dupe","expendable"],"emoji":"♟️","order":4132,"group":6,"version":11},{"shortcodes":["black_joker"],"annotation":"joker","tags":["card","game","wildcard"],"emoji":"🃏","order":4133,"group":6,"version":0.6},{"shortcodes":["mahjong"],"annotation":"mahjong red dragon","tags":["game","mahjong","red"],"emoji":"🀄️","order":4134,"group":6,"version":0.6},{"shortcodes":["flower_playing_cards"],"annotation":"flower playing cards","tags":["card","flower","game","japanese","playing"],"emoji":"🎴","order":4135,"group":6,"version":0.6},{"shortcodes":["performing_arts"],"annotation":"performing arts","tags":["art","mask","performing","theater","theatre"],"emoji":"🎭️","order":4136,"group":6,"version":0.6},{"shortcodes":["frame_with_picture","framed_picture"],"annotation":"framed picture","tags":["art","frame","museum","painting","picture"],"emoji":"🖼️","order":4138,"group":6,"version":0.7},{"shortcodes":["art","palette"],"annotation":"artist palette","tags":["art","museum","painting","palette"],"emoji":"🎨","order":4139,"group":6,"version":0.6},{"shortcodes":["thread"],"annotation":"thread","tags":["needle","sewing","spool","string"],"emoji":"🧵","order":4140,"group":6,"version":11},{"shortcodes":["sewing_needle"],"annotation":"sewing needle","tags":["embroidery","needle","sewing","stitches","sutures","tailoring"],"emoji":"🪡","order":4141,"group":6,"version":13},{"shortcodes":["yarn"],"annotation":"yarn","tags":["ball","crochet","knit"],"emoji":"🧶","order":4142,"group":6,"version":11},{"shortcodes":["knot"],"annotation":"knot","tags":["rope","tangled","tie","twine","twist"],"emoji":"🪢","order":4143,"group":6,"version":13},{"shortcodes":["eyeglasses","glasses"],"annotation":"glasses","tags":["clothing","eye","eyeglasses","eyewear"],"emoji":"👓️","order":4144,"group":7,"version":0.6},{"shortcodes":["sunglasses"],"annotation":"sunglasses","tags":["dark","eye","eyewear","glasses"],"emoji":"🕶️","order":4146,"group":7,"version":0.7},{"shortcodes":["goggles"],"annotation":"goggles","tags":["eye protection","swimming","welding"],"emoji":"🥽","order":4147,"group":7,"version":11},{"shortcodes":["lab_coat"],"annotation":"lab coat","tags":["doctor","experiment","scientist"],"emoji":"🥼","order":4148,"group":7,"version":11},{"shortcodes":["safety_vest"],"annotation":"safety vest","tags":["emergency","safety","vest"],"emoji":"🦺","order":4149,"group":7,"version":12},{"shortcodes":["necktie"],"annotation":"necktie","tags":["clothing","tie"],"emoji":"👔","order":4150,"group":7,"version":0.6},{"shortcodes":["shirt"],"annotation":"t-shirt","tags":["clothing","shirt","tshirt"],"emoji":"👕","order":4151,"group":7,"version":0.6},{"shortcodes":["jeans"],"annotation":"jeans","tags":["clothing","pants","trousers"],"emoji":"👖","order":4152,"group":7,"version":0.6},{"shortcodes":["scarf"],"annotation":"scarf","tags":["neck"],"emoji":"🧣","order":4153,"group":7,"version":5},{"shortcodes":["gloves"],"annotation":"gloves","tags":["hand"],"emoji":"🧤","order":4154,"group":7,"version":5},{"shortcodes":["coat"],"annotation":"coat","tags":["jacket"],"emoji":"🧥","order":4155,"group":7,"version":5},{"shortcodes":["socks"],"annotation":"socks","tags":["stocking"],"emoji":"🧦","order":4156,"group":7,"version":5},{"shortcodes":["dress"],"annotation":"dress","tags":["clothing"],"emoji":"👗","order":4157,"group":7,"version":0.6},{"shortcodes":["kimono"],"annotation":"kimono","tags":["clothing"],"emoji":"👘","order":4158,"group":7,"version":0.6},{"shortcodes":["sari"],"annotation":"sari","tags":["clothing","dress"],"emoji":"🥻","order":4159,"group":7,"version":12},{"shortcodes":["one_piece_swimsuit"],"annotation":"one-piece swimsuit","tags":["bathing suit"],"emoji":"🩱","order":4160,"group":7,"version":12},{"shortcodes":["briefs"],"annotation":"briefs","tags":["bathing suit","one-piece","swimsuit","underwear"],"emoji":"🩲","order":4161,"group":7,"version":12},{"shortcodes":["shorts"],"annotation":"shorts","tags":["bathing suit","pants","underwear"],"emoji":"🩳","order":4162,"group":7,"version":12},{"shortcodes":["bikini"],"annotation":"bikini","tags":["clothing","swim"],"emoji":"👙","order":4163,"group":7,"version":0.6},{"shortcodes":["womans_clothes"],"annotation":"woman’s clothes","tags":["clothing","woman"],"emoji":"👚","order":4164,"group":7,"version":0.6},{"shortcodes":["folding_fan"],"annotation":"folding hand fan","tags":["cooling","dance","fan","flutter","hot","shy"],"emoji":"🪭","order":4165,"group":7,"version":15},{"shortcodes":["purse"],"annotation":"purse","tags":["clothing","coin"],"emoji":"👛","order":4166,"group":7,"version":0.6},{"shortcodes":["handbag"],"annotation":"handbag","tags":["bag","clothing","purse"],"emoji":"👜","order":4167,"group":7,"version":0.6},{"shortcodes":["clutch_bag","pouch"],"annotation":"clutch bag","tags":["bag","clothing","pouch"],"emoji":"👝","order":4168,"group":7,"version":0.6},{"shortcodes":["shopping_bags"],"annotation":"shopping bags","tags":["bag","hotel","shopping"],"emoji":"🛍️","order":4170,"group":7,"version":0.7},{"shortcodes":["backpack","school_satchel"],"annotation":"backpack","tags":["bag","rucksack","satchel","school"],"emoji":"🎒","order":4171,"group":7,"version":0.6},{"shortcodes":["thong_sandal"],"annotation":"thong sandal","tags":["beach sandals","sandals","thong sandals","thongs","zōri"],"emoji":"🩴","order":4172,"group":7,"version":13},{"shortcodes":["mans_shoe"],"annotation":"man’s shoe","tags":["clothing","man","shoe"],"emoji":"👞","order":4173,"group":7,"version":0.6},{"shortcodes":["athletic_shoe","sneaker"],"annotation":"running shoe","tags":["athletic","clothing","shoe","sneaker"],"emoji":"👟","order":4174,"group":7,"version":0.6},{"shortcodes":["hiking_boot"],"annotation":"hiking boot","tags":["backpacking","boot","camping","hiking"],"emoji":"🥾","order":4175,"group":7,"version":11},{"shortcodes":["flat_shoe","womans_flat_shoe"],"annotation":"flat shoe","tags":["ballet flat","slip-on","slipper"],"emoji":"🥿","order":4176,"group":7,"version":11},{"shortcodes":["high_heel"],"annotation":"high-heeled shoe","tags":["clothing","heel","shoe","woman"],"emoji":"👠","order":4177,"group":7,"version":0.6},{"shortcodes":["sandal"],"annotation":"woman’s sandal","tags":["clothing","sandal","shoe","woman"],"emoji":"👡","order":4178,"group":7,"version":0.6},{"shortcodes":["ballet_shoes"],"annotation":"ballet shoes","tags":["ballet","dance"],"emoji":"🩰","order":4179,"group":7,"version":12},{"shortcodes":["boot"],"annotation":"woman’s boot","tags":["boot","clothing","shoe","woman"],"emoji":"👢","order":4180,"group":7,"version":0.6},{"shortcodes":["hair_pick"],"annotation":"hair pick","tags":["afro","comb","hair","pick"],"emoji":"🪮","order":4181,"group":7,"version":15},{"shortcodes":["crown"],"annotation":"crown","tags":["clothing","king","queen"],"emoji":"👑","order":4182,"group":7,"version":0.6},{"shortcodes":["womans_hat"],"annotation":"woman’s hat","tags":["clothing","hat","woman"],"emoji":"👒","order":4183,"group":7,"version":0.6},{"shortcodes":["top_hat","tophat"],"annotation":"top hat","tags":["clothing","hat","top","tophat"],"emoji":"🎩","order":4184,"group":7,"version":0.6},{"shortcodes":["graduation_cap","mortar_board"],"annotation":"graduation cap","tags":["cap","celebration","clothing","graduation","hat"],"emoji":"🎓️","order":4185,"group":7,"version":0.6},{"shortcodes":["billed_cap"],"annotation":"billed cap","tags":["baseball cap"],"emoji":"🧢","order":4186,"group":7,"version":5},{"shortcodes":["military_helmet"],"annotation":"military helmet","tags":["army","helmet","military","soldier","warrior"],"emoji":"🪖","order":4187,"group":7,"version":13},{"shortcodes":["helmet_with_cross","rescue_worker_helmet"],"annotation":"rescue worker’s helmet","tags":["aid","cross","face","hat","helmet"],"emoji":"⛑️","order":4189,"group":7,"version":0.7},{"shortcodes":["prayer_beads"],"annotation":"prayer beads","tags":["beads","clothing","necklace","prayer","religion"],"emoji":"📿","order":4190,"group":7,"version":1},{"shortcodes":["lipstick"],"annotation":"lipstick","tags":["cosmetics","makeup"],"emoji":"💄","order":4191,"group":7,"version":0.6},{"shortcodes":["ring"],"annotation":"ring","tags":["diamond"],"emoji":"💍","order":4192,"group":7,"version":0.6},{"shortcodes":["gem"],"annotation":"gem stone","tags":["diamond","gem","jewel"],"emoji":"💎","order":4193,"group":7,"version":0.6},{"shortcodes":["mute","no_sound"],"annotation":"muted speaker","tags":["mute","quiet","silent","speaker"],"emoji":"🔇","order":4194,"group":7,"version":1},{"shortcodes":["low_volume","quiet_sound","speaker"],"annotation":"speaker low volume","tags":["soft"],"emoji":"🔈️","order":4195,"group":7,"version":0.7},{"shortcodes":["medium_volumne","sound"],"annotation":"speaker medium volume","tags":["medium"],"emoji":"🔉","order":4196,"group":7,"version":1},{"shortcodes":["high_volume","loud_sound"],"annotation":"speaker high volume","tags":["loud"],"emoji":"🔊","order":4197,"group":7,"version":0.6},{"shortcodes":["loudspeaker"],"annotation":"loudspeaker","tags":["loud","public address"],"emoji":"📢","order":4198,"group":7,"version":0.6},{"shortcodes":["mega","megaphone"],"annotation":"megaphone","tags":["cheering"],"emoji":"📣","order":4199,"group":7,"version":0.6},{"shortcodes":["postal_horn"],"annotation":"postal horn","tags":["horn","post","postal"],"emoji":"📯","order":4200,"group":7,"version":1},{"shortcodes":["bell"],"annotation":"bell","tags":["bell"],"emoji":"🔔","order":4201,"group":7,"version":0.6},{"shortcodes":["no_bell"],"annotation":"bell with slash","tags":["bell","forbidden","mute","quiet","silent"],"emoji":"🔕","order":4202,"group":7,"version":1},{"shortcodes":["musical_score"],"annotation":"musical score","tags":["music","score"],"emoji":"🎼","order":4203,"group":7,"version":0.6},{"shortcodes":["musical_note"],"annotation":"musical note","tags":["music","note"],"emoji":"🎵","order":4204,"group":7,"version":0.6},{"shortcodes":["musical_notes","notes"],"annotation":"musical notes","tags":["music","note","notes"],"emoji":"🎶","order":4205,"group":7,"version":0.6},{"shortcodes":["studio_microphone"],"annotation":"studio microphone","tags":["mic","microphone","music","studio"],"emoji":"🎙️","order":4207,"group":7,"version":0.7},{"shortcodes":["level_slider"],"annotation":"level slider","tags":["level","music","slider"],"emoji":"🎚️","order":4209,"group":7,"version":0.7},{"shortcodes":["control_knobs"],"annotation":"control knobs","tags":["control","knobs","music"],"emoji":"🎛️","order":4211,"group":7,"version":0.7},{"shortcodes":["microphone"],"annotation":"microphone","tags":["karaoke","mic"],"emoji":"🎤","order":4212,"group":7,"version":0.6},{"shortcodes":["headphones"],"annotation":"headphone","tags":["earbud"],"emoji":"🎧️","order":4213,"group":7,"version":0.6},{"shortcodes":["radio"],"annotation":"radio","tags":["video"],"emoji":"📻️","order":4214,"group":7,"version":0.6},{"shortcodes":["saxophone"],"annotation":"saxophone","tags":["instrument","music","sax"],"emoji":"🎷","order":4215,"group":7,"version":0.6},{"shortcodes":["accordion"],"annotation":"accordion","tags":["concertina","squeeze box"],"emoji":"🪗","order":4216,"group":7,"version":13},{"shortcodes":["guitar"],"annotation":"guitar","tags":["instrument","music"],"emoji":"🎸","order":4217,"group":7,"version":0.6},{"shortcodes":["musical_keyboard"],"annotation":"musical keyboard","tags":["instrument","keyboard","music","piano"],"emoji":"🎹","order":4218,"group":7,"version":0.6},{"shortcodes":["trumpet"],"annotation":"trumpet","tags":["instrument","music"],"emoji":"🎺","order":4219,"group":7,"version":0.6},{"shortcodes":["violin"],"annotation":"violin","tags":["instrument","music"],"emoji":"🎻","order":4220,"group":7,"version":0.6},{"shortcodes":["banjo"],"annotation":"banjo","tags":["music","stringed"],"emoji":"🪕","order":4221,"group":7,"version":12},{"shortcodes":["drum"],"annotation":"drum","tags":["drumsticks","music"],"emoji":"🥁","order":4222,"group":7,"version":3},{"shortcodes":["long_drum"],"annotation":"long drum","tags":["beat","conga","drum","rhythm"],"emoji":"🪘","order":4223,"group":7,"version":13},{"shortcodes":["maracas"],"annotation":"maracas","tags":["instrument","music","percussion","rattle","shake"],"emoji":"🪇","order":4224,"group":7,"version":15},{"shortcodes":["flute"],"annotation":"flute","tags":["fife","music","pipe","recorder","woodwind"],"emoji":"🪈","order":4225,"group":7,"version":15},{"shortcodes":["android","iphone","mobile_phone"],"annotation":"mobile phone","tags":["cell","mobile","phone","telephone"],"emoji":"📱","order":4226,"group":7,"version":0.6},{"shortcodes":["calling","mobile_phone_arrow"],"annotation":"mobile phone with arrow","tags":["arrow","cell","mobile","phone","receive"],"emoji":"📲","order":4227,"group":7,"version":0.6},{"shortcodes":["telephone"],"annotation":"telephone","tags":["phone"],"emoji":"☎️","order":4229,"group":7,"version":0.6},{"shortcodes":["telephone_receiver"],"annotation":"telephone receiver","tags":["phone","receiver","telephone"],"emoji":"📞","order":4230,"group":7,"version":0.6},{"shortcodes":["pager"],"annotation":"pager","tags":["pager"],"emoji":"📟️","order":4231,"group":7,"version":0.6},{"shortcodes":["fax","fax_machine"],"annotation":"fax machine","tags":["fax"],"emoji":"📠","order":4232,"group":7,"version":0.6},{"shortcodes":["battery"],"annotation":"battery","tags":["battery"],"emoji":"🔋","order":4233,"group":7,"version":0.6},{"shortcodes":["low_battery"],"annotation":"low battery","tags":["electronic","low energy"],"emoji":"🪫","order":4234,"group":7,"version":14},{"shortcodes":["electric_plug"],"annotation":"electric plug","tags":["electric","electricity","plug"],"emoji":"🔌","order":4235,"group":7,"version":0.6},{"shortcodes":["laptop"],"annotation":"laptop","tags":["computer","pc","personal"],"emoji":"💻️","order":4236,"group":7,"version":0.6},{"shortcodes":["computer","desktop_computer"],"annotation":"desktop computer","tags":["computer","desktop"],"emoji":"🖥️","order":4238,"group":7,"version":0.7},{"shortcodes":["printer"],"annotation":"printer","tags":["computer"],"emoji":"🖨️","order":4240,"group":7,"version":0.7},{"shortcodes":["keyboard"],"annotation":"keyboard","tags":["computer"],"emoji":"⌨️","order":4242,"group":7,"version":1},{"shortcodes":["computer_mouse"],"annotation":"computer mouse","tags":["computer"],"emoji":"🖱️","order":4244,"group":7,"version":0.7},{"shortcodes":["trackball"],"annotation":"trackball","tags":["computer"],"emoji":"🖲️","order":4246,"group":7,"version":0.7},{"shortcodes":["computer_disk","minidisc"],"annotation":"computer disk","tags":["computer","disk","minidisk","optical"],"emoji":"💽","order":4247,"group":7,"version":0.6},{"shortcodes":["floppy_disk"],"annotation":"floppy disk","tags":["computer","disk","floppy"],"emoji":"💾","order":4248,"group":7,"version":0.6},{"shortcodes":["cd","optical_disk"],"annotation":"optical disk","tags":["cd","computer","disk","optical"],"emoji":"💿️","order":4249,"group":7,"version":0.6},{"shortcodes":["dvd"],"annotation":"dvd","tags":["blu-ray","computer","disk","optical"],"emoji":"📀","order":4250,"group":7,"version":0.6},{"shortcodes":["abacus"],"annotation":"abacus","tags":["calculation"],"emoji":"🧮","order":4251,"group":7,"version":11},{"shortcodes":["movie_camera"],"annotation":"movie camera","tags":["camera","cinema","movie"],"emoji":"🎥","order":4252,"group":7,"version":0.6},{"shortcodes":["film_frames"],"annotation":"film frames","tags":["cinema","film","frames","movie"],"emoji":"🎞️","order":4254,"group":7,"version":0.7},{"shortcodes":["film_projector"],"annotation":"film projector","tags":["cinema","film","movie","projector","video"],"emoji":"📽️","order":4256,"group":7,"version":0.7},{"shortcodes":["clapper"],"annotation":"clapper board","tags":["clapper","movie"],"emoji":"🎬️","order":4257,"group":7,"version":0.6},{"shortcodes":["tv"],"annotation":"television","tags":["tv","video"],"emoji":"📺️","order":4258,"group":7,"version":0.6},{"shortcodes":["camera"],"annotation":"camera","tags":["video"],"emoji":"📷️","order":4259,"group":7,"version":0.6},{"shortcodes":["camera_with_flash"],"annotation":"camera with flash","tags":["camera","flash","video"],"emoji":"📸","order":4260,"group":7,"version":1},{"shortcodes":["video_camera"],"annotation":"video camera","tags":["camera","video"],"emoji":"📹️","order":4261,"group":7,"version":0.6},{"shortcodes":["vhs","videocassette"],"annotation":"videocassette","tags":["tape","vhs","video"],"emoji":"📼","order":4262,"group":7,"version":0.6},{"shortcodes":["mag"],"annotation":"magnifying glass tilted left","tags":["glass","magnifying","search","tool"],"emoji":"🔍️","order":4263,"group":7,"version":0.6},{"shortcodes":["mag_right"],"annotation":"magnifying glass tilted right","tags":["glass","magnifying","search","tool"],"emoji":"🔎","order":4264,"group":7,"version":0.6},{"shortcodes":["candle"],"annotation":"candle","tags":["light"],"emoji":"🕯️","order":4266,"group":7,"version":0.7},{"shortcodes":["bulb","light_bulb"],"annotation":"light bulb","tags":["bulb","comic","electric","idea","light"],"emoji":"💡","order":4267,"group":7,"version":0.6},{"shortcodes":["flashlight"],"annotation":"flashlight","tags":["electric","light","tool","torch"],"emoji":"🔦","order":4268,"group":7,"version":0.6},{"shortcodes":["izakaya_lantern","red_paper_lantern"],"annotation":"red paper lantern","tags":["bar","lantern","light","red"],"emoji":"🏮","order":4269,"group":7,"version":0.6},{"shortcodes":["diya_lamp"],"annotation":"diya lamp","tags":["diya","lamp","oil"],"emoji":"🪔","order":4270,"group":7,"version":12},{"shortcodes":["notebook_with_decorative_cover"],"annotation":"notebook with decorative cover","tags":["book","cover","decorated","notebook"],"emoji":"📔","order":4271,"group":7,"version":0.6},{"shortcodes":["closed_book"],"annotation":"closed book","tags":["book","closed"],"emoji":"📕","order":4272,"group":7,"version":0.6},{"shortcodes":["book","open_book"],"annotation":"open book","tags":["book","open"],"emoji":"📖","order":4273,"group":7,"version":0.6},{"shortcodes":["green_book"],"annotation":"green book","tags":["book","green"],"emoji":"📗","order":4274,"group":7,"version":0.6},{"shortcodes":["blue_book"],"annotation":"blue book","tags":["blue","book"],"emoji":"📘","order":4275,"group":7,"version":0.6},{"shortcodes":["orange_book"],"annotation":"orange book","tags":["book","orange"],"emoji":"📙","order":4276,"group":7,"version":0.6},{"shortcodes":["books"],"annotation":"books","tags":["book"],"emoji":"📚️","order":4277,"group":7,"version":0.6},{"shortcodes":["notebook"],"annotation":"notebook","tags":["notebook"],"emoji":"📓","order":4278,"group":7,"version":0.6},{"shortcodes":["ledger"],"annotation":"ledger","tags":["notebook"],"emoji":"📒","order":4279,"group":7,"version":0.6},{"shortcodes":["page_with_curl"],"annotation":"page with curl","tags":["curl","document","page"],"emoji":"📃","order":4280,"group":7,"version":0.6},{"shortcodes":["scroll"],"annotation":"scroll","tags":["paper"],"emoji":"📜","order":4281,"group":7,"version":0.6},{"shortcodes":["page_facing_up"],"annotation":"page facing up","tags":["document","page"],"emoji":"📄","order":4282,"group":7,"version":0.6},{"shortcodes":["newspaper"],"annotation":"newspaper","tags":["news","paper"],"emoji":"📰","order":4283,"group":7,"version":0.6},{"shortcodes":["rolled_up_newspaper"],"annotation":"rolled-up newspaper","tags":["news","newspaper","paper","rolled"],"emoji":"🗞️","order":4285,"group":7,"version":0.7},{"shortcodes":["bookmark_tabs"],"annotation":"bookmark tabs","tags":["bookmark","mark","marker","tabs"],"emoji":"📑","order":4286,"group":7,"version":0.6},{"shortcodes":["bookmark"],"annotation":"bookmark","tags":["mark"],"emoji":"🔖","order":4287,"group":7,"version":0.6},{"shortcodes":["label"],"annotation":"label","tags":["label"],"emoji":"🏷️","order":4289,"group":7,"version":0.7},{"shortcodes":["moneybag"],"annotation":"money bag","tags":["bag","dollar","money","moneybag"],"emoji":"💰️","order":4290,"group":7,"version":0.6},{"shortcodes":["coin"],"annotation":"coin","tags":["gold","metal","money","silver","treasure"],"emoji":"🪙","order":4291,"group":7,"version":13},{"shortcodes":["yen"],"annotation":"yen banknote","tags":["banknote","bill","currency","money","note","yen"],"emoji":"💴","order":4292,"group":7,"version":0.6},{"shortcodes":["dollar"],"annotation":"dollar banknote","tags":["banknote","bill","currency","dollar","money","note"],"emoji":"💵","order":4293,"group":7,"version":0.6},{"shortcodes":["euro"],"annotation":"euro banknote","tags":["banknote","bill","currency","euro","money","note"],"emoji":"💶","order":4294,"group":7,"version":1},{"shortcodes":["pound"],"annotation":"pound banknote","tags":["banknote","bill","currency","money","note","pound"],"emoji":"💷","order":4295,"group":7,"version":1},{"shortcodes":["money_with_wings"],"annotation":"money with wings","tags":["banknote","bill","fly","money","wings"],"emoji":"💸","order":4296,"group":7,"version":0.6},{"shortcodes":["credit_card"],"annotation":"credit card","tags":["card","credit","money"],"emoji":"💳️","order":4297,"group":7,"version":0.6},{"shortcodes":["receipt"],"annotation":"receipt","tags":["accounting","bookkeeping","evidence","proof"],"emoji":"🧾","order":4298,"group":7,"version":11},{"shortcodes":["chart"],"annotation":"chart increasing with yen","tags":["chart","graph","growth","money","yen"],"emoji":"💹","order":4299,"group":7,"version":0.6},{"shortcodes":["envelope"],"annotation":"envelope","tags":["email","letter"],"emoji":"✉️","order":4301,"group":7,"version":0.6},{"shortcodes":["e-mail","email"],"annotation":"e-mail","tags":["email","letter","mail"],"emoji":"📧","order":4302,"group":7,"version":0.6},{"shortcodes":["incoming_envelope"],"annotation":"incoming envelope","tags":["e-mail","email","envelope","incoming","letter","receive"],"emoji":"📨","order":4303,"group":7,"version":0.6},{"shortcodes":["envelope_with_arrow"],"annotation":"envelope with arrow","tags":["arrow","e-mail","email","envelope","outgoing"],"emoji":"📩","order":4304,"group":7,"version":0.6},{"shortcodes":["outbox_tray"],"annotation":"outbox tray","tags":["box","letter","mail","outbox","sent","tray"],"emoji":"📤️","order":4305,"group":7,"version":0.6},{"shortcodes":["inbox_tray"],"annotation":"inbox tray","tags":["box","inbox","letter","mail","receive","tray"],"emoji":"📥️","order":4306,"group":7,"version":0.6},{"shortcodes":["package"],"annotation":"package","tags":["box","parcel"],"emoji":"📦️","order":4307,"group":7,"version":0.6},{"shortcodes":["mailbox"],"annotation":"closed mailbox with raised flag","tags":["closed","mail","mailbox","postbox"],"emoji":"📫️","order":4308,"group":7,"version":0.6},{"shortcodes":["mailbox_closed"],"annotation":"closed mailbox with lowered flag","tags":["closed","lowered","mail","mailbox","postbox"],"emoji":"📪️","order":4309,"group":7,"version":0.6},{"shortcodes":["mailbox_with_mail"],"annotation":"open mailbox with raised flag","tags":["mail","mailbox","open","postbox"],"emoji":"📬️","order":4310,"group":7,"version":0.7},{"shortcodes":["mailbox_with_no_mail"],"annotation":"open mailbox with lowered flag","tags":["lowered","mail","mailbox","open","postbox"],"emoji":"📭️","order":4311,"group":7,"version":0.7},{"shortcodes":["postbox"],"annotation":"postbox","tags":["mail","mailbox"],"emoji":"📮","order":4312,"group":7,"version":0.6},{"shortcodes":["ballot_box"],"annotation":"ballot box with ballot","tags":["ballot","box"],"emoji":"🗳️","order":4314,"group":7,"version":0.7},{"shortcodes":["pencil"],"annotation":"pencil","tags":["pencil"],"emoji":"✏️","order":4316,"group":7,"version":0.6},{"shortcodes":["black_nib"],"annotation":"black nib","tags":["nib","pen"],"emoji":"✒️","order":4318,"group":7,"version":0.6},{"shortcodes":["fountain_pen"],"annotation":"fountain pen","tags":["fountain","pen"],"emoji":"🖋️","order":4320,"group":7,"version":0.7},{"shortcodes":["pen"],"annotation":"pen","tags":["ballpoint"],"emoji":"🖊️","order":4322,"group":7,"version":0.7},{"shortcodes":["paintbrush"],"annotation":"paintbrush","tags":["painting"],"emoji":"🖌️","order":4324,"group":7,"version":0.7},{"shortcodes":["crayon"],"annotation":"crayon","tags":["crayon"],"emoji":"🖍️","order":4326,"group":7,"version":0.7},{"shortcodes":["memo"],"annotation":"memo","tags":["pencil"],"emoji":"📝","order":4327,"group":7,"version":0.6},{"shortcodes":["briefcase"],"annotation":"briefcase","tags":["briefcase"],"emoji":"💼","order":4328,"group":7,"version":0.6},{"shortcodes":["file_folder"],"annotation":"file folder","tags":["file","folder"],"emoji":"📁","order":4329,"group":7,"version":0.6},{"shortcodes":["open_file_folder"],"annotation":"open file folder","tags":["file","folder","open"],"emoji":"📂","order":4330,"group":7,"version":0.6},{"shortcodes":["card_index_dividers"],"annotation":"card index dividers","tags":["card","dividers","index"],"emoji":"🗂️","order":4332,"group":7,"version":0.7},{"shortcodes":["date"],"annotation":"calendar","tags":["date"],"emoji":"📅","order":4333,"group":7,"version":0.6},{"shortcodes":["calendar"],"annotation":"tear-off calendar","tags":["calendar"],"emoji":"📆","order":4334,"group":7,"version":0.6},{"shortcodes":["notepad_spiral"],"annotation":"spiral notepad","tags":["note","pad","spiral"],"emoji":"🗒️","order":4336,"group":7,"version":0.7},{"shortcodes":["calendar_spiral"],"annotation":"spiral calendar","tags":["calendar","pad","spiral"],"emoji":"🗓️","order":4338,"group":7,"version":0.7},{"shortcodes":["card_index"],"annotation":"card index","tags":["card","index","rolodex"],"emoji":"📇","order":4339,"group":7,"version":0.6},{"shortcodes":["chart_increasing","chart_with_upwards_trend"],"annotation":"chart increasing","tags":["chart","graph","growth","trend","upward"],"emoji":"📈","order":4340,"group":7,"version":0.6},{"shortcodes":["chart_decreasing","chart_with_downwards_trend"],"annotation":"chart decreasing","tags":["chart","down","graph","trend"],"emoji":"📉","order":4341,"group":7,"version":0.6},{"shortcodes":["bar_chart"],"annotation":"bar chart","tags":["bar","chart","graph"],"emoji":"📊","order":4342,"group":7,"version":0.6},{"shortcodes":["clipboard"],"annotation":"clipboard","tags":["clipboard"],"emoji":"📋️","order":4343,"group":7,"version":0.6},{"shortcodes":["pushpin"],"annotation":"pushpin","tags":["pin"],"emoji":"📌","order":4344,"group":7,"version":0.6},{"shortcodes":["round_pushpin"],"annotation":"round pushpin","tags":["pin","pushpin"],"emoji":"📍","order":4345,"group":7,"version":0.6},{"shortcodes":["paperclip"],"annotation":"paperclip","tags":["paperclip"],"emoji":"📎","order":4346,"group":7,"version":0.6},{"shortcodes":["paperclips"],"annotation":"linked paperclips","tags":["link","paperclip"],"emoji":"🖇️","order":4348,"group":7,"version":0.7},{"shortcodes":["straight_ruler"],"annotation":"straight ruler","tags":["ruler","straight edge"],"emoji":"📏","order":4349,"group":7,"version":0.6},{"shortcodes":["triangular_ruler"],"annotation":"triangular ruler","tags":["ruler","set","triangle"],"emoji":"📐","order":4350,"group":7,"version":0.6},{"shortcodes":["scissors"],"annotation":"scissors","tags":["cutting","tool"],"emoji":"✂️","order":4352,"group":7,"version":0.6},{"shortcodes":["card_file_box"],"annotation":"card file box","tags":["box","card","file"],"emoji":"🗃️","order":4354,"group":7,"version":0.7},{"shortcodes":["file_cabinet"],"annotation":"file cabinet","tags":["cabinet","file","filing"],"emoji":"🗄️","order":4356,"group":7,"version":0.7},{"shortcodes":["trashcan","wastebasket"],"annotation":"wastebasket","tags":["wastebasket"],"emoji":"🗑️","order":4358,"group":7,"version":0.7},{"shortcodes":["lock","locked"],"annotation":"locked","tags":["closed"],"emoji":"🔒️","order":4359,"group":7,"version":0.6},{"shortcodes":["unlock","unlocked"],"annotation":"unlocked","tags":["lock","open","unlock"],"emoji":"🔓️","order":4360,"group":7,"version":0.6},{"shortcodes":["lock_with_ink_pen","locked_with_pen"],"annotation":"locked with pen","tags":["ink","lock","nib","pen","privacy"],"emoji":"🔏","order":4361,"group":7,"version":0.6},{"shortcodes":["closed_lock_with_key","locked_with_key"],"annotation":"locked with key","tags":["closed","key","lock","secure"],"emoji":"🔐","order":4362,"group":7,"version":0.6},{"shortcodes":["key"],"annotation":"key","tags":["lock","password"],"emoji":"🔑","order":4363,"group":7,"version":0.6},{"shortcodes":["old_key"],"annotation":"old key","tags":["clue","key","lock","old"],"emoji":"🗝️","order":4365,"group":7,"version":0.7},{"shortcodes":["hammer"],"annotation":"hammer","tags":["tool"],"emoji":"🔨","order":4366,"group":7,"version":0.6},{"shortcodes":["axe"],"annotation":"axe","tags":["chop","hatchet","split","wood"],"emoji":"🪓","order":4367,"group":7,"version":12},{"shortcodes":["pick"],"annotation":"pick","tags":["mining","tool"],"emoji":"⛏️","order":4369,"group":7,"version":0.7},{"shortcodes":["hammer_and_pick"],"annotation":"hammer and pick","tags":["hammer","pick","tool"],"emoji":"⚒️","order":4371,"group":7,"version":1},{"shortcodes":["hammer_and_wrench"],"annotation":"hammer and wrench","tags":["hammer","spanner","tool","wrench"],"emoji":"🛠️","order":4373,"group":7,"version":0.7},{"shortcodes":["dagger"],"annotation":"dagger","tags":["knife","weapon"],"emoji":"🗡️","order":4375,"group":7,"version":0.7},{"shortcodes":["crossed_swords"],"annotation":"crossed swords","tags":["crossed","swords","weapon"],"emoji":"⚔️","order":4377,"group":7,"version":1},{"shortcodes":["bomb"],"annotation":"bomb","tags":["comic"],"emoji":"💣️","order":4378,"group":7,"version":0.6},{"shortcodes":["boomerang"],"annotation":"boomerang","tags":["rebound","repercussion"],"emoji":"🪃","order":4379,"group":7,"version":13},{"shortcodes":["bow_and_arrow"],"annotation":"bow and arrow","tags":["archer","arrow","bow","sagittarius","zodiac"],"emoji":"🏹","order":4380,"group":7,"version":1},{"shortcodes":["shield"],"annotation":"shield","tags":["weapon"],"emoji":"🛡️","order":4382,"group":7,"version":0.7},{"shortcodes":["carpentry_saw"],"annotation":"carpentry saw","tags":["carpenter","lumber","saw","tool"],"emoji":"🪚","order":4383,"group":7,"version":13},{"shortcodes":["wrench"],"annotation":"wrench","tags":["spanner","tool"],"emoji":"🔧","order":4384,"group":7,"version":0.6},{"shortcodes":["screwdriver"],"annotation":"screwdriver","tags":["screw","tool"],"emoji":"🪛","order":4385,"group":7,"version":13},{"shortcodes":["nut_and_bolt"],"annotation":"nut and bolt","tags":["bolt","nut","tool"],"emoji":"🔩","order":4386,"group":7,"version":0.6},{"shortcodes":["gear"],"annotation":"gear","tags":["cog","cogwheel","tool"],"emoji":"⚙️","order":4388,"group":7,"version":1},{"shortcodes":["clamp","compression"],"annotation":"clamp","tags":["compress","tool","vice"],"emoji":"🗜️","order":4390,"group":7,"version":0.7},{"shortcodes":["scales"],"annotation":"balance scale","tags":["balance","justice","libra","scale","zodiac"],"emoji":"⚖️","order":4392,"group":7,"version":1},{"shortcodes":["probing_cane","white_cane"],"annotation":"white cane","tags":["accessibility","blind"],"emoji":"🦯","order":4393,"group":7,"version":12},{"shortcodes":["link"],"annotation":"link","tags":["link"],"emoji":"🔗","order":4394,"group":7,"version":0.6},{"shortcodes":["broken_chain"],"annotation":"broken chain","tags":["break","breaking","chain","cuffs","freedom"],"emoji":"⛓️💥","order":4395,"group":7,"version":15.1},{"shortcodes":["chains"],"annotation":"chains","tags":["chain"],"emoji":"⛓️","order":4398,"group":7,"version":0.7},{"shortcodes":["hook"],"annotation":"hook","tags":["catch","crook","curve","ensnare","selling point"],"emoji":"🪝","order":4399,"group":7,"version":13},{"shortcodes":["toolbox"],"annotation":"toolbox","tags":["chest","mechanic","tool"],"emoji":"🧰","order":4400,"group":7,"version":11},{"shortcodes":["magnet"],"annotation":"magnet","tags":["attraction","horseshoe","magnetic"],"emoji":"🧲","order":4401,"group":7,"version":11},{"shortcodes":["ladder"],"annotation":"ladder","tags":["climb","rung","step"],"emoji":"🪜","order":4402,"group":7,"version":13},{"shortcodes":["alembic"],"annotation":"alembic","tags":["chemistry","tool"],"emoji":"⚗️","order":4404,"group":7,"version":1},{"shortcodes":["test_tube"],"annotation":"test tube","tags":["chemist","chemistry","experiment","lab","science"],"emoji":"🧪","order":4405,"group":7,"version":11},{"shortcodes":["petri_dish"],"annotation":"petri dish","tags":["bacteria","biologist","biology","culture","lab"],"emoji":"🧫","order":4406,"group":7,"version":11},{"shortcodes":["dna","double_helix"],"annotation":"dna","tags":["biologist","evolution","gene","genetics","life"],"emoji":"🧬","order":4407,"group":7,"version":11},{"shortcodes":["microscope"],"annotation":"microscope","tags":["science","tool"],"emoji":"🔬","order":4408,"group":7,"version":1},{"shortcodes":["telescope"],"annotation":"telescope","tags":["science","tool"],"emoji":"🔭","order":4409,"group":7,"version":1},{"shortcodes":["satellite_antenna"],"annotation":"satellite antenna","tags":["antenna","dish","satellite"],"emoji":"📡","order":4410,"group":7,"version":0.6},{"shortcodes":["syringe"],"annotation":"syringe","tags":["medicine","needle","shot","sick"],"emoji":"💉","order":4411,"group":7,"version":0.6},{"shortcodes":["drop_of_blood"],"annotation":"drop of blood","tags":["bleed","blood donation","injury","medicine","menstruation"],"emoji":"🩸","order":4412,"group":7,"version":12},{"shortcodes":["pill"],"annotation":"pill","tags":["doctor","medicine","sick"],"emoji":"💊","order":4413,"group":7,"version":0.6},{"shortcodes":["adhesive_bandage","bandaid"],"annotation":"adhesive bandage","tags":["bandage"],"emoji":"🩹","order":4414,"group":7,"version":12},{"shortcodes":["crutch"],"annotation":"crutch","tags":["cane","disability","hurt","mobility aid","stick"],"emoji":"🩼","order":4415,"group":7,"version":14},{"shortcodes":["stethoscope"],"annotation":"stethoscope","tags":["doctor","heart","medicine"],"emoji":"🩺","order":4416,"group":7,"version":12},{"shortcodes":["x-ray","xray"],"annotation":"x-ray","tags":["bones","doctor","medical","skeleton"],"emoji":"🩻","order":4417,"group":7,"version":14},{"shortcodes":["door"],"annotation":"door","tags":["door"],"emoji":"🚪","order":4418,"group":7,"version":0.6},{"shortcodes":["elevator"],"annotation":"elevator","tags":["accessibility","hoist","lift"],"emoji":"🛗","order":4419,"group":7,"version":13},{"shortcodes":["mirror"],"annotation":"mirror","tags":["reflection","reflector","speculum"],"emoji":"🪞","order":4420,"group":7,"version":13},{"shortcodes":["window"],"annotation":"window","tags":["frame","fresh air","opening","transparent","view"],"emoji":"🪟","order":4421,"group":7,"version":13},{"shortcodes":["bed"],"annotation":"bed","tags":["hotel","sleep"],"emoji":"🛏️","order":4423,"group":7,"version":0.7},{"shortcodes":["couch_and_lamp"],"annotation":"couch and lamp","tags":["couch","hotel","lamp"],"emoji":"🛋️","order":4425,"group":7,"version":0.7},{"shortcodes":["chair"],"annotation":"chair","tags":["seat","sit"],"emoji":"🪑","order":4426,"group":7,"version":12},{"shortcodes":["toilet"],"annotation":"toilet","tags":["toilet"],"emoji":"🚽","order":4427,"group":7,"version":0.6},{"shortcodes":["plunger"],"annotation":"plunger","tags":["force cup","plumber","suction","toilet"],"emoji":"🪠","order":4428,"group":7,"version":13},{"shortcodes":["shower"],"annotation":"shower","tags":["water"],"emoji":"🚿","order":4429,"group":7,"version":1},{"shortcodes":["bathtub"],"annotation":"bathtub","tags":["bath"],"emoji":"🛁","order":4430,"group":7,"version":1},{"shortcodes":["mouse_trap"],"annotation":"mouse trap","tags":["bait","mousetrap","snare","trap"],"emoji":"🪤","order":4431,"group":7,"version":13},{"shortcodes":["razor"],"annotation":"razor","tags":["sharp","shave"],"emoji":"🪒","order":4432,"group":7,"version":12},{"shortcodes":["lotion_bottle"],"annotation":"lotion bottle","tags":["lotion","moisturizer","shampoo","sunscreen"],"emoji":"🧴","order":4433,"group":7,"version":11},{"shortcodes":["safety_pin"],"annotation":"safety pin","tags":["diaper","punk rock"],"emoji":"🧷","order":4434,"group":7,"version":11},{"shortcodes":["broom"],"annotation":"broom","tags":["cleaning","sweeping","witch"],"emoji":"🧹","order":4435,"group":7,"version":11},{"shortcodes":["basket"],"annotation":"basket","tags":["farming","laundry","picnic"],"emoji":"🧺","order":4436,"group":7,"version":11},{"shortcodes":["roll_of_paper","toilet_paper"],"annotation":"roll of paper","tags":["paper towels","toilet paper"],"emoji":"🧻","order":4437,"group":7,"version":11},{"shortcodes":["bucket"],"annotation":"bucket","tags":["cask","pail","vat"],"emoji":"🪣","order":4438,"group":7,"version":13},{"shortcodes":["soap"],"annotation":"soap","tags":["bar","bathing","cleaning","lather","soapdish"],"emoji":"🧼","order":4439,"group":7,"version":11},{"shortcodes":["bubbles"],"annotation":"bubbles","tags":["burp","clean","soap","underwater"],"emoji":"🫧","order":4440,"group":7,"version":14},{"shortcodes":["toothbrush"],"annotation":"toothbrush","tags":["bathroom","brush","clean","dental","hygiene","teeth"],"emoji":"🪥","order":4441,"group":7,"version":13},{"shortcodes":["sponge"],"annotation":"sponge","tags":["absorbing","cleaning","porous"],"emoji":"🧽","order":4442,"group":7,"version":11},{"shortcodes":["fire_extinguisher"],"annotation":"fire extinguisher","tags":["extinguish","fire","quench"],"emoji":"🧯","order":4443,"group":7,"version":11},{"shortcodes":["shopping_cart"],"annotation":"shopping cart","tags":["cart","shopping","trolley"],"emoji":"🛒","order":4444,"group":7,"version":3},{"shortcodes":["cigarette","smoking"],"annotation":"cigarette","tags":["smoking"],"emoji":"🚬","order":4445,"group":7,"version":0.6},{"shortcodes":["coffin"],"annotation":"coffin","tags":["death"],"emoji":"⚰️","order":4447,"group":7,"version":1},{"shortcodes":["headstone"],"annotation":"headstone","tags":["cemetery","grave","graveyard","tombstone"],"emoji":"🪦","order":4448,"group":7,"version":13},{"shortcodes":["funeral_urn"],"annotation":"funeral urn","tags":["ashes","death","funeral","urn"],"emoji":"⚱️","order":4450,"group":7,"version":1},{"shortcodes":["nazar_amulet"],"annotation":"nazar amulet","tags":["bead","charm","evil-eye","nazar","talisman"],"emoji":"🧿","order":4451,"group":7,"version":11},{"shortcodes":["hamsa"],"annotation":"hamsa","tags":["amulet","fatima","hand","mary","miriam","protection"],"emoji":"🪬","order":4452,"group":7,"version":14},{"shortcodes":["moai","moyai"],"annotation":"moai","tags":["face","moyai","statue"],"emoji":"🗿","order":4453,"group":7,"version":0.6},{"shortcodes":["placard"],"annotation":"placard","tags":["demonstration","picket","protest","sign"],"emoji":"🪧","order":4454,"group":7,"version":13},{"shortcodes":["id_card"],"annotation":"identification card","tags":["credentials","id","license","security"],"emoji":"🪪","order":4455,"group":7,"version":14},{"shortcodes":["atm"],"annotation":"ATM sign","tags":["atm","atm sign","automated","bank","teller"],"emoji":"🏧","order":4456,"group":8,"version":0.6},{"shortcodes":["litter_bin","put_litter_in_its_place"],"annotation":"litter in bin sign","tags":["litter","litter bin"],"emoji":"🚮","order":4457,"group":8,"version":1},{"shortcodes":["potable_water"],"annotation":"potable water","tags":["drinking","potable","water"],"emoji":"🚰","order":4458,"group":8,"version":1},{"shortcodes":["handicapped","wheelchair"],"annotation":"wheelchair symbol","tags":["access"],"emoji":"♿️","order":4459,"group":8,"version":0.6},{"shortcodes":["mens"],"annotation":"men’s room","tags":["bathroom","lavatory","man","restroom","toilet","wc"],"emoji":"🚹️","order":4460,"group":8,"version":0.6},{"shortcodes":["womens"],"annotation":"women’s room","tags":["bathroom","lavatory","restroom","toilet","wc","woman"],"emoji":"🚺️","order":4461,"group":8,"version":0.6},{"shortcodes":["bathroom","restroom"],"annotation":"restroom","tags":["bathroom","lavatory","toilet","wc"],"emoji":"🚻","order":4462,"group":8,"version":0.6},{"shortcodes":["baby_symbol"],"annotation":"baby symbol","tags":["baby","changing"],"emoji":"🚼️","order":4463,"group":8,"version":0.6},{"shortcodes":["water_closet","wc"],"annotation":"water closet","tags":["bathroom","closet","lavatory","restroom","toilet","water","wc"],"emoji":"🚾","order":4464,"group":8,"version":0.6},{"shortcodes":["passport_control"],"annotation":"passport control","tags":["control","passport"],"emoji":"🛂","order":4465,"group":8,"version":1},{"shortcodes":["customs"],"annotation":"customs","tags":["customs"],"emoji":"🛃","order":4466,"group":8,"version":1},{"shortcodes":["baggage_claim"],"annotation":"baggage claim","tags":["baggage","claim"],"emoji":"🛄","order":4467,"group":8,"version":1},{"shortcodes":["left_luggage"],"annotation":"left luggage","tags":["baggage","locker","luggage"],"emoji":"🛅","order":4468,"group":8,"version":1},{"shortcodes":["warning"],"annotation":"warning","tags":["warning"],"emoji":"⚠️","order":4470,"group":8,"version":0.6},{"shortcodes":["children_crossing"],"annotation":"children crossing","tags":["child","crossing","pedestrian","traffic"],"emoji":"🚸","order":4471,"group":8,"version":1},{"shortcodes":["no_entry"],"annotation":"no entry","tags":["entry","forbidden","no","not","prohibited","traffic"],"emoji":"⛔️","order":4472,"group":8,"version":0.6},{"shortcodes":["no_entry_sign"],"annotation":"prohibited","tags":["entry","forbidden","no","not"],"emoji":"🚫","order":4473,"group":8,"version":0.6},{"shortcodes":["no_bicycles"],"annotation":"no bicycles","tags":["bicycle","bike","forbidden","no","prohibited"],"emoji":"🚳","order":4474,"group":8,"version":1},{"shortcodes":["no_smoking"],"annotation":"no smoking","tags":["forbidden","no","not","prohibited","smoking"],"emoji":"🚭️","order":4475,"group":8,"version":0.6},{"shortcodes":["do_not_litter","no_littering"],"annotation":"no littering","tags":["forbidden","litter","no","not","prohibited"],"emoji":"🚯","order":4476,"group":8,"version":1},{"shortcodes":["non-potable_water"],"annotation":"non-potable water","tags":["non-drinking","non-potable","water"],"emoji":"🚱","order":4477,"group":8,"version":1},{"shortcodes":["no_pedestrians"],"annotation":"no pedestrians","tags":["forbidden","no","not","pedestrian","prohibited"],"emoji":"🚷","order":4478,"group":8,"version":1},{"shortcodes":["no_mobile_phones"],"annotation":"no mobile phones","tags":["cell","forbidden","mobile","no","phone"],"emoji":"📵","order":4479,"group":8,"version":1},{"shortcodes":["no_one_under_18","underage"],"annotation":"no one under eighteen","tags":["18","age restriction","eighteen","prohibited","underage"],"emoji":"🔞","order":4480,"group":8,"version":0.6},{"shortcodes":["radioactive"],"annotation":"radioactive","tags":["sign"],"emoji":"☢️","order":4482,"group":8,"version":1},{"shortcodes":["biohazard"],"annotation":"biohazard","tags":["sign"],"emoji":"☣️","order":4484,"group":8,"version":1},{"shortcodes":["arrow_up"],"annotation":"up arrow","tags":["arrow","cardinal","direction","north"],"emoji":"⬆️","order":4486,"group":8,"version":0.6},{"shortcodes":["arrow_upper_right"],"annotation":"up-right arrow","tags":["arrow","direction","intercardinal","northeast"],"emoji":"↗️","order":4488,"group":8,"version":0.6},{"shortcodes":["arrow_right"],"annotation":"right arrow","tags":["arrow","cardinal","direction","east"],"emoji":"➡️","order":4490,"group":8,"version":0.6},{"shortcodes":["arrow_lower_right"],"annotation":"down-right arrow","tags":["arrow","direction","intercardinal","southeast"],"emoji":"↘️","order":4492,"group":8,"version":0.6},{"shortcodes":["arrow_down"],"annotation":"down arrow","tags":["arrow","cardinal","direction","down","south"],"emoji":"⬇️","order":4494,"group":8,"version":0.6},{"shortcodes":["arrow_lower_left"],"annotation":"down-left arrow","tags":["arrow","direction","intercardinal","southwest"],"emoji":"↙️","order":4496,"group":8,"version":0.6},{"shortcodes":["arrow_left"],"annotation":"left arrow","tags":["arrow","cardinal","direction","west"],"emoji":"⬅️","order":4498,"group":8,"version":0.6},{"shortcodes":["arrow_upper_left"],"annotation":"up-left arrow","tags":["arrow","direction","intercardinal","northwest"],"emoji":"↖️","order":4500,"group":8,"version":0.6},{"shortcodes":["arrow_up_down"],"annotation":"up-down arrow","tags":["arrow"],"emoji":"↕️","order":4502,"group":8,"version":0.6},{"shortcodes":["left_right_arrow"],"annotation":"left-right arrow","tags":["arrow"],"emoji":"↔️","order":4504,"group":8,"version":0.6},{"shortcodes":["arrow_left_hook","leftwards_arrow_with_hook"],"annotation":"right arrow curving left","tags":["arrow"],"emoji":"↩️","order":4506,"group":8,"version":0.6},{"shortcodes":["arrow_right_hook","rightwards_arrow_with_hook"],"annotation":"left arrow curving right","tags":["arrow"],"emoji":"↪️","order":4508,"group":8,"version":0.6},{"shortcodes":["arrow_heading_up"],"annotation":"right arrow curving up","tags":["arrow"],"emoji":"⤴️","order":4510,"group":8,"version":0.6},{"shortcodes":["arrow_heading_down"],"annotation":"right arrow curving down","tags":["arrow","down"],"emoji":"⤵️","order":4512,"group":8,"version":0.6},{"shortcodes":["arrows_clockwise","clockwise"],"annotation":"clockwise vertical arrows","tags":["arrow","clockwise","reload"],"emoji":"🔃","order":4513,"group":8,"version":0.6},{"shortcodes":["arrows_counterclockwise","counterclockwise"],"annotation":"counterclockwise arrows button","tags":["anticlockwise","arrow","counterclockwise","withershins"],"emoji":"🔄","order":4514,"group":8,"version":1},{"shortcodes":["back"],"annotation":"BACK arrow","tags":["arrow","back"],"emoji":"🔙","order":4515,"group":8,"version":0.6},{"shortcodes":["end"],"annotation":"END arrow","tags":["arrow","end"],"emoji":"🔚","order":4516,"group":8,"version":0.6},{"shortcodes":["on"],"annotation":"ON! arrow","tags":["arrow","mark","on","on!"],"emoji":"🔛","order":4517,"group":8,"version":0.6},{"shortcodes":["soon"],"annotation":"SOON arrow","tags":["arrow","soon"],"emoji":"🔜","order":4518,"group":8,"version":0.6},{"shortcodes":["top"],"annotation":"TOP arrow","tags":["arrow","top","up"],"emoji":"🔝","order":4519,"group":8,"version":0.6},{"shortcodes":["place_of_worship"],"annotation":"place of worship","tags":["religion","worship"],"emoji":"🛐","order":4520,"group":8,"version":1},{"shortcodes":["atom","atom_symbol"],"annotation":"atom symbol","tags":["atheist","atom"],"emoji":"⚛️","order":4522,"group":8,"version":1},{"shortcodes":["om"],"annotation":"om","tags":["hindu","religion"],"emoji":"🕉️","order":4524,"group":8,"version":0.7},{"shortcodes":["star_of_david"],"annotation":"star of David","tags":["david","jew","jewish","religion","star","star of david"],"emoji":"✡️","order":4526,"group":8,"version":0.7},{"shortcodes":["wheel_of_dharma"],"annotation":"wheel of dharma","tags":["buddhist","dharma","religion","wheel"],"emoji":"☸️","order":4528,"group":8,"version":0.7},{"shortcodes":["yin_yang"],"annotation":"yin yang","tags":["religion","tao","taoist","yang","yin"],"emoji":"☯️","order":4530,"group":8,"version":0.7},{"shortcodes":["latin_cross"],"annotation":"latin cross","tags":["christian","cross","religion"],"emoji":"✝️","order":4532,"group":8,"version":0.7},{"shortcodes":["orthodox_cross"],"annotation":"orthodox cross","tags":["christian","cross","religion"],"emoji":"☦️","order":4534,"group":8,"version":1},{"shortcodes":["star_and_crescent"],"annotation":"star and crescent","tags":["islam","muslim","religion"],"emoji":"☪️","order":4536,"group":8,"version":0.7},{"shortcodes":["peace","peace_symbol"],"annotation":"peace symbol","tags":["peace"],"emoji":"☮️","order":4538,"group":8,"version":1},{"shortcodes":["menorah"],"annotation":"menorah","tags":["candelabrum","candlestick","religion"],"emoji":"🕎","order":4539,"group":8,"version":1},{"shortcodes":["six_pointed_star"],"annotation":"dotted six-pointed star","tags":["fortune","star"],"emoji":"🔯","order":4540,"group":8,"version":0.6},{"shortcodes":["khanda"],"annotation":"khanda","tags":["religion","sikh"],"emoji":"🪯","order":4541,"group":8,"version":15},{"shortcodes":["aries"],"annotation":"Aries","tags":["aries","ram","zodiac"],"emoji":"♈️","order":4542,"group":8,"version":0.6},{"shortcodes":["taurus"],"annotation":"Taurus","tags":["bull","ox","taurus","zodiac"],"emoji":"♉️","order":4543,"group":8,"version":0.6},{"shortcodes":["gemini"],"annotation":"Gemini","tags":["gemini","twins","zodiac"],"emoji":"♊️","order":4544,"group":8,"version":0.6},{"shortcodes":["cancer"],"annotation":"Cancer","tags":["cancer","crab","zodiac"],"emoji":"♋️","order":4545,"group":8,"version":0.6},{"shortcodes":["leo"],"annotation":"Leo","tags":["leo","lion","zodiac"],"emoji":"♌️","order":4546,"group":8,"version":0.6},{"shortcodes":["virgo"],"annotation":"Virgo","tags":["virgo","zodiac"],"emoji":"♍️","order":4547,"group":8,"version":0.6},{"shortcodes":["libra"],"annotation":"Libra","tags":["balance","justice","libra","scales","zodiac"],"emoji":"♎️","order":4548,"group":8,"version":0.6},{"shortcodes":["scorpius"],"annotation":"Scorpio","tags":["scorpio","scorpion","scorpius","zodiac"],"emoji":"♏️","order":4549,"group":8,"version":0.6},{"shortcodes":["sagittarius"],"annotation":"Sagittarius","tags":["archer","sagittarius","zodiac"],"emoji":"♐️","order":4550,"group":8,"version":0.6},{"shortcodes":["capricorn"],"annotation":"Capricorn","tags":["capricorn","goat","zodiac"],"emoji":"♑️","order":4551,"group":8,"version":0.6},{"shortcodes":["aquarius"],"annotation":"Aquarius","tags":["aquarius","bearer","water","zodiac"],"emoji":"♒️","order":4552,"group":8,"version":0.6},{"shortcodes":["pisces"],"annotation":"Pisces","tags":["fish","pisces","zodiac"],"emoji":"♓️","order":4553,"group":8,"version":0.6},{"shortcodes":["ophiuchus"],"annotation":"Ophiuchus","tags":["bearer","ophiuchus","serpent","snake","zodiac"],"emoji":"⛎️","order":4554,"group":8,"version":0.6},{"shortcodes":["shuffle","twisted_rightwards_arrows"],"annotation":"shuffle tracks button","tags":["arrow","crossed"],"emoji":"🔀","order":4555,"group":8,"version":1},{"shortcodes":["repeat"],"annotation":"repeat button","tags":["arrow","clockwise","repeat"],"emoji":"🔁","order":4556,"group":8,"version":1},{"shortcodes":["repeat_one"],"annotation":"repeat single button","tags":["arrow","clockwise","once"],"emoji":"🔂","order":4557,"group":8,"version":1},{"shortcodes":["arrow_forward","play"],"annotation":"play button","tags":["arrow","play","right","triangle"],"emoji":"▶️","order":4559,"group":8,"version":0.6},{"shortcodes":["fast_forward"],"annotation":"fast-forward button","tags":["arrow","double","fast","forward"],"emoji":"⏩️","order":4560,"group":8,"version":0.6},{"shortcodes":["next_track"],"annotation":"next track button","tags":["arrow","next scene","next track","triangle"],"emoji":"⏭️","order":4562,"group":8,"version":0.7},{"shortcodes":["play_pause"],"annotation":"play or pause button","tags":["arrow","pause","play","right","triangle"],"emoji":"⏯️","order":4564,"group":8,"version":1},{"shortcodes":["arrow_backward","reverse"],"annotation":"reverse button","tags":["arrow","left","reverse","triangle"],"emoji":"◀️","order":4566,"group":8,"version":0.6},{"shortcodes":["fast_reverse","rewind"],"annotation":"fast reverse button","tags":["arrow","double","rewind"],"emoji":"⏪️","order":4567,"group":8,"version":0.6},{"shortcodes":["previous_track"],"annotation":"last track button","tags":["arrow","previous scene","previous track","triangle"],"emoji":"⏮️","order":4569,"group":8,"version":0.7},{"shortcodes":["arrow_up_small","up"],"annotation":"upwards button","tags":["arrow","button"],"emoji":"🔼","order":4570,"group":8,"version":0.6},{"shortcodes":["arrow_double_up","fast_up"],"annotation":"fast up button","tags":["arrow","double"],"emoji":"⏫️","order":4571,"group":8,"version":0.6},{"shortcodes":["arrow_down_small","down"],"annotation":"downwards button","tags":["arrow","button","down"],"emoji":"🔽","order":4572,"group":8,"version":0.6},{"shortcodes":["arrow_double_down","fast_down"],"annotation":"fast down button","tags":["arrow","double","down"],"emoji":"⏬️","order":4573,"group":8,"version":0.6},{"shortcodes":["pause"],"annotation":"pause button","tags":["bar","double","pause","vertical"],"emoji":"⏸️","order":4575,"group":8,"version":0.7},{"shortcodes":["stop"],"annotation":"stop button","tags":["square","stop"],"emoji":"⏹️","order":4577,"group":8,"version":0.7},{"shortcodes":["record"],"annotation":"record button","tags":["circle","record"],"emoji":"⏺️","order":4579,"group":8,"version":0.7},{"shortcodes":["eject"],"annotation":"eject button","tags":["eject"],"emoji":"⏏️","order":4581,"group":8,"version":1},{"shortcodes":["cinema"],"annotation":"cinema","tags":["camera","film","movie"],"emoji":"🎦","order":4582,"group":8,"version":0.6},{"shortcodes":["dim_button","low_brightness"],"annotation":"dim button","tags":["brightness","dim","low"],"emoji":"🔅","order":4583,"group":8,"version":1},{"shortcodes":["bright_button","high_brightness"],"annotation":"bright button","tags":["bright","brightness"],"emoji":"🔆","order":4584,"group":8,"version":1},{"shortcodes":["antenna_bars","signal_strength"],"annotation":"antenna bars","tags":["antenna","bar","cell","mobile","phone"],"emoji":"📶","order":4585,"group":8,"version":0.6},{"shortcodes":["wireless"],"annotation":"wireless","tags":["computer","internet","network","wi-fi","wifi"],"emoji":"🛜","order":4586,"group":8,"version":15},{"shortcodes":["vibration_mode"],"annotation":"vibration mode","tags":["cell","mobile","mode","phone","telephone","vibration"],"emoji":"📳","order":4587,"group":8,"version":0.6},{"shortcodes":["mobile_phone_off"],"annotation":"mobile phone off","tags":["cell","mobile","off","phone","telephone"],"emoji":"📴","order":4588,"group":8,"version":0.6},{"shortcodes":["female","female_sign"],"annotation":"female sign","tags":["woman"],"emoji":"♀️","order":4590,"group":8,"version":4},{"shortcodes":["male","male_sign"],"annotation":"male sign","tags":["man"],"emoji":"♂️","order":4592,"group":8,"version":4},{"shortcodes":["transgender_symbol"],"annotation":"transgender symbol","tags":["transgender"],"emoji":"⚧️","order":4594,"group":8,"version":13},{"shortcodes":["multiplication","multiply"],"annotation":"multiply","tags":["cancel","multiplication","sign","x","×"],"emoji":"✖️","order":4596,"group":8,"version":0.6},{"shortcodes":["plus"],"annotation":"plus","tags":["+","math","sign"],"emoji":"➕️","order":4597,"group":8,"version":0.6},{"shortcodes":["minus"],"annotation":"minus","tags":["-","math","sign","−"],"emoji":"➖️","order":4598,"group":8,"version":0.6},{"shortcodes":["divide","division"],"annotation":"divide","tags":["division","math","sign","÷"],"emoji":"➗️","order":4599,"group":8,"version":0.6},{"shortcodes":["heavy_equals_sign"],"annotation":"heavy equals sign","tags":["equality","math"],"emoji":"🟰","order":4600,"group":8,"version":14},{"shortcodes":["infinity"],"annotation":"infinity","tags":["forever","unbounded","universal"],"emoji":"♾️","order":4602,"group":8,"version":11},{"shortcodes":["bangbang","double_exclamation"],"annotation":"double exclamation mark","tags":["!","!!","bangbang","exclamation","mark"],"emoji":"‼️","order":4604,"group":8,"version":0.6},{"shortcodes":["exclamation_question","interrobang"],"annotation":"exclamation question mark","tags":["!","!?","?","exclamation","interrobang","mark","punctuation","question"],"emoji":"⁉️","order":4606,"group":8,"version":0.6},{"shortcodes":["question"],"annotation":"red question mark","tags":["?","mark","punctuation","question"],"emoji":"❓️","order":4607,"group":8,"version":0.6},{"shortcodes":["white_question"],"annotation":"white question mark","tags":["?","mark","outlined","punctuation","question"],"emoji":"❔️","order":4608,"group":8,"version":0.6},{"shortcodes":["white_exclamation"],"annotation":"white exclamation mark","tags":["!","exclamation","mark","outlined","punctuation"],"emoji":"❕️","order":4609,"group":8,"version":0.6},{"shortcodes":["exclamation"],"annotation":"red exclamation mark","tags":["!","exclamation","mark","punctuation"],"emoji":"❗️","order":4610,"group":8,"version":0.6},{"shortcodes":["wavy_dash"],"annotation":"wavy dash","tags":["dash","punctuation","wavy"],"emoji":"〰️","order":4612,"group":8,"version":0.6},{"shortcodes":["currency_exchange"],"annotation":"currency exchange","tags":["bank","currency","exchange","money"],"emoji":"💱","order":4613,"group":8,"version":0.6},{"shortcodes":["heavy_dollar_sign"],"annotation":"heavy dollar sign","tags":["currency","dollar","money"],"emoji":"💲","order":4614,"group":8,"version":0.6},{"shortcodes":["medical","medical_symbol"],"annotation":"medical symbol","tags":["aesculapius","medicine","staff"],"emoji":"⚕️","order":4616,"group":8,"version":4},{"shortcodes":["recycle","recycling_symbol"],"annotation":"recycling symbol","tags":["recycle"],"emoji":"♻️","order":4618,"group":8,"version":0.6},{"shortcodes":["fleur-de-lis"],"annotation":"fleur-de-lis","tags":["fleur-de-lis"],"emoji":"⚜️","order":4620,"group":8,"version":1},{"shortcodes":["trident"],"annotation":"trident emblem","tags":["anchor","emblem","ship","tool","trident"],"emoji":"🔱","order":4621,"group":8,"version":0.6},{"shortcodes":["name_badge"],"annotation":"name badge","tags":["badge","name"],"emoji":"📛","order":4622,"group":8,"version":0.6},{"shortcodes":["beginner"],"annotation":"Japanese symbol for beginner","tags":["beginner","chevron","japanese","japanese symbol for beginner","leaf"],"emoji":"🔰","order":4623,"group":8,"version":0.6},{"shortcodes":["hollow_red_circle","red_o"],"annotation":"hollow red circle","tags":["circle","large","o","red"],"emoji":"⭕️","order":4624,"group":8,"version":0.6},{"shortcodes":["check_mark_button","white_check_mark"],"annotation":"check mark button","tags":["button","check","mark","✓"],"emoji":"✅️","order":4625,"group":8,"version":0.6},{"shortcodes":["ballot_box_with_check"],"annotation":"check box with check","tags":["box","check","✓"],"emoji":"☑️","order":4627,"group":8,"version":0.6},{"shortcodes":["check_mark","heavy_check_mark"],"annotation":"check mark","tags":["check","mark","✓"],"emoji":"✔️","order":4629,"group":8,"version":0.6},{"shortcodes":["cross_mark","x"],"annotation":"cross mark","tags":["cancel","cross","mark","multiplication","multiply","x","×"],"emoji":"❌️","order":4630,"group":8,"version":0.6},{"shortcodes":["cross_mark_button","negative_squared_cross_mark"],"annotation":"cross mark button","tags":["mark","square","x","×"],"emoji":"❎️","order":4631,"group":8,"version":0.6},{"shortcodes":["curly_loop"],"annotation":"curly loop","tags":["curl","loop"],"emoji":"➰️","order":4632,"group":8,"version":0.6},{"shortcodes":["double_curly_loop","loop"],"annotation":"double curly loop","tags":["curl","double","loop"],"emoji":"➿️","order":4633,"group":8,"version":1},{"shortcodes":["part_alternation_mark"],"annotation":"part alternation mark","tags":["mark","part"],"emoji":"〽️","order":4635,"group":8,"version":0.6},{"shortcodes":["eight_spoked_asterisk"],"annotation":"eight-spoked asterisk","tags":["*","asterisk"],"emoji":"✳️","order":4637,"group":8,"version":0.6},{"shortcodes":["eight_pointed_black_star"],"annotation":"eight-pointed star","tags":["*","star"],"emoji":"✴️","order":4639,"group":8,"version":0.6},{"shortcodes":["sparkle"],"annotation":"sparkle","tags":["*"],"emoji":"❇️","order":4641,"group":8,"version":0.6},{"shortcodes":["copyright"],"annotation":"copyright","tags":["c"],"emoji":"©️","order":4643,"group":8,"version":0.6},{"shortcodes":["registered"],"annotation":"registered","tags":["r"],"emoji":"®️","order":4645,"group":8,"version":0.6},{"shortcodes":["tm","trade_mark"],"annotation":"trade mark","tags":["mark","tm","trademark"],"emoji":"™️","order":4647,"group":8,"version":0.6},{"shortcodes":["hash","number_sign"],"annotation":"keycap: #","tags":["keycap"],"emoji":"#️⃣","order":4648,"group":8,"version":0.6},{"shortcodes":["asterisk"],"annotation":"keycap: *","tags":["keycap"],"emoji":"*️⃣","order":4650,"group":8,"version":2},{"shortcodes":["zero"],"annotation":"keycap: 0","tags":["keycap"],"emoji":"0️⃣","order":4652,"group":8,"version":0.6},{"shortcodes":["one"],"annotation":"keycap: 1","tags":["keycap"],"emoji":"1️⃣","order":4654,"group":8,"version":0.6},{"shortcodes":["two"],"annotation":"keycap: 2","tags":["keycap"],"emoji":"2️⃣","order":4656,"group":8,"version":0.6},{"shortcodes":["three"],"annotation":"keycap: 3","tags":["keycap"],"emoji":"3️⃣","order":4658,"group":8,"version":0.6},{"shortcodes":["four"],"annotation":"keycap: 4","tags":["keycap"],"emoji":"4️⃣","order":4660,"group":8,"version":0.6},{"shortcodes":["five"],"annotation":"keycap: 5","tags":["keycap"],"emoji":"5️⃣","order":4662,"group":8,"version":0.6},{"shortcodes":["six"],"annotation":"keycap: 6","tags":["keycap"],"emoji":"6️⃣","order":4664,"group":8,"version":0.6},{"shortcodes":["seven"],"annotation":"keycap: 7","tags":["keycap"],"emoji":"7️⃣","order":4666,"group":8,"version":0.6},{"shortcodes":["eight"],"annotation":"keycap: 8","tags":["keycap"],"emoji":"8️⃣","order":4668,"group":8,"version":0.6},{"shortcodes":["nine"],"annotation":"keycap: 9","tags":["keycap"],"emoji":"9️⃣","order":4670,"group":8,"version":0.6},{"shortcodes":["ten"],"annotation":"keycap: 10","tags":["keycap"],"emoji":"🔟","order":4672,"group":8,"version":0.6},{"shortcodes":["capital_abcd"],"annotation":"input latin uppercase","tags":["abcd","input","latin","letters","uppercase"],"emoji":"🔠","order":4673,"group":8,"version":0.6},{"shortcodes":["abcd"],"annotation":"input latin lowercase","tags":["abcd","input","latin","letters","lowercase"],"emoji":"🔡","order":4674,"group":8,"version":0.6},{"shortcodes":["1234"],"annotation":"input numbers","tags":["1234","input","numbers"],"emoji":"🔢","order":4675,"group":8,"version":0.6},{"shortcodes":["symbols"],"annotation":"input symbols","tags":["input","〒♪&%"],"emoji":"🔣","order":4676,"group":8,"version":0.6},{"shortcodes":["abc"],"annotation":"input latin letters","tags":["abc","alphabet","input","latin","letters"],"emoji":"🔤","order":4677,"group":8,"version":0.6},{"shortcodes":["a","a_blood"],"annotation":"A button (blood type)","tags":["a","a button (blood type)","blood type"],"emoji":"🅰️","order":4679,"group":8,"version":0.6},{"shortcodes":["ab","ab_blood"],"annotation":"AB button (blood type)","tags":["ab","ab button (blood type)","blood type"],"emoji":"🆎","order":4680,"group":8,"version":0.6},{"shortcodes":["b","b_blood"],"annotation":"B button (blood type)","tags":["b","b button (blood type)","blood type"],"emoji":"🅱️","order":4682,"group":8,"version":0.6},{"shortcodes":["cl"],"annotation":"CL button","tags":["cl","cl button"],"emoji":"🆑","order":4683,"group":8,"version":0.6},{"shortcodes":["cool"],"annotation":"COOL button","tags":["cool","cool button"],"emoji":"🆒","order":4684,"group":8,"version":0.6},{"shortcodes":["free"],"annotation":"FREE button","tags":["free","free button"],"emoji":"🆓","order":4685,"group":8,"version":0.6},{"shortcodes":["info","information_source"],"annotation":"information","tags":["i"],"emoji":"ℹ️","order":4687,"group":8,"version":0.6},{"shortcodes":["id"],"annotation":"ID button","tags":["id","id button","identity"],"emoji":"🆔","order":4688,"group":8,"version":0.6},{"shortcodes":["m"],"annotation":"circled M","tags":["circle","circled m","m"],"emoji":"Ⓜ️","order":4690,"group":8,"version":0.6},{"shortcodes":["new"],"annotation":"NEW button","tags":["new","new button"],"emoji":"🆕","order":4691,"group":8,"version":0.6},{"shortcodes":["ng"],"annotation":"NG button","tags":["ng","ng button"],"emoji":"🆖","order":4692,"group":8,"version":0.6},{"shortcodes":["o","o_blood"],"annotation":"O button (blood type)","tags":["blood type","o","o button (blood type)"],"emoji":"🅾️","order":4694,"group":8,"version":0.6},{"shortcodes":["ok"],"annotation":"OK button","tags":["ok","ok button"],"emoji":"🆗","order":4695,"group":8,"version":0.6},{"shortcodes":["parking"],"annotation":"P button","tags":["p","p button","parking"],"emoji":"🅿️","order":4697,"group":8,"version":0.6},{"shortcodes":["sos"],"annotation":"SOS button","tags":["help","sos","sos button"],"emoji":"🆘","order":4698,"group":8,"version":0.6},{"shortcodes":["up2"],"annotation":"UP! button","tags":["mark","up","up!","up! button"],"emoji":"🆙","order":4699,"group":8,"version":0.6},{"shortcodes":["vs"],"annotation":"VS button","tags":["versus","vs","vs button"],"emoji":"🆚","order":4700,"group":8,"version":0.6},{"shortcodes":["ja_here","koko"],"annotation":"Japanese “here” button","tags":["japanese","japanese “here” button","katakana","“here”","ココ"],"emoji":"🈁","order":4701,"group":8,"version":0.6},{"shortcodes":["ja_service_charge"],"annotation":"Japanese “service charge” button","tags":["japanese","japanese “service charge” button","katakana","“service charge”","サ"],"emoji":"🈂️","order":4703,"group":8,"version":0.6},{"shortcodes":["ja_monthly_amount"],"annotation":"Japanese “monthly amount” button","tags":["ideograph","japanese","japanese “monthly amount” button","“monthly amount”","月"],"emoji":"🈷️","order":4705,"group":8,"version":0.6},{"shortcodes":["ja_not_free_of_carge"],"annotation":"Japanese “not free of charge” button","tags":["ideograph","japanese","japanese “not free of charge” button","“not free of charge”","有"],"emoji":"🈶","order":4706,"group":8,"version":0.6},{"shortcodes":["ja_reserved"],"annotation":"Japanese “reserved” button","tags":["ideograph","japanese","japanese “reserved” button","“reserved”","指"],"emoji":"🈯️","order":4707,"group":8,"version":0.6},{"shortcodes":["ideograph_advantage","ja_bargain"],"annotation":"Japanese “bargain” button","tags":["ideograph","japanese","japanese “bargain” button","“bargain”","得"],"emoji":"🉐","order":4708,"group":8,"version":0.6},{"shortcodes":["ja_discount"],"annotation":"Japanese “discount” button","tags":["ideograph","japanese","japanese “discount” button","“discount”","割"],"emoji":"🈹","order":4709,"group":8,"version":0.6},{"shortcodes":["ja_free_of_charge"],"annotation":"Japanese “free of charge” button","tags":["ideograph","japanese","japanese “free of charge” button","“free of charge”","無"],"emoji":"🈚️","order":4710,"group":8,"version":0.6},{"shortcodes":["ja_prohibited"],"annotation":"Japanese “prohibited” button","tags":["ideograph","japanese","japanese “prohibited” button","“prohibited”","禁"],"emoji":"🈲","order":4711,"group":8,"version":0.6},{"shortcodes":["accept","ja_acceptable"],"annotation":"Japanese “acceptable” button","tags":["ideograph","japanese","japanese “acceptable” button","“acceptable”","可"],"emoji":"🉑","order":4712,"group":8,"version":0.6},{"shortcodes":["ja_application"],"annotation":"Japanese “application” button","tags":["ideograph","japanese","japanese “application” button","“application”","申"],"emoji":"🈸","order":4713,"group":8,"version":0.6},{"shortcodes":["ja_passing_grade"],"annotation":"Japanese “passing grade” button","tags":["ideograph","japanese","japanese “passing grade” button","“passing grade”","合"],"emoji":"🈴","order":4714,"group":8,"version":0.6},{"shortcodes":["ja_vacancy"],"annotation":"Japanese “vacancy” button","tags":["ideograph","japanese","japanese “vacancy” button","“vacancy”","空"],"emoji":"🈳","order":4715,"group":8,"version":0.6},{"shortcodes":["congratulations","ja_congratulations"],"annotation":"Japanese “congratulations” button","tags":["ideograph","japanese","japanese “congratulations” button","“congratulations”","祝"],"emoji":"㊗️","order":4717,"group":8,"version":0.6},{"shortcodes":["ja_secret","secret"],"annotation":"Japanese “secret” button","tags":["ideograph","japanese","japanese “secret” button","“secret”","秘"],"emoji":"㊙️","order":4719,"group":8,"version":0.6},{"shortcodes":["ja_open_for_business"],"annotation":"Japanese “open for business” button","tags":["ideograph","japanese","japanese “open for business” button","“open for business”","営"],"emoji":"🈺","order":4720,"group":8,"version":0.6},{"shortcodes":["ja_no_vacancy"],"annotation":"Japanese “no vacancy” button","tags":["ideograph","japanese","japanese “no vacancy” button","“no vacancy”","満"],"emoji":"🈵","order":4721,"group":8,"version":0.6},{"shortcodes":["red_circle"],"annotation":"red circle","tags":["circle","geometric","red"],"emoji":"🔴","order":4722,"group":8,"version":0.6},{"shortcodes":["orange_circle"],"annotation":"orange circle","tags":["circle","orange"],"emoji":"🟠","order":4723,"group":8,"version":12},{"shortcodes":["yellow_circle"],"annotation":"yellow circle","tags":["circle","yellow"],"emoji":"🟡","order":4724,"group":8,"version":12},{"shortcodes":["green_circle"],"annotation":"green circle","tags":["circle","green"],"emoji":"🟢","order":4725,"group":8,"version":12},{"shortcodes":["blue_circle"],"annotation":"blue circle","tags":["blue","circle","geometric"],"emoji":"🔵","order":4726,"group":8,"version":0.6},{"shortcodes":["purple_circle"],"annotation":"purple circle","tags":["circle","purple"],"emoji":"🟣","order":4727,"group":8,"version":12},{"shortcodes":["brown_circle"],"annotation":"brown circle","tags":["brown","circle"],"emoji":"🟤","order":4728,"group":8,"version":12},{"shortcodes":["black_circle"],"annotation":"black circle","tags":["circle","geometric"],"emoji":"⚫️","order":4729,"group":8,"version":0.6},{"shortcodes":["white_circle"],"annotation":"white circle","tags":["circle","geometric"],"emoji":"⚪️","order":4730,"group":8,"version":0.6},{"shortcodes":["red_square"],"annotation":"red square","tags":["red","square"],"emoji":"🟥","order":4731,"group":8,"version":12},{"shortcodes":["orange_square"],"annotation":"orange square","tags":["orange","square"],"emoji":"🟧","order":4732,"group":8,"version":12},{"shortcodes":["yellow_square"],"annotation":"yellow square","tags":["square","yellow"],"emoji":"🟨","order":4733,"group":8,"version":12},{"shortcodes":["green_square"],"annotation":"green square","tags":["green","square"],"emoji":"🟩","order":4734,"group":8,"version":12},{"shortcodes":["blue_square"],"annotation":"blue square","tags":["blue","square"],"emoji":"🟦","order":4735,"group":8,"version":12},{"shortcodes":["purple_square"],"annotation":"purple square","tags":["purple","square"],"emoji":"🟪","order":4736,"group":8,"version":12},{"shortcodes":["brown_square"],"annotation":"brown square","tags":["brown","square"],"emoji":"🟫","order":4737,"group":8,"version":12},{"shortcodes":["black_large_square"],"annotation":"black large square","tags":["geometric","square"],"emoji":"⬛️","order":4738,"group":8,"version":0.6},{"shortcodes":["white_large_square"],"annotation":"white large square","tags":["geometric","square"],"emoji":"⬜️","order":4739,"group":8,"version":0.6},{"shortcodes":["black_medium_square"],"annotation":"black medium square","tags":["geometric","square"],"emoji":"◼️","order":4741,"group":8,"version":0.6},{"shortcodes":["white_medium_square"],"annotation":"white medium square","tags":["geometric","square"],"emoji":"◻️","order":4743,"group":8,"version":0.6},{"shortcodes":["black_medium_small_square"],"annotation":"black medium-small square","tags":["geometric","square"],"emoji":"◾️","order":4744,"group":8,"version":0.6},{"shortcodes":["white_medium_small_square"],"annotation":"white medium-small square","tags":["geometric","square"],"emoji":"◽️","order":4745,"group":8,"version":0.6},{"shortcodes":["black_small_square"],"annotation":"black small square","tags":["geometric","square"],"emoji":"▪️","order":4747,"group":8,"version":0.6},{"shortcodes":["white_small_square"],"annotation":"white small square","tags":["geometric","square"],"emoji":"▫️","order":4749,"group":8,"version":0.6},{"shortcodes":["large_orange_diamond"],"annotation":"large orange diamond","tags":["diamond","geometric","orange"],"emoji":"🔶","order":4750,"group":8,"version":0.6},{"shortcodes":["large_blue_diamond"],"annotation":"large blue diamond","tags":["blue","diamond","geometric"],"emoji":"🔷","order":4751,"group":8,"version":0.6},{"shortcodes":["small_orange_diamond"],"annotation":"small orange diamond","tags":["diamond","geometric","orange"],"emoji":"🔸","order":4752,"group":8,"version":0.6},{"shortcodes":["small_blue_diamond"],"annotation":"small blue diamond","tags":["blue","diamond","geometric"],"emoji":"🔹","order":4753,"group":8,"version":0.6},{"shortcodes":["small_red_triangle"],"annotation":"red triangle pointed up","tags":["geometric","red"],"emoji":"🔺","order":4754,"group":8,"version":0.6},{"shortcodes":["small_red_triangle_down"],"annotation":"red triangle pointed down","tags":["down","geometric","red"],"emoji":"🔻","order":4755,"group":8,"version":0.6},{"shortcodes":["diamond_shape_with_a_dot_inside","diamond_with_a_dot"],"annotation":"diamond with a dot","tags":["comic","diamond","geometric","inside"],"emoji":"💠","order":4756,"group":8,"version":0.6},{"shortcodes":["radio_button"],"annotation":"radio button","tags":["button","geometric","radio"],"emoji":"🔘","order":4757,"group":8,"version":0.6},{"shortcodes":["white_square_button"],"annotation":"white square button","tags":["button","geometric","outlined","square"],"emoji":"🔳","order":4758,"group":8,"version":0.6},{"shortcodes":["black_square_button"],"annotation":"black square button","tags":["button","geometric","square"],"emoji":"🔲","order":4759,"group":8,"version":0.6},{"shortcodes":["checkered_flag"],"annotation":"chequered flag","tags":["checkered","chequered","racing"],"emoji":"🏁","order":4760,"group":9,"version":0.6},{"shortcodes":["triangular_flag","triangular_flag_on_post"],"annotation":"triangular flag","tags":["post"],"emoji":"🚩","order":4761,"group":9,"version":0.6},{"shortcodes":["crossed_flags"],"annotation":"crossed flags","tags":["celebration","cross","crossed","japanese"],"emoji":"🎌","order":4762,"group":9,"version":0.6},{"shortcodes":["black_flag"],"annotation":"black flag","tags":["waving"],"emoji":"🏴","order":4763,"group":9,"version":1},{"shortcodes":["white_flag"],"annotation":"white flag","tags":["waving"],"emoji":"🏳️","order":4765,"group":9,"version":0.7},{"shortcodes":["rainbow_flag"],"annotation":"rainbow flag","tags":["pride","rainbow"],"emoji":"🏳️🌈","order":4766,"group":9,"version":4},{"shortcodes":["transgender_flag"],"annotation":"transgender flag","tags":["flag","light blue","pink","transgender","white"],"emoji":"🏳️⚧️","order":4768,"group":9,"version":13},{"shortcodes":["jolly_roger","pirate_flag"],"annotation":"pirate flag","tags":["jolly roger","pirate","plunder","treasure"],"emoji":"🏴☠️","order":4772,"group":9,"version":11},{"shortcodes":["ascension_island","flag_ac"],"annotation":"flag: Ascension Island","tags":["AC","flag","flag: ascension island"],"emoji":"🇦🇨","order":4774,"group":9,"version":2},{"shortcodes":["andorra","flag_ad"],"annotation":"flag: Andorra","tags":["AD","flag","flag: andorra"],"emoji":"🇦🇩","order":4775,"group":9,"version":2},{"shortcodes":["flag_ae","united_arab_emirates"],"annotation":"flag: United Arab Emirates","tags":["AE","flag","flag: united arab emirates"],"emoji":"🇦🇪","order":4776,"group":9,"version":2},{"shortcodes":["afghanistan","flag_af"],"annotation":"flag: Afghanistan","tags":["AF","flag","flag: afghanistan"],"emoji":"🇦🇫","order":4777,"group":9,"version":2},{"shortcodes":["antigua_barbuda","flag_ag"],"annotation":"flag: Antigua & Barbuda","tags":["AG","flag","flag: antigua & barbuda"],"emoji":"🇦🇬","order":4778,"group":9,"version":2},{"shortcodes":["anguilla","flag_ai"],"annotation":"flag: Anguilla","tags":["AI","flag","flag: anguilla"],"emoji":"🇦🇮","order":4779,"group":9,"version":2},{"shortcodes":["albania","flag_al"],"annotation":"flag: Albania","tags":["AL","flag","flag: albania"],"emoji":"🇦🇱","order":4780,"group":9,"version":2},{"shortcodes":["armenia","flag_am"],"annotation":"flag: Armenia","tags":["AM","flag","flag: armenia"],"emoji":"🇦🇲","order":4781,"group":9,"version":2},{"shortcodes":["angola","flag_ao"],"annotation":"flag: Angola","tags":["AO","flag","flag: angola"],"emoji":"🇦🇴","order":4782,"group":9,"version":2},{"shortcodes":["antarctica","flag_aq"],"annotation":"flag: Antarctica","tags":["AQ","flag","flag: antarctica"],"emoji":"🇦🇶","order":4783,"group":9,"version":2},{"shortcodes":["argentina","flag_ar"],"annotation":"flag: Argentina","tags":["AR","flag","flag: argentina"],"emoji":"🇦🇷","order":4784,"group":9,"version":2},{"shortcodes":["american_samoa","flag_as"],"annotation":"flag: American Samoa","tags":["AS","flag","flag: american samoa"],"emoji":"🇦🇸","order":4785,"group":9,"version":2},{"shortcodes":["austria","flag_at"],"annotation":"flag: Austria","tags":["AT","flag","flag: austria"],"emoji":"🇦🇹","order":4786,"group":9,"version":2},{"shortcodes":["australia","flag_au"],"annotation":"flag: Australia","tags":["AU","flag","flag: australia"],"emoji":"🇦🇺","order":4787,"group":9,"version":2},{"shortcodes":["aruba","flag_aw"],"annotation":"flag: Aruba","tags":["AW","flag","flag: aruba"],"emoji":"🇦🇼","order":4788,"group":9,"version":2},{"shortcodes":["aland_islands","flag_ax"],"annotation":"flag: Åland Islands","tags":["AX","flag","flag: åland islands"],"emoji":"🇦🇽","order":4789,"group":9,"version":2},{"shortcodes":["azerbaijan","flag_az"],"annotation":"flag: Azerbaijan","tags":["AZ","flag","flag: azerbaijan"],"emoji":"🇦🇿","order":4790,"group":9,"version":2},{"shortcodes":["bosnia_herzegovina","flag_ba"],"annotation":"flag: Bosnia & Herzegovina","tags":["BA","flag","flag: bosnia & herzegovina"],"emoji":"🇧🇦","order":4791,"group":9,"version":2},{"shortcodes":["barbados","flag_bb"],"annotation":"flag: Barbados","tags":["BB","flag","flag: barbados"],"emoji":"🇧🇧","order":4792,"group":9,"version":2},{"shortcodes":["bangladesh","flag_bd"],"annotation":"flag: Bangladesh","tags":["BD","flag","flag: bangladesh"],"emoji":"🇧🇩","order":4793,"group":9,"version":2},{"shortcodes":["belgium","flag_be"],"annotation":"flag: Belgium","tags":["BE","flag","flag: belgium"],"emoji":"🇧🇪","order":4794,"group":9,"version":2},{"shortcodes":["burkina_faso","flag_bf"],"annotation":"flag: Burkina Faso","tags":["BF","flag","flag: burkina faso"],"emoji":"🇧🇫","order":4795,"group":9,"version":2},{"shortcodes":["bulgaria","flag_bg"],"annotation":"flag: Bulgaria","tags":["BG","flag","flag: bulgaria"],"emoji":"🇧🇬","order":4796,"group":9,"version":2},{"shortcodes":["bahrain","flag_bh"],"annotation":"flag: Bahrain","tags":["BH","flag","flag: bahrain"],"emoji":"🇧🇭","order":4797,"group":9,"version":2},{"shortcodes":["burundi","flag_bi"],"annotation":"flag: Burundi","tags":["BI","flag","flag: burundi"],"emoji":"🇧🇮","order":4798,"group":9,"version":2},{"shortcodes":["benin","flag_bj"],"annotation":"flag: Benin","tags":["BJ","flag","flag: benin"],"emoji":"🇧🇯","order":4799,"group":9,"version":2},{"shortcodes":["flag_bl","st_barthelemy"],"annotation":"flag: St. Barthélemy","tags":["BL","flag","flag: st. barthélemy"],"emoji":"🇧🇱","order":4800,"group":9,"version":2},{"shortcodes":["bermuda","flag_bm"],"annotation":"flag: Bermuda","tags":["BM","flag","flag: bermuda"],"emoji":"🇧🇲","order":4801,"group":9,"version":2},{"shortcodes":["brunei","flag_bn"],"annotation":"flag: Brunei","tags":["BN","flag","flag: brunei"],"emoji":"🇧🇳","order":4802,"group":9,"version":2},{"shortcodes":["bolivia","flag_bo"],"annotation":"flag: Bolivia","tags":["BO","flag","flag: bolivia"],"emoji":"🇧🇴","order":4803,"group":9,"version":2},{"shortcodes":["caribbean_netherlands","flag_bq"],"annotation":"flag: Caribbean Netherlands","tags":["BQ","flag","flag: caribbean netherlands"],"emoji":"🇧🇶","order":4804,"group":9,"version":2},{"shortcodes":["brazil","flag_br"],"annotation":"flag: Brazil","tags":["BR","flag","flag: brazil"],"emoji":"🇧🇷","order":4805,"group":9,"version":2},{"shortcodes":["bahamas","flag_bs"],"annotation":"flag: Bahamas","tags":["BS","flag","flag: bahamas"],"emoji":"🇧🇸","order":4806,"group":9,"version":2},{"shortcodes":["bhutan","flag_bt"],"annotation":"flag: Bhutan","tags":["BT","flag","flag: bhutan"],"emoji":"🇧🇹","order":4807,"group":9,"version":2},{"shortcodes":["bouvet_island","flag_bv"],"annotation":"flag: Bouvet Island","tags":["BV","flag","flag: bouvet island"],"emoji":"🇧🇻","order":4808,"group":9,"version":2},{"shortcodes":["botswana","flag_bw"],"annotation":"flag: Botswana","tags":["BW","flag","flag: botswana"],"emoji":"🇧🇼","order":4809,"group":9,"version":2},{"shortcodes":["belarus","flag_by"],"annotation":"flag: Belarus","tags":["BY","flag","flag: belarus"],"emoji":"🇧🇾","order":4810,"group":9,"version":2},{"shortcodes":["belize","flag_bz"],"annotation":"flag: Belize","tags":["BZ","flag","flag: belize"],"emoji":"🇧🇿","order":4811,"group":9,"version":2},{"shortcodes":["canada","flag_ca"],"annotation":"flag: Canada","tags":["CA","flag","flag: canada"],"emoji":"🇨🇦","order":4812,"group":9,"version":2},{"shortcodes":["cocos_islands","flag_cc"],"annotation":"flag: Cocos (Keeling) Islands","tags":["CC","flag","flag: cocos (keeling) islands"],"emoji":"🇨🇨","order":4813,"group":9,"version":2},{"shortcodes":["congo_kinshasa","flag_cd"],"annotation":"flag: Congo - Kinshasa","tags":["CD","flag","flag: congo - kinshasa"],"emoji":"🇨🇩","order":4814,"group":9,"version":2},{"shortcodes":["central_african_republic","flag_cf"],"annotation":"flag: Central African Republic","tags":["CF","flag","flag: central african republic"],"emoji":"🇨🇫","order":4815,"group":9,"version":2},{"shortcodes":["congo_brazzaville","flag_cg"],"annotation":"flag: Congo - Brazzaville","tags":["CG","flag","flag: congo - brazzaville"],"emoji":"🇨🇬","order":4816,"group":9,"version":2},{"shortcodes":["flag_ch","switzerland"],"annotation":"flag: Switzerland","tags":["CH","flag","flag: switzerland"],"emoji":"🇨🇭","order":4817,"group":9,"version":2},{"shortcodes":["cote_divoire","flag_ci"],"annotation":"flag: Côte d’Ivoire","tags":["CI","flag","flag: côte d’ivoire"],"emoji":"🇨🇮","order":4818,"group":9,"version":2},{"shortcodes":["cook_islands","flag_ck"],"annotation":"flag: Cook Islands","tags":["CK","flag","flag: cook islands"],"emoji":"🇨🇰","order":4819,"group":9,"version":2},{"shortcodes":["chile","flag_cl"],"annotation":"flag: Chile","tags":["CL","flag","flag: chile"],"emoji":"🇨🇱","order":4820,"group":9,"version":2},{"shortcodes":["cameroon","flag_cm"],"annotation":"flag: Cameroon","tags":["CM","flag","flag: cameroon"],"emoji":"🇨🇲","order":4821,"group":9,"version":2},{"shortcodes":["china","flag_cn"],"annotation":"flag: China","tags":["CN","flag","flag: china"],"emoji":"🇨🇳","order":4822,"group":9,"version":0.6},{"shortcodes":["colombia","flag_co"],"annotation":"flag: Colombia","tags":["CO","flag","flag: colombia"],"emoji":"🇨🇴","order":4823,"group":9,"version":2},{"shortcodes":["clipperton_island","flag_cp"],"annotation":"flag: Clipperton Island","tags":["CP","flag","flag: clipperton island"],"emoji":"🇨🇵","order":4824,"group":9,"version":2},{"shortcodes":["costa_rica","flag_cr"],"annotation":"flag: Costa Rica","tags":["CR","flag","flag: costa rica"],"emoji":"🇨🇷","order":4825,"group":9,"version":2},{"shortcodes":["cuba","flag_cu"],"annotation":"flag: Cuba","tags":["CU","flag","flag: cuba"],"emoji":"🇨🇺","order":4826,"group":9,"version":2},{"shortcodes":["cape_verde","flag_cv"],"annotation":"flag: Cape Verde","tags":["CV","flag","flag: cape verde"],"emoji":"🇨🇻","order":4827,"group":9,"version":2},{"shortcodes":["curacao","flag_cw"],"annotation":"flag: Curaçao","tags":["CW","flag","flag: curaçao"],"emoji":"🇨🇼","order":4828,"group":9,"version":2},{"shortcodes":["christmas_island","flag_cx"],"annotation":"flag: Christmas Island","tags":["CX","flag","flag: christmas island"],"emoji":"🇨🇽","order":4829,"group":9,"version":2},{"shortcodes":["cyprus","flag_cy"],"annotation":"flag: Cyprus","tags":["CY","flag","flag: cyprus"],"emoji":"🇨🇾","order":4830,"group":9,"version":2},{"shortcodes":["czech_republic","czechia","flag_cz"],"annotation":"flag: Czechia","tags":["CZ","flag","flag: czechia"],"emoji":"🇨🇿","order":4831,"group":9,"version":2},{"shortcodes":["flag_de","germany"],"annotation":"flag: Germany","tags":["DE","flag","flag: germany"],"emoji":"🇩🇪","order":4832,"group":9,"version":0.6},{"shortcodes":["diego_garcia","flag_dg"],"annotation":"flag: Diego Garcia","tags":["DG","flag","flag: diego garcia"],"emoji":"🇩🇬","order":4833,"group":9,"version":2},{"shortcodes":["djibouti","flag_dj"],"annotation":"flag: Djibouti","tags":["DJ","flag","flag: djibouti"],"emoji":"🇩🇯","order":4834,"group":9,"version":2},{"shortcodes":["denmark","flag_dk"],"annotation":"flag: Denmark","tags":["DK","flag","flag: denmark"],"emoji":"🇩🇰","order":4835,"group":9,"version":2},{"shortcodes":["dominica","flag_dm"],"annotation":"flag: Dominica","tags":["DM","flag","flag: dominica"],"emoji":"🇩🇲","order":4836,"group":9,"version":2},{"shortcodes":["dominican_republic","flag_do"],"annotation":"flag: Dominican Republic","tags":["DO","flag","flag: dominican republic"],"emoji":"🇩🇴","order":4837,"group":9,"version":2},{"shortcodes":["algeria","flag_dz"],"annotation":"flag: Algeria","tags":["DZ","flag","flag: algeria"],"emoji":"🇩🇿","order":4838,"group":9,"version":2},{"shortcodes":["ceuta_melilla","flag_ea"],"annotation":"flag: Ceuta & Melilla","tags":["EA","flag","flag: ceuta & melilla"],"emoji":"🇪🇦","order":4839,"group":9,"version":2},{"shortcodes":["ecuador","flag_ec"],"annotation":"flag: Ecuador","tags":["EC","flag","flag: ecuador"],"emoji":"🇪🇨","order":4840,"group":9,"version":2},{"shortcodes":["estonia","flag_ee"],"annotation":"flag: Estonia","tags":["EE","flag","flag: estonia"],"emoji":"🇪🇪","order":4841,"group":9,"version":2},{"shortcodes":["egypt","flag_eg"],"annotation":"flag: Egypt","tags":["EG","flag","flag: egypt"],"emoji":"🇪🇬","order":4842,"group":9,"version":2},{"shortcodes":["flag_eh","western_sahara"],"annotation":"flag: Western Sahara","tags":["EH","flag","flag: western sahara"],"emoji":"🇪🇭","order":4843,"group":9,"version":2},{"shortcodes":["eritrea","flag_er"],"annotation":"flag: Eritrea","tags":["ER","flag","flag: eritrea"],"emoji":"🇪🇷","order":4844,"group":9,"version":2},{"shortcodes":["flag_es","spain"],"annotation":"flag: Spain","tags":["ES","flag","flag: spain"],"emoji":"🇪🇸","order":4845,"group":9,"version":0.6},{"shortcodes":["ethiopia","flag_et"],"annotation":"flag: Ethiopia","tags":["ET","flag","flag: ethiopia"],"emoji":"🇪🇹","order":4846,"group":9,"version":2},{"shortcodes":["european_union","flag_eu"],"annotation":"flag: European Union","tags":["EU","flag","flag: european union"],"emoji":"🇪🇺","order":4847,"group":9,"version":2},{"shortcodes":["finland","flag_fi"],"annotation":"flag: Finland","tags":["FI","flag","flag: finland"],"emoji":"🇫🇮","order":4848,"group":9,"version":2},{"shortcodes":["fiji","flag_fj"],"annotation":"flag: Fiji","tags":["FJ","flag","flag: fiji"],"emoji":"🇫🇯","order":4849,"group":9,"version":2},{"shortcodes":["falkland_islands","flag_fk"],"annotation":"flag: Falkland Islands","tags":["FK","flag","flag: falkland islands"],"emoji":"🇫🇰","order":4850,"group":9,"version":2},{"shortcodes":["flag_fm","micronesia"],"annotation":"flag: Micronesia","tags":["FM","flag","flag: micronesia"],"emoji":"🇫🇲","order":4851,"group":9,"version":2},{"shortcodes":["faroe_islands","flag_fo"],"annotation":"flag: Faroe Islands","tags":["FO","flag","flag: faroe islands"],"emoji":"🇫🇴","order":4852,"group":9,"version":2},{"shortcodes":["flag_fr","france"],"annotation":"flag: France","tags":["FR","flag","flag: france"],"emoji":"🇫🇷","order":4853,"group":9,"version":0.6},{"shortcodes":["flag_ga","gabon"],"annotation":"flag: Gabon","tags":["GA","flag","flag: gabon"],"emoji":"🇬🇦","order":4854,"group":9,"version":2},{"shortcodes":["flag_gb","uk","united_kingdom"],"annotation":"flag: United Kingdom","tags":["GB","flag","flag: united kingdom"],"emoji":"🇬🇧","order":4855,"group":9,"version":0.6},{"shortcodes":["flag_gd","grenada"],"annotation":"flag: Grenada","tags":["GD","flag","flag: grenada"],"emoji":"🇬🇩","order":4856,"group":9,"version":2},{"shortcodes":["flag_ge","georgia"],"annotation":"flag: Georgia","tags":["GE","flag","flag: georgia"],"emoji":"🇬🇪","order":4857,"group":9,"version":2},{"shortcodes":["flag_gf","french_guiana"],"annotation":"flag: French Guiana","tags":["GF","flag","flag: french guiana"],"emoji":"🇬🇫","order":4858,"group":9,"version":2},{"shortcodes":["flag_gg","guernsey"],"annotation":"flag: Guernsey","tags":["GG","flag","flag: guernsey"],"emoji":"🇬🇬","order":4859,"group":9,"version":2},{"shortcodes":["flag_gh","ghana"],"annotation":"flag: Ghana","tags":["GH","flag","flag: ghana"],"emoji":"🇬🇭","order":4860,"group":9,"version":2},{"shortcodes":["flag_gi","gibraltar"],"annotation":"flag: Gibraltar","tags":["GI","flag","flag: gibraltar"],"emoji":"🇬🇮","order":4861,"group":9,"version":2},{"shortcodes":["flag_gl","greenland"],"annotation":"flag: Greenland","tags":["GL","flag","flag: greenland"],"emoji":"🇬🇱","order":4862,"group":9,"version":2},{"shortcodes":["flag_gm","gambia"],"annotation":"flag: Gambia","tags":["GM","flag","flag: gambia"],"emoji":"🇬🇲","order":4863,"group":9,"version":2},{"shortcodes":["flag_gn","guinea"],"annotation":"flag: Guinea","tags":["GN","flag","flag: guinea"],"emoji":"🇬🇳","order":4864,"group":9,"version":2},{"shortcodes":["flag_gp","guadeloupe"],"annotation":"flag: Guadeloupe","tags":["GP","flag","flag: guadeloupe"],"emoji":"🇬🇵","order":4865,"group":9,"version":2},{"shortcodes":["equatorial_guinea","flag_gq"],"annotation":"flag: Equatorial Guinea","tags":["GQ","flag","flag: equatorial guinea"],"emoji":"🇬🇶","order":4866,"group":9,"version":2},{"shortcodes":["flag_gr","greece"],"annotation":"flag: Greece","tags":["GR","flag","flag: greece"],"emoji":"🇬🇷","order":4867,"group":9,"version":2},{"shortcodes":["flag_gs","south_georgia_south_sandwich_islands"],"annotation":"flag: South Georgia & South Sandwich Islands","tags":["GS","flag","flag: south georgia & south sandwich islands"],"emoji":"🇬🇸","order":4868,"group":9,"version":2},{"shortcodes":["flag_gt","guatemala"],"annotation":"flag: Guatemala","tags":["GT","flag","flag: guatemala"],"emoji":"🇬🇹","order":4869,"group":9,"version":2},{"shortcodes":["flag_gu","guam"],"annotation":"flag: Guam","tags":["GU","flag","flag: guam"],"emoji":"🇬🇺","order":4870,"group":9,"version":2},{"shortcodes":["flag_gw","guinea_bissau"],"annotation":"flag: Guinea-Bissau","tags":["GW","flag","flag: guinea-bissau"],"emoji":"🇬🇼","order":4871,"group":9,"version":2},{"shortcodes":["flag_gy","guyana"],"annotation":"flag: Guyana","tags":["GY","flag","flag: guyana"],"emoji":"🇬🇾","order":4872,"group":9,"version":2},{"shortcodes":["flag_hk","hong_kong"],"annotation":"flag: Hong Kong SAR China","tags":["HK","flag","flag: hong kong sar china"],"emoji":"🇭🇰","order":4873,"group":9,"version":2},{"shortcodes":["flag_hm","heard_mcdonald_islands"],"annotation":"flag: Heard & McDonald Islands","tags":["HM","flag","flag: heard & mcdonald islands"],"emoji":"🇭🇲","order":4874,"group":9,"version":2},{"shortcodes":["flag_hn","honduras"],"annotation":"flag: Honduras","tags":["HN","flag","flag: honduras"],"emoji":"🇭🇳","order":4875,"group":9,"version":2},{"shortcodes":["croatia","flag_hr"],"annotation":"flag: Croatia","tags":["HR","flag","flag: croatia"],"emoji":"🇭🇷","order":4876,"group":9,"version":2},{"shortcodes":["flag_ht","haiti"],"annotation":"flag: Haiti","tags":["HT","flag","flag: haiti"],"emoji":"🇭🇹","order":4877,"group":9,"version":2},{"shortcodes":["flag_hu","hungary"],"annotation":"flag: Hungary","tags":["HU","flag","flag: hungary"],"emoji":"🇭🇺","order":4878,"group":9,"version":2},{"shortcodes":["canary_islands","flag_ic"],"annotation":"flag: Canary Islands","tags":["IC","flag","flag: canary islands"],"emoji":"🇮🇨","order":4879,"group":9,"version":2},{"shortcodes":["flag_id","indonesia"],"annotation":"flag: Indonesia","tags":["ID","flag","flag: indonesia"],"emoji":"🇮🇩","order":4880,"group":9,"version":2},{"shortcodes":["flag_ie","ireland"],"annotation":"flag: Ireland","tags":["IE","flag","flag: ireland"],"emoji":"🇮🇪","order":4881,"group":9,"version":2},{"shortcodes":["flag_il","israel"],"annotation":"flag: Israel","tags":["IL","flag","flag: israel"],"emoji":"🇮🇱","order":4882,"group":9,"version":2},{"shortcodes":["flag_im","isle_of_man"],"annotation":"flag: Isle of Man","tags":["IM","flag","flag: isle of man"],"emoji":"🇮🇲","order":4883,"group":9,"version":2},{"shortcodes":["flag_in","india"],"annotation":"flag: India","tags":["IN","flag","flag: india"],"emoji":"🇮🇳","order":4884,"group":9,"version":2},{"shortcodes":["british_indian_ocean_territory","flag_io"],"annotation":"flag: British Indian Ocean Territory","tags":["IO","flag","flag: british indian ocean territory"],"emoji":"🇮🇴","order":4885,"group":9,"version":2},{"shortcodes":["flag_iq","iraq"],"annotation":"flag: Iraq","tags":["IQ","flag","flag: iraq"],"emoji":"🇮🇶","order":4886,"group":9,"version":2},{"shortcodes":["flag_ir","iran"],"annotation":"flag: Iran","tags":["IR","flag","flag: iran"],"emoji":"🇮🇷","order":4887,"group":9,"version":2},{"shortcodes":["flag_is","iceland"],"annotation":"flag: Iceland","tags":["IS","flag","flag: iceland"],"emoji":"🇮🇸","order":4888,"group":9,"version":2},{"shortcodes":["flag_it","italy"],"annotation":"flag: Italy","tags":["IT","flag","flag: italy"],"emoji":"🇮🇹","order":4889,"group":9,"version":0.6},{"shortcodes":["flag_je","jersey"],"annotation":"flag: Jersey","tags":["JE","flag","flag: jersey"],"emoji":"🇯🇪","order":4890,"group":9,"version":2},{"shortcodes":["flag_jm","jamaica"],"annotation":"flag: Jamaica","tags":["JM","flag","flag: jamaica"],"emoji":"🇯🇲","order":4891,"group":9,"version":2},{"shortcodes":["flag_jo","jordan"],"annotation":"flag: Jordan","tags":["JO","flag","flag: jordan"],"emoji":"🇯🇴","order":4892,"group":9,"version":2},{"shortcodes":["flag_jp","japan"],"annotation":"flag: Japan","tags":["JP","flag","flag: japan"],"emoji":"🇯🇵","order":4893,"group":9,"version":0.6},{"shortcodes":["flag_ke","kenya"],"annotation":"flag: Kenya","tags":["KE","flag","flag: kenya"],"emoji":"🇰🇪","order":4894,"group":9,"version":2},{"shortcodes":["flag_kg","kyrgyzstan"],"annotation":"flag: Kyrgyzstan","tags":["KG","flag","flag: kyrgyzstan"],"emoji":"🇰🇬","order":4895,"group":9,"version":2},{"shortcodes":["cambodia","flag_kh"],"annotation":"flag: Cambodia","tags":["KH","flag","flag: cambodia"],"emoji":"🇰🇭","order":4896,"group":9,"version":2},{"shortcodes":["flag_ki","kiribati"],"annotation":"flag: Kiribati","tags":["KI","flag","flag: kiribati"],"emoji":"🇰🇮","order":4897,"group":9,"version":2},{"shortcodes":["comoros","flag_km"],"annotation":"flag: Comoros","tags":["KM","flag","flag: comoros"],"emoji":"🇰🇲","order":4898,"group":9,"version":2},{"shortcodes":["flag_kn","st_kitts_nevis"],"annotation":"flag: St. Kitts & Nevis","tags":["KN","flag","flag: st. kitts & nevis"],"emoji":"🇰🇳","order":4899,"group":9,"version":2},{"shortcodes":["flag_kp","north_korea"],"annotation":"flag: North Korea","tags":["KP","flag","flag: north korea"],"emoji":"🇰🇵","order":4900,"group":9,"version":2},{"shortcodes":["flag_kr","south_korea"],"annotation":"flag: South Korea","tags":["KR","flag","flag: south korea"],"emoji":"🇰🇷","order":4901,"group":9,"version":0.6},{"shortcodes":["flag_kw","kuwait"],"annotation":"flag: Kuwait","tags":["KW","flag","flag: kuwait"],"emoji":"🇰🇼","order":4902,"group":9,"version":2},{"shortcodes":["cayman_islands","flag_ky"],"annotation":"flag: Cayman Islands","tags":["KY","flag","flag: cayman islands"],"emoji":"🇰🇾","order":4903,"group":9,"version":2},{"shortcodes":["flag_kz","kazakhstan"],"annotation":"flag: Kazakhstan","tags":["KZ","flag","flag: kazakhstan"],"emoji":"🇰🇿","order":4904,"group":9,"version":2},{"shortcodes":["flag_la","laos"],"annotation":"flag: Laos","tags":["LA","flag","flag: laos"],"emoji":"🇱🇦","order":4905,"group":9,"version":2},{"shortcodes":["flag_lb","lebanon"],"annotation":"flag: Lebanon","tags":["LB","flag","flag: lebanon"],"emoji":"🇱🇧","order":4906,"group":9,"version":2},{"shortcodes":["flag_lc","st_lucia"],"annotation":"flag: St. Lucia","tags":["LC","flag","flag: st. lucia"],"emoji":"🇱🇨","order":4907,"group":9,"version":2},{"shortcodes":["flag_li","liechtenstein"],"annotation":"flag: Liechtenstein","tags":["LI","flag","flag: liechtenstein"],"emoji":"🇱🇮","order":4908,"group":9,"version":2},{"shortcodes":["flag_lk","sri_lanka"],"annotation":"flag: Sri Lanka","tags":["LK","flag","flag: sri lanka"],"emoji":"🇱🇰","order":4909,"group":9,"version":2},{"shortcodes":["flag_lr","liberia"],"annotation":"flag: Liberia","tags":["LR","flag","flag: liberia"],"emoji":"🇱🇷","order":4910,"group":9,"version":2},{"shortcodes":["flag_ls","lesotho"],"annotation":"flag: Lesotho","tags":["LS","flag","flag: lesotho"],"emoji":"🇱🇸","order":4911,"group":9,"version":2},{"shortcodes":["flag_lt","lithuania"],"annotation":"flag: Lithuania","tags":["LT","flag","flag: lithuania"],"emoji":"🇱🇹","order":4912,"group":9,"version":2},{"shortcodes":["flag_lu","luxembourg"],"annotation":"flag: Luxembourg","tags":["LU","flag","flag: luxembourg"],"emoji":"🇱🇺","order":4913,"group":9,"version":2},{"shortcodes":["flag_lv","latvia"],"annotation":"flag: Latvia","tags":["LV","flag","flag: latvia"],"emoji":"🇱🇻","order":4914,"group":9,"version":2},{"shortcodes":["flag_ly","libya"],"annotation":"flag: Libya","tags":["LY","flag","flag: libya"],"emoji":"🇱🇾","order":4915,"group":9,"version":2},{"shortcodes":["flag_ma","morocco"],"annotation":"flag: Morocco","tags":["MA","flag","flag: morocco"],"emoji":"🇲🇦","order":4916,"group":9,"version":2},{"shortcodes":["flag_mc","monaco"],"annotation":"flag: Monaco","tags":["MC","flag","flag: monaco"],"emoji":"🇲🇨","order":4917,"group":9,"version":2},{"shortcodes":["flag_md","moldova"],"annotation":"flag: Moldova","tags":["MD","flag","flag: moldova"],"emoji":"🇲🇩","order":4918,"group":9,"version":2},{"shortcodes":["flag_me","montenegro"],"annotation":"flag: Montenegro","tags":["ME","flag","flag: montenegro"],"emoji":"🇲🇪","order":4919,"group":9,"version":2},{"shortcodes":["flag_mf","st_martin"],"annotation":"flag: St. Martin","tags":["MF","flag","flag: st. martin"],"emoji":"🇲🇫","order":4920,"group":9,"version":2},{"shortcodes":["flag_mg","madagascar"],"annotation":"flag: Madagascar","tags":["MG","flag","flag: madagascar"],"emoji":"🇲🇬","order":4921,"group":9,"version":2},{"shortcodes":["flag_mh","marshall_islands"],"annotation":"flag: Marshall Islands","tags":["MH","flag","flag: marshall islands"],"emoji":"🇲🇭","order":4922,"group":9,"version":2},{"shortcodes":["flag_mk","macedonia"],"annotation":"flag: North Macedonia","tags":["MK","flag","flag: north macedonia"],"emoji":"🇲🇰","order":4923,"group":9,"version":2},{"shortcodes":["flag_ml","mali"],"annotation":"flag: Mali","tags":["ML","flag","flag: mali"],"emoji":"🇲🇱","order":4924,"group":9,"version":2},{"shortcodes":["burma","flag_mm","myanmar"],"annotation":"flag: Myanmar (Burma)","tags":["MM","flag","flag: myanmar (burma)"],"emoji":"🇲🇲","order":4925,"group":9,"version":2},{"shortcodes":["flag_mn","mongolia"],"annotation":"flag: Mongolia","tags":["MN","flag","flag: mongolia"],"emoji":"🇲🇳","order":4926,"group":9,"version":2},{"shortcodes":["flag_mo","macao","macau"],"annotation":"flag: Macao SAR China","tags":["MO","flag","flag: macao sar china"],"emoji":"🇲🇴","order":4927,"group":9,"version":2},{"shortcodes":["flag_mp","northern_mariana_islands"],"annotation":"flag: Northern Mariana Islands","tags":["MP","flag","flag: northern mariana islands"],"emoji":"🇲🇵","order":4928,"group":9,"version":2},{"shortcodes":["flag_mq","martinique"],"annotation":"flag: Martinique","tags":["MQ","flag","flag: martinique"],"emoji":"🇲🇶","order":4929,"group":9,"version":2},{"shortcodes":["flag_mr","mauritania"],"annotation":"flag: Mauritania","tags":["MR","flag","flag: mauritania"],"emoji":"🇲🇷","order":4930,"group":9,"version":2},{"shortcodes":["flag_ms","montserrat"],"annotation":"flag: Montserrat","tags":["MS","flag","flag: montserrat"],"emoji":"🇲🇸","order":4931,"group":9,"version":2},{"shortcodes":["flag_mt","malta"],"annotation":"flag: Malta","tags":["MT","flag","flag: malta"],"emoji":"🇲🇹","order":4932,"group":9,"version":2},{"shortcodes":["flag_mu","mauritius"],"annotation":"flag: Mauritius","tags":["MU","flag","flag: mauritius"],"emoji":"🇲🇺","order":4933,"group":9,"version":2},{"shortcodes":["flag_mv","maldives"],"annotation":"flag: Maldives","tags":["MV","flag","flag: maldives"],"emoji":"🇲🇻","order":4934,"group":9,"version":2},{"shortcodes":["flag_mw","malawi"],"annotation":"flag: Malawi","tags":["MW","flag","flag: malawi"],"emoji":"🇲🇼","order":4935,"group":9,"version":2},{"shortcodes":["flag_mx","mexico"],"annotation":"flag: Mexico","tags":["MX","flag","flag: mexico"],"emoji":"🇲🇽","order":4936,"group":9,"version":2},{"shortcodes":["flag_my","malaysia"],"annotation":"flag: Malaysia","tags":["MY","flag","flag: malaysia"],"emoji":"🇲🇾","order":4937,"group":9,"version":2},{"shortcodes":["flag_mz","mozambique"],"annotation":"flag: Mozambique","tags":["MZ","flag","flag: mozambique"],"emoji":"🇲🇿","order":4938,"group":9,"version":2},{"shortcodes":["flag_na","namibia"],"annotation":"flag: Namibia","tags":["NA","flag","flag: namibia"],"emoji":"🇳🇦","order":4939,"group":9,"version":2},{"shortcodes":["flag_nc","new_caledonia"],"annotation":"flag: New Caledonia","tags":["NC","flag","flag: new caledonia"],"emoji":"🇳🇨","order":4940,"group":9,"version":2},{"shortcodes":["flag_ne","niger"],"annotation":"flag: Niger","tags":["NE","flag","flag: niger"],"emoji":"🇳🇪","order":4941,"group":9,"version":2},{"shortcodes":["flag_nf","norfolk_island"],"annotation":"flag: Norfolk Island","tags":["NF","flag","flag: norfolk island"],"emoji":"🇳🇫","order":4942,"group":9,"version":2},{"shortcodes":["flag_ng","nigeria"],"annotation":"flag: Nigeria","tags":["NG","flag","flag: nigeria"],"emoji":"🇳🇬","order":4943,"group":9,"version":2},{"shortcodes":["flag_ni","nicaragua"],"annotation":"flag: Nicaragua","tags":["NI","flag","flag: nicaragua"],"emoji":"🇳🇮","order":4944,"group":9,"version":2},{"shortcodes":["flag_nl","netherlands"],"annotation":"flag: Netherlands","tags":["NL","flag","flag: netherlands"],"emoji":"🇳🇱","order":4945,"group":9,"version":2},{"shortcodes":["flag_no","norway"],"annotation":"flag: Norway","tags":["NO","flag","flag: norway"],"emoji":"🇳🇴","order":4946,"group":9,"version":2},{"shortcodes":["flag_np","nepal"],"annotation":"flag: Nepal","tags":["NP","flag","flag: nepal"],"emoji":"🇳🇵","order":4947,"group":9,"version":2},{"shortcodes":["flag_nr","nauru"],"annotation":"flag: Nauru","tags":["NR","flag","flag: nauru"],"emoji":"🇳🇷","order":4948,"group":9,"version":2},{"shortcodes":["flag_nu","niue"],"annotation":"flag: Niue","tags":["NU","flag","flag: niue"],"emoji":"🇳🇺","order":4949,"group":9,"version":2},{"shortcodes":["flag_nz","new_zealand"],"annotation":"flag: New Zealand","tags":["NZ","flag","flag: new zealand"],"emoji":"🇳🇿","order":4950,"group":9,"version":2},{"shortcodes":["flag_om","oman"],"annotation":"flag: Oman","tags":["OM","flag","flag: oman"],"emoji":"🇴🇲","order":4951,"group":9,"version":2},{"shortcodes":["flag_pa","panama"],"annotation":"flag: Panama","tags":["PA","flag","flag: panama"],"emoji":"🇵🇦","order":4952,"group":9,"version":2},{"shortcodes":["flag_pe","peru"],"annotation":"flag: Peru","tags":["PE","flag","flag: peru"],"emoji":"🇵🇪","order":4953,"group":9,"version":2},{"shortcodes":["flag_pf","french_polynesia"],"annotation":"flag: French Polynesia","tags":["PF","flag","flag: french polynesia"],"emoji":"🇵🇫","order":4954,"group":9,"version":2},{"shortcodes":["flag_pg","papua_new_guinea"],"annotation":"flag: Papua New Guinea","tags":["PG","flag","flag: papua new guinea"],"emoji":"🇵🇬","order":4955,"group":9,"version":2},{"shortcodes":["flag_ph","philippines"],"annotation":"flag: Philippines","tags":["PH","flag","flag: philippines"],"emoji":"🇵🇭","order":4956,"group":9,"version":2},{"shortcodes":["flag_pk","pakistan"],"annotation":"flag: Pakistan","tags":["PK","flag","flag: pakistan"],"emoji":"🇵🇰","order":4957,"group":9,"version":2},{"shortcodes":["flag_pl","poland"],"annotation":"flag: Poland","tags":["PL","flag","flag: poland"],"emoji":"🇵🇱","order":4958,"group":9,"version":2},{"shortcodes":["flag_pm","st_pierre_miquelon"],"annotation":"flag: St. Pierre & Miquelon","tags":["PM","flag","flag: st. pierre & miquelon"],"emoji":"🇵🇲","order":4959,"group":9,"version":2},{"shortcodes":["flag_pn","pitcairn_islands"],"annotation":"flag: Pitcairn Islands","tags":["PN","flag","flag: pitcairn islands"],"emoji":"🇵🇳","order":4960,"group":9,"version":2},{"shortcodes":["flag_pr","puerto_rico"],"annotation":"flag: Puerto Rico","tags":["PR","flag","flag: puerto rico"],"emoji":"🇵🇷","order":4961,"group":9,"version":2},{"shortcodes":["flag_ps","palestinian_territories"],"annotation":"flag: Palestinian Territories","tags":["PS","flag","flag: palestinian territories"],"emoji":"🇵🇸","order":4962,"group":9,"version":2},{"shortcodes":["flag_pt","portugal"],"annotation":"flag: Portugal","tags":["PT","flag","flag: portugal"],"emoji":"🇵🇹","order":4963,"group":9,"version":2},{"shortcodes":["flag_pw","palau"],"annotation":"flag: Palau","tags":["PW","flag","flag: palau"],"emoji":"🇵🇼","order":4964,"group":9,"version":2},{"shortcodes":["flag_py","paraguay"],"annotation":"flag: Paraguay","tags":["PY","flag","flag: paraguay"],"emoji":"🇵🇾","order":4965,"group":9,"version":2},{"shortcodes":["flag_qa","qatar"],"annotation":"flag: Qatar","tags":["QA","flag","flag: qatar"],"emoji":"🇶🇦","order":4966,"group":9,"version":2},{"shortcodes":["flag_re","reunion"],"annotation":"flag: Réunion","tags":["RE","flag","flag: réunion"],"emoji":"🇷🇪","order":4967,"group":9,"version":2},{"shortcodes":["flag_ro","romania"],"annotation":"flag: Romania","tags":["RO","flag","flag: romania"],"emoji":"🇷🇴","order":4968,"group":9,"version":2},{"shortcodes":["flag_rs","serbia"],"annotation":"flag: Serbia","tags":["RS","flag","flag: serbia"],"emoji":"🇷🇸","order":4969,"group":9,"version":2},{"shortcodes":["flag_ru","russia"],"annotation":"flag: Russia","tags":["RU","flag","flag: russia"],"emoji":"🇷🇺","order":4970,"group":9,"version":0.6},{"shortcodes":["flag_rw","rwanda"],"annotation":"flag: Rwanda","tags":["RW","flag","flag: rwanda"],"emoji":"🇷🇼","order":4971,"group":9,"version":2},{"shortcodes":["flag_sa","saudi_arabia"],"annotation":"flag: Saudi Arabia","tags":["SA","flag","flag: saudi arabia"],"emoji":"🇸🇦","order":4972,"group":9,"version":2},{"shortcodes":["flag_sb","solomon_islands"],"annotation":"flag: Solomon Islands","tags":["SB","flag","flag: solomon islands"],"emoji":"🇸🇧","order":4973,"group":9,"version":2},{"shortcodes":["flag_sc","seychelles"],"annotation":"flag: Seychelles","tags":["SC","flag","flag: seychelles"],"emoji":"🇸🇨","order":4974,"group":9,"version":2},{"shortcodes":["flag_sd","sudan"],"annotation":"flag: Sudan","tags":["SD","flag","flag: sudan"],"emoji":"🇸🇩","order":4975,"group":9,"version":2},{"shortcodes":["flag_se","sweden"],"annotation":"flag: Sweden","tags":["SE","flag","flag: sweden"],"emoji":"🇸🇪","order":4976,"group":9,"version":2},{"shortcodes":["flag_sg","singapore"],"annotation":"flag: Singapore","tags":["SG","flag","flag: singapore"],"emoji":"🇸🇬","order":4977,"group":9,"version":2},{"shortcodes":["flag_sh","st_helena"],"annotation":"flag: St. Helena","tags":["SH","flag","flag: st. helena"],"emoji":"🇸🇭","order":4978,"group":9,"version":2},{"shortcodes":["flag_si","slovenia"],"annotation":"flag: Slovenia","tags":["SI","flag","flag: slovenia"],"emoji":"🇸🇮","order":4979,"group":9,"version":2},{"shortcodes":["flag_sj","svalbard_jan_mayen"],"annotation":"flag: Svalbard & Jan Mayen","tags":["SJ","flag","flag: svalbard & jan mayen"],"emoji":"🇸🇯","order":4980,"group":9,"version":2},{"shortcodes":["flag_sk","slovakia"],"annotation":"flag: Slovakia","tags":["SK","flag","flag: slovakia"],"emoji":"🇸🇰","order":4981,"group":9,"version":2},{"shortcodes":["flag_sl","sierra_leone"],"annotation":"flag: Sierra Leone","tags":["SL","flag","flag: sierra leone"],"emoji":"🇸🇱","order":4982,"group":9,"version":2},{"shortcodes":["flag_sm","san_marino"],"annotation":"flag: San Marino","tags":["SM","flag","flag: san marino"],"emoji":"🇸🇲","order":4983,"group":9,"version":2},{"shortcodes":["flag_sn","senegal"],"annotation":"flag: Senegal","tags":["SN","flag","flag: senegal"],"emoji":"🇸🇳","order":4984,"group":9,"version":2},{"shortcodes":["flag_so","somalia"],"annotation":"flag: Somalia","tags":["SO","flag","flag: somalia"],"emoji":"🇸🇴","order":4985,"group":9,"version":2},{"shortcodes":["flag_sr","suriname"],"annotation":"flag: Suriname","tags":["SR","flag","flag: suriname"],"emoji":"🇸🇷","order":4986,"group":9,"version":2},{"shortcodes":["flag_ss","south_sudan"],"annotation":"flag: South Sudan","tags":["SS","flag","flag: south sudan"],"emoji":"🇸🇸","order":4987,"group":9,"version":2},{"shortcodes":["flag_st","sao_tome_principe"],"annotation":"flag: São Tomé & Príncipe","tags":["ST","flag","flag: são tomé & príncipe"],"emoji":"🇸🇹","order":4988,"group":9,"version":2},{"shortcodes":["el_salvador","flag_sv"],"annotation":"flag: El Salvador","tags":["SV","flag","flag: el salvador"],"emoji":"🇸🇻","order":4989,"group":9,"version":2},{"shortcodes":["flag_sx","sint_maarten"],"annotation":"flag: Sint Maarten","tags":["SX","flag","flag: sint maarten"],"emoji":"🇸🇽","order":4990,"group":9,"version":2},{"shortcodes":["flag_sy","syria"],"annotation":"flag: Syria","tags":["SY","flag","flag: syria"],"emoji":"🇸🇾","order":4991,"group":9,"version":2},{"shortcodes":["eswatini","flag_sz","swaziland"],"annotation":"flag: Eswatini","tags":["SZ","flag","flag: eswatini"],"emoji":"🇸🇿","order":4992,"group":9,"version":2},{"shortcodes":["flag_ta","tristan_da_cunha"],"annotation":"flag: Tristan da Cunha","tags":["TA","flag","flag: tristan da cunha"],"emoji":"🇹🇦","order":4993,"group":9,"version":2},{"shortcodes":["flag_tc","turks_caicos_islands"],"annotation":"flag: Turks & Caicos Islands","tags":["TC","flag","flag: turks & caicos islands"],"emoji":"🇹🇨","order":4994,"group":9,"version":2},{"shortcodes":["chad","flag_td"],"annotation":"flag: Chad","tags":["TD","flag","flag: chad"],"emoji":"🇹🇩","order":4995,"group":9,"version":2},{"shortcodes":["flag_tf","french_southern_territories"],"annotation":"flag: French Southern Territories","tags":["TF","flag","flag: french southern territories"],"emoji":"🇹🇫","order":4996,"group":9,"version":2},{"shortcodes":["flag_tg","togo"],"annotation":"flag: Togo","tags":["TG","flag","flag: togo"],"emoji":"🇹🇬","order":4997,"group":9,"version":2},{"shortcodes":["flag_th","thailand"],"annotation":"flag: Thailand","tags":["TH","flag","flag: thailand"],"emoji":"🇹🇭","order":4998,"group":9,"version":2},{"shortcodes":["flag_tj","tajikistan"],"annotation":"flag: Tajikistan","tags":["TJ","flag","flag: tajikistan"],"emoji":"🇹🇯","order":4999,"group":9,"version":2},{"shortcodes":["flag_tk","tokelau"],"annotation":"flag: Tokelau","tags":["TK","flag","flag: tokelau"],"emoji":"🇹🇰","order":5000,"group":9,"version":2},{"shortcodes":["flag_tl","timor_leste"],"annotation":"flag: Timor-Leste","tags":["TL","flag","flag: timor-leste"],"emoji":"🇹🇱","order":5001,"group":9,"version":2},{"shortcodes":["flag_tm","turkmenistan"],"annotation":"flag: Turkmenistan","tags":["TM","flag","flag: turkmenistan"],"emoji":"🇹🇲","order":5002,"group":9,"version":2},{"shortcodes":["flag_tn","tunisia"],"annotation":"flag: Tunisia","tags":["TN","flag","flag: tunisia"],"emoji":"🇹🇳","order":5003,"group":9,"version":2},{"shortcodes":["flag_to","tonga"],"annotation":"flag: Tonga","tags":["TO","flag","flag: tonga"],"emoji":"🇹🇴","order":5004,"group":9,"version":2},{"shortcodes":["flag_tr","turkey_tr"],"annotation":"flag: Türkiye","tags":["TR","flag","flag: türkiye"],"emoji":"🇹🇷","order":5005,"group":9,"version":2},{"shortcodes":["flag_tt","trinidad_tobago"],"annotation":"flag: Trinidad & Tobago","tags":["TT","flag","flag: trinidad & tobago"],"emoji":"🇹🇹","order":5006,"group":9,"version":2},{"shortcodes":["flag_tv","tuvalu"],"annotation":"flag: Tuvalu","tags":["TV","flag","flag: tuvalu"],"emoji":"🇹🇻","order":5007,"group":9,"version":2},{"shortcodes":["flag_tw","taiwan"],"annotation":"flag: Taiwan","tags":["TW","flag","flag: taiwan"],"emoji":"🇹🇼","order":5008,"group":9,"version":2},{"shortcodes":["flag_tz","tanzania"],"annotation":"flag: Tanzania","tags":["TZ","flag","flag: tanzania"],"emoji":"🇹🇿","order":5009,"group":9,"version":2},{"shortcodes":["flag_ua","ukraine"],"annotation":"flag: Ukraine","tags":["UA","flag","flag: ukraine"],"emoji":"🇺🇦","order":5010,"group":9,"version":2},{"shortcodes":["flag_ug","uganda"],"annotation":"flag: Uganda","tags":["UG","flag","flag: uganda"],"emoji":"🇺🇬","order":5011,"group":9,"version":2},{"shortcodes":["flag_um","us_outlying_islands"],"annotation":"flag: U.S. Outlying Islands","tags":["UM","flag","flag: u.s. outlying islands"],"emoji":"🇺🇲","order":5012,"group":9,"version":2},{"shortcodes":["flag_un","un","united_nations"],"annotation":"flag: United Nations","tags":["UN","flag","flag: united nations"],"emoji":"🇺🇳","order":5013,"group":9,"version":4},{"shortcodes":["flag_us","united_states","usa"],"annotation":"flag: United States","tags":["US","flag","flag: united states"],"emoji":"🇺🇸","order":5014,"group":9,"version":0.6},{"shortcodes":["flag_uy","uruguay"],"annotation":"flag: Uruguay","tags":["UY","flag","flag: uruguay"],"emoji":"🇺🇾","order":5015,"group":9,"version":2},{"shortcodes":["flag_uz","uzbekistan"],"annotation":"flag: Uzbekistan","tags":["UZ","flag","flag: uzbekistan"],"emoji":"🇺🇿","order":5016,"group":9,"version":2},{"shortcodes":["flag_va","vatican_city"],"annotation":"flag: Vatican City","tags":["VA","flag","flag: vatican city"],"emoji":"🇻🇦","order":5017,"group":9,"version":2},{"shortcodes":["flag_vc","st_vincent_grenadines"],"annotation":"flag: St. Vincent & Grenadines","tags":["VC","flag","flag: st. vincent & grenadines"],"emoji":"🇻🇨","order":5018,"group":9,"version":2},{"shortcodes":["flag_ve","venezuela"],"annotation":"flag: Venezuela","tags":["VE","flag","flag: venezuela"],"emoji":"🇻🇪","order":5019,"group":9,"version":2},{"shortcodes":["british_virgin_islands","flag_vg"],"annotation":"flag: British Virgin Islands","tags":["VG","flag","flag: british virgin islands"],"emoji":"🇻🇬","order":5020,"group":9,"version":2},{"shortcodes":["flag_vi","us_virgin_islands"],"annotation":"flag: U.S. Virgin Islands","tags":["VI","flag","flag: u.s. virgin islands"],"emoji":"🇻🇮","order":5021,"group":9,"version":2},{"shortcodes":["flag_vn","vietnam"],"annotation":"flag: Vietnam","tags":["VN","flag","flag: vietnam"],"emoji":"🇻🇳","order":5022,"group":9,"version":2},{"shortcodes":["flag_vu","vanuatu"],"annotation":"flag: Vanuatu","tags":["VU","flag","flag: vanuatu"],"emoji":"🇻🇺","order":5023,"group":9,"version":2},{"shortcodes":["flag_wf","wallis_futuna"],"annotation":"flag: Wallis & Futuna","tags":["WF","flag","flag: wallis & futuna"],"emoji":"🇼🇫","order":5024,"group":9,"version":2},{"shortcodes":["flag_ws","samoa"],"annotation":"flag: Samoa","tags":["WS","flag","flag: samoa"],"emoji":"🇼🇸","order":5025,"group":9,"version":2},{"shortcodes":["flag_xk","kosovo"],"annotation":"flag: Kosovo","tags":["XK","flag","flag: kosovo"],"emoji":"🇽🇰","order":5026,"group":9,"version":2},{"shortcodes":["flag_ye","yemen"],"annotation":"flag: Yemen","tags":["YE","flag","flag: yemen"],"emoji":"🇾🇪","order":5027,"group":9,"version":2},{"shortcodes":["flag_yt","mayotte"],"annotation":"flag: Mayotte","tags":["YT","flag","flag: mayotte"],"emoji":"🇾🇹","order":5028,"group":9,"version":2},{"shortcodes":["flag_za","south_africa"],"annotation":"flag: South Africa","tags":["ZA","flag","flag: south africa"],"emoji":"🇿🇦","order":5029,"group":9,"version":2},{"shortcodes":["flag_zm","zambia"],"annotation":"flag: Zambia","tags":["ZM","flag","flag: zambia"],"emoji":"🇿🇲","order":5030,"group":9,"version":2},{"shortcodes":["flag_zw","zimbabwe"],"annotation":"flag: Zimbabwe","tags":["ZW","flag","flag: zimbabwe"],"emoji":"🇿🇼","order":5031,"group":9,"version":2},{"shortcodes":["england","flag_gbeng"],"annotation":"flag: England","tags":["flag","flag: england","gbeng"],"emoji":"🏴","order":5032,"group":9,"version":5},{"shortcodes":["flag_gbsct","scotland"],"annotation":"flag: Scotland","tags":["flag","flag: scotland","gbsct"],"emoji":"🏴","order":5033,"group":9,"version":5},{"shortcodes":["flag_gbwls","wales"],"annotation":"flag: Wales","tags":["flag","flag: wales","gbwls"],"emoji":"🏴","order":5034,"group":9,"version":5}]
\ No newline at end of file
diff --git a/frontend/src/components/input/Reactions.vue b/frontend/src/components/input/Reactions.vue
new file mode 100644
index 000000000..16d4d5af9
--- /dev/null
+++ b/frontend/src/components/input/Reactions.vue
@@ -0,0 +1,193 @@
+
+
+
+
+
+ {{ value }} {{ users.length }}
+
+
+
+
+
+ addReaction(detail.unicode)"
+ />
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/input/editor/TipTap.vue b/frontend/src/components/input/editor/TipTap.vue
index c39eaddcd..1cc3c165d 100644
--- a/frontend/src/components/input/editor/TipTap.vue
+++ b/frontend/src/components/input/editor/TipTap.vue
@@ -558,6 +558,10 @@ function handleImagePaste(event) {
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function setFocusToEditor(event) {
+ if(event.target.shadowRoot) {
+ return
+ }
+
const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return
if (hotkeyString !== editShortcut ||
diff --git a/frontend/src/components/misc/Icon.ts b/frontend/src/components/misc/Icon.ts
index d50e3953d..8b2d1a011 100644
--- a/frontend/src/components/misc/Icon.ts
+++ b/frontend/src/components/misc/Icon.ts
@@ -87,7 +87,7 @@ import {
faStar,
faSun,
faTimesCircle,
- faCircleQuestion,
+ faCircleQuestion, faFaceLaugh,
} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
@@ -186,6 +186,7 @@ library.add(faXmarksLines)
library.add(faFont)
library.add(faRulerHorizontal)
library.add(faUnderline)
+library.add(faFaceLaugh)
// overwriting the wrong types
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
\ No newline at end of file
diff --git a/frontend/src/components/tasks/partials/comments.vue b/frontend/src/components/tasks/partials/comments.vue
index 188336f4f..d88ac8cbb 100644
--- a/frontend/src/components/tasks/partials/comments.vue
+++ b/frontend/src/components/tasks/partials/comments.vue
@@ -97,6 +97,12 @@
editComment()
}"
/>
+
implements IReaction {
+ id: number = 0
+ kind: 'tasks' | 'comments' = 'tasks'
+ value: string = ''
+
+ constructor(data: Partial) {
+ super()
+ this.assignData(data)
+ }
+}
+
\ No newline at end of file
diff --git a/frontend/src/models/task.ts b/frontend/src/models/task.ts
index 618fe35c1..2e2f1095a 100644
--- a/frontend/src/models/task.ts
+++ b/frontend/src/models/task.ts
@@ -86,6 +86,8 @@ export default class TaskModel extends AbstractModel implements ITask {
position = 0
kanbanPosition = 0
+
+ reactions = {}
createdBy: IUser = UserModel
created: Date = null
@@ -148,6 +150,12 @@ export default class TaskModel extends AbstractModel implements ITask {
this.updated = new Date(this.updated)
this.projectId = Number(this.projectId)
+
+ // We can't convert emojis to camel case, hence we do this manually
+ this.reactions = {}
+ Object.keys(data.reactions || {}).forEach(reaction => {
+ this.reactions[reaction] = data.reactions[reaction].map(u => new UserModel(u))
+ })
}
getTextIdentifier() {
diff --git a/frontend/src/models/taskComment.ts b/frontend/src/models/taskComment.ts
index 4579140fc..5928ca5d6 100644
--- a/frontend/src/models/taskComment.ts
+++ b/frontend/src/models/taskComment.ts
@@ -10,6 +10,8 @@ export default class TaskCommentModel extends AbstractModel implem
taskId: ITask['id'] = 0
comment = ''
author: IUser = UserModel
+
+ reactions = {}
created: Date = null
updated: Date = null
@@ -21,5 +23,11 @@ export default class TaskCommentModel extends AbstractModel implem
this.author = new UserModel(this.author)
this.created = new Date(this.created)
this.updated = new Date(this.updated)
+
+ // We can't convert emojis to camel case, hence we do this manually
+ this.reactions = {}
+ Object.keys(data.reactions || {}).forEach(reaction => {
+ this.reactions[reaction] = data.reactions[reaction].map(u => new UserModel(u))
+ })
}
}
diff --git a/frontend/src/services/abstractService.ts b/frontend/src/services/abstractService.ts
index dc0b13f08..820347618 100644
--- a/frontend/src/services/abstractService.ts
+++ b/frontend/src/services/abstractService.ts
@@ -77,19 +77,25 @@ export default abstract class AbstractService): ReactionModel {
+ return new ReactionModel(data)
+ }
+
+ modelGetAllFactory(data: Partial): Partial {
+ Object.keys(data).forEach(reaction => {
+ data[reaction] = data[reaction]?.map(u => new UserModel(u))
+ })
+
+ return data
+ }
+
+ async delete(model: IAbstract) {
+ const finalUrl = this.getReplacedRoute(this.paths.delete, model)
+ return super.post(finalUrl, model)
+ }
+}
diff --git a/frontend/src/services/task.ts b/frontend/src/services/task.ts
index 48e1a8967..6071b3585 100644
--- a/frontend/src/services/task.ts
+++ b/frontend/src/services/task.ts
@@ -6,6 +6,7 @@ import LabelService from './label'
import {colorFromHex} from '@/helpers/color/colorFromHex'
import {SECONDS_A_DAY, SECONDS_A_HOUR, SECONDS_A_WEEK} from '@/constants/date'
+import {objectToSnakeCase} from '@/helpers/case'
const parseDate = date => {
if (date) {
@@ -38,8 +39,12 @@ export default class TaskService extends AbstractService {
return this.processModel(model)
}
+ autoTransformBeforePost(): boolean {
+ return false
+ }
+
processModel(updatedModel) {
- const model = { ...updatedModel }
+ const model = {...updatedModel}
model.title = model.title?.trim()
@@ -108,7 +113,15 @@ export default class TaskService extends AbstractService {
model.labels = model.labels.map(l => labelService.processModel(l))
}
- return model as ITask
+ const transformed = objectToSnakeCase(model)
+
+ // We can't convert emojis to skane case, hence we add them back again
+ transformed.reactions = {}
+ Object.keys(updatedModel.reactions || {}).forEach(reaction => {
+ transformed.reactions[reaction] = updatedModel.reactions[reaction].map(u => objectToSnakeCase(u))
+ })
+
+ return transformed as ITask
}
}
diff --git a/frontend/src/services/taskComment.ts b/frontend/src/services/taskComment.ts
index 040a212fa..6eaa1aea4 100644
--- a/frontend/src/services/taskComment.ts
+++ b/frontend/src/services/taskComment.ts
@@ -1,6 +1,7 @@
import AbstractService from './abstractService'
import TaskCommentModel from '@/models/taskComment'
import type {ITaskComment} from '@/modelTypes/ITaskComment'
+import {objectToSnakeCase} from '@/helpers/case'
export default class TaskCommentService extends AbstractService {
constructor() {
@@ -16,4 +17,22 @@ export default class TaskCommentService extends AbstractService {
modelFactory(data) {
return new TaskCommentModel(data)
}
+
+ autoTransformBeforePost(): boolean {
+ return false
+ }
+
+ beforeUpdate(model: ITaskComment) {
+ const transformed = objectToSnakeCase({...model})
+
+ // We can't convert emojis to skane case, hence we add them back again
+ transformed.reactions = {}
+ Object.keys(model.reactions || {}).forEach(reaction => {
+ transformed.reactions[reaction] = model.reactions[reaction].map(u => objectToSnakeCase(u))
+ })
+
+ console.log()
+
+ return transformed as ITaskComment
+ }
}
\ No newline at end of file
diff --git a/frontend/src/stores/tasks.ts b/frontend/src/stores/tasks.ts
index 332a4eba7..331625b89 100644
--- a/frontend/src/stores/tasks.ts
+++ b/frontend/src/stores/tasks.ts
@@ -152,6 +152,7 @@ export const useTaskStore = defineStore('task', () => {
const taskService = new TaskService()
try {
const updatedTask = await taskService.update(task)
+ console.log({updated: updatedTask.reactions, old: task.reactions})
kanbanStore.setTaskInBucket(updatedTask)
return updatedTask
} finally {
diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue
index da8c7c9a0..f3e274396 100644
--- a/frontend/src/views/tasks/TaskDetailView.vue
+++ b/frontend/src/views/tasks/TaskDetailView.vue
@@ -312,6 +312,14 @@
@update:modelValue="Object.assign(task, $event)"
/>
+
+
+
.
+
+package migration
+
+import (
+ "time"
+
+ "src.techknowlogick.com/xormigrate"
+ "xorm.io/xorm"
+)
+
+type reactions20240311173251 struct {
+ ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"reaction"`
+ UserID int64 `xorm:"bigint not null INDEX" json:"-"`
+ EntityID int64 `xorm:"bigint not null INDEX" json:"entity_id"`
+ EntityKind int `xorm:"bigint not null INDEX" json:"entity_kind"`
+ Value string `xorm:"varchar(20) not null INDEX" json:"value"`
+ Created time.Time `xorm:"created not null" json:"created"`
+}
+
+func (reactions20240311173251) TableName() string {
+ return "reactions"
+}
+
+func init() {
+ migrations = append(migrations, &xormigrate.Migration{
+ ID: "20240311173251",
+ Description: "Create reactions table",
+ Migrate: func(tx *xorm.Engine) error {
+ return tx.Sync2(reactions20240311173251{})
+ },
+ Rollback: func(tx *xorm.Engine) error {
+ return nil
+ },
+ })
+}
diff --git a/pkg/models/error.go b/pkg/models/error.go
index 431d9e7d5..8eb151830 100644
--- a/pkg/models/error.go
+++ b/pkg/models/error.go
@@ -1060,6 +1060,33 @@ func (err ErrInvalidFilterExpression) HTTPError() web.HTTPError {
}
}
+// ErrInvalidReactionEntityKind represents an error where the reaction kind is invalid
+type ErrInvalidReactionEntityKind struct {
+ Kind string
+}
+
+// IsErrInvalidReactionEntityKind checks if an error is ErrInvalidReactionEntityKind.
+func IsErrInvalidReactionEntityKind(err error) bool {
+ _, ok := err.(ErrInvalidReactionEntityKind)
+ return ok
+}
+
+func (err ErrInvalidReactionEntityKind) Error() string {
+ return fmt.Sprintf("Reaction kind %s is invalid", err.Kind)
+}
+
+// ErrCodeInvalidReactionEntityKind holds the unique world-error code of this error
+const ErrCodeInvalidReactionEntityKind = 4025
+
+// HTTPError holds the http error description
+func (err ErrInvalidReactionEntityKind) HTTPError() web.HTTPError {
+ return web.HTTPError{
+ HTTPCode: http.StatusBadRequest,
+ Code: ErrCodeInvalidReactionEntityKind,
+ Message: fmt.Sprintf("The reaction kind '%s' is invalid.", err.Kind),
+ }
+}
+
// ============
// Team errors
// ============
diff --git a/pkg/models/models.go b/pkg/models/models.go
index 7c35f4ed8..f8404978c 100644
--- a/pkg/models/models.go
+++ b/pkg/models/models.go
@@ -61,6 +61,7 @@ func GetTables() []interface{} {
&APIToken{},
&TypesenseSync{},
&Webhook{},
+ &Reaction{},
}
}
diff --git a/pkg/models/project.go b/pkg/models/project.go
index 7958cbef5..7be2d933c 100644
--- a/pkg/models/project.go
+++ b/pkg/models/project.go
@@ -23,7 +23,6 @@ import (
"strings"
"time"
- "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
@@ -370,10 +369,7 @@ type projectOptions struct {
}
func getUserProjectsStatement(parentProjectIDs []int64, userID int64, search string, getArchived bool) *builder.Builder {
- dialect := config.DatabaseType.GetString()
- if dialect == "sqlite" {
- dialect = builder.SQLITE
- }
+ dialect := db.GetDialect()
// Adding a 1=1 condition by default here because xorm always needs a condition and cannot handle nil conditions
var getArchivedCond builder.Cond = builder.Eq{"1": 1}
diff --git a/pkg/models/reaction.go b/pkg/models/reaction.go
new file mode 100644
index 000000000..44fe73d62
--- /dev/null
+++ b/pkg/models/reaction.go
@@ -0,0 +1,191 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package models
+
+import (
+ "time"
+
+ "code.vikunja.io/web"
+ "xorm.io/builder"
+ "xorm.io/xorm"
+
+ "code.vikunja.io/api/pkg/user"
+)
+
+type ReactionKind int
+
+const (
+ ReactionKindTask = iota
+ ReactionKindComment
+)
+
+type Reaction struct {
+ // The unique numeric id of this reaction
+ ID int64 `xorm:"autoincr not null unique pk" json:"-" param:"reaction"`
+
+ // The user who reacted
+ User *user.User `xorm:"-" json:"user" valid:"-"`
+ UserID int64 `xorm:"bigint not null INDEX" json:"-"`
+
+ // The id of the entity you're reacting to
+ EntityID int64 `xorm:"bigint not null INDEX" json:"-" param:"entityid"`
+ // The entity kind which you're reacting to. Can be 0 for task, 1 for comment.
+ EntityKind ReactionKind `xorm:"bigint not null INDEX" json:"-"`
+ EntityKindString string `xorm:"-" json:"-" param:"entitykind"`
+
+ // The actual reaction. This can be any valid utf character or text, up to a length of 20.
+ Value string `xorm:"varchar(20) not null INDEX" json:"value" valid:"required"`
+
+ // A timestamp when this reaction was created. You cannot change this value.
+ Created time.Time `xorm:"created not null" json:"created"`
+
+ web.CRUDable `xorm:"-" json:"-"`
+ web.Rights `xorm:"-" json:"-"`
+}
+
+func (*Reaction) TableName() string {
+ return "reactions"
+}
+
+type ReactionMap map[string][]*user.User
+
+// ReadAll gets all reactions for an entity
+// @Summary Get all reactions for an entity
+// @Description Returns all reactions for an entity
+// @tags task
+// @Accept json
+// @Produce json
+// @Security JWTKeyAuth
+// @Param id path int true "Entity ID"
+// @Param kind path int true "The kind of the entity. Can be either `tasks` or `comments` for task comments"
+// @Success 200 {array} models.ReactionMap "The reactions"
+// @Failure 403 {object} web.HTTPError "The user does not have access to the entity"
+// @Failure 500 {object} models.Message "Internal error"
+// @Router /{kind}/{id}/reactions [get]
+func (r *Reaction) ReadAll(s *xorm.Session, a web.Auth, _ string, _ int, _ int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
+
+ can, _, err := r.CanRead(s, a)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ if !can {
+ return nil, 0, 0, ErrGenericForbidden{}
+ }
+
+ reactions, err := getReactionsForEntityIDs(s, r.EntityKind, []int64{r.EntityID})
+ if err != nil {
+ return
+ }
+
+ return reactions[r.EntityID], len(reactions[r.EntityID]), int64(len(reactions[r.EntityID])), nil
+}
+
+func getReactionsForEntityIDs(s *xorm.Session, entityKind ReactionKind, entityIDs []int64) (reactionsWithTasks map[int64]ReactionMap, err error) {
+
+ where := builder.And(
+ builder.Eq{"entity_kind": entityKind},
+ builder.In("entity_id", entityIDs),
+ )
+
+ reactions := []*Reaction{}
+ err = s.Where(where).Find(&reactions)
+ if err != nil {
+ return
+ }
+
+ if len(reactions) == 0 {
+ return
+ }
+
+ cond := builder.
+ Select("user_id").
+ From("reactions").
+ Where(where)
+
+ users, err := user.GetUsersByCond(s, builder.In("id", cond))
+ if err != nil {
+ return
+ }
+
+ reactionsWithTasks = make(map[int64]ReactionMap)
+ for _, reaction := range reactions {
+ if _, taskExists := reactionsWithTasks[reaction.EntityID]; !taskExists {
+ reactionsWithTasks[reaction.EntityID] = make(ReactionMap)
+ }
+
+ if _, has := reactionsWithTasks[reaction.EntityID][reaction.Value]; !has {
+ reactionsWithTasks[reaction.EntityID][reaction.Value] = []*user.User{}
+ }
+
+ reactionsWithTasks[reaction.EntityID][reaction.Value] = append(reactionsWithTasks[reaction.EntityID][reaction.Value], users[reaction.UserID])
+ }
+
+ return
+}
+
+// Delete removes the user's own reaction
+// @Summary Removes the user's reaction
+// @Description Removes the reaction of that user on that entity.
+// @tags task
+// @Accept json
+// @Produce json
+// @Security JWTKeyAuth
+// @Param id path int true "Entity ID"
+// @Param kind path int true "The kind of the entity. Can be either `tasks` or `comments` for task comments"
+// @Param project body models.Reaction true "The reaction you want to add to the entity."
+// @Success 200 {object} models.Message "The reaction was successfully removed."
+// @Failure 403 {object} web.HTTPError "The user does not have access to the entity"
+// @Failure 500 {object} models.Message "Internal error"
+// @Router /{kind}/{id}/reactions/delete [post]
+func (r *Reaction) Delete(s *xorm.Session, a web.Auth) (err error) {
+ r.UserID = a.GetID()
+
+ _, err = s.Where("user_id = ? AND entity_id = ? AND entity_kind = ? AND value = ?", r.UserID, r.EntityID, r.EntityKind, r.Value).
+ Delete(&Reaction{})
+ return
+}
+
+// Create adds a new reaction to an entity
+// @Summary Add a reaction to an entity
+// @Description Add a reaction to an entity. Will do nothing if the reaction already exists.
+// @tags task
+// @Accept json
+// @Produce json
+// @Security JWTKeyAuth
+// @Param id path int true "Entity ID"
+// @Param kind path int true "The kind of the entity. Can be either `tasks` or `comments` for task comments"
+// @Param project body models.Reaction true "The reaction you want to add to the entity."
+// @Success 200 {object} models.Reaction "The created reaction"
+// @Failure 403 {object} web.HTTPError "The user does not have access to the entity"
+// @Failure 500 {object} models.Message "Internal error"
+// @Router /{kind}/{id}/reactions [put]
+func (r *Reaction) Create(s *xorm.Session, a web.Auth) (err error) {
+ r.UserID = a.GetID()
+
+ exists, err := s.Where("user_id = ? AND entity_id = ? AND entity_kind = ? AND value = ?", r.UserID, r.EntityID, r.EntityKind, r.Value).
+ Exist(&Reaction{})
+ if err != nil {
+ return err
+ }
+
+ if exists {
+ return
+ }
+
+ _, err = s.Insert(r)
+ return
+}
diff --git a/pkg/models/reaction_rights.go b/pkg/models/reaction_rights.go
new file mode 100644
index 000000000..75ca684c1
--- /dev/null
+++ b/pkg/models/reaction_rights.go
@@ -0,0 +1,81 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package models
+
+import (
+ "code.vikunja.io/web"
+ "xorm.io/xorm"
+)
+
+func (r *Reaction) setEntityKindFromString() (err error) {
+ switch r.EntityKindString {
+ case "tasks":
+ r.EntityKind = ReactionKindTask
+ return
+ case "comments":
+ r.EntityKind = ReactionKindComment
+ return
+ }
+
+ return ErrInvalidReactionEntityKind{
+ Kind: r.EntityKindString,
+ }
+}
+
+func (r *Reaction) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
+ t, err := r.getTask(s)
+ if err != nil {
+ return false, 0, err
+ }
+ return t.CanRead(s, a)
+}
+
+func (r *Reaction) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
+ t, err := r.getTask(s)
+ if err != nil {
+ return false, err
+ }
+ return t.CanUpdate(s, a)
+}
+
+func (r *Reaction) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
+ t, err := r.getTask(s)
+ if err != nil {
+ return false, err
+ }
+ return t.CanUpdate(s, a)
+}
+
+func (r *Reaction) getTask(s *xorm.Session) (t *Task, err error) {
+ err = r.setEntityKindFromString()
+ if err != nil {
+ return
+ }
+
+ t = &Task{ID: r.EntityID}
+
+ if r.EntityKind == ReactionKindComment {
+ tc := &TaskComment{ID: r.EntityID}
+ err = getTaskCommentSimple(s, tc)
+ if err != nil {
+ return
+ }
+ t.ID = tc.TaskID
+ }
+
+ return
+}
diff --git a/pkg/models/reaction_test.go b/pkg/models/reaction_test.go
new file mode 100644
index 000000000..d788e450a
--- /dev/null
+++ b/pkg/models/reaction_test.go
@@ -0,0 +1,217 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package models
+
+import (
+ "testing"
+
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/user"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestReaction_ReadAll(t *testing.T) {
+ t.Run("normal", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+
+ r := &Reaction{
+ EntityID: 1,
+ EntityKindString: "tasks",
+ }
+
+ reactions, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.NoError(t, err)
+ assert.IsType(t, ReactionMap{}, reactions)
+
+ reactionMap := reactions.(ReactionMap)
+ assert.Len(t, reactionMap["👋"], 1)
+ assert.Equal(t, int64(1), reactionMap["👋"][0].ID)
+ })
+ t.Run("invalid entity", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 1,
+ EntityKindString: "loremipsum",
+ }
+
+ _, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.Error(t, err)
+ assert.ErrorIs(t, err, ErrInvalidReactionEntityKind{Kind: "loremipsum"})
+ })
+ t.Run("no access to task", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+
+ r := &Reaction{
+ EntityID: 34,
+ EntityKindString: "tasks",
+ }
+
+ _, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.Error(t, err)
+ })
+ t.Run("nonexistant task", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 9999999,
+ EntityKindString: "tasks",
+ }
+
+ _, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.Error(t, err)
+ assert.ErrorIs(t, err, ErrTaskDoesNotExist{ID: r.EntityID})
+ })
+ t.Run("no access to comment", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+
+ r := &Reaction{
+ EntityID: 18,
+ EntityKindString: "comments",
+ }
+
+ _, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.Error(t, err)
+ })
+ t.Run("nonexistant comment", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 9999999,
+ EntityKindString: "comments",
+ }
+
+ _, _, _, err := r.ReadAll(s, u, "", 0, 0)
+ require.Error(t, err)
+ assert.ErrorIs(t, err, ErrTaskCommentDoesNotExist{ID: r.EntityID})
+ })
+}
+
+func TestReaction_Create(t *testing.T) {
+ t.Run("normal", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 1,
+ EntityKindString: "tasks",
+ Value: "🦙",
+ }
+
+ can, err := r.CanCreate(s, u)
+ require.NoError(t, err)
+ assert.True(t, can)
+
+ err = r.Create(s, u)
+ require.NoError(t, err)
+
+ err = s.Commit()
+ require.NoError(t, err)
+
+ db.AssertExists(t, "reactions", map[string]interface{}{
+ "entity_id": r.EntityID,
+ "entity_kind": ReactionKindTask,
+ "user_id": u.ID,
+ "value": r.Value,
+ }, false)
+ })
+ t.Run("no permission to access task", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 34,
+ EntityKindString: "tasks",
+ Value: "🦙",
+ }
+
+ can, err := r.CanCreate(s, u)
+ require.NoError(t, err)
+ assert.False(t, can)
+ })
+ t.Run("no permission to access comment", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+ r := &Reaction{
+ EntityID: 18,
+ EntityKindString: "comments",
+ Value: "🦙",
+ }
+
+ can, err := r.CanCreate(s, u)
+ require.NoError(t, err)
+ assert.False(t, can)
+ })
+}
+
+func TestReaction_Delete(t *testing.T) {
+ t.Run("normal", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ u := &user.User{ID: 1}
+
+ r := &Reaction{
+ EntityID: 1,
+ EntityKindString: "tasks",
+ Value: "👋",
+ }
+
+ can, err := r.CanDelete(s, u)
+ require.NoError(t, err)
+ assert.True(t, can)
+
+ err = r.Delete(s, u)
+ require.NoError(t, err)
+
+ db.AssertMissing(t, "reactions", map[string]interface{}{
+ "entity_id": r.EntityID,
+ "entity_kind": ReactionKindTask,
+ "value": "👋",
+ })
+ })
+}
diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go
index 0745b586a..63a8ab766 100644
--- a/pkg/models/task_collection_test.go
+++ b/pkg/models/task_collection_test.go
@@ -98,6 +98,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
BucketID: 1,
IsFavorite: true,
Position: 2,
+ Reactions: ReactionMap{
+ "👋": []*user.User{user1},
+ },
Labels: []*Label{
label4,
},
diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go
index 6c219a86a..ded53acf4 100644
--- a/pkg/models/task_comments.go
+++ b/pkg/models/task_comments.go
@@ -37,6 +37,8 @@ type TaskComment struct {
Author *user.User `xorm:"-" json:"author"`
TaskID int64 `xorm:"not null" json:"-" param:"task"`
+ Reactions ReactionMap `xorm:"-" json:"reactions"`
+
Created time.Time `xorm:"created" json:"created"`
Updated time.Time `xorm:"updated" json:"updated"`
@@ -167,7 +169,7 @@ func (tc *TaskComment) Update(s *xorm.Session, _ web.Auth) error {
func getTaskCommentSimple(s *xorm.Session, tc *TaskComment) error {
exists, err := s.
- Where("id = ? and task_id = ?", tc.ID, tc.TaskID).
+ Where("id = ?", tc.ID).
NoAutoCondition().
Get(tc)
if err != nil {
@@ -263,8 +265,10 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa
}
var authorIDs []int64
+ var commentIDs []int64
for _, comment := range comments {
authorIDs = append(authorIDs, comment.AuthorID)
+ commentIDs = append(commentIDs, comment.ID)
}
authors, err := getUsersOrLinkSharesFromIDs(s, authorIDs)
@@ -272,8 +276,17 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa
return
}
+ reactions, err := getReactionsForEntityIDs(s, ReactionKindComment, commentIDs)
+ if err != nil {
+ return
+ }
+
for _, comment := range comments {
comment.Author = authors[comment.AuthorID]
+ r, has := reactions[comment.ID]
+ if has {
+ comment.Reactions = r
+ }
}
numberOfTotalItems, err = s.
diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go
index 2f331d482..4a3b28da7 100644
--- a/pkg/models/tasks.go
+++ b/pkg/models/tasks.go
@@ -125,6 +125,9 @@ type Task struct {
// The position of tasks in the kanban board. See the docs for the `position` property on how to use this.
KanbanPosition float64 `xorm:"double null" json:"kanban_position"`
+ // Reactions on that task.
+ Reactions ReactionMap `xorm:"-" json:"reactions"`
+
// The user who initially created the task.
CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the project
@@ -584,6 +587,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
return err
}
+ reactions, err := getReactionsForEntityIDs(s, ReactionKindTask, taskIDs)
+ if err != nil {
+ return
+ }
+
// Add all objects to their tasks
for _, task := range taskMap {
@@ -600,6 +608,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
task.setIdentifier(projects[task.ProjectID])
task.IsFavorite = taskFavorites[task.ID]
+
+ r, has := reactions[task.ID]
+ if has {
+ task.Reactions = r
+ }
}
// Get all related tasks
diff --git a/pkg/models/unit_tests.go b/pkg/models/unit_tests.go
index 6dd66990b..b8f0a99de 100644
--- a/pkg/models/unit_tests.go
+++ b/pkg/models/unit_tests.go
@@ -65,6 +65,7 @@ func SetupTests() {
"subscriptions",
"favorites",
"api_tokens",
+ "reactions",
)
if err != nil {
log.Fatal(err)
diff --git a/pkg/modules/migration/trello/trello.go b/pkg/modules/migration/trello/trello.go
index 380ec539c..d19fd48b7 100644
--- a/pkg/modules/migration/trello/trello.go
+++ b/pkg/modules/migration/trello/trello.go
@@ -25,6 +25,7 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
+
"github.com/adlio/trello"
"github.com/yuin/goldmark"
)
diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go
index 42934d2bf..d59970059 100644
--- a/pkg/routes/routes.go
+++ b/pkg/routes/routes.go
@@ -589,6 +589,15 @@ func registerAPIRoutes(a *echo.Group) {
a.POST("/projects/:project/webhooks/:webhook", webhookProvider.UpdateWeb)
a.GET("/webhooks/events", apiv1.GetAvailableWebhookEvents)
}
+
+ reactionProvider := &handler.WebHandler{
+ EmptyStruct: func() handler.CObject {
+ return &models.Reaction{}
+ },
+ }
+ a.GET("/:entitykind/:entityid/reactions", reactionProvider.ReadAllWeb)
+ a.POST("/:entitykind/:entityid/reactions/delete", reactionProvider.DeleteWeb)
+ a.PUT("/:entitykind/:entityid/reactions", reactionProvider.CreateWeb)
}
func registerMigrations(m *echo.Group) {
diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go
index 0fa109f4d..9f805a3e9 100644
--- a/pkg/swagger/docs.go
+++ b/pkg/swagger/docs.go
@@ -1,4 +1,5 @@
-// Package swagger Code generated by swaggo/swag. DO NOT EDIT
+// Code generated by swaggo/swag. DO NOT EDIT.
+
package swagger
import "github.com/swaggo/swag"
@@ -7042,6 +7043,190 @@ const docTemplate = `{
}
}
},
+ "/{kind}/{id}/reactions": {
+ "get": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "description": "Returns all reactions for an entity",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Get all reactions for an entity",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either ` + "`" + `tasks` + "`" + ` or ` + "`" + `comments` + "`" + ` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The reactions",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ },
+ "put": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Add a reaction to an entity",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either ` + "`" + `tasks` + "`" + ` or ` + "`" + `comments` + "`" + ` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "The reaction you want to add to the entity.",
+ "name": "project",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The created reaction",
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ },
+ "delete": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "description": "Removes the reaction of that user on that entity.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Removes the user's reaction",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either ` + "`" + `tasks` + "`" + ` or ` + "`" + `comments` + "`" + ` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "The reaction you want to add to the entity.",
+ "name": "project",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The reaction was successfully removed.",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ }
+ },
"/{username}/avatar": {
"get": {
"description": "Returns the user avatar as image.",
@@ -7754,6 +7939,36 @@ const docTemplate = `{
}
}
},
+ "models.Reaction": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "description": "A timestamp when this reaction was created. You cannot change this value.",
+ "type": "string"
+ },
+ "user": {
+ "description": "The user who reacted",
+ "allOf": [
+ {
+ "$ref": "#/definitions/user.User"
+ }
+ ]
+ },
+ "value": {
+ "description": "The actual reaction. This can be any valid utf character or text, up to a length of 20.",
+ "type": "string"
+ }
+ }
+ },
+ "models.ReactionMap": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/user.User"
+ }
+ }
+ },
"models.RelatedTaskMap": {
"type": "object",
"additionalProperties": {
@@ -8975,8 +9190,6 @@ var SwaggerInfo = &swag.Spec{
Description: "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Rights\nAll endpoints which return a single item (project, task, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.\nThis can be used to show or hide ui elements based on the rights the user has.\n# Errors\nAll errors have an error code and a human-readable error message in addition to the http status code. You should always check for the status code in the response, not only the http status code.\nDue to limitations in the swagger library we're using for this document, only one error per http status code is documented here. Make sure to check the [error docs](https://vikunja.io/docs/errors/) in Vikunja's documentation for a full list of available error codes.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully.\n\n**API Token:** You can create scoped API tokens for your user and use the token to make authenticated requests in the context of that user. The token must be provided via an `Authorization: Bearer ` header, similar to jwt auth. See the documentation for the `api` group to manage token creation and revocation.\n\n**BasicAuth:** Only used when requesting tasks via CalDAV.\n",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
- LeftDelim: "{{",
- RightDelim: "}}",
}
func init() {
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index 0bc6e5b2e..b8624844f 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -7034,6 +7034,190 @@
}
}
},
+ "/{kind}/{id}/reactions": {
+ "get": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "description": "Returns all reactions for an entity",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Get all reactions for an entity",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either `tasks` or `comments` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The reactions",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ },
+ "put": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Add a reaction to an entity",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either `tasks` or `comments` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "The reaction you want to add to the entity.",
+ "name": "project",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The created reaction",
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ },
+ "delete": {
+ "security": [
+ {
+ "JWTKeyAuth": []
+ }
+ ],
+ "description": "Removes the reaction of that user on that entity.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "task"
+ ],
+ "summary": "Removes the user's reaction",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Entity ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "The kind of the entity. Can be either `tasks` or `comments` for task comments",
+ "name": "kind",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "The reaction you want to add to the entity.",
+ "name": "project",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/models.Reaction"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The reaction was successfully removed.",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ },
+ "403": {
+ "description": "The user does not have access to the entity",
+ "schema": {
+ "$ref": "#/definitions/web.HTTPError"
+ }
+ },
+ "500": {
+ "description": "Internal error",
+ "schema": {
+ "$ref": "#/definitions/models.Message"
+ }
+ }
+ }
+ }
+ },
"/{username}/avatar": {
"get": {
"description": "Returns the user avatar as image.",
@@ -7746,6 +7930,36 @@
}
}
},
+ "models.Reaction": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "description": "A timestamp when this reaction was created. You cannot change this value.",
+ "type": "string"
+ },
+ "user": {
+ "description": "The user who reacted",
+ "allOf": [
+ {
+ "$ref": "#/definitions/user.User"
+ }
+ ]
+ },
+ "value": {
+ "description": "The actual reaction. This can be any valid utf character or text, up to a length of 20.",
+ "type": "string"
+ }
+ }
+ },
+ "models.ReactionMap": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/user.User"
+ }
+ }
+ },
"models.RelatedTaskMap": {
"type": "object",
"additionalProperties": {
diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml
index 5f6722b42..1accd27a6 100644
--- a/pkg/swagger/swagger.yaml
+++ b/pkg/swagger/swagger.yaml
@@ -509,6 +509,27 @@ definitions:
description: The username.
type: string
type: object
+ models.Reaction:
+ properties:
+ created:
+ description: A timestamp when this reaction was created. You cannot change
+ this value.
+ type: string
+ user:
+ allOf:
+ - $ref: '#/definitions/user.User'
+ description: The user who reacted
+ value:
+ description: The actual reaction. This can be any valid utf character or text,
+ up to a length of 20.
+ type: string
+ type: object
+ models.ReactionMap:
+ additionalProperties:
+ items:
+ $ref: '#/definitions/user.User'
+ type: array
+ type: object
models.RelatedTaskMap:
additionalProperties:
items:
@@ -1439,6 +1460,128 @@ info:
url: https://code.vikunja.io/api/src/branch/main/LICENSE
title: Vikunja API
paths:
+ /{kind}/{id}/reactions:
+ delete:
+ consumes:
+ - application/json
+ description: Removes the reaction of that user on that entity.
+ parameters:
+ - description: Entity ID
+ in: path
+ name: id
+ required: true
+ type: integer
+ - description: The kind of the entity. Can be either `tasks` or `comments` for
+ task comments
+ in: path
+ name: kind
+ required: true
+ type: integer
+ - description: The reaction you want to add to the entity.
+ in: body
+ name: project
+ required: true
+ schema:
+ $ref: '#/definitions/models.Reaction'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: The reaction was successfully removed.
+ schema:
+ $ref: '#/definitions/models.Message'
+ "403":
+ description: The user does not have access to the entity
+ schema:
+ $ref: '#/definitions/web.HTTPError'
+ "500":
+ description: Internal error
+ schema:
+ $ref: '#/definitions/models.Message'
+ security:
+ - JWTKeyAuth: []
+ summary: Removes the user's reaction
+ tags:
+ - task
+ get:
+ consumes:
+ - application/json
+ description: Returns all reactions for an entity
+ parameters:
+ - description: Entity ID
+ in: path
+ name: id
+ required: true
+ type: integer
+ - description: The kind of the entity. Can be either `tasks` or `comments` for
+ task comments
+ in: path
+ name: kind
+ required: true
+ type: integer
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: The reactions
+ schema:
+ items:
+ $ref: '#/definitions/models.ReactionMap'
+ type: array
+ "403":
+ description: The user does not have access to the entity
+ schema:
+ $ref: '#/definitions/web.HTTPError'
+ "500":
+ description: Internal error
+ schema:
+ $ref: '#/definitions/models.Message'
+ security:
+ - JWTKeyAuth: []
+ summary: Get all reactions for an entity
+ tags:
+ - task
+ put:
+ consumes:
+ - application/json
+ parameters:
+ - description: Entity ID
+ in: path
+ name: id
+ required: true
+ type: integer
+ - description: The kind of the entity. Can be either `tasks` or `comments` for
+ task comments
+ in: path
+ name: kind
+ required: true
+ type: integer
+ - description: The reaction you want to add to the entity.
+ in: body
+ name: project
+ required: true
+ schema:
+ $ref: '#/definitions/models.Reaction'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: The created reaction
+ schema:
+ $ref: '#/definitions/models.Reaction'
+ "403":
+ description: The user does not have access to the entity
+ schema:
+ $ref: '#/definitions/web.HTTPError'
+ "500":
+ description: Internal error
+ schema:
+ $ref: '#/definitions/models.Message'
+ security:
+ - JWTKeyAuth: []
+ summary: Add a reaction to an entity
+ tags:
+ - task
/{username}/avatar:
get:
description: Returns the user avatar as image.
diff --git a/pkg/user/user.go b/pkg/user/user.go
index 43dc339c5..6feb6ad23 100644
--- a/pkg/user/user.go
+++ b/pkg/user/user.go
@@ -34,6 +34,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
+ "xorm.io/builder"
"xorm.io/xorm"
)
@@ -259,13 +260,17 @@ func GetUserWithEmail(s *xorm.Session, user *User) (userOut *User, err error) {
// GetUsersByIDs returns a map of users from a slice of user ids
func GetUsersByIDs(s *xorm.Session, userIDs []int64) (users map[int64]*User, err error) {
- users = make(map[int64]*User)
-
if len(userIDs) == 0 {
return users, nil
}
- err = s.In("id", userIDs).Find(&users)
+ return GetUsersByCond(s, builder.In("id", userIDs))
+}
+
+func GetUsersByCond(s *xorm.Session, cond builder.Cond) (users map[int64]*User, err error) {
+ users = make(map[int64]*User)
+
+ err = s.Where(cond).Find(&users)
if err != nil {
return
}
From 792bf88dcf973548069026d3eca6b50426a1f6f9 Mon Sep 17 00:00:00 2001
From: "Frederick [Bot]"
Date: Tue, 12 Mar 2024 19:47:16 +0000
Subject: [PATCH 26/66] [skip ci] Updated swagger docs
---
pkg/swagger/docs.go | 31 +++++++++++--
pkg/swagger/swagger.json | 26 ++++++++++-
pkg/swagger/swagger.yaml | 97 +++++++++++++++++++++++-----------------
3 files changed, 106 insertions(+), 48 deletions(-)
diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go
index 9f805a3e9..a27794f94 100644
--- a/pkg/swagger/docs.go
+++ b/pkg/swagger/docs.go
@@ -1,5 +1,4 @@
-// Code generated by swaggo/swag. DO NOT EDIT.
-
+// Package swagger Code generated by swaggo/swag. DO NOT EDIT
package swagger
import "github.com/swaggo/swag"
@@ -7107,6 +7106,7 @@ const docTemplate = `{
"JWTKeyAuth": []
}
],
+ "description": "Add a reaction to an entity. Will do nothing if the reaction already exists.",
"consumes": [
"application/json"
],
@@ -7162,8 +7162,10 @@ const docTemplate = `{
}
}
}
- },
- "delete": {
+ }
+ },
+ "/{kind}/{id}/reactions/delete": {
+ "post": {
"security": [
{
"JWTKeyAuth": []
@@ -7591,6 +7593,14 @@ const docTemplate = `{
"description": "The project this task belongs to.",
"type": "integer"
},
+ "reactions": {
+ "description": "Reactions on that task.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ ]
+ },
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"allOf": [
@@ -8239,6 +8249,14 @@ const docTemplate = `{
"description": "The project this task belongs to.",
"type": "integer"
},
+ "reactions": {
+ "description": "Reactions on that task.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ ]
+ },
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"allOf": [
@@ -8362,6 +8380,9 @@ const docTemplate = `{
"id": {
"type": "integer"
},
+ "reactions": {
+ "$ref": "#/definitions/models.ReactionMap"
+ },
"updated": {
"type": "string"
}
@@ -9190,6 +9211,8 @@ var SwaggerInfo = &swag.Spec{
Description: "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Rights\nAll endpoints which return a single item (project, task, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.\nThis can be used to show or hide ui elements based on the rights the user has.\n# Errors\nAll errors have an error code and a human-readable error message in addition to the http status code. You should always check for the status code in the response, not only the http status code.\nDue to limitations in the swagger library we're using for this document, only one error per http status code is documented here. Make sure to check the [error docs](https://vikunja.io/docs/errors/) in Vikunja's documentation for a full list of available error codes.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully.\n\n**API Token:** You can create scoped API tokens for your user and use the token to make authenticated requests in the context of that user. The token must be provided via an `Authorization: Bearer ` header, similar to jwt auth. See the documentation for the `api` group to manage token creation and revocation.\n\n**BasicAuth:** Only used when requesting tasks via CalDAV.\n",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
+ LeftDelim: "{{",
+ RightDelim: "}}",
}
func init() {
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index b8624844f..d5d89a40b 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -7098,6 +7098,7 @@
"JWTKeyAuth": []
}
],
+ "description": "Add a reaction to an entity. Will do nothing if the reaction already exists.",
"consumes": [
"application/json"
],
@@ -7153,8 +7154,10 @@
}
}
}
- },
- "delete": {
+ }
+ },
+ "/{kind}/{id}/reactions/delete": {
+ "post": {
"security": [
{
"JWTKeyAuth": []
@@ -7582,6 +7585,14 @@
"description": "The project this task belongs to.",
"type": "integer"
},
+ "reactions": {
+ "description": "Reactions on that task.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ ]
+ },
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"allOf": [
@@ -8230,6 +8241,14 @@
"description": "The project this task belongs to.",
"type": "integer"
},
+ "reactions": {
+ "description": "Reactions on that task.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/models.ReactionMap"
+ }
+ ]
+ },
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"allOf": [
@@ -8353,6 +8372,9 @@
"id": {
"type": "integer"
},
+ "reactions": {
+ "$ref": "#/definitions/models.ReactionMap"
+ },
"updated": {
"type": "string"
}
diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml
index 1accd27a6..9a9f34822 100644
--- a/pkg/swagger/swagger.yaml
+++ b/pkg/swagger/swagger.yaml
@@ -237,6 +237,10 @@ definitions:
project_id:
description: The project this task belongs to.
type: integer
+ reactions:
+ allOf:
+ - $ref: '#/definitions/models.ReactionMap'
+ description: Reactions on that task.
related_tasks:
allOf:
- $ref: '#/definitions/models.RelatedTaskMap'
@@ -743,6 +747,10 @@ definitions:
project_id:
description: The project this task belongs to.
type: integer
+ reactions:
+ allOf:
+ - $ref: '#/definitions/models.ReactionMap'
+ description: Reactions on that task.
related_tasks:
allOf:
- $ref: '#/definitions/models.RelatedTaskMap'
@@ -834,6 +842,8 @@ definitions:
type: string
id:
type: integer
+ reactions:
+ $ref: '#/definitions/models.ReactionMap'
updated:
type: string
type: object
@@ -1461,48 +1471,6 @@ info:
title: Vikunja API
paths:
/{kind}/{id}/reactions:
- delete:
- consumes:
- - application/json
- description: Removes the reaction of that user on that entity.
- parameters:
- - description: Entity ID
- in: path
- name: id
- required: true
- type: integer
- - description: The kind of the entity. Can be either `tasks` or `comments` for
- task comments
- in: path
- name: kind
- required: true
- type: integer
- - description: The reaction you want to add to the entity.
- in: body
- name: project
- required: true
- schema:
- $ref: '#/definitions/models.Reaction'
- produces:
- - application/json
- responses:
- "200":
- description: The reaction was successfully removed.
- schema:
- $ref: '#/definitions/models.Message'
- "403":
- description: The user does not have access to the entity
- schema:
- $ref: '#/definitions/web.HTTPError'
- "500":
- description: Internal error
- schema:
- $ref: '#/definitions/models.Message'
- security:
- - JWTKeyAuth: []
- summary: Removes the user's reaction
- tags:
- - task
get:
consumes:
- application/json
@@ -1544,6 +1512,8 @@ paths:
put:
consumes:
- application/json
+ description: Add a reaction to an entity. Will do nothing if the reaction already
+ exists.
parameters:
- description: Entity ID
in: path
@@ -1582,6 +1552,49 @@ paths:
summary: Add a reaction to an entity
tags:
- task
+ /{kind}/{id}/reactions/delete:
+ post:
+ consumes:
+ - application/json
+ description: Removes the reaction of that user on that entity.
+ parameters:
+ - description: Entity ID
+ in: path
+ name: id
+ required: true
+ type: integer
+ - description: The kind of the entity. Can be either `tasks` or `comments` for
+ task comments
+ in: path
+ name: kind
+ required: true
+ type: integer
+ - description: The reaction you want to add to the entity.
+ in: body
+ name: project
+ required: true
+ schema:
+ $ref: '#/definitions/models.Reaction'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: The reaction was successfully removed.
+ schema:
+ $ref: '#/definitions/models.Message'
+ "403":
+ description: The user does not have access to the entity
+ schema:
+ $ref: '#/definitions/web.HTTPError'
+ "500":
+ description: Internal error
+ schema:
+ $ref: '#/definitions/models.Message'
+ security:
+ - JWTKeyAuth: []
+ summary: Removes the user's reaction
+ tags:
+ - task
/{username}/avatar:
get:
description: Returns the user avatar as image.
From 0e2ad5dde6382dc8134f91f53af47429501fd087 Mon Sep 17 00:00:00 2001
From: renovate
Date: Tue, 12 Mar 2024 20:07:21 +0000
Subject: [PATCH 27/66] fix(deps): pin dependency vuemoji-picker to 0.2.1
---
frontend/package.json | 2 +-
frontend/pnpm-lock.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 424c68f46..ddf6a1aa2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -123,7 +123,7 @@
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.10.1",
"vue-router": "4.3.0",
- "vuemoji-picker": "^0.2.1",
+ "vuemoji-picker": "0.2.1",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"
},
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index ff01839a5..02b83faa3 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -233,7 +233,7 @@ dependencies:
specifier: 4.3.0
version: 4.3.0(vue@3.4.21)
vuemoji-picker:
- specifier: ^0.2.1
+ specifier: 0.2.1
version: 0.2.1(vue@3.4.21)
workbox-precaching:
specifier: 7.0.0
From e44897e0d4aaf0cc3dec9895e829c6950f00efd8 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 21:28:31 +0100
Subject: [PATCH 28/66] fix(filter): do not match join operator
Partial fix for https://kolaente.dev/vikunja/vikunja/issues/2194
---
.../project/partials/FilterInput.vue | 19 +++++++++++--------
frontend/src/helpers/filters.ts | 2 +-
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue
index e02eb6838..d668fde47 100644
--- a/frontend/src/components/project/partials/FilterInput.vue
+++ b/frontend/src/components/project/partials/FilterInput.vue
@@ -10,13 +10,15 @@ import User from '@/components/misc/user.vue'
import ProjectUserService from '@/services/projectUsers'
import {useProjectStore} from '@/stores/projects'
import {
- DATE_FIELDS,
ASSIGNEE_FIELDS,
AUTOCOMPLETE_FIELDS,
AVAILABLE_FILTER_FIELDS,
+ DATE_FIELDS,
FILTER_JOIN_OPERATOR,
FILTER_OPERATORS,
- FILTER_OPERATORS_REGEX, LABEL_FIELDS, getFilterFieldRegexPattern,
+ FILTER_OPERATORS_REGEX,
+ getFilterFieldRegexPattern,
+ LABEL_FIELDS,
} from '@/helpers/filters'
const {
@@ -110,9 +112,9 @@ const highlightedFilterQuery = computed(() => {
if (typeof value === 'undefined') {
value = ''
}
-
- let labelTitles = [value]
- if(operator === 'in' || operator === '?=') {
+
+ let labelTitles = [value.trim()]
+ if (operator === 'in' || operator === '?=') {
labelTitles = value.split(',').map(v => v.trim())
}
@@ -122,7 +124,8 @@ const highlightedFilterQuery = computed(() => {
labelsHtml.push(`${label?.title ?? t} `)
})
- return `${f} ${operator} ${labelsHtml.join(', ')}`
+ const endSpace = value.endsWith(' ') ? ' ' : ''
+ return `${f} ${operator} ${labelsHtml.join(', ')}${endSpace}`
})
})
FILTER_OPERATORS
@@ -195,7 +198,7 @@ function handleFieldInput() {
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
let search = keyword
- if(operator === 'in' || operator === '?=') {
+ if (operator === 'in' || operator === '?=') {
const keywords = keyword.split(',')
search = keywords[keywords.length - 1].trim()
}
@@ -348,7 +351,7 @@ function autocompleteSelect(value) {
resize: none;
text-fill-color: transparent;
-webkit-text-fill-color: transparent;
-
+
&::placeholder {
text-fill-color: var(--input-placeholder-color);
-webkit-text-fill-color: var(--input-placeholder-color);
diff --git a/frontend/src/helpers/filters.ts b/frontend/src/helpers/filters.ts
index de10f57f6..4a15696e3 100644
--- a/frontend/src/helpers/filters.ts
+++ b/frontend/src/helpers/filters.ts
@@ -58,7 +58,7 @@ export const FILTER_JOIN_OPERATOR = [
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=|in)'
export function getFilterFieldRegexPattern(field: string): RegExp {
- return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
+ return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()<]+\\1?)?', 'ig')
}
export function transformFilterStringForApi(
From eb4f880c64e9a4a3e2eb521144630e4901af09e4 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 21:30:54 +0100
Subject: [PATCH 29/66] fix(filter): do not show filter footer when creating a
filter
---
frontend/src/views/filters/FilterNew.vue | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend/src/views/filters/FilterNew.vue b/frontend/src/views/filters/FilterNew.vue
index 0a5baf1a1..67f8642f6 100644
--- a/frontend/src/views/filters/FilterNew.vue
+++ b/frontend/src/views/filters/FilterNew.vue
@@ -62,6 +62,7 @@
:class="{ 'disabled': filterService.loading}"
:disabled="filterService.loading"
class="has-no-shadow has-no-border"
+ :has-footer="false"
/>
From cf6b476b7d750265026fde462128eb65a8cbff78 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 21:33:24 +0100
Subject: [PATCH 30/66] chore: cleanup leftover console.log
---
frontend/src/services/abstractService.ts | 1 -
frontend/src/stores/tasks.ts | 1 -
2 files changed, 2 deletions(-)
diff --git a/frontend/src/services/abstractService.ts b/frontend/src/services/abstractService.ts
index 820347618..f95723056 100644
--- a/frontend/src/services/abstractService.ts
+++ b/frontend/src/services/abstractService.ts
@@ -392,7 +392,6 @@ export default abstract class AbstractService {
const taskService = new TaskService()
try {
const updatedTask = await taskService.update(task)
- console.log({updated: updatedTask.reactions, old: task.reactions})
kanbanStore.setTaskInBucket(updatedTask)
return updatedTask
} finally {
From e1c972d64d8e614b3c423a1644e6d9e01e0f8b28 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 22:05:26 +0100
Subject: [PATCH 31/66] fix(filters): replace project titles at the match
position, not anywhere in the filter string
This fixes a bug where the project title would not be replaced correctly in cases where the project title contained parts of the word "project".
Resolves https://kolaente.dev/vikunja/vikunja/issues/2194
---
frontend/src/helpers/filters.test.ts | 14 ++++++++++++--
frontend/src/helpers/filters.ts | 9 ++++++++-
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/frontend/src/helpers/filters.test.ts b/frontend/src/helpers/filters.test.ts
index 6f04f5757..2a71d9f9f 100644
--- a/frontend/src/helpers/filters.test.ts
+++ b/frontend/src/helpers/filters.test.ts
@@ -18,7 +18,7 @@ describe('Filter Transformation', () => {
'labels': 'labels',
}
- describe('For api', () => {
+ describe('For API', () => {
for (const c in fieldCases) {
it('should transform all filter params for ' + c + ' to snake_case', () => {
const transformed = transformFilterStringForApi(c + ' = ipsum', nullTitleToIdResolver, nullTitleToIdResolver)
@@ -97,6 +97,16 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('project in 1, 2')
})
+
+ it('should resolve projects at the correct position', () => {
+ const transformed = transformFilterStringForApi(
+ 'project = pr',
+ nullTitleToIdResolver,
+ (title: string) => 1,
+ )
+
+ expect(transformed).toBe('project = 1')
+ })
})
describe('To API', () => {
@@ -138,7 +148,7 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('labels = lorem && dueDate = now && labels = ipsum')
})
-
+
it('should correctly resolve multiple labels in', () => {
const transformed = transformFilterStringFromApi(
'labels in 1, 2',
diff --git a/frontend/src/helpers/filters.ts b/frontend/src/helpers/filters.ts
index 4a15696e3..7f9a39e10 100644
--- a/frontend/src/helpers/filters.ts
+++ b/frontend/src/helpers/filters.ts
@@ -108,12 +108,19 @@ export function transformFilterStringForApi(
keywords = keyword.trim().split(',').map(k => k.trim())
}
+ let replaced = keyword
+
keywords.forEach(k => {
const projectId = projectResolver(k)
if (projectId !== null) {
- filter = filter.replace(k, String(projectId))
+ replaced = replaced.replace(k, String(projectId))
}
})
+
+ const actualKeywordStart = (match?.index || 0) + prefix.length
+ filter = filter.substring(0, actualKeywordStart) +
+ replaced +
+ filter.substring(actualKeywordStart + keyword.length)
}
}
})
From 5b2b7f7bdc2e03f011bcee5404ef8308d689dcc3 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Tue, 12 Mar 2024 22:23:35 +0100
Subject: [PATCH 32/66] fix(kanban): reset done and default bucket when the
bucket itself is deleted
Resolves https://github.com/go-vikunja/vikunja/issues/234
---
pkg/models/kanban.go | 30 +++++++++++++++++++++++-------
pkg/models/kanban_test.go | 17 +++++++++++++++++
2 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go
index 597420a97..0b6adaffc 100644
--- a/pkg/models/kanban.go
+++ b/pkg/models/kanban.go
@@ -310,7 +310,7 @@ func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/buckets/{bucketID} [delete]
-func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
+func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
// Prevent removing the last bucket
total, err := s.Where("project_id = ?", b.ProjectID).Count(&Bucket{})
@@ -324,17 +324,27 @@ func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
}
}
- // Remove the bucket itself
- _, err = s.Where("id = ?", b.ID).Delete(&Bucket{})
- if err != nil {
- return
- }
-
// Get the default bucket
p, err := GetProjectSimpleByID(s, b.ProjectID)
if err != nil {
return
}
+ var updateProject bool
+ if b.ID == p.DefaultBucketID {
+ p.DefaultBucketID = 0
+ updateProject = true
+ }
+ if b.ID == p.DoneBucketID {
+ p.DoneBucketID = 0
+ updateProject = true
+ }
+ if updateProject {
+ err = p.Update(s, a)
+ if err != nil {
+ return
+ }
+ }
+
defaultBucketID, err := getDefaultBucketID(s, p)
if err != nil {
return err
@@ -345,5 +355,11 @@ func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
Where("bucket_id = ?", b.ID).
Cols("bucket_id").
Update(&Task{BucketID: defaultBucketID})
+ if err != nil {
+ return
+ }
+
+ // Remove the bucket itself
+ _, err = s.Where("id = ?", b.ID).Delete(&Bucket{})
return
}
diff --git a/pkg/models/kanban_test.go b/pkg/models/kanban_test.go
index e972b126b..c997d9d18 100644
--- a/pkg/models/kanban_test.go
+++ b/pkg/models/kanban_test.go
@@ -197,6 +197,23 @@ func TestBucket_Delete(t *testing.T) {
"project_id": 18,
}, false)
})
+ t.Run("done bucket should be reset", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ b := &Bucket{
+ ID: 3,
+ ProjectID: 1,
+ }
+ err := b.Delete(s, user)
+ require.NoError(t, err)
+
+ db.AssertMissing(t, "projects", map[string]interface{}{
+ "id": 1,
+ "done_bucket_id": 3,
+ })
+ })
}
func TestBucket_Update(t *testing.T) {
From fb5b2542a5774c59e0d93798f3afb2fbb94ab6ac Mon Sep 17 00:00:00 2001
From: "Frederick [Bot]"
Date: Wed, 13 Mar 2024 00:05:52 +0000
Subject: [PATCH 33/66] chore(i18n): update translations via Crowdin
---
frontend/src/i18n/lang/ar-SA.json | 74 ++++++++++++++++++++-
frontend/src/i18n/lang/ca-ES.json | 74 ++++++++++++++++++++-
frontend/src/i18n/lang/cs-CZ.json | 90 +++++++++++++++++++++++---
frontend/src/i18n/lang/da-DK.json | 74 ++++++++++++++++++++-
frontend/src/i18n/lang/de-DE.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/de-swiss.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/eo-UY.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/es-ES.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/fr-FR.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/hu-HU.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/it-IT.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/ja-JP.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/ko-KR.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/nl-NL.json | 74 ++++++++++++++++++++-
frontend/src/i18n/lang/no-NO.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/pl-PL.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/pt-BR.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/pt-PT.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/ro-RO.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/ru-RU.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/sk-SK.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/sl-SI.json | 96 +++++++++++++++++++++++++---
frontend/src/i18n/lang/sr-CS.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/sv-SE.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/tr-TR.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/vi-VN.json | 80 ++++++++++++++++++++++-
frontend/src/i18n/lang/zh-CN.json | 74 ++++++++++++++++++++-
frontend/src/i18n/lang/zh-TW.json | 74 ++++++++++++++++++++-
28 files changed, 2186 insertions(+), 44 deletions(-)
diff --git a/frontend/src/i18n/lang/ar-SA.json b/frontend/src/i18n/lang/ar-SA.json
index 813ded57e..79b5682fa 100644
--- a/frontend/src/i18n/lang/ar-SA.json
+++ b/frontend/src/i18n/lang/ar-SA.json
@@ -248,6 +248,7 @@
"text2": "هذا يشمل جميع المهام ولا يمكن التراجع عن هذا الإجراء!",
"success": "تم حذف المشروع بنجاح.",
"tasksToDelete": "سيؤدي هذا إلى حذف ما يقارب {count} من المهام.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "هذا المشروع لا يحتوي على أي مهام، يمكن حذفه بشكل آمن."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "الفلاتر",
"clear": "مسح الفلاتر",
+ "showResults": "Show results",
"attributes": {
"title": "العنوان",
"titlePlaceholder": "عنوان الفلتر المحفوظ هنا…",
@@ -415,6 +417,52 @@
"edit": {
"title": "تعديل هذا الفلتر المحفوظ",
"success": "تم حفظ الفلتر بنجاح."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "إلى",
"from": "من",
"fromto": "{from} إلى {to}",
+ "date": "Date",
"ranges": {
"today": "اليوم",
"thisWeek": "هذا الأسبوع",
@@ -598,6 +647,27 @@
"lastMonth": "الشهر الماضي",
"thisYear": "هذه السنة",
"restOfThisYear": "المتبقي من هذه السنة"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "الوصف",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "مشرف",
- "member": "عضو"
+ "member": "عضو",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/i18n/lang/ca-ES.json b/frontend/src/i18n/lang/ca-ES.json
index 3c444813e..2028930a3 100644
--- a/frontend/src/i18n/lang/ca-ES.json
+++ b/frontend/src/i18n/lang/ca-ES.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/i18n/lang/cs-CZ.json b/frontend/src/i18n/lang/cs-CZ.json
index 8447dfa5c..f91a44be3 100644
--- a/frontend/src/i18n/lang/cs-CZ.json
+++ b/frontend/src/i18n/lang/cs-CZ.json
@@ -57,11 +57,11 @@
"logout": "Odhlásit se",
"emailInvalid": "Prosím zadejte platnou emailovou adresu.",
"usernameRequired": "Zadejte prosím uživatelské jméno.",
- "usernameMustNotContainSpace": "The username must not contain spaces.",
- "usernameMustNotLookLikeUrl": "The username must not look like a URL.",
+ "usernameMustNotContainSpace": "Uživatelské jméno nesmí obsahovat mezery.",
+ "usernameMustNotLookLikeUrl": "Uživatelské jméno nesmí vypadat jako adresa URL.",
"passwordRequired": "Zadejte prosím heslo.",
- "passwordNotMin": "Password must have at least 8 characters.",
- "passwordNotMax": "Password must have at most 250 characters.",
+ "passwordNotMin": "Heslo musí mít nejméně 8 znaků.",
+ "passwordNotMax": "Heslo může mít maximálně 250 znaků.",
"showPassword": "Ukázat heslo",
"hidePassword": "Skrýt heslo",
"noAccountYet": "Ještě nemáte účet?",
@@ -248,6 +248,7 @@
"text2": "To zahrnuje všechny úkoly a JE TO NEVRATNÉ!",
"success": "Projekt byl úspěšně smazán.",
"tasksToDelete": "Neodvolatelně tím odstraníme asi {count} úloh.",
+ "tasksAndChildProjectsToDelete": "Chystáte se neodvolatelně odstranit nějaké úkoly (cca {tasks}) a projekty (cca {projects}).",
"noTasksToDelete": "Tento projekt neobsahuje žádné úkoly, mělo by být bezpečné ho smazat."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtry",
"clear": "Vymazat filtry",
+ "showResults": "Zobrazit výsledky",
"attributes": {
"title": "Název",
"titlePlaceholder": "Název uloženého filtru přijde sem…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Upravit tento uložený filtr",
"success": "Filtr byl úspěšně uložen."
+ },
+ "query": {
+ "title": "Dotaz",
+ "placeholder": "Zadejte vyhledávací nebo filtrační dotaz…",
+ "help": {
+ "intro": "K filtrování úkolů můžete použít syntaxi dotazů podobnou SQL. Dostupná pole pro filtrování zahrnují:",
+ "link": "Jak to funguje?",
+ "canUseDatemath": "Můžete použít matematiku a nastavit relativní data. Pro více informací klikněte na datum v dotazu.",
+ "fields": {
+ "done": "Zda je úkol dokončen nebo ne",
+ "priority": "Úroveň priority úkolu (1-5)",
+ "percentDone": "Procento dokončení úkolu (0-100)",
+ "dueDate": "Datum dokončení úkolu",
+ "startDate": "Datum zahájení úkolu",
+ "endDate": "Datum dokončení úkolu",
+ "doneAt": "Datum a čas, kdy byl úkol dokončen",
+ "assignees": "Přiřazení uživatelé",
+ "labels": "Štítky přiřazené úkolu",
+ "project": "Projekt, do kterého úkol patří (k dispozici pouze pro uložené filtry, ne na úrovni projektu)"
+ },
+ "operators": {
+ "intro": "Dostupné operátory pro filtrování zahrnují:",
+ "notEqual": "Nerovná se",
+ "equal": "Rovná se",
+ "greaterThan": "Větší než",
+ "greaterThanOrEqual": "Větší nebo rovno než",
+ "lessThan": "Menší než",
+ "lessThanOrEqual": "Menší nebo rovno než",
+ "like": "Odpovídá vzoru (s použitím zástupného znaku %)",
+ "in": "Odpovídá libovolné hodnotě v seznamu hodnot oddělených čárkou"
+ },
+ "logicalOperators": {
+ "intro": "Pro kombinování více podmínek můžete použít tyto logické operátory:",
+ "and": "AND - shoduje se, pokud jsou splněny všechny podmínky",
+ "or": "OR - shoduje se, pokud je splněna alespoň jedna podmínka",
+ "parentheses": "Závorky seskupují podmínky"
+ },
+ "examples": {
+ "intro": "Zde jsou některé příklady filtrovacích dotazů:",
+ "priorityEqual": "Odpovídá úkolům s úrovní priority 4",
+ "dueDatePast": "Odpovídá úkolům s termínem dokončení v minulosti",
+ "undoneHighPriority": "Odpovídá nedokončeným úkolům s úrovní priority 3 nebo vyšší",
+ "assigneesIn": "Odpovídá úkolům přiřazeným buď \"uživatel1\", nebo \"uživatel2\"",
+ "priorityOneOrTwoPastDue": "Odpovídá úkolům s prioritou 1 nebo 2 a termínem dokončení v minulosti"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Do",
"from": "Od",
"fromto": "{from} – {to}",
+ "date": "Datum",
"ranges": {
"today": "Dnes",
"thisWeek": "Tento týden",
@@ -598,6 +647,27 @@
"lastMonth": "Minulý měsíc",
"thisYear": "Tento rok",
"restOfThisYear": "Zbytek tohoto roku"
+ },
+ "values": {
+ "now": "Teď",
+ "startOfToday": "Začátek dnes",
+ "endOfToday": "Konec dnes",
+ "beginningOflastWeek": "Začátek minulého týdne",
+ "endOfLastWeek": "Konec posledního týdne",
+ "beginningOfThisWeek": "Začátek tohoto týdne",
+ "endOfThisWeek": "Konec tohoto týdne",
+ "startOfNextWeek": "Začátek příštího týdne",
+ "endOfNextWeek": "Konec tohoto týdne",
+ "in7Days": "Za 7 dní",
+ "beginningOfLastMonth": "Začátek minulého měsíce",
+ "endOfLastMonth": "Konec minulého měsíce",
+ "startOfThisMonth": "Začátek tohoto měsíce",
+ "endOfThisMonth": "Konec tohoto měsíce",
+ "startOfNextMonth": "Začátek příštího měsíce",
+ "endOfNextMonth": "Konec příštího měsíce",
+ "in30Days": "Za 30 dní",
+ "startOfThisYear": "Začátek tohoto roku",
+ "endOfThisYear": "Konec tohoto roku"
}
},
"datemathHelp": {
@@ -713,7 +783,7 @@
"startDate": "Počáteční datum",
"title": "Název",
"updated": "Aktualizováno",
- "doneAt": "Done At"
+ "doneAt": "Dokončeno"
},
"subscription": {
"subscribedTaskThroughParentProject": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto úkolu prostřednictvím jeho projektu.",
@@ -916,7 +986,9 @@
"description": "Popis",
"descriptionPlaceholder": "Popište tým, stiskněte '/' pro více možností…",
"admin": "Administrátor",
- "member": "Člen"
+ "member": "Člen",
+ "isPublic": "Veřejný tým",
+ "isPublicDescription": "Učinit tým veřejně dostupným. Pokud je povoleno, každý může s tímto týmem sdílet projekty, i když není přímým členem."
}
},
"keyboardShortcuts": {
@@ -975,8 +1047,8 @@
"share": "Sdílet",
"newProject": "Nový projekt",
"createProject": "Vytvořit projekt",
- "cantArchiveIsDefault": "You cannot archive this because it is your default project.",
- "cantDeleteIsDefault": "You cannot delete this because it is your default project."
+ "cantArchiveIsDefault": "Nemůžete archivovat svůj výchozí projekt.",
+ "cantDeleteIsDefault": "Nemůžete smazat svůj výchozí projekt."
},
"apiConfig": {
"url": "Vikunja URL",
@@ -1096,7 +1168,7 @@
},
"about": {
"title": "O aplikaci",
- "version": "Version: {version}"
+ "version": "Verze: {version}"
},
"time": {
"units": {
diff --git a/frontend/src/i18n/lang/da-DK.json b/frontend/src/i18n/lang/da-DK.json
index 73021a2d1..ab9bd8a9e 100644
--- a/frontend/src/i18n/lang/da-DK.json
+++ b/frontend/src/i18n/lang/da-DK.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtre",
"clear": "Ryd Filtre",
+ "showResults": "Show results",
"attributes": {
"title": "Titel",
"titlePlaceholder": "Det gemte filters titel skrives her…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Rediger Dette Gemte Filter",
"success": "Filteret blev slettet."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Til",
"from": "Fra",
"fromto": "{from} til {to}",
+ "date": "Date",
"ranges": {
"today": "I dag",
"thisWeek": "Denne uge",
@@ -598,6 +647,27 @@
"lastMonth": "Sidste måned",
"thisYear": "Dette år",
"restOfThisYear": "Resten af dette år"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beskrivelse",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Administrator",
- "member": "Medlem"
+ "member": "Medlem",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json
index 92969b72f..78a806353 100644
--- a/frontend/src/i18n/lang/de-DE.json
+++ b/frontend/src/i18n/lang/de-DE.json
@@ -248,6 +248,7 @@
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
"success": "Das Projekt wurde erfolgreich gelöscht.",
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
+ "tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filter",
"clear": "Filter zurücksetzen",
+ "showResults": "Ergebnisse anzeigen",
"attributes": {
"title": "Titel",
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
@@ -415,6 +417,52 @@
"edit": {
"title": "Diesen gespeicherten Filter bearbeiten",
"success": "Filter gespeichert."
+ },
+ "query": {
+ "title": "Abfrage",
+ "placeholder": "Gib eine Suche oder Filterabfrage ein…",
+ "help": {
+ "intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
+ "link": "Wie funktioniert das?",
+ "canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
+ "fields": {
+ "done": "Ob die Aufgabe erledigt ist oder nicht",
+ "priority": "Die Priorität der Aufgabe (1-5)",
+ "percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
+ "dueDate": "Das Fälligkeitsdatum der Aufgabe",
+ "startDate": "Das Startdatum der Aufgabe",
+ "endDate": "Das Enddatum der Aufgabe",
+ "doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
+ "assignees": "Die der Aufgabe Zugewiesenen",
+ "labels": "Die der Aufgabe zugeordneten Labels",
+ "project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
+ },
+ "operators": {
+ "intro": "Die verfügbaren Operatoren für die Filterung sind:",
+ "notEqual": "Ungleich",
+ "equal": "Gleich",
+ "greaterThan": "Größer als",
+ "greaterThanOrEqual": "Größer oder gleich",
+ "lessThan": "Kleiner als",
+ "lessThanOrEqual": "Kleiner als oder gleich",
+ "like": "Vergleicht zu einem Muster (mit Platzhalter %)",
+ "in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
+ },
+ "logicalOperators": {
+ "intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
+ "and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
+ "or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
+ "parentheses": "Klammern zum Gruppieren von Bedingungen"
+ },
+ "examples": {
+ "intro": "Hier sind einige Beispiele für Filterabfragen:",
+ "priorityEqual": "Findet Aufgaben mit Priorität Level 4",
+ "dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
+ "undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
+ "assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
+ "priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Bis",
"from": "Von",
"fromto": "{from} bis {to}",
+ "date": "Datum",
"ranges": {
"today": "Heute",
"thisWeek": "Diese Woche",
@@ -598,6 +647,27 @@
"lastMonth": "Letzter Monat",
"thisYear": "Dieses Jahr",
"restOfThisYear": "Der Rest des Jahres"
+ },
+ "values": {
+ "now": "Jetzt",
+ "startOfToday": "Beginn des heutigen Tages",
+ "endOfToday": "Ende des heutigen Tages",
+ "beginningOflastWeek": "Beginn der letzten Woche",
+ "endOfLastWeek": "Ende der letzten Woche",
+ "beginningOfThisWeek": "Beginn dieser Woche",
+ "endOfThisWeek": "Ende dieser Woche",
+ "startOfNextWeek": "Beginn der nächsten Woche",
+ "endOfNextWeek": "Ende der nächsten Woche",
+ "in7Days": "In 7 Tagen",
+ "beginningOfLastMonth": "Beginn des letzten Monats",
+ "endOfLastMonth": "Ende des letzten Monats",
+ "startOfThisMonth": "Beginn diesen Monats",
+ "endOfThisMonth": "Ende diesen Monats",
+ "startOfNextMonth": "Beginn des nächsten Monats",
+ "endOfNextMonth": "Ende des nächsten Monats",
+ "in30Days": "In 30 Tagen",
+ "startOfThisYear": "Beginn dieses Jahres",
+ "endOfThisYear": "Ende dieses Jahres"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beschreibung",
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
"admin": "Admin",
- "member": "Mitglied"
+ "member": "Mitglied",
+ "isPublic": "Öffentliches Team",
+ "isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Fehler",
"success": "Erfolgreich",
diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json
index 5ac9d32f7..15da13b8f 100644
--- a/frontend/src/i18n/lang/de-swiss.json
+++ b/frontend/src/i18n/lang/de-swiss.json
@@ -248,6 +248,7 @@
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
"success": "Das Projekt wurde erfolgreich gelöscht.",
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
+ "tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filter",
"clear": "Filter zurücksetzen",
+ "showResults": "Ergebnisse anzeigen",
"attributes": {
"title": "Titl",
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
@@ -415,6 +417,52 @@
"edit": {
"title": "De g'speicheret Filter bearbeite",
"success": "De filter isch erfolgriich g'speichered wore."
+ },
+ "query": {
+ "title": "Abfrage",
+ "placeholder": "Gib eine Suche oder Filterabfrage ein…",
+ "help": {
+ "intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
+ "link": "Wie funktioniert das?",
+ "canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
+ "fields": {
+ "done": "Ob die Aufgabe erledigt ist oder nicht",
+ "priority": "Die Priorität der Aufgabe (1-5)",
+ "percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
+ "dueDate": "Das Fälligkeitsdatum der Aufgabe",
+ "startDate": "Das Startdatum der Aufgabe",
+ "endDate": "Das Enddatum der Aufgabe",
+ "doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
+ "assignees": "Die der Aufgabe Zugewiesenen",
+ "labels": "Die der Aufgabe zugeordneten Labels",
+ "project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
+ },
+ "operators": {
+ "intro": "Die verfügbaren Operatoren für die Filterung sind:",
+ "notEqual": "Ungleich",
+ "equal": "Gleich",
+ "greaterThan": "Größer als",
+ "greaterThanOrEqual": "Größer oder gleich",
+ "lessThan": "Kleiner als",
+ "lessThanOrEqual": "Kleiner als oder gleich",
+ "like": "Vergleicht zu einem Muster (mit Platzhalter %)",
+ "in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
+ },
+ "logicalOperators": {
+ "intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
+ "and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
+ "or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
+ "parentheses": "Klammern zum Gruppieren von Bedingungen"
+ },
+ "examples": {
+ "intro": "Hier sind einige Beispiele für Filterabfragen:",
+ "priorityEqual": "Findet Aufgaben mit Priorität Level 4",
+ "dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
+ "undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
+ "assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
+ "priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Bis",
"from": "Von",
"fromto": "{from} bis {to}",
+ "date": "Datum",
"ranges": {
"today": "Heute",
"thisWeek": "Diese Woche",
@@ -598,6 +647,27 @@
"lastMonth": "Letzter Monat",
"thisYear": "Dieses Jahr",
"restOfThisYear": "Der Rest des Jahres"
+ },
+ "values": {
+ "now": "Jetzt",
+ "startOfToday": "Beginn des heutigen Tages",
+ "endOfToday": "Ende des heutigen Tages",
+ "beginningOflastWeek": "Beginn der letzten Woche",
+ "endOfLastWeek": "Ende der letzten Woche",
+ "beginningOfThisWeek": "Beginn dieser Woche",
+ "endOfThisWeek": "Ende dieser Woche",
+ "startOfNextWeek": "Beginn der nächsten Woche",
+ "endOfNextWeek": "Ende der nächsten Woche",
+ "in7Days": "In 7 Tagen",
+ "beginningOfLastMonth": "Beginn des letzten Monats",
+ "endOfLastMonth": "Ende des letzten Monats",
+ "startOfThisMonth": "Beginn diesen Monats",
+ "endOfThisMonth": "Ende diesen Monats",
+ "startOfNextMonth": "Beginn des nächsten Monats",
+ "endOfNextMonth": "Ende des nächsten Monats",
+ "in30Days": "In 30 Tagen",
+ "startOfThisYear": "Beginn dieses Jahres",
+ "endOfThisYear": "Ende dieses Jahres"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beschriibig",
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
"admin": "Chef",
- "member": "Mitglied"
+ "member": "Mitglied",
+ "isPublic": "Öffentliches Team",
+ "isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Fähler",
"success": "Erfolg",
diff --git a/frontend/src/i18n/lang/eo-UY.json b/frontend/src/i18n/lang/eo-UY.json
index 3c444813e..1ba512373 100644
--- a/frontend/src/i18n/lang/eo-UY.json
+++ b/frontend/src/i18n/lang/eo-UY.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/es-ES.json b/frontend/src/i18n/lang/es-ES.json
index 16536a433..85ce3246c 100644
--- a/frontend/src/i18n/lang/es-ES.json
+++ b/frontend/src/i18n/lang/es-ES.json
@@ -248,6 +248,7 @@
"text2": "¡Esto incluye todas las tareas y NO SE PUEDE DESHACER!",
"success": "El proyecto se eliminó con éxito.",
"tasksToDelete": "Esto eliminará de forma definitiva aprox. {count} tareas.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Este proyecto no contiene tareas. Debería ser seguro eliminarlo."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtros",
"clear": "Limpiar Filtros",
+ "showResults": "Show results",
"attributes": {
"title": "Título",
"titlePlaceholder": "El título del filtro guardado va aquí…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Editar este Filtro Guardado",
"success": "El filtro se guardó con éxito."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Para",
"from": "De",
"fromto": "De {from} para {to}",
+ "date": "Date",
"ranges": {
"today": "Hoy",
"thisWeek": "Esta Semana",
@@ -598,6 +647,27 @@
"lastMonth": "El Mes Pasado",
"thisYear": "Este Año",
"restOfThisYear": "El Resto del Año"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Descripción",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Miembro"
+ "member": "Miembro",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Éxito",
diff --git a/frontend/src/i18n/lang/fr-FR.json b/frontend/src/i18n/lang/fr-FR.json
index 5e9d6fc91..ed12f9d9c 100644
--- a/frontend/src/i18n/lang/fr-FR.json
+++ b/frontend/src/i18n/lang/fr-FR.json
@@ -248,6 +248,7 @@
"text2": "Ceci inclut toutes les tâches et NE PEUT PAS ÊTRE ANNULÉ !",
"success": "Le projet a bien été supprimé.",
"tasksToDelete": "Cela supprimera définitivement environ {count} tâches.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Ce projet ne contient aucune tâche, vous pouvez le supprimer sans problème."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtres",
"clear": "Effacer les filtres",
+ "showResults": "Show results",
"attributes": {
"title": "Nom",
"titlePlaceholder": "Entre un nom de filtre enregistré…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Modifier ce filtre enregistré",
"success": "Filtre enregistré."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "À",
"from": "De",
"fromto": "Du {from} au {to}",
+ "date": "Date",
"ranges": {
"today": "Aujourd’hui",
"thisWeek": "Cette semaine",
@@ -598,6 +647,27 @@
"lastMonth": "Le mois dernier",
"thisYear": "Cette année",
"restOfThisYear": "Le reste de cette année"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Membre"
+ "member": "Membre",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Erreur",
"success": "Succès",
diff --git a/frontend/src/i18n/lang/hu-HU.json b/frontend/src/i18n/lang/hu-HU.json
index 3163bec38..fa5aca3e0 100644
--- a/frontend/src/i18n/lang/hu-HU.json
+++ b/frontend/src/i18n/lang/hu-HU.json
@@ -248,6 +248,7 @@
"text2": "Ez magában foglalja az összes feladatot és NEM VISSZAVONHATÓ!",
"success": "A projekt sikeresen törölve.",
"tasksToDelete": "Ezzel visszavonhatatlanul eltávolítjuk kb. {count} feladatát.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Ez a projekt nem tartalmaz feladatokat, biztonságosan törölhető."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Szűrők",
"clear": "Szűrők törlése",
+ "showResults": "Show results",
"attributes": {
"title": "Cím",
"titlePlaceholder": "A mentett szűrő címe ide kerül…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Mentett szűrő szerkesztése",
"success": "A szűrőt sikeresen mentette."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Eddig",
"from": "Ettől",
"fromto": "{from} - tól {to} - ig",
+ "date": "Date",
"ranges": {
"today": "Ma",
"thisWeek": "Ezen a héten",
@@ -598,6 +647,27 @@
"lastMonth": "Előző hónap",
"thisYear": "Aktuális év",
"restOfThisYear": "Az év hátralévő része"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Leírás",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Adminisztrátor",
- "member": "Tag"
+ "member": "Tag",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Hiba",
"success": "Siker",
diff --git a/frontend/src/i18n/lang/it-IT.json b/frontend/src/i18n/lang/it-IT.json
index 1384b3a8a..934b5e61d 100644
--- a/frontend/src/i18n/lang/it-IT.json
+++ b/frontend/src/i18n/lang/it-IT.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtri",
"clear": "Pulisci Filtri",
+ "showResults": "Show results",
"attributes": {
"title": "Titolo",
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Modifica Questo Filtro Salvato",
"success": "Filtro salvato."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "A",
"from": "Da",
"fromto": "da {from} a {to}",
+ "date": "Date",
"ranges": {
"today": "Oggi",
"thisWeek": "Questa Settimana",
@@ -598,6 +647,27 @@
"lastMonth": "Mese scorso",
"thisYear": "Quest'anno",
"restOfThisYear": "Il resto di quest'anno"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Descrizione",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Amministratore",
- "member": "Membro"
+ "member": "Membro",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Errore",
"success": "Fatto",
diff --git a/frontend/src/i18n/lang/ja-JP.json b/frontend/src/i18n/lang/ja-JP.json
index ec3cb11e7..5050264b0 100644
--- a/frontend/src/i18n/lang/ja-JP.json
+++ b/frontend/src/i18n/lang/ja-JP.json
@@ -248,6 +248,7 @@
"text2": "このプロジェクトに含まれるタスクはすべて削除されます。この操作は元に戻せません。",
"success": "プロジェクトは正常に削除されました。",
"tasksToDelete": "約{count}件のタスクが抹消されます。",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "このプロジェクトにはタスクが含まれていないので問題なく削除できます。"
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "絞り込み",
"clear": "絞り込みの解除",
+ "showResults": "Show results",
"attributes": {
"title": "絞り込み条件名",
"titlePlaceholder": "絞り込み条件名を入力…",
@@ -415,6 +417,52 @@
"edit": {
"title": "絞り込み条件の編集",
"success": "絞り込み条件は正常に保存されました。"
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "タスクの終了日",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "終了",
"from": "開始",
"fromto": "{from} 〜 {to} まで",
+ "date": "Date",
"ranges": {
"today": "今日",
"thisWeek": "今週",
@@ -598,6 +647,27 @@
"lastMonth": "先月",
"thisYear": "今年",
"restOfThisYear": "今から年末まで"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "今日の終わり",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "先週の終わり",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "今週の終わり",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "来週の終わり",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "先月の終わり",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "今月の終わり",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "来月の終わり",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "今年の終わり"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "説明",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "管理者",
- "member": "メンバー"
+ "member": "メンバー",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "Y/n/j H:i",
"altFormatShort": "Y/n/j"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/ko-KR.json b/frontend/src/i18n/lang/ko-KR.json
index 9d317edc9..2ccb6833b 100644
--- a/frontend/src/i18n/lang/ko-KR.json
+++ b/frontend/src/i18n/lang/ko-KR.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "프로젝트가 성공적으로 삭제되었습니다.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "필터",
"clear": "필터 초기화",
+ "showResults": "Show results",
"attributes": {
"title": "제목",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} 에서 {to}",
+ "date": "Date",
"ranges": {
"today": "오늘",
"thisWeek": "이번 주",
@@ -598,6 +647,27 @@
"lastMonth": "지난달",
"thisYear": "올해",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/nl-NL.json b/frontend/src/i18n/lang/nl-NL.json
index a376841ca..d5824f257 100644
--- a/frontend/src/i18n/lang/nl-NL.json
+++ b/frontend/src/i18n/lang/nl-NL.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Titel",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "Het filter is succesvol opgeslagen."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beschrijving",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Beheerder",
- "member": "Lid"
+ "member": "Lid",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/i18n/lang/no-NO.json b/frontend/src/i18n/lang/no-NO.json
index 0acd59186..10b2f0568 100644
--- a/frontend/src/i18n/lang/no-NO.json
+++ b/frontend/src/i18n/lang/no-NO.json
@@ -248,6 +248,7 @@
"text2": "Dette inkluderer alle oppgaver og KAN IKKE ANGRES!",
"success": "Prosjektet ble slettet.",
"tasksToDelete": "Dette vil ugjenkallelig fjerne ca. {count} oppgaver.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Dette prosjektet inneholder ingen oppgaver, det bør være trygt å slette."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtre",
"clear": "Fjern filtre",
+ "showResults": "Show results",
"attributes": {
"title": "Tittel",
"titlePlaceholder": "Den lagrede filtertittelen kommer hit…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Rediger dette lagrede filteret",
"success": "Filteret ble lagret."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Til",
"from": "Fra",
"fromto": "{from} til {to}",
+ "date": "Date",
"ranges": {
"today": "Idag",
"thisWeek": "Denne uken",
@@ -598,6 +647,27 @@
"lastMonth": "Forrige måned",
"thisYear": "Dette året",
"restOfThisYear": "Resten av denne uken"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beskrivelse",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Administrator",
- "member": "Medlem"
+ "member": "Medlem",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "d.m.Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Feil",
"success": "Suksess",
diff --git a/frontend/src/i18n/lang/pl-PL.json b/frontend/src/i18n/lang/pl-PL.json
index bfa4b752c..d164a0a31 100644
--- a/frontend/src/i18n/lang/pl-PL.json
+++ b/frontend/src/i18n/lang/pl-PL.json
@@ -248,6 +248,7 @@
"text2": "Dotyczy to wszystkich zadań i tego NIE DA SIĘ COFNĄĆ!",
"success": "Projekt został pomyślnie usunięty.",
"tasksToDelete": "To nieodwracalnie usunie około {count} zadań.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Ten projekt nie zawiera żadnych zadań, więc można go bezpiecznie usunąć."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtry",
"clear": "Wyczyść filtry",
+ "showResults": "Show results",
"attributes": {
"title": "Tytuł",
"titlePlaceholder": "Tu wpisz tytuł filtra stałego…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edytuj ten filtr stały",
"success": "Filtr został pomyślnie zapisany."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Do",
"from": "Od",
"fromto": "{from} do {to}",
+ "date": "Date",
"ranges": {
"today": "Dziś",
"thisWeek": "W tym tygodniu",
@@ -598,6 +647,27 @@
"lastMonth": "Zeszły miesiąc",
"thisYear": "Ten rok",
"restOfThisYear": "Reszta tego roku"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Opis",
"descriptionPlaceholder": "Opisz tutaj zespół, naciśnij '/' aby uzyskać więcej opcji…",
"admin": "Administrator",
- "member": "Członek"
+ "member": "Członek",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Błąd",
"success": "Sukces",
diff --git a/frontend/src/i18n/lang/pt-BR.json b/frontend/src/i18n/lang/pt-BR.json
index 2c22590ac..61a4b9f06 100644
--- a/frontend/src/i18n/lang/pt-BR.json
+++ b/frontend/src/i18n/lang/pt-BR.json
@@ -248,6 +248,7 @@
"text2": "Isso inclui todas as tarefas e NÃO PODE SER DESFEITO!",
"success": "Seu projeto foi excluído com sucesso.",
"tasksToDelete": "Isto irá remover permanentemente approx. {count} tarefas.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Este projeto não contém tarefas, é seguro excluir."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtros",
"clear": "Limpar Filtros",
+ "showResults": "Show results",
"attributes": {
"title": "Título",
"titlePlaceholder": "O título do filtro salvo fica aqui…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Editar este filtro salvo",
"success": "O filtro foi salvo com sucesso."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Para",
"from": "De",
"fromto": "{from} até {to}",
+ "date": "Date",
"ranges": {
"today": "Hoje",
"thisWeek": "Esta semana",
@@ -598,6 +647,27 @@
"lastMonth": "Último mês",
"thisYear": "Este ano",
"restOfThisYear": "O resto deste ano"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Descrição",
"descriptionPlaceholder": "Descreva a equipe aqui, aperte '/' para mais opções…",
"admin": "Administrador",
- "member": "Membro"
+ "member": "Membro",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Erro",
"success": "Sucesso",
diff --git a/frontend/src/i18n/lang/pt-PT.json b/frontend/src/i18n/lang/pt-PT.json
index 0403b4c7b..57ab411cd 100644
--- a/frontend/src/i18n/lang/pt-PT.json
+++ b/frontend/src/i18n/lang/pt-PT.json
@@ -248,6 +248,7 @@
"text2": "Isto inclui todas as tarefas e NÃO PODE SER REVERTIDO!",
"success": "O projeto foi eliminado com sucesso.",
"tasksToDelete": "Isto irá remover irrevogavelmente aprox. {count} tarefas.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "Este projeto não contém tarefas, deve ser seguro eliminá-lo."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtros",
"clear": "Limpar Filtros",
+ "showResults": "Show results",
"attributes": {
"title": "Título",
"titlePlaceholder": "O título do filtro memorizado será aqui…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Editar Este Filtro Memorizado",
"success": "O filtro foi memorizado com sucesso."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Até",
"from": "De",
"fromto": "{from} até {to}",
+ "date": "Date",
"ranges": {
"today": "Hoje",
"thisWeek": "Esta semana",
@@ -598,6 +647,27 @@
"lastMonth": "Mês Passado",
"thisYear": "Este Ano",
"restOfThisYear": "O Resto Deste Ano"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Descrição",
"descriptionPlaceholder": "Descreve aqui a equipa, pressiona '/' para mais opções…",
"admin": "Administrador",
- "member": "Membro"
+ "member": "Membro",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Erro",
"success": "Sucesso",
diff --git a/frontend/src/i18n/lang/ro-RO.json b/frontend/src/i18n/lang/ro-RO.json
index 3c444813e..1ba512373 100644
--- a/frontend/src/i18n/lang/ro-RO.json
+++ b/frontend/src/i18n/lang/ro-RO.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/ru-RU.json b/frontend/src/i18n/lang/ru-RU.json
index c2186a5cf..7ac95bdad 100644
--- a/frontend/src/i18n/lang/ru-RU.json
+++ b/frontend/src/i18n/lang/ru-RU.json
@@ -248,6 +248,7 @@
"text2": "Это включает в себя все задачи, и отменить это будет нельзя!",
"success": "Проект успешно удалён.",
"tasksToDelete": "Это безвозвратно удалит примерно {count} задач.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "В этом проекте нет никаких задач, можно спокойно удалять."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Фильтры",
"clear": "Сбросить фильтры",
+ "showResults": "Show results",
"attributes": {
"title": "Название",
"titlePlaceholder": "Введите название сохранённого фильтра…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Изменить этот сохранённый фильтр",
"success": "Фильтр сохранён."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "По",
"from": "С",
"fromto": "С {from} по {to}",
+ "date": "Date",
"ranges": {
"today": "Сегодня",
"thisWeek": "Эта неделя",
@@ -598,6 +647,27 @@
"lastMonth": "Прошлый месяц",
"thisYear": "Этот год",
"restOfThisYear": "Остаток этого года"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Описание",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Администратор",
- "member": "Участник"
+ "member": "Участник",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Ошибка",
"success": "Успех",
diff --git a/frontend/src/i18n/lang/sk-SK.json b/frontend/src/i18n/lang/sk-SK.json
index 3c444813e..1ba512373 100644
--- a/frontend/src/i18n/lang/sk-SK.json
+++ b/frontend/src/i18n/lang/sk-SK.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/sl-SI.json b/frontend/src/i18n/lang/sl-SI.json
index e162c0a26..ed4073644 100644
--- a/frontend/src/i18n/lang/sl-SI.json
+++ b/frontend/src/i18n/lang/sl-SI.json
@@ -57,11 +57,11 @@
"logout": "Odjava",
"emailInvalid": "Prosim vnesite veljaven e-poštni naslov.",
"usernameRequired": "Prosim vnesite uporabniško ime.",
- "usernameMustNotContainSpace": "The username must not contain spaces.",
- "usernameMustNotLookLikeUrl": "The username must not look like a URL.",
+ "usernameMustNotContainSpace": "Uporabniško ime ne sme vsebovati presledkov.",
+ "usernameMustNotLookLikeUrl": "Uporabniško ime ne sme izgledati kot URL.",
"passwordRequired": "Prosim vnesite geslo.",
- "passwordNotMin": "Password must have at least 8 characters.",
- "passwordNotMax": "Password must have at most 250 characters.",
+ "passwordNotMin": "Geslo mora imeti vsaj 8 znakov.",
+ "passwordNotMax": "Geslo mora imeti največ 250 znakov.",
"showPassword": "Prikažite geslo",
"hidePassword": "Skrijte geslo",
"noAccountYet": "Še nimate računa?",
@@ -248,6 +248,7 @@
"text2": "To vključuje vse naloge in GA NI MOGOČE RAZVELJAVITI!",
"success": "Projekt je bil uspešno izbrisan.",
"tasksToDelete": "S tem bo nepreklicno odstranjeno približno {count} nalog.",
+ "tasksAndChildProjectsToDelete": "S tem bo nepreklicno odstranjeno cca. {tasks} nalog in {projects} projektov.",
"noTasksToDelete": "Ta projekt ne vsebuje nobenih nalog, zato ga lahko varno izbrišete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filtri",
"clear": "Počisti filtre",
+ "showResults": "Prikaži rezultate",
"attributes": {
"title": "Naslov",
"titlePlaceholder": "Tu je naslov shranjenega filtra…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Uredi shranjeni filter",
"success": "Filter je bil uspešno shranjen."
+ },
+ "query": {
+ "title": "Poizvedba",
+ "placeholder": "Vnesite poizvedbo za iskanje ali filtriranje…",
+ "help": {
+ "intro": "Za filtriranje nalog lahko uporabite sintakso poizvedbe, podobno SQL. Razpoložljiva polja za filtriranje vključujejo:",
+ "link": "Kako to deluje?",
+ "canUseDatemath": "Za nastavitev relativnih datumov lahko določite datume. Če želite izvedeti več, kliknite vrednost datuma v poizvedbi.",
+ "fields": {
+ "done": "Ali je naloga opravljena ali ne",
+ "priority": "Stopnja prioritete naloge (1-5)",
+ "percentDone": "Odstotek dokončanja naloge (0–100)",
+ "dueDate": "Datum zapadlosti naloge",
+ "startDate": "Začetni datum opravila",
+ "endDate": "Končni datum opravila",
+ "doneAt": "Datum in čas, ko je bila naloga opravljena",
+ "assignees": "Dodeljeni k nalogi",
+ "labels": "Oznake, povezane z opravilom",
+ "project": "Projekt, ki mu naloga pripada (na voljo samo za shranjene filtre, ne na ravni projekta)"
+ },
+ "operators": {
+ "intro": "Razpoložljivi operaterji za filtriranje vključujejo:",
+ "notEqual": "Ni enako",
+ "equal": "Je enako",
+ "greaterThan": "Večje kot",
+ "greaterThanOrEqual": "Večje ali enako kot",
+ "lessThan": "Manj kot",
+ "lessThanOrEqual": "Manjše ali enako kot",
+ "like": "Ujema se z vzorcem (z nadomestnimi znaki %)",
+ "in": "Ujema se s katero koli vrednostjo na seznamu vrednosti, ločenih z vejico"
+ },
+ "logicalOperators": {
+ "intro": "Če želite združiti več pogojev, lahko uporabite naslednje logične operatorje:",
+ "and": "IN operator, se ujema, če so izpolnjeni vsi pogoji",
+ "or": "ALI operator, se ujema, če je kateri od pogojev resničen",
+ "parentheses": "Oklepaji za združevanje pogojev"
+ },
+ "examples": {
+ "intro": "Tukaj je nekaj primerov filtrirnih poizvedb:",
+ "priorityEqual": "Ujema se z nalogami s prioritetno stopnjo 4",
+ "dueDatePast": "Ujema se z opravili z rokom v preteklosti",
+ "undoneHighPriority": "Ujema se z neopravljenimi opravili s prioritetno stopnjo 3 ali višjo",
+ "assigneesIn": "Ujema se z nalogami, dodeljenimi k \"user1\" ali \"user2\"",
+ "priorityOneOrTwoPastDue": "Ujema se z opravili s prioritetno stopnjo 1 ali 2 in rokom v preteklosti"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Za",
"from": "Od",
"fromto": "{from} do {to}",
+ "date": "Datum",
"ranges": {
"today": "Danes",
"thisWeek": "Ta teden",
@@ -598,6 +647,27 @@
"lastMonth": "Prejšnji mesec",
"thisYear": "Letos",
"restOfThisYear": "Preostanek tega leta"
+ },
+ "values": {
+ "now": "Zdaj",
+ "startOfToday": "Začetek današnjega dne",
+ "endOfToday": "Konec današnjega dne",
+ "beginningOflastWeek": "Začetek prejšnjega tedna",
+ "endOfLastWeek": "Konec prejšnjega tedna",
+ "beginningOfThisWeek": "Začetek tega tedna",
+ "endOfThisWeek": "Konec tega tedna",
+ "startOfNextWeek": "Začetek naslednjega tedna",
+ "endOfNextWeek": "Konec naslednjega tedna",
+ "in7Days": "V 7 dneh",
+ "beginningOfLastMonth": "Začetek prejšnjega meseca",
+ "endOfLastMonth": "Konec prejšnjega meseca",
+ "startOfThisMonth": "Začetek tega meseca",
+ "endOfThisMonth": "Konec tega meseca",
+ "startOfNextMonth": "Začetek naslednjega meseca",
+ "endOfNextMonth": "Konec naslednjega meseca",
+ "in30Days": "V 30 dneh",
+ "startOfThisYear": "Začetek tega leta",
+ "endOfThisYear": "Konec tega leta"
}
},
"datemathHelp": {
@@ -713,7 +783,7 @@
"startDate": "Začetni datum",
"title": "Naslov",
"updated": "Posodobljeno",
- "doneAt": "Done At"
+ "doneAt": "Končano ob"
},
"subscription": {
"subscribedTaskThroughParentProject": "Ker ste na to nalogo naročeni prek njenega projekta, se tu ne morete odjaviti.",
@@ -916,7 +986,9 @@
"description": "Opis",
"descriptionPlaceholder": "Tukaj opiši ekipo, pritisni '/' za več možnosti…",
"admin": "Administrator",
- "member": "Član"
+ "member": "Član",
+ "isPublic": "Javna ekipa",
+ "isPublicDescription": "Naj bo ekipa javno vidna. Ko je omogočeno, lahko vsakdo deli projekte s to ekipo, tudi če ni neposredni član."
}
},
"keyboardShortcuts": {
@@ -975,8 +1047,8 @@
"share": "Skupna raba",
"newProject": "Nov projekt",
"createProject": "Ustvari projekt",
- "cantArchiveIsDefault": "You cannot archive this because it is your default project.",
- "cantDeleteIsDefault": "You cannot delete this because it is your default project."
+ "cantArchiveIsDefault": "Tega ne morete arhivirati, ker je to vaš privzeti projekt.",
+ "cantDeleteIsDefault": "Tega ne morete izbrisati, ker je to vaš privzeti projekt."
},
"apiConfig": {
"url": "Vikunja URL",
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Napaka",
"success": "Uspeh",
@@ -1096,7 +1174,7 @@
},
"about": {
"title": "O programu",
- "version": "Version: {version}"
+ "version": "Verzija: {version}"
},
"time": {
"units": {
diff --git a/frontend/src/i18n/lang/sr-CS.json b/frontend/src/i18n/lang/sr-CS.json
index 3c444813e..1ba512373 100644
--- a/frontend/src/i18n/lang/sr-CS.json
+++ b/frontend/src/i18n/lang/sr-CS.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/sv-SE.json b/frontend/src/i18n/lang/sv-SE.json
index d4540142a..c41c219fe 100644
--- a/frontend/src/i18n/lang/sv-SE.json
+++ b/frontend/src/i18n/lang/sv-SE.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filter",
"clear": "Rensa filter",
+ "showResults": "Visa resultat",
"attributes": {
"title": "Titel",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Lika med",
+ "greaterThan": "Större än",
+ "greaterThanOrEqual": "Större än eller lika med",
+ "lessThan": "Mindre än",
+ "lessThanOrEqual": "Mindre än eller lika med",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Till",
"from": "Från",
"fromto": "{from} till {to}",
+ "date": "Datum",
"ranges": {
"today": "I dag",
"thisWeek": "Denna vecka",
@@ -598,6 +647,27 @@
"lastMonth": "Förra månaden",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Nu",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Beskrivning",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Medlem"
+ "member": "Medlem",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/tr-TR.json b/frontend/src/i18n/lang/tr-TR.json
index 3c444813e..1ba512373 100644
--- a/frontend/src/i18n/lang/tr-TR.json
+++ b/frontend/src/i18n/lang/tr-TR.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/vi-VN.json b/frontend/src/i18n/lang/vi-VN.json
index 75ac1ce16..b34d94f96 100644
--- a/frontend/src/i18n/lang/vi-VN.json
+++ b/frontend/src/i18n/lang/vi-VN.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Bộ lọc",
"clear": "Xoá các bộ lọc",
+ "showResults": "Show results",
"attributes": {
"title": "Tiêu đề",
"titlePlaceholder": "Tiêu đề bộ lọc đã lưu ở đây…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Sửa bộ lọc sẵn này",
"success": "Bộ lọc đã được lưu thành công."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "Đến",
"from": "Từ",
"fromto": "{from} đến {to}",
+ "date": "Date",
"ranges": {
"today": "Hôm nay",
"thisWeek": "Tuần này",
@@ -598,6 +647,27 @@
"lastMonth": "Tháng trước",
"thisYear": "Năm nay",
"restOfThisYear": "Toàn bộ ngày còn lại trong năm"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Mô tả",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Quản trị viên",
- "member": "Thành viên"
+ "member": "Thành viên",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
@@ -1023,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Lỗi",
"success": "Thành công",
diff --git a/frontend/src/i18n/lang/zh-CN.json b/frontend/src/i18n/lang/zh-CN.json
index d0bd62c64..6cd78c06e 100644
--- a/frontend/src/i18n/lang/zh-CN.json
+++ b/frontend/src/i18n/lang/zh-CN.json
@@ -248,6 +248,7 @@
"text2": "这包括所有的任务,并且无法撤销!",
"success": "项目已成功删除。",
"tasksToDelete": "此操作无法撤消!将移除大约 {count} 个任务。",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "此列表不包含任何任务,可以安全删除。"
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "筛选器",
"clear": "清除筛选条件",
+ "showResults": "Show results",
"attributes": {
"title": "标题",
"titlePlaceholder": "填写筛选器标题",
@@ -415,6 +417,52 @@
"edit": {
"title": "编辑此保存的过滤器",
"success": "过滤器保存成功。"
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "到",
"from": "开始",
"fromto": "{from} 到 {to}",
+ "date": "Date",
"ranges": {
"today": "今天",
"thisWeek": "本周",
@@ -598,6 +647,27 @@
"lastMonth": "上个月",
"thisYear": "今年",
"restOfThisYear": "本年度剩余时间"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "描述信息",
"descriptionPlaceholder": "在此描述团队,点击'/'获取更多选项…",
"admin": "管理员",
- "member": "成员"
+ "member": "成员",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
diff --git a/frontend/src/i18n/lang/zh-TW.json b/frontend/src/i18n/lang/zh-TW.json
index 3c444813e..2028930a3 100644
--- a/frontend/src/i18n/lang/zh-TW.json
+++ b/frontend/src/i18n/lang/zh-TW.json
@@ -248,6 +248,7 @@
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The project was successfully deleted.",
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
+ "tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
},
"duplicate": {
@@ -385,6 +386,7 @@
"filters": {
"title": "Filters",
"clear": "Clear Filters",
+ "showResults": "Show results",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
@@ -415,6 +417,52 @@
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
+ },
+ "query": {
+ "title": "Query",
+ "placeholder": "Type a search or filter query…",
+ "help": {
+ "intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
+ "link": "How does this work?",
+ "canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
+ "fields": {
+ "done": "Whether the task is completed or not",
+ "priority": "The priority level of the task (1-5)",
+ "percentDone": "The percentage of completion for the task (0-100)",
+ "dueDate": "The due date of the task",
+ "startDate": "The start date of the task",
+ "endDate": "The end date of the task",
+ "doneAt": "The date and time when the task was completed",
+ "assignees": "The assignees of the task",
+ "labels": "The labels associated with the task",
+ "project": "The project the task belongs to (only available for saved filters, not on a project level)"
+ },
+ "operators": {
+ "intro": "The available operators for filtering include:",
+ "notEqual": "Not equal to",
+ "equal": "Equal to",
+ "greaterThan": "Greater than",
+ "greaterThanOrEqual": "Greater than or equal to",
+ "lessThan": "Less than",
+ "lessThanOrEqual": "Less than or equal to",
+ "like": "Matches a pattern (using wildcard %)",
+ "in": "Matches any value in a comma-seperated list of values"
+ },
+ "logicalOperators": {
+ "intro": "To combine multiple conditions, you can use the following logical operators:",
+ "and": "AND operator, matches if all conditions are true",
+ "or": "OR operator, matches if any of the conditions are true",
+ "parentheses": "Parentheses for grouping conditions"
+ },
+ "examples": {
+ "intro": "Here are some examples of filter queries:",
+ "priorityEqual": "Matches tasks with priority level 4",
+ "dueDatePast": "Matches tasks with a due date in the past",
+ "undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
+ "assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
+ "priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
+ }
+ }
}
},
"migrate": {
@@ -584,6 +632,7 @@
"to": "To",
"from": "From",
"fromto": "{from} to {to}",
+ "date": "Date",
"ranges": {
"today": "Today",
"thisWeek": "This Week",
@@ -598,6 +647,27 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"restOfThisYear": "The Rest of This Year"
+ },
+ "values": {
+ "now": "Now",
+ "startOfToday": "Start of today",
+ "endOfToday": "End of today",
+ "beginningOflastWeek": "Beginning of last week",
+ "endOfLastWeek": "End of last week",
+ "beginningOfThisWeek": "Beginning of this week",
+ "endOfThisWeek": "End of this week",
+ "startOfNextWeek": "Start of next week",
+ "endOfNextWeek": "End of next week",
+ "in7Days": "In 7 days",
+ "beginningOfLastMonth": "Beginning of last month",
+ "endOfLastMonth": "End of last month",
+ "startOfThisMonth": "Start of this month",
+ "endOfThisMonth": "End of this month",
+ "startOfNextMonth": "Start of next month",
+ "endOfNextMonth": "End of next month",
+ "in30Days": "In 30 days",
+ "startOfThisYear": "Beginning of this year",
+ "endOfThisYear": "End of this year"
}
},
"datemathHelp": {
@@ -916,7 +986,9 @@
"description": "Description",
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
"admin": "Admin",
- "member": "Member"
+ "member": "Member",
+ "isPublic": "Public Team",
+ "isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
}
},
"keyboardShortcuts": {
From 17e222edfde7f690de824c5bb4f5232f9980f96b Mon Sep 17 00:00:00 2001
From: renovate
Date: Wed, 13 Mar 2024 00:06:58 +0000
Subject: [PATCH 34/66] chore(deps): update dependency happy-dom to v13.8.2
---
frontend/package.json | 2 +-
frontend/pnpm-lock.yaml | 14 +++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index ddf6a1aa2..4dc40c491 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -161,7 +161,7 @@
"esbuild": "0.20.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.23.0",
- "happy-dom": "13.7.8",
+ "happy-dom": "13.8.2",
"histoire": "0.17.9",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 02b83faa3..fe085dd35 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -340,8 +340,8 @@ devDependencies:
specifier: 9.23.0
version: 9.23.0(eslint@8.57.0)
happy-dom:
- specifier: 13.7.8
- version: 13.7.8
+ specifier: 13.8.2
+ version: 13.8.2
histoire:
specifier: 0.17.9
version: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
@@ -392,7 +392,7 @@ devDependencies:
version: 5.1.0(vue@3.4.21)
vitest:
specifier: 1.3.1
- version: 1.3.1(@types/node@20.11.26)(happy-dom@13.7.8)(sass@1.71.1)(terser@5.24.0)
+ version: 1.3.1(@types/node@20.11.26)(happy-dom@13.8.2)(sass@1.71.1)(terser@5.24.0)
vue-tsc:
specifier: 2.0.6
version: 2.0.6(typescript@5.4.2)
@@ -6460,8 +6460,8 @@ packages:
strip-bom-string: 1.0.0
dev: true
- /happy-dom@13.7.8:
- resolution: {integrity: sha512-dnvgCiPPfXXts+AW1DVAoDa9nPmI48YPHUv34L6pmjv2lwNZte8OwsK9SajEXENfibS8uo1zG7xJwlW/NXlDxQ==}
+ /happy-dom@13.8.2:
+ resolution: {integrity: sha512-u9KxyeQNIzkJDR2iCitKeS5Uy0YUv5eOntpO8e7ZzbDVv4kP5Y77Zo2LnZitwMrss/1pY2Uc2e5qOVGkiKE5Gg==}
engines: {node: '>=16.0.0'}
dependencies:
entities: 4.5.0
@@ -10090,7 +10090,7 @@ packages:
fsevents: 2.3.3
dev: true
- /vitest@1.3.1(@types/node@20.11.26)(happy-dom@13.7.8)(sass@1.71.1)(terser@5.24.0):
+ /vitest@1.3.1(@types/node@20.11.26)(happy-dom@13.8.2)(sass@1.71.1)(terser@5.24.0):
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -10125,7 +10125,7 @@ packages:
chai: 4.3.10
debug: 4.3.4(supports-color@8.1.1)
execa: 8.0.1
- happy-dom: 13.7.8
+ happy-dom: 13.8.2
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.1
From 99c55241156095a81f1a01bddfd7a6897abac894 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 16:59:57 +0100
Subject: [PATCH 35/66] fix(editor): don't allow image upload when it's not
possible to do it
---
.../components/input/editor/EditorToolbar.vue | 8 ++---
.../src/components/input/editor/TipTap.vue | 29 ++++++++++++++-----
2 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/frontend/src/components/input/editor/EditorToolbar.vue b/frontend/src/components/input/editor/EditorToolbar.vue
index c31bcfe35..9018f79dd 100644
--- a/frontend/src/components/input/editor/EditorToolbar.vue
+++ b/frontend/src/components/input/editor/EditorToolbar.vue
@@ -139,7 +139,7 @@
emit('imageUploadClicked', e)"
>
@@ -347,16 +347,14 @@ const {
editor: Editor,
}>()
+const emit = defineEmits(['imageUploadClicked'])
+
const tableMode = ref(false)
function toggleTableMode() {
tableMode.value = !tableMode.value
}
-function openImagePicker() {
- document.getElementById('tiptap__image-upload').click()
-}
-
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor)
}
diff --git a/frontend/src/components/input/editor/TipTap.vue b/frontend/src/components/input/editor/TipTap.vue
index 1cc3c165d..0213546f6 100644
--- a/frontend/src/components/input/editor/TipTap.vue
+++ b/frontend/src/components/input/editor/TipTap.vue
@@ -6,7 +6,7 @@
{
await nextTick()
- const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
- input?.addEventListener('paste', handleImagePaste)
+ if (typeof uploadCallback !== 'undefined') {
+ const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
+ input?.addEventListener('paste', handleImagePaste)
+ }
setModeAndValue(modelValue)
})
onBeforeUnmount(() => {
nextTick(() => {
- const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
- input?.removeEventListener('paste', handleImagePaste)
+ if (typeof uploadCallback !== 'undefined') {
+ const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
+ input?.removeEventListener('paste', handleImagePaste)
+ }
})
if (editShortcut !== '') {
document.removeEventListener('keydown', setFocusToEditor)
@@ -558,10 +571,10 @@ function handleImagePaste(event) {
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function setFocusToEditor(event) {
- if(event.target.shadowRoot) {
+ if (event.target.shadowRoot) {
return
}
-
+
const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return
if (hotkeyString !== editShortcut ||
@@ -600,7 +613,7 @@ watch(
() => isEditing.value,
async editing => {
await nextTick()
-
+
let checkboxes = tiptapInstanceRef.value?.querySelectorAll('[data-checked]')
if (typeof checkboxes === 'undefined' || checkboxes.length === 0) {
// For some reason, this works when we check a second time.
From 79577c14b71d32154e931d2f8cfd6fb4e023df6c Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 17:07:10 +0100
Subject: [PATCH 36/66] fix(filters): set default filter value to only undone
tasks
---
frontend/src/services/taskCollection.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/services/taskCollection.ts b/frontend/src/services/taskCollection.ts
index f83dac2e2..046c3f0d9 100644
--- a/frontend/src/services/taskCollection.ts
+++ b/frontend/src/services/taskCollection.ts
@@ -17,7 +17,7 @@ export function getDefaultTaskFilterParams(): TaskFilterParams {
return {
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
- filter: '',
+ filter: 'done = false',
filter_include_nulls: false,
filter_timezone: '',
s: '',
From 15215b30a0a0dd693d5ceb4a213f6d68e93b97e7 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 17:19:15 +0100
Subject: [PATCH 37/66] fix(filters): rework filter popup button
---
.../project/partials/filter-popup.vue | 7 -
.../components/project/partials/filters.vue | 12 +
frontend/src/styles/components/project.scss | 41 +--
frontend/src/views/project/ProjectKanban.vue | 48 +--
frontend/src/views/project/ProjectList.vue | 78 +---
frontend/src/views/project/ProjectTable.vue | 336 +++++++++---------
6 files changed, 232 insertions(+), 290 deletions(-)
diff --git a/frontend/src/components/project/partials/filter-popup.vue b/frontend/src/components/project/partials/filter-popup.vue
index 4c4d3eb5c..eaef47288 100644
--- a/frontend/src/components/project/partials/filter-popup.vue
+++ b/frontend/src/components/project/partials/filter-popup.vue
@@ -1,11 +1,4 @@
-
- {{ $t('filters.clear') }}
-
+
+ {{ $t('filters.clear') }}
+
diff --git a/frontend/src/styles/components/project.scss b/frontend/src/styles/components/project.scss
index b94b1da9c..8c9efb929 100644
--- a/frontend/src/styles/components/project.scss
+++ b/frontend/src/styles/components/project.scss
@@ -18,13 +18,11 @@ $filter-container-top-link-share-list: -47px;
margin-top: $filter-container-top-default;
z-index: 4;
- .items {
- display: flex;
- justify-content: flex-end;
+ display: flex;
+ justify-content: flex-end;
- .button:not(:last-of-type) {
- margin-right: .5rem;
- }
+ .button:not(:last-of-type) {
+ margin-right: .5rem;
}
.button {
@@ -35,37 +33,6 @@ $filter-container-top-link-share-list: -47px;
text-align: left;
}
- .fancycheckbox {
- display: block;
- }
-
- .search {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-right: .5rem;
-
- .field {
- transition: width $transition;
- width: 100%;
-
- &.hidden {
- width: 0;
- height: 0;
- margin: 0;
- overflow: hidden;
- }
-
- .button {
- height: 100%;
- }
- }
- }
-
- .filters input {
- font-size: .9rem;
- }
-
@media screen and (max-width: $tablet) {
position: static;
margin: 0 0 1rem 0 !important;
diff --git a/frontend/src/views/project/ProjectKanban.vue b/frontend/src/views/project/ProjectKanban.vue
index 9793235bb..8345e4eda 100644
--- a/frontend/src/views/project/ProjectKanban.vue
+++ b/frontend/src/views/project/ProjectKanban.vue
@@ -5,13 +5,11 @@
view-name="kanban"
>
-
-
-
-
+
+
@@ -47,7 +45,7 @@
v-tooltip="$t('project.kanban.doneBucketHint')"
class="icon is-small has-text-success mr-2"
>
-
+
@@ -290,7 +290,11 @@ import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
-import {getCollapsedBucketState, saveCollapsedBucketState, type CollapsedBuckets} from '@/helpers/saveCollapsedBucketState'
+import {
+ type CollapsedBuckets,
+ getCollapsedBucketState,
+ saveCollapsedBucketState,
+} from '@/helpers/saveCollapsedBucketState'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import {isSavedFilter} from '@/services/savedFilter'
@@ -322,7 +326,7 @@ const kanbanStore = useKanbanStore()
const taskStore = useTaskStore()
const projectStore = useProjectStore()
-const taskContainerRefs = ref<{[id: IBucket['id']]: HTMLElement}>({})
+const taskContainerRefs = ref<{ [id: IBucket['id']]: HTMLElement }>({})
const bucketLimitInputRef = ref(null)
const drag = ref(false)
@@ -334,18 +338,18 @@ const bucketToDelete = ref(0)
const bucketTitleEditable = ref(false)
const newTaskText = ref('')
-const showNewTaskInput = ref<{[id: IBucket['id']]: boolean}>({})
+const showNewTaskInput = ref<{ [id: IBucket['id']]: boolean }>({})
const newBucketTitle = ref('')
const showNewBucketInput = ref(false)
-const newTaskError = ref<{[id: IBucket['id']]: boolean}>({})
+const newTaskError = ref<{ [id: IBucket['id']]: boolean }>({})
const newTaskInputFocused = ref(false)
const showSetLimitInput = ref(false)
const collapsedBuckets = ref({})
// We're using this to show the loading animation only at the task when updating it
-const taskUpdating = ref<{[id: ITask['id']]: boolean}>({})
+const taskUpdating = ref<{ [id: ITask['id']]: boolean }>({})
const oneTaskUpdating = ref(false)
const params = ref({
@@ -378,7 +382,7 @@ const bucketDraggableComponentData = computed(() => ({
],
}))
const canWrite = computed(() => baseStore.currentProject?.maxRight > Rights.READ)
-const project = computed(() => projectId ? projectStore.projects[projectId]: null)
+const project = computed(() => projectId ? projectStore.projects[projectId] : null)
const buckets = computed(() => kanbanStore.buckets)
const loading = computed(() => kanbanStore.isLoading)
@@ -497,7 +501,7 @@ async function updateTaskPosition(e) {
await taskStore.update(newTask)
// Make sure the first and second task don't both get position 0 assigned
- if(newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
+ if (newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
const taskAfterAfter = newBucket.tasks[newTaskIndex + 2] ?? null
const newTaskAfter = klona(taskAfter) // cloning the task to avoid pinia store manipulation
newTaskAfter.bucketId = newBucket.id
@@ -602,7 +606,7 @@ function updateBuckets(value: IBucket[]) {
}
// TODO: fix type
-function updateBucketPosition(e: {newIndex: number}) {
+function updateBucketPosition(e: { newIndex: number }) {
// (2) bucket positon is changed
dragBucket.value = false
@@ -631,19 +635,19 @@ async function saveBucketLimit(bucketId: IBucket['id'], limit: number) {
success({message: t('project.kanban.bucketLimitSavedSuccess')})
}
-const setBucketLimitCancel = ref(null)
+const setBucketLimitCancel = ref(null)
async function setBucketLimit(bucketId: IBucket['id'], now: boolean = false) {
const limit = parseInt(bucketLimitInputRef.value?.value || '')
-
+
if (setBucketLimitCancel.value !== null) {
clearTimeout(setBucketLimitCancel.value)
}
-
+
if (now) {
return saveBucketLimit(bucketId, limit)
}
-
+
setBucketLimitCancel.value = setTimeout(saveBucketLimit, 2500, bucketId, limit)
}
@@ -739,6 +743,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
* {
opacity: 0;
}
+
&::after {
content: '';
position: absolute;
@@ -780,6 +785,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
&:first-of-type {
padding-top: .5rem;
}
+
&:last-of-type {
padding-bottom: .5rem;
}
diff --git a/frontend/src/views/project/ProjectList.vue b/frontend/src/views/project/ProjectList.vue
index 060b734ca..399f217bf 100644
--- a/frontend/src/views/project/ProjectList.vue
+++ b/frontend/src/views/project/ProjectList.vue
@@ -5,52 +5,12 @@
view-name="project"
>
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('misc.search') }}
-
-
-
-
-
-
-
+
+
@@ -113,14 +73,14 @@
>
-
+
-
@@ -131,7 +91,7 @@
diff --git a/frontend/src/components/project/partials/filters.vue b/frontend/src/components/project/partials/filters.vue
index dab0efdbe..c614af99e 100644
--- a/frontend/src/components/project/partials/filters.vue
+++ b/frontend/src/components/project/partials/filters.vue
@@ -28,6 +28,7 @@
{{ $t('filters.clear') }}
From b3caece2560aa9e8d5461bfd6fa584ebf98c6ff3 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 18:03:49 +0100
Subject: [PATCH 40/66] fix(datepicker): emit date value changes as soon as
they happen
Flatpickr only returns a change event when the value in the input it's referring to changes. That means it will usually only trigger when the focus is moved out of the input field. This is fine most of the time. However, since we're displaying flatpickr in a popup, the whole html dom instance might get destroyed, before the change event had a chance to fire. In that case, it would not update the date value. To fix this, we're now listening on every change and bubble them up as soon as they happen.
Resolves https://community.vikunja.io/t/due-date-confirm-button-not-working/2104
---
.../src/components/input/datepickerInline.vue | 41 ++++++++++++++++++-
1 file changed, 39 insertions(+), 2 deletions(-)
diff --git a/frontend/src/components/input/datepickerInline.vue b/frontend/src/components/input/datepickerInline.vue
index b8a7a7d5b..eca16d99d 100644
--- a/frontend/src/components/input/datepickerInline.vue
+++ b/frontend/src/components/input/datepickerInline.vue
@@ -63,6 +63,7 @@
@@ -70,7 +71,7 @@
From 7bf2664e558c5aea47c28e8f15c4cd778b230351 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 19:03:23 +0100
Subject: [PATCH 43/66] fix(filters): persist filters in url
This allows us to keep the filters when navigating back from a task or other url.
---
.../project/partials/filter-popup.vue | 26 ++++++++++++++++---
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/frontend/src/components/project/partials/filter-popup.vue b/frontend/src/components/project/partials/filter-popup.vue
index 8f037def4..7d3247a07 100644
--- a/frontend/src/components/project/partials/filter-popup.vue
+++ b/frontend/src/components/project/partials/filter-popup.vue
@@ -30,21 +30,39 @@ import {computed, ref, watch} from 'vue'
import Filters from '@/components/project/partials/filters.vue'
-import {type TaskFilterParams} from '@/services/taskCollection'
+import {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
+import {useRouteQuery} from '@vueuse/router'
const modelValue = defineModel({})
const value = ref({})
+const filter = useRouteQuery('filter')
watch(
() => modelValue.value,
(modelValue: TaskFilterParams) => {
value.value = modelValue
+ if (value.value.filter !== '' && value.value.filter !== getDefaultTaskFilterParams().filter) {
+ filter.value = value.value.filter
+ }
+ },
+ {immediate: true},
+)
+
+watch(
+ () => filter.value,
+ val => {
+ if (modelValue.value?.filter === val || typeof val === 'undefined') {
+ return
+ }
+
+ modelValue.value.filter = val
},
{immediate: true},
)
function emitChanges(newValue: TaskFilterParams) {
+ filter.value = newValue.filter
if (modelValue.value?.filter === newValue.filter && modelValue.value?.s === newValue.s) {
return
}
@@ -54,7 +72,7 @@ function emitChanges(newValue: TaskFilterParams) {
}
const hasFilters = computed(() => {
- return value.value.filter !== '' ||
+ return value.value.filter !== '' ||
value.value.s !== ''
})
@@ -73,13 +91,13 @@ const modalOpen = ref(false)
$filter-bubble-size: .75rem;
.has-filters {
position: relative;
-
+
&::after {
content: '';
position: absolute;
top: math.div($filter-bubble-size, -2);
right: math.div($filter-bubble-size, -2);
-
+
width: $filter-bubble-size;
height: $filter-bubble-size;
border-radius: 100%;
From 8ff59d46490aa8c114d203d8cb9e4d1c05896f60 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 19:11:49 +0100
Subject: [PATCH 44/66] fix(task): navigate back to project when the project
was the last page in the history the user visited
---
frontend/src/views/tasks/TaskDetailView.vue | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue
index f3e274396..12b593e37 100644
--- a/frontend/src/views/tasks/TaskDetailView.vue
+++ b/frontend/src/views/tasks/TaskDetailView.vue
@@ -25,7 +25,16 @@
v-for="p in projectStore.getAncestors(project)"
:key="p.id"
>
-
+
+ {{ getProjectTitle(p) }}
+
+
{{ getProjectTitle(p) }}
Date: Wed, 13 Mar 2024 19:23:02 +0100
Subject: [PATCH 45/66] fix(editor): do not use Tiptap to open links when
clicking on them, use the browser native attributes instead
It looks like links are opened twice, when the openOnClick option is enabled. That means they will get opened twice when clicking on them. Disabling that option will not fire the click handler and only rely on browser functionality to open links.
Resolves https://kolaente.dev/vikunja/vikunja/issues/2155
---
frontend/src/components/input/editor/TipTap.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/input/editor/TipTap.vue b/frontend/src/components/input/editor/TipTap.vue
index 0213546f6..af4da5455 100644
--- a/frontend/src/components/input/editor/TipTap.vue
+++ b/frontend/src/components/input/editor/TipTap.vue
@@ -374,7 +374,7 @@ const editor = useEditor({
Typography,
Underline,
Link.configure({
- openOnClick: true,
+ openOnClick: false,
validate: (href: string) => /^https?:\/\//.test(href),
}),
Table.configure({
From 117079bbda9a6b83f6d49061dd2a16ac167cc8e1 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 19:31:43 +0100
Subject: [PATCH 46/66] fix(sentry): do not send api errors to sentry
---
frontend/src/sentry.ts | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/frontend/src/sentry.ts b/frontend/src/sentry.ts
index d48769dbf..32362f80e 100644
--- a/frontend/src/sentry.ts
+++ b/frontend/src/sentry.ts
@@ -1,6 +1,7 @@
import 'virtual:vite-plugin-sentry/sentry-config'
import type {App} from 'vue'
import type {Router} from 'vue-router'
+import {AxiosError} from 'axios'
export default async function setupSentry(app: App, router: Router) {
const Sentry = await import('@sentry/vue')
@@ -18,5 +19,15 @@ export default async function setupSentry(app: App, router: Router) {
}),
],
tracesSampleRate: 1.0,
+ beforeSend(event, hint) {
+
+ if ((typeof hint.originalException?.code !== 'undefined' &&
+ typeof hint.originalException?.message !== 'undefined')
+ || hint.originalException instanceof AxiosError) {
+ return null
+ }
+
+ return event
+ },
})
}
From 8c826c44d2684538d7b59c12bfbe74c6ce6d8bf5 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 19:41:34 +0100
Subject: [PATCH 47/66] fix(webhooks): fire webhooks set on parent projects as
well
---
pkg/models/listeners.go | 36 +++++++++++++++++++++++++++---------
1 file changed, 27 insertions(+), 9 deletions(-)
diff --git a/pkg/models/listeners.go b/pkg/models/listeners.go
index ea3a5a462..9809d77c8 100644
--- a/pkg/models/listeners.go
+++ b/pkg/models/listeners.go
@@ -725,24 +725,36 @@ func (wl *WebhookListener) Handle(msg *message.Message) (err error) {
s := db.NewSession()
defer s.Close()
+ parents, err := GetAllParentProjects(s, projectID)
+ if err != nil {
+ return err
+ }
+
+ projectIDs := make([]int64, 0, len(parents)+1)
+ projectIDs = append(projectIDs, projectID)
+
+ for _, p := range parents {
+ projectIDs = append(projectIDs, p.ID)
+ }
+
ws := []*Webhook{}
- err = s.Where("project_id = ?", projectID).
+ err = s.In("project_id", projectIDs).
Find(&ws)
if err != nil {
return err
}
- var webhook *Webhook
+ matchingWebhooks := []*Webhook{}
for _, w := range ws {
for _, e := range w.Events {
if e == wl.EventName {
- webhook = w
+ matchingWebhooks = append(matchingWebhooks, w)
break
}
}
}
- if webhook == nil {
+ if len(matchingWebhooks) == 0 {
log.Debugf("Did not find any webhook for the %s event for project %d, not sending", wl.EventName, projectID)
return nil
}
@@ -789,11 +801,17 @@ func (wl *WebhookListener) Handle(msg *message.Message) (err error) {
}
}
- err = webhook.sendWebhookPayload(&WebhookPayload{
- EventName: wl.EventName,
- Time: time.Now(),
- Data: event,
- })
+ for _, webhook := range matchingWebhooks {
+ err = webhook.sendWebhookPayload(&WebhookPayload{
+ EventName: wl.EventName,
+ Time: time.Now(),
+ Data: event,
+ })
+ if err != nil {
+ return err
+ }
+ }
+
return
}
From d4605905d3ef32807ae39e8f409753fd5e1e9b7f Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 19:58:24 +0100
Subject: [PATCH 48/66] fix(filters): do not fire filter change immediately
Related to https://kolaente.dev/vikunja/vikunja/issues/2194#issuecomment-61081
---
.../components/project/partials/FilterInput.vue | 7 ++++++-
.../src/components/project/partials/filters.vue | 16 ++++++++++++----
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/frontend/src/components/project/partials/FilterInput.vue b/frontend/src/components/project/partials/FilterInput.vue
index d668fde47..518d71033 100644
--- a/frontend/src/components/project/partials/FilterInput.vue
+++ b/frontend/src/components/project/partials/FilterInput.vue
@@ -20,6 +20,7 @@ import {
getFilterFieldRegexPattern,
LABEL_FIELDS,
} from '@/helpers/filters'
+import {useDebounceFn} from '@vueuse/core'
const {
modelValue,
@@ -236,6 +237,10 @@ function autocompleteSelect(value) {
autocompleteResults.value = []
}
+
+// The blur from the textarea might happen before the replacement after autocomplete select was done.
+// That caused listeners to try and replace values earlier, resulting in broken queries.
+const blurDebounced = useDebounceFn(() => emit('blur'), 500)
@@ -264,7 +269,7 @@ function autocompleteSelect(value) {
@input="handleFieldInput"
@focus="onFocusField"
@keydown="onKeydown"
- @blur="e => emit('blur', e)"
+ @blur="blurDebounced"
/>
-
+
labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
- projectTitle => projectStore.searchProject(projectTitle)[0]?.id || null,
+ projectTitle => {
+ const found = projectStore.findProjectByExactname(projectTitle)
+
+ if (found === null) {
+ return null
+ }
+
+ return found[0]?.id || null
+ },
)
let s = ''
From 07e84f2abf1802b52e96310a3ff193fa233e4c2d Mon Sep 17 00:00:00 2001
From: kolaente
Date: Wed, 13 Mar 2024 20:10:53 +0100
Subject: [PATCH 49/66] fix(reminders): make debounce logic actually work
---
.../components/project/partials/filters.vue | 2 +-
.../tasks/partials/reminder-detail.vue | 21 ++++++++++---------
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/frontend/src/components/project/partials/filters.vue b/frontend/src/components/project/partials/filters.vue
index 3c6e6a9fd..14212ca20 100644
--- a/frontend/src/components/project/partials/filters.vue
+++ b/frontend/src/components/project/partials/filters.vue
@@ -19,7 +19,7 @@
-
+
{{ $t('misc.confirm') }}
@@ -113,7 +113,7 @@ const presets = computed
(() => [
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: defaultRelativeTo},
])
-const reminderDate = ref(null)
+const reminderDate = ref(null)
type availableForms = null | 'relative' | 'absolute'
@@ -143,16 +143,16 @@ const reminderText = computed(() => {
watch(
() => modelValue,
(newReminder) => {
- if(newReminder) {
+ if (newReminder) {
reminder.value = newReminder
-
- if(newReminder.relativeTo === null) {
+
+ if (newReminder.relativeTo === null) {
reminderDate.value = new Date(newReminder.reminder)
}
-
+
return
}
-
+
reminder.value = new TaskReminderModel()
},
{immediate: true},
@@ -182,9 +182,10 @@ function setReminderFromPreset(preset, close) {
close()
}
-const updateDataDebounced = useDebounceFn(updateData, 1000)
-function updateDataAndMaybeClose(close) {
- updateDataDebounced()
+const updateDataAndMaybeClose = useDebounceFn(updateDataAndMaybeCloseNow, 500)
+
+function updateDataAndMaybeCloseNow(close) {
+ updateData()
if (clearAfterUpdate) {
close()
}
From 88fdfb50b77d433bb84a2abe1b3ba9faf6b6dc24 Mon Sep 17 00:00:00 2001
From: "Frederick [Bot]"
Date: Thu, 14 Mar 2024 00:06:47 +0000
Subject: [PATCH 50/66] chore(i18n): update translations via Crowdin
---
frontend/src/i18n/lang/ar-SA.json | 6 ++++++
frontend/src/i18n/lang/ca-ES.json | 6 ++++++
frontend/src/i18n/lang/cs-CZ.json | 6 ++++++
frontend/src/i18n/lang/da-DK.json | 6 ++++++
frontend/src/i18n/lang/de-DE.json | 8 ++++----
frontend/src/i18n/lang/de-swiss.json | 8 ++++----
frontend/src/i18n/lang/nl-NL.json | 6 ++++++
frontend/src/i18n/lang/sl-SI.json | 8 ++++----
frontend/src/i18n/lang/zh-CN.json | 6 ++++++
frontend/src/i18n/lang/zh-TW.json | 6 ++++++
10 files changed, 54 insertions(+), 12 deletions(-)
diff --git a/frontend/src/i18n/lang/ar-SA.json b/frontend/src/i18n/lang/ar-SA.json
index 79b5682fa..9770e0a1e 100644
--- a/frontend/src/i18n/lang/ar-SA.json
+++ b/frontend/src/i18n/lang/ar-SA.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "خطأ",
"success": "تم بنجاح",
diff --git a/frontend/src/i18n/lang/ca-ES.json b/frontend/src/i18n/lang/ca-ES.json
index 2028930a3..1ba512373 100644
--- a/frontend/src/i18n/lang/ca-ES.json
+++ b/frontend/src/i18n/lang/ca-ES.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
diff --git a/frontend/src/i18n/lang/cs-CZ.json b/frontend/src/i18n/lang/cs-CZ.json
index f91a44be3..3b92cfc27 100644
--- a/frontend/src/i18n/lang/cs-CZ.json
+++ b/frontend/src/i18n/lang/cs-CZ.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Chyba",
"success": "Úspěch",
diff --git a/frontend/src/i18n/lang/da-DK.json b/frontend/src/i18n/lang/da-DK.json
index ab9bd8a9e..4bb5816b4 100644
--- a/frontend/src/i18n/lang/da-DK.json
+++ b/frontend/src/i18n/lang/da-DK.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "d m Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Fejl",
"success": "Succes",
diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json
index 78a806353..0f8452c72 100644
--- a/frontend/src/i18n/lang/de-DE.json
+++ b/frontend/src/i18n/lang/de-DE.json
@@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
- "reactedWith": "{user} reacted with {value}",
- "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
- "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
- "add": "Add your reaction"
+ "reactedWith": "{user} hat mit {value} reagiert",
+ "reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
+ "reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
+ "add": "Reaktion hinzufügen"
},
"error": {
"error": "Fehler",
diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json
index 15da13b8f..5d3d3ff40 100644
--- a/frontend/src/i18n/lang/de-swiss.json
+++ b/frontend/src/i18n/lang/de-swiss.json
@@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
- "reactedWith": "{user} reacted with {value}",
- "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
- "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
- "add": "Add your reaction"
+ "reactedWith": "{user} hat mit {value} reagiert",
+ "reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
+ "reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
+ "add": "Reaktion hinzufügen"
},
"error": {
"error": "Fähler",
diff --git a/frontend/src/i18n/lang/nl-NL.json b/frontend/src/i18n/lang/nl-NL.json
index d5824f257..bf1e46c99 100644
--- a/frontend/src/i18n/lang/nl-NL.json
+++ b/frontend/src/i18n/lang/nl-NL.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Fout",
"success": "Succes",
diff --git a/frontend/src/i18n/lang/sl-SI.json b/frontend/src/i18n/lang/sl-SI.json
index ed4073644..03ac8c3b8 100644
--- a/frontend/src/i18n/lang/sl-SI.json
+++ b/frontend/src/i18n/lang/sl-SI.json
@@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
- "reactedWith": "{user} reacted with {value}",
- "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
- "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
- "add": "Add your reaction"
+ "reactedWith": "{user} se je odzval z {value}",
+ "reactedWithAnd": "{users} in {lastUser} sta reagirala z {value}",
+ "reactedWithAndMany": "{users} in {num} drugih se je odzvalo z {value}",
+ "add": "Dodaj svoj odziv"
},
"error": {
"error": "Napaka",
diff --git a/frontend/src/i18n/lang/zh-CN.json b/frontend/src/i18n/lang/zh-CN.json
index 6cd78c06e..0e6ac6681 100644
--- a/frontend/src/i18n/lang/zh-CN.json
+++ b/frontend/src/i18n/lang/zh-CN.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "Y M d H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "错误",
"success": "成功",
diff --git a/frontend/src/i18n/lang/zh-TW.json b/frontend/src/i18n/lang/zh-TW.json
index 2028930a3..1ba512373 100644
--- a/frontend/src/i18n/lang/zh-TW.json
+++ b/frontend/src/i18n/lang/zh-TW.json
@@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
+ "reaction": {
+ "reactedWith": "{user} reacted with {value}",
+ "reactedWithAnd": "{users} and {lastUser} reacted with {value}",
+ "reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
+ "add": "Add your reaction"
+ },
"error": {
"error": "Error",
"success": "Success",
From 273f5ddf59bf67c2547668908ab482f8772a7a94 Mon Sep 17 00:00:00 2001
From: renovate
Date: Thu, 14 Mar 2024 00:08:06 +0000
Subject: [PATCH 51/66] chore(deps): update dev-dependencies
---
desktop/package.json | 2 +-
desktop/yarn.lock | 8 +-
frontend/package.json | 14 +-
frontend/pnpm-lock.yaml | 481 ++++++++++++++++++++--------------------
4 files changed, 249 insertions(+), 256 deletions(-)
diff --git a/desktop/package.json b/desktop/package.json
index 9cac08f66..ee8f940ab 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -51,7 +51,7 @@
}
},
"devDependencies": {
- "electron": "29.1.1",
+ "electron": "29.1.3",
"electron-builder": "24.13.3"
},
"dependencies": {
diff --git a/desktop/yarn.lock b/desktop/yarn.lock
index e6703d466..0c3a1f284 100644
--- a/desktop/yarn.lock
+++ b/desktop/yarn.lock
@@ -769,10 +769,10 @@ electron-publish@24.13.1:
lazy-val "^1.0.5"
mime "^2.5.2"
-electron@29.1.1:
- version "29.1.1"
- resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.1.tgz#e9cb11311324e4b43a3e73667cd2b65a30e8fa34"
- integrity sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==
+electron@29.1.3:
+ version "29.1.3"
+ resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.3.tgz#bd127c6c5bef03ca2cf4595480b8db7e4f111dbe"
+ integrity sha512-E+ZDRlrVQp4lRxVpK8uTaiHZ8CgjpZEs3gvhFOfbnUGHRHQ6FpPOBZQpQUx84JimOFSaz/KP6Jm2x4TFgoN56A==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"
diff --git a/frontend/package.json b/frontend/package.json
index 4dc40c491..c58146735 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -133,7 +133,7 @@
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.8",
- "@histoire/plugin-vue": "0.17.12",
+ "@histoire/plugin-vue": "0.17.13",
"@rushstack/eslint-patch": "1.7.2",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.15",
@@ -142,7 +142,7 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
- "@types/node": "20.11.26",
+ "@types/node": "20.11.27",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.2.0",
@@ -150,27 +150,27 @@
"@vitejs/plugin-legacy": "5.3.2",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
- "@vue/test-utils": "2.4.4",
+ "@vue/test-utils": "2.4.5",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.18",
"browserslist": "4.23.0",
"caniuse-lite": "1.0.30001597",
"css-has-pseudo": "6.0.2",
"csstype": "3.1.3",
- "cypress": "13.6.6",
+ "cypress": "13.7.0",
"esbuild": "0.20.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.23.0",
- "happy-dom": "13.8.2",
+ "happy-dom": "13.8.4",
"histoire": "0.17.9",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
- "postcss-preset-env": "9.5.0",
+ "postcss-preset-env": "9.5.1",
"rollup": "4.13.0",
"rollup-plugin-visualizer": "5.12.0",
- "sass": "1.71.1",
+ "sass": "1.72.0",
"start-server-and-test": "2.0.3",
"typescript": "5.4.2",
"vite": "5.1.6",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index fe085dd35..9f4441ad1 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -245,13 +245,13 @@ dependencies:
devDependencies:
'@4tw/cypress-drag-drop':
specifier: 2.2.5
- version: 2.2.5(cypress@13.6.6)
+ version: 2.2.5(cypress@13.7.0)
'@cypress/vite-dev-server':
specifier: 5.0.7
version: 5.0.7
'@cypress/vue':
specifier: 6.0.0
- version: 6.0.0(cypress@13.6.6)(vue@3.4.21)
+ version: 6.0.0(cypress@13.7.0)(vue@3.4.21)
'@faker-js/faker':
specifier: 8.4.1
version: 8.4.1
@@ -259,8 +259,8 @@ devDependencies:
specifier: 0.17.8
version: 0.17.8(histoire@0.17.9)
'@histoire/plugin-vue':
- specifier: 0.17.12
- version: 0.17.12(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21)
+ specifier: 0.17.13
+ version: 0.17.13(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21)
'@rushstack/eslint-patch':
specifier: 1.7.2
version: 1.7.2
@@ -283,8 +283,8 @@ devDependencies:
specifier: 5.0.2
version: 5.0.2
'@types/node':
- specifier: 20.11.26
- version: 20.11.26
+ specifier: 20.11.27
+ version: 20.11.27
'@types/postcss-preset-env':
specifier: 7.7.0
version: 7.7.0
@@ -307,8 +307,8 @@ devDependencies:
specifier: 13.0.0
version: 13.0.0(eslint-plugin-vue@9.23.0)(eslint@8.57.0)(typescript@5.4.2)
'@vue/test-utils':
- specifier: 2.4.4
- version: 2.4.4(vue@3.4.21)
+ specifier: 2.4.5
+ version: 2.4.5
'@vue/tsconfig':
specifier: 0.5.1
version: 0.5.1
@@ -328,8 +328,8 @@ devDependencies:
specifier: 3.1.3
version: 3.1.3
cypress:
- specifier: 13.6.6
- version: 13.6.6
+ specifier: 13.7.0
+ version: 13.7.0
esbuild:
specifier: 0.20.1
version: 0.20.1
@@ -340,11 +340,11 @@ devDependencies:
specifier: 9.23.0
version: 9.23.0(eslint@8.57.0)
happy-dom:
- specifier: 13.8.2
- version: 13.8.2
+ specifier: 13.8.4
+ version: 13.8.4
histoire:
specifier: 0.17.9
- version: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
+ version: 0.17.9(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)(vite@5.1.6)
postcss:
specifier: 8.4.35
version: 8.4.35
@@ -358,8 +358,8 @@ devDependencies:
specifier: 8.0.1
version: 8.0.1(postcss@8.4.35)
postcss-preset-env:
- specifier: 9.5.0
- version: 9.5.0(postcss@8.4.35)
+ specifier: 9.5.1
+ version: 9.5.1(postcss@8.4.35)
rollup:
specifier: 4.13.0
version: 4.13.0
@@ -367,8 +367,8 @@ devDependencies:
specifier: 5.12.0
version: 5.12.0(rollup@4.13.0)
sass:
- specifier: 1.71.1
- version: 1.71.1
+ specifier: 1.72.0
+ version: 1.72.0
start-server-and-test:
specifier: 2.0.3
version: 2.0.3
@@ -377,7 +377,7 @@ devDependencies:
version: 5.4.2
vite:
specifier: 5.1.6
- version: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ version: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
vite-plugin-inject-preload:
specifier: 1.3.3
version: 1.3.3(vite@5.1.6)
@@ -392,7 +392,7 @@ devDependencies:
version: 5.1.0(vue@3.4.21)
vitest:
specifier: 1.3.1
- version: 1.3.1(@types/node@20.11.26)(happy-dom@13.8.2)(sass@1.71.1)(terser@5.24.0)
+ version: 1.3.1(@types/node@20.11.27)(happy-dom@13.8.4)(sass@1.72.0)(terser@5.24.0)
vue-tsc:
specifier: 2.0.6
version: 2.0.6(typescript@5.4.2)
@@ -405,12 +405,12 @@ devDependencies:
packages:
- /@4tw/cypress-drag-drop@2.2.5(cypress@13.6.6):
+ /@4tw/cypress-drag-drop@2.2.5(cypress@13.7.0):
resolution: {integrity: sha512-3ghTmzhOmUqeN6U3QmUnKRUxI7OMLbJA4hHUY/eS/FhWJgxbiGgcaELbolWnBAOpajPXcsNQGYEj9brd59WH6A==}
peerDependencies:
cypress: 2 - 13
dependencies:
- cypress: 13.6.6
+ cypress: 13.7.0
dev: true
/@aashutoshrathi/word-wrap@1.2.6:
@@ -1694,15 +1694,15 @@ packages:
w3c-keyname: 2.2.6
dev: true
- /@csstools/cascade-layer-name-parser@1.0.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3):
- resolution: {integrity: sha512-xHxXavWvXB5nAA9IvZtjEzkONM3hPXpxqYK4cEw60LcqPiFjq7ZlEFxOyYFPrG4UdANKtnucNtRVDy7frjq6AA==}
+ /@csstools/cascade-layer-name-parser@1.0.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4):
+ resolution: {integrity: sha512-RRqNjxTZDUhx7pxYOBG/AkCVmPS3zYzfE47GEhIGkFuWFTQGJBgWOUUkKNo5MfxIfjDz5/1L3F3rF1oIsYaIpw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
- '@csstools/css-parser-algorithms': ^2.6.0
- '@csstools/css-tokenizer': ^2.2.3
+ '@csstools/css-parser-algorithms': ^2.6.1
+ '@csstools/css-tokenizer': ^2.2.4
dependencies:
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
dev: true
/@csstools/color-helpers@4.0.0:
@@ -1710,53 +1710,53 @@ packages:
engines: {node: ^14 || ^16 || >=18}
dev: true
- /@csstools/css-calc@1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3):
- resolution: {integrity: sha512-+7bUzB5I4cI97tKmBJA8ilTl/YRo6VAOdlrnd/4x2NyK60nvYurGKa5TZpE1zcgIrTC97iJRE0/V65feyFytuw==}
+ /@csstools/css-calc@1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4):
+ resolution: {integrity: sha512-iQqIW5vDPqQdLx07/atCuNKDprhIWjB0b8XRhUyXZWBZYUG+9mNyFwyu30rypX84WLevVo25NYW2ipxR8WyseQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
- '@csstools/css-parser-algorithms': ^2.6.0
- '@csstools/css-tokenizer': ^2.2.3
+ '@csstools/css-parser-algorithms': ^2.6.1
+ '@csstools/css-tokenizer': ^2.2.4
dependencies:
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
dev: true
- /@csstools/css-color-parser@1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3):
- resolution: {integrity: sha512-5GEkuuUxD5dael3xoWjyf7gAPAi4pwm8X8JW/nUMhxntGY4Wo4Lp7vKlex4V5ZgTfAoov14rZFsZyOantdTatg==}
+ /@csstools/css-color-parser@1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4):
+ resolution: {integrity: sha512-Wc1X6jZvGhT8Bii4jUF6tC3Je3wgDFg7D/SvGKndrnakDsCPk4TMxtt4AQHyWdMBrBJ1hLjXbppaXgP1DUIpBw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
- '@csstools/css-parser-algorithms': ^2.6.0
- '@csstools/css-tokenizer': ^2.2.3
+ '@csstools/css-parser-algorithms': ^2.6.1
+ '@csstools/css-tokenizer': ^2.2.4
dependencies:
'@csstools/color-helpers': 4.0.0
- '@csstools/css-calc': 1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-calc': 1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
dev: true
- /@csstools/css-parser-algorithms@2.6.0(@csstools/css-tokenizer@2.2.3):
- resolution: {integrity: sha512-YfEHq0eRH98ffb5/EsrrDspVWAuph6gDggAE74ZtjecsmyyWpW768hOyiONa8zwWGbIWYfa2Xp4tRTrpQQ00CQ==}
+ /@csstools/css-parser-algorithms@2.6.1(@csstools/css-tokenizer@2.2.4):
+ resolution: {integrity: sha512-ubEkAaTfVZa+WwGhs5jbo5Xfqpeaybr/RvWzvFxRs4jfq16wH8l8Ty/QEEpINxll4xhuGfdMbipRyz5QZh9+FA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
- '@csstools/css-tokenizer': ^2.2.3
+ '@csstools/css-tokenizer': ^2.2.4
dependencies:
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-tokenizer': 2.2.4
dev: true
- /@csstools/css-tokenizer@2.2.3:
- resolution: {integrity: sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==}
+ /@csstools/css-tokenizer@2.2.4:
+ resolution: {integrity: sha512-PuWRAewQLbDhGeTvFuq2oClaSCKPIBmHyIobCV39JHRYN0byDcUWJl5baPeNUcqrjtdMNqFooE0FGl31I3JOqw==}
engines: {node: ^14 || ^16 || >=18}
dev: true
- /@csstools/media-query-list-parser@2.1.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3):
- resolution: {integrity: sha512-DiD3vG5ciNzeuTEoh74S+JMjQDs50R3zlxHnBnfd04YYfA/kh2KiBCGhzqLxlJcNq+7yNQ3stuZZYLX6wK/U2g==}
+ /@csstools/media-query-list-parser@2.1.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4):
+ resolution: {integrity: sha512-qqGuFfbn4rUmyOB0u8CVISIp5FfJ5GAR3mBrZ9/TKndHakdnm6pY0L/fbLcpPnrzwCyyTEZl1nUcXAYHEWneTA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
- '@csstools/css-parser-algorithms': ^2.6.0
- '@csstools/css-tokenizer': ^2.2.3
+ '@csstools/css-parser-algorithms': ^2.6.1
+ '@csstools/css-tokenizer': ^2.2.4
dependencies:
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
dev: true
/@csstools/postcss-cascade-layers@4.0.3(postcss@8.4.35):
@@ -1770,43 +1770,43 @@ packages:
postcss-selector-parser: 6.0.15
dev: true
- /@csstools/postcss-color-function@3.0.10(postcss@8.4.35):
- resolution: {integrity: sha512-jxiXmSl4ZYX8KewFjL5ef6of9uW73VkaHeDb2tqb5q4ZDPYxjusNX1KJ8UXY8+7ydqS5QBo42tVMrSMGy+rDmw==}
+ /@csstools/postcss-color-function@3.0.11(postcss@8.4.35):
+ resolution: {integrity: sha512-z53Pp2tsemiIq72PKu4vjD0CtcQlXdvA22elEHuDOvCIlqphNjd5ZD5HBns/ZjaJF7BjPls2zaAT58hfLyS0MQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-color-mix-function@2.0.10(postcss@8.4.35):
- resolution: {integrity: sha512-zeD856+FDCUjB077pPS+Z9OnTQnqpiJrao3TW+sasCb/gJ3vZCX7sRSRFsRUo0/MntTtJu9hkKv9eMkFmfjydA==}
+ /@csstools/postcss-color-mix-function@2.0.11(postcss@8.4.35):
+ resolution: {integrity: sha512-Jz1R5ZXxpT5FIY95F3VSJtwQYWCYOtCBUBS/ShDxS+fUtd3sAdAtD3a9tAdz3FG3BvkmqtlURyoIhJRu/wfo/A==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-exponential-functions@1.0.4(postcss@8.4.35):
- resolution: {integrity: sha512-frMf0CFVnZoGEKAHlxLy3s4g/tpjyFn5+A+h895UJNm9Uc+ewGT7+EeK7Kh9IHH4pD4FkaGW1vOQtER00PLurQ==}
+ /@csstools/postcss-exponential-functions@1.0.5(postcss@8.4.35):
+ resolution: {integrity: sha512-7S7I7KgwHWQYzJJAoIjRtUf7DQs1dxipeg1A6ikZr0PYapNJX7UHz0evlpE67SQqYj1xBs70gpG7xUv3uLp4PA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-calc': 1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-calc': 1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
postcss: 8.4.35
dev: true
@@ -1821,53 +1821,53 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /@csstools/postcss-gamut-mapping@1.0.3(postcss@8.4.35):
- resolution: {integrity: sha512-P0+ude1KyCy9LXOe2pHJmpcXK4q/OQbr2Sn2wQSssMw0rALGmny2MfHiCqEu8n6mf2cN6lWDZdzY8enBk8WHXQ==}
+ /@csstools/postcss-gamut-mapping@1.0.4(postcss@8.4.35):
+ resolution: {integrity: sha512-jjHP44awnSijgddNJpZEFfmb8csFx+BiYYpX+ydyScWwLzSpve5eLXneu4uIhZmKom+WXLXWc4y7CvOfVLQ2VQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
postcss: 8.4.35
dev: true
- /@csstools/postcss-gradients-interpolation-method@4.0.11(postcss@8.4.35):
- resolution: {integrity: sha512-LFom5jCVUfzF+iuiOZvhvX7RRN8vc+tKpcKo9s4keEBAU2mPwV5/Fgz5iylEfXP/DZbEdq2C0At20urMi/lupw==}
+ /@csstools/postcss-gradients-interpolation-method@4.0.12(postcss@8.4.35):
+ resolution: {integrity: sha512-F1mOb6MuIMAV7qq9dYLhi2tlmmQn+osCVl+VdDNI+4AO6y3l6dTWmc7XVQMsVxIZCKEZMie9KLtE0PRp3i1UyQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-hwb-function@3.0.9(postcss@8.4.35):
- resolution: {integrity: sha512-S3/Z+mGHWIKAex7DLsHFDiku5lBEK34avT2My6sGPNCXB38TZjrKI0rd7JdN9oulem5sn+CU7oONyIftui24oQ==}
+ /@csstools/postcss-hwb-function@3.0.10(postcss@8.4.35):
+ resolution: {integrity: sha512-wYyhFLQ1zkirAhfRxh5BK9WRIJGBb7jtE9H9a2wPOf20kGbS/PmqxHtGmE+o1vSz/MaBIbW+6lqyS16yEzjQJA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-ic-unit@3.0.4(postcss@8.4.35):
- resolution: {integrity: sha512-OB6ojl33/TQHhjVx1NI+n3EnYbdUM6Q/mSUv3WFATdcz7IrH/CmBaZt7P1R6j1Xdp58thIa6jm4Je7saGs+2AA==}
+ /@csstools/postcss-ic-unit@3.0.5(postcss@8.4.35):
+ resolution: {integrity: sha512-9CriM/zvKXa/lDARlxs/MgeyKE6ZmmX4V77VLD7VUxKLVSt0Go3NCy/gRMbwGzxbrk3iaHFXnFbc2lNw+/7jcg==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
postcss-value-parser: 4.2.0
@@ -1893,15 +1893,15 @@ packages:
postcss-selector-parser: 6.0.15
dev: true
- /@csstools/postcss-light-dark-function@1.0.0(postcss@8.4.35):
- resolution: {integrity: sha512-KHo633V16DGo6tmpr1ARAwO73CPBNmDI3PfSQYe7ZBMiv60WEizbcEroK75fHjxKYJ4tj9uCCzp5sYG4cEUqqw==}
+ /@csstools/postcss-light-dark-function@1.0.1(postcss@8.4.35):
+ resolution: {integrity: sha512-CJOcp+m7Njbu91HtYMMoYuZznsvNSpJtLiR/7BO8/bHTXYPiuAZfxunh7wXLkMbHd5dRBgAVAQZ+H4iFqrvWZw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
@@ -1943,39 +1943,39 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /@csstools/postcss-logical-viewport-units@2.0.6(postcss@8.4.35):
- resolution: {integrity: sha512-6hV0ngZh8J7HqNY3kyt+z5ABN/XE18qvrU7ne4YSkKfltrWDnQgGiW/Q+h7bdQz8/W5juAefcdCCAJUIBE7erg==}
+ /@csstools/postcss-logical-viewport-units@2.0.7(postcss@8.4.35):
+ resolution: {integrity: sha512-L4G3zsp/bnU0+WXUyysihCUH14LkfMgUJsS9vKz3vCYbVobOTqQRoNXnEPpyNp8WYyolLqAWbGGJhVu8J6u2OQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-tokenizer': 2.2.4
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-media-minmax@1.1.3(postcss@8.4.35):
- resolution: {integrity: sha512-W9AFRQSLvT+Dxtp20AewzGTUxzkJ21XSKzqRALwQdAv0uJGXkR76qgdhkoX0L/tcV4gXtgDfVtGYL/x2Nz/M5Q==}
+ /@csstools/postcss-media-minmax@1.1.4(postcss@8.4.35):
+ resolution: {integrity: sha512-xl/PIO3TUbXO1ZA4SA6HCw+Q9UGe2cgeRKx3lHCzoNig2D4bT5vfVCOrwhxjUb09oHihc9eI3I0iIfVPiXaN1A==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-calc': 1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/media-query-list-parser': 2.1.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
+ '@csstools/css-calc': 1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/media-query-list-parser': 2.1.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
postcss: 8.4.35
dev: true
- /@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.6(postcss@8.4.35):
- resolution: {integrity: sha512-awc2qenSDvx6r+w6G9xxENp+LsbvHC8mMMV23KYmk4pR3YL8JxeKPDSiDhmqd93FQ9nNNDc/CaCQEcvP+GV4rw==}
+ /@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.7(postcss@8.4.35):
+ resolution: {integrity: sha512-HBDAQw1K0NilcHGMUHv8jzf2mpOtcWTVKtuY3AeZ5TS1uyWWNVi5/yuA/tREPLU9WifNdqHQ+rfbsV/8zTIkTg==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/media-query-list-parser': 2.1.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/media-query-list-parser': 2.1.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
postcss: 8.4.35
dev: true
@@ -2000,22 +2000,22 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /@csstools/postcss-oklab-function@3.0.10(postcss@8.4.35):
- resolution: {integrity: sha512-s9trs1c+gUMtaTtwrrIpdVQkUbRuwi6bQ9rBHaqwt4kd3kEnEYfP85uLY1inFx6Rt8OM2XVg3PSYbfnFSAO51A==}
+ /@csstools/postcss-oklab-function@3.0.11(postcss@8.4.35):
+ resolution: {integrity: sha512-nIeOZqTFn/zJXSb70JwUcyUBb9658FED7saZlaZNEEhQ3GYxjRhdlV7hgflNi0FDdqNqaEeeI/B/BqnPG9+Q/Q==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
- /@csstools/postcss-progressive-custom-properties@3.1.0(postcss@8.4.35):
- resolution: {integrity: sha512-Mfb1T1BHa6pktLI+poMEHI7Q+VYvAsdwJZPFsSkIB2ZUsawCiPxXLw06BKSVPITxFlaY/FEUzfpyOTfX9YCE2w==}
+ /@csstools/postcss-progressive-custom-properties@3.1.1(postcss@8.4.35):
+ resolution: {integrity: sha512-cx/bZgj+MK8SpRZNTu2zGeVFMCQfhsaeuDhukAhfA53yykvIXaTIwLi5shW9hfkvPrkqBeFoiRAzq/qogxeHTA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
@@ -2024,16 +2024,16 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /@csstools/postcss-relative-color-syntax@2.0.10(postcss@8.4.35):
- resolution: {integrity: sha512-IkTIk9Eq2VegSN4lgsljGY8boyfX3l3Pw58e+R9oyPe/Ye7r3NwuiQ3w0nkXoQ+RC+d240V6n7eZme2mEPqQvg==}
+ /@csstools/postcss-relative-color-syntax@2.0.11(postcss@8.4.35):
+ resolution: {integrity: sha512-YmYGwGLoqZp71wXqjyFuG+JApL+CoZqUZ+MJshlokdqqryKX/zj/NrSrwMTAwB4xSx2DgHJUQK3iWumUse8rXw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
@@ -2048,15 +2048,15 @@ packages:
postcss-selector-parser: 6.0.15
dev: true
- /@csstools/postcss-stepped-value-functions@3.0.5(postcss@8.4.35):
- resolution: {integrity: sha512-B8K8RaTrYVZLxbNzVUvFO3SlCDJDaUTAO7KRth05fa7f01ufPvb6ztdBuxSoRwOtmNp8iROxPJHOemWo2kBBtA==}
+ /@csstools/postcss-stepped-value-functions@3.0.6(postcss@8.4.35):
+ resolution: {integrity: sha512-rnyp8tWRuBXERTHVdB5hjUlif5dQgPcyN+BX55wUnYpZ3LN9QPfK2Z3/HUZymwyou8Gg6vhd6X2W+g1pLq1jYg==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-calc': 1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-calc': 1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
postcss: 8.4.35
dev: true
@@ -2071,15 +2071,15 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /@csstools/postcss-trigonometric-functions@3.0.5(postcss@8.4.35):
- resolution: {integrity: sha512-RhBfQ0TsBudyPuoo8pXKdfQuUiQxMU/Sc5GyV57bWk93JbUHXq6b4CdPx+B/tHUeFKvocVJn/e2jbu96rh0d3Q==}
+ /@csstools/postcss-trigonometric-functions@3.0.6(postcss@8.4.35):
+ resolution: {integrity: sha512-i5Zd0bMJooZAn+ZcDmPij2WCkcOJJJ6opzK+QeDjxbMrYmoGQl0CY8FDHdeQyBF1Nly+Q0Fq3S7QfdNLKBBaCg==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-calc': 1.1.7(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/css-calc': 1.2.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
postcss: 8.4.35
dev: true
@@ -2163,7 +2163,7 @@ packages:
- supports-color
dev: true
- /@cypress/vue@6.0.0(cypress@13.6.6)(vue@3.4.21):
+ /@cypress/vue@6.0.0(cypress@13.7.0)(vue@3.4.21):
resolution: {integrity: sha512-KMfRw8y/kXn/RJqaDdFnYnW7YLk47313UE3Yip+sLDjILJ2kA0WEiEa6MYKe58v8TNRtwcRpUH5xAYVNs1N6/A==}
engines: {node: '>=8'}
peerDependencies:
@@ -2174,7 +2174,7 @@ packages:
'@cypress/webpack-dev-server':
optional: true
dependencies:
- cypress: 13.6.6
+ cypress: 13.7.0
vue: 3.4.21(typescript@5.4.2)
dev: true
@@ -2712,7 +2712,7 @@ packages:
resolution: {integrity: sha512-JoSGbsoo1/JY5TtTiMBUSPllIEJLvC6jHIGruvwPG/cJ3niqa3EyEMOsOWtcu+xjtx1uETgL9Yj5RJMJjC+OBA==}
dependencies:
'@histoire/controls': 0.17.9(vite@5.1.6)
- '@histoire/shared': 0.17.9(vite@5.1.6)
+ '@histoire/shared': 0.17.10(vite@5.1.6)
'@histoire/vendors': 0.17.8
'@types/flexsearch': 0.7.6
flexsearch: 0.7.21
@@ -2731,7 +2731,7 @@ packages:
'@codemirror/state': 6.3.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.22.1
- '@histoire/shared': 0.17.9(vite@5.1.6)
+ '@histoire/shared': 0.17.10(vite@5.1.6)
'@histoire/vendors': 0.17.8
transitivePeerDependencies:
- vite
@@ -2745,7 +2745,7 @@ packages:
capture-website: 2.4.1
defu: 6.1.3
fs-extra: 10.1.0
- histoire: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
+ histoire: 0.17.9(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)(vite@5.1.6)
pathe: 1.1.1
transitivePeerDependencies:
- bufferutil
@@ -2754,8 +2754,8 @@ packages:
- utf-8-validate
dev: true
- /@histoire/plugin-vue@0.17.12(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21):
- resolution: {integrity: sha512-mpx2uwHq/qemnX+ARQtDR3M9kIt1y4kBCmzBkOquhJTp61mtHMu4hZKSzzQpQWA2QxEyuuwpaNiU7Mlms13EaQ==}
+ /@histoire/plugin-vue@0.17.13(histoire@0.17.9)(vite@5.1.6)(vue@3.4.21):
+ resolution: {integrity: sha512-HyEvOr/3MehjLeMUs0ta4Z01I5yJ93gkJkE4S8ZY6mQYuDiFLJZgUEpK87XDAbI37fjQ7fV7EvXpnQENQhlmdA==}
peerDependencies:
histoire: ^0.17.9
vue: ^3.2.47
@@ -2765,7 +2765,7 @@ packages:
'@histoire/vendors': 0.17.8
change-case: 4.1.2
globby: 13.2.2
- histoire: 0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6)
+ histoire: 0.17.9(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)(vite@5.1.6)
launch-editor: 2.6.1
pathe: 1.1.1
vue: 3.4.21(typescript@5.4.2)
@@ -2784,7 +2784,7 @@ packages:
chokidar: 3.5.3
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
dev: true
/@histoire/shared@0.17.9(vite@5.1.6):
@@ -2798,7 +2798,7 @@ packages:
chokidar: 3.5.3
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
dev: true
/@histoire/vendors@0.17.8:
@@ -3801,7 +3801,7 @@ packages:
/@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
dev: true
/@types/har-format@1.2.10:
@@ -3825,7 +3825,7 @@ packages:
/@types/keyv@3.1.3:
resolution: {integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==}
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
dev: true
/@types/linkify-it@3.0.2:
@@ -3866,8 +3866,8 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
- /@types/node@20.11.26:
- resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==}
+ /@types/node@20.11.27:
+ resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -3894,13 +3894,13 @@ packages:
/@types/resolve@1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
dev: true
/@types/responselike@1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
dev: true
/@types/semver@7.5.0:
@@ -3949,7 +3949,7 @@ packages:
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
requiresBuild: true
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
dev: true
optional: true
@@ -4105,7 +4105,7 @@ packages:
regenerator-runtime: 0.14.1
systemjs: 6.14.3
terser: 5.24.0
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
transitivePeerDependencies:
- supports-color
dev: true
@@ -4117,7 +4117,7 @@ packages:
vite: ^5.0.0
vue: ^3.2.25
dependencies:
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
vue: 3.4.21(typescript@5.4.2)
dev: true
@@ -4290,18 +4290,11 @@ packages:
/@vue/shared@3.4.21:
resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==}
- /@vue/test-utils@2.4.4(vue@3.4.21):
- resolution: {integrity: sha512-8jkRxz8pNhClAf4Co4ZrpAoFISdvT3nuSkUlY6Ys6rmTpw3DMWG/X3mw3gQ7QJzgCZO9f+zuE2kW57fi09MW7Q==}
- peerDependencies:
- '@vue/server-renderer': ^3.0.1
- vue: ^3.0.1
- peerDependenciesMeta:
- '@vue/server-renderer':
- optional: true
+ /@vue/test-utils@2.4.5:
+ resolution: {integrity: sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==}
dependencies:
js-beautify: 1.14.9
- vue: 3.4.21(typescript@5.4.2)
- vue-component-type-helpers: 1.8.22
+ vue-component-type-helpers: 2.0.6
dev: true
/@vue/tsconfig@0.5.1:
@@ -5267,8 +5260,8 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- /cypress@13.6.6:
- resolution: {integrity: sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==}
+ /cypress@13.7.0:
+ resolution: {integrity: sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==}
engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
hasBin: true
requiresBuild: true
@@ -5310,7 +5303,7 @@ packages:
process: 0.11.10
proxy-from-env: 1.0.0
request-progress: 3.0.0
- semver: 7.5.4
+ semver: 7.6.0
supports-color: 8.1.1
tmp: 0.2.1
untildify: 4.0.0
@@ -6460,8 +6453,8 @@ packages:
strip-bom-string: 1.0.0
dev: true
- /happy-dom@13.8.2:
- resolution: {integrity: sha512-u9KxyeQNIzkJDR2iCitKeS5Uy0YUv5eOntpO8e7ZzbDVv4kP5Y77Zo2LnZitwMrss/1pY2Uc2e5qOVGkiKE5Gg==}
+ /happy-dom@13.8.4:
+ resolution: {integrity: sha512-FjLmsOMgwpX9gc00nz830RVGCG1V6Rj+AB4amdEAbKmbeIL3Ude1peC8bcTCzTvrtm2TFKimY3Ws6Xeh5Q1XhA==}
engines: {node: '>=16.0.0'}
dependencies:
entities: 4.5.0
@@ -6528,7 +6521,7 @@ packages:
engines: {node: '>=12.0.0'}
dev: false
- /histoire@0.17.9(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)(vite@5.1.6):
+ /histoire@0.17.9(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)(vite@5.1.6):
resolution: {integrity: sha512-z5Jb9QwbOw0TKvpkU0v7+CxJG6hIljIKMhWXzOfteteRZGDFElpTEwbr5/8EdPI6VTdF/k76fqZ07nmS9YdUvA==}
hasBin: true
peerDependencies:
@@ -6564,8 +6557,8 @@ packages:
sade: 1.8.1
shiki-es: 0.2.0
sirv: 2.0.3
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
- vite-node: 0.34.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
+ vite-node: 0.34.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- bufferutil
@@ -7011,7 +7004,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@@ -8115,16 +8108,16 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /postcss-color-functional-notation@6.0.5(postcss@8.4.35):
- resolution: {integrity: sha512-aTFsIy89ftjyclwUHRwvz1IxucLzVrzmmcXmtbPWT9GdyYeaJEKeAwbaZzOZn7AQlXg4xfwgkYhKsofC4aLIwg==}
+ /postcss-color-functional-notation@6.0.6(postcss@8.4.35):
+ resolution: {integrity: sha512-2GENDVgEk1dt+OdVhPO+zO4Dzj31Xs9EuKgQLbY9RSkKS3jUqnbTAh33bUhKce5JM1ZmsXm0azCb7Bh8j6W6Nw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
@@ -8151,42 +8144,42 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /postcss-custom-media@10.0.3(postcss@8.4.35):
- resolution: {integrity: sha512-wfJ9nKpLn/Qy7LASKu0Rj9Iq2uMzlRt27P4FAE1889IKRMdYUgy8SqvdXfAOs7LJLQX9Fjm0mZ+TSFphD/mKwA==}
+ /postcss-custom-media@10.0.4(postcss@8.4.35):
+ resolution: {integrity: sha512-Ubs7O3wj2prghaKRa68VHBvuy3KnTQ0zbGwqDYY1mntxJD0QL2AeiAy+AMfl3HBedTCVr2IcFNktwty9YpSskA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/cascade-layer-name-parser': 1.0.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/media-query-list-parser': 2.1.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
+ '@csstools/cascade-layer-name-parser': 1.0.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/media-query-list-parser': 2.1.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
postcss: 8.4.35
dev: true
- /postcss-custom-properties@13.3.5(postcss@8.4.35):
- resolution: {integrity: sha512-xHg8DTCMfN2nrqs2CQTF+0m5jgnzKL5zrW5Y05KF6xBRO0uDPxiplBm/xcr1o49SLbyJXkMuaRJKhRzkrquKnQ==}
+ /postcss-custom-properties@13.3.6(postcss@8.4.35):
+ resolution: {integrity: sha512-vVVIwQbJiIz+PBLMIWA6XMi53Zg66/f474KolA7x0Das6EwkATc/9ZvM6zZx2gs7ZhcgVHjmWBbHkK9FlCgLeA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/cascade-layer-name-parser': 1.0.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/cascade-layer-name-parser': 1.0.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
postcss-value-parser: 4.2.0
dev: true
- /postcss-custom-selectors@7.1.7(postcss@8.4.35):
- resolution: {integrity: sha512-N19MpExaR+hYTXU59VO02xE42zLoAUYSVcupwkKlWWLteOb+sWCWHw5FhV7u7gVLTzaGULy7nZP3DNTHgOZAPA==}
+ /postcss-custom-selectors@7.1.8(postcss@8.4.35):
+ resolution: {integrity: sha512-fqDkGSEsO7+oQaqdRdR8nwwqH+N2uk6LE/2g4myVJJYz/Ly418lHKEleKTdV/GzjBjFcG4n0dbfuH/Pd2BE8YA==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/cascade-layer-name-parser': 1.0.8(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
+ '@csstools/cascade-layer-name-parser': 1.0.9(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
postcss: 8.4.35
postcss-selector-parser: 6.0.15
dev: true
@@ -8201,13 +8194,13 @@ packages:
postcss-selector-parser: 6.0.15
dev: true
- /postcss-double-position-gradients@5.0.4(postcss@8.4.35):
- resolution: {integrity: sha512-xOH2QhazCPeYR+ziYaDcGlpo7Bpw8PVoggOFfU/xPkmBRUQH8MR2eWoPY1CZM93CB0WKs2mxq3ORo83QGIooLw==}
+ /postcss-double-position-gradients@5.0.5(postcss@8.4.35):
+ resolution: {integrity: sha512-26Tx4BfoxMNO9C89Nk56bfGv4jAwdDVgrQOyHZOP/6/D+xuOBf306KzTjHC2oBzaIIVtX+famOWHv4raxMjJMQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
postcss-value-parser: 4.2.0
@@ -8281,16 +8274,16 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /postcss-lab-function@6.0.10(postcss@8.4.35):
- resolution: {integrity: sha512-Csvw/CwwuwTojK2O3Ad0SvYKrfnAKy+uvT+1Fjk6igR+n8gHuJHIwdj1A2s46EZZojg3RkibdMBuv1vMvR6Sng==}
+ /postcss-lab-function@6.0.11(postcss@8.4.35):
+ resolution: {integrity: sha512-toTAozTlBBhqSynSJ32O6ssukZFphS58AAQcVqMA8kG/E04+v+e7E5OKRqq68M/VJaWIeMdpyeBEO51buMrdvw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
- '@csstools/css-color-parser': 1.5.2(@csstools/css-parser-algorithms@2.6.0)(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-parser-algorithms': 2.6.0(@csstools/css-tokenizer@2.2.3)
- '@csstools/css-tokenizer': 2.2.3
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
+ '@csstools/css-color-parser': 1.6.0(@csstools/css-parser-algorithms@2.6.1)(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4)
+ '@csstools/css-tokenizer': 2.2.4
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
'@csstools/utilities': 1.0.0(postcss@8.4.35)
postcss: 8.4.35
dev: true
@@ -8354,40 +8347,40 @@ packages:
postcss-value-parser: 4.2.0
dev: true
- /postcss-preset-env@9.5.0(postcss@8.4.35):
- resolution: {integrity: sha512-ZTrTWCSqKVYSABB1GerMBb6F8Uto5YWIq1nqi+TKOHPzrXMcyJNuJTc0v2lp5WjG4Sfvwdo7HF/7/3j7HskRog==}
+ /postcss-preset-env@9.5.1(postcss@8.4.35):
+ resolution: {integrity: sha512-m2biepZ2amqH/ygGRV+lQxnT9+AsYG2OScMwBRLa9YefDOXaCVKzsPtmnvdUG7QENdhAl9tE9nsHbYHVYsqJmQ==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
postcss: ^8.4
dependencies:
'@csstools/postcss-cascade-layers': 4.0.3(postcss@8.4.35)
- '@csstools/postcss-color-function': 3.0.10(postcss@8.4.35)
- '@csstools/postcss-color-mix-function': 2.0.10(postcss@8.4.35)
- '@csstools/postcss-exponential-functions': 1.0.4(postcss@8.4.35)
+ '@csstools/postcss-color-function': 3.0.11(postcss@8.4.35)
+ '@csstools/postcss-color-mix-function': 2.0.11(postcss@8.4.35)
+ '@csstools/postcss-exponential-functions': 1.0.5(postcss@8.4.35)
'@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.35)
- '@csstools/postcss-gamut-mapping': 1.0.3(postcss@8.4.35)
- '@csstools/postcss-gradients-interpolation-method': 4.0.11(postcss@8.4.35)
- '@csstools/postcss-hwb-function': 3.0.9(postcss@8.4.35)
- '@csstools/postcss-ic-unit': 3.0.4(postcss@8.4.35)
+ '@csstools/postcss-gamut-mapping': 1.0.4(postcss@8.4.35)
+ '@csstools/postcss-gradients-interpolation-method': 4.0.12(postcss@8.4.35)
+ '@csstools/postcss-hwb-function': 3.0.10(postcss@8.4.35)
+ '@csstools/postcss-ic-unit': 3.0.5(postcss@8.4.35)
'@csstools/postcss-initial': 1.0.1(postcss@8.4.35)
'@csstools/postcss-is-pseudo-class': 4.0.5(postcss@8.4.35)
- '@csstools/postcss-light-dark-function': 1.0.0(postcss@8.4.35)
+ '@csstools/postcss-light-dark-function': 1.0.1(postcss@8.4.35)
'@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.35)
'@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.35)
'@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.35)
'@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.35)
- '@csstools/postcss-logical-viewport-units': 2.0.6(postcss@8.4.35)
- '@csstools/postcss-media-minmax': 1.1.3(postcss@8.4.35)
- '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.6(postcss@8.4.35)
+ '@csstools/postcss-logical-viewport-units': 2.0.7(postcss@8.4.35)
+ '@csstools/postcss-media-minmax': 1.1.4(postcss@8.4.35)
+ '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.7(postcss@8.4.35)
'@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.35)
'@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.35)
- '@csstools/postcss-oklab-function': 3.0.10(postcss@8.4.35)
- '@csstools/postcss-progressive-custom-properties': 3.1.0(postcss@8.4.35)
- '@csstools/postcss-relative-color-syntax': 2.0.10(postcss@8.4.35)
+ '@csstools/postcss-oklab-function': 3.0.11(postcss@8.4.35)
+ '@csstools/postcss-progressive-custom-properties': 3.1.1(postcss@8.4.35)
+ '@csstools/postcss-relative-color-syntax': 2.0.11(postcss@8.4.35)
'@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.35)
- '@csstools/postcss-stepped-value-functions': 3.0.5(postcss@8.4.35)
+ '@csstools/postcss-stepped-value-functions': 3.0.6(postcss@8.4.35)
'@csstools/postcss-text-decoration-shorthand': 3.0.4(postcss@8.4.35)
- '@csstools/postcss-trigonometric-functions': 3.0.5(postcss@8.4.35)
+ '@csstools/postcss-trigonometric-functions': 3.0.6(postcss@8.4.35)
'@csstools/postcss-unset-value': 3.0.1(postcss@8.4.35)
autoprefixer: 10.4.18(postcss@8.4.35)
browserslist: 4.23.0
@@ -8398,20 +8391,20 @@ packages:
postcss: 8.4.35
postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.35)
postcss-clamp: 4.1.0(postcss@8.4.35)
- postcss-color-functional-notation: 6.0.5(postcss@8.4.35)
+ postcss-color-functional-notation: 6.0.6(postcss@8.4.35)
postcss-color-hex-alpha: 9.0.4(postcss@8.4.35)
postcss-color-rebeccapurple: 9.0.3(postcss@8.4.35)
- postcss-custom-media: 10.0.3(postcss@8.4.35)
- postcss-custom-properties: 13.3.5(postcss@8.4.35)
- postcss-custom-selectors: 7.1.7(postcss@8.4.35)
+ postcss-custom-media: 10.0.4(postcss@8.4.35)
+ postcss-custom-properties: 13.3.6(postcss@8.4.35)
+ postcss-custom-selectors: 7.1.8(postcss@8.4.35)
postcss-dir-pseudo-class: 8.0.1(postcss@8.4.35)
- postcss-double-position-gradients: 5.0.4(postcss@8.4.35)
+ postcss-double-position-gradients: 5.0.5(postcss@8.4.35)
postcss-focus-visible: 9.0.1(postcss@8.4.35)
postcss-focus-within: 8.0.1(postcss@8.4.35)
postcss-font-variant: 5.0.0(postcss@8.4.35)
postcss-gap-properties: 5.0.1(postcss@8.4.35)
postcss-image-set-function: 6.0.3(postcss@8.4.35)
- postcss-lab-function: 6.0.10(postcss@8.4.35)
+ postcss-lab-function: 6.0.11(postcss@8.4.35)
postcss-logical: 7.0.1(postcss@8.4.35)
postcss-nesting: 12.1.0(postcss@8.4.35)
postcss-opacity-percentage: 2.0.0(postcss@8.4.35)
@@ -9067,8 +9060,8 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
- /sass@1.71.1:
- resolution: {integrity: sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==}
+ /sass@1.72.0:
+ resolution: {integrity: sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
@@ -9955,7 +9948,7 @@ packages:
extsprintf: 1.3.0
dev: true
- /vite-node@0.34.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
+ /vite-node@0.34.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0):
resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -9965,7 +9958,7 @@ packages:
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -9977,7 +9970,7 @@ packages:
- terser
dev: true
- /vite-node@1.3.1(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
+ /vite-node@1.3.1(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0):
resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -9986,7 +9979,7 @@ packages:
debug: 4.3.4(supports-color@8.1.1)
pathe: 1.1.1
picocolors: 1.0.0
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -10005,7 +9998,7 @@ packages:
vite: ^3.0.0 || ^4.0.0
dependencies:
mime-types: 2.1.35
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
dev: true
/vite-plugin-pwa@0.19.2(vite@5.1.6)(workbox-build@7.0.0)(workbox-window@7.0.0):
@@ -10023,7 +10016,7 @@ packages:
debug: 4.3.4(supports-color@8.1.1)
fast-glob: 3.3.2
pretty-bytes: 6.1.1
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
workbox-build: 7.0.0(acorn@8.11.2)
workbox-window: 7.0.0
transitivePeerDependencies:
@@ -10037,7 +10030,7 @@ packages:
vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies:
'@sentry/cli': 2.19.1
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
transitivePeerDependencies:
- encoding
- supports-color
@@ -10052,7 +10045,7 @@ packages:
vue: 3.4.21(typescript@5.4.2)
dev: true
- /vite@5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0):
+ /vite@5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0):
resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -10080,17 +10073,17 @@ packages:
terser:
optional: true
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
esbuild: 0.19.12
postcss: 8.4.35
rollup: 4.13.0
- sass: 1.71.1
+ sass: 1.72.0
terser: 5.24.0
optionalDependencies:
fsevents: 2.3.3
dev: true
- /vitest@1.3.1(@types/node@20.11.26)(happy-dom@13.8.2)(sass@1.71.1)(terser@5.24.0):
+ /vitest@1.3.1(@types/node@20.11.27)(happy-dom@13.8.4)(sass@1.72.0)(terser@5.24.0):
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -10115,7 +10108,7 @@ packages:
jsdom:
optional: true
dependencies:
- '@types/node': 20.11.26
+ '@types/node': 20.11.27
'@vitest/expect': 1.3.1
'@vitest/runner': 1.3.1
'@vitest/snapshot': 1.3.1
@@ -10125,7 +10118,7 @@ packages:
chai: 4.3.10
debug: 4.3.4(supports-color@8.1.1)
execa: 8.0.1
- happy-dom: 13.8.2
+ happy-dom: 13.8.4
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.1
@@ -10134,8 +10127,8 @@ packages:
strip-literal: 2.0.0
tinybench: 2.5.1
tinypool: 0.8.2
- vite: 5.1.6(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
- vite-node: 1.3.1(@types/node@20.11.26)(sass@1.71.1)(terser@5.24.0)
+ vite: 5.1.6(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
+ vite-node: 1.3.1(@types/node@20.11.27)(sass@1.72.0)(terser@5.24.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@@ -10159,8 +10152,8 @@ packages:
vue: 3.4.21(typescript@5.4.2)
dev: false
- /vue-component-type-helpers@1.8.22:
- resolution: {integrity: sha512-LK3wJHs3vJxHG292C8cnsRusgyC5SEZDCzDCD01mdE/AoREFMl2tzLRuzwyuEsOIz13tqgBcnvysN3Lxsa14Fw==}
+ /vue-component-type-helpers@2.0.6:
+ resolution: {integrity: sha512-qdGXCtoBrwqk1BT6r2+1Wcvl583ZVkuSZ3or7Y1O2w5AvWtlvvxwjGhmz5DdPJS9xqRdDlgTJ/38ehWnEi0tFA==}
dev: true
/vue-demi@0.14.6(vue@3.4.21):
From 3ab22d8e062f2573abb22bb4fb59a9d1b2b01135 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Thu, 14 Mar 2024 08:32:55 +0100
Subject: [PATCH 52/66] chore(deps): update google.golang.org/protobuf from
1.32.0 to 1.33.0
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index bcb56100a..0c5bf3487 100644
--- a/go.mod
+++ b/go.mod
@@ -179,7 +179,7 @@ require (
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/protobuf v1.32.0 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
diff --git a/go.sum b/go.sum
index 5e6e8ac31..121d6b6e0 100644
--- a/go.sum
+++ b/go.sum
@@ -705,8 +705,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
From 161bb1b19258231fb6b89d49c883f0e0ee88d205 Mon Sep 17 00:00:00 2001
From: kolaente
Date: Thu, 14 Mar 2024 08:40:03 +0100
Subject: [PATCH 53/66] fix(filters): do not watch debounced
---
frontend/src/components/project/partials/filters.vue | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frontend/src/components/project/partials/filters.vue b/frontend/src/components/project/partials/filters.vue
index 14212ca20..58ea80970 100644
--- a/frontend/src/components/project/partials/filters.vue
+++ b/frontend/src/components/project/partials/filters.vue
@@ -48,8 +48,7 @@ export const ALPHABETICAL_SORT = 'title'